Files
ARC/backend/app/agents/pm_agent.py

120 lines
4.5 KiB
Python
Raw Normal View History

2026-06-16 11:27:41 +02:00
import logging
2026-06-17 10:18:55 +02:00
import httpx
2026-06-16 11:27:41 +02:00
from langchain_openai import ChatOpenAI
2026-06-17 10:18:55 +02:00
from langchain_core.messages import SystemMessage, HumanMessage
2026-06-12 18:16:58 +02:00
from app.schemas.spec import ProjectSpec
2026-06-16 11:27:41 +02:00
from app.llm.prompts import PM_AGENT_PROMPT
from app.core.config import settings
2026-06-12 18:16:58 +02:00
2026-06-16 11:27:41 +02:00
logger = logging.getLogger(__name__)
2026-06-17 10:18:55 +02:00
sync_client = httpx.Client(verify=False)
async_client = httpx.AsyncClient(verify=False)
2026-06-12 18:16:58 +02:00
2026-06-16 11:27:41 +02:00
async def run_pm_agent(user_input: str, history: list | None = None) -> ProjectSpec:
"""Agent PM qui remplit ProjectSpec en analysant le besoin utilisateur."""
2026-06-17 10:18:55 +02:00
2026-06-16 11:27:41 +02:00
llm = ChatOpenAI(
base_url=settings.llm_base_url,
api_key=settings.llm_api_key,
2026-06-17 10:18:55 +02:00
model=settings.llm_model,
2026-06-16 11:27:41 +02:00
temperature=0.3,
2026-06-17 10:18:55 +02:00
max_retries=2,
http_client=sync_client,
http_async_client=async_client,
2026-06-16 11:27:41 +02:00
)
2026-06-17 10:18:55 +02:00
structured_llm = llm.with_structured_output(ProjectSpec, strict=True)
2026-06-16 11:27:41 +02:00
messages = [
2026-06-17 10:18:55 +02:00
SystemMessage(content=PM_AGENT_PROMPT),
2026-06-16 11:27:41 +02:00
]
if history:
messages.extend(history)
2026-06-17 10:18:55 +02:00
messages.append(HumanMessage(
content=f"""En te basant sur TOUT l'historique de notre conversation et sur cette dernière précision, génère le JSON ProjectSpec valide et complètement rempli.
2026-06-16 11:27:41 +02:00
DERNIÈRE PRÉCISION / INPUT UTILISATEUR :
{user_input}
IMPORTANT : Tu dois impérativement combiner les contraintes énoncées précédemment (comme le langage de programmation choisi, l'OS, etc.) avec cette nouvelle information pour enrichir la spécification existante.
"""
2026-06-17 10:18:55 +02:00
))
2026-06-16 11:27:41 +02:00
try:
2026-06-17 10:18:55 +02:00
project_spec = await structured_llm.ainvoke(messages)
logger.info(f"[PM Agent] ✅ Tentative réussie (Structured Output)")
2026-06-16 11:27:41 +02:00
project_spec = _validate_and_sanitize(project_spec, user_input)
return project_spec
except Exception as e2:
logger.error(f"[PM Agent] ❌ Tentative échouée: {type(e2).__name__}: {str(e2)}")
logger.info("[PM Agent] Fallback: Génération automatique du spec")
return _generate_fallback_spec(user_input)
def _validate_and_sanitize(spec: ProjectSpec, user_input: str) -> ProjectSpec:
"""
Fait confiance au LLM pour la logique métier.
Se contente de s'assurer de la cohérence technique minimale.
"""
logger.debug(f"[Sanitize] Validation du spec généré pour : {spec.title}")
# 1. Sécurité minimale sur les chaînes de texte vides
if not spec.title or spec.title.lower() in ["null", "none", ""]:
spec.title = "Automation Script"
if not spec.description:
spec.description = f"Script d'automatisation basé sur : {user_input[:50]}..."
# 2. Gestion de la complétude :
if not spec.is_complete or (spec.clarifying_question and spec.clarifying_question.strip()):
spec.is_complete = False
if not spec.clarifying_question:
spec.clarifying_question = "Pourriez-vous apporter plus de précisions sur les actions que doit effectuer ce script ?"
logger.info(f"[Sanitize] Le besoin est qualifié d'INCOMPLET par l'agent PM.")
else:
spec.is_complete = True
spec.clarifying_question = None
logger.info(f"[Sanitize] Le besoin est qualifié de COMPLET par l'agent PM.")
return spec
def _generate_fallback_spec(user_input: str) -> ProjectSpec:
"""Génère un ProjectSpec par défaut en cas d'erreur complète."""
logger.warning(f"[Fallback] Génération d'un spec par défaut pour: {user_input}")
2026-06-12 18:16:58 +02:00
return ProjectSpec(
2026-06-16 11:27:41 +02:00
title="Automation Script",
description=f"Automatisation du besoin utilisateur: {user_input}",
requirements=[
f"Exécuter l'action demandée: {user_input}",
"Gérer les erreurs",
"Générer des logs"
],
constraints=["PEP 8 strict"],
language="Python",
target_stack="",
io_config={
"has_inputs": False,
"input_type": "none",
"input_paths_or_sources": [],
"has_outputs": False,
"output_type": "log_only",
"output_formats": []
},
auth_config={
"requires_auth": False,
"auth_method": "none",
"target_tools_and_apis": []
},
env_config={
"target_os": "cross_platform",
"language_version": "3.11",
"critical_dependencies": ["logging"]
},
error_handling_strategy="log_and_continue",
is_complete=False,
clarifying_question="Pourriez-vous donner plus de détails sur votre demande ?"
2026-06-12 18:16:58 +02:00
)