目录
[一、一个公式讲完 Agent 是什么](#一、一个公式讲完 Agent 是什么)
[二、上下文五组件:Agent 每次推理时到底看到了什么](#二、上下文五组件:Agent 每次推理时到底看到了什么)
[实测结果(Kimi K3,真实 API 运行)](#实测结果(Kimi K3,真实 API 运行))
[三、ReAct 循环:Agent 是怎么跑起来的](#三、ReAct 循环:Agent 是怎么跑起来的)
[四、从经验中学习:Q-learning 与 LLM 的正面对决](#四、从经验中学习:Q-learning 与 LLM 的正面对决)
[选手一:表格型 Q-learning(更新参数)](#选手一:表格型 Q-learning(更新参数))
[选手二:LLM 上下文学习(把经验留在输入里)](#选手二:LLM 上下文学习(把经验留在输入里))
[六、Harness 工程:真正的竞争力在模型之外](#六、Harness 工程:真正的竞争力在模型之外)
[6.1 什么是 Harness:马具,而非缰绳锁链](#6.1 什么是 Harness:马具,而非缰绳锁链)
[6.2 五要素:两个让它能做事,三个让它不做错事](#6.2 五要素:两个让它能做事,三个让它不做错事)
[6.3 Harness 会被模型吃掉吗:苦涩的教训的务实解法](#6.3 Harness 会被模型吃掉吗:苦涩的教训的务实解法)
[6.4 编排取舍:守住边界,把决策空间还给模型](#6.4 编排取舍:守住边界,把决策空间还给模型)
[6.5 护栏三层与人工干预](#6.5 护栏三层与人工干预)
[6.6 三条原则与一条演进主线](#6.6 三条原则与一条演进主线)
一、一个公式讲完 Agent 是什么
AI Agent 的最小工程实现可以用一个简洁的公式表达:
Agent = LLM + 上下文 + 工具
换一种更直观的说法:大脑 + 眼睛 + 手脚。
|----|--------|------------|-------------------------|
| 直觉 | 实现组件 | 学术概念 | 含义 |
| 大脑 | LLM | 策略(Policy) | 决定「下一步做什么」的决策内核 |
| 眼睛 | 上下文构造 | 观察与历史 | 把环境返回的观察与已有历史组织成决策所需的信息 |
| 手脚 | 工具与适配器 | 观察/行动接口 | 规定 Agent 能读什么、能做什么 |
这里有一个容易被忽略的边界:Environment(环境)不在公式里。Agent 与环境是闭环交互的两方------环境返回观察,Agent 结合上下文选择行动,行动改变环境,产生新的观察,循环往复。这是理解一切 Agent 行为的最小结构。
而进入生产环境后,这个公式会展开成:
Agent = Model + Harness
Harness = 上下文管理 + 工具接口 + 约束 + 验证 + 纠正
前两项让 Agent「能做事」,后三项让 Agent「不做错事」。一个能跑的 Demo 和一个可靠的产品之间的鸿沟,全在 Harness 里。
二、上下文五组件:Agent 每次推理时到底看到了什么
从 API 视角看,每次调用 LLM 的上下文由五部分构成:
静态前缀(不变):
系统提示词(System Prompt) ------ Agent 的「岗位说明书」
工具定义(Tool Definitions) ------ 有哪些工具、参数格式
动态轨迹(随交互增长):
用户消息(User Messages)
模型回复(Assistant Messages)------ 可含 reasoning / content / tool_calls
工具执行结果(Tool Results)
验证每个组件是否不可或缺,最直接的方法是消融实验(Ablation Study):像医生排查病因一样,一次只去掉一个组件,看系统哪里坏掉。
消融实验设计:上下文的关键作用
我给 Agent 设计了一个需要「解析 PDF → 多次货币换算 → 计算汇总」的财务任务,然后用同一个模型跑五组对照。
消融不是嘴上说说,代码里每一处「移除」都有明确的落点:
python
class ContextMode(Enum):
FULL = "full" # 完整上下文(基线)
NO_HISTORY = "no_history" # 移除历史消息
NO_REASONING = "no_reasoning" # 移除思考过程
NO_TOOL_CALLS = "no_tool_calls" # 移除工具定义
NO_TOOL_RESULTS = "no_tool_results" # 移除工具执行结果
四种消融的代码实现,各自对应一个巧妙的手术位置:
python
# 消融①:no_tool_calls ------ 请求里直接不带 tools 参数,
# 模型「不知道」世界上存在任何工具
if self.context_mode != ContextMode.NO_TOOL_CALLS:
request_data["tools"] = self._get_tools_description()
request_data["tool_choice"] = "auto"
消融②:no_tool_results ------ 工具照常执行,但结果消息被掏空。
API 协议要求 tool 消息必须存在,所以只能让它携带空内容
if self.context_mode != ContextMode.NO_TOOL_RESULTS:
tool_msg = {"role": "tool", "tool_call_id": tool_call.id,
"content": json.dumps(result, default=str)}
else:
tool_msg = {"role": "tool", "tool_call_id": tool_call.id,
"content": self.hidden_result_content} # 空串
消融③:no_reasoning ------ 写回轨迹前剥离 reasoning_content
if self.context_mode == ContextMode.NO_REASONING and 'reasoning_content' in msg_dict:
msg_dict.pop('reasoning_content')
消融④:no_history ------ 只发送系统提示词 + 当前任务,
此前所有 ReAct 步骤全部不发送
def _prepare_messages_for_api(self):
messages = self.conversation_history
if self.context_mode != ContextMode.NO_HISTORY:
return messages
windowed = [m for m in messages if m.get("role") == "system"]
user_indices = [i for i, m in enumerate(messages) if m.get("role") == "user"]
windowed.append(messages[user_indices[-1]])
return windowed
实测结果(Kimi K3,真实 API 运行)
|-----------------|-------|------|-------|-------------------------------------|
| 实验组 | 迭代轮数 | 工具调用 | 重复行动 | 结果(outcome) |
| full(基线) | 3 | 4 | 否 | correct:正确完成 |
| no_history | 5(触顶) | 15 | 是 | no_terminal_response:反复重调工具直到耗尽预算 |
| no_reasoning | 3 | 4 | 否 | correct:没有测出退化 |
| no_tool_calls | 1 | 0 | 否 | 模型声明「会话中没有可用的换汇工具」 |
| no_tool_results | 5(触顶) | 9 | 是 | 盲目重调换汇工具,始终不收敛 |
这组结果里有三个值得单独说的发现:
发现一:去掉了思考过程,居然没坏。 直觉预期是「无思考过程 → 决策不连贯」,但实测与基线无异。想通了也简单:当推理内容可以从工具结果重建时,把它从历史中丢掉几乎没有代价。「每个组件都不可或缺」这类论断必须实测------换一个模型、换一个任务,结论完全可能不同。
发现二:「给出了回答」不等于「完成了任务」。 这是整个实验最锋利的一刀。去掉工具定义后,模型并没有沉默------它照样会给出一份格式工整、语气笃定的答案,只是汇率数据来自参数记忆。实验里去掉提示词中「不要自行估计汇率」这一句约束后,同一个模型报出了 $9,587,333.33------与工具汇率表只差 0.16%,但汇率是它自己编的,附带的「基于所假设汇率」小字说明,只看总数的读者根本不会注意。
为了让这种失败无处遁形,我专门写了 grounding.py 做可依据性检查:
python
# 判断标准:答案里的每个数字,是否能在「任务文本 + 工具观测」里找到出处?
known = extract_quantities(task_text) + list(observations)
unsupported = [q for q in quantities if not matches_any(q, known)]
它的哲学是:可依据性与正确性是两条独立的轴。一个模型没看过任何观测却报出了正确的总数,同样不是「读到」的。
发现三:隐藏观测的方式本身会影响实验结论。 把工具结果替换成可见占位符 [Tool result hidden due to context mode] 时,模型 4 次里有 2 次明确拒绝作答;而静默掏空内容(标准做法)时,7 次里 6 次盲目跑到迭代上限,还会用 1 EUR→USD 这样的试探值反复戳工具,想知道为什么什么都没回来。占位符本身是一个信号------拿走一个信号,和拿走观测,不是同一个消融。
三、ReAct 循环:Agent 是怎么跑起来的
三大组件靠 ReAct(Reasoning + Acting)循环串联:想 → 做 → 看 → 想 → 做 → 看,直到任务完成。
Agent 的最小运行骨架(Python 风格伪代码):
python
trajectory = [user_request]
repeat:
context = stable_prefix + trajectory # 静态前缀 + 轨迹
decision = Model(context) # 大脑决策
trajectory.append(decision)
if decision has no tool call:
return decision.answer # 没有工具调用 = 任务完成
for call in decision.tool_calls:
validated_call = Harness.validate(call)
observation = Environment.execute(validated_call)
trajectory.append(observation) # 观察追加进轨迹
以「多币种收入汇总」为例,3 次迭代、4 次工具调用就走完全程:
第 1 轮 user: "计算年度总收入:Q1 $2.5M, Q2 €2.1M, Q3 £1.8M"
assistant: reasoning("需要把 EUR/GBP 转 USD")
→ convert_currency(2100000, "EUR", "USD")
→ convert_currency(1800000, "GBP", "USD")
tool: EUR→USD: 2,282,608.70 | GBP→USD: 2,278,481.01
第 2 轮 assistant: reasoning("已有汇率,调代码解释器汇总")
→ code_interpreter("total = 2.5M + 2.28M + 2.28M")
第 3 轮 assistant: content("年度总收入 $7,061,089.71 ...") ← 终止
关键特性就一条:上下文不断追加。每轮调用 LLM 都能看到完整轨迹,所以它清楚任务进行到哪一步、试过什么、得到了什么。第二节消融实验之所以成立,正是因为轨迹是 ReAct 的血液------抽掉其中一种成分,循环的病症就会立刻显现。
四、从经验中学习:Q-learning 与 LLM 的正面对决
我还做了另一组对比实验:把两种「学习」放进同一个游戏------一个规则不对玩家公开的文本寻宝游戏。
python
# 隐藏机制(Agent 事先不知道)
self.color_key_mapping = {"red key": "red door", ...}
self.weapon_effectiveness = {
"rusty sword": ["weak guard"],
"silver sword": ["weak guard", "strong guard", "dragon"]}
self.crafting_recipes = {
frozenset(["rusty sword", "magic crystal"]): "silver sword"}
选手一:表格型 Q-learning(更新参数)
python
# Q(s,a) <- Q(s,a) + α·[r + γ·max Q(s',a') - Q(s,a)]
current_q = self.q_table[state][action]
target = reward if done else reward + self.discount_factor * max_next_q
self.q_table[state][action] = current_q + self.learning_rate * (target - current_q)
它把「门 / 钥匙 / 剑」当作无意义符号,只能靠 ε-贪婪探索暴力试错。
选手二:LLM 上下文学习(把经验留在输入里)
python
def _build_context(self, current_state, available_actions):
...
context.append("\n=== PAST EXPERIENCES ===")
context.append("** Successful actions:")
for pattern in successful_patterns[-10:]: # 最近 10 条成功经验
context.append(pattern)
context.append("** Failed actions to avoid:")
for pattern in failed_patterns[-5:]: # 最近 5 条失败经验
context.append(pattern)
context.append("\n=== CURRENT SITUATION ===")
...
模型阅读行动历史,提出关于规则的假设(「红色钥匙开红门」「锈剑 + 魔晶能合成银剑」),据此选择下一步。
同一个游戏,两种命运的实测对比
Q-learning 学习曲线(本地实测,10000 局,约 3 秒跑完):
|------------------|---------|-------|-------|-------|-------|
| 训练局数 | 7000 之前 | 7000 | 8000 | 9000 | 10000 |
| 胜率(近 1000 局滑动窗口) | ≈ 0% | 97.0% | 99.6% | 99.8% | 98.1% |
前 7000 局几乎全败------它在几千局里连「钥匙能开门」这件事都没摸出来,直到探索概率衰减到足够低、偶然通关的奖励才能沿着 Q 表传播开。训练后 100 局贪婪评估胜率 100%,平均 12 步通关。
而 Kimi K3 第一局就通关了:17 步,17 次 API 调用零错误,共消耗 28,242 tokens。
|------|-------------------|---------------------------|
| 维度 | Q-learning | LLM 上下文学习 |
| 样本效率 | 需要约 10000 局试错 | 第一局即可通关(高出 2~3 个数量级) |
| 学习机制 | 统计式更新 Q 表 | 推理 + 预训练先验理解「钥匙开门」的概念结构 |
| 训练成本 | 10000 局 ≈ 3 秒(本地) | 单局 17 次推理调用 ≈ 7 分钟(API) |
| 记忆形式 | Q 表(状态→动作价值) | 上下文里的经验列表(任务结束即消失) |
这正好呼应 Shunyu Yao 在《The Second Half》里的论断:当「能不能解」不再是问题时,竞争转向「多高效」。LLM 不是更好的 RL,两者在完全不同的时间尺度和成本结构上工作------上下文负责临场适应,参数更新负责能力内化,理解各自边界比分高下更有价值。
五、文生图实验:适配层的兴衰
第三组实验回答另一个问题:工作流里那些「给模型短板打补丁」的节点,到底还有没有存在价值?
用户说「帮我画一个 AGI 实现以后程序员的工作场景」,而 Stable Diffusion 类模型只听得懂逗号分隔的英文 tag。于是经典工作流要在中间安排一个「提示词改写」节点:
REWRITE_SYSTEM_PROMPT = """\
你是 Stable Diffusion 风格的文生图提示词专家。...
要求:
prompt 字段:逗号分隔的英文 tag,先主体后细节,包含质量词
negative_prompt 字段:逗号分隔的英文负面提示词
style_notes 字段:一句中文,说明你这次改写做了哪些关键增补/取舍
"""
我让同一句口语化中文需求走三条路线对照:
工作流路线: 用户需求 → 节点1: kimi-k3 改写 → 节点2: 通义万相 wan2.2-t2i-flash → 图片
原生路线 A: 用户需求 → gemini-3-pro-image(一次调用直接出图)
原生路线 B: 用户需求 → gpt-image-2(一次调用直接出图)
正式运行 5 句需求 × 3 条路线 = 15 次,15/15 全部成功。最有信息量的是「降噪耳机海报」用例------用户明确指定了海报文案「深夜独处也清净」:
|------------------|-----------------------------------------------------------------------------------------------------|
| 路线 | 结果 |
| 工作流(改写 + 万相) | 产品图质感不错,但整张图没有任何文案 ------改写节点把 text, logo 塞进了 negative_prompt(它担心旧模型生成乱码文字),用户的核心需求在改写环节就被丢弃了 |
| 原生 Nano Banana 2 | 指定文案一字不差渲染为大标题,产品与文案融合自然 |
| 原生 GPT-Image 2 | 文案准确,排版整齐,黑色背景高级感强 |
而宽泛需求(「AGI 之后的程序员场景」)下,改写节点确实注入了有观点的叙事(程序员悠闲喝咖啡、机器人写代码),但 GPT-Image 2 直接产出了一张带中文标注的概念图解------标题就是「AGI 驱动的时代,程序员的工作重点从编写代码转向创造价值」。
实验的结论很冷静:改写节点补的始终是模型能力短板------短板从「听不懂格式」变成了「缺少观点」,但最强的原生模型连观点也能自己补。 由此可以总结出一条规律:few-shot 示例被指令微调内化了,JSON 格式修复被结构化输出内化了,文生图提示词改写正在被原生多模态能力吃掉------每一轮内化,消灭的都是「翻译」和「脚手架」这类适配层代码。模型还做不稳的,Harness 先补上;模型每内化一层,Harness 就卸下一层。
六、Harness 工程:真正的竞争力在模型之外
前四节回答了 Agent 是什么、怎么跑起来,第五节看到了适配层如何随模型变强而消亡。这一节展开我在整个学习过程中认为最值得深挖的主题:Harness------模型之外、Agent 边界之内的那层工程,也是「能跑的 Demo」与「可靠的产品」之间那道鸿沟的全部答案。
6.1 什么是 Harness:马具,而非缰绳锁链
Harness 原意是马具------套在马身上的缰绳与挽具。关键在理解它的目的:不是为了限制马奔跑,而是把力量引导到正确的方向上。放到 Agent 语境里:模型是那匹强大但不可预测的马,Harness 是把它的能力转化为可靠任务执行的工程外壳。
更精确的界定:Harness 不是「模型之外的一切」,而是 Agent 边界内、模型之外的运行与治理层。拿两个容易混淆的例子划清边界:
- 工具定义、调用适配器、沙盒的权限控制与重置机制 → 属于 Harness
- 沙盒内随行动变化的文件与进程、外部数据库、网页、用户、物理世界 → 属于 Environment
还有一个反直觉的点:物理部署位置不能决定概念归属。即使仿真环境与 Agent 跑在同一个进程里,它仍然是 Environment。Harness 可以创建、隔离、代理一个环境,但不因此拥有环境自身的状态与转移规律。
6.2 五要素:两个让它能做事,三个让它不做错事
生产形态的 Harness 由五项职责构成:
|--------------|-----------------------------|--------------------------|
| 要素 | 职责与核心原则 | 实际例子 |
| Context 上下文 | 信息要充分:让 Agent 在每个决策点都有足够依据 | 系统提示词、知识库、Agent 状态栏 |
| Tools 工具接口 | 接口要清晰:命名直观、参数有例子、边界有说明 | MCP 工具、代码解释器、搜索工具 |
| Constrain 约束 | 故障安全默认值:所有能力默认关闭,必须显式开放 | Claude Code 中每个工具默认需用户授权 |
| Verify 验证 | 自动判断对错:只看结构化数据,不看模型自由文本 | Linter、类型系统、工具结果校验 |
| Correct 纠正 | 确认无法恢复前,不把中间态暴露给用户 | 静默重试、接续生成、熔断后转人工 |
有两处细节我认为最能体现工程智慧:
Verify 为什么只看结构化数据? 因为模型自由生成的文本可能已被提示注入操纵------攻击者可以让模型「说」出验证者想听的正确答案,但工具返回的 JSON 字段伪造不了。信任结构化证据,不信任叙事。
Correct 为什么强调「静默」? 工具调用失败时先静默重试,而不是把半成品抛给用户;错误连续发生时熔断------就像家里电路短路时保险丝自动跳闸,防止整个系统崩溃------最后才回退到人工判断。
五个功能构成一个闭环:上下文与工具让 Agent「能做事」;约束预防错误,验证发现偏差,纠正让闭环得以形成,三者让 Agent「不做错事」。缺任何一环,系统都有可靠性缺口。而这个闭环的重心,正是行业从玩具到产品的分水岭------早期框架基本只做前两项,生产级系统的绝大部分代码在做后三项。
把闭环写成最小控制循环(伪代码):
python
observation = Environment.observe()
trajectory = [observation]
while true:
actions = Model(Harness.build_context(trajectory))
if len(actions) == 0:
break
allowed_actions = Harness.constrain(actions) # 约束:滤掉越权操作
observation = Environment.apply(allowed_actions)
if not Harness.verify(Environment): # 验证:只信结构化证据
observation = Harness.correct(Environment) # 纠正:静默修复或回退
trajectory.append(allowed_actions, observation)
以 Claude Code 为例,它的 Harness 中绝大部分代码是约束、验证与纠正,而非上下文与工具本身:流程状态管理(追踪执行到哪一步)、多层上下文压缩(信息过载时自动精简)、权限分类(哪些操作需要用户确认)、熔断器、错误恢复(捕获异常 → 回滚到稳定状态 → 重试或交还人类)。
6.3 Harness 会被模型吃掉吗:苦涩的教训的务实解法
Rich Sutton 在《The Bitter Lesson》里回顾了 AI 研究七十年反复上演的一幕:研究者一次次把自己对领域的理解编码进系统,短期见效,长期却总是输给能随算力与数据规模扩展的通用方法。以此衡量,一个尖锐的问题悬在头上:Harness 里的约束、验证与纠正,有多少属于「人类先验」,注定会被模型内化?
我的理解是八个字:方向认同,节奏务实。
- 方向上,模型确实在持续吃掉 Harness:工具调用、长程规划都曾靠外部编排,如今已是模型的原生能力;
- 节奏上,「吃」的过程比想象中慢:训练以月计,模型也无法一次内化真实业务中所有的约束与偏好。
所以结论是:模型此刻的能力边界,就是 Harness 此刻的价值所在。Harness 工程不是对苦涩的教训的抵抗,而是这一教训在工程时间尺度上的实践------模型还做不稳的,Harness 先补上;模型每内化一层,Harness 就卸下一层,转而兜底新的能力前沿。第五节文生图实验里「改写节点被原生多模态吃掉」的过程,就是这个规律的一次具体演出。
6.4 编排取舍:守住边界,把决策空间还给模型
Harness 里关于「结构该给多少」的判断,我总结为一条主原则 + 一个反例。
主原则是从简单到复杂:先优化单次 LLM 调用;任务能清晰分解为固定子任务时,用工作流;只有需要动态决策和灵活执行路径时,才上自主 Agent。Agent 系统用延迟和成本换任务性能,这交换是否值得要时刻掂量。
反例是一开始就画大图:为「从百万条聊天记录提炼记忆」设计「切分 → 提取 → 核验 → 解析 → 整理 → 合并」六段流水线,外加事实图、覆盖账本、不可变版本------每个部件单看都有道理,组合起来低效又不可靠。因为复杂工作流的执行拓扑是固定的:遇到新的例外就继续加节点,架构越来越复杂,通用性越来越差,模型原本能凭上下文处理的语义判断反而被写死。
Manus 用一句 "Less structure, more intelligence" 概括了正确姿势:先给能力足够的 Agent 清晰的目标、必要的上下文和可组合的工具;程序只固化「必须始终成立的边界」------权限、不得覆盖原始材料、原子发布。只有业务约束本身要求,或评估反复暴露同类失败时,才把对应步骤升级为专门的验证器或确定性流程。一句话:好的结构不是替 Agent 预演全部思考,而是守住边界,把边界内的决策空间还给模型。
实践中两种模式还常常混合:关键合规流程走工作流保可靠性,灵活决策部分切自主模式;或者先由自主 Agent 把工作流写出来、再由工作流去执行------生成阶段保留面对未知任务的灵活性,执行阶段退回确定性。
6.5 护栏三层与人工干预
护栏按被绕过的难度(而非请求处理顺序)分三层,越往下越不依赖模型自己的判断:
- 上下文层:管模型能看到什么------相关性分类器、安全分类器、内容审核、规则黑名单。结构性上限:处在同一上下文里的 Agent 很难判断自己是否已被注入,所以这层只能降低攻击成功率,给不出保证;
- 执行层:管模型能做什么------工具风险评级(按可逆性、权限、财务影响分低/中/高),高风险操作需额外审查或人工确认。关键:复核必须由上下文之外的机制完成(独立审查进程、最小权限凭证、沙盒隔离),否则会和被注入的 Agent 一起沦陷;
- 数据层:管世界最终能被改成什么------行级安全策略、约束校验、受控视图。即使提示注入得手、代码漏写权限判断,越权操作仍会在数据层被拒绝。
人工干预(Human in the Loop)有两个标准触发条件:超过失败阈值 (重试次数或操作次数超限即升级人工)和高风险操作(大额退款、不可逆删除等,至少在团队对可靠性建立信心之前全程人工监督)。
6.6 三条原则与一条演进主线
工程实践层面,Anthropic 的经验总结为三条:
- 保持简单 ------ 直接的 API 调用优于复杂框架,每多一层抽象都是将来调试的新盲区;
- 保持透明 ------ 黑箱里的错误一旦发生,外部观察者既无法定位也无法纠正;
- 设计好工具接口(ACI) ------ 从 Agent 视角设计接口,用「防呆」设计让错误无法发生(SIM 卡缺角只有一个插法、微波炉门没关绝不加热)。
而整个领域的视野在一路外扩:提示工程 → 上下文工程 → Harness 工程 → Loop 工程 → Graph 工程 ------每一层都包含前一层而不是替代它。当各家模型的能力越来越接近、不再是决定性差异时,竞争优势就转移到了模型之外的工程实践。LangChain 在 Terminal Bench 2.0 上把得分从 52.8% 提到 66.5%(排行榜 30 名开外跃升至前 5),换的不是模型,而是 Harness:让 Agent 自动检查执行结果、检测重复循环、优化思考策略。这就是本章标题所说的「模型之外的竞争力」。