Amazon Textract Custom Queries 适配器从实验走向生产时,难点往往不在训练本身,而在于如何管理版本、跨 AWS 账户晋级、识别不同表单版本,以及落实网络隔离、加密和最小权限。一个可靠的方案应把适配器视为不可变发布物:训练数据有版本、适配器版本可追踪、晋级过程可重复,业务代码也不依赖人工填写的 ID。
不要把适配器 ID 当成部署配置
在开发、预生产和生产账户中,同一个逻辑适配器通常会对应不同的账户内资源标识。因此,不应把开发账户里的 AdapterId 直接写进生产代码,更不能把“在控制台重新点一遍”当作发布流程。
更稳妥的方式是维护一份逻辑发布清单:
schemaVersion: 1
release: invoice-queries-2025-03
region: us-east-1
trainingDataset:
bucket: company-textract-datasets
manifestKey: invoices/2025-03/manifest.json
forms:
invoice-v1:
adapterName: invoice-v1
expectedVersion: "3"
invoice-v2:
adapterName: invoice-v2
expectedVersion: "1"
acceptance:
minimumF1: 0.92
maximumCriticalFieldErrorRate: 0.01
这里的版本号是示例,实际流水线应在目标账户训练或创建版本后,将真实的 AdapterId、版本和评估结果写入参数存储、配置仓库或部署产物。发布清单负责描述“应该部署什么”,环境注册表负责记录“这个账户实际部署了什么”。
例如,应用最终只读取这样的环境映射:
{
"invoice-v1": {
"adapter_id": "TARGET_ACCOUNT_ADAPTER_ID_V1",
"version": "3"
},
"invoice-v2": {
"adapter_id": "TARGET_ACCOUNT_ADAPTER_ID_V2",
"version": "1"
}
}
这层间接映射解决了两个问题:账户间 ID 不同,以及适配器升级时无需修改业务代码。
跨账户晋级应当是“重放发布”,而不是复制控制台状态
可以把开发、预生产和生产分别放在独立 AWS 账户,并让 CI/CD 流水线按以下步骤晋级:
- 冻结训练集清单和验证集,计算并保存校验和。
- 在开发账户创建并训练适配器版本。
- 使用固定测试集执行回归评估,记录字段级指标。
- 由流水线承担目标账户中的部署角色。
- 将经过批准的数据清单复制到目标账户的受控 S3 前缀。
- 在目标账户创建对应适配器或新版本,并等待训练完成。
- 再次执行验收测试;达标后更新环境注册表。
- 保留旧版本映射,以便快速回滚。
下面的脚本展示了如何在目标账户创建适配器并启动版本训练。它假设目标账户已经存在训练清单、输出桶和可供流水线承担的角色。运行前需要替换环境变量,并确认当前 AWS CLI 版本支持相应的 Textract 命令参数。
#!/usr/bin/env bash
set -euo pipefail
: "${TARGET_ROLE_ARN:?set TARGET_ROLE_ARN}"
: "${AWS_REGION:=us-east-1}"
: "${ADAPTER_NAME:=invoice-v2}"
: "${DATASET_BUCKET:?set DATASET_BUCKET}"
: "${MANIFEST_KEY:?set MANIFEST_KEY}"
: "${OUTPUT_BUCKET:?set OUTPUT_BUCKET}"
: "${OUTPUT_PREFIX:=textract-training/${ADAPTER_NAME}}"
CREDS="$(aws sts assume-role \
--role-arn "$TARGET_ROLE_ARN" \
--role-session-name textract-adapter-promotion \
--query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken]' \
--output text)"
read -r AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN <<<"$CREDS"
export AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN AWS_REGION
ADAPTER_ID="$(aws textract create-adapter \
--adapter-name "$ADAPTER_NAME" \
--feature-types QUERIES \
--query AdapterId \
--output text)"
aws textract create-adapter-version \
--adapter-id "$ADAPTER_ID" \
--dataset-config "{\"ManifestS3Object\":{\"Bucket\":\"$DATASET_BUCKET\",\"Name\":\"$MANIFEST_KEY\"}}" \
--output-config "{\"S3Bucket\":\"$OUTPUT_BUCKET\",\"S3Prefix\":\"$OUTPUT_PREFIX\"}"
echo "Created adapter: $ADAPTER_ID"
echo "Record the returned version, wait for training, then run acceptance tests."
生产流水线还应增加幂等控制:创建前按逻辑名称查询现有适配器,避免重试时生成重复资源;训练期间轮询版本状态;失败时保留日志和输出,而不是立即覆盖稳定版本。
适配器本身的创建和训练也可以封装为 CloudFormation 自定义资源或 Terraform 调用模块。需要注意,自定义资源的 Create、Update 和 Delete 必须可重入,并且删除旧基础设施时不应顺带删除仍在生产使用的适配器版本。
多种表单版本:先分类,再选择适配器
现实中的文档不会同时升级。供应商可能继续发送旧版发票,而新客户已经切换到新版。如果把所有版式都交给同一个适配器,训练数据和回归范围会不断膨胀,错误也更难定位。
一种更可控的模式是“两阶段路由”:
- 先读取文档中的版本标记、模板编号或稳定标题。
- 将分类结果映射到一个明确的适配器版本。
- 再调用
AnalyzeDocument执行自定义查询。 - 低置信度或未知模板进入人工审核,不要猜测路由。
下面是可改造的 Python 示例。它使用一次 OCR 调用识别表单版本,再选择对应适配器。运行前安装 boto3,设置 TEXTRACT_ADAPTERS_JSON,并确保调用身份能够读取输入对象和调用 Textract。
#!/usr/bin/env python3
import json
import os
import re
import sys
import boto3
textract = boto3.client("textract")
ADAPTERS = json.loads(os.environ["TEXTRACT_ADAPTERS_JSON"])
def classify_form(bucket: str, key: str) -> str:
response = textract.detect_document_text(
Document={"S3Object": {"Bucket": bucket, "Name": key}}
)
text = "\n".join(
block["Text"]
for block in response.get("Blocks", [])
if block.get("BlockType") == "LINE" and "Text" in block
)
match = re.search(r"FORM[- ]VERSION[: ]+(V[12])", text, re.IGNORECASE)
if not match:
raise ValueError("Unknown form version; route document to manual review")
return f"invoice-{match.group(1).lower()}"
def analyze(bucket: str, key: str) -> dict:
form_type = classify_form(bucket, key)
adapter = ADAPTERS.get(form_type)
if not adapter:
raise ValueError(f"No approved adapter configured for {form_type}")
return textract.analyze_document(
Document={"S3Object": {"Bucket": bucket, "Name": key}},
FeatureTypes=["QUERIES"],
QueriesConfig={
"Queries": [
{"Text": "What is the invoice number?", "Alias": "invoice_number"},
{"Text": "What is the total amount?", "Alias": "total_amount"},
]
},
AdaptersConfig={
"Adapters": [
{
"AdapterId": adapter["adapter_id"],
"Version": adapter["version"],
"Pages": ["*"],
}
]
},
)
if __name__ == "__main__":
if len(sys.argv) != 3:
raise SystemExit(f"Usage: {sys.argv[0]} BUCKET KEY")
result = analyze(sys.argv[1], sys.argv[2])
print(json.dumps(result, indent=2, default=str))
运行示例:
export AWS_REGION=us-east-1
export TEXTRACT_ADAPTERS_JSON='{
"invoice-v1":{"adapter_id":"ADAPTER_ID_1","version":"3"},
"invoice-v2":{"adapter_id":"ADAPTER_ID_2","version":"1"}
}'
python3 route_and_analyze.py document-input-bucket samples/invoice.pdf
示例中的正则分类器适合带有稳定版本标记的表单。若文档没有这类标记,可以使用专门的分类模型,但仍应输出置信度并定义拒绝阈值。分类错误会把文档交给错误的适配器,因此路由准确率必须单独监控。
用基础设施即代码收紧网络和数据边界
适配器生命周期不应脱离基础设施管理。至少应将以下资源纳入 CloudFormation 或 Terraform:
- 训练、验证、输入和输出数据使用的 S3 桶与前缀策略;
- 客户管理的 KMS 密钥、密钥轮换和授权策略;
- Textract 接口型 VPC Endpoint;
- S3 Gateway Endpoint 或接口端点;
- 流水线部署角色和运行时调用角色;
- CloudTrail、日志、告警以及失败文档隔离位置。
以下 Terraform 片段可用于创建 Textract 接口端点。它假设已有 VPC、私有子网和允许 HTTPS 的安全组;请把变量值替换成自己的网络资源。
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}
variable "aws_region" {
type = string
default = "us-east-1"
}
variable "vpc_id" {
type = string
}
variable "private_subnet_ids" {
type = list(string)
}
variable "endpoint_security_group_ids" {
type = list(string)
}
data "aws_region" "current" {}
resource "aws_vpc_endpoint" "textract" {
vpc_id = var.vpc_id
service_name = "com.amazonaws.${data.aws_region.current.name}.textract"
vpc_endpoint_type = "Interface"
subnet_ids = var.private_subnet_ids
security_group_ids = var.endpoint_security_group_ids
private_dns_enabled = true
tags = {
Name = "textract-private-endpoint"
}
}
调用角色应只访问指定 S3 前缀和 KMS 密钥。部分 Textract 操作可能要求 IAM 策略中的 Resource 使用 *;这不意味着整个角色都要宽泛授权。可以把 Textract 动作限制为必要集合,同时把 S3、KMS 和角色承担权限精确到 ARN,并使用组织策略、VPC Endpoint 策略和账户边界补充控制。
还要注意:VPC Endpoint 保护的是工作负载到 AWS 服务的网络路径,不会自动阻止其他调用路径。若合规要求禁止公网访问,需要同时检查路由、DNS、端点策略、S3 桶策略以及组织级控制。
上线前的检查表
将 Custom Queries 适配器投入生产前,可以逐项确认:
- 训练集、验证集和测试集都具有不可变版本及校验和;
- 适配器名称是逻辑名称,账户内 ID 由环境注册表解析;
- 跨账户流水线使用短期凭证承担目标角色,没有长期访问密钥;
- 晋级后重新运行测试,而不是沿用开发账户的评估结果;
- 表单分类器具有置信度阈值、未知类型处理和人工审核通道;
- S3 默认加密、KMS 密钥策略和输出数据生命周期已经配置;
- Textract、S3 与 KMS 权限拆分,并限制到必要动作和数据范围;
- 监控覆盖训练失败、调用错误、分类漂移、字段准确率和成本;
- 旧适配器版本不会在新版本上线时立即删除,回滚映射已验证。
真正可运营的适配器平台,不是一个训练成功的模型,而是一条能够重复部署、量化验收、按账户隔离并安全回滚的发布链路。把数据版本、适配器版本和应用路由一起管理,才能避免文档格式变化最终演变成生产事故。