文档处理的边界在哪里:自建 vs. API 外包
做后端开发一段时间后,你会遇到一类需求,它们看起来并不复杂,但实现起来隐藏着大量工程量:把 Word 文档转成 PDF、从扫描件里提取表格、把合同里的手机号抹掉再分发。
这类需求有一个共同特征——表面上是「一行代码的事」,实际上是一个成熟软件产品才能覆盖的问题域。
文档处理的「冰山成本」
我曾经尝试自建 PDF 处理能力。第一个版本用 Python 的 reportlab 生成 PDF,看起来工作正常。然后需求来了:
- 产品说「字体渲染不对,中文有乱码」
- 客户上传了扫描件,不是文字 PDF,
pdfplumber提取出来全是空字符串 - 需要把 Excel 表格里的内容嵌进 PDF 某几页
- 合规部门要求分发前抹掉所有身份证号
每一条需求背后都是一套单独的工程。OCR 涉及语言模型的选型(Tesseract vs. PaddleOCR vs. 云 OCR)、扫描件质量预处理、多语言支持;格式转换涉及 LibreOffice 的无头模式部署、字体嵌入、页面布局兼容;PII 抹除涉及正则、预设规则、文档坐标系的像素级遮盖。
这些能力单独拿出来每一个都不算难,但组合在一起、部署在生产、处理真实用户上传的「野生」文档时,边界情况会把你淹没。
什么时候应该外包
我现在的判断标准很简单:如果文档处理不是你产品的核心差异化能力,就不要自建。
Nutrient DWS 这类专业文档 API 的价值不在于「它能做你做不到的事」,而在于它把一个复杂问题域的维护责任彻底转移出去。你用一个 multipart POST 请求,加一个 JSON 格式的 instructions 字段,就能描述一整条处理流水线:先 OCR、再抽取文本、最后抹掉 PII。
# 描述一条「OCR → 导出可搜索 PDF」流水线
curl -X POST https://api.nutrient.io/build \
-H "Authorization: Bearer $NUTRIENT_API_KEY" \
-F "scanned.pdf=@scanned.pdf" \
-F 'instructions={"parts":[{"file":"scanned.pdf"}],"actions":[{"type":"ocr","language":"chi_sim"}]}' \
-o searchable.pdf这个设计的关键在于 instructions 是声明式的——你描述「要什么结果」,不描述「怎么做到」。格式兼容、语言模型选择、坐标系转换,全部封装在 API 内部。
声明式接口的核心收益是将实现细节的演化与你的代码隔离。PII 抹除的正则规则更新了、OCR 模型换代了、某个格式的兼容性改善了——这些变化对你透明。命令式自建的代价恰好相反:你既是调用方,也是实现方,两个角色的维护负担全部落在你身上。
判断「外包」成本是否合理
外包给 API 也不是没有代价:接口依赖第三方稳定性、延迟变成网络 RTT 而不是本地计算、文档数据要出境(合规敏感场景需要审查)、按量付费的成本在高频场景下可能超过自建。
我的评估框架:
| 维度 | 倾向自建 | 倾向外包 | |------|---------|---------| | 数据合规 | 涉及高敏感数据、出境受限 | 通用文档、非核心业务数据 | | 调用频次 | 每天百万次以上 | 低频、峰值不稳定 | | 差异化需求 | 文档处理本身是产品核心 | 文档处理只是支撑流程 | | 团队能力 | 有专职文档/NLP 工程师 | 全栈小团队、人力有限 |
大多数业务系统落在「外包」那一列——文档处理是支撑性能力,不是产品的核心。这时候花几天自建 OCR 流水线,本质上是在用工程时间换取一个维护负担。
MCP 集成:给 Agent 加文档处理能力
值得单独提一句:这类 API 通常提供 MCP Server,可以直接作为工具注册进 AI Agent 的工具链。配置完成后,Agent 可以在推理过程中直接调用「转换这份合同为 PDF」「抹掉这个文件里的邮箱地址」这样的动作,不需要额外的胶水代码。
这是「外包」思路在 AI 工程场景下的自然延伸:把有明确规格、高可靠性要求的子任务,外包给专业工具,而不是让 LLM 自己生成代码去处理。
一句话心法
重新发明轮子的真实成本不在于第一次写出来,而在于之后每一次维护边界情况——把这笔账算进去,再决定要不要自建。