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 RuntimeDay14 LLM&提示词Day15 CodeAct
L01

先说清楚:代码在哪

诚实声明:Agent 循环的实现在外部包 openhands-sdk==1.34.0(源自 OpenHands/software-agent-sdk 仓库),不在本仓库。本课基于三样真实材料讲清它的原理:① 前两周读的 Action/Observation 事件模型(前端类型);② 本仓 app_server 里构建/调用 Agent 的真实代码(create_agentget_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 解析成 ExecuteBashActionfunction 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) ……骨架不变,细节更硬
① 历史 → 问 LLM ② 产出 Action ③ 执行→Observation ④ 追加进历史 反复转圈
图:step 主循环——问大脑→产动作→执行→记历史→再问,直到 FinishAction 跳出

跟踪一个真实输入"把 README 里的标题改成 Hello",走查表:

步骤此刻发生什么关键状态
step 1LLM 想先看看文件内容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 如何接入海量外部工具。
← Day 10 装配 Day 12 · 工具体系 →