Files
PlantAI-rag/api/v15_agentic_qa_routes.py
2026-07-10 17:59:59 +08:00

150 lines
4.8 KiB
Python
Executable File

from __future__ import annotations
import os
from fastapi import APIRouter
from pydantic import BaseModel, Field
router = APIRouter(prefix="/v15-agentic-qa", tags=["V15 Agentic QA"])
_retriever = None
_coordinator = None
_memory = None
_mem_ok = False
def _get_retriever():
global _retriever
if _retriever is None:
from src.rag.v15_hybrid_retriever import V15HybridRetriever
_retriever = V15HybridRetriever()
return _retriever
def _get_coordinator():
global _coordinator
if _coordinator is None:
from src.rag.agentic_coordinator import AgenticCoordinator
_coordinator = AgenticCoordinator(
retriever=_get_retriever(), max_iterations=3, top_k_base=15)
return _coordinator
def _get_memory():
global _memory, _mem_ok
if _memory is None and not _mem_ok:
try:
from src.rag.memory.memory_manager import MemoryManager
_memory = MemoryManager(user_id="agentic", session_id="qa")
_mem_ok = True
except Exception as e:
print(f"[V15 Agentic] Memory disabled: {e}")
_mem_ok = True
return _memory
class AgenticQARequest(BaseModel):
question: str = Field(..., description="Question")
top_k: int = Field(15, ge=1, le=50)
max_hops: int | None = Field(None)
include_candidate: bool = Field(False)
use_llm: bool = Field(True)
provider: str | None = Field(None)
model: str | None = Field(None)
anchor_entity: str | None = Field(None)
no_memory: bool = Field(False)
@router.post("/chat")
async def agentic_qa_chat(req: AgenticQARequest):
coordinator = _get_coordinator()
memory = _get_memory()
question = req.question
mem_note = ""
if memory and not req.no_memory:
try:
enriched = memory.augment_query(question)
if enriched != question:
question = enriched
mem_note = "memory_augmented"
except:
pass
result = await coordinator.run(
question=question, top_k=req.top_k,
anchor_entity=req.anchor_entity or None,
include_candidate=req.include_candidate)
evidence = result["retrieved_evidence"]
gpaths = result["graph_paths"]
aplan = result["retrieval_plan"]
evres = result["evaluation"]
notes = result["notes"]
if req.max_hops is not None:
aplan["max_hops"] = req.max_hops
citations = []
for idx, ev in enumerate(evidence[:30]):
citations.append({
"id": f"S{idx+1}",
"source": ev.get("source", ""),
"relation": ev.get("relation", ""),
"target": ev.get("target", ""),
"evidence": ev.get("evidence", ""),
"paper_id": ev.get("paper_id", ""),
"pubmed_id": ev.get("pubmed_id", ""),
"section": ev.get("section", ""),
})
answer = ""
llm_model = ""
if req.use_llm and evidence:
try:
from api.kb_qa_routes import _generate_llm_answer
answer = await _generate_llm_answer(
req.question, aplan.get("layer", "auto"),
evidence, provider=req.provider, model=req.model)
p = req.provider or os.environ.get("V15_KB_QA_LLM_PROVIDER", "")
m = req.model or os.environ.get("V15_KB_QA_LLM_MODEL", "")
llm_model = f"{p}:{m}"
except Exception as exc:
answer = f"[agentic] LLM failed: {exc}"
if not answer and not evidence:
answer = "Insufficient evidence."
if memory and not req.no_memory and answer:
try: memory.add_interaction(req.question, answer)
except: pass
return {
"question": req.question,
"answer": answer,
"citations": citations,
"graph_paths": gpaths,
"agentic_plan": aplan,
"evaluation": evres,
"retrieval_notes": notes,
"evidence_count": len(evidence),
"graph_paths_count": len(gpaths),
"llm_model": llm_model,
"memory_note": mem_note,
"mode": "v15_agentic_qa",
}
@router.get("/info")
async def agentic_qa_info():
return {
"mode": "v15_agentic_qa",
"components": {
"planner": "QueryAnalyzer + RetrievalStrategyAgent",
"evaluator": "Self-evaluation (count/confidence/entity)",
"retriever": "V15HybridRetriever (Qdrant+Neo4j)",
"memory": "MemoryManager",
},
"loop": "Max 3 iterations with self-evaluation",
"question_types": {
"FAQ": "hybrid mode, 2 hops",
"METHOD_CONSULT": "method_relations layer, 1 hop",
"ENTITY_QUERY": "exact+vector+KG, 1 hop",
"RELATION_QUERY": "KG+vector, 3 hops",
"UNKNOWN": "hybrid mode, 2 hops",
},
}
print("[V15 Agentic QA] Routes loaded with AgenticCoordinator")