邮件数据抽取看起来像一个简单的文本理解任务:把发件人、订单号、金额、日期、地址、客户意图从邮件里拿出来。但真实业务邮件往往格式混乱、字段相似、表达重复,通用大模型很容易把“账单地址”和“收货地址”混在一起,或者把邮件正文里的历史引用当成最新请求。
这篇内容关注一个具体方向:用 Amazon SageMaker AI 微调 Amazon Nova 模型,让模型学习企业自己的邮件样式和字段边界。来源案例提到,微调后可达到最高 94.77% 的抽取准确率,并将成本降低 50%。这不是“换一个更大的模型”,而是把模型训练成更懂你的邮件模板、业务术语和字段规则。
为什么通用提示词会卡在邮件抽取上
只靠 prompt 做邮件抽取,通常能快速启动,但很快会遇到三个问题。
一是字段长得太像。例如采购邮件里可能同时出现 billing_email、requester_email、approver_email,模型知道它们都是邮箱,却不一定知道业务上应该抽哪个。
二是上下文有噪声。邮件线程会包含转发、回复、签名、免责声明、历史报价。模型如果没有见过你的真实邮件结构,可能会抽到旧邮件里的订单号。
三是输出格式不稳定。即使你要求 JSON,模型也可能在边界场景里返回解释文字、缺字段、或者把数字单位混在字符串里。对下游系统来说,这些都不是“小问题”,而是会导致自动化流程中断。
微调的价值在这里变得清晰:它不是让模型“更聪明”这么泛泛,而是让模型反复看到你关心的输入样式和标准答案,学会稳定地区分相似字段。
训练数据要像生产邮件,而不是像测试样例
邮件抽取微调的质量,很大程度取决于训练样本是否贴近生产。一个好样本不只是“邮件 + JSON”,还应该覆盖真实场景里的脏数据。
建议准备这些类型的样本:
- 标准订单邮件:字段清晰,用来建立基础模式。
- 多轮转发邮件:训练模型忽略历史引用和签名。
- 相似字段邮件:比如账单地址、收货地址、联系人地址同时出现。
- 缺失字段邮件:要求模型返回
null,而不是臆造。 - 非目标邮件:比如营销邮件、投诉邮件,让模型学会拒绝抽取或返回空结构。
可以这样组织一条 JSONL 训练样本,字段名称按你的业务系统调整:
{"messages":[{"role":"user","content":"Extract order data as JSON.\n\nEmail:\nSubject: New hardware request\nFrom: requester@example.com\n\nHi team,\nPlease ship 12 docking stations to 88 Market Street, Seattle, WA 98101.\nBilling address is 100 Finance Ave, Portland, OR 97201.\nPO number: PO-77821\nNeeded by: 2025-03-18\n\nThanks,\nMia"},{"role":"assistant","content":"{\"po_number\":\"PO-77821\",\"item\":\"docking stations\",\"quantity\":12,\"ship_to_address\":\"88 Market Street, Seattle, WA 98101\",\"billing_address\":\"100 Finance Ave, Portland, OR 97201\",\"needed_by\":\"2025-03-18\",\"requester_email\":\"requester@example.com\"}"}]}
这里有一个关键点:答案要体现你希望模型稳定学习的边界。例如 ship_to_address 和 billing_address 同时出现,训练样本必须明确告诉模型哪个字段对应哪个业务含义。
可以这样实践:准备数据并提交微调任务
下面是一个可改造的最小流程。它不假设你的账号已经有某个固定模型 ID;你需要把 MODEL_ID、ROLE_ARN、S3 路径和区域替换成自己环境里的值。
先准备本地训练文件:
mkdir -p nova-email-ft
cat > nova-email-ft/train.jsonl <<'EOF'
{"messages":[{"role":"user","content":"Extract invoice fields as JSON.\n\nEmail:\nSubject: Invoice INV-1042 due April 30\nFrom: billing@vendor.example\n\nHello,\nInvoice INV-1042 for USD 1840.50 is due on 2025-04-30.\nPlease charge cost center CC-9021.\nRegards,\nVendor Billing"},{"role":"assistant","content":"{\"invoice_id\":\"INV-1042\",\"amount\":1840.50,\"currency\":\"USD\",\"due_date\":\"2025-04-30\",\"cost_center\":\"CC-9021\",\"sender\":\"billing@vendor.example\"}"}]}
{"messages":[{"role":"user","content":"Extract invoice fields as JSON. Return null for missing fields.\n\nEmail:\nSubject: Payment question\nFrom: ap@example.com\n\nCan you confirm whether invoice INV-2209 has already been paid?"},{"role":"assistant","content":"{\"invoice_id\":\"INV-2209\",\"amount\":null,\"currency\":null,\"due_date\":null,\"cost_center\":null,\"sender\":\"ap@example.com\"}"}]}
EOF
上传到 S3:
export AWS_REGION=us-east-1
export BUCKET=my-nova-finetune-bucket
aws s3 mb s3://$BUCKET --region $AWS_REGION 2>/dev/null || true
aws s3 cp nova-email-ft/train.jsonl s3://$BUCKET/email-extraction/train.jsonl
提交 SageMaker 训练任务时,可以把下面配置当作骨架。不同账号、区域和模型版本的镜像、超参数、输入格式可能不同,实际值应以当前 Amazon Nova 和 SageMaker AI 文档为准:
export ROLE_ARN=arn:aws:iam::123456789012:role/SageMakerExecutionRole
export MODEL_ID=amazon-nova-model-id
export JOB_NAME=nova-email-extraction-ft-$(date +%Y%m%d%H%M%S)
aws sagemaker create-training-job \
--region $AWS_REGION \
--training-job-name $JOB_NAME \
--role-arn $ROLE_ARN \
--algorithm-specification '{
"TrainingImage": "ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/nova-finetune:latest",
"TrainingInputMode": "File"
}' \
--input-data-config "[{
\"ChannelName\": \"train\",
\"DataSource\": {\"S3DataSource\": {\"S3DataType\": \"S3Prefix\", \"S3Uri\": \"s3://$BUCKET/email-extraction/\", \"S3DataDistributionType\": \"FullyReplicated\"}}
}]" \
--output-data-config "S3OutputPath=s3://$BUCKET/email-extraction/output/" \
--resource-config '{"InstanceType":"ml.g5.2xlarge","InstanceCount":1,"VolumeSizeInGB":100}' \
--stopping-condition '{"MaxRuntimeInSeconds":7200}' \
--hyper-parameters "base_model=$MODEL_ID,task=email_extraction"
如果团队还没准备好直接启动微调,至少可以先用这套 JSONL 格式沉淀标注数据。数据一旦稳定,后续换训练方式、换模型版本,迁移成本都会低很多。
评估不要只看“整体准确率”
来源摘要里提到最高 94.77% 的抽取准确率,这是一个有价值的目标,但落到工程里还要拆细看。
更实用的评估维度包括:
- 字段级准确率:每个字段单独算,例如金额、日期、地址、联系人。
- 严格 JSON 合法率:返回内容是否可被
json.loads()直接解析。 - 缺失字段处理:真实缺失时是否返回
null,而不是编造。 - 相似字段混淆率:收货地址和账单地址是否被调换。
- 单封邮件成本和延迟:微调后是否能用更短 prompt、更小上下文完成任务。
可以用一个简单脚本先做离线评估。下面示例假设你已经有模型输出文件 predictions.jsonl,每行包含 expected 和 actual 两个 JSON 对象:
import json
from pathlib import Path
fields = ["invoice_id", "amount", "currency", "due_date", "cost_center", "sender"]
correct = {field: 0 for field in fields}
total = {field: 0 for field in fields}
valid_rows = 0
for line in Path("predictions.jsonl").read_text().splitlines():
row = json.loads(line)
expected = row["expected"]
actual = row["actual"]
valid_rows += 1
for field in fields:
total[field] += 1
if actual.get(field) == expected.get(field):
correct[field] += 1
for field in fields:
accuracy = correct[field] / total[field] if total[field] else 0
print(f"{field}: {accuracy:.2%}")
overall = sum(correct.values()) / sum(total.values())
print(f"overall_field_accuracy: {overall:.2%}")
print(f"evaluated_rows: {valid_rows}")
这类评估能帮助你发现“总分不错,但地址字段经常错”的问题。邮件抽取通常不是平均主义任务,一个关键字段错了,整个自动化流程就可能失败。
采用建议:从高价值、低歧义流程开始
微调 Amazon Nova 适合那些邮件量稳定、字段定义清晰、错误成本可衡量的场景。例如发票处理、采购请求、客服工单分流、物流通知解析、合规邮件归档等。
落地时可以按这个顺序推进:
- 先选一个字段集合固定的流程,不要一开始就覆盖所有邮件类型。
- 用真实历史邮件构建训练集和验证集,并清理个人敏感信息。
- 把相似字段、缺失字段、转发邮件作为重点样本。
- 上线前做字段级评估,而不是只看人工抽样感觉。
- 上线后保留人工复核通道,把错误样本回流到下一轮训练。
微调的边界也要说清楚:它不能替代数据治理,也不能保证模型永远不犯错。它更像是把通用模型变成一个熟悉你公司邮件习惯的抽取器。只要训练数据、评估指标和人工兜底设计得当,邮件自动化就能从“能演示”走向“能稳定跑”。