69 lines
3.1 KiB
Python
69 lines
3.1 KiB
Python
import json
|
|
import logging
|
|
import httpx
|
|
from langchain_openai import ChatOpenAI
|
|
from langchain_core.messages import SystemMessage, HumanMessage
|
|
from app.core.config import settings
|
|
from app.schemas.code_output import ProjectCodeOutput, GeneratedFile
|
|
from app.llm.prompts import DEV_AGENT_PROMPT
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Clients HTTPX configurés pour ignorer les blocages de certificats/révocation du lab
|
|
sync_client = httpx.Client(verify=False)
|
|
async_client = httpx.AsyncClient(verify=False)
|
|
|
|
async def run_dev_agent(spec: dict, qa_feedback: list = None) -> dict:
|
|
"""
|
|
Agent Dev : prend un état/spec validé, génère l'arborescence et le code,
|
|
et valide techniquement la sortie avant de la transmettre à la QA.
|
|
"""
|
|
logger.info(f"[Dev Agent] Début de la génération pour : {spec.get('title', 'Sans titre')}")
|
|
|
|
llm = ChatOpenAI(
|
|
base_url=settings.llm_base_url,
|
|
api_key=settings.llm_api_key,
|
|
model=settings.llm_model_dev,
|
|
temperature=0.2,
|
|
max_retries=2,
|
|
http_client=sync_client,
|
|
http_async_client=async_client,
|
|
model_kwargs={"response_format": {"type": "json_object"}}
|
|
)
|
|
structured_llm = llm.with_structured_output(ProjectCodeOutput, strict=True)
|
|
|
|
messages = [
|
|
SystemMessage(content=DEV_AGENT_PROMPT),
|
|
]
|
|
|
|
user_content = f"CAHIER DES CHARGES (ProjectSpec) :\n{json.dumps(spec, indent=2, ensure_ascii=False)}\n\n"
|
|
if qa_feedback:
|
|
user_content += f"⚠️ RETOURS DE VALIDATION QA (Corrections à appliquer impérativement) :\n{json.dumps(qa_feedback, indent=2, ensure_ascii=False)}\n\n"
|
|
|
|
user_content += "Génère maintenant le JSON complet contenant l'arborescence ('tree') et tous les fichiers ('files') décrits."
|
|
messages.append(HumanMessage(content=user_content))
|
|
|
|
try:
|
|
validated_code = await structured_llm.ainvoke(messages)
|
|
logger.info(f"[Dev Agent] ✅ Code généré et validé avec succès ({len(validated_code.files)} fichiers)")
|
|
return validated_code.model_dump()
|
|
|
|
except Exception as e:
|
|
logger.error(f"[Dev Agent] ❌ Échec de la génération/validation : {type(e).__name__}: {str(e)}")
|
|
return _generate_fallback_code_output(spec)
|
|
|
|
def _generate_fallback_code_output(spec: dict) -> dict:
|
|
"""Génère un livrable minimal de secours en cas de crash du LLM."""
|
|
logger.warning("[Dev Agent] Génération du package de secours (Fallback)")
|
|
title = spec.get("title", "automation_script")
|
|
|
|
fallback = ProjectCodeOutput(
|
|
spec_title=title,
|
|
tree=["main.py", "README.md", "requirements.txt"],
|
|
files=[
|
|
GeneratedFile(path="main.py", content="import logging\nlogging.basicConfig(level=logging.INFO)\n\ndef main():\n logging.error('Le Dev Agent a rencontré une erreur de génération.')\n\nif __name__ == '__main__':\n main()"),
|
|
GeneratedFile(path="README.md", content=f"# {title}\nGénération en mode fallback suite à une erreur technique."),
|
|
GeneratedFile(path="requirements.txt", content="# Aucune dépendance externe définie (Fallback)\n")
|
|
]
|
|
)
|
|
return fallback.model_dump() |