diff --git a/AGENTS.md b/AGENTS.md index f5d6ac8..f27aaa4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -208,4 +208,5 @@ samples/ - 2026-11-04:查标第 1 步(链路打通)完成:上传投标分册(类型、版本、加密格式识别)→ 检查(六个阶段,响应匹配先只有检索 + 模型判断,提示词 `check.match` v1)→ 每条清单项留带证据的结论 → 生成问题 → 逐条处理(致命确认无误要写说明并进误报反馈)→ 复查沿用状态;计费三种方式和失败退回都有测试,后端 80 个测试通过。前端加了上传投标文件并检查页和查标页。SK1、SK2 的补丁已做。真实模型在虚构样例上走通了链路,效果还没评。见 `docs/architecture/m3-plan.md` 进度一节。 - 2026-11-04:查标第 0 步(评测集)。规范 v0.2(`evals/check/GUIDELINES.md`,Fable 审查后修改):响应方式按「文件 × 类别」记,规矩投标文件靠构造保证,问题只挂复核过的要求,留出集词库和风格 C 封存。C12(响应方式)、E1(复核由 Fable 做、用户抽查 20%)已确认。开发集 S02、S04、S10 六份试跑(`docs/architecture/check-eval.md` §1):查标召回 38%,规矩投标文件的误报大头是不需要响应的条目、表格里看不见同一行的响应格、扫描件和格式文件找不到。 - 2026-11-04:用户抽查 Fable 的复核 17 条,全部同意。 -- 下一步:修评测集待修项、做风格 B、扩开发集;查标第 2 步(投标侧结构识别、规则、响应方式抽取)。 +- 2026-11-04:查标评测开发集第一版:7 份招标文件、14 份投标文件(风格 A / B 轮换),注入问题 222 个、Fable 复核通过 188 个。查标召回 40%(致命级 49%),带标志词的问题只有 22%;规矩投标文件报出 699 个问题,近一半在不需要响应的要求上(`docs/architecture/check-eval.md` §2)。 +- 下一步:用户填第二批复核抽查(`evals/out/check/review/spot-check-2.md`);修评测集待修项;查标第 2、3 步按 check-eval §2.2 的顺序改(整行上下文 / 偏离表直取 → 响应方式 → 扫描件和 presence)。 diff --git a/docs/architecture/check-eval.md b/docs/architecture/check-eval.md index 44f85de..9bf9e54 100644 --- a/docs/architecture/check-eval.md +++ b/docs/architecture/check-eval.md @@ -46,8 +46,45 @@ | 「提供某文件」类要求被当成参数注入偏离 | 待修:推响应方式时识别 presence | | 风格 B 的改写(截断、同义替换、只写值)还没做;所有数字都是风格 A 的 | 待做 | -## 2. 下一步 +## 2. 开发集第一版(2026-11-04) -1. 修 §1.3 的待修项,做风格 B 的改写,再扩到开发集其余 4 份(S01、S05、S07、S08)。 -2. 第 2 步(投标侧结构识别、规则、C12 响应方式抽取)和第 3 步(整行上下文、偏离表直取、presence 判断)按 §1.2 的方向改,每改一轮在开发集上重跑对比。 +**范围**:开发集 7 份招标文件各两份投标文件,共 14 份:S02、S05、S08、S10 是 A 规矩 + B 马虎,S01、S04、S07 是 B 规矩 + A 马虎(GUIDELINES §4)。生成器修了试跑的待修项(「暂不支持」不再套在数值参数上、表格响应方式以档案为准、「提供……文件」不注入参数类问题),风格 B 由模型改写、数字对不上的改写丢弃。查标代码仍是第 1 步的版本。 + +**复核**:注入的要求项问题两批共 222 个,Fable 复核通过 188 个。第二批 140 个里不通过 22 个,其中 14 个在 S07:S07 须知要求逐条标明,格式注又写未列入视作接受,且只有 ★ 项写明未响应无效,非 ★ 参数删一行算不算问题拿不准,金标准从严不算(查标侧仍按 C12 取更严的逐条)。用户抽查第一批 17 条全部同意;第二批抽查 28 条(`evals/out/check/review/spot-check-2.md`)待填。 + +### 2.1 结果 + +| 指标 | 结果 | +|---|---| +| 查标召回(复核通过的问题) | 51 / 128(40%);风格 A 25 / 70(36%),风格 B 26 / 58(45%) | +| 端到端召回(含读标漏抽) | 51 / 132(39%) | +| 致命级召回 | 18 / 37(49%) | +| 不带标志词 / 带标志词 | 40 / 81(49%)/ 11 / 51(22%) | +| 连续一段 / 单点 | 7 / 12(58%)/ 44 / 120(37%) | +| 规矩的投标文件报出的问题 | 清单 2049 条,报出 699 个 | +| 其中规矩响应的要求上的误报 | 153 / 620(每 100 条 24.7) | +| 其中不需要响应的要求上报的 | 334 个(占 48%) | +| 干扰项误报 | 默认接受的要求没复述(T06)39,改写后的要求(T02)20,响应写在别的分册(T01)3 | +| 费用 | 14 次检查输入 150 万 token、输出 25 万 token | + +按问题类型:资格证明缺失(D11)9 / 14、评分材料缺失(D13)9 / 12、漏响应(D01)15 / 23 较好;负偏离未说明(D06)0 / 20、响应对象错位(D09)1 / 14、证明材料缺失(D02)2 / 9、写明不支持(D05)11 / 31 很差。 + +### 2.2 读法 + +- **试跑的四个判断都成立,而且更清楚了**:「带标志词」的问题(写明不支持、负偏离)召回只有 22%,比不带的还低,正是表格里同一行的响应格、偏离格不在模型输入里;风格 B 反而比 A 高,因为 A 照抄要求原文,检索更容易命中照抄的那一格。规矩的投标文件上近一半的问题落在不需要响应的要求上,默认接受的要求被报了 39 次。 +- **第 2、3 步按这个顺序改收益最大**:① 整行上下文 / 偏离表直取(直接对应 D05、D06、D09,51 个问题里的 45 个);② 响应方式和「这条要不要投标人回答」(C12,对应 334 + 39 个误报);③ 扫描件兜底和 presence 判断(证明材料、格式文件类误报)。 + +### 2.3 评测集还要改的 + +| 问题 | 状态 | +|---|---| +| 「暂不支持」「可选配置」仍会套在证书、供货清单、服务义务、文档交付这类非功能要求上(两批共 12 条被复核拒掉) | 待修:注入前按「功能类要求」过滤 | +| 同一要求被两个标注单元挂了同一个问题(实质性 + 技术参数、资格 + 符合性,第二批约 20 对);「第二十二条总述」这类伞状单元和具体条目不重叠 | 待修:注入前按 anchor 重叠合并标注单元;伞状单元不挂问题 | +| 条件性要求(「如依法免税则须提供」)被当作必须提供的材料挂 D11 | 待修:按投标人情况排除 | +| 低密度(每份 5~8 个问题)的投标文件还没做 | 待做:选 3 份 | + +## 3. 下一步 + +1. 修 §2.3 的待修项,补 3 份低密度的投标文件。 +2. 第 2 步(投标侧结构识别、规则、C12 响应方式抽取)和第 3 步(整行上下文、偏离表直取、presence 判断)按 §2.2 的顺序改,每改一轮在开发集 14 份上重跑对比。 3. 留出集(S03、S06、S09,风格 B + C,封存的词库)和真实国动投标文件,在第 3 步冻结代码后只跑一次。 diff --git a/docs/architecture/m3-plan.md b/docs/architecture/m3-plan.md index 5bd58f1..1ceb841 100644 --- a/docs/architecture/m3-plan.md +++ b/docs/architecture/m3-plan.md @@ -8,7 +8,7 @@ | 步骤 | 状态 | 说明 | |---|---|---| -| 第 0 步 评测集 | 试跑完成,继续扩充 | 规范 v0.2、留出集划分已定,留出集词库和风格 C 已封存;生成、注入、渲染、离线查标、评分工具已写;开发集 S02、S04、S10 六份试跑完,结果和发现见 `check-eval.md` §1 | +| 第 0 步 评测集 | 试跑完成,继续扩充 | 规范 v0.2、留出集划分已定,留出集词库和风格 C 已封存;生成、注入、渲染、离线查标、评分工具已写;开发集 7 份招标文件、14 份投标文件第一版跑完,结果和发现见 `check-eval.md` §2 | | 第 1 步 链路打通 | ✅ 2026-11-04 | 见下方「第 1 步落地记录」 | | 第 2~6 步 | 未开始 | | diff --git a/evals/check/report.py b/evals/check/report.py new file mode 100644 index 0000000..d41233f --- /dev/null +++ b/evals/check/report.py @@ -0,0 +1,75 @@ +"""汇总一批投标文件的查标评分(evals/check/GUIDELINES.md §7)。先对每份跑过 score.py。 + + cd server && ../.venv/bin/python ../evals/check/report.py --run base <投标文件> [...] +""" + +from __future__ import annotations + +import argparse +import json +import pathlib +from collections import Counter + +OUT = pathlib.Path(__file__).resolve().parents[1] / "out" / "check" + + +def pct(a: int, b: int) -> str: + return f"{a}/{b}({a / b:.0%})" if b else "—" + + +def main() -> None: + ap = argparse.ArgumentParser() + ap.add_argument("bids", nargs="+") + ap.add_argument("--run", default="base") + a = ap.parse_args() + rec: Counter[str] = Counter() + tot: Counter[str] = Counter() + by_style: Counter[str] = Counter() + by_style_tot: Counter[str] = Counter() + fp = ok = unmapped = other = 0 + traps: Counter[str] = Counter() + tokens = [0, 0] + findings_clean = items_clean = 0 + for bid in a.bids: + d = OUT / bid + m = json.loads((d / "manifest.json").read_text()) + s = json.loads((d / "runs" / a.run / "score.json").read_text()) + r = json.loads((d / "runs" / a.run / "results.json").read_text()) + tokens[0] += r["summary"]["usage"]["tokens_in"] + tokens[1] += r["summary"]["usage"]["tokens_out"] + for k, (hit, n) in s["recall"].items(): + rec[k] += hit + tot[k] += n + if "查标" in s["recall"]: + by_style[m["style"]] += s["recall"]["查标"][0] + by_style_tot[m["style"]] += s["recall"]["查标"][1] + if m["kind"] == "clean": + fp += s["fp"] + ok += s["ok_units"] + other += s["other_findings"] + unmapped += s["unmapped"] + traps.update(s["fp_by_trap"]) + findings_clean += sum(1 for it in r["items"] if it["finding"]) + items_clean += len(r["items"]) + lines = [ + f"# 汇总:{len(a.bids)} 份投标文件 · 运行 {a.run}", + "", + f"- 查标召回:{pct(rec['查标'], tot['查标'])};端到端:{pct(rec['端到端'], tot['端到端'])};致命级:{pct(rec['致命级'], tot['致命级'])}", + "- 按风格:" + ";".join(f"{k} {pct(by_style[k], by_style_tot[k])}" for k in sorted(by_style_tot)), + "", + "| 分类 | 召回 |", + "|---|---|", + *[f"| {k} | {pct(rec[k], tot[k])} |" for k in sorted(tot) if k not in ("查标", "端到端", "致命级")], + "", + f"- 规矩的投标文件:清单 {items_clean} 条,报出问题 {findings_clean} 个;其中规矩响应的要求上 {fp}/{ok}(每 100 条 {100 * fp / ok:.1f})" + if ok + else "", + f"- 规矩的投标文件里,不需要响应(na / unknown)的要求上报了问题 {other} 个,对不上标注单元的问题 {unmapped} 个", + f"- 干扰项误报:{dict(traps)}", + f"- token:输入 {tokens[0]:,},输出 {tokens[1]:,}({len(a.bids)} 次检查)", + ] + print("\n".join(lines)) + + +if __name__ == "__main__": + main()