Day 11 / 共 20 天 · 第 3 周 Agent 大脑
Agent SDK 概念
进入工人的大脑。Agent 循环的核心逻辑在外部包 openhands-sdk,但我们能从本仓的调用方式 + 前两周的事件模型,把它的工作原理讲透。上周(Day 06-10)我们把"管家"(app_server)如何供给沙箱、装配服务全部读完了;今天起进入沙箱里那个"工人"的大脑——先建立 Agent 循环的整体世界观,为接下来 Day 12 工具、Day 13 运行时、Day 14 LLM 的拆解打底。
📍 第 3 周 Agent 大脑(SDK)· 你在这里
Day11 SDK概念→Day12 工具体系→Day13 Runtime→Day14 LLM&提示词→Day15 CodeAct
L01
先说清楚:代码在哪
诚实声明:Agent 循环的实现在外部包
openhands-sdk==1.34.0(源自 OpenHands/software-agent-sdk 仓库),不在本仓库。本课基于三样真实材料讲清它的原理:① 前两周读的 Action/Observation 事件模型(前端类型);② 本仓 app_server 里构建/调用 Agent 的真实代码(create_agent、get_default_tools);③ OpenHands 公开的 CodeAct 设计。凡涉及外部包的地方都会标注。别慌,你已经懂一大半了
Agent 的"输入输出"你其实早就学过了——它吃事件历史、吐 Action(Day 02/03),然后环境执行产生 Observation 喂回来。这周只是把"大模型如何决定下一个 Action"这个中间过程讲清楚。核心循环你在 Day 05 已经见过,本周是放大细节。
💡 本质:SDK 就像一家你可以"外聘的大脑外包公司"。本仓的 app_server(你的公司)自己不研究"怎么思考、怎么选工具",而是把这套"大脑"外包给专业公司
openhands-sdk:你只要按合同(接口)提要求——给它模型、工具、上下文,它就还你一个能干活的 Agent。本篇后面都在这个"外包大脑"的世界观里讲:你是甲方(app_server),SDK 是乙方(大脑供应商)。L02
Agent 的"一步"(step)
🤔 痛点:LLM 只会"聊天回文字",怎么让它真去改代码、跑命令?大模型本身不能碰你的文件系统,它只能吐字符串。如果直接把它的回复当命令跑,模型稍微多说一句废话就解析失败。
💡 本质:
step 就是外包大脑公司的"标准工作流程一轮"。就像你把任务丢给外包团队,他们内部一轮的固定流程是:读需求 → 决定下一步做什么 → 交给执行部门去做 → 拿回结果 → 再开下一轮会。Agent 的一步(step)= 一次"读历史→问 LLM→产出一个动作→执行→收观察"。反复开这样的短会,直到任务完成。Agent 的核心是一个 step(一步)方法。每一步做的事:
1把"系统提示 + 事件历史"整理成消息,发给 LLM
2LLM 返回"要调用哪个工具、参数是什么"(tool call)
3把 tool call 转成一个 Action(Day 02)
4Action 交给运行时执行 → 得到 Observation(Day 13)
5Observation 追加进历史,进入下一步
读法:"一步"= 一次 LLM 调用 + 一个 Action 的产生。整个任务 = 反复 step,直到 LLM 决定结束(产生 FinishAction)。这就是 Day 01 那张"感知→决策→行动→观察→循环"图的代码化。
现代 Agent 靠 LLM 的 function calling(函数调用)能力实现步进:你把可用工具的定义(名字、参数 schema)告诉 LLM,LLM 就能在回复里结构化地说"我要调用 execute_bash,command='ls'"。SDK 把这个 tool call 解析成
ExecuteBashAction。function calling 是"让大模型可靠地选工具、填参数"的关键机制——没有它,就得靠解析自由文本,极不可靠。💡 简化版 → 真实版:主循环长什么样如果让你写 Agent 主循环,最朴素的版本其实只有几行——真实的 SDK 循环骨架也就是它,只是每一步都更健壮。
# 如果让你自己写(朴素版主循环):
history = [system_prompt, user_task]
while True:
reply = llm.chat(history, tools=tool_defs) # 问大脑
if reply.is_finish(): # 它说"做完了"
break
action = parse_tool_call(reply) # 解析成一个动作
obs = runtime.run(action) # 交执行部门去做
history += [reply, obs] # 结果追加进历史,再开下一轮
# 真实版多出来的:并行 tool_calls、历史压缩(condenser)、
# 步数/预算熔断、确认模式暂停、事件持久化(Day08) ……骨架不变,细节更硬
图:step 主循环——问大脑→产动作→执行→记历史→再问,直到 FinishAction 跳出
跟踪一个真实输入"把 README 里的标题改成 Hello",走查表:
| 步骤 | 此刻发生什么 | 关键状态 |
|---|---|---|
| step 1 | LLM 想先看看文件内容 | FileEditorAction(view);history=3 条 |
| step 2 | 拿到内容,决定替换标题 | FileEditorAction(str_replace);history=5 条 |
| step 3 | 改成功,认为任务完成 | FinishAction → 跳出循环;history=7 条 |
L03
Agent 三要素:LLM / 工具 / 上下文
从本仓构建 Agent 的代码能反推出 Agent 由三部分组成(live_status_app_conversation_service.py:1760):
configured_agent_settings = user.agent_settings.model_copy(update={
'llm': llm, # ① 大脑:用哪个大模型
'tools': tools, # ② 双手:能用哪些工具
'mcp_config': mcp_config, # 扩展工具(MCP)
'agent_context': AgentContext( # ③ 上下文:系统提示、密钥等
system_message_suffix=..., secrets=secrets)})
agent = configured_agent_settings.create_agent() # 组装成 Agent
读法:Agent = LLM(大脑)+ tools(双手)+ context(人设与环境)。
create_agent()(外部包方法)把这三样组装成一个可运行的 Agent 对象。改任何一样都能得到一个不同行为的 Agent——换更强的模型、给更多工具、改系统提示定制人设。类比:招一个员工
LLM = 这个员工的"智力"(用 GPT-4 还是 Claude);tools = 给他配的"工具箱"(能不能上网、能不能改代码);context = 他的"岗位说明书 + 门禁卡"(你是干什么的、有哪些权限密钥)。同一套流程,配不同的智力/工具/说明书,就得到不同能力的 Agent。这也解释了 Day 04 那些
enable_* 开关——它们就是在配"工具箱里放哪些工具"。L04
create_agent 与规划型 Agent
本仓在构建 Agent 时会根据类型选不同工具集(:1760):
if agent_type == AgentType.PLAN:
tools = get_planning_tools(plan_path=plan_path) # 规划型:专注做计划
else:
register_builtins_agents(enable_browser=True)
tools = get_default_tools(enable_browser=True,
enable_sub_agents=...) # 默认:全能工具集
读法:OpenHands 有不止一种 Agent。
PLAN 型是"规划者"——它的工具集专注于"拆解任务、写计划(PLAN.md)",不直接改代码;默认型是"执行者"——工具齐全,直接干活。这呼应 Day 05 提到的"主对话 + planning 子 agent 两条并行连接"。为什么要分规划者和执行者? 复杂任务先规划再执行效果更好——就像人接到大项目先列计划再动手。规划型 Agent 产出计划,执行型 Agent 照计划干。两者协作(Day 18 的 microagents、子 Agent 委派 TaskAction 也是类似思路)。
register_builtins_agents 注册内置 Agent 类型供选用。L05
内置 Agent 与默认工具集
get_default_tools()(外部包 openhands-tools)返回默认 Agent 的工具清单——正好对应 Day 02 的动作全家福:bash 执行、文件编辑、浏览器、搜索(glob/grep)、思考、结束、任务清单等。enable_browser/enable_sub_agents 等参数控制放不放某些工具。
串起来(Day 02+04+11)
配置
[agent] 的 enable_* 开关(Day 04)→ 决定 get_default_tools 返回哪些工具(Day 11)→ 这些工具的定义写进 SystemPromptEvent 告诉 LLM(Day 03)→ LLM 据此产出对应的 Action(Day 02)。"配置→工具集→系统提示→动作"这条链,把散落各周的知识彻底焊死了。默认 Agent 就是大名鼎鼎的 CodeActAgent(Day 15 精读它的独特之处)。L06
会话状态与历史
Agent 每一步都需要"完整的历史"来决策(否则失忆)。这个历史就是 Day 03 的事件流——所有 Action/Observation/Message 按时间累积。Agent step 前,SDK 把这些事件转成 LLM 能理解的消息序列(system + user + assistant + tool 消息)。
历史会越来越长——这就是 Day 03 讲的历史压缩(CondensationEvent)要解决的问题。配置
[condenser] 段选策略(noop/recent/llm 摘要等)。当历史逼近 LLM 上下文窗口上限时,压缩器把老事件折叠成摘要,腾出空间。Agent 能跑长任务(几百步),靠的就是"历史累积 + 适时压缩"这对组合。🗣️ 一句话复述外包大脑每开一轮短会(step),都要把"到目前为止发生的所有事"重新读一遍才能决策;会越开越多、记录越堆越长,快撑爆时就把老记录折叠成摘要(压缩)——这样几百轮会开下来也不会失忆、也不会爆内存。
L07
Agent 什么时候停下来
step 循环的三个出口:
- 正常结束:LLM 产出
FinishAction(Day 02)——它认为任务完成了,附一句给用户的话。 - 达到上限:循环步数超过
max_iterations(默认 500)或花费超过max_budget_per_task(Day 04)——强制熔断。 - 等待人工:confirmation mode 下遇到高危动作暂停等确认(Day 17);或 Agent 主动向用户提问。
谁决定"任务做完了"?
主要是 LLM 自己——当它判断目标达成,就产出 FinishAction。步数/预算上限只是防失控的安全网,正常任务用不到。所以 Agent 好不好用,很大程度取决于 LLM 的判断力和系统提示的质量。这和 eino 教程里 ReAct Agent 的收尾逻辑一模一样——模型不再要工具、给出最终答案即结束。天下 Agent 一大抄(褒义)。
L08
今日小结 + 动手
🧠 今天你应该能回答
- Agent 循环代码在哪?(外部包 openhands-sdk)
- Agent 的"一步"做什么?function calling 起什么作用?
- Agent 三要素?(LLM / tools / context)
- 规划型和执行型 Agent 的区别?
- Agent 靠什么决策?(累积历史 + 压缩)什么时候停?
✋ 动手:看本仓怎么调用 SDK
# 构建 Agent 的真实代码(本仓调用外部包)
grep -n 'create_agent\|get_default_tools\|get_planning_tools\|register_builtins_agents' \
openhands/app_server/app_conversation/live_status_app_conversation_service.py
# 确认外部包依赖
grep 'openhands-sdk\|openhands-tools' pyproject.toml
明天预告 · Day 12:工具体系 openhands-tools——一个工具怎么定义(名字 + 参数 schema + 执行函数)、LLM 怎么"选中并调用"它、bash/文件/浏览器工具各自的门道,以及 MCP 如何接入海量外部工具。