把 Amazon Textract Custom Queries 适配器安全地推向多账户生产环境

2026-09-28 18 预计阅读时间: 1 分钟
来源: aws.amazon.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:13 分钟

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 流水线按以下步骤晋级:

  1. 冻结训练集清单和验证集,计算并保存校验和。
  2. 在开发账户创建并训练适配器版本。
  3. 使用固定测试集执行回归评估,记录字段级指标。
  4. 由流水线承担目标账户中的部署角色。
  5. 将经过批准的数据清单复制到目标账户的受控 S3 前缀。
  6. 在目标账户创建对应适配器或新版本,并等待训练完成。
  7. 再次执行验收测试;达标后更新环境注册表。
  8. 保留旧版本映射,以便快速回滚。

下面的脚本展示了如何在目标账户创建适配器并启动版本训练。它假设目标账户已经存在训练清单、输出桶和可供流水线承担的角色。运行前需要替换环境变量,并确认当前 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 必须可重入,并且删除旧基础设施时不应顺带删除仍在生产使用的适配器版本。

多种表单版本:先分类,再选择适配器

现实中的文档不会同时升级。供应商可能继续发送旧版发票,而新客户已经切换到新版。如果把所有版式都交给同一个适配器,训练数据和回归范围会不断膨胀,错误也更难定位。

一种更可控的模式是“两阶段路由”:

  1. 先读取文档中的版本标记、模板编号或稳定标题。
  2. 将分类结果映射到一个明确的适配器版本。
  3. 再调用 AnalyzeDocument 执行自定义查询。
  4. 低置信度或未知模板进入人工审核,不要猜测路由。

下面是可改造的 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 权限拆分,并限制到必要动作和数据范围;
  • 监控覆盖训练失败、调用错误、分类漂移、字段准确率和成本;
  • 旧适配器版本不会在新版本上线时立即删除,回滚映射已验证。

真正可运营的适配器平台,不是一个训练成功的模型,而是一条能够重复部署、量化验收、按账户隔离并安全回滚的发布链路。把数据版本、适配器版本和应用路由一起管理,才能避免文档格式变化最终演变成生产事故。


相关推荐