commit a7d8914e25fd4198e1fe2a2ee03fc69150aa1929 Author: Chevallier Date: Fri Jun 12 18:16:58 2026 +0200 first push diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..050fbba --- /dev/null +++ b/.gitignore @@ -0,0 +1,48 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class + +# Virtual environments +.venv/ +venv/ +env/ + +# Environment variables +.env +.env.* + +# Chainlit +.chainlit/ + +# Application files +.files/ + +# Tests / coverage +.pytest_cache/ +.coverage +htmlcov/ + +# Type checkers / linters +.mypy_cache/ +.ruff_cache/ +.pyre/ + +# IDEs +.vscode/ +.idea/ + +# OS files +.DS_Store +Thumbs.db + +# Logs +*.log + +# Build artifacts +build/ +dist/ +*.egg-info/ + +# Jupyter +.ipynb_checkpoints/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..8c7dba3 --- /dev/null +++ b/README.md @@ -0,0 +1,136 @@ +# Projet ARC + +## 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 multi‑agents avec états, transitions conditionnelles, persistance et human‑in‑the‑loop +- Python -> backend, les agents, les appels LLM, les tests, les embeddings et les intégrations + compatible avec autres tools +- Pydantic AI / Pydantic -> forcer l’Agent 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 d’embedding +- Redis (optionnel) -> cache de recherche ;sessions utilisateur ;état temporaire ;verrouillage d’un workflow ;file d’attente 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 multi‑agents avec états, transitions conditionnelles, persistance et human‑in‑the‑loop +- Mistral ou Gemma (modèle trop généraliste/léger->tache simple) -> DeepSeek Coder/Qwen2.5 +- vLLM -> meilleur choix qu’Ollama pour une plateforme plus industrialisée. Llama.cpp modèles quantifiés sur CPU ou machines modestes + moins adapté à une plateforme multi‑utilisateur +- 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 + +## Structure du projet +```bash +backend/ +│ +├── app/ +│ ├── api/ +│ │ ├── routes/ +│ │ │ ├── health.py +│ │ │ └── workflow.py +│ │ └── deps.py +│ │ +│ ├── core/ +│ │ ├── config.py +│ │ ├── logging.py +│ │ └── security.py +│ │ +│ ├── graph/ # LangGraph +│ │ ├── state.py +│ │ ├── nodes.py +│ │ └── workflow.py +│ │ +│ ├── agents/ # Gestion agents IA +│ │ ├── pm_agent.py +│ │ ├── dev_agent.py +│ │ └── qa_agent.py +│ │ +│ ├── schemas/ # Pydantic +│ │ ├── api.py +│ │ ├── spec.py +│ │ └── project.py +│ │ +│ ├── models/ # modèles métier / persistance (métadonnées d’un projet/version/statut/lien Git/hash/tags) +│ │ └── project.py +│ │ +│ ├── services/ +│ │ ├── workflow_service.py +│ │ ├── embedding_service.py +│ │ ├── retrieval_service.py +│ │ └── delivery_service.py +│ │ +│ ├── repositories/ # accès externes, Qdrant / Redis / stockage +│ │ ├── qdrant_repository.py +│ │ ├── redis_repository.py +│ │ └── project_repository.py +│ │ +│ ├── llm/ # appels modèles +│ │ ├── client.py # wrapper d’appel +│ │ ├── prompts.py # prompts centralisés +│ │ └── providers.py # Gemma/llama.cpp.... +│ │ +│ ├── sandbox/ +│ │ └── docker_runner.py +│ │ +│ ├── main.py +│ └── __init__.py +│ +├── chainlit_app.py +├── tests/ +│ ├── test_health.py +│ ├── test_workflow.py +│ └── test_agents.py +│ +├── .env +├── requirements.txt +├── Dockerfile +└── README.md +``` \ No newline at end of file diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..5025d63 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,23 @@ +FROM python:3.13.13 + +WORKDIR /workspace + +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + && rm -rf /var/lib/apt/lists/* + +COPY requirements.txt . +RUN pip install --no-cache-dir \ + --trusted-host pypi.org \ + --trusted-host pypi.python.org \ + --trusted-host files.pythonhosted.org \ + -r requirements.txt + +COPY . . + +RUN chmod +x start.sh + +EXPOSE 8000 +EXPOSE 8001 + +CMD ["./start.sh"] \ No newline at end of file diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 0000000..a51b031 --- /dev/null +++ b/backend/README.md @@ -0,0 +1,39 @@ +# ARC Backend + +Backend minimal pour le projet ARC : +- API FastAPI +- orchestration LangGraph +- agents PM / Dev / QA +- interface Chainlit +- intégration future Qdrant / Redis / vLLM + +## Installation + +```bash +python -m venv .venv +.venv\Scripts\activate +pip install -r requirements.txt +uvicorn app.main:app --reload --port 8000 +``` + +## Lancer Chainlit + +```bash +chainlit run chainlit_app.py --port 8001 +``` + +## Lancement auto + +```bash +docker compose up --build +``` + +## 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 \ No newline at end of file diff --git a/backend/app/agents/dev_agent.py b/backend/app/agents/dev_agent.py new file mode 100644 index 0000000..950b65a --- /dev/null +++ b/backend/app/agents/dev_agent.py @@ -0,0 +1,14 @@ +async def run_dev_agent(spec: dict, qa_feedback: list = None) -> dict: + """ + Agent Dev minimal : + - retourne une pseudo arborescence + un code exemple + """ + return { + "tree": [ + "main.py", + "README.md", + "app/__init__.py", + ], + "code": 'print("Hello from ARC generated project")', + "spec_title": spec.get("title"), + } \ No newline at end of file diff --git a/backend/app/agents/pm_agent.py b/backend/app/agents/pm_agent.py new file mode 100644 index 0000000..cfcfa00 --- /dev/null +++ b/backend/app/agents/pm_agent.py @@ -0,0 +1,15 @@ +from app.schemas.spec import ProjectSpec + + +async def run_pm_agent(user_input: str) -> ProjectSpec: + """ + Agent PM minimal : + - transforme l'entrée utilisateur en cahier des charges structuré + """ + return ProjectSpec( + title="Projet généré depuis demande utilisateur", + description=user_input, + requirements=["MVP minimal", "Architecture modulaire"], + constraints=["Python", "LangGraph", "Pydantic"], + target_stack="Python", + ) \ No newline at end of file diff --git a/backend/app/agents/qa_agent.py b/backend/app/agents/qa_agent.py new file mode 100644 index 0000000..39dd169 --- /dev/null +++ b/backend/app/agents/qa_agent.py @@ -0,0 +1,10 @@ +async def run_qa_agent(generated_code: dict) -> dict: + """ + Agent QA minimal : + - renvoie un statut de validation simulé + """ + return { + "status": "passed", + "logs": [], + "checked_files": generated_code.get("tree", []), + } \ No newline at end of file diff --git a/backend/app/api/deps.py b/backend/app/api/deps.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/api/routes/health.py b/backend/app/api/routes/health.py new file mode 100644 index 0000000..145e821 --- /dev/null +++ b/backend/app/api/routes/health.py @@ -0,0 +1,8 @@ +from fastapi import APIRouter + +router = APIRouter(tags=["health"]) + + +@router.get("/health") +def health(): + return {"status": "ok"} \ No newline at end of file diff --git a/backend/app/api/routes/workflow.py b/backend/app/api/routes/workflow.py new file mode 100644 index 0000000..8ac92f4 --- /dev/null +++ b/backend/app/api/routes/workflow.py @@ -0,0 +1,11 @@ +from fastapi import APIRouter +from app.schemas.api import WorkflowRequest, WorkflowResponse +from app.services.workflow_service import run_arc_workflow + +router = APIRouter(tags=["workflow"]) + + +@router.post("/workflow/run", response_model=WorkflowResponse) +async def run_workflow(payload: WorkflowRequest): + result = await run_arc_workflow(payload.user_input) + return WorkflowResponse(**result) \ No newline at end of file diff --git a/backend/app/core/config.py b/backend/app/core/config.py new file mode 100644 index 0000000..b598e0c --- /dev/null +++ b/backend/app/core/config.py @@ -0,0 +1,25 @@ +from pydantic_settings import BaseSettings, SettingsConfigDict + + +class Settings(BaseSettings): + app_name: str = "ARC Backend" + app_env: str = "dev" + app_host: str = "0.0.0.0" + app_port: int = 8000 + + qdrant_url: str = "http://localhost:6333" + qdrant_collection: str = "arc_projects" + + redis_url: str = "redis://localhost:6379/0" + + llm_base_url: str = "http://gemma-server:8080/v1" + llm_api_key: str = "llama-cpp-local" + # llm_model: str = "gemma-4-E4B-it-UD-Q4_K_XL.gguf" + + embedding_base_url: str = "http://localhost:8002/v1" + embedding_model: str = "snowflake-arctic-embed-m-v1.5" + + model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8") + + +settings = Settings() \ No newline at end of file diff --git a/backend/app/core/logging.py b/backend/app/core/logging.py new file mode 100644 index 0000000..5c35670 --- /dev/null +++ b/backend/app/core/logging.py @@ -0,0 +1,8 @@ +import logging + + +def setup_logging() -> None: + logging.basicConfig( + level=logging.INFO, + format="%(asctime)s | %(levelname)s | %(name)s | %(message)s", + ) \ No newline at end of file diff --git a/backend/app/core/security.py b/backend/app/core/security.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/graph/nodes.py b/backend/app/graph/nodes.py new file mode 100644 index 0000000..5e78a5e --- /dev/null +++ b/backend/app/graph/nodes.py @@ -0,0 +1,56 @@ +from app.agents.pm_agent import run_pm_agent +from app.agents.dev_agent import run_dev_agent +from app.agents.qa_agent import run_qa_agent +from app.services.retrieval_service import find_existing_project +from app.graph.state import WorkflowState + +async def pm_node(state: WorkflowState): + prompt = state["user_input"] + if state.get("user_feedback"): + prompt += f"\nRetour utilisateur pour correction : {state['user_feedback']}" + + spec = await run_pm_agent(prompt) + return { + "spec": spec.model_dump(), + "status": "spec_ready", + "loop_count": 0, + } + +async def retrieval_node(state: WorkflowState): + existing_project = await find_existing_project(state["user_input"]) + return { + "existing_project": existing_project, + "status": "existing_found" if existing_project else "no_existing_project", + } + +async def dev_node(state: WorkflowState): + qa_logs = state.get("qa_result", {}).get("logs", "") if state.get("qa_result") else None + + generated_code = await run_dev_agent(state["spec"], qa_feedback=qa_logs) + return { + "generated_code": generated_code, + "status": "code_generated", + } + +async def qa_node(state: WorkflowState): + qa_result = await run_qa_agent(state["generated_code"]) + current_loops = state.get("loop_count", 0) + + is_success = True + + clean_qa_result = {"success": is_success, "raw": qa_result} + + return { + "qa_result": clean_qa_result, + "loop_count": current_loops if is_success else current_loops + 1, + "status": "qa_done", + } + +async def human_review_node(state: WorkflowState): + print("[Human Review] Passage en mode automatique (Mock)...") + + return { + "existing_project_approved": True, + "is_completed": True, + "status": "approved_by_human" + } \ No newline at end of file diff --git a/backend/app/graph/state.py b/backend/app/graph/state.py new file mode 100644 index 0000000..bdbe1c1 --- /dev/null +++ b/backend/app/graph/state.py @@ -0,0 +1,13 @@ +from typing import TypedDict, Optional, Any, Dict + +class WorkflowState(TypedDict, total=False): + user_input: str + spec: dict + existing_project: Optional[dict] + existing_project_approved: Optional[bool] # Choix utilisateur si projet similaire trouvé + generated_code: Optional[Dict[str, str]] # Arborescence et code + qa_result: Optional[dict] # Contient les clés 'success' et 'logs' + loop_count: int # Compteur pour la Loop 1 (Dev <-> QA) + user_feedback: Optional[str] # Retours si l'utilisateur refuse le code final + is_completed: bool # Statut de livraison finale + status: str \ No newline at end of file diff --git a/backend/app/graph/workflow.py b/backend/app/graph/workflow.py new file mode 100644 index 0000000..385594d --- /dev/null +++ b/backend/app/graph/workflow.py @@ -0,0 +1,92 @@ +import warnings +from langchain_core._api.deprecation import LangChainPendingDeprecationWarning +warnings.filterwarnings("ignore", category=LangChainPendingDeprecationWarning) + +from langgraph.graph import StateGraph, END +from app.graph.state import WorkflowState +from app.graph.nodes import ( + pm_node, + retrieval_node, + dev_node, + qa_node, + human_review_node, +) + +# --- Fonctions de Routage (Conditional Edges) --- + +def route_after_retrieval(state: WorkflowState): + # Si un projet existe, on demande d'abord à l'humain (via le nœud de review) + if state.get("existing_project"): + return "human_review" + return "dev" + +def route_after_qa(state: WorkflowState): + qa_res = state.get("qa_result", {}) + + # Loop 1 : Si échec des tests ET qu'on a pas dépassé 3 essais -> On renvoie chez le Dev + if not qa_res.get("success") and state.get("loop_count", 0) < 3: + return "dev" + + # Si c'est vert (ou trop d'échecs), on présente le résultat à l'utilisateur + # EXTENSION FUTURE : si trop d'échecs, on pourrait envoyer à une IA plus puissante + return "human_review" + +def route_after_human(state: WorkflowState): + # Cas d'un projet existant proposé + if state.get("existing_project") and not state.get("generated_code"): + if state.get("existing_project_approved") == True: + return END # L'utilisateur est satisfait du projet existant + return "dev" # L'utilisateur refuse l'existant, on génère du neuf + + # Cas du code généré + if state.get("is_completed") == True: + return END + + # Si l'utilisateur a refusé le code -> Retour à la case PM avec ses commentaires + return "pm" + +# --- Assemblage du Graphe --- + +graph = StateGraph(WorkflowState) + +graph.add_node("pm", pm_node) +graph.add_node("retrieval", retrieval_node) +graph.add_node("dev", dev_node) +graph.add_node("qa", qa_node) +graph.add_node("human_review", human_review_node) + +graph.set_entry_point("pm") +graph.add_edge("pm", "retrieval") + +# Étape 1 : Choix après recherche vectorielle +graph.add_conditional_edges( + "retrieval", + route_after_retrieval, + { + "dev": "dev", + "human_review": "human_review", + }, +) + +# Étape 2 & 3 : Boucle Dev <-> QA (Loop 1) +graph.add_edge("dev", "qa") +graph.add_conditional_edges( + "qa", + route_after_qa, + { + "dev": "dev", + "human_review": "human_review", + }, +) + +# Étape 4 : Boucle de Feedback Humain (Loop 2) ou Clôture +graph.add_conditional_edges( + "human_review", + route_after_human, + { + "pm": "pm", + "dev": "dev", + END: END, + }, +) +compiled_graph = graph.compile() \ No newline at end of file diff --git a/backend/app/llm/client.py b/backend/app/llm/client.py new file mode 100644 index 0000000..28ae1a9 --- /dev/null +++ b/backend/app/llm/client.py @@ -0,0 +1,12 @@ +from openai import AsyncOpenAI +from app.core.config import settings + +def get_llm_client() -> AsyncOpenAI: + """ + Initialise le client de génération (LLM) compatible OpenAI. + Configuré pour pointer vers notre instance locale llama.cpp (Gemma 4). + """ + return AsyncOpenAI( + base_url=settings.llm_base_url, + api_key=settings.llm_api_key, + ) \ No newline at end of file diff --git a/backend/app/llm/prompts.py b/backend/app/llm/prompts.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/llm/providers.py b/backend/app/llm/providers.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/main.py b/backend/app/main.py new file mode 100644 index 0000000..9c50627 --- /dev/null +++ b/backend/app/main.py @@ -0,0 +1,34 @@ +from fastapi import FastAPI +from contextlib import asynccontextmanager +from app.api.routes.health import router as health_router +from app.api.routes.workflow import router as workflow_router +from app.core.config import settings +from app.core.logging import setup_logging +from app.repositories.qdrant_repository import QdrantRepository + +setup_logging() + +@asynccontextmanager +async def lifespan(app: FastAPI): + print("[Startup] Initialisation automatique de Qdrant dans Docker...") + qdrant_repo = QdrantRepository() + try: + await qdrant_repo.init_collection(vector_size=1024) + except Exception as e: + print(f"[Startup] Erreur lors de l'initialisation de Qdrant : {e}") + yield + + print("[Shutdown] Fermeture propre de la connexion Qdrant...") + await qdrant_repo.close() + + +app = FastAPI( + title=settings.app_name, + docs_url=None, + redoc_url=None, + openapi_url=None, + lifespan=lifespan +) + +app.include_router(health_router, prefix="/api") +app.include_router(workflow_router, prefix="/api") \ No newline at end of file diff --git a/backend/app/models/project.py b/backend/app/models/project.py new file mode 100644 index 0000000..da8a973 --- /dev/null +++ b/backend/app/models/project.py @@ -0,0 +1,10 @@ +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 \ No newline at end of file diff --git a/backend/app/repositories/project_repository.py b/backend/app/repositories/project_repository.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/repositories/qdrant_repository.py b/backend/app/repositories/qdrant_repository.py new file mode 100644 index 0000000..32748d6 --- /dev/null +++ b/backend/app/repositories/qdrant_repository.py @@ -0,0 +1,58 @@ +# backend/app/repositories/qdrant_repository.py +from typing import Optional, List +from qdrant_client import AsyncQdrantClient +from qdrant_client.http import models +from app.core.config import settings + +class QdrantRepository: + def __init__(self): + # Initialisation du client asynchrone + self.client = AsyncQdrantClient( + url=settings.qdrant_url, + # api_key=getattr(settings, "qdrant_api_key", None) # Qdrant Cloud + ) + self.collection_name = settings.qdrant_collection + + async def init_collection(self, vector_size: int = 1024): + """ + Crée la collection si elle n'existe pas encore. + 1024 correspond à la taille des vecteurs de Snowflake Arctic Embed 2.0 (large). + """ + exists = await self.client.collection_exists(collection_name=self.collection_name) + if not exists: + print(f"[Qdrant] Création de la collection '{self.collection_name}'...") + await self.client.create_collection( + collection_name=self.collection_name, + vectors_config=models.VectorParams( + size=vector_size, + distance=models.Distance.COSINE + ) + ) + print("[Qdrant] Collection créée avec succès.") + else: + print(f"[Qdrant] La collection '{self.collection_name}' existe déjà.") + + async def search_similar_project(self, query_vector: List[float], limit: int = 1) -> Optional[dict]: + """ + Effectue la vraie recherche vectorielle. + Note : On passe un 'query_vector' (généré par ton embedding_service) et non du texte brut. + """ + try: + results = await self.client.search( + collection_name=self.collection_name, + query_vector=query_vector, + limit=limit + ) + + if results: + # On retourne le payload (les métadonnées du projet) du meilleur match + return results[0].payload + return None + + except Exception as e: + print(f"[Qdrant] Erreur lors de la recherche : {e}") + return None + + async def close(self): + """Ferme proprement la connexion au client""" + await self.client.close() \ No newline at end of file diff --git a/backend/app/repositories/redis_repository.py b/backend/app/repositories/redis_repository.py new file mode 100644 index 0000000..8c56adc --- /dev/null +++ b/backend/app/repositories/redis_repository.py @@ -0,0 +1,13 @@ +from app.core.config import settings + + +class RedisRepository: + """ + Stub minimal Redis (optionnel). + """ + + def __init__(self): + self.url = settings.redis_url + + async def ping(self) -> bool: + return True \ No newline at end of file diff --git a/backend/app/sandbox/docker_runner.py b/backend/app/sandbox/docker_runner.py new file mode 100644 index 0000000..ab2859c --- /dev/null +++ b/backend/app/sandbox/docker_runner.py @@ -0,0 +1,8 @@ +async def run_in_sandbox(code: str) -> dict: + """ + Stub minimal pour future exécution sécurisée dans Docker. + """ + return { + "status": "not_implemented", + "logs": ["Sandbox Docker non branchée à l'étape 0."], + } diff --git a/backend/app/schemas/api.py b/backend/app/schemas/api.py new file mode 100644 index 0000000..db28bb9 --- /dev/null +++ b/backend/app/schemas/api.py @@ -0,0 +1,14 @@ +from pydantic import BaseModel +from typing import Optional, Any + + +class WorkflowRequest(BaseModel): + user_input: str + + +class WorkflowResponse(BaseModel): + status: str + spec: Optional[dict] = None + existing_project: Optional[dict] = None + generated_code: Optional[Any] = None + qa_result: Optional[Any] = None \ No newline at end of file diff --git a/backend/app/schemas/project.py b/backend/app/schemas/project.py new file mode 100644 index 0000000..137a39e --- /dev/null +++ b/backend/app/schemas/project.py @@ -0,0 +1,10 @@ +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 \ No newline at end of file diff --git a/backend/app/schemas/spec.py b/backend/app/schemas/spec.py new file mode 100644 index 0000000..f76e00a --- /dev/null +++ b/backend/app/schemas/spec.py @@ -0,0 +1,10 @@ +from pydantic import BaseModel, Field +from typing import List, Optional + + +class ProjectSpec(BaseModel): + title: str = Field(default="Projet ARC") + description: str + requirements: List[str] = Field(default_factory=list) + constraints: List[str] = Field(default_factory=list) + target_stack: Optional[str] = "Python" \ No newline at end of file diff --git a/backend/app/services/delivery_service.py b/backend/app/services/delivery_service.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/services/embedding_service.py b/backend/app/services/embedding_service.py new file mode 100644 index 0000000..66891a8 --- /dev/null +++ b/backend/app/services/embedding_service.py @@ -0,0 +1,40 @@ +import httpx +from app.core.config import settings + + +async def build_embedding(text: str) -> dict: + """ + Génère un vecteur d'embedding en interrogeant le conteneur local llama.cpp + """ + url = f"{settings.embedding_base_url}/embeddings" + + headers = { + "Content-Type": "application/json" + } + + payload = { + "input": text, + "model": settings.embedding_model + } + + async with httpx.AsyncClient(timeout=30.0) as client: + try: + response = await client.post(url, json=payload, headers=headers) + response.raise_for_status() + + data = response.json() + vector = data["data"][0]["embedding"] + + return { + "model": settings.embedding_model, + "text_length": len(text), + "vector": vector, + } + + except httpx.HTTPError as e: + print(f"Erreur lors de la génération de l'embedding : {e}") + return { + "model": settings.embedding_model, + "text_length": len(text), + "vector": [], + } \ No newline at end of file diff --git a/backend/app/services/retrieval_service.py b/backend/app/services/retrieval_service.py new file mode 100644 index 0000000..dfe418e --- /dev/null +++ b/backend/app/services/retrieval_service.py @@ -0,0 +1,11 @@ +from app.repositories.qdrant_repository import QdrantRepository +from app.services.embedding_service import build_embedding + + +qdrant_repository = QdrantRepository() + + +async def find_existing_project(user_input: str): + # query_vector = await build_embedding.get_mesh_embedding(user_input) + dummy_vector = [0.0] * 1024 # A modifier avec un vrai embedding plus tard TODO + return await qdrant_repository.search_similar_project(query_vector=dummy_vector) \ No newline at end of file diff --git a/backend/app/services/workflow_service.py b/backend/app/services/workflow_service.py new file mode 100644 index 0000000..ac21dd0 --- /dev/null +++ b/backend/app/services/workflow_service.py @@ -0,0 +1,6 @@ +from app.graph.workflow import compiled_graph + + +async def run_arc_workflow(user_input: str) -> dict: + result = await compiled_graph.ainvoke({"user_input": user_input}) + return result \ No newline at end of file diff --git a/backend/chainlit.md b/backend/chainlit.md new file mode 100644 index 0000000..e69de29 diff --git a/backend/chainlit_app.py b/backend/chainlit_app.py new file mode 100644 index 0000000..8083e34 --- /dev/null +++ b/backend/chainlit_app.py @@ -0,0 +1,25 @@ +import chainlit as cl +import httpx +import json + + +@cl.on_chat_start +async def on_chat_start(): + await cl.Message( + content="Bonjour 👋 Je suis ARC. Décris-moi ton besoin logiciel." + ).send() + + +@cl.on_message +async def on_message(message: cl.Message): + async with httpx.AsyncClient() as client: + response = await client.post( + "http://127.0.0.1:8000/api/workflow/run", + json={"user_input": message.content}, + ) + + result = response.json() + + await cl.Message( + content=f"Résultat workflow :\n```json\n{json.dumps(result, indent=2, ensure_ascii=False)}\n```" + ).send() \ No newline at end of file diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml new file mode 100644 index 0000000..a138ca4 --- /dev/null +++ b/backend/docker-compose.yml @@ -0,0 +1,102 @@ +services: + qdrant: + image: qdrant/qdrant:latest + container_name: qdrant-arc + ports: + - "6333:6333" + - "6334:6334" + environment: + - QDRANT__TELEMETRY_DISABLED=true + volumes: + - qdrant_storage:/qdrant/storage + networks: + - arc-network + + download-model: + image: alpine:latest + container_name: download-embedding-model + volumes: + - model_storage:/models + command: > + sh -c " + if [ ! -f /models/snowflake-arctic-embed-m-v1.5-f16.gguf ]; then + echo 'Téléchargement du modèle (Contournement SSL Proxy activé)...'; + wget --no-check-certificate 'https://huggingface.co/Snowflake/snowflake-arctic-embed-m-v1.5/resolve/main/gguf/snowflake-arctic-embed-m-v1.5-f16.gguf' -O /models/snowflake-arctic-embed-m-v1.5-f16.gguf; + echo 'Téléchargement terminé avec succès !'; + else + echo 'Le modèle est déjà présent.'; + fi + " + + embedding-server: + image: ghcr.io/ggml-org/llama.cpp:server + container_name: embedding-arc + volumes: + - model_storage:/models + ports: + - "8002:8080" + command: "-m /models/snowflake-arctic-embed-m-v1.5-f16.gguf --embedding --host 0.0.0.0 --port 8080" + restart: unless-stopped + networks: + - arc-network + depends_on: + download-model: + condition: service_completed_successfully + + download-gemma: + image: alpine:latest + container_name: download-gemma-model + volumes: + - model_storage:/models + command: > + sh -c " + if [ ! -f /models/gemma-4-E4B-it-UD-Q4_K_XL.gguf ]; then + echo 'Téléchargement de Gemma 4 (Contournement SSL Proxy)...'; + wget --no-check-certificate 'https://huggingface.co/unsloth/gemma-4-E4B-it-GGUF/resolve/main/gemma-4-E4B-it-UD-Q4_K_XL.gguf' -O /models/gemma-4-E4B-it-UD-Q4_K_XL.gguf; + echo 'Téléchargement de Gemma 4 terminé !'; + else + echo 'Le modèle Gemma 4 est déjà présent.'; + fi + " + + gemma-server: + image: ghcr.io/ggml-org/llama.cpp:server + container_name: gemma-arc + volumes: + - model_storage:/models + ports: + - "8003:8080" + command: "-m /models/gemma-4-E4B-it-UD-Q4_K_XL.gguf --host 0.0.0.0 --port 8080 -c 4096" + restart: unless-stopped + networks: + - arc-network + depends_on: + download-gemma: + condition: service_completed_successfully + + app: + build: . + container_name: arc-app + ports: + - "8000:8000" + - "8001:8001" + volumes: + - .:/workspace + environment: + - PYTHONPATH=/workspace + - QDRANT_URL=http://qdrant:6333 + - QDRANT_COLLECTION=arc_projects + - EMBEDDING_SERVER_URL=http://embedding-server:8080 + depends_on: + - qdrant + - embedding-server + networks: + - arc-network + +volumes: + qdrant_storage: + model_storage: + +networks: + arc-network: + driver: bridge \ No newline at end of file diff --git a/backend/public/logo_dark.png b/backend/public/logo_dark.png new file mode 100644 index 0000000..1ac95bb Binary files /dev/null and b/backend/public/logo_dark.png differ diff --git a/backend/public/logo_light.png b/backend/public/logo_light.png new file mode 100644 index 0000000..1ac95bb Binary files /dev/null and b/backend/public/logo_light.png differ diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..1874dbe --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,16 @@ +fastapi==0.117.0 +uvicorn[standard]==0.35.0 +anyio>=4.6.0 +pydantic==2.12 +pydantic-settings==2.10.1 +langgraph==0.2.39 +chainlit==2.11.0 +qdrant-client==1.11.3 +redis==5.0.8 +httpx==0.27.2 +openai==1.51.2 +python-dotenv==1.0.1 +pytest==8.3.3 +ruff==0.6.8 +bandit==1.7.10 +requests \ No newline at end of file diff --git a/backend/start.sh b/backend/start.sh new file mode 100644 index 0000000..43ff7cc --- /dev/null +++ b/backend/start.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +echo "Démarrage du Backend FastAPI sur le port 8000..." +uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload & + +echo "Démarrage de Chainlit sur le port 8001..." +chainlit run chainlit_app.py --host 0.0.0.0 --port 8001 \ No newline at end of file diff --git a/backend/tests/test_agents.py b/backend/tests/test_agents.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/tests/test_gemma.py b/backend/tests/test_gemma.py new file mode 100644 index 0000000..12d2091 --- /dev/null +++ b/backend/tests/test_gemma.py @@ -0,0 +1,28 @@ +import requests + +def tester_gemma(): + url = "http://localhost:8003/v1/chat/completions" + + payload = { + "messages": [ + {"role": "user", "content": "Donne-moi une astuce de code Python originale."} + ], + "temperature": 0.7 + } + + print("🧠 Envoi de la requête à Gemma 4...") + try: + response = requests.post(url, json=payload) + response.raise_for_status() + answer = response.json()["choices"][0]["message"]["content"] + + print("\n🤖 Réponse de Gemma 4 :") + print("-" * 40) + print(answer) + print("-" * 40) + + except Exception as e: + print(f"❌ Erreur : {e}") + +if __name__ == "__main__": + tester_gemma() \ No newline at end of file diff --git a/backend/tests/test_health.py b/backend/tests/test_health.py new file mode 100644 index 0000000..f50b1cd --- /dev/null +++ b/backend/tests/test_health.py @@ -0,0 +1,10 @@ +from fastapi.testclient import TestClient +from app.main import app + +client = TestClient(app) + + +def test_health(): + response = client.get("/api/health") + assert response.status_code == 200 + assert response.json()["status"] == "ok" \ No newline at end of file diff --git a/backend/tests/test_qdrant.py b/backend/tests/test_qdrant.py new file mode 100644 index 0000000..4b627bf --- /dev/null +++ b/backend/tests/test_qdrant.py @@ -0,0 +1,55 @@ +import asyncio +import random +from app.repositories.qdrant_repository import QdrantRepository +from qdrant_client.http import models + +async def test_pipeline(): + print("--- Test de connexion Qdrant ---") + repo = QdrantRepository() + + try: + # 1. Tester la connexion et initialiser la collection + await repo.init_collection(vector_size=1024) + + # 2. Insérer un faux projet pour valider le fonctionnement (Upsert) + print("\n[Test] Insertion d'un faux projet indexé...") + mock_vector = [random.uniform(-1.0, 1.0) for _ in range(1024)] + + await repo.client.upsert( + collection_name=repo.collection_name, + points=[ + models.PointStruct( + id=1, + vector=mock_vector, + payload={ + "title": "Application E-commerce de test", + "description": "Un projet test généré pour valider Qdrant", + "git_url": "https://github.com/test/test" + } + ) + ] + ) + print("[Test] Faux projet inséré.") + + # 3. Tester la recherche vectorielle + print("\n[Test] Lancement de la recherche vectorielle...") + project_found = await repo.search_similar_project(query_vector=mock_vector) + + if project_found: + print(f"🎉 Succès ! Projet trouvé en BDD : {project_found['title']} ({project_found['git_url']})") + else: + print("❌ Erreur : Aucun projet trouvé alors qu'on vient d'en insérer un.") + + except Exception as e: + print(f"❌ Échec critique du test : {e}") + print("Vérifie que ton conteneur Qdrant est bien lancé et que l'URL dans ton .env est correcte.") + + finally: + await repo.close() + print("\n--- Fin du test ---") + +if __name__ == "__main__": + from dotenv import load_dotenv + load_dotenv() + + asyncio.run(test_pipeline()) \ No newline at end of file diff --git a/backend/tests/test_snowflake.py b/backend/tests/test_snowflake.py new file mode 100644 index 0000000..8fe0c3c --- /dev/null +++ b/backend/tests/test_snowflake.py @@ -0,0 +1,42 @@ +import requests +import json + +def test_embedding_server(): + url = "http://localhost:8002/v1/embeddings" + + phrase = "Ceci est un test." + + payload = { + "input": phrase + } + + headers = { + "Content-Type": "application/json" + } + + print("Envoi de la phrase au serveur Snowflake Arctic local...") + + try: + response = requests.post(url, json=payload, headers=headers) + + response.raise_for_status() + + resultat = response.json() + + vecteur = resultat["data"][0]["embedding"] + tokens_utilises = resultat["usage"]["total_tokens"] + + print("\n[SUCCÈS] Le serveur d'embedding répond parfaitement !") + print(f"Texte analysé : '{phrase}'") + print(f"Nombre de tokens consommés : {tokens_utilises}") + print(f"Dimension du vecteur : {len(vecteur)} (Attendu : 768)") + print(f"Début du vecteur (5 premiers chiffres) : {vecteur[:5]}") + + except requests.exceptions.ConnectionError: + print("\n[ERREUR] Impossible de joindre le serveur d'embedding.") + print("Vérifie que ton Docker Compose est bien démarré avec 'docker compose up'.") + except Exception as e: + print(f"\n[ERREUR] Une erreur inattendue est survenue : {e}") + +if __name__ == "__main__": + test_embedding_server() \ No newline at end of file diff --git a/backend/tests/test_workflow.py b/backend/tests/test_workflow.py new file mode 100644 index 0000000..e69de29 diff --git a/ressources/Etape0.md b/ressources/Etape0.md new file mode 100644 index 0000000..f8c1465 --- /dev/null +++ b/ressources/Etape0.md @@ -0,0 +1,42 @@ +# Projet ARC +```mermaid +gantt + title Section 0 - Préparation de l'environnement du projet + dateFormat YYYY-MM-DD + axisFormat %d/%m + excludes weekends + todayMarker off + + section Initialisation + Créer structure projet :a1, 2026-06-09, 0.5d + Backend + structure :a2, 2026-06-09, 0.5d + + section LangGraph + Installer LangGraph :b1, 2026-06-09, 0.5d + Créer structure workflow :b2, 2026-06-09, 1d + Définir state global :b3, 2026-06-09, 1d + + section Setup Qdrant + Installer Qdrant :c1, after b3, 0.5d + Tester connexion Python :c2, after b3, 0.5d + + section Setup modèle d’embedding + Installer Snowflake :d1, after b3, 0.5d + Implémenter embedding() :d2, after b3, 0.5d + + section Setup LLM + Installer llama.cpp :e1, after b3, 0.5d + Tester appel modèle :e2, after b3, 0.5d + + section Setup Chainlit + Installer Chainlit :f1, after e2, 0.5d + Lancer app test :f2, after e2, 0.5d + + section Organisation du code + Créer dossiers agents :g1, after e2, 0.5d + Créer dossiers services :g2, after e2, 0.5d + + section Logging et debug + Logs simples :h1, after e2, 0.5d + Structuration logs :h2, after e2, 0.5d +``` \ No newline at end of file diff --git a/ressources/Etape1.md b/ressources/Etape1.md new file mode 100644 index 0000000..a69a9db --- /dev/null +++ b/ressources/Etape1.md @@ -0,0 +1,53 @@ +# Projet ARC +```mermaid +gantt + title Section 1 - Analyse du Besoin & Qualification + dateFormat YYYY-MM-DD + axisFormat %d/%m + excludes weekends + todayMarker off + + section Interface utilisateur + Créer interface Chainlit :a1, 2026-06-12, 1d + Connecter Chainlit → backend Python :a2, 2026-06-12, 0.5d + + section Modèle cahier des charges + Définir structure JSON :b1, after a2, 0.5d + Schéma Pydantic :b2, after a2, 1d + + section Agent PM + Prompt Agent PM :c1, after b2, 0.5d + Sortie structurée Pydantic :c2, after b2, 0.5d + Gestion erreurs :c3, after c2, 0.5d + + section Questions clarificatrices + Détection champs manquants :d1, after c2, 0.5d + Génération questions LLM :d2, after d1, 0.5d + Boucle interaction Chainlit :d3, after d1, 1d + + section Validation du cahier des charges + Affichage CDC :e1, after d3, 0.5d + Boutons validation/refus :e2, after d3, 0.5d + + section Qdrant (BDD vectorielle) + Installer Qdrant :f1, after e2, 0.5d + Créer collection :f2, after e2, 0.5d + Structure payload :f3, after f2, 0.5d + + section Embedding + Intégrer Snowflake Arctic :g1, after f2, 0.5d + Fonction embedding :g2, after f2, 0.5d + + section Recherche d'existant + Recherche projets similaires :h1, after g2, 0.5d + Filtres payload :h2, after g2, 0.5d + Formatage résultats :h3, after h2, 0.5d + + section Proposition utilisateur + Affichage résultats Chainlit :i1, after h2, 0.5d + Bouton "utiliser projet" :i2, after h2, 0.5d + Bouton "continuer génération" :i3, after h2, 0.5d + + section Redis (optionnel) + Cache recherche :j1, after i3, 1d +``` \ No newline at end of file diff --git a/ressources/Etape2.md b/ressources/Etape2.md new file mode 100644 index 0000000..b598c12 --- /dev/null +++ b/ressources/Etape2.md @@ -0,0 +1,50 @@ +# Projet ARC +```mermaid +gantt + title Section 2 - Génération de Code + dateFormat YYYY-MM-DD + axisFormat %d/%m + excludes weekends + todayMarker off + + section Préparation des entrées + Récupérer cahier des charges :a1, 2026-06-29, 0.5d + Normaliser / valider données :a2, 2026-06-29, 0.5d + + section Format de sortie du code + Structure projet JSON/Pydantic :b1, after a2, 0.5d + Modèle Pydantic sortie code :b2, after a2, 1d + + section Prompt Agent Dev + Prompt génération code :c1, after b2, 1d + Contraintes strictes :c2, after b2, 0.5d + + section Intégration LLM + Setup llama.cpp :d1, after c2, 0.5d + Connexion LangGraph → LLM :d2, after c2, 0.5d + + section Arborescence projet + Génération structure fichiers :e1, after d2, 0.5d + Vérification structure :e2, after d2, 0.5d + + section Génération code source + Génération fichiers Python :f1, after e2, 2d + Conformité structure :f2, after f1, 1d + + section Fichiers complémentaires + README.md :g1, after f2, 0.5d + requirements.txt :g2, after f2, 0.5d + Instructions exécution :g3, after f2, 0.5d + + section Validation backend + Vérification fichiers :h1, after g3, 0.5d + Nettoyage output LLM :h2, after g3, 0.5d + + section Intégration LangGraph + Ajouter noeud Agent Dev :i1, after h2, 0.5d + Connecter PM → Dev :i2, after h2, 0.5d + + section Préparation QA + Formatter code sandbox :j1, after i2, 0.5d + Transmettre au state :j2, after i2, 0.5d +``` \ No newline at end of file diff --git a/ressources/Etape3.md b/ressources/Etape3.md new file mode 100644 index 0000000..7828570 --- /dev/null +++ b/ressources/Etape3.md @@ -0,0 +1,59 @@ +# Projet ARC +```mermaid +gantt + title Section 3 - Tests et Assurance Qualité + dateFormat YYYY-MM-DD + axisFormat %d/%m + excludes weekends + todayMarker off + + section Préparation entrées + Récupérer projet Agent Dev :a1, 2026-07-15, 0.5d + + section Sandbox Docker + Dockerfile générique :b1, 2026-07-15, 1d + Script build/run :b2, after b1, 1d + Isolation environnement :b3, after b1, 0.5d + + section Exécution sandbox + Lancer exécution :c1, after b3, 0.5d + Capturer logs :c2, after b3, 0.5d + + section Ruff (qualité) + Installer Ruff :d1, after c2, 0.5d + Ruff check :d2, after c2, 0.5d + + section Bandit (sécurité) + Installer Bandit :e1, after c2, 0.5d + Scan projet :e2, after c2, 0.5d + + section Semgrep (optionnel) + Installer Semgrep :f1, after c2, 0.5d + Analyse règles :f2, after c2, 0.5d + + section Structuration QA + Modèle Pydantic rapport :g1, after f2, 1d + Parser résultats outils :g2, after g1, 0.5d + + section Agent QA + Prompt Agent QA :h1, after g2, 1d + Résumé intelligible :h2, after h1, 0.5d + Traduction erreurs → dev :h3, after h2, 1d + + section Boucle Dev ↔ QA + Implémenter boucle LangGraph :i1, after h3, 1.5d + Condition stop :i2, after h3, 0.5d + Limite itérations :i3, after h3, 1d + + section Logs pour correction + Structurer logs :j1, after i3, 0.5d + Injecter logs dans Agent Dev :j2, after i3, 0.5d + + section Intégration LangGraph + Ajouter noeud QA :k1, after j2, 0.5d + Connecter Dev → QA → loop :k2, after j2, 0.5d + + section Sécurisation minimale + Limiter temps exécution :l1, after k2, 0.5d + Bloquer accès disque/réseau :l2, after k2, 0.5d +``` \ No newline at end of file diff --git a/ressources/Etape4.md b/ressources/Etape4.md new file mode 100644 index 0000000..5696460 --- /dev/null +++ b/ressources/Etape4.md @@ -0,0 +1,53 @@ +# Projet ARC +```mermaid +gantt + title Section 4 - Livraison & Feedback Utilisateur + dateFormat YYYY-MM-DD + axisFormat %d/%m + excludes weekends + todayMarker off + + section Préparation affichage + Récupérer code validé :a1, 2026-08-03, 0.5d + Récupérer rapport QA :a2, 2026-08-03, 0.5d + + section Affichage Chainlit + Afficher code :b1, after a2, 0.5d + Afficher rapport QA :b2, after a2, 0.5d + Afficher instructions exécution :b3, after a2, 0.5d + + section Actions utilisateur + Bouton valider :c1, after a2, 0.5d + Bouton refuser :c2, after a2, 0.5d + + section Gestion refus + Champ feedback :d1, after c2, 0.5d + Structuration Pydantic :d2, after c2, 0.5d + Injection LangGraph :d3, after c2, 0.5d + + section Boucle retour PM + Transition QA → PM :e1, after d3, 0.5d + Conserver contexte + feedback :e2, after d3, 0.5d + + section Génération ZIP + Créer archive :f1, after e2, 0.5d + Vérifier structure :f2, after e2, 0.5d + + section Téléchargement + Bouton téléchargement ZIP :g1, after e2, 0.5d + + section Sauvegarde projet + Sauvegarder code + metadata :h1, after g1, 0.5d + Flag validated :h2, after g1, 0.5d + + section Réindexation Qdrant + Générer embedding :i1, after h2, 0.5d + Ajouter dans Qdrant :i2, after h2, 0.5d + + section Git (optionnel) + Init dépôt :j1, after i2, 0.5d + + section Intégration finale LangGraph + Ajouter noeud Delivery :k1, after i2, 0.5d + Connecter QA → Delivery → fin :k2, after i2, 0.5d +``` \ No newline at end of file diff --git a/ressources/EtapeFinale.md b/ressources/EtapeFinale.md new file mode 100644 index 0000000..25006d0 --- /dev/null +++ b/ressources/EtapeFinale.md @@ -0,0 +1,13 @@ +# Projet ARC +```mermaid +gantt + title Section finale - Rendu + dateFormat YYYY-MM-DD + axisFormat %d/%m + excludes weekends + todayMarker off + + Tests fonctionnels de bout en bout :a1, 2026-08-13, 0.5d + Documentation finale :a2, 2026-08-13, 0.5d + Présentation :a3, after a2, 1d +``` \ No newline at end of file diff --git a/ressources/Projet.md b/ressources/Projet.md new file mode 100644 index 0000000..25ce03b --- /dev/null +++ b/ressources/Projet.md @@ -0,0 +1,88 @@ +### É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 multi‑agents avec états, transitions conditionnelles, persistance et human‑in‑the‑loop +- Python -> backend, les agents, les appels LLM, les tests, les embeddings et les intégrations + compatible avec autres tools +- Pydantic AI / Pydantic -> forcer l’Agent 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 d’embedding +- Redis (optionnel) -> cache de recherche ;sessions utilisateur ;état temporaire ;verrouillage d’un workflow ;file d’attente simple + +Tâches : +- Interface Chainlit +- Créer le prompt Agent PM +- Créer le schéma Pydantic du cahier des charges +- Gérer les questions clarificatrices +- Valider le cahier des charges +- Recherche d’existant (Qdrant/Snowflake Arctic Embed 2.0/Redis) +___ + +### É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 multi‑agents avec états, transitions conditionnelles, persistance et human‑in‑the‑loop +- Mistral ou Gemma (modèle trop généraliste/léger->tache simple) -> DeepSeek Coder/Qwen2.5 +- vLLM -> meilleur choix qu’Ollama pour une plateforme plus industrialisée. Llama.cpp modèles quantifiés sur CPU ou machines modestes + moins adapté à une plateforme multi‑utilisateur +- Pydantic + +Tâches : +- Créer le prompt Agent Dev +- Partie dev +___ + +### É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 X fois jusqu'à ce que le code soit "vert". + +Outils : +- Docker +- Ruff -> qualité de code +- Bandit -> sécurité +- Semgrep (optionnel) -> règles de sécurité et qualité plus larges + Boucle correction : +- LangGraph +- Pydantic + +Tâches : +- QA et sandbox +- Intégration Ruff/Bandit/Semgrep +- Boucle automatique Dev-QA +___ + +### É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 (optionnel) + +Tâches : +- Interface Chainlit +- Livraison +___ + +### Étape finale : + +Tâches : +- Tests fonctionnels de bout en bout +- Sécurité minimale +- Documentation finale +- Présentation \ No newline at end of file diff --git a/ressources/test.md b/ressources/test.md new file mode 100644 index 0000000..ed36a94 --- /dev/null +++ b/ressources/test.md @@ -0,0 +1,288 @@ + +# Projet ARC +```mermaid +gantt + title Diagramme Gantt du projet ARC + dateFormat YYYY-MM-DD + axisFormat %d/%m + excludes weekends + todayMarker on + + Etape 0 - Initialisation :a1, 2026-06-08, 2026-06-11 + Etape 1 - Analyse du Besoin & Qualification :a2, 2026-06-11, 2026-06-27 + Etape 2 - Génération de Code :a2, 2026-06-29, 2026-07-15 + Etape 3 - Tests et Assurance Qualité :a2, 2026-07-15, 2026-08-01 + Etape 4 - Livraison & Feedback Utilisateur :a2, 2026-08-01, 2026-08-13 + Etape finale :a2, 2026-08-13, 2026-08-15 + +``` +--- +```mermaid +gantt + title Section 0 - Préparation de l'environnement du projet + dateFormat YYYY-MM-DD + axisFormat %d/%m + excludes weekends + todayMarker off + + section Initialisation + Créer structure projet :a1, 2026-06-09, 0.5d + Backend + structure :a2, 2026-06-09, 0.5d + + section LangGraph + Installer LangGraph :b1, 2026-06-09, 0.5d + Créer structure workflow :b2, 2026-06-09, 1d + Définir state global :b3, 2026-06-09, 1d + + section Setup Qdrant + Installer Qdrant :c1, after b3, 0.5d + Tester connexion Python :c2, after b3, 0.5d + + section Setup modèle d’embedding + Installer Snowflake :d1, after b3, 0.5d + Implémenter embedding() :d2, after b3, 0.5d + + section Setup LLM + Installer llama.cpp :e1, after b3, 0.5d + Tester appel modèle :e2, after b3, 0.5d + + section Setup Chainlit + Installer Chainlit :f1, after e2, 0.5d + Lancer app test :f2, after e2, 0.5d + + section Organisation du code + Créer dossiers agents :g1, after e2, 0.5d + Créer dossiers services :g2, after e2, 0.5d + + section Logging et debug + Logs simples :h1, after e2, 0.5d + Structuration logs :h2, after e2, 0.5d +``` + +```mermaid +gantt + title Section 1 - Analyse du Besoin & Qualification + dateFormat YYYY-MM-DD + axisFormat %d/%m + excludes weekends + todayMarker off + + section Interface utilisateur + Créer interface Chainlit :a1, 2026-06-12, 1d + Connecter Chainlit → backend Python :a2, 2026-06-12, 0.5d + + section Modèle cahier des charges + Définir structure JSON :b1, after a2, 0.5d + Schéma Pydantic :b2, after a2, 1d + + section Agent PM + Prompt Agent PM :c1, after b2, 0.5d + Sortie structurée Pydantic :c2, after b2, 0.5d + Gestion erreurs :c3, after c2, 0.5d + + section Questions clarificatrices + Détection champs manquants :d1, after c2, 0.5d + Génération questions LLM :d2, after d1, 0.5d + Boucle interaction Chainlit :d3, after d1, 1d + + section Validation du cahier des charges + Affichage CDC :e1, after d3, 0.5d + Boutons validation/refus :e2, after d3, 0.5d + + section Qdrant (BDD vectorielle) + Installer Qdrant :f1, after e2, 0.5d + Créer collection :f2, after e2, 0.5d + Structure payload :f3, after f2, 0.5d + + section Embedding + Intégrer Snowflake Arctic :g1, after f2, 0.5d + Fonction embedding :g2, after f2, 0.5d + + section Recherche d'existant + Recherche projets similaires :h1, after g2, 0.5d + Filtres payload :h2, after g2, 0.5d + Formatage résultats :h3, after h2, 0.5d + + section Proposition utilisateur + Affichage résultats Chainlit :i1, after h2, 0.5d + Bouton "utiliser projet" :i2, after h2, 0.5d + Bouton "continuer génération" :i3, after h2, 0.5d + + section Redis (optionnel) + Cache recherche :j1, after i3, 1d +``` + +```mermaid +gantt + title Section 2 - Génération de Code + dateFormat YYYY-MM-DD + axisFormat %d/%m + excludes weekends + todayMarker off + + section Préparation des entrées + Récupérer cahier des charges :a1, 2026-06-29, 0.5d + Normaliser / valider données :a2, 2026-06-29, 0.5d + + section Format de sortie du code + Structure projet JSON/Pydantic :b1, after a2, 0.5d + Modèle Pydantic sortie code :b2, after a2, 1d + + section Prompt Agent Dev + Prompt génération code :c1, after b2, 1d + Contraintes strictes :c2, after b2, 0.5d + + section Intégration LLM + Setup llama.cpp :d1, after c2, 0.5d + Connexion LangGraph → LLM :d2, after c2, 0.5d + + section Arborescence projet + Génération structure fichiers :e1, after d2, 0.5d + Vérification structure :e2, after d2, 0.5d + + section Génération code source + Génération fichiers Python :f1, after e2, 2d + Conformité structure :f2, after f1, 1d + + section Fichiers complémentaires + README.md :g1, after f2, 0.5d + requirements.txt :g2, after f2, 0.5d + Instructions exécution :g3, after f2, 0.5d + + section Validation backend + Vérification fichiers :h1, after g3, 0.5d + Nettoyage output LLM :h2, after g3, 0.5d + + section Intégration LangGraph + Ajouter noeud Agent Dev :i1, after h2, 0.5d + Connecter PM → Dev :i2, after h2, 0.5d + + section Préparation QA + Formatter code sandbox :j1, after i2, 0.5d + Transmettre au state :j2, after i2, 0.5d +``` + +```mermaid +gantt + title Section 3 - Tests et Assurance Qualité + dateFormat YYYY-MM-DD + axisFormat %d/%m + excludes weekends + todayMarker off + + section Préparation entrées + Récupérer projet Agent Dev :a1, 2026-07-15, 0.5d + + section Sandbox Docker + Dockerfile générique :b1, 2026-07-15, 1d + Script build/run :b2, after b1, 1d + Isolation environnement :b3, after b1, 0.5d + + section Exécution sandbox + Lancer exécution :c1, after b3, 0.5d + Capturer logs :c2, after b3, 0.5d + + section Ruff (qualité) + Installer Ruff :d1, after c2, 0.5d + Ruff check :d2, after c2, 0.5d + + section Bandit (sécurité) + Installer Bandit :e1, after c2, 0.5d + Scan projet :e2, after c2, 0.5d + + section Semgrep (optionnel) + Installer Semgrep :f1, after c2, 0.5d + Analyse règles :f2, after c2, 0.5d + + section Structuration QA + Modèle Pydantic rapport :g1, after f2, 1d + Parser résultats outils :g2, after g1, 0.5d + + section Agent QA + Prompt Agent QA :h1, after g2, 1d + Résumé intelligible :h2, after h1, 0.5d + Traduction erreurs → dev :h3, after h2, 1d + + section Boucle Dev ↔ QA + Implémenter boucle LangGraph :i1, after h3, 1.5d + Condition stop :i2, after h3, 0.5d + Limite itérations :i3, after h3, 1d + + section Logs pour correction + Structurer logs :j1, after i3, 0.5d + Injecter logs dans Agent Dev :j2, after i3, 0.5d + + section Intégration LangGraph + Ajouter noeud QA :k1, after j2, 0.5d + Connecter Dev → QA → loop :k2, after j2, 0.5d + + section Sécurisation minimale + Limiter temps exécution :l1, after k2, 0.5d + Bloquer accès disque/réseau :l2, after k2, 0.5d +``` + +```mermaid +gantt + title Section 4 - Livraison & Feedback Utilisateur + dateFormat YYYY-MM-DD + axisFormat %d/%m + excludes weekends + todayMarker off + + section Préparation affichage + Récupérer code validé :a1, 2026-08-03, 0.5d + Récupérer rapport QA :a2, 2026-08-03, 0.5d + + section Affichage Chainlit + Afficher code :b1, after a2, 0.5d + Afficher rapport QA :b2, after a2, 0.5d + Afficher instructions exécution :b3, after a2, 0.5d + + section Actions utilisateur + Bouton valider :c1, after a2, 0.5d + Bouton refuser :c2, after a2, 0.5d + + section Gestion refus + Champ feedback :d1, after c2, 0.5d + Structuration Pydantic :d2, after c2, 0.5d + Injection LangGraph :d3, after c2, 0.5d + + section Boucle retour PM + Transition QA → PM :e1, after d3, 0.5d + Conserver contexte + feedback :e2, after d3, 0.5d + + section Génération ZIP + Créer archive :f1, after e2, 0.5d + Vérifier structure :f2, after e2, 0.5d + + section Téléchargement + Bouton téléchargement ZIP :g1, after e2, 0.5d + + section Sauvegarde projet + Sauvegarder code + metadata :h1, after g1, 0.5d + Flag validated :h2, after g1, 0.5d + + section Réindexation Qdrant + Générer embedding :i1, after h2, 0.5d + Ajouter dans Qdrant :i2, after h2, 0.5d + + section Git (optionnel) + Init dépôt :j1, after i2, 0.5d + + section Intégration finale LangGraph + Ajouter noeud Delivery :k1, after i2, 0.5d + Connecter QA → Delivery → fin :k2, after i2, 0.5d +``` + +```mermaid +gantt + title Section finale - Rendu + dateFormat YYYY-MM-DD + axisFormat %d/%m + excludes weekends + todayMarker off + + Tests fonctionnels de bout en bout :a1, 2026-08-13, 0.5d + Documentation finale :a2, 2026-08-13, 0.5d + Présentation :a3, after a2, 1d +``` \ No newline at end of file