我为什么要做这个工具

在企业采购流程中,合同初审经常卡在一个具体问题上:合同正文、技术附件和商务条款加起来几十页,法务或业务人员需要先通读,再把付款、交付、验收、违约、解除、保密、知识产权和争议解决等内容整理出来,最后才能形成一份可以继续谈判的风险清单。

这项工作不是每次都需要完整法律意见书,但重复阅读和整理很耗时,风险也可能分散在不同章节甚至附件中。因此我想做一个初筛工具:先把值得复核的地方找出来,再由专业人员确认法律依据和业务处理方式。

但采购合同审查并不只是“把合同丢给模型,问它有什么风险”。真正费时间的往往是后续工作:核对模型说的是哪一条、回到原文确认上下文、区分哪些问题已经复核、整理修改建议,最后再把结果交付给业务部门。

经过一轮开发和三轮迭代,我最终做出了一个采购合同风险审查工作台:它支持 TXT、DOCX、文字版 PDF 和扫描版 PDF,可以从采购方视角生成结构化风险清单;点击风险能跳回合同对应页面,还可以筛选、搜索、记录复核状态和备注,并导出 Markdown、HTML、PDF 三种报告。

先看最终效果

【上传合同 → 自动识别文字版或扫描版 → 生成风险 → 点击风险跳转原文 → 标记复核状态和备注 → 导出报告】

采购合同风险审查工具

进入工具后,上传区会明确显示支持的文件格式、大小限制和处理边界。
最终效果1.png

合同上传后,程序会判断它走文字解析还是扫描件多模态识别,并显示当前处理状态。

最终效果2.png

分析完成后,左侧是风险清单,右侧是合同原文。每条风险包含等级、类别、页码、章节、原文摘录、风险分析和修改建议。
最终效果3.png

点击左侧风险,右侧会跳到对应页面并突出当前页。相比单独输出一篇报告,这一步让使用者可以马上核对模型结论,而不是盲目接受答案。
最终效果6.png

页面还支持按风险等级、类别和关键词筛选,并可把风险标记为“待复核”或“已复核”,补充人工备注。
最终效果4.png

复核完成后,可以导出 Markdown、独立 HTML 或 PDF 报告。复核状态和备注也会一并进入报告。
最终效果5.png

这里需要先说明边界:它是一套合同风险初筛工具,不是自动出具法律意见的系统。模型结论、风险等级、页码和原文引用都需要专业人员复核。

第一轮:先把完整闭环跑起来

我给 Qoder 的第一轮需求很短,核心是先做出能运行的 MVP:

搭建一个本地工具——合同风险审查工具:上传采购合同后,使用 Qwen3.8-Max API 自动生成采购方视角的风险初筛报告。请采用最简单、最容易本地复现的技术方案,优先完成“上传合同 → 调用 Qwen API → 展示结构化风险 → 导出报告”的完整闭环,不要加入无关功能。模型运行时也必须显式配置为 qwen3.8-max,API Key 和接口地址放入环境变量或 .env.example,禁止写死密钥。

需求.png

Qoder 选择了一个很轻量的方案:后端使用 Flask,前端使用原生 HTML、CSS 和 JavaScript,模型通过 OpenAI 兼容接口调用。第一轮完成了文件上传、合同解析、风险展示和 Markdown 导出,没有数据库,也没有引入复杂框架。
完成.png

第一版页面和主流程能跑通。
效果3.png

但真实测试很快暴露了三个关键问题:

  • 模型有时会返回带 Markdown 围栏或说明文字的 JSON,前端无法解析,只能显示原始内容;
  • 扫描 PDF 没有文字层,本地提取结果为空;
  • 首轮一次分析耗时约 143 秒,等待时间过长。

这些问题说明,“模型能回答”与“产品能稳定使用”之间还有一段距离。

第二轮:修复结构化输出、扫描件和响应时间

第二轮我没有扩大需求,而是直接针对第一次实测失败点迭代:

请基于当前合同风险审查工具进行第二轮迭代并实际运行验证:修复模型返回内容无法解析为结构化 JSON 的问题,确保结果可以正常展示和导出;确认当前 QWEN_BASE_URL 是否支持 qwen3.8-max 的多模态输入,如支持则增加 PDF 图片或扫描件解析,如不支持则明确提示限制,不要伪造结果;同时记录文件解析和模型调用耗时,找出响应较慢的原因并做必要优化。请保持 API Key、接口地址和模型名称使用环境变量,完成后说明修改文件、运行结果和当前限制。

开始优化1.png

针对 JSON 不稳定,程序增加了多层容错:先提取 JSON 主体,清理 Markdown 代码围栏和尾逗号;仍然失败时,再调用一次模型进行 JSON 修复;最后还会统一字段名称和缺省值。这样模型的小幅格式波动不会直接让整个页面失效。

针对扫描 PDF,程序使用 PyMuPDF 将页面逐页渲染为图片,再通过 Qwen3.8-Max 的多模态输入进行分析。这里并不是把扫描件假装成文字 PDF,而是明确走另一条处理路径。

解决的三个问题.png

耗时问题也在这一轮找到原因:模型的思考 Token 挤占了输出预算,第三方兼容接口的流式连接又不够稳定。关闭思考模式、限制输出长度并改用非流式调用后,同一份示例合同曾从约 143 秒缩短到约 16 秒。这个数字只是同一测试样例在当时环境中的结果,不代表所有合同都能达到相同速度。

最终,扫描版 PDF 也能正常生成风险清单。

f16122dd2b9f606240bb79ff81b93d41.png

第三轮:让每个风险都能回到原文

第二轮解决了“能不能读”和“能不能正常展示”,第三轮继续解决“结果能不能核验”。

请基于当前合同风险审查工具完成第三轮迭代:为每条风险增加页码、章节号和合同原文摘录;将页面改为左侧风险清单、右侧合同文档预览,点击风险后跳转到对应页码,文字 PDF 和扫描 PDF 都要支持,无法可靠定位时显示“页码待核验”;增加风险等级筛选并实际运行验证。不要加入法律条文引用,保持当前模型和 API 配置不变。

开发.png

这轮把原来的自由文本字段拆成了 pagesectionquote、风险分析和修改建议。文字 PDF 在提取时插入明确的页码标记,扫描 PDF 则按图片顺序保留页码。运行时 Prompt 约束模型只能依据页码标记或图片顺序判断,无法确定就返回空值。

页面也改为左右分栏:左边看风险,右边看合同。对于 TXT 和 DOCX,因为不同软件的排版会改变页码,工具不会伪造定位,而是明确显示“页码待核验”。
完成及效果.png

这一轮的三组实测结果如下:

输入 风险数量 模型耗时 定位表现
3页文字版 PDF 11条 34.6秒 风险均定位到第1至3页
扫描版 PDF 12条 56.1秒 页码定位成功,可点击跳转
TXT 10条 26.5秒 无稳定页码,显示“页码待核验”

这些数据验证的是几个测试样例中的运行和定位表现,不等于通用审查准确率。

最终一轮:解决长合同截断,并补齐复核与交付

工具能定位后,我又检查了更长的合同,发现代码对文字合同和扫描件分别设置了 25000 字符、20 页的处理上限。如果直接截断,后半部分合同可能完全没有经过模型,却仍然显示为“分析完成”。这是必须解决的问题。

最终一轮的 Prompt 是:

请基于当前工具完成第四轮迭代:解决文字合同超过 25000 字符、扫描 PDF 超过 20 页时可能漏检的问题,按页或章节分段分析并合并结果,同时显示实际处理的页数或章节;保留多模态扫描件能力、JSON 校验、失败重试和耗时记录,禁止静默截断内容;请使用示例合同连续运行两次并报告修改文件、耗时和结果差异。

在保留 Markdown 导出的基础上,增加样式清晰的 HTML 和 PDF 报告,报告包含风险摘要、风险等级、合同原文、页码、分析、修改建议、处理耗时和免责声明;增加风险搜索、类别筛选以及“待复核/ 已复核”状态和备注功能;请选择最简单、最容易本地复现的实现方式,完成后实际导出并验证文件可打开。

开发1.png

文字合同现在会优先按页和段落边界分段,扫描件则每批最多处理20页。各段分别调用模型,再统一合并、去重;同一风险出现不同等级时保留较高等级。某一段失败时,页面会明确告诉用户哪一段失败,不再静默丢弃内容。

在交互上,这一轮增加了关键词搜索、类别筛选、复核状态和备注;在交付上,增加了独立 HTML 和 PDF 报告。
完成2.png

我做了哪些实测

为了避免只看“代码已经修改”的说明,我用重复运行、强制分段和多页扫描件做了实际验证。

测试项目 实测结果
同一合同连续运行两次 两次均成功,耗时54.2秒和35.3秒,均输出12项风险
两次结果一致性 11项共同风险,2项风险等级发生变化
强制文字分段 将上限临时调为600字符,分成2段,耗时20.8秒和14.6秒,合并后13项风险
24页扫描 PDF 自动分为20页和4页两批,总耗时105.7秒,识别15项风险,定位覆盖到第24页
报告导出 Markdown、HTML、PDF均可打开,测试导出的PDF报告共3页

重复运行也提醒我,大模型输出并不是确定性规则引擎。同一合同的主要风险较为一致,但风险等级仍会波动。因此页面中的复核功能不是装饰,而是这个工具能够进入实际流程的必要环节。

工具内部如何约束模型

Demo 运行时始终固定采购方立场,并要求模型只输出 JSON。核心 Prompt 如下:

你是一名资深企业法务顾问,精通采购合同审查。请站在采购方(买方)立场,对收到的合同全文或当前分段进行风险初筛。

审查主体与资质、标的与质量、价格与付款、交付与验收、违约责任、知识产权与保密、解除与终止、争议解决及其他明显不利条款。

只输出合法 JSON。每条风险包含 level、category、page、section、quote、risk_point、analysis 和 suggestion。page 只能依据【第N页】标记或图片顺序判断,不确定时填 null;quote 必须逐字引用真实原文,不得改写或编造。所有分析只基于合同文本,不引用法律法规,不构成最终法律意见。

完整 Prompt 还包含字段定义、风险等级规则、缺失条款判断和分段信息。这里最重要的约束有三点:

  • 页码只能来自明确页标记或扫描图片顺序;
  • 原文摘录必须逐字引用,不能由模型改写;
  • 只根据合同文本分析,不引用未经核验的法律条文。

这次开发中最有价值的几个认识

第一,结构化输出不能只靠一句“请返回 JSON”。真实运行中,模型可能带上代码围栏、解释文字、尾逗号,甚至字段名称也会有变化。产品层必须同时做 Prompt 约束、解析容错、字段校验和失败提示。

第二,多模态能力解决了“扫描件能不能读”,但页数增加后,图片转换、上传和模型推理都会增加耗时。24页扫描合同耗时105.7秒,明显高于短文本合同。这是输入规模和多次调用共同造成的,不是前端卡死。

第三,长合同处理的关键不是简单提高字符上限,而是让分段行为可见、可验证。只要存在静默截断,报告看起来再完整也不可信。

第四,法律类工具不能停在模型结论。原文定位、逐字摘录、人工复核和备注,才是把一次模型回答变成工作流的关键。

第五,开发阶段的模型能力和 Demo 运行阶段的模型能力是两件事。在这个项目里,Qwen3.8-Max 一方面通过 Qoder 持续理解现有代码并完成多轮修改;另一方面又作为运行时模型读取合同、识别扫描页并生成结构化风险。前者体现 Agent 编程能力,后者体现长文档、多模态和结构化理解能力。

总结

这次实践让我看到,Qwen3.8-Max 的价值不只是“读完合同并列出风险”。它既能在 Qoder 中参与完整工具的搭建和多轮迭代,也能在运行时处理文字、表格和扫描页面,并输出可以进入前端工作流的结构化结果。

从第一版能上传,到第二轮修复 JSON、多模态和耗时,再到第三轮原文定位,最后补上长合同分段、人工复核和三格式导出,真正提升完成度的是一次次真实运行后暴露的问题。

最终的工具没有试图替代法务判断,而是把合同初筛中最重复的部分组织成一条可核验、可复核、可交付的流程。对我来说,这比让模型一次性给出一篇看似完整的审查意见,更接近大模型在专业场景中的实际价值。

使用边界:本工具仅用于合同文本辅助整理和风险初筛,不构成律师意见或最终法律意见。正式签署前,必须结合交易背景、有效规则和具体证据进行人工复核。

技术方案与复现方式

为了让项目容易在本地复现,我保留了简单的技术栈:

  • 后端:Python + Flask;
  • 前端:原生 HTML、CSS、JavaScript;
  • 文档解析:python-docx、PyMuPDF;
  • 模型调用:OpenAI Python SDK,接口调用 qwen3.8-max
  • 数据存储:不使用数据库,复核状态和备注保存在浏览器 localStorage。

本地启动命令:

cd D:\code\test9
pip install -r requirements.txt
python app.py

浏览器访问 http://127.0.0.1:5000。API Key、接口地址和模型名称均放在项目 .env 中,没有写死在代码里。测试时使用的API是同样是qwen3.8-max

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐