人手一份纸质合同、每个月堆成山的发票、来自不同渠道的申报表——我估计做过企业信息化的人都懂,真正的业务瓶颈往往不在系统流程,而在"文档怎么变成数据"这一步。哪怕你在数据库里设计了再漂亮的表结构,数据进不来,后面全是空转。Azure认知服务里的Document Intelligence(文档智能)解决的就是这件事:把PDF、扫描件、照片里的内容,识别成程序能读的结构化数据。这篇笔记不是官方文档的复述,是我在一个模拟项目里实际跑通后的记录——从服务创建、API调用、字段提取,到后面踩过的几个坑,以及最终怎么把它嵌进业务链路,都会写到。
如果你在选型阶段、刚接触Azure AI,或者准备考Azure架构师方向但一直没想清楚认知服务到底怎么落地,这篇会比较对胃口。它不涉及复杂的数据科学训练,核心是告诉大家:一个普通的云架构师,如何用现成的认知服务快速完成"文档结构化",以及哪些地方容易想当然。
1. Document Intelligence是干嘛的:从人工录入到机器阅读的转变
1.1 它解决的问题是"结构提取",不是单纯OCR
很多人一听文档识别,第一反应是OCR。这个理解不算错,但容易把方案选偏。OCR解决的是"图片里的字变成文本",而Document Intelligence解决的是"文档里的信息变成键值对、表格、实体,并且带结构、带置信度"。
举一个对比你就明白了。传统OCR给你一行行的文字,你拿到之后还得自己写正则、按坐标抠字段;Document Intelligence直接告诉你:这张发票的"开票日期"是2025-03-12,"含税金额"是12800.00,"销售方名称"是某公司——每个字段还带一个confidence分数,告诉系统这个结果有多可信。对下游系统来说,后者才是真正可用的数据。
1.2 服务内部把文档处理拆成了哪几步
我用下来发现,Document Intelligence其实是把一条流水线封装成了几个模型Asset,理解这条流水线对正确选型很有帮助。大致分四步:
读取(Read):这是基础层,负责检测页面上的文本、手写内容、印刷体,输出逐行的文本和坐标。任何类型文档都会先经过它,相当于"看清字"。
分析(Analyze):在这一层,服务开始理解文档的版式结构——哪些是大标题、哪些是段落、哪些是表格、哪些是页眉页脚。它会把一页纸拆成区块,告诉你"这里是一张表,那里是一个段落"。
提取(Extract):针对特定类型的文档,用预训练模型去抽键值对。比如发票模型知道"金额"通常长什么样、合同模型知道"甲方乙方"怎么配对。这是Document Intelligence最有价值的部分。
分类(Classify):识别文档属于什么类型,比如这张是身份证还是驾驶证,是发票还是提单。分类模型通常放在前面,决定后面走哪个提取模型。
这套链路的价值在于:它不是拿一个模型硬干所有事,而是让每个环节干自己最擅长的活。你在调用时需要知道当前API用的是哪一层的能力。很多人拿到API就直接上"分析发票",忽略了先用分类模型判断文档类型,结果遇到混合文档时精度会明显下降。
2. 动手前的关键选型:预训练模型、自定义模型与工作流怎么搭
2.1 先弄清你的文档长什么样
任何AI服务,第一步都该是盘点自己的数据,而不是急着调API。我见过不少团队一上来就调发票模型,测出效果不好就下结论说"这服务不行",其实是输入数据跟模型能力不匹配。
你至少要回答三个问题:
- 文档类型是否固定:是每个月固定几种类型,还是五花八门什么都有?固定类型适合用预训练模型或自定义提取模型,五花八门得走分类+多模型组合。
- 版式是否统一:供应商发来的发票虽然都叫发票,但不同地区、不同软件打出来的版式差别很大。如果版式高度统一,用模板模型又快又准;如果变化多端,得用神经模型或者预训练模型。
- 字段种类和准确率要求:财务入账对金额准确率要求极高,可能需要置信度阈值兜底人工复核;而档案数字化只需要提取个大概分类,就没必要上高精度的提取模型。
我自己做了一个简单的决策表格,实测选型时够用:
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 单种固定版式表单 | 自定义模板提取模型 | 训练成本低、字段提取精准、响应快 |
| 多种固定版式但格式统一 | 分类模型 + 多个模板提取模型 | 先分流再提取,整体精度高 |
| 版式多变的同类文档(发票/合同) | 预训练模型(如Invoice模型) | 微软已用海量同类样本预训练,泛化能力强 |
| 版式多变且字段随业务定制 | 自定义神经模型 | 能容忍键值位置漂移,适合长尾文档 |
| 只需要全文搜索 | Read或Layout API | 不需要键值对,只要文本和坐标 |
2.2 预训练模型与自定义模型的差异对照
预训练模型是微软用大量行业文档训好的,比如Invoice(发票)、Receipt(收据)、ID document(身份证/护照)、Contract(合同)。好处是开箱即用,你传一张发票进去,它就告诉你金额、日期、税号这些标准字段。限制是字段集合固定,不是你的业务想要什么它就提什么。
我之前在一个模拟项目中试过合同预训练模型,它能提取常见的甲方、乙方、签署日期、合同金额,但我们的合同里还有"交付期限""违约金比例"这类字段,预训练模型不会给你提出来。这种情况就得训练自定义模型。
自定义模型分两种架构:template(模板)和neural(神经)。模板模型比较传统,本质上是建立"某个字段在版式的哪个位置"的特征,适合版式固定的表单,训练只需要5份样本,出结果很快;神经模型则是基于深度学习的端到端提取,对版式变化容忍度高,但训练样本要求高一些,一般建议准备50份以上,训练时间也长。
很多人以为自定义模型一定比预训练模型准,这是误区。如果你的文档类型恰好落在预训练模型覆盖范围内,直接用预训练模型通常比自定义模型更稳,因为它的训练集规模不是你那几十份样本能比的。
2.3 几种常见架构组合方式
选型最终要落成架构,我梳理了三种常见组合,由简到繁:
组合一:单模型直连。一个Document Intelligence资源,一个预训练模型,前端传文件,后端收结果。适合业务简单、文档单一的场景。最快当天就能跑通。
组合二:分类器前置。一个分类模型先判断文档类型,再路由到对应的提取模型。适合业务里有发票、身份证、合同等多种文档混合的场景。代价是业务链路里多一跳,多一次API调用,但整体准确率提高非常明显。
组合三:提取+人工审核闭环。这是我认为真正能落地生产的组合。Document Intelligence不可能做到100%准确,生产系统必须设计"置信度不够就转人工"的流程。每个关键字段带confidence值,低于阈值就进入人工复核工作流,复核后的结果再回流用于后续模型优化。
我在真实项目里的体会是:大部分业务其实不需要端到端100%自动化。你要设计的不是"机器替代人",而是"机器完成大部分工作,人只处理疑难杂症"。这个思路看起来保守,实际上比强行追求全自动要可靠得多,也更符合架构师该有的决策逻辑。
3. 最常用的几种调用路径:REST API、Python SDK与样本代码走查
3.1 创建认知服务资源并拿到三个关键值
在Azure门户里找到"Document Intelligence"服务,创建一个资源。需要选资源组、区域和定价层。定价层有免费层F0和标准层S0,免费层每月有一定次数的免费调用,适合试用;生产环境通常用S0。
创建完成后,进入"Keys and Endpoint"页面,你会拿到三个关键值:
- Endpoint:形如
https://<your-resource-name>.cognitiveservices.azure.com/ - Key1 / Key2:两个密钥,作用相同,轮换时用
这三个值是所有调用的凭据。我建议在代码里不要硬编码,用环境变量或者Azure Key Vault存。之前在测试环境我图省事把Key写进了配置文件,后来清理时费了很大劲。Key是敏感的,任何人拿到Key就能调你的服务消耗配额。
Document Intelligence的调用逻辑就两大类:同步和异步轮询。大部分分析操作都是异步的——你先提交一个分析请求,得到一个Operation-Location头,然后轮询这个地址获取结果。为什么这么设计?因为文档分析有时需要几十秒,HTTP连接不可能一直挂着等,所以采用"提交任务-轮询结果"的模式。如果你用SDK,这个轮询过程被封装的很好,开箱即用。
3.2 用REST API跑通第一个Layout分析
先不着急上SDK,我建议先用REST把链路跑通,理解HTTP层面发生了什么,这样排查问题时不至于一头雾水。
下面用curl做一次Layout分析。Layout是文档版面分析,可以简单理解为"返回每一行文字和它在页面上的坐标框"。
# 1. 提交分析请求(异步) curl -X POST \ "https://<your-resource-name>.cognitiveservices.azure.com/documentintelligence/v1.0/prebuilt/layout:analyze?outputContentFormat=markdown" \ -H "Ocp-Apim-Subscription-Key: <your-key>" \ -H "Content-Type: application/json" \ -d '{ "urlSource": "https://example.com/sample-contract.pdf" }'注意这里是urlSource,传一个公网能访问的文件地址。如果你的文件在Azure Blob Storage里,要给Blob配一个临时SAS URL。如果文件在本地,用base64Source传文件的Base64编码。
服务端会返回HTTP 202 Accepted,响应头里有Operation-Location,类似:
https://<your-resource-name>.cognitiveservices.azure.com/documentintelligence/v1.0/prebuilt/layout:analyze/operations/123456789?api-version=2023-07-31拿着这个地址轮询,直到结果状态变成succeeded:
curl -X GET \ "https://<your-resource-name>.cognitiveservices.azure.com/documentintelligence/v1.0/prebuilt/layout:analyze/operations/123456789?api-version=2023-07-31" \ -H "Ocp-Apim-Subscription-Key: <your-key>"轮询响应的status字段可能是running,等它变成succeeded后,响应体里就是完整的分析结果。每个文本元素都带content、confidence、boundingRegions(坐标)等字段。
3.3 用Python SDK调发票识别并提取关键字段
REST跑通之后,日常开发我还是更推荐SDK。Python的SDK包名是azure-ai-formrecognizer,不过要注意,新版SDK的命名已经改成了azure-ai-documentintelligence,旧包是Form Recognizer时代的叫法。我下面按新包写。
pip install azure-ai-documentintelligence azure-identity示例代码:
from azure.ai.documentintelligence import DocumentIntelligenceClient from azure.ai.documentintelligence.models import AnalyzeDocumentRequest from azure.core.credentials import AzureKeyCredential endpoint = "https://<your-resource-name>.cognitiveservices.azure.com/" key = "<your-key>" client = DocumentIntelligenceClient(endpoint=endpoint, credential=AzureKeyCredential(key)) # 引用公网PDF文件 poller = client.begin_analyze_document( model_id="prebuilt-invoice", analyze_request=AnalyzeDocumentRequest(url_source="https://example.com/sample-invoice.pdf"), ) result = poller.result() # 遍历识别出的文档字段 for doc in result.documents: print(f"文档类型: {doc.doc_type}, 置信度: {doc.confidence}") fields = doc.fields if "InvoiceId" in fields: print(f"发票号: {fields['InvoiceId'].value_string}") if "InvoiceDate" in fields: print(f"开票日期: {fields['InvoiceDate'].value_date}") if "InvoiceTotal" in fields: print(f"含税总金额: {fields['InvoiceTotal'].value_currency.amount}") if "VendorName" in fields: print(f"销售方: {fields['VendorName'].value_string}")begin_analyze_document返回一个轮询器,result()方法会阻塞等待分析完成。整个过程对业务代码是同步的,但底层依然是异步轮询。
有两点我要特别提醒:
- 传URL时要用可访问的地址。如果你传内网地址,确认你的网络能访问到。我遇到过同事在本地测试时传了
localhost地址,结果服务端根本拉取不到文件,报了404或超时。本地测试可以把PDF转Base64再传,省心很多。 - 字段名以官方文档为准。预训练模型的字段是按标准行业语义命名的,比如发票的
InvoiceTotal、VendorName。不同模型的字段集合不一样,写代码前先看一遍API文档确认字段名,别靠猜。
4. 字段提取精度与训练数据的坑:实测踩过的三个典型问题
这一节是我最想写的部分。真实项目里,Api调用本身不会出什么幺蛾子,出问题的永远是数据跟模型边界之间的灰色地带。我整理了自己在实验里真实撞过的三个问题,附带排查思路,比官方文档里那套"最佳实践"要具体得多。
4.1 问题一:表格跨页分裂,坐标与内容对不上
有一次处理多页PDF合同,里面有大面积的表格。我用Layout模型分析后,发现一个奇怪现象:同一个表格的逻辑行分布在了前后两页,结果模型返回了两个(甚至三个)独立的table对象,每页各有一个。原本的"表头"只出现在了第一页,第二页起就没有表头了。
如果你只是把table数组里的内容直接导出,就会出现第二页数据不知道对应哪个字段的问题。你心里清楚这是同一张表,但模型的输出里它们是分开的。
这个问题的根源在于PD F渲染时表格在物理页面上被强制分页,而版面分析模型是按物理页面切割内容的。解决思路有两个方向:
- 在数据层面做后处理:根据表格列的列数、表头文本相似度等特征,把跨页的表格片段重新拼接。需要你自己写逻辑去匹配表头与单元格顺序。
- 在源文件层面处理:如果业务允许,尽量要求上游在导出PDF时把表格设置为"行不分页"或者"重复表头"。这属于治本的做法,但不是所有上游系统都能配合。
我当时的做法是治标加治本一起:先联系业务方改了部分导出的分页设置,同时在代码里加了"跨页表格合并"的后处理逻辑,用表头文本和列数做匹配条件。最后导出数据的完整率从七成提到了接近满分。
4.2 问题二:印章与手写体干扰键值对提取
扫描文件上的红色印章是个大杀手。我有一批发票扫描件,销售方名称旁边盖了一个圆形的发票专用章,结果模型把章上的残字和发票上的印刷文字混在了一起,VendorName字段里出现了一堆乱码。印章的识别错误还会连带降低整个文档的平均置信度。
手写体是另一个干扰源。预训练模型对印刷体的提取很稳,但表格里若出现手写的备注,或者单据上有手写签名,模型的表现会明显下滑。
这类问题的处理顺序很重要,我的经验是:
- 先在输入侧做预处理:把扫描件转成高DPI的PNG或JPEG,建议300 DPI以上;做灰度化或二值化,去掉背景噪点;有条件的可以做倾斜校正。Document Intelligence虽然内置了图像增强逻辑,但你送进去的图越干净,后面的置信度越高。
- 分析失败样本的模式:如果模型总是把印章文字和正文混在一起,可以考虑用图像处理算法先做印章区域检测并去除,但这属于锦上添花,业务上不常见。
- 接受"部分失败"并设计兜底:在大多数生产场景里,与其绞尽脑汁让模型把写着印章的发票一次识别对,不如让置信度机制自己说话——
VendorName字段的置信度低于0.8,就把它送进人工复核队列。对财务场景来说,人工复核少量异常单据的成本,远低于你花两周调优提取模型的时间成本。
4.3 问题三:自定义模型训练集被"模板化"误导
这是我在训练自定义模板提取模型时踩得最深的坑。我做了一批固定版式的报销单识别,刚开始只收集了十来份样本,而且全都是同一个打印机打出来的、排版几乎一模一样的单子。训练完满意度很高,一测就是95%以上的字段提取率。
结果一到生产就露馅了。业务方的真实报销单是从不同地区、不同系统导出的,版式虽然大体一致,但有的表单右边多了一栏,有的字体小一号,有的页边距不一样——模板模型的识别率立刻掉到六成多。
问题出在训练数据"模板化"了:模型看到的所有样本都长一个样,它很快就学会了"死记硬背"那个版式,而不是学会"理解"这个字段。遇到版式轻微变化,它就懵了。
教训是:收集自定义模型的训练样本时,要刻意追求多样性。最少准备20份不同来源的样本,确保覆盖多种字体、不同分辨率、不同拍照角度、不同背景。如果业务上有不同区域版本,每个区域版本都要有样本进训练集。
标注时也要注意一致性:同一个字段,不同标注员的框选范围要尽量一致。比如"报销事由",有的人标的是整个输入框,有的人只标了里面的文字,这会导致模型学歪。我在一个样本量较大的项目里做过统计,标注边框范围不一致对模型精度的影响,往往比样本量不够还要明显。
5. 把认知服务装进真实业务的架构建议:配额、成本与可用性
5.1 配额与并发:为什么不能把Document Intelligence当成无限OCR用
认知服务这种托管API,很多人容易默认它是无限并发、随便调的。这是架构设计里非常危险的假设。Document Intelligence对资源有明确的速率限制,你需要在Azure门户的资源详情页里查看当前的配额和已用情况。
生产环境里我建议这样设计调用层:
- 异步队列 + 限速调度:文件上传后直接扔进消息队列,后端按配额匀速调用Document Intelligence,避免突发流量把RPS打满。Azure上可以组合Blob Storage + Queue Storage + 一个简单的Worker。
- 按模型分Key分资源:如果业务里同时跑发票识别和合同识别,尽量给不同模型申请不同资源,或者至少用不同Key做隔离。这样一个模型被打满不会拖垮另一个。
- 设置熔断:连续报429(限流)时,不要无脑重试。用指数退避重试,重试多次后直接转人工审核通道。业务宁可晚一小时拿结果,也不要被一堆重试请求拖死。
5.2 成本估算的方式
Document Intelligence的计费基本逻辑是按调用量、按页数计费,不同的预训练模型价格不同。免费层F0每月有一定额度,标准层S0按每千次调用收费,自定义模型训练和调用还有额外费用。具体单价建议去Azure定价页看你所在区域的实时价格。
成本估算的公式其实很简单:
月度成本 ≈ 单次调用价格 × 预估月调用次数
举个例子:假设每月有1万份发票需要处理,平均每份3页,那么月调用次数就是3万次(按页计费的话就是3万页)。用标准层Invoice模型的单价去乘,就能得到一个大致的月度成本。
做成本评估时我有两个提醒:
- 别忽略分类器前置的成本。如果架构里加了分类模型,意味着每份文档都多一次分类调用。分类模型单价通常较低,但量大的时候积少成多。
- 训练自定义模型本身是要花钱的。模板模型训练费用相对低,神经模型训练时间久、成本明显更高。把这些一次性训练费用摊进项目成本,比单看调用单价更靠谱。
至于结果数据,我建议分析完成后直接写进Azure Blob Storage或Cosmos DB,后续用其他分析服务做数据分析和归档。Document Intelligence是"文档到数据"的管道,数据落地之后就不该继续留在认知服务这边。
5.3 架构中的降级方案与人工兜底
最后聊一下可用性设计。托管AI服务出问题时的降级策略,跟普通API不一样——你不能简单failover到另一个数据中心就完事,因为你不但要考虑服务本身是否可用,还要考虑模型在某个区域是否可用。
我的做法是给系统设计三级降级:
- 正常路径:Document Intelligence自动分析 → 置信度高于阈值 → 直接入下游系统。
- 人工复核路径:置信度低于阈值,或字段缺失 → 进入人工复核队列,操作员用预填充表单修正。
- 降级路径:服务不可用或持续超时 → 队列里积压的任务自动转为"暂缓处理",同时告警触发,运维人员介入恢复。
这套方案的巧妙之处在于,它不是在"AI可用"和"AI不可用"两个极端之间做二元切换,而是承认AI会失败,并给失败设计了缓冲区。我在实际项目里观察到一个有趣的现象:让业务方知道"识别不了会转人工,不会丢单据"之后,他们对AI的接受度反而明显变高了。有时候,敢于承认AI有边界,比硬撑着全自动更能赢得业务信任。
最后,再分享一个我个人的体会:如果你正在规划类似文档智能的项目,别急着上全自动,也别急着买一大堆预制模型。先拿真实业务里的"脏数据"小批量跑一遍,看清哪些字段能稳定提取、哪些字段天生困难,再决定训练自定义模型还是引入人工校验。文档智能这个东西,技术的下限由模型决定,上限其实是由架构师的兜底设计决定的。