docs(evals): S01–S10 标注集 v1、评测工具和评测报告
- evals/gold:1,887 个标注单元(AI 标注、逐条裁定,未经人工复核),规范见 GUIDELINES.md - prepare / run_all / score / gold:导出文字层、批量抽取、打分、标注定位 - docs/architecture/extract-eval.md:修复前后的结果(三项目标全部达标)和剩余问题 - decisions.md 记 M1(只用 Flash)和 Q1–Q4;架构 §13 SK13;AGENTS.md 进度 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
../.venv/bin/python ../evals/run_extract.py <文件> ../evals/out/<名字> [--v extract.tech_param=2 ...] [--only extract.substantive]
|
||||
BD_AI_MODELS='{"reasoning": "deepseek-v4-pro"}' 换模型对比。
|
||||
同一模型 + 同一提示词版本 + 同一输入只调用一次模型(缓存在 <输出目录>/../cache/),只改规则时重放不花钱。
|
||||
输出:report.md(结果统计 + 模型输出里不合理模式的审计)、items.json(每条结果和模型原始输出)、normalized.pdf。
|
||||
输出:report.md(结果统计 + 模型输出里不合理模式的审计)、items.json(每条结果和模型原始输出)、info.json(项目基本信息和统计)、normalized.pdf。
|
||||
真实招标文件和输出都不进 git(evals/out/ 已忽略)。
|
||||
"""
|
||||
|
||||
@@ -24,11 +24,12 @@ from uuid import UUID
|
||||
|
||||
from bid_doctor.ai import prompts
|
||||
from bid_doctor.ai import service as ai_service
|
||||
from bid_doctor.ai.providers.base import near_limit
|
||||
from bid_doctor.ai.service import provider
|
||||
from bid_doctor.checklist import extract
|
||||
from bid_doctor.core.db import TenantContext
|
||||
from bid_doctor.document import formats
|
||||
from bid_doctor.document.pipeline import convert, structure, text_layer
|
||||
from bid_doctor.document.pipeline import convert, text_layer
|
||||
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("file")
|
||||
@@ -39,7 +40,7 @@ args = ap.parse_args()
|
||||
|
||||
out = pathlib.Path(args.out)
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
cache_dir = out.parent / "cache"
|
||||
cache_dir = pathlib.Path(__file__).resolve().parent / "out" / "cache" # 各次评测共用
|
||||
cache_dir.mkdir(exist_ok=True)
|
||||
|
||||
versions = dict(v.split("=") for v in args.v)
|
||||
@@ -77,8 +78,8 @@ async def cached_run_json(ctx: Any, *, job_type: str, job_id: Any, task: str, pr
|
||||
"incomplete": r.incomplete}
|
||||
f.write_text(json.dumps(data, ensure_ascii=False))
|
||||
calls.append({"task": task, "version": version, "user": user, **data})
|
||||
if data.get("incomplete"):
|
||||
raise ai_service.OutputIncomplete(task) # 和线上一样:输出被截断时由 extract.run 对半重跑
|
||||
if data.get("incomplete") or near_limit(data.get("tokens_out")):
|
||||
raise ai_service.OutputIncomplete(task, data["data"]) # 和线上一样:输出被截断时由 extract.run 对半重跑
|
||||
return data["data"]
|
||||
|
||||
|
||||
@@ -92,18 +93,12 @@ async def main() -> None:
|
||||
(out / "normalized.pdf").write_bytes(pdf)
|
||||
pages = text_layer.extract("D", pdf)
|
||||
body = text_layer.body_sentences(pages)
|
||||
notes = extract.marking_notes(body)
|
||||
doc = extract.Doc(
|
||||
sentences=body,
|
||||
parents=structure.parents(body),
|
||||
continuations=text_layer.continuations(body),
|
||||
notes=notes,
|
||||
declared={m for n in notes for m in n.markers},
|
||||
)
|
||||
doc = extract.build_doc(body)
|
||||
ctx = TenantContext(tenant_id=UUID(int=1), user_id=UUID(int=1))
|
||||
items, info, run_stats = await extract.run(ctx, UUID(int=2), doc)
|
||||
|
||||
by_sid = {f"p{s.page_no}s{s.seq}": s for s in body}
|
||||
_sid = extract._sid # noqa: SLF001
|
||||
by_sid = {_sid(s): s for s in body}
|
||||
# ---------- 结果 ----------
|
||||
zone = Counter((i.zone, i.pending_reason) for i in items)
|
||||
cats = Counter((c, i.zone) for i in items for c in i.categories)
|
||||
@@ -115,6 +110,8 @@ async def main() -> None:
|
||||
"categories": i.categories,
|
||||
"title": i.summary,
|
||||
"importance": i.importance,
|
||||
"importance_basis": i.importance_basis,
|
||||
"basis": i.basis,
|
||||
"sentences": [f"p{s.page_no}s{s.seq}" for s in i.sentences],
|
||||
"quote_pages": sorted({s.page_no for s in i.sentences}),
|
||||
"raw": i.ai_values,
|
||||
@@ -122,6 +119,15 @@ async def main() -> None:
|
||||
for i in items
|
||||
]
|
||||
(out / "items.json").write_text(json.dumps(res, ensure_ascii=False, indent=1))
|
||||
info_out = {
|
||||
"fields": {k: {"value": v.value, "unit": v.unit, "sentence": _sid(v.sentence)} for k, v in info.fields.items()},
|
||||
"status": info.status,
|
||||
"lots": [{k: v for k, v in lot.items() if k != "sentence"} for lot in info.lots],
|
||||
"stats": run_stats,
|
||||
"tokens": [sum(c.get("tokens_in") or 0 for c in calls), sum(c.get("tokens_out") or 0 for c in calls)],
|
||||
"models": sorted({c.get("model") or "" for c in calls}),
|
||||
}
|
||||
(out / "info.json").write_text(json.dumps(info_out, ensure_ascii=False, indent=1))
|
||||
|
||||
# ---------- 模型原始输出的审计 ----------
|
||||
audit: dict[str, Counter[str]] = defaultdict(Counter)
|
||||
|
||||
Reference in New Issue
Block a user