105 lines
4.6 KiB
Python
105 lines
4.6 KiB
Python
import json
|
||
import logging
|
||
import time
|
||
|
||
from google import genai
|
||
from google.genai import types
|
||
|
||
from app.config import settings
|
||
|
||
logger = logging.getLogger("app.services.ai_advisor")
|
||
|
||
SYSTEM_PROMPT = """
|
||
Ты — эксперт по 3D-печати из инженерных пластиков по технологии FDM.
|
||
Твоя задача — рекомендовать оптимальный материал для печати на основе описания задачи клиента.
|
||
|
||
Доступные материалы:
|
||
{materials_json}
|
||
|
||
Правила:
|
||
1. Всегда рекомендуй один основной материал и 1-2 альтернативы.
|
||
2. Учитывай: температурный режим, механические нагрузки, химическое воздействие, UV, влажность.
|
||
3. Если задача не подходит для FDM-печати (слишком мелкие детали, высокая точность) — честно скажи об этом.
|
||
4. Отвечай кратко, по делу, на русском языке.
|
||
5. Если клиент не указал критичные параметры — задай уточняющие вопросы.
|
||
|
||
Формат ответа — строго JSON:
|
||
{{
|
||
"recommended_material_id": <int>,
|
||
"recommended_material_name": "<str>",
|
||
"reasoning": "<обоснование на русском>",
|
||
"alternatives": [{{"material_id": <int>, "name": "<str>", "why": "<причина>"}}],
|
||
"questions": ["<вопрос, если нужна доп. информация>"]
|
||
}}
|
||
"""
|
||
|
||
|
||
async def get_material_recommendation(
|
||
task_description: str,
|
||
materials_data: list[dict],
|
||
budget_preference: str = "optimal",
|
||
file_info: dict | None = None,
|
||
) -> dict:
|
||
"""Get material recommendation from Google Gemini API."""
|
||
logger.info("=== AI Advisor request ===")
|
||
logger.info("Task: %s", task_description)
|
||
logger.info("Budget preference: %s", budget_preference)
|
||
logger.info("File info: %s", file_info)
|
||
logger.info("Materials count: %d", len(materials_data))
|
||
|
||
if not settings.GOOGLE_API_KEY:
|
||
logger.error("GOOGLE_API_KEY is not configured")
|
||
raise ValueError("GOOGLE_API_KEY not configured")
|
||
|
||
materials_json = json.dumps(materials_data, ensure_ascii=False, indent=2)
|
||
system = SYSTEM_PROMPT.format(materials_json=materials_json)
|
||
logger.debug("System prompt length: %d chars", len(system))
|
||
|
||
user_message = f"Описание задачи: {task_description}\nПредпочтение по бюджету: {budget_preference}"
|
||
if file_info:
|
||
user_message += f"\nИнформация о модели: {json.dumps(file_info, ensure_ascii=False)}"
|
||
logger.debug("User message: %s", user_message)
|
||
|
||
logger.info("Sending request to Gemini API (model: gemini-2.0-flash)...")
|
||
start_time = time.time()
|
||
|
||
client = genai.Client(api_key=settings.GOOGLE_API_KEY)
|
||
response = await client.aio.models.generate_content(
|
||
model="gemini-3-flash-preview",
|
||
contents=user_message,
|
||
config=types.GenerateContentConfig(
|
||
system_instruction=system,
|
||
max_output_tokens=1024,
|
||
temperature=0.3,
|
||
),
|
||
)
|
||
|
||
elapsed = time.time() - start_time
|
||
logger.info("Gemini API responded in %.2f seconds", elapsed)
|
||
|
||
response_text = response.text
|
||
logger.debug("Raw response (%d chars): %s", len(response_text), response_text[:500])
|
||
|
||
try:
|
||
result = json.loads(response_text)
|
||
logger.info("Response parsed as JSON successfully")
|
||
logger.info("Recommended material: id=%s, name=%s",
|
||
result.get("recommended_material_id"), result.get("recommended_material_name"))
|
||
logger.info("Alternatives: %d, Questions: %d",
|
||
len(result.get("alternatives", [])), len(result.get("questions", [])))
|
||
logger.info("=== AI Advisor complete ===")
|
||
return result
|
||
except json.JSONDecodeError:
|
||
logger.warning("Direct JSON parse failed, trying to extract JSON from response...")
|
||
start = response_text.find("{")
|
||
end = response_text.rfind("}") + 1
|
||
if start != -1 and end > start:
|
||
extracted = response_text[start:end]
|
||
logger.debug("Extracted JSON substring [%d:%d]: %s", start, end, extracted[:300])
|
||
result = json.loads(extracted)
|
||
logger.info("Extracted JSON parsed successfully")
|
||
logger.info("=== AI Advisor complete ===")
|
||
return result
|
||
logger.error("Failed to extract JSON from AI response: %s", response_text[:200])
|
||
raise ValueError("AI вернул невалидный JSON")
|