fix(r4-28v2-amend): 补 direct_flow_retriever 阈值参数链路+单测

上一提交中 direct_flow_retriever 的 qdrant_score_threshold 参数
(retrieve 签名/audit/调用点)未落盘,真实 search 路径会 TypeError。
本次补上三处 + 新增 qdrant_search 透传单测(不触发 IO 的对象伪造)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-11 16:34:37 +08:00
co-authored by Claude Fable 5
parent 1fe80b293e
commit aec01ca0df
2 changed files with 54 additions and 2 deletions
@@ -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 = [
@@ -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