diff --git a/agent/qa/src/rag/qa_v1_1_73/direct_flow_retriever.py b/agent/qa/src/rag/qa_v1_1_73/direct_flow_retriever.py index 56bb4d9..8ae9ceb 100644 --- a/agent/qa/src/rag/qa_v1_1_73/direct_flow_retriever.py +++ b/agent/qa/src/rag/qa_v1_1_73/direct_flow_retriever.py @@ -117,6 +117,7 @@ class DirectFlowRetriever: use_qdrant: bool = True, use_neo4j: bool = True, use_graph_expansion: bool = True, + qdrant_score_threshold: float | None = None, ) -> Tuple[List[EvidenceItem], Dict[str, Any]]: effective_use_neo4j = bool(use_neo4j) and self.graph_schema in { "canonical", @@ -138,6 +139,7 @@ class DirectFlowRetriever: "use_graph_expansion": use_graph_expansion, "effective_use_neo4j": effective_use_neo4j, "effective_use_graph_expansion": effective_use_graph_expansion, + "qdrant_score_threshold": qdrant_score_threshold, }, "neo4j_status": ( "enabled" @@ -153,7 +155,12 @@ class DirectFlowRetriever: per_query = max(10, min(qdrant_top_k // max(1, len(all_queries)), 50)) if self.vector_query_workers == 1 or len(all_queries) == 1: query_rows = [ - (query, self.qdrant_search(query, per_query)) + ( + query, + self.qdrant_search( + query, per_query, score_threshold=qdrant_score_threshold + ), + ) for query in all_queries ] else: @@ -162,7 +169,12 @@ class DirectFlowRetriever: thread_name_prefix="qa-vector", ) as executor: futures = [ - executor.submit(self.qdrant_search, query, per_query) + executor.submit( + self.qdrant_search, + query, + per_query, + score_threshold=qdrant_score_threshold, + ) for query in all_queries ] query_rows = [ diff --git a/agent/qa/src/rag/qa_v1_1_73/test_score_gate.py b/agent/qa/src/rag/qa_v1_1_73/test_score_gate.py index eb71835..bc82e64 100644 --- a/agent/qa/src/rag/qa_v1_1_73/test_score_gate.py +++ b/agent/qa/src/rag/qa_v1_1_73/test_score_gate.py @@ -224,6 +224,46 @@ def test_chat_mode_passes_no_threshold(): assert p._retriever_stub.last_kwargs["qdrant_score_threshold"] is None +def test_direct_flow_retriever_threshold_passthrough(): + """真实 retriever 的 qdrant_score_threshold 参数链路(不触发 IO)。 + + 用对象伪造构造 DirectFlowRetriever 实例,直接调用 qdrant_search: + - 传阈值时透传给 qdrant.query_points(同域向量阈值); + - 不传阈值时 score_threshold=None(chat 保召回)。 + """ + import rag.qa_v1_1_73.direct_flow_retriever as dfr_mod + + class _FakeQdrant: + def __init__(self): + self.calls = [] + + def query_points(self, **kwargs): + self.calls.append(kwargs) + from qdrant_client.http import models + + return models.QueryResponse(points=[]) + + class _MinimalRetriever(dfr_mod.DirectFlowRetriever): + """绕过 __init__(不建连接),只测 qdrant_search 参数透传。""" + + def __init__(self): + self.qdrant = _FakeQdrant() + self.collection = "plant_srt_pc2025_v3_20260817_normalized" + self.embed_url = "" + self.embed_model = "" + + def embed(self, text): + return [0.0] * 1024 + + r = _MinimalRetriever() + r.qdrant_search("景甜是谁", 10, score_threshold=0.45) + assert r.qdrant.calls[-1]["score_threshold"] == 0.45 + assert r.qdrant.calls[-1]["collection_name"] == r.collection + + r.qdrant_search("arginine", 10) + assert r.qdrant.calls[-1]["score_threshold"] is None + + def test_env_overrides(monkeypatch): monkeypatch.setenv("QA_MIN_VECTOR_SCORE", "0.5") assert score_gate._env_value("QA_MIN_VECTOR_SCORE", 0.45) == 0.5