17 - Self-Harness 与自我验证¶
核心主题:从 Prompt Engineering 到 Context Engineering,再到 Harness Engineering 的范式演进;Plan-Generate-Validate 三层架构实战;自我验证闭环的设计与实现
前置知识:Agent 基础(第1章)、上下文工程(第8章)、多Agent系统(第4章)
参考项目:Datawhale self-harness · Anthropic Harness Engineering · LangChain Deep Agents
目录¶
- 一、为什么需要 Harness Engineering
- 二、三层范式演进
- 三、Harness Engineering 核心架构
- 四、Plan-Generate-Validate 三层架构
- 五、自我验证闭环设计
- 六、上下文管理与动态工作记忆
- 七、Managed Agents:大脑与执行器的解耦
- 八、miniMaster 2.0 实战解析
- 九、Harness 的迭代方法论
- 十、实战练习
- 总结
一、为什么需要 Harness Engineering¶
1.1 长周期任务的困境¶
在早期实验中,工程师们使用初始化智能体将产品规格分解为任务列表,并由编码智能体逐项实现功能。但有些问题依然顽固存在——对于更复杂的任务,在长时间执行过程中,智能体会随着时间推移逐渐偏离正轨。
剖析这一问题时,工程师们观察到两种常见的失效模式:
| 失效模式 | 表现 | 根因 |
|---|---|---|
| 上下文过长 | 模型在长任务中失去连贯性,出现"上下文焦虑"——接近容量极限时过早收尾 | 上下文窗口填满,注意力被摊薄 |
| 自我评估偏差 | 智能体自信地给予自己高度评价,即使产出质量很差 | 模型天然倾向于肯定自身输出 |
1.2 从"写好提示词"到"构建工程系统"¶
与大模型交互的早期,开发者往往认为只要"把话说清楚",就能得到想要的结果。然而,随着 AI 应用深入复杂业务场景,仅靠单一指令输入已经无法驾驭大模型在长周期、多跳推理任务中的不确定性。
AI 工程师们的视角正在经历一次深刻的范式转移:
核心理念:不仅仅是使用大模型,而是构建能够稳定、可控、高效驱动大模型的工程化系统。
二、三层范式演进¶
2.1 Prompt Engineering —— 解决"如何表达清楚"¶
核心命题:如何精准地向模型表达任务意图?
模型被视为一个"拥有海量知识但缺乏常识的超级实习生"。工程的核心挑战是消除自然语言中的模糊性。
关键技术:
| 技术 | 原理 | 适用场景 |
|---|---|---|
| 角色设定 (Role-playing) | 激活模型权重中特定领域的知识分布 | 专业领域任务 |
| 少样本提示 (Few-shot) | 利用上下文学习能力,让模型"模仿"而非"理解" | 格式要求严格的任务 |
| 思维链 (CoT) | 强制模型展示推理路径,分配更多计算资源 | 复杂推理、数学问题 |
| 任务拆解 (Decomposition) | 将巨型 Prompt 拆解为流水线式步骤 | 多步骤复杂任务 |
| 结构化输出 | 强制 JSON/XML/Markdown 输出 | 工程系统集成 |
万能公式:
局限性:Prompt Engineering 假设环境是静态的。面对需要跨文档、调用工具或持续数天的任务时,静态 Prompt 无法应对动态变化的信息流。
2.2 Context Engineering —— 解决"如何信息供给"¶
核心命题:如何在对的时间、以对的密度,向模型提供最相关的背景信息?
Context Engineering 将上下文重新概念化为多个信息组件的动态组合:
| 组件 | 含义 | 来源 |
|---|---|---|
c_instr | 系统指令和规则 | 开发者预设 |
c_know | 外部知识(RAG/知识图谱) | 检索系统 |
c_tools | 可用工具定义和签名 | 工具注册表 |
c_mem | 历史交互的持久化信息 | 记忆系统 |
c_state | 用户/环境/多Agent的动态状态 | 状态管理器 |
c_query | 用户当下的直接请求 | 用户输入 |
一句话总结:
构建有效的 context 结构,在上下文中找到最小的一组高信息密度 token,同时最大化模型产生目标结果的概率。
关键技术:
- 检索增强生成 (RAG):打通外部知识库与模型
- 渐进式披露 (Progressive Disclosure):先给目录级线索,再按需展开
- 分层上下文:主 Agent 持有控制层上下文,子 Agent 持有任务层上下文
- 上下文压缩:摘要、过滤、重组,保留最重要部分
渐进式披露的核心原则:
局限性:Context Engineering 保证了模型在单次推理时拥有"完美的情报",但它仍然是一个静态切片。当模型需要自主运行数小时时,谁来决定何时检索?检索失败怎么办?模型陷入死循环怎么办?
2.3 Harness Engineering —— 解决"如何稳定执行"¶
核心命题:如何构建一个鲁棒的运行环境,确保智能体在长周期任务中不脱轨、不遗忘、能自愈?
相对于 Context Engineering,Harness Engineering 新增了三个关键维度:
关键突破技术:
| 技术 | 作用 | 类比 |
|---|---|---|
| 多智能体编排 | 任务拆解,分别派发给规划者/执行者/评审者 | 公司部门分工 |
| 评估器分离 | 生成与评估由不同智能体完成 | QA 独立于开发 |
| 状态管理 | 持久化文件系统和记忆库 | 项目管理工具 |
| 上下文重置 | 完全清空上下文,通过结构化交接传递状态 | 换班交接 |
2.4 三层对比总结¶
| 维度 | Prompt Engineering | Context Engineering | Harness Engineering |
|---|---|---|---|
| 核心命题 | 如何表达清楚 | 如何信息供给 | 如何稳定执行 |
| 关键突破 | 角色设定、Few-shot、CoT | RAG、渐进式披露、分层上下文 | 多智能体协作、评估器分离、状态管理 |
| 解决痛点 | 模型"听不懂"指令 | 模型"看不到"关键信息 | 模型"做不对"复杂任务 |
| 适用场景 | 单轮对话、短文本生成 | 企业知识库、长文本分析 | 长周期复杂任务 |
| 视角 | 静态单点 | 动态全局 | 系统工程 |
核心洞察:Context Engineering 只能保证每次思考时"手上拿的资料是对的",但只有 Harness Engineering 能够保证运行过程是稳定的。
三、Harness Engineering 核心架构¶
3.1 三智能体系统¶
基于 Anthropic 的工程实践,一个典型的 Harness 系统包含三个核心角色:
graph TB
User["用户需求<br/>1-4句简单描述"]
Planner["规划者 Planner<br/>扩展为完整规格书"]
Generator["生成器 Generator<br/>迭代冲刺实现功能"]
Evaluator["评估器 Evaluator<br/>独立测试与评分"]
User --> Planner
Planner -->|"规格说明书"| Generator
Generator -->|"实现成果"| Evaluator
Evaluator -->|"评分+反馈"| Generator
Evaluator -->|"最终验收"| Done["交付"]
style Planner fill:#4CAF50,color:white
style Generator fill:#2196F3,color:white
style Evaluator fill:#FF9800,color:white 各角色职责:
| 角色 | 职责 | 关键约束 |
|---|---|---|
| Planner(规划者) | 将简单提示扩展为完整产品规格书 | 聚焦业务上下文和高层技术设计,不深入实现细节 |
| Generator(生成器) | 以迭代冲刺方式逐个实现功能 | 每次迭代先自我评估,再交付 QA |
| Evaluator(评估者) | 像真实用户一样测试运行中的应用 | 使用 Playwright 等工具做自动化交互测试 |
3.2 冲刺契约(Sprint Contract)¶
在每轮开发冲刺开始前,Generator 与 Evaluator 会协商一份冲刺契约——在编写任何代码之前,先就当前任务块的"完成标准(Definition of Done)"达成一致。
# 冲刺契约示例
sprint_contract = {
"sprint_id": "sprint-3",
"scope": "关卡编辑器核心功能",
"definition_of_done": [
"矩形填充工具允许通过点击拖动填充矩形区域",
"用户可以选择并删除已放置的实体生成点",
"用户可以通过 API 重新排序动画帧",
# ... 共 27 项验收标准
],
"test_method": "Playwright 自动化交互测试",
"pass_threshold": "所有标准必须通过"
}
3.3 评估量化体系¶
以 Anthropic 的前端设计实验为例,四个评分维度:
| 维度 | 含义 | 权重倾向 |
|---|---|---|
| 设计质量 (Design Quality) | 是否感觉像一个连贯的整体 | ⭐⭐⭐ 高 |
| 原创性 (Originality) | 是否有定制决策的证据 | ⭐⭐⭐ 高 |
| 工艺 (Craft) | 技术执行:字体层次、间距、色彩 | ⭐⭐ 中 |
| 功能性 (Functionality) | 用户能否理解并完成任务 | ⭐⭐ 中 |
设计哲学:Claude 在工艺和功能性方面已经默认出色,但在设计和原创性方面往往平淡无奇。评估标准通过更重视设计和原创性,推动模型进行更具美学风险的尝试。
四、Plan-Generate-Validate 三层架构¶
4.1 完整运行流程¶
以 Anthropic 的实验为例——提示词为"创建一个 2D 复古游戏制作工具":
| 运行模式 | 时长 | 成本 | 产出质量 |
|---|---|---|---|
| Solo(单次运行) | 20 分钟 | $9 | 不可用:实体不响应用户输入 |
| Full Harness | 6 小时 | $200 | 功能完整:游戏可玩,含 AI 集成 |
Solo 运行的问题:
- 界面布局浪费空间,固定高度面板导致大量留白
- 工作流僵化,无引导提示
- 核心功能断开:实体定义与运行时之间的连接缺失
Full Harness 的产出:
- Planner 将一句话提示扩展为 10 个冲刺中的 16 项功能规范
- 界面精致流畅,精灵编辑器功能丰富
- 游戏真正可玩,角色可以移动并互动
- Evaluator 通过 Playwright 精准找出多个代码级 Bug
4.2 Evaluator 的价值验证¶
Evaluator 在实际运行中发现的具体问题示例:
| 验收标准 | Evaluator 发现 |
|---|---|
| 矩形填充工具允许通过拖动填充矩形区域 | 失败 — 工具仅在起点/终点放置图块,fillRectangle 函数未在 mouseUp 时正确触发 |
| 用户可以选择并删除已放置的实体生成点 | 失败 — 删除键处理程序要求同时设置 selection 和 selectedEntityId,条件应为 || 而非 && |
| 用户可以通过 API 重新排序动画帧 | 失败 — PUT /frames/reorder 路由定义在 /{frame_id} 之后,FastAPI 将 'reorder' 匹配为 frame_id 整数并返回 422 |
关键洞察:Evaluator 始终确保实现与规范保持一致。仅冲刺 3 就包含了 27 项验收标准,Evaluator 的发现足够具体,无需额外调查即可直接处理。
五、自我验证闭环设计¶
5.1 为什么需要自我验证¶
LangChain 在 Terminal Bench 2.0 上的实验揭示了智能体最常见的失败模式:
智能体编写了一个解决方案,重新阅读自己的代码,确认看起来没问题,然后就停止了。
测试是自主智能体编码的关键部分。它有助于测试整体正确性,同时为智能体提供信号以便逐步优化。
5.2 自我验证循环¶
flowchart LR
Plan["规划<br/>阅读任务,扫描代码库<br/>制定初步计划"]
Build["构建<br/>实现计划<br/>创建测试用例"]
Verify["验证<br/>运行测试<br/>对照任务要求比对"]
Fix["修复<br/>分析错误<br/>重新审视规范"]
Plan --> Build --> Verify
Verify -->|"测试通过"| Done["完成"]
Verify -->|"测试失败"| Fix --> Build 5.3 PreCompletionChecklist 机制¶
LangChain 使用 PreCompletionChecklistMiddleware 在智能体退出前拦截它,提醒其根据任务规范运行验证流程:
class PreCompletionChecklistMiddleware:
"""在智能体退出前强制执行验证检查清单"""
def __init__(self, checklist: list[str]):
self.checklist = checklist
def on_before_exit(self, agent_state: AgentState) -> AgentState:
"""拦截退出,注入验证提醒"""
if not agent_state.all_checks_passed:
reminder = self._build_reminder(agent_state)
agent_state.inject_context(reminder)
agent_state.prevent_exit() # 阻止退出
return agent_state
def _build_reminder(self, state: AgentState) -> str:
unchecked = [
item for item in self.checklist
if not state.is_verified(item)
]
return (
f"⚠️ 在退出前,请确保完成以下验证:\n"
+ "\n".join(f" - [ ] {item}" for item in unchecked)
)
5.4 死循环检测¶
智能体一旦确定计划就可能变得短视,导致陷入"厄运循环"——对同一错误方法进行微小调整(在某些轨迹中重复 10 次以上)。
class LoopDetectionMiddleware:
"""检测并打破智能体的死循环"""
def __init__(self, max_edits_per_file: int = 5):
self.max_edits = max_edits_per_file
self.edit_counts: dict[str, int] = {}
def on_tool_call(self, tool_name: str, params: dict) -> str | None:
if tool_name in ("write_file", "edit_file"):
filepath = params.get("path", "")
self.edit_counts[filepath] = self.edit_counts.get(filepath, 0) + 1
if self.edit_counts[filepath] >= self.max_edits:
return (
f"⚠️ 你已经对 {filepath} 编辑了 {self.edit_counts[filepath]} 次。"
f"请考虑重新审视你的方法,尝试完全不同的解决方案。"
)
return None # 不注入额外上下文
5.5 推理资源分配¶
LangChain 发现,推理资源(thinking budget)的分配对性能有显著影响。他们最终采用了 "推理三明治" 策略:
核心思想:在规划和验证上投入更多推理计算资源,执行阶段可以适度节省。全程使用 xhigh 反而因超时问题导致得分下降。
六、上下文管理与动态工作记忆¶
6.1 上下文重置 vs 上下文压缩¶
面对长任务中上下文窗口填满的问题,有两种解决策略:
| 策略 | 方法 | 优势 | 劣势 |
|---|---|---|---|
| 压缩 | 将对话早期部分就地总结 | 保持连续性 | 上下文焦虑仍可能存在 |
| 重置 | 完全清空上下文,启动新智能体 | 全新开始,消除焦虑 | 需要结构化交接流程 |
Anthropic 的实验发现:Claude Sonnet 4.5 表现出足够强烈的上下文焦虑,仅靠压缩不足以实现强大的长任务性能,上下文重置成为关键设计。
6.2 结构化交接流程¶
当采用上下文重置时,交接工件需要包含足够的状态:
@dataclass
class HandoffPacket:
"""智能体间结构化交接包"""
# 任务信息
current_subproblem: str # 当前要解决的子问题
boundary: str # 子问题的边界和非目标
# 输入材料
input_materials: list[str] # 可用输入材料
reference_only: list[str] # 仅参考的材料
# 输出要求
expected_output: str # 输出应该长什么样
stop_condition: str # 什么时候停止
# 引用方式
result_format: str # 返回原文片段、链接还是 artifact ID
6.3 Working Memory 设计¶
miniMaster 2.0 中的动态工作记忆实现:
class WorkingMemory:
"""动态工作记忆管理"""
def __init__(
self,
keep_latest_n: int = 3,
max_chars: int = 8000
):
self.memories: list[MemoryEntry] = []
self.keep_latest_n = keep_latest_n
self.max_chars = max_chars
self.summary = ""
def add_memory(
self,
step: int,
tool_name: str,
parameters: dict,
result: Any
):
"""添加执行记录(写入前先压缩)"""
self.memories.append(MemoryEntry(
step=step,
tool_call=MemoryToolCall(
tool_name=tool_name,
parameters=compact_for_memory(parameters),
),
result=prepare_memory_result(tool_name, parameters, result),
))
def get_prompt_context(
self,
view: str = "generator"
) -> str:
"""为不同角色渲染不同视图"""
if view == "planner":
return self._render_planner_view()
elif view == "generator":
return self._render_generator_view()
elif view == "validation":
return self._render_validation_view()
def compact_old_memories(self) -> bool:
"""超过阈值时将较早记录压成摘要"""
if self.total_chars() > self.max_chars:
old = self.memories[:-self.keep_latest_n]
self.summary = summarize(old)
self.memories = self.memories[-self.keep_latest_n:]
return True
return False
记忆控制四原则:
- 单次结果先压缩:超长的 stdout、content 等先压缩成预览结构
- 超过阈值再摘要:整体长度超限时将较早记录压成确定性摘要
- 不同角色不同视图:Planner/Executor/Validator 分别看到不同格式
- 失败经验进入重试归档:执行记忆、验证记忆压成 retry archive
七、Managed Agents:大脑与执行器的解耦¶
7.1 Harness 的"保质期"问题¶
Harness 的本质是开发者为了弥补当前大模型能力不足而编写的"补丁"。但模型进化速度太快:
- Sonnet 4.5 时代:模型有"上下文焦虑",需要上下文重置机制
- Opus 4.5 时代:模型自己克服了这个毛病,重置机制变成"累赘"
7.2 操作系统式抽象¶
Anthropic 借鉴操作系统的虚拟化思想,将 Agent 系统严格解耦为三个独立组件:
graph TB
subgraph Brain["大脑层"]
LLM["Claude 模型"]
Harness["Harness<br/>路由与调度"]
end
subgraph Memory["记忆层"]
Session["Session<br/>仅追加日志"]
end
subgraph Hands["执行层"]
Sandbox["Sandbox<br/>安全执行环境"]
Tools["Tools<br/>外部工具"]
end
Harness <-->|"getSession / emitEvent"| Session
Harness <-->|"execute"| Sandbox
Harness <-->|"调用"| Tools
LLM <-->|"推理"| Harness 六大抽象接口:
| 组件 | 接口 | 含义 |
|---|---|---|
| Session | getSession / emitEvent | 仅追加日志,不关心存储实现 |
| Orchestration | wake(session_id) | 唤醒会话,可以是定时任务或消息队列 |
| Harness | yield Effect → 返回结果 | 纯粹的逻辑循环,负责思考下一步 |
| Sandbox | provision / execute | 执行环境,可随时丢弃和替换 |
| Resources | source_ref / mount_path | 代码和文件的来源与挂载 |
| Tools | name + description + input_schema | 任何可描述的能力都可被调用 |
7.3 从"宠物"到"牛群"¶
旧架构下,所有组件塞在一个容器里,变成了碰不得的"宠物":
旧架构(宠物模式):
容器 = Session + Harness + Sandbox + 用户数据
→ 容器卡死 = 全部丢失
→ 无法登录排查 Bug
→ 企业客户 VPC 部署困难
新架构(牛群模式):
Harness 无状态 → 崩溃后从 Session 恢复
Sandbox 可替换 → 崩溃后拉起新实例
→ 系统最脆弱的部分变成了可随时丢弃的"牛群"
7.4 Many Brains, Many Hands¶
解耦后的两大扩展能力:
Many Brains(多大脑):
- TTFT(首字节响应时间)中位数下降 60%,p95 暴降 90%+
- 大脑脱离容器,通过 API 远程调用客户内网的"手脚"
- 按需懒加载:如果 Claude 一开始只需规划,立刻开始思考
Many Hands(多手脚):
- 所有"手脚"统一抽象为
execute(name, input) → string - 大脑不关心对面是 Docker 容器、真实手机还是模拟器
- 不同 Agent 大脑之间可以传递"手脚"控制权
八、miniMaster 2.0 实战解析¶
miniMaster 2.0 是 Datawhale 社区开源的 Harness Engineering 教学项目,实现了 Plan-Generate-Validate 三层嵌套循环的多智能体协作架构。
8.1 项目结构¶
miniMaster2.0/
├── main_agent.py # 入口
├── bootstrap/ # 启动与运行时
├── domain/ # 领域模型
│ ├── state_machine.py # 状态迁移控制
│ ├── task_requirements.py
│ ├── todo.py # 任务看板
│ └── types.py
├── engine/ # 核心引擎
│ ├── main_loop.py # 主循环
│ ├── runner.py # 执行器
│ ├── validator.py # 验证器
│ ├── guards.py # 防护机制
│ └── plan_actions.py # 规划动作
├── llm/ # LLM 调用层
│ ├── runner.py # 统一调用入口
│ └── prompting/ # Prompt 管理
│ ├── builders.py # Prompt 构造
│ ├── policies.py # 动作策略
│ └── protocol.py # Function Call 协议
├── memory/ # 记忆管理
│ ├── working_memory.py # 动态工作记忆
│ ├── session.py # 会话记忆
│ └── prompt_context.py # 上下文渲染
├── skills/ # 技能系统
├── tools/ # 工具系统
│ ├── base_tool/ # 基础工具(bash/read/write/edit)
│ ├── search_tool/ # 搜索工具(glob/grep)
│ └── core/ # 工具管理框架
└── utils/ # 工具函数
8.2 三层 Agent 协作¶
# 简化的三层循环伪代码
class MiniMasterLoop:
def run(self, user_input: str):
# 第一层:Planner-Agent(全局调度)
plan_action = self.planner.decide(user_input)
if plan_action.type == "respond_to_user":
return plan_action.response
elif plan_action.type == "init_tasks":
tasks = self.planner.create_tasks(plan_action)
elif plan_action.type == "subagent_tool":
# 第二层:Executor-Agent(执行者)
for task in tasks.pending:
conclusion = self.executor.execute(task)
# 第三层:Validator-Agent(评估者)
validation = self.validator.validate(
task=task,
conclusion=conclusion,
checklist=task.done_when
)
if validation.result == "有效":
task.mark_done()
else:
task.mark_failed(validation.feedback)
8.3 动作策略(Policy)¶
不同角色拥有不同的动作边界,这是 Harness 稳定性的关键:
# 各角色的工具权限
PLANNER_TOOLS = ["read", "glob", "grep"] # 只能侦察
PLANNER_ACTIONS = [
"init_tasks", "add_task", "split_task",
"retry_task", "subagent_tool", "respond_to_user"
]
EXECUTOR_TOOLS = ["bash", "read", "write", "edit", "glob", "grep"]
EXECUTOR_ACTIONS = ["update_task_conclusion"]
VALIDATOR_TOOLS = ["bash", "read", "glob", "grep"] # 只能核查
VALIDATOR_ACTIONS = ["validate_tool"] # 必须通过此工具输出结论
设计原则:Planner 不越权改文件,Validator 不偷偷完成任务,Executor 不能跳过验证层直接宣布完成。
8.4 状态机控制¶
# 任务状态迁移的受控约束
VALID_TRANSITIONS = {
"PENDING": ["RUNNING"],
"RUNNING": ["DONE", "FAILED", "BLOCKED"],
"FAILED": ["PENDING"], # 只能通过 retry_task 恢复
"BLOCKED": ["PENDING"], # 只能通过 retry_task 恢复
"DONE": [], # 终态
}
class StateMachine:
def transition(self, task: Task, new_state: str, actor: str):
"""受控状态迁移"""
if new_state not in VALID_TRANSITIONS[task.state]:
raise InvalidTransition(
f"{actor} 不能将任务从 {task.state} 转为 {new_state}"
)
task.state = new_state
九、Harness 的迭代方法论¶
9.1 核心原则¶
Harness 中的每一个组件,本质上都代表了对"模型自身能力不足"的一种假设。由于模型在不断发展,这些假设可能会过时或出错,因此必须对其进行压力测试。
9.2 科学的迭代方法¶
错误做法:激进地大面积删减组件 → 无法辨别哪些是"承重墙"
正确做法:每次仅移除一个组件,仔细评估对最终输出的具体影响
迭代循环:
1. 在实际问题上运行 Harness
2. 分析运行轨迹(Trace)
3. 识别失败模式
4. 针对性调整(增加/修改/移除组件)
5. 控制变量重新运行
6. 对比结果
9.3 Anthropic 的迭代经验¶
| 组件 | 迭代决策 | 原因 |
|---|---|---|
| 冲刺(Sprint)脚手架 | ✂️ 拆除 | 新模型原生处理复杂任务能力提升 |
| 规划器(Planner) | ✅ 保留 | 没有规划器,模型会"范围缩水"——选择最简单实现路径 |
| 评估器(Evaluator) | ⚡ 调整 | 只在任务触及模型能力"边缘"时启用,避免不必要开销 |
| 提示词工程 | 🔧 加强 | 对模型尚不熟悉的新兴技术范式,通过提示词"补课" |
9.4 Trace Analyzer Skill¶
LangChain 将 Harness 迭代过程自动化为 Trace Analyzer Skill:
注意:过度拟合特定任务的修改不利于泛化,并可能导致其他任务出现性能倒退。
十、实战练习¶
练习 1:实现一个简单的 Generate-Validate 循环¶
构建一个最小化的 Generate-Validate 系统,用于代码生成任务:
import anthropic
client = anthropic.Anthropic()
def generator(task: str, feedback: str = "") -> str:
"""生成器:根据任务和反馈生成代码"""
prompt = f"请完成以下任务:{task}"
if feedback:
prompt += f"\n\n之前的尝试收到以下反馈,请改进:\n{feedback}"
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=4096,
messages=[{"role": "user", "content": prompt}]
)
return response.content[0].text
def validator(task: str, code: str) -> tuple[bool, str]:
"""验证器:独立评估生成的代码"""
prompt = f"""你是一个严格的代码审查员。
任务要求:{task}
请审查以下代码,判断是否满足任务要求。
必须指出具体的问题,不要笼统地说"看起来不错"。
代码:
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=2048,
system="你是一个持怀疑态度的代码审查员。除非代码确实正确,否则不要通过。",
messages=[{"role": "user", "content": prompt}]
)
review = response.content[0].text
passed = "通过" in review and "不通过" not in review
return passed, review
def generate_validate_loop(task: str, max_iterations: int = 3): """Generate-Validate 循环""" feedback = "" for i in range(max_iterations): print(f"\n=== 迭代 {i+1}/{max_iterations} ===")
# 生成
code = generator(task, feedback)
print(f"生成完成,代码长度:{len(code)} 字符")
# 验证
passed, review = validator(task, code)
print(f"验证结果:{'✅ 通过' if passed else '❌ 未通过'}")
print(f"审查意见:{review[:200]}...")
if passed:
return code
feedback = review
return code # 返回最后一次的结果
<details> <summary>💡 参考答案:测试用例</summary>python
测试用例¶
task = "实现一个 Python 函数,接受一个整数列表,返回其中所有偶数的平方和。要求处理空列表和 None 输入。" result = generate_validate_loop(task, max_iterations=3) print("\n最终代码:") print(result)
预期输出类似:¶
def sum_even_squares(numbers):¶
if numbers is None:¶
return 0¶
return sum(x**2 for x in numbers if x % 2 == 0)¶
```
练习 2:实现 Working Memory¶
实现一个支持压缩和角色视图的 Working Memory:
from dataclasses import dataclass, field
from typing import Any
@dataclass
class MemoryEntry:
step: int
tool_name: str
action: str
result: str
chars: int = 0
class SimpleWorkingMemory:
def __init__(self, max_chars: int = 4000, keep_latest: int = 3):
self.entries: list[MemoryEntry] = []
self.max_chars = max_chars
self.keep_latest = keep_latest
self.summary = ""
def add(self, step: int, tool_name: str, action: str, result: str):
"""添加记录"""
# TODO: 实现添加逻辑
pass
def get_planner_view(self) -> str:
"""规划者视图:只看结论和状态"""
# TODO: 实现规划者视图
pass
def get_executor_view(self) -> str:
"""执行者视图:看最近几步的完整记录"""
# TODO: 实现执行者视图
pass
def compact(self) -> bool:
"""压缩旧记录为摘要"""
# TODO: 实现压缩逻辑
pass
💡 参考答案
from dataclasses import dataclass
from typing import Any
@dataclass
class MemoryEntry:
step: int
tool_name: str
action: str
result: str
@property
def chars(self) -> int:
return len(self.action) + len(self.result)
class SimpleWorkingMemory:
def __init__(self, max_chars: int = 4000, keep_latest: int = 3):
self.entries: list[MemoryEntry] = []
self.max_chars = max_chars
self.keep_latest = keep_latest
self.summary = ""
def add(self, step: int, tool_name: str, action: str, result: str):
# 截断过长的结果
if len(result) > 500:
result = result[:500] + "...[截断]"
self.entries.append(MemoryEntry(
step=step, tool_name=tool_name,
action=action, result=result
))
self.compact()
def total_chars(self) -> int:
return sum(e.chars for e in self.entries) + len(self.summary)
def get_planner_view(self) -> str:
lines = []
if self.summary:
lines.append(f"[历史摘要] {self.summary}")
for e in self.entries[-self.keep_latest:]:
lines.append(
f"步骤{e.step} [{e.tool_name}]: "
f"{e.action[:100]} → {e.result[:100]}"
)
return "\n".join(lines)
def get_executor_view(self) -> str:
lines = []
if self.summary:
lines.append(f"[历史摘要] {self.summary}\n")
for e in self.entries[-self.keep_latest:]:
lines.append(
f"步骤{e.step} [{e.tool_name}]\n"
f" 动作: {e.action}\n"
f" 结果: {e.result}"
)
return "\n".join(lines)
def compact(self) -> bool:
if self.total_chars() <= self.max_chars:
return False
old = self.entries[:-self.keep_latest]
new_summary_parts = [
f"步骤{e.step}[{e.tool_name}]: {e.result[:80]}"
for e in old
]
self.summary = "; ".join(new_summary_parts)
self.entries = self.entries[-self.keep_latest:]
return True
# 测试
mem = SimpleWorkingMemory(max_chars=200, keep_latest=2)
mem.add(1, "bash", "ls -la", "file1.py\nfile2.py\nREADME.md")
mem.add(2, "read", "读取 file1.py", "def hello():\n print('hello')" * 20)
mem.add(3, "bash", "python file1.py", "hello")
mem.add(4, "edit", "修改 file1.py", "添加了类型注解")
print("=== Planner View ===")
print(mem.get_planner_view())
print("\n=== Executor View ===")
print(mem.get_executor_view())
练习 3:设计评估标准¶
为一个"自动生成 API 文档"的 Agent 设计评估标准:
💡 参考答案
api_doc_eval_criteria = {
"准确性 (Accuracy)": {
"weight": 0.30,
"description": "API 端点、参数类型、返回值是否与代码实现一致",
"threshold": 0.8,
"test_method": "对照源代码自动比对"
},
"完整性 (Completeness)": {
"weight": 0.25,
"description": "是否覆盖所有公开端点、所有参数、所有错误码",
"threshold": 0.9,
"test_method": "检查覆盖率"
},
"可读性 (Readability)": {
"weight": 0.20,
"description": "示例是否清晰、描述是否易懂、结构是否规范",
"threshold": 0.7,
"test_method": "LLM-as-Judge 评分"
},
"实用性 (Practicality)": {
"weight": 0.15,
"description": "是否包含常见用例、错误处理建议、最佳实践",
"threshold": 0.6,
"test_method": "LLM-as-Judge 评分"
},
"格式规范 (Format)": {
"weight": 0.10,
"description": "是否符合 OpenAPI/Swagger 规范",
"threshold": 0.95,
"test_method": "Schema 验证"
}
}
# 评估器 Prompt 模板
EVALUATOR_PROMPT = """你是一个持怀疑态度的 API 文档审查员。
请根据以下标准对文档逐项评分(0-1):
{criteria}
被审查的 API 文档:
{documentation}
对应的源代码:
{source_code}
输出格式:
```json
{{
"scores": {{
"准确性": {{"score": 0.0, "evidence": "具体问题"}},
"完整性": {{"score": 0.0, "evidence": "具体问题"}},
...
}},
"overall_pass": true/false,
"critical_issues": ["必须修复的问题列表"]
}}
```"""
练习 4:思考题¶
-
为什么评估器(Evaluator)要和生成器(Generator)分离? 如果用同一个 Agent 既生成又评估,会出现什么问题?
-
Harness 的"保质期"是什么意思? 请用具体例子说明模型升级如何影响 Harness 设计。
-
渐进式披露(Progressive Disclosure)和 Just-in-Time 有什么区别? 请结合 Agent Skills 的设计说明。
-
在什么情况下应该使用上下文重置而非上下文压缩? 请分析各自的适用场景。
总结¶
关键要点¶
| 要点 | 说明 |
|---|---|
| 三层演进 | Prompt → Context → Harness,从"表达"到"供给"到"执行" |
| Generate-Validate | 生成与评估必须分离,避免自我肯定偏差 |
| 上下文重置 | 长任务中比压缩更激进的策略,配合结构化交接 |
| 状态管理 | 任务看板 + 工作记忆 + 状态机,防止 Agent 漂移 |
| Harness 迭代 | 每次只改一个组件,控制变量,科学评估 |
| 解耦架构 | Session + Harness + Sandbox 三大抽象,从"宠物"到"牛群" |
核心洞察¶
Harness Engineering 的本质是:将模型天生参差不齐的智能,塑造成能处理我们所关心任务的形态。 它是一门关于系统的学问——围绕模型构建工具,以优化任务性能、Token 效率和延迟等目标。
随着模型性能提升,有趣的应用组合空间并不会缩小。相反,它会不断迁移,而 AI 工程师的有趣任务正是持续发掘下一个新颖的组合方式。
延伸阅读¶
- Anthropic: Building effective agents
- Anthropic: Harness Engineering for AI agents
- Anthropic: Managed Agents
- LangChain: Harness Engineering for Deep Agents
- Datawhale self-harness 教程
- Context Engineering Survey
⚠️ 核验说明:本页内容基于 Anthropic 和 LangChain 公开发表的工程博客、Datawhale 开源教程编写。若涉及外部模型、API 或版本号,请以官方文档为准。
最后更新日期:2026-04-21