Il progetto knowledge-graph/ è un monorepo con:
knowledge-graph-api/: FastAPI, pipeline ingest (extract→chunk→embed), Neo4j + Redis, Ollama (llama3 + nomic-embed-text), porta 8000knowledge-graph-mcp/: MCP server (FastMCP, stdio), 8 tool (kg_health, kg_query, kg_ingest, kg_delete_document, kg_list_documents, kg_search_nodes, kg_traverse, kg_cypher)knowledge-graph-ui/: Next.js 15
Il file brainstorming_multi_agent_KG.md descrive l'architettura target.
Devi implementare Fase 1 — Foundation del sistema multi-agent.
Crea il modulo knowledge-graph-agents/ che implementa:
knowledge-graph-agents/
├── agents/
│ ├── __init__.py
│ ├── orchestrator.py # Router + Planner
│ ├── ingestion.py # Ingestion Agent
│ ├── analyst.py # Analyst Agent (vector + graph + hybrid)
│ ├── validator.py # Validator Agent + KGQualityReport
│ ├── kgc.py # KGC Agent (Knowledge Graph Completion)
│ ├── synthesis.py # Synthesis Agent
│ └── monitor.py # Monitor Agent
├── orchestration/
│ ├── __init__.py
│ ├── graph.py # LangGraph StateGraph
│ ├── router.py # Intent classification (8 intent types)
│ ├── state.py # AgentState shared schema
│ └── planner.py # Multi-step plan builder
├── tools/
│ ├── __init__.py
│ └── kg_tools.py # Wrappa i tool MCP come LangChain @tool
├── memory/
│ ├── __init__.py
│ └── kg_memory.py # Schema AgentRun nel KG + query memoria
├── api/
│ ├── __init__.py
│ └── agent_api.py # FastAPI endpoint /agents/run, /agents/status
├── tests/
│ ├── __init__.py
│ └── test_agents.py
├── pyproject.toml
├── requirements.txt
└── .env.example
Schema Pydantic condiviso tra tutti gli agenti:
class Intent(str, Enum):
INGEST = "ingest"
QUERY = "query"
ANALYZE = "analyze"
SYNTHESIZE = "synthesize"
VALIDATE = "validate"
KGC = "kgc"
MONITOR = "monitor"
HEALTH = "health"
class AgentStep(BaseModel):
agent: str
action: str
params: dict
status: Literal["pending", "running", "done", "failed"]
result: Optional[Any]
error: Optional[str]
class AgentState(TypedDict):
user_request: str
intent: Optional[Intent]
plan: List[AgentStep]
current_step: int
context: dict # dati intermedi condivisi
final_output: Optional[str]
error: Optional[str]
thread_id: str # namespace KG
run_id: str # UUID del run correnteWrappa le chiamate HTTP all'API (http://localhost:8000) come LangChain tools:
kg_health_tool→ GET /healthkg_query_tool(query, namespace, top_k)→ POST /querykg_ingest_tool(file_path, thread_id)→ POST /ingest (multipart)kg_list_documents_tool(namespace)→ GET /documents/{namespace}kg_delete_document_tool(doc_id)→ DELETE /documents/{doc_id}kg_search_nodes_tool(name, namespace)→ POST /graph/nodes/searchkg_traverse_tool(node_id, max_hops)→ POST /graph/traversekg_cypher_tool(query, namespace)→ POST /graph/cypher
Ogni tool usa httpx.AsyncClient, legge KG_API_URL da env (default http://localhost:8000).
Classifica l'intent dell'utente (regex + keyword matching semplice per Fase 1):
INTENT_PATTERNS = {
Intent.INGEST: [r"carica|ingest|upload|importa"],
Intent.QUERY: [r"cosa sai|dimmi|descrivi|spiega"],
Intent.ANALYZE: [r"analizza|quante entità|conta|statistiche"],
Intent.SYNTHESIZE: [r"report|genera|riassumi|sintesi"],
Intent.VALIDATE: [r"valida|verifica qualità|check"],
Intent.KGC: [r"relazioni mancanti|completa|gap"],
Intent.MONITOR: [r"salute|funziona|status|health"],
}LangGraph StateGraph con nodi per ogni agente e routing condizionale dall'orchestratore:
workflow = StateGraph(AgentState)
workflow.add_node("orchestrator", orchestrator_node)
workflow.add_node("ingestion", ingestion_node)
workflow.add_node("analyst", analyst_node)
workflow.add_node("validator", validator_node)
workflow.add_node("kgc", kgc_node)
workflow.add_node("synthesis", synthesis_node)
workflow.add_node("monitor", monitor_node)
workflow.set_entry_point("orchestrator")
workflow.add_conditional_edges("orchestrator", route_by_intent, {...})
workflow.add_edge("ingestion", "validator") # post-ingest check automaticoagents/orchestrator.py:
- Riceve
AgentState, chiamarouter.classify_intent(user_request) - Costruisce
plancon steps ordinati - Non esegue azioni dirette, solo routing
agents/ingestion.py:
- Esegue
kg_ingest_toolcon il file/URL ricevuto - Verifica il risultato (chunk count, entity count)
- Aggiorna
context["ingestion_result"] - In caso di errore setta
step.status = "failed"con dettaglio
agents/analyst.py:
- 3 sub-strategie:
vector_search,graph_traversal,hybrid - Output strutturato:
{"answer", "confidence", "sources", "graph_path"}
agents/validator.py:
- Esegue query Cypher per: nodi orfani, nodi senza embedding
- Calcola
KGQualityReportconoverall_health(0.0→1.0)
agents/kgc.py:
- Trova nodi con degree basso via Cypher
- Propone candidati relazione tramite similarity vettoriale
- Output:
KGCProposalcon lista relazioni proposte + confidence
agents/synthesis.py:
- Esegue query vector + graph, costruisce prompt per Ollama
- Genera Markdown output, opzionale auto-ingest del report
agents/monitor.py:
- Chiama
kg_health_tool+validatorquick check - Genera summary stato sistema
Schema AgentRunRecord (Pydantic) + funzione save_agent_run che scrive nel grafo Neo4j tramite API.
FastAPI app separata (porta 8001):
POST /agents/run body: {"request": str, "thread_id": str}
GET /agents/run/{id} response: AgentRunRecord
GET /agents/health response: {"status": "ok", "kg_api": bool}
langgraph>=0.2.0
langchain>=0.3.0
langchain-community>=0.3.0
httpx>=0.27.0
fastapi>=0.115.0
uvicorn>=0.30.0
pydantic>=2.0.0
python-dotenv>=1.0.0
Test con pytest e mock di httpx:
test_intent_classificationtest_ingestion_agenttest_analyst_agent_vectortest_validator_agenttest_orchestrator_plantest_full_pipeline_ingest_analyze
- Nessuna modifica a
knowledge-graph-api/oknowledge-graph-mcp/ - Il
thread_id(namespace) deve essere passato su ogni tool call - Tutti gli agenti sono
async KG_API_URL=http://localhost:8000come default da.env- LLM: Ollama (
http://localhost:11434, modellollama3) — non OpenAI - Cypher degli agenti: read-only tramite guardrail esistente
cd knowledge-graph-agents
pip install -r requirements.txt
python -m uvicorn api.agent_api:app --port 8001
curl -X POST http://localhost:8001/agents/run \
-H "Content-Type: application/json" \
-d '{"request": "cosa sai su Hevolus?", "thread_id": "default"}'Risposta attesa: {"run_id": "...", "output": "...", "plan": [...], "quality": {...}}