招采智检技术实践:让每一处疑点都有原文依据
从招标文件中分散、重复的核对需求出发,介绍招采智检如何结合文件解析、本机 OCR、AI 交叉核对和原文证据定位,将发现疑点、人工复核与报告留档连接起来,并展示实际工作台界面。

同一个项目编号,在采购公告和投标人须知里只差一个数字;同一个投标截止时间,在两个章节里相差半小时;评分表标注总分 100 分,分项相加却只有 95 分。
这些差异单独看都不复杂,但当它们散落在一份长文档里,核对就需要不断翻页、搜索、记录和回看。
围绕这个问题,我做了一个小工具——招采智检。它是一个面向招投标工作人员的文件一致性核对助手:导入一份招标文件,整理前后可能不一致的内容,展示对应的原文依据,再由使用者确认是否需要修改。
在介绍技术之前,先把做这个项目的原因和它能完成的工作讲清楚。
为什么要做招采智检
做这个项目的出发点,是把招标文件中重复、分散、需要反复对照的核对工作,整理成一套可以实际操作的流程。
首先,要核对的信息经常分布在不同位置。项目名称和编号可能同时出现在公告、须知与合同摘要中,时间、金额和评分又分别出现在其他章节。人工阅读时,需要在多个位置之间来回切换,才能判断前后是否一致。我希望工具先把值得关注的内容找出来,方便使用者集中复核。
其次,同一件事可能有不同写法。“120 万元”和“1,200,000 元”可以表达相同金额,“投标截止时间”和“提交投标文件截止时间”也可能指向同一事项。比较之前需要理解它们的含义和口径,因此,我在本地规则之外保留了接入 AI 辅助核对的能力。
还有一个很实际的问题:发现疑点以后,仍然需要找到原文、判断原因、记录处理意见。如果结果只停留在一句“这里可能有问题”,后面的查找和记录工作就没有解决。所以,我把原文定位、人工确认、复核备注和报告导出一起纳入了项目。
这个项目想探索的,是如何让 AI 和规则参与文件核对的具体环节,让使用者能够查看依据、作出判断,并把判断过程留下来。
这个项目具体是干什么的
招采智检主要面向需要整理、校对招标文件的工作人员。它围绕一份文件中的项目信息、关键时间、金额和评分分值,辅助检查前后表述是否一致。
一次使用大致分为四步:
- 导入文件。 支持 PDF、DOCX 和 TXT;PDF 中的扫描页可以通过本机 OCR 提取文字。
- 整理疑点。 选择本地规则检查,或在配置模型后使用 AI 增强核对,查看四类关键要素中的候选问题。
- 对照原文复核。 点击疑点里的引用回看对应内容,结合上下文选择“确认问题”或“无需修改”,并填写备注。
- 保存和导出。 完整核对结果自动保存到本机,之后的复核意见也会持续保存;需要留档时,可以导出带依据和人工状态的报告。
例如,虚构演示文件在两个章节里分别写了 DEMO-FW-2026-018 和 DEMO-FW-2026-081。工具会将这两处内容作为项目编号疑似不一致的依据放在一起,使用者再判断它们是否属于同一项目、是否存在更正说明,以及是否需要修改。
从使用者的角度看,它把“发现疑点、查找原文、人工复核、保留记录”连接在同一个工作台里。它提供的是辅助核对能力,最终判断仍由使用者完成。
下面结合实际界面,介绍这套流程用到了哪些技术,以及各项技术怎样服务于核对操作。
文中界面均为项目实际页面截图。工作台与历史记录使用虚构演示文件或合成测试文件;模型设置截图中的配置与连接状态来自隔离模拟服务。它们用于展示功能,不代表真实项目核对成效。
先看效果:把原文、疑点和复核放在同一个工作台

图 1:虚构 PDF 演示文件的基础检查结果。点击右侧原文依据,可定位并高亮中间的对应内容。
桌面工作台主要由三部分组成:左侧显示核对范围,中间阅读文件原文,右侧查看疑点和证据。每条疑点可以填写复核备注,再选择“确认问题”或“无需修改”。
目前,工具聚焦四类核对:
| 核对类别 | 关注的问题 | 比较时需要保留的口径 |
|---|---|---|
| 项目信息 | 项目名称、项目编号是否前后一致 | 是否属于同一项目或标包 |
| 关键时间 | 同一事项的时间是否出现不同表述 | 截止时间、开标时间等事项要区分 |
| 金额核对 | 同类金额、大小写金额是否一致 | 预算、最高限价、保证金分别比较 |
| 评分分值 | 分项与总分是否存在疑似差异 | 区分父子项,避免重复加总 |
这个范围决定了后面的技术设计:既要提取文件内容,也要保留事项、位置和上下文,才能让人核实比较是否成立。
技术结构:浏览器工作台、本机服务和可配置模型
项目采用 React、TypeScript 和 Vite 构建前端,使用 Node.js 24 与 Express 提供本机服务。文件提取、原版阅读、OCR、模型调用和记录保存分别承担不同任务。
| 层次 | 使用的技术 | 在项目中的职责 |
|---|---|---|
| 前端工作台 | React 19、TypeScript、Vite 7 | 文件导入、原文阅读、疑点列表、人工复核 |
| 文件提取 | PDF.js、Mammoth | 提取 PDF 文字,将 DOCX 内容转换后整理为文本块 |
| PDF 阅读 | EmbedPDF 2.15.1、PDFium WebAssembly | 显示原始版式,支持搜索、缩放与证据高亮 |
| 扫描识别 | Python、PaddlePaddle、PaddleOCR | 在本机识别扫描页文字,并按需解析表格结构 |
| 本机服务 | Node.js 24、Express 5 | 模型配置、AI 请求、任务事件与文件存储接口 |
| 核对进度 | Server-Sent Events,简称 SSE | 传递处理阶段、初步疑点和最终结果 |
| 历史记录 | SQLite、本机文件系统 | 保存核对结果、复核意见及原始 PDF 副本 |
| 验证工具 | Node.js 测试运行器、Playwright | 检查数据处理与实际浏览器操作流程 |
从使用流程看,数据经过下面这条路径:
text
导入 PDF / DOCX / TXT
↓
提取文字;需要时调用本机 OCR
↓
形成带编号、位置和来源的文本块
↓
选择本地规则核对,或通过本机服务调用已配置的模型
↓
校验原文引用,展示疑点与证据
↓
保存完整核对 → 人工确认与备注持续保存 → 导出报告
基础检查使用本地规则,不调用外部模型。选择 AI 增强核对并开始任务后,提取出的文字会通过本机服务发送到使用者配置的模型服务。
文件解析:把不同格式整理成可定位的文本块
核对之前,首先要解决“程序读到了什么”的问题。
项目使用 PDF.js 提取 PDF 中可识别的文字,同时记录来源页码和文字位置。DOCX 则通过 Mammoth 转换内容后整理段落和表格;TXT 按文本内容处理。
不同格式最终归入统一的数据结构。下面是保留关键字段后的简化示意:
ts
interface DocBlock {
id: string;
text: string;
page: number | null;
section: string;
source?: 'native' | 'ocr';
confidence?: number;
}
interface Evidence {
blockId: string;
quote: string;
label?: string;
}
id 把一段文字与后续引用连接起来,page 和 section 帮助定位,source 则区分原生文字与 OCR 识别内容。实际实现还保存 PDF 文字坐标、字符范围及部分表格信息,供页面高亮使用。
这种组织方式让“发现疑点”和“查找依据”使用同一份来源数据。核对结果中的引用能够指回具体文本块,而不是留下一段需要使用者重新搜索的描述。
不同文件的定位能力也需要如实呈现:PDF 可以使用来源页码;DOCX 缺少可靠的原始分页信息,因此按段落或表格位置定位。
PDF 原版阅读:让疑点直接回到文件页面
提取文字适合分析,但人工复核经常需要看原始表格、段落位置和页面上下文。因此,项目使用 EmbedPDF 与其 PDFium 引擎 展示原 PDF,并保留文本视图供切换。

图 2:专注阅读模式。演示文件中的项目编号已高亮,使用者可以结合完整页面判断疑点。
阅读器支持全文搜索、翻页、页码输入、缩放和适应宽度。“专注阅读”会扩大原文区域,适合查看较长条款或表格。
证据定位则把两条路径接在一起:先根据引用找到文本块,再结合文字的字符范围和页面坐标,在原 PDF 中标出对应位置。使用者点击右侧证据后,可以直接回看左侧页面。
为照顾大文件,超过 20 MiB 的 PDF 默认先打开文本视图,原版阅读器由使用者按需加载。阅读引擎、WebAssembly 和回退字体随应用构建,减少运行时对外部 CDN 的依赖。
本机 OCR:让扫描页也能进入核对流程
有些 PDF 的正文是一张张图片。还有一些混合页面,虽然能复制页码或水印,正文仍然无法直接提取。仅凭“页面存在文字”就跳过 OCR,会漏掉这些内容。
项目接入了本机 PaddleOCR,当前依赖锁定为 PaddleOCR 3.4.0 和 PaddlePaddle 3.3.1,使用 PP-OCRv5 中英文识别模型,并按需调用表格结构识别能力。
扫描页会先渲染为图片,再提交到本机识别服务。返回内容除文字外,还包括置信度、坐标,以及可获得的表格行列和合并跨度信息。混合页会合并原生文字与识别结果,并处理重复内容。
这些信息会继续用于证据定位:OCR 产生的疑点也能回到扫描原图,而不是停留在一份脱离页面的识别文本里。
首次安装需要联网下载依赖和模型;识别工作进程在本机运行并禁止网络连接。当前 CPU 推理并发为 1,模型保持常驻复用,逐页处理后释放页面图片。
低置信度内容会被标记。这里仍有一个需要人工判断的环节:引用能匹配识别文字,并不能证明图片中的金额、日期和分值被正确识别,因此复核时仍要查看原图。
AI 核对:先组织比较,再校验原文依据
项目保留了两种核对方式。基础检查用本地规则整理候选问题;AI 增强核对通过服务端调用兼容 OpenAI Chat Completions 的接口,要求模型返回结构化 JSON。
长文件分批读取,再做跨章节比较
24,000 字符以内的文件保留单次全文调用。更长的文件按约 12,000 字符分批抽取关键事实,最多同时处理 2 批,再按同一事项、同类字段汇集相关原文,进行跨章节比较,并复核初步疑点。
例如,项目编号可能出现在采购公告、投标人须知和合同摘要里。把这些位置重新放在一起比较,才能检查它们之间的关系。评分表也需要保留相关原文,避免按页拆开后遗漏总分,或把父项和子项重复相加。
分批处理让任务可以按阶段推进,也方便组织长文上下文;实际耗时和费用仍取决于文件内容、调用次数及模型服务,不能仅凭分批就推断会更快或更便宜。
每条疑点都要通过引用校验
模型返回结果后,程序会检查引用的文本块是否存在、引文是否逐字匹配,以及证据是否完整。AI 疑点和已核对比较需要至少两条不同的有效原文引用;不存在的段落、被改写的引文和证据不足的结果会被过滤,并产生提示。
这项校验能够约束引用来源。至于两段文字是否确实属于同一事项、金额是否采用同一口径、是否存在更正说明,仍由使用者结合上下文确认。
用 SSE 展示任务阶段和初步结果
长文核对可能需要等待,因此前端会显示正在提取事实、正在比较全文等真实阶段,以及已处理批次、已用时间和初步疑点。
这里的 SSE 承载的是本机服务的任务事件。页面逐步收到的是处理阶段和分批结果,每次模型调用本身仍使用非流式响应。
初步疑点在全文比较后可能被调整或撤回,最终结果会统一替换它们。核对完成前不能确认问题或导出最终报告;取消、超时或连接中断会显示“本轮未完成”,保留已返回内容供查看。
模型设置:把配置、测试和选择放进界面

图 3:模型设置界面。截图中的模型名称、示例地址及测试成功或失败状态均来自隔离模拟服务,不代表实际服务连接。
模型设置入口放在左下角,支持添加多个模型、编辑连接、测试连接、设为默认和选择下一次核对使用的模型。已有结果会保留当次使用的配置名称与模型标识,便于回看结果来源。
“测试连接”发送固定短文本,用于检查鉴权、模型响应和 JSON 输出,不发送当前文件。短文本测试成功后,长文是否能够顺利处理,还取决于模型与网关的实际限制。
在 Windows 上,通过页面保存的 API Key 使用当前用户的 DPAPI 加密后落盘。发起 AI 核对时,浏览器提交文档文字和配置 ID,由本机服务读取密钥并请求模型。密钥不会随模型列表、报告或历史记录返回。
这项加密针对密钥。核对记录、识别文字和 PDF 副本仍按普通本机文件保存,当前实现也没有多人账号权限体系。
历史记录与报告:让复核意见可以继续使用

图 4:使用合成 PDF 生成的核对记录,展示文件、核对方式、疑点数量及再次打开的入口。
项目使用 Node.js 的 node:sqlite 接口保存完整核对结果和人工复核意见,原始 PDF 副本则单独存放在文件系统中。完整核对结束后自动保存,之后的复核状态与备注变更也会继续保存。刷新页面或重启本机服务后,可以从历史记录重新打开原文与结果。
列表先读取摘要,打开某条记录时再加载详情和 PDF,避免进入历史页面时就把所有文件读入浏览器。
保存过程也会显示状态:正在保存、已保存到本机,或具体错误。保存失败可以直接重试,无需再次调用模型。
导出报告采用 HTML 格式,保留疑点、原文证据、人工状态、备注、处理耗时和已获得的模型用量信息。用浏览器打开后,可以打印或另存为 PDF,便于留档。
移动端效果:把双栏复核改为顺序阅读

图 5:375px 宽度下的演示界面,原文和核对结果改为上下排列。
桌面端可以同时看原文和结果,手机端则需要顺序完成这个过程。因此,窄屏会收起侧边导航,将阅读区和结果区上下排列,并提供返回核对结果的入口。
页面布局可以适配手机,但复杂 PDF 表格仍然需要缩放或进入专注阅读模式。移动端的目标是保留查看、定位和复核操作,让使用者能够继续处理当前疑点。
当前能够验证的效果,以及下一步要测量什么
目前,项目已经实现文件导入、两种核对方式、证据定位、人工复核、本机保存和报告导出的完整流程。项目中的自动化验证围绕几类问题展开:
- 文件是否完整提取,解析取消或 OCR 失败时是否明确停止。
- 无效引用是否被过滤,模型异常是否如实显示。
- PDF 搜索、缩放、证据定位和手机布局是否可操作。
- 刷新或重启后能否恢复记录,保存失败能否单独重试。
- 核对取消、超时或断流后,是否保持未完成状态。
这些功能带来的直接变化,是使用者可以在同一个界面找到候选疑点、查看对应原文、记录判断,并在之后继续打开这次核对。
至于准确率、误报率、实际节省多少时间,还需要用允许处理的真实文件测量:记录原有人工核对用时、工具处理用时、逐项复核用时,以及最终确认和排除的疑点数量。演示截图中的处理时间和预设差异不能代替这些数据。
这次实现中,我投入较多精力的部分,是把文件读取、模型结果和人的判断连接起来。一个疑点有了可点击的原文、可补充的备注和可恢复的记录,使用者才有条件核实它,并把判断依据留下来。