最初的想法很直接:做一个不联网的本地脱敏软件,把 Word、PDF、扫描图片里的姓名、单位、地址、电话、金额等信息批量替换掉,尽量保持原版式,再把脱敏后的文件交给人工审核。
真正开始做以后,我很快发现,“识别几个号码并替换成星号”只占问题的一小部分。对法律文件而言,更难的是误判、遗漏、文档结构、人工确认、输出残留、界面反馈、安装分发,以及如何确保真实敏感信息永远不会为了改进规则而进入源码和测试。
这次 Vibe Coding 最重要的变化,不是代码越写越多,而是把一个“大而全的想法”反复收缩成一个边界清楚、可以逐项复核的 Word 工具。
01 先把产品问题问清楚,再开始写代码
需求澄清时,我没有从模型或框架开始,而是先回答五个问题:谁来用、处理什么文件、准确率和速度谁优先、是否允许联网、最终需要什么输出。
| 最初选择 | 后来形成的产品约束 |
|---|---|
| 律师和企业法务使用 | 默认面对高敏感、长篇、版式复杂的合同文件 |
| 准确率优先 | 中低置信候选不能静默处理,必须进入人工复核 |
| 版式尽量不变 | 不能抽取纯文本后重建文档,要在原有 Word 结构中替换 |
| 完全本地 | 文件、命中值、人工规则和诊断日志都要服从离线边界 |
| 只需要脱敏副本 | 原件只读、禁止覆盖,输出后还要重新扫描实际副本 |
这一步看似没有写代码,却决定了后面的全部结构。尤其是“人工复核优先”,意味着软件不能只给出一个结果文件,还必须解释为什么命中、允许用户纠错,并明确显示哪些内容仍未确认。
02 第一版先跑通闭环,而不是先追求“智能”
第一阶段只做最小闭环:读取 Word、识别候选、按置信度分级、让用户选择、导出新副本、拒绝覆盖原件。手机号、邮箱、证件号码等格式稳定的内容可以先用规则和校验完成;主体、姓名、地址、金额等依赖上下文的内容则保守处理。
01离线读取文件内容不上传,不依赖远程模型完成基础流程
02候选识别检测器只负责发现可能的敏感信息,不直接宣布最终结论
03置信分级高置信默认选中,中低置信留给人工判断
04人工选择允许取消、编辑、强制加入或明确保留
05新建副本永不覆盖原件,输出名称自动避让已有文件
06残留复检针对真正写出的副本再次检测,而不是只相信导出前状态
这条链路让我第一次意识到:准确率不是识别模型上的一个百分比,而是“检测—解释—确认—导出—复检”共同形成的产品能力。
03 最早的弯路:不断增加规则,并不会自动变准
原型阶段很容易陷入“发现一个漏项,就再加一条正则”的循环。某类机构名称漏掉了,就增加机构后缀;短地名漏掉了,就扩大地名表;数字金额漏掉了,就放宽数字模式。召回率短期上升,误判也随之增加。
典型冲突包括:一个普通词因为碰巧带有机构后缀而被当成主体;组织全称里的短地名抢走了完整主体;七八位数字既可能是金额,也可能是固定电话;日期、文号和数字片段互相重叠。规则数量越多,冲突不再是例外,而会成为系统常态。
从“高置信字符串清单”转向“有证据的候选系统”
后来的判断标准不再是“像不像敏感信息”,而是:它由哪一个识别器发现、满足了什么校验、附近有什么上下文、为什么得到这个分数、与其他候选冲突时谁优先,以及用户能否推翻机器判断。
04 借鉴成熟架构,但没有照搬一个大框架
为解决规则膨胀,我研究过 Presidio、scrubadub 和规则匹配工具的设计,真正吸收的是分层思路:识别器、分析与冲突处理、替换操作、允许清单、用户自定义项和解释信息彼此分开。
项目随后把识别规则迁入 YAML 策略,建立识别器注册表,为每个候选保存类型、范围、分数、来源和理由,并把“识别什么”与“怎样替换”拆开。这样增加一种实体或修改阈值时,不必同时改动界面、导出和全部调用方。
| 层次 | 负责的问题 | 不应该负责的事 |
|---|---|---|
| 识别器 | 发现候选并提供证据 | 擅自决定是否导出 |
| 策略层 | 阈值、上下文、优先级和冲突规则 | 保存真实合同中的私有字符串 |
| 复核层 | 呈现依据并接受人工决定 | 把扫描完成当作复核完成 |
| 操作器 | 替换、掩码等输出方式 | 重新猜测实体类型 |
| 残留扫描 | 检查实际输出并报告风险 | 绕过用户决定再次偷偷修改 |
没有直接引入大型依赖,是因为“成熟”不等于“适合”。离线体积、中文法律文本准确率、Python 版本和打包复杂度都需要单独验证。先借设计,再决定是否引依赖,往往比直接安装更稳妥。
05 Word 不是一串文字,而是一组文档部件
做到版式保留以后,第二个大坑出现了:Word 里的可见文字并不都在普通段落中。同一句话可能被拆成多个 run;页眉、页脚、表格、文本框、批注、脚注和修订记录分别存放;删除过的文字甚至仍可能保留在修订历史中。
| 文档位置 | 为什么容易漏 | 处理思路 |
|---|---|---|
| 跨 run 正文 | 一个号码或名称被样式切成多段 | 先建立连续文本映射,再回写原节点 |
| 页眉、页脚、表格 | 不属于普通正文遍历 | 逐类枚举并保留位置来源 |
| 文本框与修订 | 需要低层 OOXML 才能看到 | 直接处理对应 XML 文本节点 |
| 批注与脚注 | 位于独立文档部件 | 扫描、替换和复检使用同一位置模型 |
| 旧版 .doc | 不是现代 WordprocessingML | 在临时目录本地转换,完成后清理 |
为了尽量保留格式,程序不重新生成整份文档,而是在现有节点中完成替换。这个选择更麻烦,却符合合同审阅的实际需要:字体、表格、分页和页眉页脚往往也是文件可信度的一部分。
06 人工复核不是补丁,而是产品中心
规则完善后,重心从“再多识别一种实体”转向“让人更快、更有把握地完成判断”。界面最终形成三栏工作台:左侧是多合同队列,中间是文档预览,右侧是命中项、筛选和证据说明。
高置信和待确认使用不同底色;用户可以搜索上下文、组合筛选、编辑实体类型、设置“本文强制”、设置“永不脱敏”或把同值命中同步到全文。局部确认后,预览里的内容改为红色删除线;整份确认则单独记录最近确认时间。任何勾选变化都会让文档重新进入待确认状态。
这种设计背后有一个很朴素的原则:扫描只能证明机器看过,不能证明人已经复核。导出按钮是否可用,必须依据显式的人工确认状态,而不是依据文件是否已经打开或候选是否已经生成。
07 “已经导出”仍然不等于“已经安全”
很多脱敏工具在保存文件后就结束任务,但导出过程本身也可能出错:跨 run 替换失败、某个隐藏部件没有写入、用户主动保留了一项高风险内容,或者输出名称指向了错误副本。
因此,程序对实际写出的文件重新抽取并检测,把结果分为“高风险残留”和“待确认残留”。复检只报告,不删除输出,也不会绕过用户的人工选择再次自动替换。这样既能发现漏项,也不会把机器的第二次判断凌驾于人的明确决定之上。
原件:始终只读,不允许原路径覆盖。
副本:自动避让同名文件,批量任务逐份保存状态。
复检:扫描实际副本,而不是复用导出前的内存结果。
警告:区分高风险和待确认,保留文件并等待人工处理。
08 最有价值的经验,来自几次“看似不是算法”的失败
开发中最棘手的问题,有不少并非识别精度,而是运行环境、界面状态和打包细节。它们共同说明:桌面工具的完成标准不能停留在“测试通过”或“窗口能打开”。
| 失败现象 | 真正原因 | 后来形成的规则 |
|---|---|---|
| 新电脑启动时找不到 Cocoa 插件 | 虚拟环境和 Qt 运行资源没有正确恢复 | 迁移不复制虚拟环境,使用受支持的 Python 重新构建 |
| 可编辑安装后命令行导入失败 | 特定 Python 版本跳过隐藏的路径文件 | 构建前先安装普通 wheel,再从项目外验证导入 |
| 应用能启动,打开文档却报资源缺失 | 打包时遗漏模板或包内数据目录 | 验收必须打开合成文档,完成扫描、复核、导出和复检 |
| 清空筛选时应用原生退出 | 多个控件信号触发重复刷新,并撞上辅助功能桥接问题 | 阻断批量信号、只刷新一次,并提供稳定的辅助功能摘要 |
| 规则生效但预览没有删除线 | 焦点和系统文字选区覆盖了最终标记 | 操作后清除选区,并把确认标记放到绘制队列最后 |
最后一类问题尤其值得重视:导出的文件是对的,但界面告诉用户的状态是错的,在法律工具里同样属于严重缺陷。用户需要依据屏幕决定是否继续操作,视觉反馈本身就是结果的一部分。
09 做过 PDF/OCR 版本,又主动把它删除了
项目中途曾沿用 Word 版流程开发 PDF 与扫描件处理:尝试离线 OCR、文字坐标映射、黑框像素清除和残留复检。技术闭环可以运行,但样本测试很快暴露出中文 OCR 错字、金额遗漏、混合页面判断和误报控制等问题。
如果继续保留 PDF,就要同时承担 OCR 引擎、语言数据、电子 PDF、扫描 PDF、混合页面、图片像素清除和不同 macOS 环境的兼容矩阵。这会把有限精力从最常用、最可控的 Word 场景中抽走。
删除功能,也可以是一次产品进步
最终版本主动移除了 PDF/OCR 链路,收缩为 .doc 和 .docx。Vibe Coding 很容易让功能快速膨胀,但真正的产品判断,往往是知道哪些能力暂时不应该存在。
10 迁移和打包,是对“可复现性”的两次考试
项目迁移到新电脑时,没有把旧虚拟环境整包复制过去,而是迁移源码、测试、依赖清单、项目状态、风险记录和接力说明,再用受支持的 Python 重建环境。真实样本和原始开发对话则与普通源码包分开,继续留在本地私有范围。
这次迁移暴露出一个经常被忽略的事实:如果只有原作者的电脑能运行,项目还不是一个可继续开发的产品。接力文档、环境检查、依赖锁定和一条可验证的恢复路径,都是代码的一部分。
随后又把程序打成可双击的 macOS 应用和内部测试 DMG。当前内部包限定 Apple Silicon、macOS 13 及以上,并使用临时签名;真正面向普通用户分发,还需要 Developer ID、Hardened Runtime、公证、票据附加和另一台 Mac 的首次安装验证。把“内部可用”与“正式可分发”写清楚,比绕过系统提示更负责任。
11 本地离线,不只是“代码里没有网络请求”
隐私边界最终覆盖了整个生命周期,而不只是文档处理阶段。
01处理边界识别、复核和导出均在本机完成
02原件边界原文件只读,只生成新的 Word 副本
03规则边界用户针对当前合同设置的真实字符串只存在于会话内存
04测试边界规则回归使用虚构短句和合成文档,不把真实值写进测试
05日志边界只记录版本、阶段和异常位置,不记录正文、命中值或异常原文
06发布边界真实样本、输出、临时文件和私有开发记录均排除在源码与普通分发包之外
“完全本地”如果只覆盖推理,却把真实字符串写进 YAML、日志、截图或版本库,仍然不是真正的本地隐私工具。安全要求必须进入架构、测试、调试和交付,而不是上线前再做一次清理。
12 当前做到哪里:一个可验证的内部 Beta
截至本文发布前,LocalRedact 的内部版本为 0.4.0-beta.9,产品范围已经稳定为本地 Word 合同脱敏。当前代码重新执行完整回归,128 项测试全部通过;本机安装版也已完成签名校验和基于合成文档的界面闭环验收。
| 已经具备 | 仍然明确保留的限制 |
|---|---|
| .doc、.docx 导入,统一导出新 .docx | 不处理图片文字、SmartArt 和嵌入对象 |
| 正文、表格、页眉页脚、文本框、批注、脚注和修订内容 | 复杂 Word 结构仍需人工查看最终版式 |
| 多文档队列、后台扫描、组合筛选和批量导出 | 后台取消只在文件边界安全生效 |
| 本文强制、永不脱敏、同值全文和证据解释 | 用户个人词典尚未跨会话加密保存 |
| 导出后残留复检与本地最小日志 | 仍需逐份人工复核,不是无人值守系统 |
| macOS 内部测试应用与 DMG | 尚未完成正式签名、公证和跨设备公开分发 |
13 我会保留的八条 Vibe Coding 经验
一、先定义失败:什么情况下必须停下,应该与成功路径一样早写清楚。
二、准确率是一条链:候选、证据、人工决定、实际输出和残留复检缺一不可。
三、误判与漏判同样重要:宽松规则看起来命中更多,却可能让复核界面失去信任。
四、借架构,不迷信依赖:成熟开源项目最先值得复制的是边界和分层,不一定是整个技术栈。
五、界面状态也要可审计:“已经生效但看不出来”不能算完成。
六、验收必须走到真实终点:窗口打开、测试通过、文件生成,都不等于最终副本正确。
七、删功能不是失败:主动放弃不够可靠的 PDF/OCR,让 Word 主链更扎实。
八、隐私贯穿研发全程:样本、日志、配置、截图、迁移和分发都要遵守同一边界。
写在最后:法律人的 Vibe Coding,优势不在“会不会写代码”
这次实践让我更确信,法律人做垂直工具的真正优势,是知道哪些错误不可接受、哪些信息必须保留给人工判断、哪些流程节点不能用“差不多”代替,以及什么才算一次可以交付的完成。
AI 可以加速代码、测试和界面的迭代,但它不会自动替你定义产品边界。每一次误判、崩溃、迁移失败和范围收缩,都需要回到使用场景重新判断。
Vibe Coding 最理想的结果,不是一天做出很多功能,而是把专业经验逐步写成可以验证、可以纠错、可以交接,也敢于承认限制的软件。
资料与边界
本文依据该项目的需求记录、阶段计划、技术设计、风险清单、迁移接力文档、合成样本界面验收和发布前回归结果整理。为保护隐私,公开内容删除了真实材料名称、主体信息、文件路径、日志原文和个体化规则;文中功能状态只代表发布时的内部测试版本。
交流本地脱敏与法律科技产品,请勿提交真实材料
如需讨论产品设计,请只描述抽象场景、合成样本和期望流程。不要发送真实合同、客户身份、账号、联系方式、文件截图或其他敏感信息。