Files
bid-doctor/docs/architecture/tech-validation.md
YANG JIANKUAN 604e889ca4 docs(architecture): 完成技术验证 S1–S4,确定 FR-M2-05/06 目标值
- spikes/:Word 转 PDF(Gotenberg)、格式识别、原句坐标、DeepSeek 抽取四项验证代码与两版提示词;样本派生产物和 .env 不进 git
- tech-validation.md:完整结论。四项都可行:WPS 字体字符集要修正并改用行高调整过的字体;格式识别不需要 Docling;表格内切句待改进;抽取需第三道依据校验与规则兜底
- architecture.md:§3.5 加预处理与字体包,§3.6 证据校验改为三道,§10 回填结论
- 03 功能需求与 decisions.md(TV1):召回 ≥ 99%、采纳准确率 ≥ 95%、页码 100%、高亮正文 ≥ 98% / 表格 ≥ 95%

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 12:46:49 +08:00

17 KiB
Raw Blame History

技术验证 S1–S4 结论(2026-11-03)

依据:架构文档 architecture.md §10。代码在 spikes/(一次性代码,复现步骤见 spikes/README.md),由样本派生的产物都在各子目录的 out/ 里,不进 git。 环境:本机 Docker 29.8.2(Apple Silicon,arm64),Gotenberg 8.39.0;样本是 samples/tenders/ 的 S01–S10。 模型:DeepSeek 官方接口,配置名 deepseek-chat,接口返回实际服务的模型是 deepseek-flash(DeepSeek-V4.1-Flash),非思考模式(思考 token 为 0)。

0. 结论速览

# 结论 关键数字 对架构的影响
S1 可行,但不能直接用 Gotenberg 默认配置:要先修正 WPS 写错的字体字符集,再换成按宋体行高调整过的开源字体 默认配置:页数偏差 +17%~+37%,S07 正文全部空白;调整后:两份有真值的文件 79/79、75/73 页,目录页码跨度误差 0%~-2.2% §3.5 新增两步预处理和字体包
S2 可行:在规范化 PDF 上识别「斜体且下划线」,和 DOCX 原文件逐字对得上 4 份文件按字符 100% 一致;片段级召回 0.993~1.0 不需要 Docling 来保格式
S3 正文可行,表格要改进:原句高亮的几何位置全部正确;切句在表格里会把相邻单元格拼在一起 几何回读 50/50、300/300;文本保真:正文 97.5%,表格内 78% 切句要识别表格单元格和页眉页脚
S4 可行:两道证据校验 + 第三道「依据校验」后,抽取结果可用;★ 类条款建议用规则兜底保召回 ★ 召回 93.5%(扣除银标准噪音后约 96.6%),斜体下划线 99.8%;采纳条目 95.2% 属实;每份文件约 0.15 元(高峰价) §3.6 新增依据校验和规则兜底

1. S1 Word 转 PDF 的保真度

1.1 环境核实

  • Gotenberg 镜像:gotenberg/gotenberg:8(8.39.0)有 arm64 版本,镜像约 2.5GB,基础系统是 Debian 13。
  • LibreOffice 版本:26.8.1,比参考项目踩过坑的 7.3 / 24.2 都新。
  • 自带中文字体:只有 Noto Sans / Serif CJK。宋体、仿宋、黑体全部回落到西文的 Noto Sans,衬线和黑体都分不清。
  • 样本用到的字体:宋体(约 4.8 万处)、仿宋、黑体、楷体、方正系列、Times New Roman、Arial。都是商业字体,不能随镜像分发。
  • OLE 嵌入对象:10 份样本里都没有,所以「内嵌 OLE 的 docx 会让旧版 LibreOffice 崩溃」这一条无法用本批样本验证,留到收集到这类样本时再测。
  • 耗时与稳定性:10 份文件 × 6 种配置,全部转换成功,没有崩溃;单份 0.4~2.0 秒。

1.2 真值

来源 覆盖 说明
旧版 DOC 里 WPS 保存时记录的页数 S07(79 页)、S09(73 页) OLE SummaryInformation
DOCX 的 docProps/app.xml 页数 没有 8 份 DOCX 都是 ONLYOFFICE 7.2 生成的(苏采云平台导出),不写页数
DOCX 目录里缓存的页码(PAGEREF) S03、S04、S06 生成文档时排版写入;比较「各章标题之间隔了几页」,消除页码起始值的影响

发现:平台导出的 DOCX 没有「Word 里显示的页数」可以对照。用户在 Word 或 WPS 里看到的分页,取决于他们自己装的软件和字体。所以「出处页码以规范化 PDF 为准」这条设计是必需的,不是权宜之计。

1.3 严重问题:WPS 旧版 DOC 转出来正文是空白

  • 现象:S07 默认转换后,正文渲染成空白,抽出的文字全是「第第第第」;S07 只有 61 页,真值是 79 页。
  • 原因:WPS 在字体表里把「宋体」的字符集标成了 02(SYMBOL,符号字符集)。LibreOffice 据此把正文当成符号,换成了 OpenSymbol 字体;转成 DOCX 后,这些字也变成了 <w:sym> 符号字符,不是普通文本。
  • 修复:先转 DOCX,把中文字体名被标成 SYMBOL 的字符集改成 GB2312(86),再转 PDF。修复后 S07 正文正常,80 页。
  • 影响:
    • 转换前必须加这一步预处理。
    • 用 DOCX 做交叉校验时,读文本要同时读 <w:t> 和 <w:sym>。
    • 只要规范化 PDF 里出现大段 OpenSymbol 字体、或抽出的文字大量重复同一个字,就判定为「转换异常」:不扣点,提示用户。

1.4 字体配置对比(页数)

配置 说明
A-default Gotenberg 默认,原文件直接转
B-alias 加 fontconfig 规则:宋体 / 仿宋 / 楷体 / 方正系列 → Noto Serif CJK SC,黑体 / 雅黑 → Noto Sans CJK SC
A-fixed / B-fixed 同上,但先做 §1.3 的字符集修正
C10-fixed 字符集修正 + 用 fontTools 克隆 Noto CJK,字形不变,只把行高改成 1.0em(和宋体一致,上 0.86 / 下 0.14),改名 BD Song / BD Hei,替换规则同 B
C13-fixed 同 C10,行高 1.3em
样本 真值 A-default B-alias A-fixed B-fixed C10-fixed C13-fixed
S07(WPS) 79 61(正文空白) 61(正文空白) 80 80 79 80
S09(WPS) 73 89 90 91 92 75 87
S01 — 93 95 93 95 81 90
S03 — 145 145 145 145 104 131
S06 — 198 199 198 199 166 190

目录页码真值(标题之间的页数跨度误差,括号里是最大单点偏差):

样本 目录项 A-fixed C10-fixed C13-fixed
S03 7 +36.7%(29 页) -1.3%(1 页) +24.1%(19 页)
S04 8 +32.3%(20 页) 0.0%(1 页) +22.6%(14 页)
S06 6 +17.3%(26 页) **-2.2%(11 页)**¹ +11.5%(26 页)

¹ S06 只匹配上 4 个目录项,最大偏差里有标题匹配不准的成分。

结论:

  • 分页偏差的主因是行高,不是衬线还是黑体:Noto CJK 的行高约 1.45em,宋体是 1.0em,同样的「单倍行距」在 Noto 下每行高出 45%。
  • 克隆字体的行高改成 1.0em 后,5 个有真值的数据点都落在 ±3% 以内,单点偏差大多在 1 页内。
  • 字体许可:Noto CJK 是 OFL 1.1,允许修改后再分发;改名后的字体仍按 OFL 分发,许可证文件要随镜像带上。
  • 仍然存在的风险:
    • 这 5 个数据点都以宋体为主。仿宋、楷体为主的文件(S01、S02、S08)没有真值,偏差未知。
    • 克隆字体只调了行高,西文字宽和宋体的半角宽度不同。表格密集、中英混排多的文件可能仍有偏差。

2. S2 格式识别(斜体 / 下划线 / 加粗)

2.1 方法

  • 银标准:DOCX 原文件里「斜体且下划线」的文字。解析 XML 时完整处理样式继承:文档默认 → 段落样式链 → 字符样式链 → 直接格式。注意 python-docx 的 paragraphs 不包含表格里的段落,直接用它会漏掉大半。
  • PDF 侧:
    • 斜体:字体名含 Italic,或字形矩阵有倾斜(中文没有斜体字形,LibreOffice 合成斜体时倾斜量是 0.35);
    • 下划线:字符底边附近有一条横线覆盖该字;
    • 加粗:字体名含 Bold。
  • 下划线的判定窗口要包含「字符框内侧」:克隆字体的下行部分变小后,下划线会落进字符框里。

2.2 结果(规范化 PDF = C10-fixed)

样本 DOCX 斜体下划线字数 PDF 识别字数 按字符一致 片段召回 片段精确率
S06 14209 14209 100% 0.993 0.871²
S08 1088 1088 100% 1.000 1.000
S09 70 70 100% 1.000 1.000
S03 98 98 100% 1.000 1.000

² S06 的片段精确率偏低,是因为 PDF 按阅读顺序把相邻表格单元格的片段连在了一起,按片段匹配就对不上;按字符计数两边完全一致。默认字体配置(A-fixed)下结果相同,说明格式识别不依赖字体选择。

2.3 Docling 对比(S06)

输入 耗时 保留斜体下划线 页码和坐标 表格识别
DOCX 原文件 2.1 秒 100%(14209 字) 没有 31 个
规范化 PDF 96.2 秒(CPU) 0 有 73 个
  • docling-serve-cpu 有 arm64 镜像,约 7.2GB,本机拉取约 5 分钟。版本:docling-serve 1.38.2、docling 2.142.1。
  • 结论:Docling 做不到「格式 + 坐标」同时具备;我们自己从规范化 PDF 提取,两样都有,而且 4 份文件合计只要约 6 秒。Docling 不作为主解析器。它的 PDF 表格识别可以作为候选,用来解决 S3 的表格切句问题,要再评估。

3. S3 原句级坐标与高亮

3.1 方法

  • 从规范化 PDF 取逐字坐标,按行聚类;同一行里字间空隙大于 1.5 个字号时断开,得到「行片段」。
  • 在 。!?; 处断句;段落末行或表格单元格结束时也断句。句子不跨页。
  • 编号格式:<文档>:p<页>:s<页内序号>。规范化 PDF 不再重新生成,所以编号稳定。
  • 每句生成 quads:按行合并成矩形。
  • 校验分两种:
    • 几何回读:用 quads 裁剪原页重新取字,和句子比对;
    • 文本保真:句子能在 DOCX 原文里找到,不算 Word 自动编号和页码域。

3.2 结果

指标 50 句(种子 20261103) 300 句(种子 7)
10 份文件总句数 24020 24020
跨行句子占比 23 / 50 118 / 300
几何回读正确 50 / 50 300 / 300
文本保真 49 / 50(98%) 269 / 300(89.7%)

300 句按位置分类的文本保真率:

位置 文本保真 失败原因
正文 196 / 201 = 97.5% 个别行内拼接
表格内 71 / 91 = 78.0% 相邻单元格(序号列、分值列)的文字被拼进同一句
页眉页脚带 2 / 8 = 25.0% 页眉文字混进了正文句
  • 高亮图在 spikes/s3-citation/out/highlight-*.png(8 张),样式是设计规范的蓝铅笔浅底加蓝铅笔描边。人工看过两张:跨 3 行的句子在句号处结束,同一行后面的下一句没有被带进来。
  • 要改进的两点:
    1. 切句前先识别表格单元格,用 PDF 的表格线,或者参考 Docling 的表格结构,按单元格切;
    2. 识别每页重复出现的页眉页脚带,单独处理,不并入正文句。
  • 自动编号(「60.」「24.」)由 Word 渲染时生成,规范化 PDF 里有、DOCX 文字里没有。出处高亮以 PDF 为准,所以不影响高亮;只是做 DOCX 交叉校验时要容忍这一点。

4. S4 大模型抽取实质性条款

4.1 方法

  • 输入:S3 的句子,带格式标记(<iu> 斜体且下划线、<u> 下划线、<i> 斜体)。每个分块约 6000 字,10 份文件共 150 块。每个分块前附上从全文摘出的「标注规则说明」句。
  • 银标准(实质性条款句):
    • ★ 句:排除说明句和「★号条款」这类引用句;
    • 斜体下划线句:只在文件自己声明这种标注方式时才算(S06、S08、S09),否则它常常只是模板里的填空处。
    • S03 正文的 ★ 句正好 14 条,和文件自述的「★ 项共 14 条」一致。
  • 提示词:spikes/s4-llm/prompts/extract_substantive/,有 v1、v2 两个版本。要求只能引用句子编号,引文必须逐字复制。
  • 三道校验:
    1. 编号必须在本分块里;
    2. 引文必须是原句的子串,并满足最小长度;
    3. 依据校验(v2 新增):模型声称的依据,用确定性信号核对。★ 看原句里有没有 ★,斜体下划线看有没有 <iu>,「写明无效」看原句和相邻句里有没有无效类字样。
    • 任何一道没通过,条目都进待确认区,不丢弃。

4.2 结果(10 份文件全量)

指标 v1(无依据校验) v2 + 依据校验
★ 召回(严格 / 含相邻句) 91.3% / 92.4%(84、85 / 92) 93.5% / 93.5%(86 / 92)
斜体下划线召回(严格 / 含相邻句) 91.3% / 99.8% 91.7% / 99.8%
采纳条目数 1761 651
采纳条目属实(命中银标准,或证据句写明无效) 36.7% 95.2%
进待确认区 66 283
证据两道校验通过率 95.1% 93.6%
输入 / 输出 token 61.1 万 / 11.4 万 63.6 万 / 5.2 万
费用(高峰 / 空闲) 1.93 / 0.96 元 1.49 / 0.74 元
  • 漏掉的 ★ 句(v2):共 6 条,其中 3 条是银标准的噪音:「备注:加★为核心技术参数」「非标★、▲项为一般技术参数」「属于清单中打★品目的」。真正漏掉的 3 条都是表格里的参数行。扣除噪音后召回约 96.6%(86 / 89)。
  • v1 的主要问题是过度抽取:合同履约、保密协议、付款等条款都被当成了「写明无效」。S09 有 208 条被标成「斜体下划线」,实际上全文只有 2 句。说明模型会标错依据,所以必须有第三道依据校验。
  • 证据没通过的原因:引文过短(少于 8 个字)、引文不是原句子串、编号不在本分块。都被校验拦下进了待确认区,没有一条错误出处进入正式清单。
  • 耗时:10 份文件 8 路并发,约 2 分钟;最大的 S06(199 页、28 块)单独串行累计 142 秒,并发后约 20 秒。

4.3 成本(供 D2 定价参考)

价格来源:DeepSeek 官方价目页(https://api-docs.deepseek.com/zh-cn/quick_start/pricing,2026-11-03 查询)。deepseek-flash(DeepSeek-V4.1-Flash-1017)每百万 tokens:输入缓存命中 0.04 元、未命中 2 元、输出 8 元(高峰);空闲时段半价。

口径 每份招标文件
本次实测(只做「实质性条款」一项抽取,v2) 输入约 6.4 万 token、输出约 0.5 万 token;约 0.15 元(高峰)/ 0.07 元(空闲)
推算:完整 M2 解读(资格、废标条款、评分、日期、参数、摘要等 5~6 项抽取) 约 0.6~1.0 元(高峰),推算值,未实测
  • 换成深度思考模式或 deepseek-v4-pro,输入单价是 Flash 的 2.25~4.5 倍,还会多出思考 token。本次没有测,D2 定价前要补测「Pro 或思考模式能把召回提高多少」。
  • 价格表里没有 deepseek-chat 这个名字,但接口仍然接受,实际由 Flash 提供服务。正式接入时应直接用 deepseek-flash,并把接口返回的 model 字段记进调用流水账(架构 §3.6)。

4.4 建议

  1. 标注类条款用规则保召回:★、*、文件声明了的斜体下划线,都能从规范化 PDF 上确定性地识别出来(S2)。所有这类句子都必须被某个条目覆盖;模型没覆盖的,系统自动生成一条「待确认」。模型负责归并、概括和判断依据,召回不再完全依赖模型。按 S2 的结果,这一类的召回可以做到接近 100%。
  2. 「写明无效」类靠模型加依据校验:这一类没有银标准,召回率还没测,需要人工标注 2~3 份文件。可以和样本缺口里的「人工标注 S03 / S06」一起做。
  3. 数量核对:文件写明了实质性条款数量的(S03「共 14 条」),和抽到的数量对比,不一致就提示(FR-M2-05 已有要求)。
  4. 引文最小长度从 8 个字放宽到「不少于 6 个字,或整句」:表格单元格常常很短,「引文过短」是证据被拒的首要原因。

5. 对需求目标值的建议

需求 建议目标值 依据
FR-M2-05 ★ / 标注类实质性条款召回 ≥ 99%(规则兜底 + 模型) S2 格式识别 100%;S4 模型单独约 96.6%
FR-M2-05 「写明无效」类召回 先定 ≥ 90%,人工标注后校准 尚无银标准
FR-M2-05 采纳条目准确率(进正式清单的条目属实) ≥ 95% S4 v2 实测 95.2%
FR-M2-06 出处页码正确率 100% 出处由句子编号决定,页码随编号而来;编号和引文都经过校验
FR-M2-06 原句高亮准确率(抽查 50 条) 正文 ≥ 98%,表格 ≥ 95% 实测正文 97.5%、表格 78%,表格切句改进后再验收
FR-M2-02 解析耗时(300 页) 维持 10 分钟目标 转换 ≤ 2 秒,文字层约 2 秒 / 100 页,单项抽取并发后约 20 秒;完整解读按 5~6 项推算在 2~3 分钟内

6. 未覆盖的事项

  • S1:内嵌 OLE 对象的 docx(本批样本里没有);以仿宋、楷体为主的文件的分页真值。
  • S3:扫描件 OCR 页的坐标(S5 的范围)。
  • S4:深度思考模式和 Pro 模型的对比;「写明无效」类的召回;资格、评分、日期等其他抽取项。
  • S5(OCR)、S6(签章)、S7(任务队列)按 §10 的计划另做。