Refactorisation du code, correction des quelques bugs, Clear de Readme principal, Ajout de l'entrée utilisateur pour la modification du nom de projet

This commit is contained in:
Chevallier
2026-07-17 17:12:25 +02:00
parent 9d464fb261
commit 75e035cc82
12 changed files with 248 additions and 411 deletions

118
README.md
View File

@@ -2,78 +2,45 @@
## Contexte du Projet
ARC est une plateforme d'automatisation du développement logiciel basée sur un workflow multi-agents (IA). Le système orchestre plusieurs modèles d'IA spécialisés pour transformer un besoin utilisateur en un code source validé, testé et stocké.
___
### Étape 0 : Préparation de l'environnement du projet
Tâches :
- Créer backend minimal
- Créer modèle de données simple
- langGraph
___
### Étape 1 : Analyse du Besoin & Qualification (Agent PM / Business Analyst)
- L'utilisateur entre une demande en langage naturel.
- **Agent 1 (PM)** analyse la demande. Si des informations manquent pour coder, il pose des questions clarificatrices à l'utilisateur jusqu'à obtenir un cahier des charges complet.
- **Vérification BDD :** Avant de coder, le système cherche dans une base de données vectorielle si un projet similaire existe déjà.
- *Si oui :* On propose le lien à l'utilisateur. Si l'utilisateur valide, le workflow s'arrête ici.
- *Si non (ou si l'utilisateur rejette l'existant) :* On passe à l'étape 2.
Outils :
- LangGraph -> LangGraph est adapté aux workflows multiagents avec états, transitions conditionnelles, persistance et humanintheloop
- Python -> backend, les agents, les appels LLM, les tests, les embeddings et les intégrations + compatible avec autres tools
- Pydantic AI / Pydantic -> forcer lAgent PM à produire un cahier des charges structuré
- Chainlit -> adapté aux interfaces conversationnelles
BDD :
- Qdrant -> adapté à la recherche sémantique + stocker les embeddings de projets/scripts
- Snowflake Arctic Embed 2.0 -> modèle dembedding
- Redis (optionnel) -> cache de recherche ;sessions utilisateur ;état temporaire ;verrouillage dun workflow ;file dattente simple
___
### Étape 2 : Génération de Code (Agent Développeur)
- **Agent 2 (Dev)** reçoit le cahier des charges validé et génère l'arborescence et le code source du projet.
Outils :
- LangGraph -> LangGraph est adapté aux workflows multiagents avec états, transitions conditionnelles, persistance et humanintheloop
- Mistral ou Gemma (modèle trop généraliste/léger->tache simple) -> DeepSeek Coder/Qwen2.5
- vLLM -> meilleur choix quOllama pour une plateforme plus industrialisée. Llama.cpp modèles quantifiés sur CPU ou machines modestes + moins adapté à une plateforme multiutilisateur
- Pydantic
___
### Étape 3 : Tests et Assurance Qualité (Agent QA / Testeur)
- **Agent 3 (QA)** récupère le code de l'Agent 2. Il doit exécuter le code (via une sandbox sécurisée) ou générer/exécuter des tests unitaires pour vérifier la qualité, la sécurité et le fonctionnement.
- **Boucle de correction automatique (Loop 1) :** Si les tests échouent, l'Agent 3 renvoie les erreurs à l'Agent 2 avec les logs. L'Agent 2 corrige et renvoie à l'Agent 3. Cette boucle tourne au maximum 3 fois jusqu'à ce que le code soit "vert".
- **EXTENSION FUTURE** si les tests échouent 3 fois, envoyé à une IA plus puissante.
Outils :
- Docker
- Ruff -> qualité de code
- Bandit -> sécurité
- Semgrep (optionnel) -> règles de sécurité et qualité plus larges
Boucle correction :
- LangGraph
- Pydantic
___
### Étape 4 : Livraison & Feedback Utilisateur (Boucle Humaine)
- Une fois le code validé par l'Agent 3, il est présenté à l'utilisateur.
- L'utilisateur teste et valide.
- *Si Validé :* Le projet est sauvegardé dans la base de données (pour la recherche de l'Étape 1) et livré (ex: zip ou dépôt GitHub).
- *Si Refusé :* L'utilisateur indique ce qui ne va pas. Tout le contexte (code actuel + retours) est renvoyé à l'**Étape 1** pour réanalyse, et le cycle recommence.
Outils :
- Chainlit
- Git
___
### Étape finale :
Tâches :
- Tests fonctionnels de bout en bout
- Sécurité minimale
- Documentation finale
- Présentation
## Lancer le projet
Avant de lancer le projet, il est important de créer le fichier .env dans le dossier */backend* et de remplir les éléments suivant :
```bash
chmod +x run_project.sh
./run_project.sh
LLM_BASE_URL=
LLM_API_KEY=
LLM_MODEL=
LLM_MODEL_DEV=
LLM_MODEL_QA=
EMBEDDING_BASE_URL=
EMBEDDING_MODEL=
GITEA_ADMIN_USER=
GITEA_ADMIN_PASSWORD=
```
Une fois les informations fournies, éxectuer la commande suivante pour lancer le projet
```bash
python .\orchestrator.py
```
* L'interface utilisateur conversationnelle se trouve sur l'adresse suivant : http://localhost:8001/
* Le Gitea se trouve à l'adresse suivante : http://localhost:3000/
___
## Arrêter le projet
```bash
python .\stop_project.py
```
___
## Visualiser les logs
```bash
docker compose -p arc-app logs -f
```
___
## Structure du projet
```bash
ARC/
@@ -85,15 +52,13 @@ ARC/
│ │ │ └── qa_agent.py
│ │ │
│ │ ├── api/
│ │ │ ── routes/
│ │ │ ├── health.py
│ │ │ └── workflow.py
│ │ │ └── deps.py
│ │ │ ── routes/
│ │ │ ├── health.py
│ │ │ └── workflow.py
│ │ │
│ │ ├── core/
│ │ │ ├── config.py
│ │ │ ── logging.py
│ │ │ └── security.py
│ │ │ ── logging.py
│ │ │
│ │ ├── graph/ # LangGraph
│ │ │ ├── state.py
@@ -103,10 +68,6 @@ ARC/
│ │ ├── llm/ # appels modèles
│ │ │ ├── client.py # wrapper dappel
│ │ │ ├── prompts.py # prompts centralisés
│ │ │ └── providers.py # Gemma/llama.cpp....
│ │ │
│ │ ├── models/ # modèles métier / persistance (métadonnées dun projet/version/statut/lien Git/hash/tags)
│ │ │ └── project.py
│ │ │
│ │ ├── repositories/ # accès externes, Qdrant / Redis / stockage
│ │ │ ├── qdrant_repository.py
@@ -122,8 +83,7 @@ ARC/
│ │ │ ├── api.py
│ │ │ ├── spec.py
│ │ │ ├── code_output.py
│ │ │ ── qa_report.py
│ │ │ └── project.py
│ │ │ ── qa_report.py
│ │ │
│ │ ├── services/
│ │ │ ├── workflow_service.py
@@ -146,7 +106,7 @@ ARC/
│ │ ├── test_qdrant.py
│ │ └── test_snowflake.py
│ │
│ ├── .env
│ ├── .env # IMPORTANT élément à remplir
│ ├── chainlit_app.py
│ ├── chainlit_fr_FR.md
│ ├── docker-compose-ai.yml # Conteneurs pour les agents et l'embedding

View File

@@ -1,52 +1,67 @@
# ARC Backend
Backend minimal pour le projet ARC :
- API FastAPI
- orchestration LangGraph
- agents PM / Dev / QA
- interface Chainlit
- intégration future Qdrant / Redis / vLLM
## Organisation du projet
## Installation
### Étape 0 : Préparation de l'environnement du projet
Tâches :
- Créer backend minimal
- Créer modèle de données simple
- langGraph
___
### Étape 1 : Analyse du Besoin & Qualification (Agent PM / Business Analyst)
- L'utilisateur entre une demande en langage naturel.
- **Agent 1 (PM)** analyse la demande. Si des informations manquent pour coder, il pose des questions clarificatrices à l'utilisateur jusqu'à obtenir un cahier des charges complet.
- **Vérification BDD :** Avant de coder, le système cherche dans une base de données vectorielle si un projet similaire existe déjà.
- *Si oui :* On propose le lien à l'utilisateur. Si l'utilisateur valide, le workflow s'arrête ici.
- *Si non (ou si l'utilisateur rejette l'existant) :* On passe à l'étape 2.
```bash
python -m venv .venv
.venv\Scripts\activate
pip install -r requirements.txt
uvicorn app.main:app --reload --port 8000
```
Outils :
- LangGraph -> LangGraph est adapté aux workflows multiagents avec états, transitions conditionnelles, persistance et humanintheloop
- Python -> backend, les agents, les appels LLM, les tests, les embeddings et les intégrations + compatible avec autres tools
- Pydantic AI / Pydantic -> forcer lAgent PM à produire un cahier des charges structuré
- Chainlit -> adapté aux interfaces conversationnelles
BDD :
- Qdrant (optionnel) -> adapté à la recherche sémantique + stocker les embeddings de projets/scripts
- Snowflake Arctic Embed 2.0 (optionnel) -> modèle dembedding
- Redis (optionnel) -> cache de recherche ;sessions utilisateur ;état temporaire ;verrouillage dun workflow ;file dattente simple
___
### Étape 2 : Génération de Code (Agent Développeur)
- **Agent 2 (Dev)** reçoit le cahier des charges validé et génère l'arborescence et le code source du projet.
## Lancer Chainlit
Outils :
- LangGraph -> LangGraph est adapté aux workflows multiagents avec états, transitions conditionnelles, persistance et humanintheloop
- Mistral ou Gemma (modèle trop généraliste/léger->tache simple) -> DeepSeek Coder/Qwen2.5
- vLLM -> meilleur choix quOllama pour une plateforme plus industrialisée. Llama.cpp modèles quantifiés sur CPU ou machines modestes + moins adapté à une plateforme multiutilisateur
- Pydantic
- Gitea
___
### Étape 3 : Tests et Assurance Qualité (Agent QA / Testeur)
- **Agent 3 (QA)** récupère le code de l'Agent 2. Il doit exécuter le code (via une sandbox sécurisée) ou générer/exécuter des tests unitaires pour vérifier la qualité, la sécurité et le fonctionnement.
- **Boucle de correction automatique (Loop 1) :** Si les tests échouent, l'Agent 3 renvoie les erreurs à l'Agent 2 avec les logs. L'Agent 2 corrige et renvoie à l'Agent 3. Cette boucle tourne au maximum 3 fois jusqu'à ce que le code soit "vert".
- **EXTENSION FUTURE** si les tests échouent 3 fois, envoyé à une IA plus puissante.
```bash
chainlit run chainlit_app.py --port 8001
```
Outils :
- Docker
- Semgrep -> règles de sécurité et qualité plus larges
Boucle correction :
- world model (optionnel)
- LangGraph
- Pydantic
___
### Étape 4 : Livraison & Feedback Utilisateur (Boucle Humaine)
- Une fois le code validé par l'Agent 3, il est présenté à l'utilisateur.
- L'utilisateur teste et valide.
- *Si Validé :* Le projet est sauvegardé dans la base de données (pour la recherche de l'Étape 1) et livré (ex: zip ou dépôt GitHub).
- *Si Refusé :* L'utilisateur indique ce qui ne va pas. Tout le contexte (code actuel + retours) est renvoyé à l'**Étape 1** pour réanalyse, et le cycle recommence.
## Lancement auto
Outils :
- Chainlit
- Gitea
___
### Étape finale :
```bash
python orchestrator.py
```
## Arrêt propre
```bash
python stop_project.py
```
# Logs
```bash
docker compose -p arc-app logs -f
docker compose -p arc-ai logs -f
```
## Tests
```bash
python .\tests\test_snowflake.py
docker compose exec app python tests/test_qdrant.py
```
API dispo sur :
- http://127.0.0.1:8001
Tâches :
- Tests fonctionnels de bout en bout
- Sécurité minimale
- Documentation finale
- Présentation

View File

@@ -56,7 +56,7 @@ async def run_dev_agent(spec: dict, qa_feedback: list = None, repo_url: str = No
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)
return _generate_fallback_code_output(spec, repo_url=repo_url, files=files)
async def _ensure_gitea_repo(base_name: str, repo_url: str = None) -> str:
"""
@@ -189,18 +189,46 @@ async def _deploy_to_gitea(code_data: ProjectCodeOutput, project_title: str, rep
if os.path.exists(work_dir):
shutil.rmtree(work_dir)
def _generate_fallback_code_output(spec: dict) -> dict:
"""Génère un livrable minimal de secours en cas de crash du LLM."""
def _generate_fallback_code_output(spec: dict, repo_url: str = None, files: list = None) -> dict:
"""
Génère un livrable de secours. Récupère et reconstruit l'état du code
à partir des fichiers existants s'ils sont fournis, sinon génère un README d'erreur.
"""
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")
fallback_files = []
fallback_tree = []
if files:
for f in files:
path = ""
content = ""
if isinstance(f, dict):
path = f.get("path") or f.get("filename") or f.get("name") or ""
content = f.get("content") or f.get("code") or ""
elif hasattr(f, "path") and hasattr(f, "content"):
path = getattr(f, "path", "")
content = getattr(f, "content", "")
if path:
fallback_files.append(GeneratedFile(path=path, content=content))
fallback_tree.append(path)
if not fallback_files:
fallback_files = [
GeneratedFile(
path="README.md",
content=f"# {title}\nErreur interne : le code n'a pas pu être généré par l'agent Dev lors du premier essai."
)
]
fallback_tree = ["README.md"]
fallback = ProjectCodeOutput(
repo_url=repo_url,
spec_title=title,
tree=fallback_tree,
files=fallback_files
)
return fallback.model_dump()

View File

@@ -38,7 +38,7 @@ 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:

View File

@@ -19,11 +19,11 @@ class Settings(BaseSettings):
llm_model_dev: str
llm_model_qa: str
embedding_base_url: str = "http://localhost:8002/v1"
embedding_model: str = "snowflake-arctic-embed-m-v1.5"
embedding_base_url: str
embedding_model: str
gitea_base_url: str
gitea_api_url: str
gitea_base_url: str = "http://git-arc:3000"
gitea_api_url: str = "http://git-arc:3000/api/v1"
gitea_admin_user: str
gitea_admin_password: str
gitea_token: Optional[str] = None

View File

@@ -1,45 +1,21 @@
PM_AGENT_PROMPT = """Tu es un Product Manager senior spécialisé en structuration de cahiers des charges.
PM_AGENT_PROMPT = """Tu es un Product Manager senior et un Lead Developer spécialisé en structuration de cahiers des charges techniques.
=== TÂCHE UNIQUE ET EXCLUSIVE ===
Tu reçois une demande utilisateur brute.
Tu DOIS la transformer en JSON valide suivant EXACTEMENT cette structure (ProjectSpec).
Tu reçois une demande utilisateur brute. Tu dois l'analyser et la transformer en un JSON valide suivant EXACTEMENT la structure "ProjectSpec".
⚠️ RÈGLE ABSOLUE : Tu génères UNIQUEMENT du JSON valide. Pas de texte avant, pas de texte après.
Si tu ne comprends pas un champ, tu le remplis avec une valeur par défaut INTELLIGENTE.
⚠️ RÈGLE DE SORTIE : Tu génères UNIQUEMENT du JSON valide. Pas de texte avant, pas de texte après, pas de balises markdown de code block (pas de ```json).
Si tu ne comprends pas un champ ou si l'info est absente mais non critique, remplis-le avec une valeur par défaut INTELLIGENTE.
---
=== STRUCTURE JSON À RETOURNER ===
{
"title": "...",
"description": "...",
"requirements": ["...", "..."],
"constraints": ["..."],
"language": "...",
"target_stack": "...",
"io_config": {
"has_inputs": true/false,
"input_type": "file|directory|api|database|none",
"input_paths_or_sources": ["..."],
"has_outputs": true/false,
"output_type": "file|directory|database|api_response|log_only",
"output_formats": ["..."]
},
"auth_config": {
"requires_auth": true/false,
"auth_method": "env_variables|service_account_json|oauth2|api_key|none",
"target_tools_and_apis": ["..."]
},
"env_config": {
"target_os": "linux|windows|macos|cross_platform",
"python_version": "^3.11",
"critical_dependencies": ["..."]
},
"error_handling_strategy": "abort_on_error|log_and_continue|retry_policy",
"is_complete": true,
"clarifying_question": null
}
=== GESTION PARTICULIÈRE : CAS DU HORS-SUJET ===
Si la demande de l'utilisateur n'est PAS un projet de développement, de script ou d'automatisation informatique (ex: "Comment on fait des bébés ?", "Rédige un poème", "Donne-moi une recette de cuisine") :
1. "title" : "Hors Sujet"
2. "description" : "La demande ne concerne pas un projet de développement ou d'automatisation."
3. "requirements" : ["La demande utilisateur doit être reformulée pour cibler un script ou une tâche d'automatisation."]
4. "is_complete" : false
5. "clarifying_question" : "Désolé, l'outil ARC est conçu exclusivement pour concevoir, automatiser et développer des scripts ou des projets informatiques. Votre demande ne semble pas correspondre à un projet de développement. Comment puis-je vous aider sur un sujet technique ou d'automatisation ?"
6. Remplis tous les autres champs avec des valeurs vides ou par défaut cohérentes.
---
@@ -47,253 +23,81 @@ Si tu ne comprends pas un champ, tu le remplis avec une valeur par défaut INTEL
**1. title** :
- JAMAIS vide. Jamais null.
- Si vague: génère un titre court et clair dérivé du besoin
- Format: "Verb + Object" (ex: "CSV Data Cleaner", "API Report Generator")
- Si l'utilisateur dit "teste mon code" "Code Test Runner"
- Si l'utilisateur dit juste "script""Automation Script"
- Si l'utilisateur dit juste "script" -> "Automation Script"
**2. description** :
- JAMAIS vide. Jamais null.
- 1-2 phrases qui expliquent QUOI et POURQUOI.
- Réutilise les mots clés de la demande pour que ce soit cohérent.
- Ex: "Traite les fichiers CSV mensuels. Filtre les anomalies et génère un rapport Excel."
- 1-2 phrases qui expliquent QUOI et POURQUOI de manière cohérente avec la demande.
**3. requirements** :
- JAMAIS vide. JAMAIS une liste vide [].
- MINIMUM 3 items (même générés intelligemment).
- Format: "Action: Détail" ou "Étape N: Décrire l'action"
- Ex:
[
"Lire les données CSV depuis le dossier /input",
"Valider l'intégrité des colonnes requises",
"Générer un rapport Excel avec statistiques"
]
- JAMAIS vide. JAMAIS une liste vide []. MINIMUM 3 items détaillés (même déduits intelligemment).
- Format: "Action: Détail" ou "Étape N: Description"
**4. constraints** :
- Peux être vide si pas mentionné.
- Sinon: normes de code, limitations, formats (ex: ["PEP 8 strict", "Logs rotatifs"])
- Peut être vide []. Sinon, normes, formats ou limites (ex: ["PEP 8", "Logs rotatifs"]).
**5. language** :
- JAMAIS vide.
- Par défaut: "Python"
- Sauf si l'utilisateur dit "Node.js", "Go", etc.
- Par défaut: "Python" (Sauf si spécifié autrement comme Go, Bash, Node.js).
**6. target_stack** :
- Peux être vide
- Sauf si l'utilisateur dit "FastAPI", "Django", etc.
- Framework ou outil spécifique (ex: "FastAPI", "Pandas"). Peut être vide.
**7. io_config.has_inputs** :
- TRUE si l'utilisateur mentionne : "fichier", "dossier", "données", "lire", "importer", "télécharger", "API", "base de données", "table"
- FALSE sinon.
- ⚠️ Si TRUE → input_type DOIT être rempli (pas null)
- TRUE si la demande mentionne : lire, importer, fichier, dossier, API, base de données, table, etc.
- FALSE sinon. Si TRUE -> input_type DOIT être rempli (pas "none").
**8. io_config.input_type** :
- DÉDUIS du contexte :
- "fichier CSV/Excel/JSON/PDF/TXT""file"
- "dossier""directory"
- "API REST/GraphQL/Service Web""api"
- "base de données/table SQL/MongoDB""database"
- Aucun input → "none"
- ❌ JAMAIS null si has_inputs=true
- Déduis du contexte ("file", "directory", "api", "database"). "none" si pas d'inputs.
**9. io_config.has_outputs** :
- TRUE si l'utilisateur mentionne : "exporter", "générer", "créer", "sauvegarder", "envoyer", "résultat", "fichier", "rapport"
- FALSE sinon.
- TRUE si la demande mentionne : générer, exporter, créer, sauvegarder, envoyer, rapport, fichier, etc.
**10. io_config.output_type** :
- DÉDUIS du contexte :
- "fichier Excel/CSV/PDF/JSON""file"
- "dossier de résultats""directory"
- "base de données""database"
- "appel API/réponse HTTP""api_response"
- "juste des logs/console""log_only"
- ❌ JAMAIS null si has_outputs=true
- Déduis du contexte ("file", "directory", "database", "api_response", "log_only").
**11. io_config.output_formats** :
- Formats de sortie mentionnés (ex: ["CSV", "JSON", "XLSX", "PDF"])
- Peux être vide si has_outputs=false
- Liste de formats (ex: ["CSV", "XLSX"]). Peut être vide [].
**12. auth_config.requires_auth** :
- TRUE si l'utilisateur mentionne : "API", "token", "key", "clé", "authentification", "compte", "service account", "OAuth", "login", "credential", "Jira", "SharePoint", "GCP", "AWS", "Azure", "base de données"
- FALSE sinon.
- TRUE si la demande interagit avec un service tiers, une API ou une base de données nécessitant des accès (ex: Jira, Zabbix, SharePoint, DB SQL, API externe, GCP, AWS).
**13. auth_config.auth_method** :
- DÉDUIS du contexte :
- "variables d'environnement/.env""env_variables"
- "fichier .json avec credentials""service_account_json"
- "OAuth/OpenID""oauth2"
- "API key/token""api_key"
- Pas d'auth → "none"
- ⚠️ Si requires_auth=true → DÉDUIS intelligemment, jamais null
- Déduis intelligemment ("env_variables", "service_account_json", "oauth2", "api_key"). "none" si pas d'authentification.
**14. auth_config.target_tools_and_apis** :
- Liste les services externes (ex: ["Jira API", "Google Drive", "AWS S3"])
- Peux être vide si requires_auth=false
- Services cibles à lister (ex: ["Jira API"]). Peut être vide [].
**15. env_config.target_os** :
- "cross_platform" par défaut
- Sauf si mentionné : "linux", "windows", "macos"
- "cross_platform" par défaut (Sauf si linux, windows, macos est spécifié).
**16. env_config.language_version** :
- "^3.11" par défaut si Python
- Sauf si la version exacte est mentionnée par l'utilisateur (ex: "Python 3.12", "Node.js 18", "Go 1.20")
- Sinon vide
- "^3.11" par défaut pour Python. Sinon la version mentionnée.
**17. env_config.critical_dependencies** :
- DÉDUIS du contexte :
- Données tabulaires → "pandas"
- API REST → "requests"
- Configuration → "pydantic"
- Base de données → "sqlalchemy", "pymongo"
- Fichiers → "openpyxl", "python-docx", "PyPDF2"
- Parallelisation → "asyncio", "concurrent.futures"
- Toujours inclure: ["logging"] (natif mais important)
- Déduis du contexte (ex: "pandas", "requests", "sqlalchemy", "openpyxl"). Inclure toujours: ["logging"].
**18. error_handling_strategy** :
- JAMAIS vide. JAMAIS null.
- "log_and_continue" par défaut (robuste)
- "abort_on_error" si critique
- "retry_policy" si mentionné "retry", "robustesse", "résilience"
- "log_and_continue" par défaut. "abort_on_error" ou "retry_policy" si la tolérance aux pannes est mentionnée.
**19. is_complete** :
- TRUE si tu as pu remplir tous les champs principaux (title, description, requirements) sans ambiguïté
- FALSE UNIQUEMENT si quelque chose est vraiment manquant et ambigu
- Définis impérativement à FALSE si :
- La demande est Hors Sujet (voir section GESTION DU HORS-SUJET).
- Le script doit interagir avec un serveur ou un équipement mais il manque l'adresse IP, le nom d'hôte ou le sous-réseau.
- Le script doit requêter un service ou une API (ex: Jira, SharePoint, Zabbix) mais il manque le lien (URL de l'instance/du serveur).
- Le script nécessite une authentification mais le type d'accès (compte de service, clé API, token) ou le compte n'est pas spécifié.
- Les traitements, calculs ou filtres à appliquer sur les données entrantes restent flous ou ambigus.
- Définis à TRUE uniquement si tous ces éléments fonctionnels et d'accès sont clairs et exploitables immédiatement par un développeur.
**20. clarifying_question** :
- null si is_complete=true
- Sinon: une question naturelle et polie pour l'utilisateur
- Ex: "Quel format de fichier en entrée (CSV, Excel, JSON) et où souhaitez-vous la sortie ?"
- Doit être "null" si is_complete est TRUE.
- Si is_complete est FALSE : Rédige **UNE SEULE** question polie, claire et ciblée pour débloquer le développement.
- *Exemple de question technique* : "Pourriez-vous me fournir l'adresse IP du serveur cible ainsi que le type de compte de service requis pour l'authentification ?"
- *Exemple de question hors-sujet* : Voir la section "GESTION PARTICULIÈRE : CAS DU HORS-SUJET" ci-dessus.
---
=== EXEMPLES CONCRETS ===
### Input 1: "Je veux un script en Ruby qui lit un CSV et l'exporte en Excel"
```json
{
"title": "CSV to Excel Converter",
"description": "Lit un fichier CSV, valide les données et exporte le résultat en format Excel (.xlsx).",
"requirements": [
"Lire le fichier CSV depuis le dossier source",
"Valider les colonnes requises",
"Exporter en fichier Excel avec formatage"
],
"constraints": ["PEP 8 strict"],
"language": "Ruby",
"target_stack": "",
"io_config": {
"has_inputs": true,
"input_type": "file",
"input_paths_or_sources": ["input.csv"],
"has_outputs": true,
"output_type": "file",
"output_formats": ["XLSX"]
},
"auth_config": {
"requires_auth": false,
"auth_method": "none",
"target_tools_and_apis": []
},
"env_config": {
"target_os": "cross_platform",
"language_version": "3.2.1O",
"critical_dependencies": ["pandas", "openpyxl"]
},
"error_handling_strategy": "log_and_continue",
"is_complete": true,
"clarifying_question": null
}
```
### Input 2: "Je dois récupérer des données sur une API Zabbix et les stocker en Python 3.12"
```json
{
"title": "API Data Fetcher",
"description": "Récupère les données depuis une API, les valide et les stocke en base de données.",
"requirements": [
"Appeler l'API avec authentification",
"Parser les données JSON",
"Insérer ou mettre à jour les enregistrements en base de données"
],
"constraints": [],
"language": "Python",
"target_stack": "Zabbix API",
"io_config": {
"has_inputs": true,
"input_type": "api",
"input_paths_or_sources": ["https://api.example.com/data"],
"has_outputs": true,
"output_type": "database",
"output_formats": ["SQL", "JSON"]
},
"auth_config": {
"requires_auth": true,
"auth_method": "api_key",
"target_tools_and_apis": ["REST API"]
},
"env_config": {
"target_os": "cross_platform",
"language_version": "3.12",
"critical_dependencies": ["requests", "sqlalchemy", "pydantic"]
},
"error_handling_strategy": "retry_policy",
"is_complete": true,
"clarifying_question": null
}
```
---
=== VALIDATION DE COMPLÉTUDE DE LA SPÉCIFICATION (OBLIGATOIRE) ===
Avant de considérer la ProjectSpec comme complète, tu dois te demander :
"Un développeur senior peut-il implémenter l'intégralité du projet sans devoir faire la moindre hypothèse importante ?"
Tu dois vérifier notamment que les éléments suivants sont suffisamment définis lorsqu'ils sont pertinents :
- le comportement attendu de l'application ;
- les données d'entrée (type, format, provenance) ;
- les données de sortie (type, format, emplacement) ;
- les règles métier ;
- les traitements à effectuer ;
- les services, API ou bases de données concernés ;
- les méthodes d'authentification ;
- les paramètres configurables ;
- les cas limites importants ;
- les contraintes fonctionnelles ou techniques.
Si une ou plusieurs informations indispensables sont absentes ou ambiguës, tu dois :
- définir `"is_complete": false`
- remplir `"clarifying_question"` avec UNE question claire, précise et ciblée permettant d'obtenir uniquement les informations manquantes.
- ne jamais inventer une règle métier ou un comportement qui n'a pas été demandé.
Tu peux compléter automatiquement uniquement les conventions techniques raisonnables (OS, stratégie de logs, dépendances, versions, structure du projet, etc.), mais jamais les besoins fonctionnels.
---
=== DERNIER CHECKPOINT ===
Avant de retourner le JSON :
1. ✅ title rempli ? (pas vide, pas null)
2. ✅ description rempli ? (pas vide, pas null)
3. ✅ requirements rempli ? (pas vide, 3+ items)
4. ✅ target_stack rempli ? (pas vide, pas null)
5. ✅ error_handling_strategy rempli ? (pas vide, pas null)
6. ✅ Si has_inputs=true → input_type rempli ? (pas null)
7. ✅ Si has_outputs=true → output_type rempli ? (pas null)
8. ✅ Si requires_auth=true → auth_method rempli ? (pas null, pas "none")
Si tout est ✅, tu retournes le JSON.
Si un champ fail, tu génères une VALEUR PAR DÉFAUT INTELLIGENTE et marque is_complete=false.
CRITICAL: Si la demande de l'utilisateur est trop courte, trop vague (ex: 'Je veux un script Ruby'), ou manque d'objectifs clairs, tu DOIS impérativement définir is_complete à false et formuler une question précise dans clarifying_question pour lui demander ce que le script doit faire concrètement.
---
=== MAINTENANT, ANALYSE ET GÉNÈRE LE JSON ===
Ci-dessous la demande utilisateur brute. Tu dois transformer cela en JSON ProjectSpec valide, rempli et cohérent.
=== MAINTENANT, ANALYSE LA DEMANDE CI-DESSOUS ET GÉNÈRE LE JSON ===
"""

View File

@@ -1,10 +0,0 @@
from pydantic import BaseModel
from typing import List, Optional
class ProjectRecord(BaseModel):
id: Optional[str] = None
title: str
summary: str
tags: List[str] = []
repository_url: Optional[str] = None

View File

@@ -1,10 +0,0 @@
from pydantic import BaseModel
from typing import List, Optional
class ProjectSummary(BaseModel):
id: Optional[str] = None
title: str
summary: str
tags: List[str] = []
repository_url: Optional[str] = None

View File

@@ -1,10 +1,11 @@
import chainlit as cl
import asyncio
import unicodedata
import zipfile
import httpx
import json
import os
import io
import re
# --- CONFIGURATION GITEA ---
GITEA_TOKEN = os.getenv("GITEA_TOKEN", "ton_token_gitea_ici")
@@ -143,7 +144,7 @@ def inject_conforme_badge(state: dict) -> dict:
return state
async def reset_to_start(message_text: str = "Bonjour 👋 Je suis ARC. Décris-moi ton besoin logiciel."):
async def reset_to_start(message_text: str = "Bonjour 👋 Je suis ARC. Décrivez-moi votre besoin logiciel."):
"""
Réinitialise complètement l'état de la session utilisateur.
"""
@@ -166,6 +167,25 @@ async def reset_to_start(message_text: str = "Bonjour 👋 Je suis ARC. Décris-
await cl.Message(content=message_text).send()
def sanitize_repo_name(name: str) -> str:
"""
Nettoie une chaîne pour la rendre 100% compatible avec un nom de dépôt Git.
Gère les accents, les espaces et supprime les caractères interdits.
"""
if not name:
return "mon-projet"
normalized = unicodedata.normalize('NFKD', name)
no_accent = normalized.encode('ascii', 'ignore').decode('utf-8')
lowered = no_accent.lower()
spaced_to_hyphen = lowered.replace(" ", "_")
cleaned = re.sub(r'[^a-z0-9-_]', '', spaced_to_hyphen)
cleaned = re.sub(r'-+', '-', cleaned)
cleaned = re.sub(r'_+', '_', cleaned)
return cleaned.strip('-_')
async def render_workflow_state(new_state: dict):
"""
Fonction centrale pour aiguiller l'affichage Chainlit
@@ -181,7 +201,7 @@ async def render_workflow_state(new_state: dict):
elif current_status == "spec_ready":
spec = new_state.get("spec", {})
summary = "### Éléments importants à retenir de ton projet :\n\n"
summary = "### Éléments importants à retenir de votre projet :\n\n"
summary += f"- **Nom du projet** : {spec.get('title')}\n"
summary += f"- **Description** : {spec.get('description')}\n"
summary += "- **Actions** :\n"
@@ -189,10 +209,12 @@ async def render_workflow_state(new_state: dict):
summary += "- **Contraintes** :\n"
summary += "\n".join(f" - {constraint}" for constraint in spec.get("constraints", [])) + "\n"
summary += f"- **Langage** : {spec.get('language')}\n\n"
summary += "**Est-ce que cela vous convient ?**"
summary += "💡 *(Le nom du projet indiqué pourra être modifié par la suite)*"
await cl.Message(content=summary).send()
res = await cl.AskActionMessage(
content=summary,
content="**Est-ce que cela vous convient ?**",
actions=[
cl.Action(name="oui", payload={"value": "oui"}, label="Oui, c'est parfait 👍"),
cl.Action(name="non", payload={"value": "non"}, label="Non, modifier ❌")
@@ -207,6 +229,34 @@ async def render_workflow_state(new_state: dict):
return
if res and res.get("name") == "oui":
proposed_title = spec.get('title', 'mon_projet')
name_choice = await cl.AskActionMessage(
content=f"Le nom proposé pour le projet est : **{proposed_title}**.\nSouhaitez-vous le conserver ou le modifier ?",
actions=[
cl.Action(name="garder_nom", payload={"value": "keep"}, label="Conserver ce nom 🏷️"),
cl.Action(name="modifier_nom", payload={"value": "change"}, label="Choisir un autre nom ✏️")
],
timeout=3600
).send()
if name_choice is None:
await cl.Message(content="⏰ **Session expirée.** Envoie un message pour reprendre.").send()
return
if name_choice.get("name") == "modifier_nom":
new_name_res = await cl.AskUserMessage(
content="Saisissez le nouveau nom de votre projet : 👇",
timeout=600
).send()
if new_name_res and new_name_res.get("output"):
raw_title = new_name_res["output"].strip()
custom_title = sanitize_repo_name(raw_title)
spec['title'] = custom_title
new_state['spec'] = spec
await cl.Message(content=f"🏷️ Nom du projet configuré sur : **{custom_title}**").send()
await cl.Message(content="🚀 **Spécifications validées !** Lancement de la génération du code...").send()
new_state["status"] = "spec_approved"
@@ -216,7 +266,7 @@ async def render_workflow_state(new_state: dict):
response = await client.post(
"http://127.0.0.1:8000/api/workflow/run",
json=new_state,
timeout=600.0
timeout=1200.0
)
final_state = response.json()
cl.user_session.set("graph_state", final_state)
@@ -228,7 +278,7 @@ async def render_workflow_state(new_state: dict):
cl.user_session.set("graph_state", new_state)
await cl.Message(
content="🔄 **Compris.** Qu'est-ce qui ne convient pas ? S'il te plaît, précise les éléments manquants ou à corriger :"
content="🔄 **Compris.** Qu'est-ce qui ne convient pas ? S'il vous plaît, précisez les éléments manquants ou à corriger :"
).send()
elif current_status in ["wait_human_review", "approved_by_human"]:
@@ -334,7 +384,7 @@ async def render_workflow_state(new_state: dict):
cl.user_session.set("graph_state", new_state)
async with httpx.AsyncClient() as client:
resp = await client.post("http://127.0.0.1:8000/api/workflow/run", json=new_state, timeout=600.0)
resp = await client.post("http://127.0.0.1:8000/api/workflow/run", json=new_state, timeout=1200.0)
final_state = resp.json()
cl.user_session.set("graph_state", final_state)
@@ -342,7 +392,7 @@ async def render_workflow_state(new_state: dict):
elif res and res.get("name") == "refuser_projet":
feedback_user = await cl.AskUserMessage(
content="Veuillez décrire les corrections ou les modifications à apporter au projet.",
timeout=600
timeout=1200
).send()
if feedback_user:
@@ -353,7 +403,7 @@ async def render_workflow_state(new_state: dict):
await cl.Message(content="🔄 **Feedback transmis.** Prise en compte des modifications...").send()
async with httpx.AsyncClient() as client:
resp = await client.post("http://127.0.0.1:8000/api/workflow/run", json=new_state, timeout=600.0)
resp = await client.post("http://127.0.0.1:8000/api/workflow/run", json=new_state, timeout=1200.0)
loop_state = resp.json()
cl.user_session.set("graph_state", loop_state)
@@ -392,13 +442,13 @@ async def render_workflow_state(new_state: dict):
if res.get("name") == "recommencer_projet":
reset_msg = (
"🔄 **Ancien projet supprimé avec succès.**\n\n"
"Faisons table rase ! Décris-moi ton besoin logiciel pour repartir sur de nouvelles bases : 👇"
"Faisons table rase ! Décrivez-moi votre besoin logiciel pour repartir sur de nouvelles bases : 👇"
)
await reset_to_start(reset_msg)
else:
reset_msg = (
"❌ **Session fermée et dépôt nettoyé.**\n\n"
"Si tu as un nouveau besoin à soumettre plus tard, envoie simplement un message pour démarrer."
"Si vous avez un nouveau besoin à soumettre plus tard, envoie simplement un message pour démarrer."
)
await reset_to_start(reset_msg)
@@ -466,7 +516,7 @@ async def on_message(message: cl.Message):
response = await client.post(
"http://127.0.0.1:8000/api/workflow/run",
json=state,
timeout=600.0
timeout=1200.0
)
new_state = response.json()