| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270 |
- """
- Dify RAG Retrieval Test Script (Dual Mode)
- ===========================================
- Tests knowledge base retrieval across 5 query types.
- Modes:
- --mode local Local retriever (no Dify needed)
- --mode dify Dify API (requires API key)
- Usage:
- python test_rag_retrieval.py --mode local
- python test_rag_retrieval.py --mode dify --api-key <KEY>
- """
- import os
- import sys
- import json
- import argparse
- # (type, chinese_query, expected_file_substring, doc_hint)
- TEST_QUERIES = [
- # Type 1: Position queries -> should retrieve A1 (命盘24位置详解)
- ("position", "O位置代表什么含义", "命盘", "A1"),
- ("position", "P位置如何计算", "命盘", "A1"),
- ("position", "I和J位置代表什么能量", "命盘", "A1"),
- # Type 2: Combo pair queries -> should retrieve B2 (三角命盘组合对判读规则)
- ("combo", "M和N组合出现天医星怎么解读", "判读", "B2"),
- ("combo", "横向组合对和纵向组合对的区", "判读", "B2"),
- ("combo", "组合对分析的优先顺序是什么", "判读", "B2"),
- # Type 3: Dimension queries -> should retrieve C1 (数字能量分析维度手册)
- ("dimension", "天医星财富维度如何分析", "维度", "C1"),
- ("dimension", "延年星在事业维度的含义", "维度", "C1"),
- ("dimension", "五鬼星对健康维度的影响", "维度", "C1"),
- # Type 4: Zone/group queries -> should retrieve C2 (五区三组分析指南)
- ("zone", "父源区包含哪些位置", "五区", "C2"),
- ("zone", "左侧组年龄段和人生课题", "五区", "C2"),
- ("zone", "五区和三组的交叉分析", "五区", "C2"),
- # Type 5: Main Character Personality -> should retrieve A2 (主性格深度解读)
- ("personality", "7号人深度解读:性格特征的全面分析", "A2", "A2"),
- ("personality", "卓越数11的直觉力和人生课题", "A2", "A2"),
- ("personality", "3号人的适合职业和情感模式", "A2", "A2"),
- # Type 6: Supplementary knowledge -> should retrieve E (天赋数空缺数)
- ("supplement", "天赋数的含义速查表和计算方法", "E天赋", "E"),
- ("supplement", "空缺数代表什么挑战领域", "E天赋", "E"),
- ("supplement", "天赋数的含义和空缺数的挑战领域", "E天赋", "E"),
- # Type 7: Progressive energy -> should retrieve D1 (生命数1组合递进能量)
- ("progression", "生命数1的28-10-1组合递进三阶段", "D1", "D1"),
- ("progression", "46-10-1组合的务实关怀特性", "D1", "D1"),
- # Type 8: Comprehensive (now targets A2 specifically for main character)
- ("comprehensive", "主性格6号人的感情和事业特点", "A2", "A2"),
- ]
- def _dify_retrieve(query_text, api_key, base_url, dataset_id, top_k=6):
- """Dify API retrieval"""
- import requests
- headers = {
- "Authorization": f"Bearer {api_key}",
- "Content-Type": "application/json"
- }
- url = f"{base_url}/v1/datasets/{dataset_id}/retrieve"
- payload = {
- "query": query_text,
- "retrieval_model": {
- "search_method": "hybrid_search",
- "reranking_enable": True,
- "top_k": top_k,
- "score_threshold_enabled": False
- }
- }
- try:
- resp = requests.post(url, headers=headers, json=payload, timeout=30)
- if resp.status_code == 200:
- records = resp.json().get("records", [])
- return records, None
- else:
- return None, f"HTTP {resp.status_code} {resp.text[:200]}"
- except Exception as e:
- return None, str(e)
- def _dify_run_all(api_key, base_url, dataset_id):
- """Execute all Dify retrieval tests"""
- import requests
- print("=" * 70)
- print("Dify KB RAG Retrieval Test")
- print("=" * 70)
- print(f"Dataset: {dataset_id}")
- print(f"URL: {base_url}")
- # Show config
- try:
- resp = requests.get(f"{base_url}/v1/datasets/{dataset_id}",
- headers={"Authorization": f"Bearer {api_key}"}, timeout=15)
- if resp.status_code == 200:
- info = resp.json()
- rm = info.get("retrieval_model_dict", {})
- print(f"KB: {info.get('name', '?')}")
- print(f"Docs: {info.get('document_count')}")
- print(f"Config: method={rm.get('search_method')} top_k={rm.get('top_k')}")
- except Exception:
- pass
- print(f"Queries: {len(TEST_QUERIES)}")
- print()
- stats = {"pass": 0, "fail": 0, "error": 0, "total": len(TEST_QUERIES), "mode": "dify"}
- for qtype, query, expected, hint in TEST_QUERIES:
- print(f"[{qtype}] {query[:40]}...", end=" ")
- results, error = _dify_retrieve(query, api_key, base_url, dataset_id)
- if error:
- print(f"ERROR: {error}")
- stats["error"] += 1
- continue
- if not results:
- print("NO RESULTS")
- stats["fail"] += 1
- continue
- doc_names = []
- for r in results:
- seg = r.get("segment", {})
- doc = seg.get("document", {})
- doc_name = doc.get("name", "")
- if doc_name:
- doc_names.append(doc_name)
- hit = any(expected in name for name in doc_names)
- if hit:
- print(f"HIT [{hint}]")
- stats["pass"] += 1
- else:
- sources = list(doc_names[:5])
- print(f"MISS expected='{expected}' got={sources}")
- stats["fail"] += 1
- if results:
- top = results[0]
- seg = top.get("segment", {})
- snippet = (seg.get("content", "") or "")[:120].replace("\n", " ")
- score = top.get("score", "?")
- print(f" score={score:.4f} | doc: {doc_names[0] if doc_names else '?'}")
- print(f" {snippet}...")
- print()
- return stats
- def _local_run_all():
- """Execute all local retrieval tests"""
- sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
- from local_retriever import LocalRetriever
- retriever = LocalRetriever()
- idx_stats = retriever.index_documents()
- print("=" * 70)
- print("Local KB RAG Retrieval Test")
- print("=" * 70)
- print(f"Docs: {idx_stats['documents']}, Chunks: {idx_stats['chunks']}")
- print(f"Vocab: {idx_stats['vocab_size']}")
- print(f"Queries: {len(TEST_QUERIES)}")
- print()
- stats = {"pass": 0, "fail": 0, "error": 0, "total": len(TEST_QUERIES), "mode": "local"}
- for qtype, query, expected, hint in TEST_QUERIES:
- print(f"[{qtype}] {query[:40]}...", end=" ")
- results = retriever.query(query, top_k=6)
- if not results:
- print("NO RESULTS")
- stats["error"] += 1
- continue
- hit = any(expected in r["source"] for r in results)
- if hit:
- print(f"HIT [{hint}]")
- stats["pass"] += 1
- else:
- top_src = [r["source"] for r in results[:5]]
- print(f"MISS expected='{expected}' got={top_src}")
- stats["fail"] += 1
- top = results[0]
- print(f" score={top['score']:.4f} | {top['source']} > {top['heading']}")
- print(f" {top['content_preview'][:120].replace(chr(10), ' ')}")
- print()
- return stats
- def print_report(stats):
- total = stats["total"]
- passed = stats["pass"]
- failed = stats["fail"]
- errored = stats["error"]
- print("=" * 70)
- print("TEST REPORT")
- print("=" * 70)
- print(f"Mode: {stats.get('mode', '?')}")
- print(f"Total: {total}")
- print(f"Pass: {passed}")
- print(f"Fail: {failed}")
- print(f"Error: {errored}")
- effective = total - errored
- if effective > 0:
- rate = passed * 100 // effective
- print(f"Rate: {rate}% ({passed}/{effective})")
- if failed == 0 and errored == 0:
- print(f"\n ALL TESTS PASSED!")
- elif failed > 0:
- print(f"\n {failed} queries missed expected docs")
- if errored > 0:
- print(f"\n {errored} queries errored")
- return stats
- def parse_args():
- p = argparse.ArgumentParser(description="RAG Retrieval Test")
- p.add_argument("--mode", choices=["local", "dify"], default="local")
- p.add_argument("--api-key")
- p.add_argument("--base-url", default="http://dify.bianwoyou.cn")
- p.add_argument("--dataset-id", default="3ff939b3-8686-44f6-8ef5-65b1e53b55d3")
- return p.parse_args()
- def main():
- args = parse_args()
- if args.mode == "local":
- stats = _local_run_all()
- elif args.mode == "dify":
- api_key = args.api_key or os.environ.get("DIFY_API_KEY")
- if not api_key:
- print("Error: DIFY_API_KEY required for dify mode")
- sys.exit(1)
- stats = _dify_run_all(api_key, args.base_url, args.dataset_id)
- else:
- print(f"Unknown mode: {args.mode}")
- sys.exit(1)
- stats = print_report(stats)
- if stats.get("error", 0) > 0:
- sys.exit(2)
- if stats.get("fail", 0) > 0:
- sys.exit(1)
- if __name__ == "__main__":
- main()
|