| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- #!/usr/bin/env python3
- """
- Test unified search vs BM25 baseline on 20 questions.
- Runs both engines, compares results, reports quality metrics.
- """
- import json
- import sys
- import time
- import os
- sys.path.insert(0, "/data/knowledge/maritime")
- os.environ['OLLAMA_EMBED_URL'] = 'http://127.0.0.1:11435/api/embed'
- # Load both engines
- from search_engine import search as bm25_search
- from search_unified import search as unified_search, _do_classify as classify_query
- # 20 test questions covering all 5 types
- QUESTIONS = [
- # fact (5)
- {"q": "2019年全国注册船员总数是多少?", "type": "fact", "expected_keywords": ["1659188", "船员"]},
- {"q": "船员适任证书的有效期是多少年?", "type": "fact", "expected_keywords": ["证书", "有效期"]},
- {"q": "琼州海峡船舶定线制是什么时候施行的?", "type": "fact", "expected_keywords": ["琼州海峡", "施行"]},
- {"q": "船舶进出港报告制度的法律依据是什么?", "type": "fact", "expected_keywords": ["进出港", "报告"]},
- {"q": "内河船舶船员培训机构有多少家?", "type": "fact", "expected_keywords": ["培训机构", "内河"]},
- # compare (4)
- {"q": "海上交通安全法与水污染防治法在船舶管理方面的规定有何异同?", "type": "compare", "expected_keywords": ["海上交通安全法", "水污染防治法"]},
- {"q": "对比2019年和2020年中国船员发展报告的主要变化", "type": "compare", "expected_keywords": ["2019", "2020", "船员"]},
- {"q": "国内航行船舶和国际航行船舶的检验要求有什么区别?", "type": "compare", "expected_keywords": ["国内", "国际", "检验"]},
- {"q": "海事局规范性文件与交通运输部规章有何区别?", "type": "compare", "expected_keywords": ["海事局", "交通运输部"]},
- # relation (4)
- {"q": "海事局的上级主管部门是哪个?", "type": "relation", "expected_keywords": ["海事局", "交通运输部"]},
- {"q": "船员适任证书制度依据哪些国际公约?", "type": "relation", "expected_keywords": ["适任证书", "STCW", "公约"]},
- {"q": "防治船舶污染海洋环境管理条例由谁制定?", "type": "relation", "expected_keywords": ["防治", "污染", "条例"]},
- {"q": "船舶检验机构的资质由哪个部门认可?", "type": "relation", "expected_keywords": ["检验", "资质", "认可"]},
- # synthesis (4)
- {"q": "请梳理中国海事法律法规的层级体系", "type": "synthesis", "expected_keywords": ["全国人大", "国务院", "交通运输部", "海事局"]},
- {"q": "总结船员管理方面的主要法规和政策", "type": "synthesis", "expected_keywords": ["船员", "管理", "法规"]},
- {"q": "概述海洋环境保护相关的法律框架", "type": "synthesis", "expected_keywords": ["海洋", "环境", "保护"]},
- {"q": "分析港口管理的法规体系", "type": "synthesis", "expected_keywords": ["港口", "管理"]},
- # temporal (3)
- {"q": "海上交通安全法最新修订是什么时候?", "type": "temporal", "expected_keywords": ["海上交通安全法", "修订"]},
- {"q": "船员培训管理规则的最新版本何时施行?", "type": "temporal", "expected_keywords": ["培训", "施行"]},
- {"q": "水污染防治法经历了哪些修订?", "type": "temporal", "expected_keywords": ["水污染防治法", "修订"]},
- ]
- print("=" * 70)
- print(" LambdaRAG Unified Search vs BM25 Baseline Test")
- print(" 20 questions × 2 engines")
- print("=" * 70)
- results = []
- for i, q in enumerate(QUESTIONS):
- print(f"\n{'─' * 70}")
- print(f"Q{i+1} [{q['type']}] {q['q']}")
- # Classify
- classified = classify_query(q['q'])
- classify_correct = classified == q['type']
- # BM25 baseline
- t0 = time.time()
- try:
- bm25_results = bm25_search(q['q'], top_k=5)
- except Exception as e:
- bm25_results = []
- print(f" BM25 ERROR: {e}")
- bm25_time = time.time() - t0
- # Unified
- t0 = time.time()
- try:
- unified_results = unified_search(q['q'], top_k=5)
- except Exception as e:
- unified_results = []
- print(f" UNIFIED ERROR: {e}")
- unified_time = time.time() - t0
- # Quality check: do results contain expected keywords?
- def check_relevance(search_results, keywords):
- if not search_results:
- return 0, []
- all_text = ' '.join([r.get('text', '') + ' ' + r.get('source', '') for r in search_results])
- found = [kw for kw in keywords if kw in all_text]
- return len(found) / max(len(keywords), 1), found
- bm25_score, bm25_found = check_relevance(bm25_results, q['expected_keywords'])
- unified_score, unified_found = check_relevance(unified_results, q['expected_keywords'])
- # Get unified metadata
- u_type = unified_results[0].get('_query_type', '?') if unified_results else '?'
- u_strategy = unified_results[0].get('_strategy', '?') if unified_results else '?'
- print(f" Classify: predicted={classified} expected={q['type']} {'✓' if classify_correct else '✗'}")
- print(f" BM25: {bm25_time:.2f}s | relevance={bm25_score:.0%} | found={bm25_found}")
- print(f" Unified: {unified_time:.2f}s | relevance={unified_score:.0%} | found={unified_found}")
- print(f" Route: {u_type} → {u_strategy}")
- # Check: unified sources different from BM25?
- bm25_sources = set(r.get('source', '')[:30] for r in bm25_results[:3])
- unified_sources = set(r.get('source', '')[:30] for r in unified_results[:3])
- sources_differ = bm25_sources != unified_sources
- record = {
- 'id': i + 1,
- 'question': q['q'],
- 'expected_type': q['type'],
- 'classified_type': classified,
- 'classify_correct': classify_correct,
- 'bm25_time': round(bm25_time, 2),
- 'unified_time': round(unified_time, 2),
- 'bm25_relevance': round(bm25_score, 2),
- 'unified_relevance': round(unified_score, 2),
- 'bm25_results_count': len(bm25_results),
- 'unified_results_count': len(unified_results),
- 'sources_differ': sources_differ,
- 'unified_route': f"{u_type} → {u_strategy}",
- 'winner': 'unified' if unified_score > bm25_score else ('tie' if unified_score == bm25_score else 'bm25'),
- }
- results.append(record)
- # Summary
- print(f"\n{'=' * 70}")
- print(" SUMMARY")
- print(f"{'=' * 70}")
- # Classification accuracy
- correct = sum(1 for r in results if r['classify_correct'])
- print(f"\nClassification accuracy: {correct}/{len(results)} ({correct/len(results)*100:.0f}%)")
- # Relevance comparison
- bm25_avg = sum(r['bm25_relevance'] for r in results) / len(results)
- unified_avg = sum(r['unified_relevance'] for r in results) / len(results)
- print(f"\nRelevance (keyword match):")
- print(f" BM25 avg: {bm25_avg:.0%}")
- print(f" Unified avg: {unified_avg:.0%}")
- # Winner counts
- wins = {'unified': 0, 'bm25': 0, 'tie': 0}
- for r in results:
- wins[r['winner']] += 1
- print(f"\nWins: Unified={wins['unified']} BM25={wins['bm25']} Tie={wins['tie']}")
- # By type
- print(f"\nBy question type:")
- for qtype in ['fact', 'compare', 'relation', 'synthesis', 'temporal']:
- type_results = [r for r in results if r['expected_type'] == qtype]
- if not type_results:
- continue
- b_avg = sum(r['bm25_relevance'] for r in type_results) / len(type_results)
- u_avg = sum(r['unified_relevance'] for r in type_results) / len(type_results)
- wins_u = sum(1 for r in type_results if r['winner'] == 'unified')
- wins_b = sum(1 for r in type_results if r['winner'] == 'bm25')
- print(f" {qtype:10s}: BM25={b_avg:.0%} Unified={u_avg:.0%} | U wins={wins_u} B wins={wins_b}")
- # Timing
- bm25_avg_time = sum(r['bm25_time'] for r in results) / len(results)
- unified_avg_time = sum(r['unified_time'] for r in results) / len(results)
- print(f"\nAvg search time:")
- print(f" BM25: {bm25_avg_time:.2f}s")
- print(f" Unified: {unified_avg_time:.2f}s")
- # Sources differ
- differ_count = sum(1 for r in results if r['sources_differ'])
- print(f"\nSources differ in {differ_count}/{len(results)} questions")
- # Issues
- print(f"\nPotential issues:")
- for r in results:
- if r['unified_results_count'] == 0:
- print(f" Q{r['id']}: Unified returned 0 results!")
- if not r['classify_correct']:
- print(f" Q{r['id']}: Misclassified as {r['classified_type']} (expected {r['expected_type']})")
- if r['unified_relevance'] < r['bm25_relevance']:
- print(f" Q{r['id']}: Unified worse than BM25 ({r['unified_relevance']:.0%} vs {r['bm25_relevance']:.0%})")
- # Save results
- with open('/data/knowledge/maritime/test_unified_results.json', 'w') as f:
- json.dump(results, f, ensure_ascii=False, indent=2)
- print(f"\nResults saved to test_unified_results.json")
|