| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980 |
- import argparse
- import asyncio
- import json
- import os
- from collections import defaultdict
- from pathlib import Path
- from time import perf_counter
- from uuid import UUID
- import httpx
- from zhifu.evaluation import RetrievalManifest, digest, write_report
- async def evaluate(args, manifest):
- report = {"manifest_sha256": digest(manifest.model_dump(mode="json")),
- "dataset_version": manifest.dataset_version, "origin": manifest.origin,
- "generation_attempts": 0, "request_limit": len(manifest.queries),
- "total_timeout_seconds": manifest.total_timeout_seconds,
- "embedding_model": manifest.embedding_model,
- "embedding_revision": manifest.embedding_revision, "results": [],
- "status": "RUNNING"}
- started = perf_counter()
- # 仅检索现有发布资料。写入和发布由正常业务接口完成,评测令牌仅需读取权限。
- try:
- async with asyncio.timeout(manifest.total_timeout_seconds), httpx.AsyncClient(
- base_url=args.base_url, timeout=30, trust_env=False,
- headers={"Authorization": "Bearer " + os.environ[args.token_env]},
- ) as client:
- for item in manifest.queries:
- began = perf_counter()
- response = await client.post(
- f"/openapi/v1/stores/{manifest.store_id}/knowledge/search",
- json={"query": item.query, "limit": 10},
- )
- response.raise_for_status()
- found = []
- for document in response.json()["items"]:
- identifier = UUID(document["document_id"])
- if manifest.documents.get(identifier) != document["version"]:
- raise ValueError("检索结果出现未登记资料或版本变化")
- if identifier not in found:
- found.append(identifier)
- recall = len(set(found) & set(item.expected)) / len(set(item.expected))
- report["results"].append({"query": item.query, "category": item.category,
- "expected": [str(key) for key in item.expected],
- "retrieved": [str(key) for key in found], "recall_at_10": recall,
- "seconds": perf_counter() - began})
- await asyncio.to_thread(write_report, args.output, report)
- categories = defaultdict(list)
- for result in report["results"]:
- categories[result["category"]].append(result["recall_at_10"])
- report["categories"] = {key: {"count": len(values),
- "recall_at_10": sum(values) / len(values)} for key, values in categories.items()}
- report["recall_at_10"] = sum(row["recall_at_10"] for row in report["results"]) / len(
- report["results"])
- report["status"] = "PASSED" if report["recall_at_10"] >= 0.9 else "FAILED"
- finally:
- if report["status"] == "RUNNING":
- report["status"] = "INCOMPLETE"
- report["seconds"] = perf_counter() - started
- await asyncio.to_thread(write_report, args.output, report)
- print(json.dumps({key: value for key, value in report.items() if key != "results"}))
- if report["status"] != "PASSED":
- raise ValueError("Recall@10 未达到 90%")
- if __name__ == "__main__":
- parser = argparse.ArgumentParser(description="真实知识检索 Recall@10 验收")
- parser.add_argument("--manifest", type=Path, required=True)
- parser.add_argument("--output", type=Path, required=True)
- parser.add_argument("--base-url", default="http://127.0.0.1:18000")
- parser.add_argument("--token-env", default="EVALUATION_ACCESS_TOKEN")
- args = parser.parse_args()
- manifest = RetrievalManifest.model_validate_json(args.manifest.read_bytes())
- if manifest.review:
- manifest.review.verify()
- if manifest.deployment_review:
- manifest.deployment_review.verify()
- asyncio.run(evaluate(args, manifest))
|