This commit is contained in:
xds
2026-03-22 22:31:11 +03:00
parent d9dad0f7d9
commit 9e630fb5d2
4 changed files with 30 additions and 18 deletions

2
.env
View File

@@ -11,3 +11,5 @@ MINIO_BUCKET=filam3d
MINIO_SECURE=false MINIO_SECURE=false
AI_PROXY_URL=http://82.22.174.14:8001 AI_PROXY_URL=http://82.22.174.14:8001
AI_PROXY_SALT=AbVJUkwTPcUWJWhPzmjXb5p4SYyKmYC5m1uVW7Dhi7o AI_PROXY_SALT=AbVJUkwTPcUWJWhPzmjXb5p4SYyKmYC5m1uVW7Dhi7o
QWEN_API_KEY=sk-991942d15b424cc89513498bb2946045
QWEN_MODEL=qwen3.5-plus

View File

@@ -4,6 +4,9 @@ from pydantic_settings import BaseSettings
class Settings(BaseSettings): class Settings(BaseSettings):
DATABASE_URL: str = "postgresql+asyncpg://print3d:P3D_PASSWORD@31.59.58.220:5432/print3d" DATABASE_URL: str = "postgresql+asyncpg://print3d:P3D_PASSWORD@31.59.58.220:5432/print3d"
GOOGLE_API_KEY: str = "" GOOGLE_API_KEY: str = ""
QWEN_API_KEY: str = ""
QWEN_MODEL: str = "qwen-plus"
QWEN_BASE_URL: str = "https://dashscope.aliyuncs.com/compatible-mode/v1"
AI_PROXY_URL: str = "http://82.22.174.14:8001" AI_PROXY_URL: str = "http://82.22.174.14:8001"
AI_PROXY_SALT: str = "change_me_in_production" AI_PROXY_SALT: str = "change_me_in_production"
TELEGRAM_BOT_TOKEN: str = "" TELEGRAM_BOT_TOKEN: str = ""

View File

@@ -75,25 +75,32 @@ async def _call_via_proxy(proxy_url: str, proxy_salt: str, messages: list[dict])
async def _call_direct(api_key: str, system: str, user_message: str) -> str: async def _call_direct(api_key: str, system: str, user_message: str) -> str:
"""Call Google GenAI directly via REST (no SDK dependency).""" """Call Qwen via OpenAI-compatible DashScope API."""
url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key={api_key}" url = f"{settings.QWEN_BASE_URL.rstrip('/')}/chat/completions"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
payload = { payload = {
"system_instruction": {"parts": [{"text": system}]}, "model": settings.QWEN_MODEL,
"contents": [{"role": "user", "parts": [{"text": user_message}]}], "messages": [
"generationConfig": {"temperature": 0.3, "maxOutputTokens": 1024}, {"role": "system", "content": system},
{"role": "user", "content": user_message},
],
"temperature": 0.3,
"max_tokens": 1024,
} }
async with httpx.AsyncClient(timeout=60.0) as client: async with httpx.AsyncClient(timeout=60.0) as client:
resp = await client.post(url, json=payload) resp = await client.post(url, json=payload, headers=headers)
if resp.status_code != 200: if resp.status_code != 200:
logger.error("Google API error %d: %s", resp.status_code, resp.text[:500]) logger.error("Qwen API error %d: %s", resp.status_code, resp.text[:500])
raise ValueError(f"Google API ошибка: {resp.status_code}") raise ValueError(f"Qwen API ошибка: {resp.status_code}")
data = resp.json() data = resp.json()
candidates = data.get("candidates", []) choices = data.get("choices", [])
if not candidates: if not choices:
raise ValueError("Google API вернул пустой ответ") raise ValueError("Qwen API вернул пустой ответ")
parts = candidates[0].get("content", {}).get("parts", []) return choices[0].get("message", {}).get("content", "")
return parts[0].get("text", "") if parts else ""
def _parse_json_response(text: str) -> dict: def _parse_json_response(text: str) -> dict:
@@ -122,7 +129,7 @@ async def get_material_recommendation(
use_proxy = True use_proxy = True
proxy_url = settings.AI_PROXY_URL proxy_url = settings.AI_PROXY_URL
proxy_salt = settings.AI_PROXY_SALT proxy_salt = settings.AI_PROXY_SALT
direct_api_key = settings.GOOGLE_API_KEY direct_api_key = settings.QWEN_API_KEY
if db: if db:
use_proxy_str = await _get_setting(db, "ai_use_proxy", "true") use_proxy_str = await _get_setting(db, "ai_use_proxy", "true")
@@ -153,9 +160,9 @@ async def get_material_recommendation(
messages = [{"role": "user", "content": system + "\n\n" + user_message}] messages = [{"role": "user", "content": system + "\n\n" + user_message}]
response_text = await _call_via_proxy(proxy_url, proxy_salt, messages) response_text = await _call_via_proxy(proxy_url, proxy_salt, messages)
else: else:
logger.info("Mode: DIRECT (Google API)") logger.info("Mode: DIRECT (Qwen API, model=%s)", settings.QWEN_MODEL)
if not direct_api_key: if not direct_api_key:
raise ValueError("Google API Key не настроен") raise ValueError("Qwen API Key не настроен (QWEN_API_KEY)")
response_text = await _call_direct(direct_api_key, system, user_message) response_text = await _call_direct(direct_api_key, system, user_message)
elapsed = time.time() - start_time elapsed = time.time() - start_time

View File

@@ -116,7 +116,7 @@ const settingsGroups = [
{ key: 'ai_use_proxy', label: 'Использовать AI-прокси (true/false)', placeholder: 'true' }, { key: 'ai_use_proxy', label: 'Использовать AI-прокси (true/false)', placeholder: 'true' },
{ key: 'ai_proxy_url', label: 'URL прокси', placeholder: 'http://82.22.174.14:8001' }, { key: 'ai_proxy_url', label: 'URL прокси', placeholder: 'http://82.22.174.14:8001' },
{ key: 'ai_proxy_salt', label: 'Секретная соль прокси', placeholder: '' }, { key: 'ai_proxy_salt', label: 'Секретная соль прокси', placeholder: '' },
{ key: 'ai_direct_api_key', label: 'Google API Key (прямое подключение)', placeholder: '' }, { key: 'ai_direct_api_key', label: 'Qwen API Key (DashScope)', placeholder: '' },
], ],
}, },
{ {