| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207 |
- #!/usr/bin/env python3
- """
- Step 3: Analyze RAG vs Wiki comparison results
- """
- import json
- import sys
- from collections import Counter, defaultdict
- from pathlib import Path
- sys.path.insert(0, '/home/67/knowledge/maritime')
- sys.path.insert(0, '/home/67/lambdagentpaas')
- # lambdagent migrated to src-layout — add the src dir so 'from lambdagent.X'
- # still resolves when running outside an editable-install venv.
- sys.path.insert(0, '/home/67/lambdagentpaas/lambdagent/src')
- RESULTS_FILE = Path('/home/67/knowledge/maritime/batch_results_200.json')
- REPORT_FILE = Path('/home/67/knowledge/maritime/analysis_report.md')
- from lambdagent.providers import create_provider
- def main():
- with open(RESULTS_FILE) as f:
- data = json.load(f)
- results = data['results']
- meta = data.get('meta', {})
- total = len(results)
- print(f"Analyzing {total} Q&A pairs...")
- # ── Basic Stats ──
- rag_times = [r['rag']['elapsed'] for r in results if not r['rag'].get('error')]
- wiki_times = [r['wiki']['elapsed'] for r in results if not r['wiki'].get('error')]
- rag_lens = [len(r['rag']['answer']) for r in results if not r['rag'].get('error')]
- wiki_lens = [len(r['wiki']['answer']) for r in results if not r['wiki'].get('error')]
- rag_errors = sum(1 for r in results if r['rag'].get('error'))
- wiki_errors = sum(1 for r in results if r['wiki'].get('error'))
- # Wiki source types
- wiki_sources = Counter(r['wiki'].get('source_type', 'unknown') for r in results)
- # By question type
- by_type = defaultdict(lambda: {'rag_times': [], 'wiki_times': [], 'rag_lens': [], 'wiki_lens': [], 'count': 0})
- for r in results:
- t = r.get('type', 'unknown')
- by_type[t]['count'] += 1
- if not r['rag'].get('error'):
- by_type[t]['rag_times'].append(r['rag']['elapsed'])
- by_type[t]['rag_lens'].append(len(r['rag']['answer']))
- if not r['wiki'].get('error'):
- by_type[t]['wiki_times'].append(r['wiki']['elapsed'])
- by_type[t]['wiki_lens'].append(len(r['wiki']['answer']))
- # By difficulty
- by_diff = defaultdict(lambda: {'rag_times': [], 'wiki_times': [], 'count': 0})
- for r in results:
- d = r.get('difficulty', 'medium')
- by_diff[d]['count'] += 1
- if not r['rag'].get('error'):
- by_diff[d]['rag_times'].append(r['rag']['elapsed'])
- if not r['wiki'].get('error'):
- by_diff[d]['wiki_times'].append(r['wiki']['elapsed'])
- # ── Quality Analysis using LLM ──
- # Sample 20 questions for LLM-based quality evaluation
- print("Running LLM quality evaluation on 20 sampled answers...")
- provider = create_provider('ollama', model='qwen2.5:32b', timeout=600)
- import random
- sample = random.sample(results, min(20, total))
- quality_scores = {'rag': [], 'wiki': []}
- for i, r in enumerate(sample):
- eval_prompt = f"""请评估以下两个回答的质量。评分标准(1-5分):
- - 准确性: 信息是否正确
- - 完整性: 是否覆盖问题要点
- - 引用: 是否有来源标注
- - 条理性: 结构是否清晰
- 问题: {r['question']}
- 回答A (RAG模式):
- {r['rag']['answer'][:800]}
- 回答B (Wiki模式):
- {r['wiki']['answer'][:800]}
- 请严格按以下JSON格式输出评分,不要输出其他内容:
- {{"rag_score": <1-5>, "wiki_score": <1-5>, "rag_strength": "<一句话>", "wiki_strength": "<一句话>", "winner": "<rag或wiki或tie>"}}"""
- try:
- result = provider.chat([{'role': 'user', 'content': eval_prompt}])
- # Try to parse JSON from response
- import re
- json_match = re.search(r'\{[^{}]+\}', result)
- if json_match:
- scores = json.loads(json_match.group())
- quality_scores['rag'].append(scores.get('rag_score', 3))
- quality_scores['wiki'].append(scores.get('wiki_score', 3))
- print(f" [{i+1}/20] RAG={scores.get('rag_score')}, Wiki={scores.get('wiki_score')}, Winner={scores.get('winner')}")
- else:
- print(f" [{i+1}/20] Could not parse scores")
- except Exception as e:
- print(f" [{i+1}/20] Error: {e}")
- # ── Generate Report ──
- def avg(lst):
- return round(sum(lst) / len(lst), 1) if lst else 0
- def median(lst):
- if not lst: return 0
- s = sorted(lst)
- n = len(s)
- return s[n//2] if n % 2 else round((s[n//2-1] + s[n//2]) / 2, 1)
- rag_avg_score = avg(quality_scores['rag']) if quality_scores['rag'] else '-'
- wiki_avg_score = avg(quality_scores['wiki']) if quality_scores['wiki'] else '-'
- report = f"""# Maritime QA Comparison Report
- # RAG vs Wiki -- {total} Questions
- Generated: {meta.get('completed_at', 'N/A')}
- Total time: {meta.get('total_minutes', 'N/A')} minutes
- ---
- ## 1. Overview
- | Metric | RAG | Wiki |
- |--------|-----|------|
- | Questions answered | {total - rag_errors} | {total - wiki_errors} |
- | Errors | {rag_errors} | {wiki_errors} |
- | Avg response time | {avg(rag_times)}s | {avg(wiki_times)}s |
- | Median response time | {median(rag_times)}s | {median(wiki_times)}s |
- | Min/Max time | {min(rag_times) if rag_times else '-'}/{max(rag_times) if rag_times else '-'}s | {min(wiki_times) if wiki_times else '-'}/{max(wiki_times) if wiki_times else '-'}s |
- | Avg answer length | {avg(rag_lens)} chars | {avg(wiki_lens)} chars |
- | Total time | {sum(rag_times):.0f}s | {sum(wiki_times):.0f}s |
- ## 2. Quality Evaluation (LLM-judged, 20 sample)
- | Metric | RAG | Wiki |
- |--------|-----|------|
- | Average score (1-5) | {rag_avg_score} | {wiki_avg_score} |
- | Scores distribution | {Counter(quality_scores['rag'])} | {Counter(quality_scores['wiki'])} |
- ## 3. By Question Type
- | Type | Count | RAG avg time | Wiki avg time | RAG avg len | Wiki avg len |
- |------|-------|-------------|--------------|------------|-------------|
- """
- for t in ['fact', 'reasoning', 'synthesis']:
- if t in by_type:
- d = by_type[t]
- report += f"| {t} | {d['count']} | {avg(d['rag_times'])}s | {avg(d['wiki_times'])}s | {avg(d['rag_lens'])} | {avg(d['wiki_lens'])} |\n"
- report += f"""
- ## 4. By Difficulty
- | Difficulty | Count | RAG avg time | Wiki avg time |
- |-----------|-------|-------------|--------------|
- """
- for d_name in ['easy', 'medium', 'hard']:
- if d_name in by_diff:
- d = by_diff[d_name]
- report += f"| {d_name} | {d['count']} | {avg(d['rag_times'])}s | {avg(d['wiki_times'])}s |\n"
- report += f"""
- ## 5. Wiki Knowledge Accumulation
- | Source Type | Count | Percentage |
- |------------|-------|-----------|
- """
- for src, cnt in wiki_sources.most_common():
- report += f"| {src} | {cnt} | {cnt*100/total:.1f}% |\n"
- report += f"""
- ## 6. Key Findings
- ### RAG Mode Characteristics
- - Average response time: {avg(rag_times)}s
- - Direct retrieval from 62,011 text chunks
- - Each answer based on top-{results[0]['rag'].get('chunks_used', 5) if results else 5} retrieved chunks
- - Consistent response time regardless of question complexity
- ### Wiki Mode Characteristics
- - Average response time: {avg(wiki_times)}s
- - First query requires compilation (2 LLM calls), subsequent queries faster if wiki hit
- - Wiki pages accumulated: {wiki_sources.get('wiki', 0)} hits / {wiki_sources.get('compiled', 0)} compilations
- - Knowledge accumulates over time -- later questions benefit from earlier compilations
- ### Speed Comparison
- - RAG is {'faster' if avg(rag_times) < avg(wiki_times) else 'slower'} by {abs(avg(rag_times) - avg(wiki_times))}s on average
- - Wiki first-query overhead: ~{avg(wiki_times) - avg(rag_times):.0f}s extra for compilation
- ### Quality Comparison (LLM-judged)
- - RAG average score: {rag_avg_score}/5
- - Wiki average score: {wiki_avg_score}/5
- """
- with open(REPORT_FILE, 'w', encoding='utf-8') as f:
- f.write(report)
- print(f"\nReport saved to: {REPORT_FILE}")
- print("\n" + report)
- if __name__ == '__main__':
- main()
|