150 lines
4.8 KiB
Python
Executable File
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")
|
|
|