章节08 / 14
- 01AI Harness 不是测试脚本,而是 AI 系统的质量控制台
- 02先写任务协议,再谈评测指标
- 03Golden Dataset:把“感觉不错”变成可回归样例
- 04评测不是一个分数:判分器、断言和人工复核怎么组合
- 05结构化输出 Harness:先挡住形状错误,再处理业务错误
- 06Tool Harness:模型只能提议动作,执行权必须被隔离
- 07RAG Harness:先评检索,再评回答
- 08Agent Harness:把多步智能体变成可暂停、可恢复、可审计的状态机
- 09红队与安全 Harness:把提示注入当成常规回归项
- 10观测 Harness:trace 里该看见什么,不该记录什么
- 11CI 回归门禁:让 Prompt、模型和检索改动都要过关
- 12线上反馈回流:用户反馈怎样变成下一版样例
- 13模型路由与发布 Harness:灰度、回滚和成本风险一起看
- 14综合项目:交付一个 AI Harness Engineering 蓝图
本文目录12 节
Agent Harness:把多步智能体变成可暂停、可恢复、可审计的状态机
本指南介绍如何为多步 Agent 设计状态机 Harness,实现执行预算控制、步骤级 Trace 记录、人审节点拦截以及基于状态快照的失败恢复与回放调试。
- 完成前序单元的练习或理解对应 harness 概念
- 具备基础的 Python 异步编程与面向对象设计能力
- 构建一个支持暂停、恢复与状态导出的 Agent 状态机 Harness
- 生成包含完整执行路径与 Token 消耗的步骤 Trace 日志
- 制定一份针对 Agent 中断或执行失败的 Runbook 恢复方案
在传统的单次 Prompt-Response 交互中,测试与评测相对直接。然而,当引入多步智能体(Agent)或 ReAct(Reasoning and Acting)循环时,智能体开始自主选择工具、拆解任务并进行多轮迭代。这种“链式推理”带来了极大的不可预测性:一个在第 5 步出现的幻觉,可能会毁掉前 4 步已经取得的正确结果。如果不加以工程干预,你将面临一个“黑盒”:线上出现报错时无法复现,API 账单因死循环而飙升,且无法在不重跑整个流程的情况下对其中某一步进行隔离评测。
要解决这些痛点,必须为 Agent 构建一个专属的 Harness(测试与运行容器),将其从“任意执行的黑盒代码”改造成“可暂停、可恢复、可审计的显式状态机”。
传统 Agent 的不可控性与状态化改造思路
大多数初期的 Agent 实现直接将 while 循环、LLM 调用和工具执行(Tool Call)硬编码在一个函数中。这种设计存在两个致命缺陷:
- 状态无法持久化:如果执行在第 4 步因为网络抖动或工具限流而中断,你必须从头开始运行,这不仅浪费 Token,还可能因为环境变化(例如数据库已写入部分数据)导致副作用叠加。
- 无法进行单步评测:你无法单独评估“给定前 3 步的上下文,第 4 步的 Tool Selection 是否正确”,因为你无法直接注入前 3 步的虚拟状态。
状态化改造的核心是将 执行器(Executor) 与 状态存储(State Store) 彻底解耦。Agent 的每一次思考、每一次工具调用,都必须定义为状态机的一次状态转移(Transition)。每一个状态转移都必须产生一份不可变的事件日志。这一思路与 Redux 或 Event Sourcing 类似,但在 AI 场景下,我们还需要额外管理 Token 预算和人类干预节点。
设计一个可暂停与可恢复的 Agent 状态机
我们将 Agent 抽象为一个有限状态机(FSM)。其核心数据结构包含:
session_id: 唯一标识一次任务流。current_step: 当前执行步骤的序号。status: 状态,包括PENDING(等待执行)、RUNNING(执行中)、PAUSED(暂停,如等待人审)、COMPLETED(已完成)、FAILED(失败)。history: 包含之前的思考(Thoughts)、工具调用(Tool Calls)和工具返回结果(Tool Outputs)的列表。context: 共享的任务上下文变量。
以下是该状态机 Harness 的核心 Python 实现架构:
import uuid
from typing import Dict, Any, List, Optional
from pydantic import BaseModel, Field
class ToolCall(BaseModel):
tool_name: str
arguments: Dict[str, Any]
output: Optional[str] = None
status: str = "pending" # pending, success, failed
class AgentStep(BaseModel):
step_number: int
thought: str
tool_calls: List[ToolCall] = []
timestamp: float
class AgentState(BaseModel):
session_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
status: str = "PENDING" # PENDING, RUNNING, PAUSED, COMPLETED, FAILED
current_step: int = 0
history: List[AgentStep] = []
context: Dict[str, Any] = {}
error_message: Optional[str] = None
# 运行预算控制
max_steps: int = 10
accumulated_cost: float = 0.0
max_cost_usd: float = 0.50
class AgentStateMachineHarness:
def __init__(self, state: AgentState):
self.state = state
def load_state(self, serialized_state: dict):
self.state = AgentState(**serialized_state)
def dump_state(self) -> dict:
return self.state.model_dump()
def transition_to(self, next_status: str):
valid_transitions = {
"PENDING": ["RUNNING"],
"RUNNING": ["PAUSED", "COMPLETED", "FAILED"],
"PAUSED": ["RUNNING", "FAILED"],
"FAILED": ["PENDING", "RUNNING"], # 允许从失败状态重启或恢复
"COMPLETED": []
}
if next_status not in valid_transitions[self.state.status]:
raise ValueError(f"Invalid transition from {self.state.status} to {next_status}")
self.state.status = next_status
通过此 Harness,你可以随时使用 dump_state() 将当前 Agent 的全部记忆、已消耗成本和中间执行结果序列化并保存到 Redis 或数据库中。一旦执行中断,只需从数据库读取数据,调用 load_state(),即可无缝还原现场并继续往下执行。
为 Agent 引入执行预算与死信队列
多步 Agent 最容易出现的问题是“陷入推理死循环”——由于 Prompt 微弱的偏差或工具返回了非预期的格式,Agent 不断重复尝试相同的工具调用,导致 Token 消耗激增。如果 Agent 的单次运行成本或嵌套循环深度超过了预设阈值,必须立即中断执行并将其状态 dump 到持久化存储中,否则失控的 LLM 可能会在几分钟内耗尽你的 API 额度。
我们在 Harness 的每一步执行前加入“预算网关”:
class BudgetExceededException(Exception):
pass
class AgentStateMachineHarness(AgentStateMachineHarness):
def enforce_budget(self, estimated_step_cost: float):
# 检查步骤数超限
if self.state.current_step >= self.state.max_steps:
self.transition_to("FAILED")
self.state.error_message = f"Max steps ({self.state.max_steps}) exceeded."
self.send_to_dead_letter_queue("MAX_STEPS_EXCEEDED")
raise BudgetExceededException(self.state.error_message)
# 检查资金预算超限
new_cost = self.state.accumulated_cost + estimated_step_cost
if new_cost > self.state.max_cost_usd:
self.transition_to("FAILED")
self.state.error_message = f"Max cost budget ({self.state.max_cost_usd} USD) exceeded."
self.send_to_dead_letter_queue("BUDGET_EXCEEDED")
raise BudgetExceededException(self.state.error_message)
self.state.accumulated_cost = new_cost
def send_to_dead_letter_queue(self, reason: str):
# 将当前状态快照序列化后发送至死信队列(DLQ),供开发人员离线分析
dead_letter_payload = {
"reason": reason,
"snapshot": self.dump_state()
}
# 实际生产中此处对接消息队列,如 RabbitMQ, AWS SQS 或数据库归档表
print(f"[DLQ ALERT] Session {self.state.session_id} sent to DLQ. Reason: {reason}")
这种设计不仅防止了资金损失,同时自动收集了宝贵的“边界案例”(Edge Cases)。每一个进入死信队列的 Agent 状态快照,都是最真实的坏案例(Bad Cases),可以直接转化为评测集的数据源。
步骤日志与 Trace 设计:让每一次决策都有迹可循
要对 Agent 进行持续评估,仅仅记录输入和最终输出是远远不够的。根据 LangSmith evaluation concepts 的规范,每一次评测实验(Experiment)都应该基于详尽的运行迹(Trace)进行。一个标准的 Agent Trace 应该包含完整的决策链(Chain of Thought)、工具选择参数及其实际返回。
我们在 Harness 中定义以下 Trace 输出格式。当 Agent 执行每一个 Step 时,它必须向日志收集器或 APM 系统广播符合该格式的结构化数据:
{
"trace_id": "trace_882a8f3b_901c",
"session_id": "session_abc123",
"step_number": 3,
"inputs": {
"query": "查询用户 2026 年 Q1 的消费总额,并计算折合美元"
},
"thought": "我已经获取了人民币消费总额为 7000 元。现在我需要调用货币转换工具将其转换为美元。当前汇率需要通过实时 API 获取。",
"tool_calls": [
{
"tool_name": "get_exchange_rate",
"arguments": {
"from_currency": "CNY",
"to_currency": "USD"
},
"output": "{\"rate\": 0.14}",
"status": "success"
}
],
"outputs": {
"intermediate_result": "980 USD"
},
"metrics": {
"step_latency_seconds": 1.45,
"prompt_tokens": 850,
"completion_tokens": 120,
"step_cost_usd": 0.015
}
}
在评估环节,这些 Trace 数据可以直接对接类似 Azure Foundry 评测(Evaluate generative AI apps)的内置质量 evaluator。Azure Foundry 能够解析这种结构化的 Trace,自动评估其中工具调用的准确率(Tool Call Accuracy)以及检索步骤的 Groundedness。如果 Trace 格式不标准,评测平台就只能把整个 Agent 当作黑盒,从而无法定位具体是“检索不准”还是“模型推理能力不足”导致了最终失败。
在关键节点引入人工审核(Human-in-the-Loop)
在设计人审节点时,如果步骤涉及外部资金划拨或核心数据库写操作,应该将状态机挂起并向审批队列发送阻断式事件,除非获得了显式的人工授权,否则不允许 Agent 自动尝试绕过。
OpenAI 在其 Evaluation best practices 中强调,人工审核(Human Review)和减少 Judge 偏差对于复杂生成式应用的安全性至关重要。因此,我们在 Harness 中将“人审”设计为一个一等公民(First-class Citizen)状态。状态转移到 PAUSED,并暴露一个回调接口供人工判定:
class AgentStateMachineHarness(AgentStateMachineHarness):
def request_human_approval(self, action_details: dict) -> str:
# 将状态挂起
self.transition_to("PAUSED")
# 保存需要审核的动作详情到上下文
self.state.context["pending_approval_action"] = action_details
# 实际生产中,这里会向 Web 前端或 Slack 发送一条通知
return f"Session {self.state.session_id} paused. Awaiting approval for: {action_details['tool_name']}"
def resume_with_approval(self, approved: bool, feedback: Optional[str] = None):
if self.state.status != "PAUSED":
raise ValueError("Can only resume from PAUSED status")
if approved:
# 清理审核挂载点,恢复运行状态
self.state.context.pop("pending_approval_action", None)
self.transition_to("RUNNING")
print("Approval granted. Resuming Agent execution...")
else:
self.transition_to("FAILED")
self.state.error_message = f"Rejected by human auditor. Feedback: {feedback}"
print(f"Execution rejected. Feedback: {feedback}")
通过此 Harness 控制结构,人审不仅仅是一个“开关”,人审产生的 feedback 和人工修改后的“正确 Tool 参数”还可以被捕获并保存下来。根据 OpenAI 的指导原则,这些被纠正的数据是构建**高保真黄金数据集(Golden Dataset)**的无价之源,可用于后续的 Agent 微调(Fine-tuning)和断言测试。
利用 Trace 进行回放调试与回归评测
在传统的软件测试中,我们习惯于编写单元测试。但在 Agent 开发中,Prompt 的微调经常导致不可预测的侧面效应(Side Effects)。Anthropic Evaluation Tool 允许开发者在修改 Prompt 之后,一键重跑整个测试集(Eval Suite),在控制台中直接对比前后的表现变化。为了支撑这种能力,Agent Harness 必须提供回放能力(Replay)。
如果我们在 Trace 日志中发现 Agent 在第 N 步出现了幻觉或调用了错误的 Tool,可以提取该步骤的快照并利用 LangSmith 等平台重新跑评测,因为只有隔离出错误的上下文,才能准确判定是 Prompt 缺陷还是 Model 升级导致的退化。
例如,你可以编写一个 Replay 脚本,它不需要真的去调用前 N-1 步那些耗时且可能产生外部副作用的工具,而是直接“快照注入”:
def replay_agent_at_step(failed_session_state: dict, test_prompt_patch: str) -> dict:
"""
加载失败的 Agent 状态快照,替换引发错误的 Prompt,并从出错的步骤开始单步重试。
"""
harness = AgentStateMachineHarness(AgentState(**failed_session_state))
# 1. 验证状态是否可恢复(应该处于 FAILED 或 PAUSED 状态)
if harness.state.status not in ["FAILED", "PAUSED"]:
print("Warning: Replaying a running or completed session.")
# 2. 注入修改后的 Prompt/系统指令(模拟 Patch 操作)
harness.state.context["system_instruction_override"] = test_prompt_patch
# 3. 将状态重置为 RUNNING,准备单步执行
harness.state.status = "RUNNING"
harness.state.error_message = None
print(f"Replaying session {harness.state.session_id} from step {harness.state.current_step}...")
return harness.dump_state()
这种机制允许测试工程师直接针对“第 4 步的 Model 输出”进行局部回归测试。你可以编写断言,确保在注入新 Prompt 之后,Agent 产生的 tool_calls 参数符合预期,而无需重新等待前 3 步的漫长执行。
运行 Runbook:故障恢复与状态重构实操演练
本小节提供一个可操作的 Runbook 样例,用于演示在线上 Agent 因环境或逻辑崩溃时,如何通过本 Harness 提供的机制进行手动状态重构与恢复。
故障场景
Agent 执行一项包含多步的“客户对账与转账”任务。在第 3 步,Agent 试图调用 get_bank_balance 工具,但由于银行网关暂时性服务中断,该工具抛出 HTTP 503 异常。Agent 状态变更为 FAILED。根据流程,此时不能重新运行,因为第 2 步已经完成了一部分数据拉取和临时锁定(Locking)操作,重新运行会导致死锁。
故障恢复 Runbook
第一步:检索故障快照
从死信队列或日志存储中检索 Session ID 为 session_tx_99281 的最新状态快照:
{
"session_id": "session_tx_99281",
"status": "FAILED",
"current_step": 3,
"history": [
{
"step_number": 1,
"thought": "先获取客户 ID",
"tool_calls": [{"tool_name": "get_user_id", "arguments": {"name": "Alice"}, "output": "usr_001", "status": "success"}],
"timestamp": 1716900000.0
},
{
"step_number": 2,
"thought": "锁定该客户的对账账户",
"tool_calls": [{"tool_name": "lock_account", "arguments": {"account_id": "acc_99"}, "output": "locked_success", "status": "success"}],
"timestamp": 1716900005.0
},
{
"step_number": 3,
"thought": "获取当前余额",
"tool_calls": [{"tool_name": "get_bank_balance", "arguments": {"account_id": "acc_99"}, "output": null, "status": "failed"}],
"timestamp": 1716900010.0
}
],
"context": {
"user_id": "usr_001",
"account_id": "acc_99"
},
"error_message": "HTTP 503 Service Unavailable on get_bank_balance",
"accumulated_cost": 0.045
}
第二步:状态修复与人工干预
由于银行接口依然不确定是否完全稳定,系统管理员决定通过 Runbook 脚本手动垫付/修正该步骤的值(例如通过备用查询通道查得该账户余额为 50000 元),并将该步骤的 Tool Output 直接改写为成功状态,以此绕过网络故障。这就是“状态重构”。
执行以下修复脚本:
# 模拟从 JSON 文件读取故障快照
fault_snapshot = { ... } # 填入上述第一步的 JSON 数据
harness = AgentStateMachineHarness(AgentState(**fault_snapshot))
# 修复第 3 步的输出(人工注入备用渠道获取到的真实结果)
harness.state.history[-1].tool_calls[0].output = "{\"balance\": 50000, \"currency\": \"CNY\"}"
harness.state.history[-1].tool_calls[0].status = "success"
# 清除错误信息,将状态设回等待运行
harness.state.status = "PENDING" # 允许转移到 RUNNING
harness.state.error_message = None
# 将 current_step 指向第 4 步,表明下一步直接基于已修复的第 3 步结果继续执行
harness.state.current_step = 3 # 注:如果 current_step 表示已完成的步骤索引,更新它
# 导出就绪状态
restored_state = harness.dump_state()
print("Restored State successfully. Ready to resume:")
print(restored_state)
第三步:重新调度恢复运行
将 restored_state 发送回 Agent 执行队列。执行引擎加载该状态后,由于 history 中已经包含了第 3 步的成功输出,Agent 不会再次请求失效的 get_bank_balance 工具,而是直接以此为上下文,往下执行第 4 步(例如,计算转账比例并调用 transfer_funds 工具)。
验收与自测要求
为了验证你的 Agent Harness 是否已经具备状态机的核心特征,建议使用以下 checklist 进行验收:
- 状态机解耦测试:能否在不运行任何 LLM 调用和 Tool 调用代码的情况下,通过
load_state还原一个包含 5 步历史记录的 Agent 实例? - 死信队列拦截:在模拟死循环场景(如通过 Mock 工具让 Tool 总是返回错误格式且 Agent 决定不断重试)时,Agent 是否能在达到最大步数或最大 API 费用限制后,准确地转产为
FAILED并向控制台或 MQ 输出状态快照? - 单步回放能力:能否提取一个失败 Trace 中的某一步(例如第 3 步),修改其对应 Prompt 指导词后单独重跑,并产生新的第 4 步输出?
- 人工拦截点:在执行一个特定的高危工具前,Agent 是否会将
status变更为PAUSED,并阻塞等待直至显式调用resume_with_approval?
技术来源与持续维护规范
本 Harness 的设计理念与技术边界遵循以下行业实践:
- LangSmith 评测概念 (2026-05-28 访问):关于步骤 Trace、回归评测与比较实验的设计。当 LangSmith 的 Trace 格式标准(Run Tree Structure)发生更新时,应当同步调整本 Harness 导出的 JSON Schema。
- OpenAI 评测实践建议 (2026-05-28 访问):关于人审机制引入、防范 LLM 评测 Judge 偏差以及从生产故障快照构建评估数据集的路径。当使用更先进的推理模型(如基于强化学习自我纠错的模型)时,应评估其原生的 Step-by-step 思考过程是否可被 Harness 剥离与干预。
- Anthropic 评测工具规范 (2026-05-28 访问):关于重跑测试套件与在 Prompt 变更后进行对比。未来如果 Anthropic 提供更细粒度的 API 级状态缓存(Caching State),应当将缓存 Key 与本 Harness 的
session_id和步骤哈希结合,以进一步节省重跑成本。 - Azure Foundry 评测指南 (2026-05-28 访问):关于内置质量评估器和安全过滤器的使用。在实际云端部署时,本 Harness 产生的 Trace 可直接通过 Pipeline 投递给 Azure AI Evaluation SDK,实现自动化的越狱检测与相关性评分(Relevance Scoring)。
AgentHarness:把多步智能体变成可暂停、可恢复:把判断写进 Harness 证据链
当 Agent 能行动时,harness 的价值就从“评回答”扩展到“评轨迹和副作用”。本课交付物是 一个 Agent 状态机、执行 trace 样例、失败恢复 runbook,它必须能被复跑、复核、追踪和复盘。
如果 AgentHarness:把多步 还没有对应的输入样例,先不要讨论自动化覆盖率,因为没有样例就无法判断 harness 是否真的抓住问题。 如果一次评测失败会影响发布,应该保留失败输入、模型输出、工具轨迹和判分理由,否则团队只能凭记忆争论。 如果你准备把某个结果自动放行,必须先写清人工复核的例外条件,只有例外条件明确时,门禁才不会变成新的风险源。
红队失败样例要保留原始 payload、期望阻断结果和实际绕过路径。围绕 AgentHarness:把多步智能 做排查时,记录步骤、样例、指标、风险、修复动作和复测结果。
LangChain 的《LangSmith evaluation concepts》说明:支撑数据集、experiment、evaluator、比较实验和回归评测。;这意味着 AgentHarness:把多步 要把来源转成可执行断言。OpenAI 的《Evaluation best practices》提醒:支撑目标定义、数据集、指标、连续评测、人审、judge 偏差和 eval harness 设计。;因此本课必须写清自动判断和人工判断的边界。Anthropic 的《Using the Evaluation Tool》提供的证据是:支撑 prompt 变更后重跑 eval suite 和控制台评测流程。;所以当前结论按 2026-05-28 的来源状态使用。
复核触发条件要具体:如果模型 API/SDK、评测工具版本、Agent 框架、RAG 指标、安全规范、合规政策、平台 release/changelog 或关键来源页面变化,需要重新运行本课样例并更新 harness。
练习验收:把 AgentHarness:把多步智能 接入一个最小 AI 应用,提交包含输入样例、评测结果、失败诊断、来源证据、人工复核结论和下一步修复动作的记录。缺少任何一项,都标记为 needs_review。