"""知识库消费指标单元测试(mock 数据,不依赖数据库)。""" from types import SimpleNamespace from unittest.mock import patch from app.kb_metrics import kb_metrics, kb_metrics_overview def _usage(agent_id, kb_id, chunk_id, segment_id, status="consumed"): return SimpleNamespace(agent_id=agent_id, kb_id=kb_id, chunk_id=chunk_id, segment_id=segment_id, status=status, file_name="f.md", heading="h", excerpt="e") def _segment(sid, references=None, status="approved"): return SimpleNamespace(id=sid, status=status, references=references or []) def _session(usage_rows, segments, kbs=()): def exec(*_a, **_k): # 简化:按调用顺序不可靠,直接用鸭子类型区分——这里用闭包计数不必要, # kb_metrics_overview 内部按顺序 exec 三次:usage / kbs / segments, # kb_metrics 内部两次:usage / segments。用可迭代队列处理。 raise AssertionError("请使用带计划的 mock") return SimpleNamespace(exec=exec) class _PlannedSession: def __init__(self, queues): self.queues = list(queues) def exec(self, *_a, **_k): return SimpleNamespace(all=lambda: self.queues.pop(0) if self.queues else []) def get(self, model, row_id): return None def test_metrics_empty(): session = _PlannedSession([[], []]) report = kb_metrics(session) assert report["hit_rate"] == 0.0 assert report["repeat_rate"] == 0.0 assert report["reference_coverage"] == 0.0 def test_hit_rate_and_repeat_rate(): usage = [ _usage(1, 1, "c1", 101), _usage(1, 1, "c2", 101), _usage(2, 1, "c1", 102), # 重复消费:agent1 再次用 c1(segment 103)→ pair (1, c1) 两条 _usage(1, 1, "c1", 103), ] session = _PlannedSession([usage, []]) report = kb_metrics(session) assert report["detail"]["hit_requests"] == 3 # (1,101)(2,102)(1,103) assert report["detail"]["repeated_pairs"] == 1 assert report["repeat_rate"] == round(1 / 3, 4) assert report["reference_coverage"] == 0.0 # 没有 segment 数据 def test_reference_coverage(): usage = [_usage(1, 1, "c1", 101)] segments = [_segment(101, references=[{"file": "f"}]), _segment(102, references=[])] session = _PlannedSession([usage, segments]) report = kb_metrics(session) assert report["reference_coverage"] == 0.5 def test_miss_requests_lower_hit_rate(): usage = [_usage(1, 1, "c1", 101)] # 段 102 是已完成的生产段但完全没有消费记录 → 记一次未命中 segments = [_segment(101, references=[{"x": 1}]), _segment(102)] session = _PlannedSession([usage, segments]) report = kb_metrics(session) assert report["detail"]["search_requests"] == 2 assert report["detail"]["miss_requests"] == 1 assert report["hit_rate"] == 0.5 def test_overview_by_kb_sorted(): usage = [ _usage(1, 1, "c1", 101), _usage(1, 1, "c2", 101), _usage(1, 2, "c3", 102), ] kbs = [ SimpleNamespace(id=1, name="主库", file_count=2, chunk_count=10), SimpleNamespace(id=2, name="副库", file_count=1, chunk_count=10), ] # kb_metrics_overview 先调 kb_metrics(usage, segments),再查 usage 和 kbs session = _PlannedSession([usage, [], usage, kbs]) report = kb_metrics_overview(session) assert report["by_kb"][0]["kb_id"] == 1 # 消费比 0.2 > 0.1 assert report["by_kb"][0]["consumed"] == 2 assert report["top_chunks"][0]["chunk_id"] == "c1" # c1 有 2 条记录(同 agent 两段)