课程大纲 › 领域 4:提示工程与结构化输出
4.4 实现提升抽取质量的校验、重试与反馈循环
タスクステートメント4.4:抽出品質のための検証、リトライ、フィードバックループを実装する
官方大纲原文(日文):知识 4 条 · 技能 4 条
需要掌握的知识
- エラーフィードバック付きリトライ:リトライ時に具体的な検証エラーをプロンプトに追加し、モデルを修正へと導くこと
- リトライの限界:必要な情報がそもそもソース文書に存在しない場合(形式や構造のエラーの場合とは異なり)、リトライは効果がないこと
- フィードバックループの設計:どのコード構造が指摘事項を引き起こすかを追跡し(detected_patternフィールド)、却下パターンの体系的な分析を可能にすること
- 意味的な検証エラー(値の合計が合わない、フィールドの配置が誤っている)とスキーマ構文エラー(ツール使用によって排除される)の違い
需要能做到的事
- モデルが自己修正できるように、元の文書、失敗した抽出結果、具体的な検証エラーを含むフォローアップリクエストを実装する
- リトライが効果を持たない場合(例:提供されていない外部文書にしか情報が存在しない)と成功する場合(形式の不一致、構造的な出力エラー)を見極める
- 構造化された指摘事項にdetected_patternフィールドを追加し、開発者が指摘事項を却下した際の誤検知パターンの分析を可能にする
- 自己修正のための検証フローを設計する:不一致を検出するために"stated_total"とあわせて"calculated_total"を抽出し、整合しないソースデータに対して"conflict_detected"ブール値を追加する
课程讲解 · AI 草稿,未经人工审核 以官方文档为准
这一条考什么
这一条考你会不会给抽取流水线加上"校验 → 带错误反馈的重试 → 记录模式"的闭环。你要知道哪些错误重试能修好(格式不对、结构不对),哪些重试修不好(信息根本不在源文档里)。你还要会设计 calculated_total / stated_total、conflict_detected、detected_pattern 这类字段,让模型能自我纠正,开发者也能系统地分析误报。
核心概念
两类错误:语法错误与语义错误
- schema 语法错误:JSON 不合法、类型不对、缺必填字段。用 tool use + JSON Schema(尤其是 strict 模式)基本就能消除,见 4.3。
- 语义校验错误:JSON 完全合法,但内容有问题,比如明细之和不等于总额、发票日期晚于付款日期、把供应商地址填进了客户地址、数量字段填的是 "2-3" 这种区间字符串。这类错误必须由你的应用代码做业务校验才能发现。
所以校验层通常包括:JSON Schema 校验(兜底),加上业务规则(求和、日期先后、取值范围、跨字段一致性)。
带错误反馈的重试
最常见的错误做法是校验失败后把同一个请求原样再发一遍。请求内容一样,模型大概率会犯同样的错。正确做法是发一个后续请求,里面包含三样东西:
- 原始文档;
- 上一次失败的抽取结果;
- 具体的校验错误,比如"
line_items金额合计为 1180.00,但total为 1240.00,请核对是否漏抽了明细行"。
具体的错误信息给了模型第一次没有的信息,它才知道该往哪里改。
def build_retry_messages(doc, failed_extraction, errors):
return [
{"role": "user", "content": f"请从以下文档抽取发票数据:\n{doc}"},
{"role": "assistant", "content": f"上次抽取结果:{failed_extraction}"},
{"role": "user", "content":
"上述结果未通过校验:\n- " + "\n- ".join(errors) +
"\n请对照原文逐项核对后重新调用 extract_invoice。若原文本身不一致,请如实填写并将 conflict_detected 设为 true。"}
]重试的边界:信息不存在时重试无效
判断标准很简单:模型需要的信息在不在输入里?
- 重试有效:格式不一致("1,234" 应该写成 1234)、结构错误(该是扁平数组却输出了嵌套对象)、漏抽了文档里确实有的内容、字段放错位置。
- 重试无效:信息只存在于没有提供给模型的外部文档里。比如论文里只写了 "Smith et al.",完整作者列表在另一份没给出的文件中;或者合同引用了"附件 B",但附件没有附上。这时重试多少次都只会得到同样的结果,更糟的是可能逼模型编出一个。正确的处理是填 null、标注缺失,或者把那份外部文档补进输入。
考试里经常把这两类情况混在一起,让你挑出"哪一种重试没有意义"。
让模型自我校验:calculated_total 与 conflict_detected
在 schema 里同时要求两个字段:
stated_total:文档上写的总额,原样抄下;calculated_total:根据抽出来的明细行算出的总额。
两者不一致,可能是漏抽了明细(重试能修),也可能是源文档本身就算错了(重试修不好)。所以再加一个 conflict_detected: boolean,让模型在原文自相矛盾时如实标记,不要为了凑平而篡改数字。下游代码根据这个标记决定是自动通过、重试,还是转人工。
{
"stated_total": 1240.00,
"calculated_total": 1180.00,
"conflict_detected": true,
"conflict_note": "明细合计 1180.00,票面总额 1240.00,差额 60.00 可能为未列出的运费"
}反馈循环:用 detected_pattern 追踪误报
在代码审查等场景里,每条发现除了 category(security、style 之类)之外,还要加一个 detected_pattern 字段,记录是哪种具体的代码结构触发了这条发现,例如 single_letter_loop_var、sql_string_concat、broad_except。
开发者驳回(dismiss)发现时,你就能按 detected_pattern 统计驳回率。style 类驳回率高这个信息太粗,"single_letter_loop_var 驳回率 92%"才能落实到行动上:把它加进不报告的规则,或者写一个 few-shot 反例。光有宽泛的 category,没法做这种系统性分析。
正确做法与反模式
- 正确:校验失败后,把原文 + 失败结果 + 具体错误一起发给模型重试。反模式:原样重发同一请求,或者只说一句"有错,请重试"。
- 正确:重试前先判断信息是否在输入中,不在就填 null 或补充输入。反模式:对外部文档才有的信息反复重试。
- 正确:同时抽取
stated_total和calculated_total,并用conflict_detected如实标记源数据矛盾。反模式:让后处理代码直接用明细重算总额去覆盖原值,把源文档里的问题掩盖掉。 - 正确:给发现加
detected_pattern,按模式分析驳回率。反模式:只看宽泛的类别驳回率,或者只加一个模型自评的confidence。 - 正确:重试设置次数上限,超限就转人工。反模式:无限重试,或者一次失败就丢弃整条记录。
常见陷阱
- "原样重试最多 3 次":没加新信息的重试只会重复同样的错误。正确选项一定带上具体的校验错误。
- "temperature 设为 0":输出更确定不等于更正确,还可能稳定地重复同一个错误。
- "后处理自动修正(用明细重算总额)":看起来省事,但会掩盖源文档矛盾和抽取遗漏,并且丢失证据。正确做法是保留两个值,并标记冲突。
- 把"信息在外部文档"误判成可重试的错误:题目会把"et al."、"见附件"这类情况和格式错误混着列出来。只有信息缺失这一项是重试无效的。
- 用 `model_confidence` 代替 `detected_pattern`:自评置信度说明不了为什么误报。要分析误报模式,必须记录具体的触发结构。
- 以为 strict schema 能发现求和错误:schema 只管结构,语义校验得自己写。
实操练习
目标:实现一个"抽取 → 业务校验 → 带错误反馈重试"的循环,并观察 conflict_detected 的用法。
- 准备一份明细合计与总额故意不一致的发票文本。
- 定义带
line_items、stated_total、calculated_total、conflict_detected的抽取工具。 - 第一次抽取后,用 Python 校验
calculated_total是否等于明细之和。 - 校验失败就带上具体错误重试,最多 2 次;最终仍有冲突就输出"转人工"。
import anthropic
client = anthropic.Anthropic()
tool = {
"name": "extract_invoice",
"description": "抽取发票。stated_total 抄录票面总额;calculated_total 为你抽取的明细金额之和;原文自相矛盾时 conflict_detected=true,不要篡改数字。",
"input_schema": {
"type": "object",
"properties": {
"line_items": {"type": "array", "items": {"type": "object",
"properties": {"desc": {"type": "string"}, "amount": {"type": "number"}},
"required": ["desc", "amount"]}},
"stated_total": {"type": "number"},
"calculated_total": {"type": "number"},
"conflict_detected": {"type": "boolean"},
},
"required": ["line_items", "stated_total", "calculated_total", "conflict_detected"],
},
}
doc = "Widget A $500.00\nWidget B $680.00\nTOTAL DUE $1,240.00"
def extract(messages):
r = client.messages.create(model="claude-sonnet-4-5", max_tokens=1024, tools=[tool],
tool_choice={"type": "tool", "name": "extract_invoice"}, messages=messages)
return next(b for b in r.content if b.type == "tool_use")
def validate(d):
errs = []
s = round(sum(i["amount"] for i in d["line_items"]), 2)
if abs(s - d["calculated_total"]) > 0.01:
errs.append(f"calculated_total={d['calculated_total']} 与明细合计 {s} 不一致")
if abs(d["calculated_total"] - d["stated_total"]) > 0.01 and not d["conflict_detected"]:
errs.append("stated_total 与 calculated_total 不同,但 conflict_detected 为 false")
return errs
messages = [{"role": "user", "content": f"抽取这张发票:\n{doc}"}]
for attempt in range(3):
block = extract(messages)
data, errs = block.input, validate(block.input)
print(attempt, data, errs)
if not errs:
break
messages += [
{"role": "assistant", "content": [block.model_dump(exclude_none=True)]},
{"role": "user", "content": [{"type": "tool_result", "tool_use_id": block.id,
"content": "校验失败:\n- " + "\n- ".join(errs) + "\n请对照原文修正后重新调用。", "is_error": True}]},
]
print("转人工审核" if data["conflict_detected"] else "自动通过")预期结果:模型应该抽出两条明细,calculated_total=1180、stated_total=1240、conflict_detected=true,最后打印"转人工审核"。如果第一次把 conflict_detected 填成了 false,第二次带着错误信息重试时应该会改过来。
自测清单
- 语义校验错误和 schema 语法错误有什么区别?各自靠什么手段消除?
- 一次有效的错误反馈重试,请求里应该包含哪三样东西?
- 举一个重试有效的例子和一个重试无效的例子,说明判断依据。
stated_total、calculated_total、conflict_detected三者配合解决了什么问题?为什么不直接用重算值覆盖原值?detected_pattern和issue_category、confidence相比,多提供了什么分析能力?
对应题目(5)
题目归类由 AI 判断,仅供参考。
练习这 5 道题