Files
PlantAI-rag/src/rag/qa_v1_1_73/retrieval.py
T
potaser 4fef3a65ac feat(qa): 断言锚定检索 + robustness 加固
- assertion_anchored_graph_retriever 断言锚定候选与 top30 合并
- pipeline/agentic_controller/llm_client 重试与 Q0 软回退
- question_decomposition JSON repair、citation_contract grouped 归一化
- api endpoint_path 全参数、retrieval 禁用 PC/SQLite、canonical-only 校验
- bounded_canonical/direct_flow 白名单、subquestion_orchestrator required_review
- scripts/qa_v1_1_73_server.py 新服务入口(backend_guard + gate_policy)
- 旧 start_qa_v1_1_73_*.sh 移入 docs/legacy_startup_scripts/
2026-08-29 12:39:25 +08:00

137 lines
5.0 KiB
Python

from __future__ import annotations
from typing import Any, Dict, List, Tuple
from .schemas import EvidenceItem
from .v16_retriever import V16IndependentRetriever
def _text(value: Any) -> str:
return str(value or "").strip()
DEFAULT_RETRIEVAL_OPTIONS: Dict[str, bool] = {
"use_v15": True,
"use_pc": False,
"use_sqlite": False,
"use_qdrant": True,
"use_neo4j": True,
"use_graph_expansion": True,
}
def normalize_retrieval_options(options: Dict[str, Any] | None) -> Dict[str, bool]:
normalized = dict(DEFAULT_RETRIEVAL_OPTIONS)
if isinstance(options, dict):
if options.get("use_pc") or options.get("use_sqlite"):
raise ValueError(
"PC and SQLite retrieval backends are disabled; use Neo4j and Qdrant"
)
for key in list(normalized):
if key in options:
normalized[key] = bool(options[key])
# If Neo4j is disabled, expansion cannot run.
if not normalized["use_neo4j"]:
normalized["use_graph_expansion"] = False
return normalized
class DualKBRetriever:
"""Use stable public KB clients without importing previous QA server scripts."""
def __init__(self) -> None:
self.v15 = V16IndependentRetriever()
def retrieve_queries(
self,
queries: Dict[str, List[str]],
*,
top_k_per_query: int,
graph_hops: int,
include_candidate: bool,
retrieval_options: Dict[str, Any] | None = None,
) -> Tuple[List[EvidenceItem], List[Dict[str, Any]]]:
options = normalize_retrieval_options(retrieval_options)
output: List[EvidenceItem] = []
audit: List[Dict[str, Any]] = []
for lane, lane_queries in queries.items():
for query in lane_queries:
before = len(output)
if options["use_v15"]:
output.extend(
self._retrieve_v15(
query,
lane,
top_k_per_query,
graph_hops,
include_candidate,
options,
)
)
audit.append(
{
"lane": lane,
"query": query,
"new_rows": len(output) - before,
"retrieval_options": dict(options),
}
)
return output, audit
def _retrieve_v15(
self,
query: str,
lane: str,
top_k: int,
graph_hops: int,
include_candidate: bool,
options: Dict[str, bool],
) -> List[EvidenceItem]:
items: List[EvidenceItem] = []
try:
rows = self.v15.search(
query,
top_k=top_k,
max_hops=graph_hops,
include_candidate=include_candidate,
use_sqlite=False,
use_qdrant=options["use_qdrant"],
use_neo4j=options["use_neo4j"],
use_graph_expansion=options["use_graph_expansion"],
)
except Exception:
rows = []
for row in rows:
method = str(row.get("_retrieval_method") or "v16_hybrid")
items.append(self._from_row(row, "V16/V15", f"{lane}:{method}", query))
return items
@staticmethod
def _from_row(row: Dict[str, Any], kb: str, method: str, query: str) -> EvidenceItem:
metadata = row.get("metadata") if isinstance(row.get("metadata"), dict) else {}
return EvidenceItem(
evidence_id="",
kb=kb,
source=_text(row.get("source") or metadata.get("source")),
relation=_text(row.get("relation") or metadata.get("relation")),
target=_text(row.get("target") or metadata.get("target")),
evidence=_text(row.get("evidence") or row.get("content")),
paper_id=_text(row.get("paper_id") or row.get("canonical_paper_id") or metadata.get("paper_id")),
title=_text(row.get("title") or row.get("paper_title") or metadata.get("title")),
pmid=_text(row.get("pmid") or row.get("pubmed_id") or metadata.get("pmid")),
pmcid=_text(row.get("pmcid") or metadata.get("pmcid")),
doi=_text(row.get("doi") or metadata.get("doi")),
species=_text(row.get("species") or metadata.get("species")),
section=_text(row.get("section") or metadata.get("section")),
output_layer=_text(row.get("output_layer") or metadata.get("output_layer")),
claim_scope=_text(row.get("claim_scope") or metadata.get("claim_scope")),
relation_id=_text(row.get("relation_id") or metadata.get("relation_id")),
retrieval_methods=[method],
retrieval_queries=[query],
vector_score=float(row.get("_vector_score") or row.get("score") or 0.0),
raw=dict(row),
)
def close(self) -> None:
self.v15.close()