Agent 工程学习站 做棵大树 出品 · beatree.cn 免费 · 无需登录 · 进度存本机
II 版 · Harness 工程 第 01 章 Model + Harness = Agent
II · Harness 工程 01 / 22 入门

Harness 是什么

Model + Harness = Agent
预计阅读 24 分钟
难度 入门
关键词 Harness · 能力地图
本机状态 未读

「除模型本身以外的所有工作,都属于 Harness 的范畴」——这句话看起来像一句甩锅式的定义,实际上它划出的地盘大得惊人:从工具契约到上下文预算,从失败恢复到权限沙箱,从循环终止到评测闭环。这一章给出完整的能力地图,后面七章都挂在这张图上。

编辑部注 · 本刊读者若只读一章,请读这一章。它决定了后面所有内容在你脑子里是「一堆知识点」还是「一张地图」。

先看一个被反复引用却很少被真正拆解的等式:Model + Harness = Agent。同一个模型,套上不同的 Harness,能做出效果差异巨大的产品。理解这个等式,就理解了为什么「模型能力」不是产品效果的唯一变量——也理解了为什么这个岗位值得存在。

一、把这个等式拆开看

模型提供三种能力:理解输入、生成内容、在给定格式下输出结构化结果。它不能做的是:记住上一次对话、访问外部世界、判断自己的输出对不对、在失败后重试、控制自己做事的顺序、以及在出事之后被追责。

这些「不能做」的事情,就是 Harness 的地盘。换个更工程化的说法:

Harness 是把一个概率性的文本生成器,包装成一个可交付、可观测、可控制、可恢复的执行系统的全部工程。

「可交付」意味着用户拿到的是结果而不是一段文字;「可观测」意味着每一步都能查;「可控制」意味着越权行为会被拦住;「可恢复」意味着中途断了能接着跑。这四件事没有一件是模型本身提供的。

Harness 能力地图 越靠上的层越贴近用户,越靠下的层越贴近模型。每一层都可能成为效果的瓶颈。 ⑥ 交付层 Deliverables 产物形态(报告 / 表格 / 代码 / 网页)、分享与权限、版本与回滚、导出与归档   ⑤ 编排层 Orchestration 任务分解、Subagent 派生、并行与串行、结果汇总、人工介入点、长任务状态机与检查点   ④ 记忆与知识层 Memory & Knowledge 长期记忆写入与召回、知识建模(实体/事实/推断)、检索与引用、冲突消解、可删除性   ③ 上下文层 Context Engineering ★ 效果上限的真正来源 预算分配、什么常驻什么按需、压缩与摘要、状态外置、顺序与前缀稳定性   ② 工具层 Tools as Contracts 工具描述、参数 schema、错误分类与回喂、结果裁剪、MCP 接入、幂等与副作用标记   ① 循环层 The Loop 思考-行动-观察循环、终止条件、轮数与预算上限、循环检测、流式解析与卡死恢复   模型 Model —— 提供理解与生成,其余全部由上面六层提供 越权防护、审计、可观测性贯穿所有层,不单独成层
图 1 Harness 能力地图。注意第 ③ 层的标注:上下文层是效果上限的真正来源——工具再多、编排再花,模型看到的上下文决定了它能做出什么判断。这也是后面第七、八章反复回到这一层的原因。

二、为什么「同一模型不同效果」是常态

一个常被忽略的事实:把 GPT / Claude / DeepSeek 换进同一个 Harness,效果差异往往小于「同一个模型换一个 Harness」。原因有三:

第一,工具描述的质量决定了模型会不会用、用得对不对。 同一个查询接口,描述写成「查询数据」,写成一个带参数说明、取值范例、返回结构示例的完整契约,工具调用成功率可以差出一倍。

第二,上下文里的信息决定模型能做什么判断。 如果历史全量堆进上下文,模型注意力被稀释;如果该给的信息没给(比如「这个字段昨天刚改过口径」),模型只能猜。

第三,失败处理决定系统能不能跑完。 同一个模型,遇到工具报错就整轮失败,和把错误分类后回喂让它换参数重试,任务完成率的差距是量级级别的。

模型决定上限,Harness 决定你能不能摸到那个上限。而绝大多数产品离上限还差得远,所以 Harness 才是主战场。本刊编辑部

三、一个最小可用的 Harness 应该有什么

不需要一上来就建六层。但如果只有一天时间写一个能用的 Harness,下面这六件事缺一不可:

#必需能力缺了会怎样
1循环 + 五类终止条件要么永不停止(烧钱),要么提前放弃(完不成任务)
2工具契约 + 参数校验模型调用失败率高,且你在日志里看不出为什么
3错误分类与回喂任何一次工具报错都会让整轮任务失败
4上下文预算与压缩跑十几轮就撑满窗口,成本平方级上涨
5全链路 trace出问题只能靠猜,无法定位到底哪一步坏掉
6副作用操作确认 / 最小权限模型一次误判就可能直接改坏线上数据
minimal_harness.pypython
"""
最小可用 Harness 骨架。
刻意把六层压缩成 40 行,只为展示「Harness 到底在做什么」。
生产环境每一行都会膨胀成好几十行,但骨架不变。
"""
from dataclasses import dataclass, field
@dataclass
class Budget:
    max_turns: int = 12            # 轮数上限
    max_tokens: int = 120_000      # 累计 token 上限(成本闸)
    max_seconds: float = 300.0     # 挂钟时间上限
    turns: int = 0
    tokens: int = 0
    trace: list = field(default_factory=list)   # 全链路可观测
    def spent(self) -> bool:
        return self.turns >= self.max_turns
class Tool:
    """工具 = 契约。描述、schema、是否幂等、是否有副作用,四者必须齐全。"""
    def __init__(self, name, description, schema, handler, side_effect=False):
        self.name, self.description = name, description
        self.schema, self.handler = schema, handler
        self.side_effect = side_effect      # 有副作用的工具执行前必须确认
TOOLS = {
    "search": Tool("search", "按关键词检索站内文章,返回 [{title,url,snippet}]",
                   {"q": "string"}, lambda **kw: [{"title": "示例", "url": "…"}],
                   side_effect=False),
    "write_file": Tool("write_file", "把内容写入指定路径,会覆盖同名文件",
                       {"path": "string", "content": "string"},
                       lambda **kw: "written", side_effect=True),
}
def build_context(task: str, history: list, observations: list) -> list:
    """
    第 ③ 层的核心:决定模型看到什么。这里用最朴素的三段式,
    真实系统要在这里做预算分配、摘要压缩与状态外置。
    """
    tool_docs = "\n".join(f"- {t.name}: {t.description}" for t in TOOLS.values())
    return [
        {"role": "system", "content": f"你是任务执行器。可用工具:\n{tool_docs}"},
        {"role": "user", "content": f"任务:{task}"},
        *history,
        *observations,
    ]
def classify_error(exc: Exception) -> str:
    """错误分类决定回喂方式,这是第 ②③ 层交界处最容易被跳过的一步"""
    name = type(exc).__name__
    if name in ("TimeoutError",):
        return "TIMEOUT:可以原样重试一次,超时通常是瞬时的"
    if name in ("ValueError", "KeyError"):
        return "BAD_ARGS:参数有问题,必须改参数后重试,重试原参数无意义"
    if name in ("PermissionError",):
        return "FORBIDDEN:没有权限,重试无用,应上报请求授权"
    return "UNKNOWN:记录并交由上层决定"
def run(task: str, budget: Budget) -> dict:
    history, observations = [], []
    while not budget.spent():
        budget.turns += 1
        # 1. 组装上下文(决定模型看到什么)
        messages = build_context(task, history, observations)
        # 2. 调用模型,要求结构化输出
        plan = call_model(messages, temperature=0)      # 占位
        budget.trace.append({"turn": budget.turns, "plan": plan})
        # 3. 终止判断:模型认为完成
        if plan.get("action") == "finish":
            return {"status": "done", "result": plan.get("answer"),
                    "turns": budget.turns, "trace": budget.trace}
        # 4. 执行工具,带错误分类
        tool = TOOLS.get(plan.get("tool"))
        if tool is None:
            observations.append({"role": "user",
                                 "content": f"工具 {plan.get('tool')} 不存在,请从可用列表中选择。"})
            continue
        if tool.side_effect and not confirm(f"即将执行有副作用的操作 {tool.name},确认?"):
            observations.append({"role": "user", "content": "用户拒绝了该操作,请换方案或结束。"})
            continue
        try:
            result = tool.handler(**plan.get("args", {}))
            observations.append({"role": "tool", "content": str(result)[:2000]})
        except Exception as e:                            # 关键:错误也回喂
            observations.append({"role": "user",
                                 "content": f"工具执行失败[{classify_error(e)}]:{e}"})
    return {"status": "budget_exhausted", "turns": budget.turns, "trace": budget.trace}
这段代码里最值钱的三行
  • except Exception as e 之后把错误回喂给模型——绝大多数玩具实现会直接抛出异常终止;
  • classify_error() 把错误分成「可重试 / 必须改参数 / 重试无用」——决定后续行为的不是错误本身,而是分类;
  • tool.side_effect and not confirm(...)——在把能力交出去之前先设一道闸。

四、面试里怎么答「什么是 Harness」

这道题的标准陷阱是答成「Agent 的框架」。正确的答法是给一个判据,而不是一个描述:

推荐的答题结构

结论:Harness 是「把概率性文本生成器包装成可交付、可观测、可控制、可恢复系统」的全部工程,判据是——这件事模型自己做不到。

机制:按六层展开:循环 / 工具 / 上下文 / 记忆与知识 / 编排 / 交付,另外权限与可观测性贯穿全层。

代价与边界:Harness 做得越厚,延迟越高、调试越难、模型升级带来的收益越可能被掩盖。所以好的 Harness 要能随模型变强而变薄——那些「为了绕开模型弱点」的补丁应该在模型升级后被主动拆掉。

实践:举一个你自己的例子(比如把某个失败率高的链路从「加提示词」改成「改错误分类 + 回喂」)。

五、自测

本章自测第 3 题最容易答错
Q1下面哪一项不属于 Harness 的职责?
C。注意力计算的效率是模型与推理框架的事(FlashAttention、MLA、稀疏注意力),Harness 无法改变模型内部的计算方式。这是「研究问题 vs 工程问题」的一个典型分界:凡是需要改模型结构或训练才能做到的,都不属于 Harness。
Q2「同一个模型套不同 Harness 效果差异巨大」,最主要原因是什么?
B。Harness 不碰权重(排除 D),温度是次要变量(排除 A)。真正的差异来自三条链路:信息够不够(上下文)、能力好不好用(工具契约)、坏了能不能修(错误恢复)。这三条正好对应能力地图的第 ③② 层与失败处理。
Q3「好的 Harness 应该能随模型变强而变薄」这句话的含义是?
D。这是很有区分度的一题。很多 Agent 系统里堆着大量「当年因为模型不会 X 所以加了一层 Y」的补丁,模型升级后这些补丁不但没用,还会压制模型的真实能力(例如强制模板化的输出格式让更强的模型也无法发挥)。能主动清理这类技术债,是资深工程师的标志。

六、小结

  • 判据比定义重要:模型做不到的,就是 Harness 的。
  • 六层地图:循环 / 工具 / 上下文 / 记忆与知识 / 编排 / 交付,权限与观测贯穿全层。
  • 上下文层是效果上限的真正来源,工具层是失败率的主要来源,编排层是长任务能否跑完的关键。
  • Harness 要能变薄:为绕开旧模型弱点而加的补丁要能被识别并拆除。

下一章从最底下那层开始:循环该怎么写,尤其是——什么时候该让它停下来。

面试官会怎么问

共 3 条 · 其中 1 条高频 · 先自己答一遍,再展开对照

Harness 的定义是什么?边界在哪? 高频
一句话:Agent = Model + Harness,Harness 是"除模型本身以外的所有工作"
具体可以拆成七层:① 执行层(Agent Loop、终止条件、状态机);② 能力层(工具注册与契约、MCP 接入、工具执行与沙箱);③ 上下文层(预算分配、压缩、缓存友好组装、记忆);④ 编排层(规划、Subagent、多 Agent 通信);⑤ 治理层(权限、审计、提示注入防御、配额);⑥ 质量层(评测集、指标、trace、回归门禁);⑦ 适配层(多模型网关、参数 профиль、与训练团队的反馈回流)。
边界判断有个简单标准:如果是"让模型自己变强"的事,属于模型侧;如果是"让变强的模型能可靠地完成任务"的事,属于 Harness。

追问链

  1. 模型越来越强之后,Harness 会不会被吃掉?
  2. 这七层里你认为哪一层最难做?
加分点:答"会不会被吃掉"时给出双向判断:模型变强会吃掉通用能力,但私有上下文、工具生态、权限与安全、可评测性反而变得更重要。
为什么说 Harness 的难点不在"让它跑起来",而在"让它可评测、可回滚"?
因为"跑起来"是一次性的,"可评测、可回滚"是可持续的。
一个只在开发机上跑通过的 Agent,缺三样东西就无法成为产品:① 可观测——出了问题你拿不到完整链路(每轮的 prompt、工具调用、耗时、token),只能靠猜;② 可复现——没有 trace 就没有 replay,同一个 bug 你复现不出来,也就无法验证修好了没;③ 可安全变更——prompt 和工具描述本质上就是代码,没有版本化、灰度和一键回滚,每次改动都是在赌。
所以成熟的 Harness 有个特征:prompt 变更有版本号、有对照评测、有灰度门禁,而不是直接改线上。
你怎么判断一个 Agent 产品做得好不好?
我会看四个指标,而不是看 demo 有多惊艳:
① 真实任务成功率——在用户的真实任务分布上,能独立完成的比例是多少;
② 人工介入成本——需要用户接管、确认、纠正的次数,这直接决定它是不是负担;
③ 可恢复性——跑偏之后能不能回退、能不能断点续跑、会不会把副作用做重;
④ 单位任务成本——token 与时间的消耗。
一个重要的判断信号是:好的 Agent 产品会把"不确定性"显式暴露给用户(比如告诉你它删除了哪些文件、改了哪些内容、依据是什么),而不是假装自己一直很确定。
加分点:最后那句"把不确定性显式暴露"是品味题的高分表达。
答这类题的通用结构

结论先行(一句话给出取舍)→ 机制(为什么是这样,涉及哪条链路)→ 代价或边界(这么做放弃了什么)→ 你的实践或数字(真实场景里怎么落地)。 只讲机制不讲代价,是背题;只讲取舍没有数字,是空谈。

本章收尾 · Wrap up
掌握度自评
点一下给自己打分;低于 3 分建议加入复习队列
个人笔记 · Notes