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()