结果:自我验证的智能体
技术背景
在AI智能体应用中,一个常见痛点是智能体生成的输出看似完整,但实际存在引用不准确、覆盖不全等问题。传统做法依赖人工审查循环,效率低且成本高。Outcomes机制通过引入独立的评分器智能体,自动执行验证,解决了人工审查的瓶颈。它适用于需要高准确性和可验证性的场景,如研究报告、法律文书、技术文档等。主要限制是评分标准的质量直接影响验证效果,且需要额外的计算资源。
核心原理
Outcomes的核心原理是双智能体架构:写作智能体负责生成内容,评分器智能体在独立上下文窗口中根据预定义的评分标准对产物进行验证。评分器无法看到写作智能体的推理过程,只能基于产物和评分标准进行客观检查。每次写作智能体回合后,评分器重新阅读产物,返回通过或差距清单,写作智能体据此修订,循环直至达到迭代上限或通过。关键设计决策包括:评分标准必须具体可执行,要求评分器提供证据;评分器独立无状态,避免自我验证偏差;支持内联或文件形式的评分标准。
架构分析
系统架构包括三个核心组件:写作智能体(Research Analyst)、评分器智能体(由平台自动创建)和会话管理。写作智能体配置了工具集(web_search、web_fetch、read、write),负责起草简报。评分器智能体在每次写作后启动,使用相同模型和工具,根据评分标准检查产物。会话通过define_outcome事件启动,包含交付物描述和评分标准。数据流为:写作智能体生成草稿→评分器评估→反馈给写作智能体→修订→再次评估,直至满足条件。设计思想强调评分标准与任务描述的分离,以及评分器的独立性,确保验证的客观性。
完整实战教程
智能体擅长产出*看起来*完成的东西。要求一份带引用的研究简报,你会得到一份整洁的文档,附有脚注。仔细看,通常还有改进空间:某个主题覆盖不足,引文偏离来源,引用依赖新闻稿而非原始文件。发现这些问题需要手动审查循环:你阅读输出,找出问题,再次提示。而你在这些轮次中说的多数反馈,本可以在智能体开始前就写下来。
Outcomes 在Claude托管智能体中为会话提供了第二个智能体,其唯一职责是检查。你编写一个评分标准,明确“完成”的样子以及如何验证,平台会在其自己的上下文窗口中提供一个评分器。评分器看不到写作智能体的推理过程,也不知道采取了哪些捷径。在每次写作智能体回合后,它会根据评分标准重新阅读产物,要么通过,要么返回按标准列出的差距清单。写作智能体进行修订,循环再次运行,直到达到你设定的上限。
在本指南中,你将看到该循环端到端运行。写作智能体起草一份关于电动汽车快速充电经济学的一页简报,并附上来自其来源的逐字引用。评分智能体独立获取每个引用的URL,搜索每个页面中的引用字符串,检查引用是否确实支持声明,并根据七项检查清单对覆盖度进行评分。你将看到它发现一个真实问题(引用了新闻稿展品,而需要的是10-K文件),并看到写作智能体去找到正确的文档。
你将学到什么
- 编写评分器可执行的评分标准
- 使用Outcome启动会话,让智能体朝着目标工作
- 跟踪评分与修订循环,阅读评分器的反馈
- 识别何时Outcomes是合适的工具
1. 设置环境
首先,让我们安装 SDK 并设置 Anthropic 客户端。define_outcome 事件和结果评估跨度类型属于 Managed Agents 测试版的一部分。
%%capture
%pip install -q anthropic python-dotenv
import os
import re
import time
import anthropic
from dotenv import load_dotenv
load_dotenv()
BETAS = ["managed-agents-2026-04-01"]
MODEL = os.environ.get("COOKBOOK_MODEL", "claude-sonnet-4-6")
client = anthropic.Anthropic()
2. 创建 writer 并启动会话
接下来,我们将创建 writer 代理并打开一个会话。writer 的系统提示要求它引用不超过六个来源,每个来源都附带简短的逐字引用,以便评分者能够有具体的内容进行核查。
env = client.beta.environments.create(
name="research-brief",
config={"type": "anthropic_cloud", "networking": {"type": "unrestricted"}},
)
writer = client.beta.agents.create(
name="Research Analyst",
model=MODEL,
system="""You are a research analyst. You write one-page business briefs.
Cite every factual claim with an inline footnote [n]. End the brief with a Sources section in this exact format, one entry per line:
[n] "verbatim quote from the page, 25 words or fewer" - Title - URL
Only cite pages you actually fetched and read. The quote must be copied character-for-character from the page. Cite no more than 6 sources total. Pick the strongest; do not pad. Save the brief to /mnt/session/outputs/brief.md.""",
tools=[
{
"type": "agent_toolset_20260401",
"configs": [
{"name": "web_search"},
{"name": "web_fetch"},
{"name": "read"},
{"name": "write"},
],
}
],
betas=BETAS,
)
session = client.beta.sessions.create(
agent={"type": "agent", "id": writer.id, "version": writer.version},
environment_id=env.id,
title="Brief: EV fast-charging unit economics",
betas=BETAS,
)
print(f"Session {session.id}")
3. 编写评分标准,让评分者能够据此行动
define_outcome 事件向会话提供两样东西:交付物描述(供写作者阅读)和评分标准(供评分者阅读)。在写作者每次回复后,平台会使用与写作者相同的模型和工具启动一个新的评分者,向其提供评分标准,并让其检查工件。评分者返回每个标准的判定结果,如果有任何一项未通过,解释会直接返回给写作者,供其进行下一轮修订。
评分标准是你控制评分者的唯一杠杆,其措辞决定了评分者是否真正检查任何内容。默认的失败模式是评分者批准所有内容。一个写着“检查简报是否涵盖需量电费”的评分标准会让评分者浏览简报,看到一段关于需量电费的段落,然后写一句话确认其存在。它可以在不打开任何源文件的情况下完成这一切。大多数初稿都会因此通过,循环永远不会运行。一个写着“打开简报,找到需量电费部分,并确认其陈述了每千瓦美元数或运营成本百分比”的评分标准会迫使评分者提供证据。这样的评分者才能识别出伪装成10-K报表的新闻稿。
每个评分标准中值得注意的几点:
| 原则 | 实践 |
|---|---|
| 使每个标准可检查 | 任务说“具名运营商经济性”。评分标准将其具体化:来自10-K或10-Q的GAAP净亏损,并引用sec.gov上的文件。评分标准应始终比任务更具体。 |
| 让评分者通过努力才能获得satisfied | 要求具体证据(获取的页面、追踪的公式、file:line引用),然后评分者才能通过任何项目。过于严格的评分者会多花你一轮循环。过于宽松的评分者会在坏版本仍然存在时结束循环。 |
| 描述目标,而非步骤 | 规定特定命令的评分标准在命令不可用时静默失败,而你关心的检查从未运行。定义什么算作证据;评分者拥有写作者的完整工具集,可以找到它。 |
| 预见写作者的捷径 | “不要通过镜像、转发或搜索片段来佐证。”没有这一行,一个失效的来源会被替换为抓取页面,评分者会通过它。 |
| 规定反馈格式 | 评分者的解释是写作者唯一的信号。要求一行记分板,然后每个失败项一个要点,说明问题所在和该怎么做。 |
| 告诉评分者忽略什么 | 没有禁止清单,评分者会在风格细节、先前存在的问题和范围蔓延上摇摆不定。明确说明哪些是越界的,并让评分者在提出每个发现之前自我检查。 |
还没有评分标准? 给Claude一个已知良好的工件示例,让它分析其优点,然后将分析转化为标准。这种中间地带比从空白页编写标准更可靠。
以下是我们简报的任务和评分标准。请并排阅读,注意评分标准要具体得多。它添加的每个任务中没有的细节都是评分者可以实际运行的检查。
TASK = """Write a brief on the unit economics of public DC fast charging in the United States.
The brief should cover:
1. Capex range
2. Demand charges
3. Utilization breakeven
4. Subsidy programs
5. Named-operator economics
6. A contrarian or skeptical source
7. Hardware vs install cost split
"""
RUBRIC = """
You are reviewing a research brief at /mnt/session/outputs/brief.md against a coverage checklist and verifying its citations. The writer was told the seven topics to cover; this rubric defines what counts as sufficient coverage for each topic, and how to verify citations.
COVERAGE CHECKLIST. Each item has a specific area:
1. Capex range: a dollar range for installed cost per DC fast-charging stall or station.
2. Demand charges: quantified impact on opex (a $/kW figure or a % of operating cost).
3. Utilization breakeven: a breakeven or target utilization threshold (% or kWh/day).
4. Subsidy programs: NEVI or another public funding program, named.
5. Named operator: the GAAP net income or net loss from a specific public charging operator's most recent 10-K or 10-Q, and the citation for it must be the SEC filing itself (sec.gov), not a press release, earnings-call recap, or news article.
6. Contrarian source: at least one cited source whose thesis is that the economics are unfavorable or structurally challenged.
7. Cost split: a hardware vs soft-cost (install, permitting, grid) breakdown or ratio.
CITATION CHECK. For every [n] entry in the Sources section:
a. LIVE: Fetch the URL with web_fetch. Mark LIVE only if web_fetch returns the readable page directly. Mark DEAD if 404, parked, login-walled, paywalled, returns a bot-block/403, or renders only via JavaScript. Do NOT corroborate via mirrors, reposts, or search snippets; the cited URL itself must fetch.
b. VERBATIM: Search the fetched page for the quoted string. Mark QUOTE_MATCH if the exact string appears (treat curly vs straight quotes as equivalent); NOT_FOUND otherwise.
c. SUPPORTS CLAIM: Mark SUPPORTS_CLAIM if the quoted passage actually backs the claim it's cited on in the brief; UNSUPPORTED if it's tangential, contradicts the claim, or is just a general statement of fact.
OUTPUT FORMAT:
Line 1: Coverage N/7. Citations M/K verified.
Then, for each failed item in the coverage checklist, create a new bullet, name the item and say what specific bar it failed in one sentence max per bullet. For example: "Item 3 Utilization breakeven - MISSING. <what's missing>".
Then, for each failed citation, create a new bullet with the format: "[n] domain - REASON. <what's wrong and what to do>". One sentence max per bullet. For example: "[3] evgo.com - DEAD. The URL returns a 403 error and appears to be behind a bot block. No mirrors or reposts; the cited URL itself must fetch."
"""
client.beta.sessions.events.send(
session.id,
betas=BETAS,
events=[
{
"type": "user.define_outcome",
"description": TASK,
"rubric": {"type": "text", "content": RUBRIC},
"max_iterations": 5,
},
],
)
关于上述调用的几点说明:
- 描述和评分标准各有分工。 描述告诉写作者要做什么。评分标准告诉评分者如何检查。它们应该就产物的位置和格式达成一致;如果它们相互矛盾(例如描述要求内联输出,而评分标准对
/mnt/session/outputs/下的文件进行评分),循环将返回failed,而不是反复尝试。 max_iterations默认为 3(最大 20)。 我们将其设置为 5,因为此评分标准很严格,写作者需要空间。如果每次运行都达到上限,且评分者每次都发现相同类型的问题,那么写作者无法根据反馈采取行动,你将为不收敛的迭代付费。- 内联评分标准与文件评分标准。 对于笔记本来说,内联文本就足够了。在生产环境中,你可以通过 Files API 上传一次评分标准,并传递
rubric: {"type": "file", "file_id": ...},这样它就可以在会话之间复用,并且像代码一样可审查。
为什么不直接把评分标准放在系统提示中? 你可以这样做,这有助于写作者更好地瞄准目标。但是,知道标准的写作者仍然是在给自己的作品打分。只要它认为自己通过了,它就会说通过,并且不会回去重新获取它已经引用的 URL,也不会注意到它记住的引用与页面上的引用略有不同。评分者别无选择,只能进行这些检查。它以全新的上下文窗口开始,只有评分标准和产物,平台不会让循环继续,直到它对每个标准都给出裁决。无论你如何精心编写单个提示,你都无法从单个提示中获得这种分离。
4. 观察评审循环
让我们流式处理事件,并在每个阶段发生时渲染它。当写作者完成草稿时,我们将打印一个横幅,并在每次评估后显示评分者的反馈。
下面是循环中使用的两个显示辅助函数:banner 绘制带标签的分隔线,render_feedback 则去除服务器在每个评分器解释周围包裹的样板内容。
HR = "━" * 46
def banner(label, tag=""):
print(f"\n{HR}\n{label} {tag}".rstrip())
def render_feedback(fb: str):
# Strip the server's per-criterion wrapper and trailer.
s = re.sub(
r"^An independent grader found.*?:\n\n- .*?\((?:partially |not )?met\): ",
"",
fb,
count=1,
flags=re.S,
)
s = re.sub(r"\n\nPlease revise your work.*$", "", s, flags=re.S)
print(s)
TERMINAL = {"satisfied", "max_iterations_reached", "failed", "interrupted"}
t0, res, iters = time.time(), None, 0
n_search, last_len = 0, 0
with client.beta.sessions.events.stream(session.id, betas=BETAS) as stream:
for ev in stream:
if ev.type == "agent.tool_use":
if ev.name in ("web_search", "web_fetch"):
n_search += 1
if ev.name == "write" and ev.input["file_path"].endswith("brief.md"):
last_len = len(ev.input["content"])
elif ev.type == "span.outcome_evaluation_start":
banner("writer · " + ("draft" if iters == 0 else f"revision {iters}"))
print(f"searched/fetched {n_search}× · wrote brief.md ({last_len:,} chars)")
n_search = 0
elif ev.type == "span.outcome_evaluation_end":
res = ev.result
banner(
f"grader · pass {iters}",
"✓ satisfied" if res == "satisfied" else "⟳ needs_revision",
)
render_feedback(ev.explanation)
iters += 1
if res in TERMINAL:
break
m, s = divmod(int(time.time() - t0), 60)
print(f"\ndone: {res} after {iters} pass{'es' if iters != 1 else ''} · {m}m {s:02d}s")
刚才发生了什么
循环进行了三轮评分。
第一稿覆盖了七个项目中的五个。评分者因两个遗漏将其退回:需求费用仅做了定性描述,没有给出每千瓦美元的数字;指定运营商部分引用了第三方新闻文章,而非SEC文件。撰写者补充了$20/kW的数字,并前往sec.gov查找EVgo的2024财年净亏损。
第二轮通过了七个中的六个。撰写者引用的sec.gov文档是8-K表的附件99.1,即提交的盈利新闻稿,而非评分标准要求的10-K或10-Q。评分者读取了URL,识别其为新闻稿附件,并正是基于这一区别将其拒绝。第三轮,撰写者在EDGAR上找到了EVgo实际的10-K,评分者予以通过:7/7覆盖,6/6引用有效,引文匹配且支持充分。
两次拒绝都是因为评分标准划定了评分者可以检查的界限。“每千瓦美元数字或运营成本百分比”和“SEC文件本身,而非新闻稿”是评分标准制定者做出的决定;任务本身(“覆盖需求费用”,“指定运营商经济性”)本会让两者都通过。第二次拒绝值得研究。撰写者找到了一个sec.gov URL,表面上满足了任务要求,但评分者仍然将其驳回,因为评分标准区分了8-K新闻稿附件和10-K。如果没有这一区别,循环会提前一轮结束,简报质量更差。
注意,上述追踪来自原始cookbook运行。如果你自己运行,结果可能会有所不同。
5. 阅读最终简报
最后,让我们拉取作者生成的 brief.md 的最新版本,看看它涵盖了哪些内容以及引用了什么。如果你想阅读完整的散文,可以直接打印 content 本身。
# Reconstruct the final brief from the event log: full content on `write`,
# then apply each `edit` (old_string -> new_string) in order.
content = ""
for ev in client.beta.sessions.events.list(session.id, limit=1000, betas=BETAS):
if ev.type != "agent.tool_use" or "brief.md" not in str(ev.input.get("file_path", "")):
continue
if ev.name == "write":
content = ev.input["content"]
elif ev.name == "edit":
content = content.replace(ev.input["old_string"], ev.input["new_string"], 1)
# Show the structure and sources rather than the full prose.
for line in content.splitlines():
if line.startswith(("#", "[")):
print(line)
你学到了什么
- 当你能写出好的标准时,结果就适用。 注重细节和全面覆盖的任务最合适;如果评分标准明确了标准,主观质量也可以。
- 评分标准必须让评分者产生证据。 否则,它只会批准所展示的内容。
- 评分者是独立且无状态的。 它在自己的上下文窗口中运行,因此编写者无法说服它,每次迭代都会重新检查整个工件。
- 一次一个结果,但你可以将它们串联起来。 一旦循环终止,会话恢复为对话模式,下一个
user.define_outcome将开始新的循环。
更多信息,请参阅 Outcomes 文档。
常见问题
常见错误包括:评分标准过于宽松导致循环不运行;评分标准与任务描述矛盾导致循环失败;评分器无法获取URL(如403、登录墙)导致引用验证失败。排查方法:检查评分标准是否要求具体证据;确保任务描述和评分标准在产物位置和格式上一致;使用web_fetch测试URL可访问性。生产环境优化:使用文件形式的评分标准以便复用和审查;调整max_iterations避免不收敛;监控评分器反馈质量,迭代优化评分标准。
企业应用扩展
Outcomes可扩展至多步骤任务,通过串联多个define_outcome实现复杂工作流。未来可结合强化学习优化评分标准,或引入多评分器投票机制提高验证可靠性。此外,可将Outcomes集成到CI/CD管道中,实现自动化文档验证。对于需要高可靠性的领域(如医疗、金融),可进一步定制评分标准,确保合规性。
总结
本文介绍Claude托管智能体中的Outcomes功能,通过独立的评分器智能体对写作智能体的产出进行自动验证。文章详细展示了如何编写可执行的评分标准、启动会话、观察评分与修订循环,并分析了循环中发现的真实问题(如引用新闻稿而非10-K文件)。读者将学会如何利用Outcomes提升智能体输出的准确性和可靠性,适用于需要高质量、可验证产出的AI工程实践。
- --
内容来源与验证
- 内容等级:PREMIUM_A
- 质量等级:A
- 难度:中级
- 验证等级:V1
- 原文作者:markn-ant, gaganb-ant
- 原文日期:2026-05-03
- 来源许可:MIT
- 来源版本:
024a50465978f288fac927dbffeb52c798f5fcd3 - 查看原文
站内还有更多完整实战教程
加入会员,解锁全部教程、完整代码与持续更新内容。
加入会员