作业解读一:从零实现一个 ReAct 式 Agent Harness
导语
做 Agent 应用的人常有一种「装修错觉」:模型是沙发,提示词是窗帘,工具是茶几 —— 软装换得勤,房子就总能焕然一新。CMU 11-768 的第一次作业(A1,官方仓库 cmu-agents/assignment-1)干的是相反的事:把你领进一间毛坯房,递上一台开源权重的 LLM(默认 DeepSeek-V4-Flash,走 OpenAI 兼容接口)和一个跑不通的国际象棋程序,然后说 —— 把承重墙砌起来。
在课程坐标里,A1 排在 LLM 基础与工具使用(tool use)的讲授之后,是第一次完整的「上手」作业。它回答的问题很朴素:剥掉各家 Agent 框架的包装,一个 Agent 到底靠几面墙撑着?这份笔记以伴读为立场:只拆设计思想、给路线建议,不含任何解答 —— 因为这份作业真正值钱的部分,必须由你的手指亲自输入。
核心概念梳理:四面承重墙
仓库把实现拆成几个部分:修 bug 的 CodeAgent、上下文压缩(context compaction)、会下棋的 ChessAgent,外加报告与观察实验。穿起来看,真正的承重结构是四面墙:基类抽象、终端场景、上下文压缩、程序化工具调用。墙墙相连,缺一面,房子就歪。
墙一:Agent 基类 —— 先修协议,再谈智能
基类(base class)看起来只是几行抽象,其实是整栋房子的地梁,管两件事。
第一件,消息协议。LLM 的上下文不是一张自由发挥的便签,而是一本有类型的账本:system、user、assistant、tool 各就其位。把提示词拼装抽象成独立函数,本质是承认:消息的先后与类型是协议(protocol),不是文采。工具消息跟在哪次发言之后、失败的调用如何入账 —— 这些序列化细节一旦出错,再聪明的模型也读不懂自己的历史。
第二件,有界循环。运行循环必须带步数上限(step limit)。任何没有刹车的循环都可能因为一句「再检查一遍」而无限打转,烧掉的是真实的 API 账单与沙箱费用。有界性不是性能优化,是工程底线。
这个抽象的价值在于「窄腰」(narrow waist):CodeAgent 与 ChessAgent 从同一副骨架长出来,日志、计费、评估只需写一次;之后换模型、换任务,动的都是壳,不是梁。示意性的伪代码长这样(不是作业代码):
while 未完成 and 步数 < N:
prompt = 按协议拼装(system, task, 历史)
回复 = LLM(prompt)
调用 = 解析工具调用(回复) # 可能失败
if 调用非法: 把错误当作观察写回历史; continue
历史 += 执行(调用)
墙二:CodeAgent—— 终端是驾校里最诚实的路段
为什么拿「在终端里修 bug」当第一个实战场景?因为它同时满足三个苛刻条件:真实(一个真的仓库、一个真的 bug)、可验证(跑测试即知对错)、通用(ls、grep、cat、pytest 这套工具放之四海皆准)。终端之于 Agent,就像城市道路之于新手司机:路况复杂,但规则清楚,而且导航 —— 报错信息 —— 从不撒谎。
这个场景也是 ReAct 的天然舞台:先推理(「问题大概在那个文件」)、再行动(grep 一下)、看观察(搜索结果)、再推理。修 bug 天然是多步任务:定位、复现、修改、验证、宣布收工,一步走完的几乎不存在。
而它教的最深一课是可恢复性(recoverability):模型一定会吐出格式损坏的 JSON,一定会调用不存在的工具。新手让进程当场崩掉;老手把错误当成一条普通的观察(observation)喂回循环,让下一轮自我纠正。「错误也是输入」,这六个字值回作业的票价。SWE-agent 把这套终端交互设计命名为 agent-computer interface,导读部分细说。
墙三:Compaction—— 上下文不是硬盘,是工作台面
上下文窗口(context window)常被误解成存储空间,仿佛越大越好。它其实更像木工的工作台面:台面再大也有限,而摊满杂物的台面上,你恰恰找不到那颗正要拧的螺丝。Lost in the Middle 的实验表明,关键信息被埋在长上下文的中段时,模型表现显著下滑 —— 台面上的杂物不只是占地方,还会主动坏事。
所以上下文压缩是真实工程痛点:任何跑长任务的 Agent,其轨迹(trajectory)都会无限增长。A1 在这里有两个值得咀嚼的设计。其一,摘要必须由模型自己生成,不许调服务商现成的压缩接口 —— 逼你亲口承认「摘要本身也是一次有损调用」。其二,并非一切皆可压:系统消息、任务描述与最近的动作必须原样保留。直觉很木工:旧图纸收进抽屉(摘要归档),手头的图纸和正在拧的螺丝必须留在台面上(逐字保留)。
触发时机可以写成一条不等式:
作业还要求在 SWE-bench 任务上对比压缩前后的表现。省了多少 token、掉了多少成功率 —— 这笔账,每个 Agent 团队每周都在算。
墙四:程序化工具调用 —— 把工具从「说明书」变成「插座」
老办法是把工具说明书贴进提示词:「你可以执行命令,可以查看棋盘……」,模型自由发挥,程序再从自由文本里猜它想干什么。程序化工具调用(programmatic tool calling)则给每个工具一份模式(schema):名字、参数、类型、取值范围;模型以结构化格式发起调用,程序先校验、后执行。
差别至少有三层。可靠性:UCI 记谱(UCI notation)这类受控词汇表(controlled vocabulary)让非法着法在提交前就被拦住,而不是执行后才炸。安全性:执行什么、在哪里执行由代码决定 ——run_python 被放进云端沙箱,而不是本机 shell。组合性:simulate_move 以 FEN(Forsyth–Edwards Notation,棋局描述格式)做无状态推演,不弄脏真实棋局。到这一步,你做的已经不是「写提示词」,而是接口设计(interface design)。
同属这面墙的还有技能(skills)机制:技能只把描述放进系统提示词,正文按需加载。所谓渐进式披露(progressive disclosure),本质仍是台面意识 —— 每样东西都占地方,那就只把目录摆上台面。最后那份「棋盘 vs 棋盘加合法着法」的四次观察实验(还会换用 OpenAI 开源的 gpt-oss 复跑)是最好的解毒剂:信息给得多,不等于结果好。
论文与材料导读
ReAct: Synergizing Reasoning and Acting in Language Models(Yao et al., 2022)。它解决纯推理易幻觉、纯行动缺规划的问题。核心做法一句话:让模型交替生成推理轨迹与动作,以推理指导行动、以行动取回真实观察。结论与局限:在 HotpotQA、ALFWorld 等基准上减少幻觉、提升成功率,但依赖少样本提示,且实验基于当年的小模型。为什么值得读:A1 的那个循环,就是这篇论文的可执行版本。
SWE-agent: Agent-Computer Interfaces Enable Automated Software Engineering(Yang et al., 2024)。它解决通用 Agent 面对真实仓库不会修 bug 的问题。核心做法:不换模型,而是精心设计 agent 与计算机之间的接口 —— 一套简化而顺手的浏览、编辑、测试命令。结论与局限:在 SWE-bench 上取得当时领先的通过率,但接口依赖人工设计,跨仓库、跨语言的泛化有限。为什么值得读:读完你会重新审视 CodeAgent 里每一条终端命令的取舍 —— 你也在设计一份小型 ACI。
Executable Code Actions Elicit Better LLM Agents(CodeAct)(Wang et al., 2024)。它解决 JSON 或文本动作表达力不足、工具一多就爆炸的问题。核心做法:把动作统一为可执行的 Python 代码,与解释器多轮交互、自我纠错。结论与局限:在 17 个 LLM 上成功率最高提升约两成,但需要安全的执行环境与对齐训练。为什么值得读:它是「为什么要有 run_python」的学理答案 —— 与其穷举窄工具,不如给一个能自由组合原子的逃生舱。
Lost in the Middle: How Language Models Use Long Contexts(Liu et al., 2023)。它解决「长上下文」宣传与真实利用率之间的落差。核心做法:只移动相关信息在输入中的位置,观察性能变化。结论与局限:首尾好、中间差的 U 形曲线在标榜长上下文的模型上依然成立,机制解释仍属开放问题。为什么值得读:它是 compaction「保首尾、压中段」策略的实证靠山。
顺带把基准本身也放上书架:SWE-bench: Can Language Models Resolve Real-World GitHub Issues?(Jimenez et al., 2023),两千余个真实 GitHub issue 构成的修复基准。当年表现最好的模型也只能解决不到百分之二的问题 —— 对「真实软件工程有多难」多一分敬畏,就对「跑通一个玩具 demo」少一分飘。
实践视角
站主自己维护 SecondPal/PalWave,一套本地优先(local-first)的桌面 Agent 加移动终端。做产品时对这四面墙的感受,与作业严丝合缝。
本地优先意味着上下文预算比云端更紧:常驻桌面进程的对话史只会无限增长,消费级机器既没有无限内存也没有无限窗口。compaction 在产品里不是优化项,而是生存项 —— 不定时收拾台面,Agent 会先于用户「失忆」。
工具调用同理。桌面 Agent 要碰真实的文件、窗口与通知,自由文本一旦拼错路径,破坏是真实的。我们的做法与作业殊途同归:schema 校验在前,白名单居中,沙箱分级兜底。而「错误作为观察回灌」几乎是 Agent 产品的第一条军规 —— 模型输出永远会坏格式,崩溃式处理的 Agent 活不过第一个真实用户。
评估心态也要换:别迷信单次成功,一次跑通可能只是运气,四次观察才勉强算证据;每次实验都记 token 账,这套手感比任何一次满分都值钱。
给中文学习者的路线建议
环境:跟着仓库用 uv 管理 Python 环境与依赖;密钥放 .env、绝不进 git;先跑离线的公开测试(不计费),再碰按量计费的 Modal 云端沙箱 —— 账单从第一天就该心里有数。顺序:先让最小循环转起来(哪怕 Agent 只会说「我不会」),再加工具,再压上下文,别追求一步到位的优雅。
常见坑的地图(只给类型,不给答案):协议坑,消息顺序与类型拼错,模型读不懂自己的历史;无界坑,忘设步数上限,循环打转烧钱;幻觉坑,模型调用不存在的工具或吐坏 JSON,记住要可恢复;泄漏坑,密钥提交进了仓库;硬编码坑,作业明令禁止把棋步或补丁写死,一切必须由 Agent 自己生成;归因坑,只看成败不看轨迹,或只跑一次就下结论。提交前:AI 使用说明如实填写,并确保提交的每一行都能当面解释 —— 课程会对你的代码当面提问。
本讲要点
- Agent 基类是窄腰:消息协议与有界循环写一次,所有上层 Agent 复用。
- 终端修 bug 加可恢复的错误处理,是真实 Agent 工程中最日常的两个动作。
- 上下文是台面不是仓库:压缩保首尾、压中段;工具调用走 schema,可靠性由接口保证而非提示词祈求。
延伸阅读
- A1 官方仓库:https://github.com/cmu-agents/assignment-1
- ReAct 论文:https://arxiv.org/abs/2210.03629
- SWE-agent 论文:https://arxiv.org/abs/2405.15793
- CodeAct 论文:https://arxiv.org/abs/2402.01030
- Lost in the Middle 论文:https://arxiv.org/abs/2307.03172
- SWE-bench 论文:https://arxiv.org/abs/2310.06770
- uv 文档:https://docs.astral.sh/uv/
- Modal 文档:https://modal.com/docs
写在最后:关于作业政策
明确课程政策:11-768 的作业解答不得公开 —— 不要放进公共仓库、论坛或博客。本篇是伴读笔记与论文导读,不构成、也不提供任何解答;请去做官方的作业。毛坯房的四面承重墙,只有自己砌过一遍,你才知道哪里会裂。