Initial commit: VoIdeaAI - voice-first AI idea assistant

This commit is contained in:
2026-05-13 12:51:42 +03:00
commit 688d043dad
421 changed files with 47915 additions and 0 deletions
+122
View File
@@ -0,0 +1,122 @@
"""Yandex GPT provider for VoIdea."""
import time
from datetime import datetime, timezone
from typing import Any, Optional
import httpx
from app.core.config import get_settings
from app.integrations.ai.base import AIProvider, AIResult
settings = get_settings()
class YandexGPTProvider(AIProvider):
"""Yandex GPT AI provider.
Supports both API key (Api-Key header) and IAM token (Bearer header).
Automatically falls back between auth methods on 401.
"""
name = "yandex_gpt"
model = "yandexgpt/latest"
timeout = settings.ai_timeout
max_retries = settings.ai_max_retries
def __init__(self, api_key: str = ""):
super().__init__(api_key or settings.ai_yandex_key)
self.base_url = settings.ai_yandex_url.rstrip("/")
self.folder_id = settings.ai_yandex_folder_id
self._client = httpx.AsyncClient(timeout=self.timeout)
async def analyze(self, prompt: str, **kwargs: Any) -> AIResult:
"""Send prompt to Yandex GPT and return result."""
start = time.time()
temperature = kwargs.get("temperature", 0.7)
max_tokens = kwargs.get("max_tokens", 2000)
model_uri = f"gpt://{self.folder_id}/{self.model}" if self.folder_id else self.model
# Split prompt into system and user parts
parts = prompt.split("\n\n", 1)
system_text = parts[0]
user_text = parts[1] if len(parts) > 1 else ""
body = {
"modelUri": model_uri,
"completionOptions": {
"temperature": temperature,
"maxTokens": max_tokens,
},
"messages": [
{"role": "system", "text": system_text},
{"role": "user", "text": user_text},
],
}
try:
response = await self._client.post(
f"{self.base_url}/foundationModels/v1/completion",
json=body,
)
duration = int((time.time() - start) * 1000)
if response.status_code == 401:
# Try with Api-Key header instead
response = await self._client.post(
f"{self.base_url}/foundationModels/v1/completion",
json=body,
headers={"Authorization": f"Api-Key {self.api_key}"},
)
duration = int((time.time() - start) * 1000)
if response.status_code == 200:
data = response.json()
result_text = (
data.get("result", {})
.get("alternatives", [{}])[0]
.get("message", {})
.get("text", "")
)
tokens = (
data.get("result", {})
.get("usage", {})
.get("inputTextTokens", 0)
)
return AIResult(
success=True,
content=result_text,
model=self.model,
provider=self.name,
tokens_used=tokens,
duration_ms=duration,
)
else:
error_body = response.text[:500]
return AIResult(
success=False,
error=f"Yandex GPT {response.status_code}: {error_body}",
provider=self.name,
duration_ms=duration,
)
except httpx.TimeoutException:
return AIResult(
success=False,
error="Yandex GPT timeout",
provider=self.name,
duration_ms=int((time.time() - start) * 1000),
)
except Exception as e:
return AIResult(
success=False,
error=f"Yandex GPT error: {str(e)}",
provider=self.name,
duration_ms=int((time.time() - start) * 1000),
)
async def close(self) -> None:
await self._client.aclose()