Etape 4 finie, TODO : correction QA, gestion retour au PM après refus utilisateur

This commit is contained in:
Chevallier
2026-07-08 16:11:11 +02:00
parent 5f06de19e0
commit 37d493ac20
5 changed files with 341 additions and 87 deletions

View File

@@ -1,3 +1,7 @@
import os
import json
import time
from pathlib import Path
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
@@ -106,10 +110,59 @@ async def qa_node(state: WorkflowState):
}
async def human_review_node(state: WorkflowState):
print("[Human Review] Passage en mode automatique (Mock)...")
"""
Nœud pivot. Si le statut vient du QA, il bascule en attente de validation humaine.
Si Chainlit a déjà collecté la décision, il laisse passer le flux vers le routage.
"""
current_status = state.get("status")
if current_status in ["qa_done", "existing_found"]:
return {
"status": "wait_human_review"
}
return {"status": current_status}
async def delivery_node(state: WorkflowState, config: RunnableConfig):
"""
Nœud final de livraison : Archivage ZIP en mémoire et réindexation Qdrant.
"""
dev_data = state.get("generated_code", {})
project_title = dev_data.get("spec_title", f"project_{int(time.time())}")
raw_files = dev_data.get("files", {})
files = {}
if isinstance(raw_files, list):
for f in raw_files:
if isinstance(f, dict):
path = f.get("path") or f.get("filename") or f.get("name")
content = f.get("content") or f.get("code") or ""
if path:
files[path] = content
elif isinstance(raw_files, dict):
files = raw_files
# 3. Réindexation dans Qdrant (en utilisant le payload mémoire)
# qdrant_repo = config.get("configurable", {}).get("qdrant_repo")
# if qdrant_repo:
# payload_text = f"Title: {project_title}\nDescription: {state.get('spec', {}).get('description')}"
# # /!\ À remplacer par ton vrai modèle d'embedding (ex: await qdrant_repo.embed(payload_text))
# dummy_vector = [0.1] * 1536
# qdrant_repo.client.upsert(
# collection_name="arc_projects",
# points=[
# PointStruct(
# id=str(Path(project_title).name),
# vector=dummy_vector,
# payload=metadata
# )
# ]
# )
return {
"existing_project_approved": True,
"is_completed": True,
"status": "approved_by_human"
"status": "delivered",
"user_input": f"{state.get('user_input')}\n\n[System] Projet {project_title} validé et package ZIP disponible."
}

View File

@@ -10,10 +10,14 @@ from app.graph.nodes import (
dev_node,
qa_node,
human_review_node,
delivery_node
)
def route_entry_point(state: WorkflowState):
if state.get("status") == "spec_approved":
current_status = state.get("status")
if current_status in ["human_approved", "human_refused"]:
return "human_review"
if current_status == "spec_approved":
return "retrieval"
return "pm"
@@ -40,18 +44,23 @@ def route_after_qa(state: WorkflowState):
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
current_status = state.get("status")
# Si l'utilisateur a refusé le code -> Retour à la case PM avec ses commentaires
return "pm"
if current_status == "wait_human_review":
return END
if state.get("existing_project") and not state.get("generated_code"):
if state.get("existing_project_approved") is True:
return END
return "dev"
if current_status == "human_approved":
return "delivery"
if current_status == "human_refused":
return "pm"
return END
# --- Assemblage du Graphe ---
@@ -62,12 +71,14 @@ 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.add_node("delivery", delivery_node)
graph.set_conditional_entry_point(
route_entry_point,
{
"pm": "pm",
"retrieval": "retrieval",
"human_review": "human_review"
},
)
@@ -108,7 +119,12 @@ graph.add_conditional_edges(
{
"pm": "pm",
"dev": "dev",
"delivery": "delivery",
END: END,
},
)
# Maillon final : Après Delivery, c'est la fin du cycle
graph.add_edge("delivery", END)
compiled_graph = graph.compile()