Etape 2 fini
This commit is contained in:
@@ -1,14 +1,69 @@
|
|||||||
|
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:
|
async def run_dev_agent(spec: dict, qa_feedback: list = None) -> dict:
|
||||||
"""
|
"""
|
||||||
Agent Dev minimal :
|
Agent Dev : prend un état/spec validé, génère l'arborescence et le code,
|
||||||
- retourne une pseudo arborescence + un code exemple
|
et valide techniquement la sortie avant de la transmettre à la QA.
|
||||||
"""
|
"""
|
||||||
return {
|
logger.info(f"[Dev Agent] Début de la génération pour : {spec.get('title', 'Sans titre')}")
|
||||||
"tree": [
|
|
||||||
"main.py",
|
llm = ChatOpenAI(
|
||||||
"README.md",
|
base_url=settings.llm_base_url,
|
||||||
"app/__init__.py",
|
api_key=settings.llm_api_key,
|
||||||
],
|
model=settings.llm_model_dev,
|
||||||
"code": 'print("Hello from ARC generated project")',
|
temperature=0.2,
|
||||||
"spec_title": spec.get("title"),
|
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()
|
||||||
@@ -1,52 +1,49 @@
|
|||||||
#pm_agent.py
|
|
||||||
import json
|
|
||||||
import re
|
|
||||||
import logging
|
import logging
|
||||||
|
import httpx
|
||||||
from langchain_openai import ChatOpenAI
|
from langchain_openai import ChatOpenAI
|
||||||
|
from langchain_core.messages import SystemMessage, HumanMessage
|
||||||
from app.schemas.spec import ProjectSpec
|
from app.schemas.spec import ProjectSpec
|
||||||
from app.llm.prompts import PM_AGENT_PROMPT
|
from app.llm.prompts import PM_AGENT_PROMPT
|
||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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:
|
async def run_pm_agent(user_input: str, history: list | None = None) -> ProjectSpec:
|
||||||
"""Agent PM qui remplit ProjectSpec en analysant le besoin utilisateur."""
|
"""Agent PM qui remplit ProjectSpec en analysant le besoin utilisateur."""
|
||||||
|
|
||||||
llm = ChatOpenAI(
|
llm = ChatOpenAI(
|
||||||
base_url=settings.llm_base_url,
|
base_url=settings.llm_base_url,
|
||||||
api_key=settings.llm_api_key,
|
api_key=settings.llm_api_key,
|
||||||
model="local-model",
|
model=settings.llm_model,
|
||||||
temperature=0.3,
|
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 = [
|
messages = [
|
||||||
{"role": "system", "content": PM_AGENT_PROMPT},
|
SystemMessage(content=PM_AGENT_PROMPT),
|
||||||
]
|
]
|
||||||
|
|
||||||
if history:
|
if history:
|
||||||
messages.extend(history)
|
messages.extend(history)
|
||||||
|
|
||||||
messages.append({
|
messages.append(HumanMessage(
|
||||||
"role": "user",
|
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.
|
||||||
"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 :
|
DERNIÈRE PRÉCISION / INPUT UTILISATEUR :
|
||||||
{user_input}
|
{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.
|
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."""
|
Réponds UNIQUEMENT avec du JSON valide. Aucun texte avant le JSON, aucun texte après le JSON."""
|
||||||
})
|
))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = await llm.ainvoke(messages)
|
project_spec = await structured_llm.ainvoke(messages)
|
||||||
raw_response = response.content if hasattr(response, 'content') else str(response)
|
logger.info(f"[PM Agent] ✅ Tentative réussie (Structured Output)")
|
||||||
logger.debug(f"[PM Agent] Réponse brute: {raw_response}")
|
|
||||||
|
|
||||||
parsed_spec = _extract_json_robust(raw_response)
|
|
||||||
|
|
||||||
project_spec = ProjectSpec(**parsed_spec)
|
|
||||||
logger.info(f"[PM Agent] ✅ Tentative réussie (parsing JSON manuel)")
|
|
||||||
|
|
||||||
project_spec = _validate_and_sanitize(project_spec, user_input)
|
project_spec = _validate_and_sanitize(project_spec, user_input)
|
||||||
return project_spec
|
return project_spec
|
||||||
|
|
||||||
@@ -56,62 +53,6 @@ Réponds UNIQUEMENT avec du JSON valide. Aucun texte avant le JSON, aucun texte
|
|||||||
|
|
||||||
return _generate_fallback_spec(user_input)
|
return _generate_fallback_spec(user_input)
|
||||||
|
|
||||||
|
|
||||||
def _extract_json_robust(response_text: str) -> dict:
|
|
||||||
"""
|
|
||||||
Extrait et parse le JSON depuis la réponse du LLM.
|
|
||||||
Robuste pour llama.cpp qui ajoute souvent du texte avant/après le JSON.
|
|
||||||
"""
|
|
||||||
|
|
||||||
logger.debug(f"[JSON Extract] Réponse brute: {response_text}")
|
|
||||||
|
|
||||||
# Stratégie 1: Chercher un bloc JSON entre ```json et ```
|
|
||||||
json_match = re.search(r'```json\s*(.*?)\s*```', response_text, re.DOTALL)
|
|
||||||
if json_match:
|
|
||||||
json_str = json_match.group(1).strip()
|
|
||||||
logger.debug(f"[JSON Extract] ✓ Trouvé via ```json block")
|
|
||||||
return json.loads(json_str)
|
|
||||||
|
|
||||||
# Stratégie 2: Chercher un bloc JSON entre ``` et ``` (sans spécifier json)
|
|
||||||
json_match = re.search(r'```\s*(.*?)\s*```', response_text, re.DOTALL)
|
|
||||||
if json_match:
|
|
||||||
json_str = json_match.group(1).strip()
|
|
||||||
logger.debug(f"[JSON Extract] ✓ Trouvé via ``` block")
|
|
||||||
try:
|
|
||||||
return json.loads(json_str)
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
logger.debug(f"[JSON Extract] ✗ Code block trouvé mais pas JSON valide")
|
|
||||||
|
|
||||||
# Stratégie 3: Chercher directement un objet JSON { ... } en étant très prudent
|
|
||||||
first_brace = response_text.find('{')
|
|
||||||
last_brace = response_text.rfind('}')
|
|
||||||
|
|
||||||
if first_brace != -1 and last_brace != -1 and last_brace > first_brace:
|
|
||||||
json_str = response_text[first_brace:last_brace+1].strip()
|
|
||||||
|
|
||||||
logger.debug(f"[JSON Extract] Tentative extraction (longueur: {len(json_str)})")
|
|
||||||
|
|
||||||
try:
|
|
||||||
parsed = json.loads(json_str)
|
|
||||||
logger.debug(f"[JSON Extract] ✓ Trouvé via extraction")
|
|
||||||
return parsed
|
|
||||||
except json.JSONDecodeError as e:
|
|
||||||
logger.debug(f"[JSON Extract] ✗ Extraction invalide: {e}")
|
|
||||||
|
|
||||||
# Stratégie 4: Parser tout le texte comme JSON (dernier recours)
|
|
||||||
try:
|
|
||||||
parsed = json.loads(response_text.strip())
|
|
||||||
logger.debug(f"[JSON Extract] ✓ Trouvé via parsing direct")
|
|
||||||
return parsed
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
logger.debug(f"[JSON Extract] ✗ Parsing direct échoué")
|
|
||||||
|
|
||||||
# Aucune stratégie n'a fonctionné
|
|
||||||
logger.error(f"[JSON Extract] ❌ Impossible d'extraire JSON valide")
|
|
||||||
logger.error(f"[JSON Extract] Réponse brute complète:\n{response_text}")
|
|
||||||
raise ValueError(f"Impossible d'extraire un JSON valide de la réponse du LLM.\nRéponse: {response_text}")
|
|
||||||
|
|
||||||
|
|
||||||
def _validate_and_sanitize(spec: ProjectSpec, user_input: str) -> ProjectSpec:
|
def _validate_and_sanitize(spec: ProjectSpec, user_input: str) -> ProjectSpec:
|
||||||
"""
|
"""
|
||||||
Fait confiance au LLM pour la logique métier.
|
Fait confiance au LLM pour la logique métier.
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
|
|
||||||
|
|
||||||
class Settings(BaseSettings):
|
class Settings(BaseSettings):
|
||||||
app_name: str = "ARC Backend"
|
app_name: str = "ARC Backend"
|
||||||
app_env: str = "dev"
|
app_env: str = "dev"
|
||||||
@@ -12,14 +11,18 @@ class Settings(BaseSettings):
|
|||||||
|
|
||||||
redis_url: str = "redis://localhost:6379/0"
|
redis_url: str = "redis://localhost:6379/0"
|
||||||
|
|
||||||
llm_base_url: str = "http://gemma-server:8080/v1"
|
llm_base_url: str
|
||||||
llm_api_key: str = "llama-cpp-local"
|
llm_api_key: str
|
||||||
# llm_model: str = "gemma-4-E4B-it-UD-Q4_K_XL.gguf"
|
llm_model: str
|
||||||
|
llm_model_dev: str
|
||||||
|
|
||||||
embedding_base_url: str = "http://localhost:8002/v1"
|
embedding_base_url: str = "http://localhost:8002/v1"
|
||||||
embedding_model: str = "snowflake-arctic-embed-m-v1.5"
|
embedding_model: str = "snowflake-arctic-embed-m-v1.5"
|
||||||
|
|
||||||
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")
|
model_config = SettingsConfigDict(
|
||||||
|
env_file=".env",
|
||||||
|
env_file_encoding="utf-8",
|
||||||
|
extra="ignore"
|
||||||
|
)
|
||||||
|
|
||||||
settings = Settings()
|
settings = Settings()
|
||||||
@@ -267,39 +267,86 @@ CRITICAL: Si la demande de l'utilisateur est trop courte, trop vague (ex: 'Je ve
|
|||||||
Ci-dessous la demande utilisateur brute. Tu dois transformer cela en JSON ProjectSpec valide, rempli et cohérent.
|
Ci-dessous la demande utilisateur brute. Tu dois transformer cela en JSON ProjectSpec valide, rempli et cohérent.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# DEV_AGENT_PROMPT = """Vous êtes un Product Manager & Architecte Logiciel Senior spécialisé dans la conception d'outils d'automatisation et de scripts robustes en Python.
|
|
||||||
|
|
||||||
# Votre objectif est double :
|
# ==============================================================================================================================================
|
||||||
# 1. Agir comme garde-fou technique en imposant des standards de développement d'entreprise drastiques.
|
# **********************************************************************************************************************************************
|
||||||
# 2. Fournir un code Python prêt à l'emploi, structuré et commenté, accompagné d'un fichier 'requirements.txt' et d'un 'README.md' de qualité professionnelle.
|
# ==============================================================================================================================================
|
||||||
|
|
||||||
# ---
|
|
||||||
|
|
||||||
# ### DIRECTIVES DE CONCEPTION ET STANDARDS IMPOSÉS :
|
DEV_AGENT_PROMPT = """Vous êtes un Développeur Senior & Architecte Logiciel Multi-langages, spécialisé dans l'écriture d'outils d'automatisation, de scripts et d'applications modulaires hautement sécurisées.
|
||||||
# Vous devez concevoir la solution technique en respectant les règles suivantes :
|
|
||||||
|
|
||||||
# 1. ARCHITECTURE ET FORMAT DES LIVRABLES :
|
Votre objectif est de générer l'intégralité du code source d'un projet basé sur les spécifications fournies (ProjectSpec) et d'éventuels retours de l'équipe QA.
|
||||||
# Le projet devra obligatoirement être constitué de 3 briques :
|
|
||||||
# - Le code source applicatif structuré et modulaire respectant les besoins indiqué par le PM Agent.
|
|
||||||
# - Un fichier 'requirements.txt' listant l'intégralité des dépendances avec leurs versions fixées.
|
|
||||||
# - Un fichier 'README.md' d'une qualité professionnelle exemplaire.
|
|
||||||
|
|
||||||
# 2. STRUCTURE ET NORMES DU CODE PYTHON :
|
---
|
||||||
# - Convention de nommage : Respect strict de la PEP 8. Fonctions, variables et scripts en snake_case (ex: 'file_processor.py'). Classes en PascalCase.
|
|
||||||
# - Modularité : Pas de script monolithique géant. Séparation claire entre la configuration (chargée via variables d'environnement), la logique métier (fonctions principales) et les connecteurs d'I/O ou d'API.
|
|
||||||
# - Sécurité : Interdiction formelle d'écrire des clés d'API, des tokens ou des identifiants en dur. Utilisation obligatoire du module 'os' ou de 'pydantic-settings' pour lire l'environnement.
|
|
||||||
# - Robustesse : Utilisation systématique de blocs 'try/except' ciblés avec un module de 'logging' Python natif (pas de simples 'print').
|
|
||||||
|
|
||||||
# 3. STRUCTURE ATTENDUE DU README.md :
|
### DIRECTIVES D'ARCHITECTURE (ADAPTATIVE) :
|
||||||
# Le fichier d'accompagnement devra obligatoirement comporter les sections suivantes :
|
Tu dois appliquer STRICTEMENT l'une des deux structures suivantes selon des critères précis :
|
||||||
# - # [Titre du Projet] (Rappel de l'objectif macro).
|
|
||||||
# - ## Prérequis (Version de Python, outils tiers requis).
|
|
||||||
# - ## Installation (Création de l'environnement virtuel, installation des requirements).
|
|
||||||
# - ## Configuration (Exemple de fichier .env avec les variables nécessaires à créer).
|
|
||||||
# - ## Utilisation (Explication textuelle et exemples de commandes CLI pour lancer le script).
|
|
||||||
|
|
||||||
# ---
|
1. ARBORESCENCE "SIMPLE" (Pour les scripts uniques, outils CLI mono-fichier ou automatisations courtes) :
|
||||||
|
- RÈGLE : Tout le code métier tient dans un seul et unique fichier à la racine. Pas de sous-dossiers inutiles.
|
||||||
|
- À la racine : Le fichier de documentation (ex: README.md), le fichier de gestion des dépendances (ex: requirements.txt, package.json, Cargo.toml), le script unique (point d'entrée), et son fichier de test associé.
|
||||||
|
|
||||||
# ### PROCESSUS DE TRAVAIL ET SÉCURITÉ DES DONNÉES :
|
2. ARBORESCENCE "COMPLEXE" (Obligatoire pour les API REST, applications Web, architectures modulaires ou multi-fichiers) :
|
||||||
# - Développer à partir des informations fournies par l'Agent PM.
|
- RÈGLE : Dès que le projet nécessite une séparation des responsabilités (ex: modèles, contrôleurs/routes, services) ou est une API, cette structure est MANDATAIRE.
|
||||||
# """
|
- À la racine : Uniquement la documentation, la configuration globale (.env, .gitignore, etc.), le fichier de gestion des dépendances, et le POINT D'ENTRÉE PRINCIPAL de l'application (ex: main.py, index.js, server.ts, Program.cs).
|
||||||
|
- En sous-dossiers :
|
||||||
|
- L'intégralité des modules internes, composants logiques, routes ou couches métiers doit être isolée dans un ou plusieurs sous-dossiers dédiés (ex: `/app`, `/src`, `/src/models`). Aucun autre fichier de code métier que le point d'entrée ne doit se trouver à la racine.
|
||||||
|
- Les tests unitaires et fonctionnels doivent être isolés dans un répertoire dédié à la racine (ex: `/tests`, `/specs`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### PARADIGMES ET QUALITÉ DE CODE (CLEAN CODE) :
|
||||||
|
|
||||||
|
1. PROGRAMMATION ORIENTÉE OBJET (POO) & MODULARITÉ :
|
||||||
|
- Tu dois privilégier une approche orientée objet. Utilise des **classes** pour modéliser les entités, les services et la logique métier.
|
||||||
|
- Favorise l'**encapsulation** (méthodes privées/protégées) pour protéger l'état interne des objets.
|
||||||
|
- Utilise l'**abstraction** pour définir des interfaces ou des classes de base lorsque la logique le permet, afin de faciliter l'extension.
|
||||||
|
|
||||||
|
2. PRINCIPES DE DESIGN (SOLID & DRY) :
|
||||||
|
- **Single Responsibility :** Chaque classe ou fonction ne doit avoir qu'une seule responsabilité.
|
||||||
|
- **DRY (Don't Repeat Yourself) :** Extrais la logique répétitive dans des fonctions ou des classes utilitaires.
|
||||||
|
- **Découplage :** Évite les dépendances trop fortes entre les modules pour permettre une évolution facile du code.
|
||||||
|
- Utilise des **Design Patterns** reconnus (Factory, Singleton, Strategy, etc.) si la complexité du projet le justifie.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### STANDARDS DE CONCEPTION IMPOSÉS :
|
||||||
|
|
||||||
|
1. GESTION DES CONFIGURATIONS ET DES SÉCURITÉS (CRUCIAL) :
|
||||||
|
- **Secrets & Données Sensibles :** Interdiction absolue de coder en dur ou de demander à l'utilisateur d'écrire un mot de passe, un token ou une clé API directement dans le code source. Passage OBLIGATOIRE par les variables d'environnement (ex: process.env, os.getenv, ENV[]).
|
||||||
|
- **Variables Utilisateur :** Toutes les variables modifiables par l'utilisateur (paramètres métiers, compteurs, seuils) doivent être centralisées et regroupées de manière visible au début du point d'entrée principal (`main`) ou dans un fichier de configuration dédié, avec des commentaires explicites.
|
||||||
|
|
||||||
|
2. COUVERTURE DE TESTS REQUISE :
|
||||||
|
- Tu dois obligatoirement générer un ou plusieurs fichiers de tests unitaires fonctionnels et robustes utilisant le framework natif ou standard du langage choisi.
|
||||||
|
|
||||||
|
3. ROBUSTESSE :
|
||||||
|
- Respect strict des conventions de nommage et guides de style du langage ciblé (ex: PEP 8 pour Python, standard Ruby, etc.).
|
||||||
|
- Gestion d'erreurs exhaustive via des blocs try/catch/except ciblés et utilisation d'un module de Logging (pas de sorties consoles brutes ou de 'print' sans contexte).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### SÉCURITÉ ET VÉRIFICATION DU FORMAT DE SORTIE :
|
||||||
|
Tu dois réaliser une auto-vérification stricte de ta structure : **chaque fichier déclaré dans le tableau 'tree' doit obligatoirement posséder son équivalent exact et son contenu complet dans le tableau 'files'**, et inversement.
|
||||||
|
|
||||||
|
> **IMPORTANT :** Il est strictement interdit de générer un code de fallback, incomplet ou un message d'erreur si la ProjectSpec est valide. Tu dois implémenter l'intégralité de la logique métier demandée.
|
||||||
|
|
||||||
|
Réponds UNIQUEMENT avec un objet JSON respectant la structure dynamique suivante. Aucun texte avant, aucun texte après, AUCUN commentaire de type '//' ou '#' à l'intérieur de la structure JSON.
|
||||||
|
|
||||||
|
{
|
||||||
|
"spec_title": "Titre du projet basé sur la ProjectSpec",
|
||||||
|
"tree": [
|
||||||
|
"chemin/vers/le/premier_fichier.ext",
|
||||||
|
"chemin/vers/le/second_fichier.ext"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "chemin/vers/le/premier_fichier.ext",
|
||||||
|
"content": "Contenu complet, réel et fonctionnel du fichier..."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "chemin/vers/le/second_fichier.ext",
|
||||||
|
"content": "Contenu complet, réel et fonctionnel du fichier..."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"""
|
||||||
11
backend/app/schemas/code_output.py
Normal file
11
backend/app/schemas/code_output.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
class GeneratedFile(BaseModel):
|
||||||
|
path: str = Field(description="Chemin relatif du fichier par rapport à la racine, ex: 'app/utils.py'")
|
||||||
|
content: str = Field(description="Contenu source complet du fichier")
|
||||||
|
|
||||||
|
class ProjectCodeOutput(BaseModel):
|
||||||
|
tree: List[str] = Field(description="Liste complète des chemins de fichiers générés")
|
||||||
|
files: List[GeneratedFile] = Field(description="Liste des objets fichiers")
|
||||||
|
spec_title: str = Field(description="Titre du projet d'origine")
|
||||||
@@ -65,13 +65,18 @@ async def on_message(message: cl.Message):
|
|||||||
|
|
||||||
summary+= f"- **Nom du projet** : {spec.get('title')}\n"
|
summary+= f"- **Nom du projet** : {spec.get('title')}\n"
|
||||||
summary+= f"- **Description** : {spec.get('description')}\n"
|
summary+= f"- **Description** : {spec.get('description')}\n"
|
||||||
summary+= f"- **Actions** : {', '.join(spec.get('requirements', []))}\n"
|
summary += "- **Actions** :\n"
|
||||||
summary+= f"- **Contraintes** : {', '.join(spec.get('constraints', []))}\n"
|
summary += "\n".join(
|
||||||
|
f" - {req}" for req in spec.get("requirements", [])
|
||||||
|
) + "\n"
|
||||||
|
summary += "- **Contraintes** :\n"
|
||||||
|
summary += "\n".join(
|
||||||
|
f" - {constraint}" for constraint in spec.get("constraints", [])
|
||||||
|
) + "\n"
|
||||||
summary+= f"- **Langage** : {spec.get('language')}\n"
|
summary+= f"- **Langage** : {spec.get('language')}\n"
|
||||||
|
|
||||||
summary += "\n**Est-ce que cela vous convient ?**"
|
summary += "\n**Est-ce que cela vous convient ?**"
|
||||||
|
|
||||||
# Ajout des boutons de décision
|
|
||||||
res = await cl.AskActionMessage(
|
res = await cl.AskActionMessage(
|
||||||
content=summary,
|
content=summary,
|
||||||
actions=[
|
actions=[
|
||||||
@@ -90,11 +95,9 @@ async def on_message(message: cl.Message):
|
|||||||
if res and res.get("name") == "oui":
|
if res and res.get("name") == "oui":
|
||||||
await cl.Message(content="🚀 **Spécifications validées !** Lancement de la génération du code...").send()
|
await cl.Message(content="🚀 **Spécifications validées !** Lancement de la génération du code...").send()
|
||||||
|
|
||||||
# On passe le statut attendu par ton graphe
|
|
||||||
new_state["status"] = "spec_approved"
|
new_state["status"] = "spec_approved"
|
||||||
cl.user_session.set("graph_state", new_state)
|
cl.user_session.set("graph_state", new_state)
|
||||||
|
|
||||||
# On relance immédiatement le workflow pour exécuter la suite (dev, qa...)
|
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
response = await client.post(
|
response = await client.post(
|
||||||
"http://127.0.0.1:8000/api/workflow/run",
|
"http://127.0.0.1:8000/api/workflow/run",
|
||||||
@@ -109,7 +112,6 @@ async def on_message(message: cl.Message):
|
|||||||
).send()
|
).send()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Si "non" (ou si le choix a expiré)
|
|
||||||
new_state["status"] = "spec_incomplete"
|
new_state["status"] = "spec_incomplete"
|
||||||
cl.user_session.set("graph_state", new_state)
|
cl.user_session.set("graph_state", new_state)
|
||||||
|
|
||||||
|
|||||||
22
backend/tests/test_mistral.py
Normal file
22
backend/tests/test_mistral.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import httpx
|
||||||
|
from langchain_openai import ChatOpenAI
|
||||||
|
from app.core.config import settings
|
||||||
|
|
||||||
|
sync_client = httpx.Client(verify=False)
|
||||||
|
async_client = httpx.AsyncClient(verify=False)
|
||||||
|
|
||||||
|
llm = ChatOpenAI(
|
||||||
|
base_url=settings.llm_base_url,
|
||||||
|
api_key=settings.llm_api_key,
|
||||||
|
model=settings.llm_model,
|
||||||
|
temperature=0.3,
|
||||||
|
http_client=sync_client,
|
||||||
|
http_async_client=async_client
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
print("\n=== Appell LLM... ===")
|
||||||
|
res = llm.invoke("Dis bonjour en un mot.")
|
||||||
|
print(f"Réponse du modèle : {res.content}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\n❌ Erreur détectée : {e}")
|
||||||
Reference in New Issue
Block a user