init
This commit is contained in:
2
.env
2
.env
@@ -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
|
||||||
@@ -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 = ""
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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: '' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user