120 lines
4.6 KiB
Python
120 lines
4.6 KiB
Python
import logging
|
|
import httpx
|
|
from langchain_openai import ChatOpenAI
|
|
from langchain_core.messages import SystemMessage, HumanMessage
|
|
from app.schemas.spec import ProjectSpec
|
|
from app.llm.prompts import PM_AGENT_PROMPT
|
|
from app.core.config import settings
|
|
|
|
logger = logging.getLogger(__name__)
|
|
sync_client = httpx.Client(verify=False)
|
|
async_client = httpx.AsyncClient(verify=False)
|
|
|
|
async def run_pm_agent(user_input: str, history: list | None = None) -> ProjectSpec:
|
|
"""Agent PM qui remplit ProjectSpec en analysant le besoin utilisateur."""
|
|
|
|
llm = ChatOpenAI(
|
|
base_url=settings.llm_base_url,
|
|
api_key=settings.llm_api_key,
|
|
model=settings.llm_model,
|
|
temperature=0.3,
|
|
max_retries=2,
|
|
http_client=sync_client,
|
|
http_async_client=async_client,
|
|
)
|
|
structured_llm = llm.with_structured_output(ProjectSpec, strict=True)
|
|
|
|
messages = [
|
|
SystemMessage(content=PM_AGENT_PROMPT),
|
|
]
|
|
|
|
if history:
|
|
messages.extend(history)
|
|
|
|
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.
|
|
|
|
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.
|
|
Réponds UNIQUEMENT avec du JSON valide. Aucun texte avant le JSON, aucun texte après le JSON."""
|
|
))
|
|
|
|
try:
|
|
project_spec = await structured_llm.ainvoke(messages)
|
|
logger.info(f"[PM Agent] ✅ Tentative réussie (Structured Output)")
|
|
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}")
|
|
|
|
return ProjectSpec(
|
|
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 ?"
|
|
) |