跳转至

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

1.1 长周期任务的困境

在早期实验中,工程师们使用初始化智能体将产品规格分解为任务列表,并由编码智能体逐项实现功能。但有些问题依然顽固存在——对于更复杂的任务,在长时间执行过程中,智能体会随着时间推移逐渐偏离正轨

剖析这一问题时,工程师们观察到两种常见的失效模式:

失效模式 表现 根因
上下文过长 模型在长任务中失去连贯性,出现"上下文焦虑"——接近容量极限时过早收尾 上下文窗口填满,注意力被摊薄
自我评估偏差 智能体自信地给予自己高度评价,即使产出质量很差 模型天然倾向于肯定自身输出

1.2 从"写好提示词"到"构建工程系统"

与大模型交互的早期,开发者往往认为只要"把话说清楚",就能得到想要的结果。然而,随着 AI 应用深入复杂业务场景,仅靠单一指令输入已经无法驾驭大模型在长周期、多跳推理任务中的不确定性。

AI 工程师们的视角正在经历一次深刻的范式转移:

Text Only
Prompt Engineering(提示词工程)
  → Context Engineering(上下文工程)
    → Harness Engineering(Harness 工程)

核心理念:不仅仅是使用大模型,而是构建能够稳定、可控、高效驱动大模型的工程化系统。


二、三层范式演进

2.1 Prompt Engineering —— 解决"如何表达清楚"

核心命题:如何精准地向模型表达任务意图?

模型被视为一个"拥有海量知识但缺乏常识的超级实习生"。工程的核心挑战是消除自然语言中的模糊性。

关键技术

技术 原理 适用场景
角色设定 (Role-playing) 激活模型权重中特定领域的知识分布 专业领域任务
少样本提示 (Few-shot) 利用上下文学习能力,让模型"模仿"而非"理解" 格式要求严格的任务
思维链 (CoT) 强制模型展示推理路径,分配更多计算资源 复杂推理、数学问题
任务拆解 (Decomposition) 将巨型 Prompt 拆解为流水线式步骤 多步骤复杂任务
结构化输出 强制 JSON/XML/Markdown 输出 工程系统集成

万能公式

Text Only
Prompt = Instruction + Context + Examples + Output Constraint

局限性:Prompt Engineering 假设环境是静态的。面对需要跨文档、调用工具或持续数天的任务时,静态 Prompt 无法应对动态变化的信息流。

2.2 Context Engineering —— 解决"如何信息供给"

核心命题:如何在对的时间、以对的密度,向模型提供最相关的背景信息?

Context Engineering 将上下文重新概念化为多个信息组件的动态组合:

Text Only
C = A(c_instr, c_know, c_tools, c_mem, c_state, c_query)
组件 含义 来源
c_instr 系统指令和规则 开发者预设
c_know 外部知识(RAG/知识图谱) 检索系统
c_tools 可用工具定义和签名 工具注册表
c_mem 历史交互的持久化信息 记忆系统
c_state 用户/环境/多Agent的动态状态 状态管理器
c_query 用户当下的直接请求 用户输入

一句话总结

构建有效的 context 结构,在上下文中找到最小的一组高信息密度 token,同时最大化模型产生目标结果的概率。

关键技术

  • 检索增强生成 (RAG):打通外部知识库与模型
  • 渐进式披露 (Progressive Disclosure):先给目录级线索,再按需展开
  • 分层上下文:主 Agent 持有控制层上下文,子 Agent 持有任务层上下文
  • 上下文压缩:摘要、过滤、重组,保留最重要部分

渐进式披露的核心原则

Text Only
1. 默认不注入,只给索引
2. 先给概览,再给细则
3. 能执行就别展开
4. 把探索设计成有反馈的路径

局限性:Context Engineering 保证了模型在单次推理时拥有"完美的情报",但它仍然是一个静态切片。当模型需要自主运行数小时时,谁来决定何时检索?检索失败怎么办?模型陷入死循环怎么办?

2.3 Harness Engineering —— 解决"如何稳定执行"

核心命题:如何构建一个鲁棒的运行环境,确保智能体在长周期任务中不脱轨、不遗忘、能自愈?

相对于 Context Engineering,Harness Engineering 新增了三个关键维度:

Text Only
Harness = Context 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)"达成一致。

Python
# 冲刺契约示例
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 时正确触发
用户可以选择并删除已放置的实体生成点 失败 — 删除键处理程序要求同时设置 selectionselectedEntityId,条件应为 || 而非 &&
用户可以通过 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 在智能体退出前拦截它,提醒其根据任务规范运行验证流程:

Python
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 次以上)。

Python
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)的分配对性能有显著影响。他们最终采用了 "推理三明治" 策略:

Text Only
规划阶段 → xhigh(最高推理预算)
执行阶段 → high(高推理预算)  
验证阶段 → xhigh(最高推理预算)

核心思想:在规划和验证上投入更多推理计算资源,执行阶段可以适度节省。全程使用 xhigh 反而因超时问题导致得分下降。


六、上下文管理与动态工作记忆

6.1 上下文重置 vs 上下文压缩

面对长任务中上下文窗口填满的问题,有两种解决策略:

策略 方法 优势 劣势
压缩 将对话早期部分就地总结 保持连续性 上下文焦虑仍可能存在
重置 完全清空上下文,启动新智能体 全新开始,消除焦虑 需要结构化交接流程

Anthropic 的实验发现:Claude Sonnet 4.5 表现出足够强烈的上下文焦虑,仅靠压缩不足以实现强大的长任务性能,上下文重置成为关键设计

6.2 结构化交接流程

当采用上下文重置时,交接工件需要包含足够的状态:

Python
@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 中的动态工作记忆实现:

Python
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

记忆控制四原则

  1. 单次结果先压缩:超长的 stdout、content 等先压缩成预览结构
  2. 超过阈值再摘要:整体长度超限时将较早记录压成确定性摘要
  3. 不同角色不同视图:Planner/Executor/Validator 分别看到不同格式
  4. 失败经验进入重试归档:执行记忆、验证记忆压成 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 从"宠物"到"牛群"

旧架构下,所有组件塞在一个容器里,变成了碰不得的"宠物":

Text Only
旧架构(宠物模式):
  容器 = 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 项目结构

Text Only
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 协作

Python
# 简化的三层循环伪代码
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 稳定性的关键:

Python
# 各角色的工具权限
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 状态机控制

Python
# 任务状态迁移的受控约束
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 科学的迭代方法

错误做法:激进地大面积删减组件 → 无法辨别哪些是"承重墙"

正确做法:每次仅移除一个组件,仔细评估对最终输出的具体影响

Text Only
迭代循环:
  1. 在实际问题上运行 Harness
  2. 分析运行轨迹(Trace)
  3. 识别失败模式
  4. 针对性调整(增加/修改/移除组件)
  5. 控制变量重新运行
  6. 对比结果

9.3 Anthropic 的迭代经验

组件 迭代决策 原因
冲刺(Sprint)脚手架 ✂️ 拆除 新模型原生处理复杂任务能力提升
规划器(Planner) ✅ 保留 没有规划器,模型会"范围缩水"——选择最简单实现路径
评估器(Evaluator) ⚡ 调整 只在任务触及模型能力"边缘"时启用,避免不必要开销
提示词工程 🔧 加强 对模型尚不熟悉的新兴技术范式,通过提示词"补课"

9.4 Trace Analyzer Skill

LangChain 将 Harness 迭代过程自动化为 Trace Analyzer Skill:

Text Only
流程:
  1. 从 LangSmith 获取实验追踪记录
  2. 并行启动错误分析代理
  3. 主代理综合发现结果与改进建议
  4. 汇总反馈并对 Harness 进行针对性调整

注意:过度拟合特定任务的修改不利于泛化,并可能导致其他任务出现性能倒退。


十、实战练习

练习 1:实现一个简单的 Generate-Validate 循环

构建一个最小化的 Generate-Validate 系统,用于代码生成任务:

Python
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}

请审查以下代码,判断是否满足任务要求。
必须指出具体的问题,不要笼统地说"看起来不错"。

代码:
{code} ```"""

Text Only
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} ===")

Text Only
    # 生成
    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:

Python
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
💡 参考答案
Python
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 设计评估标准:

💡 参考答案
Python
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:思考题

  1. 为什么评估器(Evaluator)要和生成器(Generator)分离? 如果用同一个 Agent 既生成又评估,会出现什么问题?

  2. Harness 的"保质期"是什么意思? 请用具体例子说明模型升级如何影响 Harness 设计。

  3. 渐进式披露(Progressive Disclosure)和 Just-in-Time 有什么区别? 请结合 Agent Skills 的设计说明。

  4. 在什么情况下应该使用上下文重置而非上下文压缩? 请分析各自的适用场景。


总结

关键要点

要点 说明
三层演进 Prompt → Context → Harness,从"表达"到"供给"到"执行"
Generate-Validate 生成与评估必须分离,避免自我肯定偏差
上下文重置 长任务中比压缩更激进的策略,配合结构化交接
状态管理 任务看板 + 工作记忆 + 状态机,防止 Agent 漂移
Harness 迭代 每次只改一个组件,控制变量,科学评估
解耦架构 Session + Harness + Sandbox 三大抽象,从"宠物"到"牛群"

核心洞察

Harness Engineering 的本质是:将模型天生参差不齐的智能,塑造成能处理我们所关心任务的形态。 它是一门关于系统的学问——围绕模型构建工具,以优化任务性能、Token 效率和延迟等目标。

随着模型性能提升,有趣的应用组合空间并不会缩小。相反,它会不断迁移,而 AI 工程师的有趣任务正是持续发掘下一个新颖的组合方式。

延伸阅读


⚠️ 核验说明:本页内容基于 Anthropic 和 LangChain 公开发表的工程博客、Datawhale 开源教程编写。若涉及外部模型、API 或版本号,请以官方文档为准。

最后更新日期:2026-04-21