Day 14 / 共 20 天 · 第 3 周 Agent 大脑

LLM 抽象与提示词

大模型是 Agent 的"智力"。昨天(Day 13)讲清了 Action 由谁在哪执行(Runtime);今天补上"智力从哪来"——OpenHands 怎么用 litellm 统一对接上百种模型、系统提示怎么组织、流式为什么强制开、成本怎么算,为明天(Day 15)收官 CodeAct 备好最后一块拼图。

📍 第 3 周 Agent 大脑(SDK)· 你在这里
Day11 SDK概念 Day12 工具体系 Day13 Runtime Day14 LLM&提示词 Day15 CodeAct
L01

litellm:统一上百种模型

OpenHands 不自己写"怎么调 OpenAI/怎么调 Claude"——它用 litellmpyproject.toml:55,固定 1.84.1)作为统一层。你换模型只改配置,代码不动。

litellm 是什么? 每家大模型的 API 长得不一样(OpenAI 一套、Anthropic 一套、Google 一套……请求格式、参数名、返回结构都不同)。litellm 是一个"万能翻译适配器"——你用统一的方式调用,它负责翻译成各家的私有格式。想从 GPT-4 换成 Claude?改个模型名字符串即可。这就是 Day 04 说的"bring your own model(自带模型)"能成立的原因——litellm 把上百种模型的差异抹平了。和 eino 用适配器统一各家模型是同一招。
litellm:一个"万能翻译插座",一头统一、一头适配各家 OpenHands统一调用方式 litellm1.84.1 · 翻译层 OpenAI (GPT-4o…) Anthropic (Claude) Google (Gemini) …上百种 换模型 = 改一个字符串
图注:OpenHands 只对 litellm 说一种"普通话",litellm 负责翻译成各家私有格式——所以换模型只改配置、不动代码。

👶 小白:OpenHands 为什么不自己写"怎么调 OpenAI、怎么调 Claude",非要靠 litellm?

👨‍🏫 老师:因为各家 API 的请求格式、参数名、返回结构都不一样,还经常变。如果自己一家家对接,等于家里每种电器都配一个专用插头——出国就抓瞎。litellm 就是那个万能转换插座:你只认一个统一接口,插座负责适配墙上各国插孔。少写一堆胶水代码,还能一行切换模型。

L02

_configure_llm 真实代码

本仓构建 LLM 配置的地方(live_status_app_conversation_service.py:1215):

def _configure_llm(self, ...):
    llm = ...  # 按用户选的 model / api_key 构建
    llm.stream = True          # ★ 强制流式(L03)
    llm.usage_id = 'agent'     # ★ 用量归属标记(L05)
    return llm
读法:两个强制设置很关键:stream=True(流式输出,见 L03)和 usage_id='agent'(把这次调用的 token 用量归到 "agent" 名下,便于分类统计成本)。模型和 key 来自用户设置(Settings UI 或配置)。
L03

为什么强制流式(stream=True)

回忆 Day 03 的 StreamingDeltaEvent——大模型一个字一个字生成,流式推给前端做"打字机效果"。_configure_llm 强制 stream=True 就是为了这个。

流式为什么对 Agent 体验至关重要? Agent 一步可能要生成很长的内容(一段思考 + 一个动作)。如果非流式,你得干等十几秒直到整段生成完才看到任何东西——像卡死。流式让你边生成边看:AI 的思考一个字一个字冒出来、命令逐渐成形。① 体验上"活了";② 你能提前发现它跑偏(看到它开始想歪就赶紧暂停,省时间省钱)。对自主 Agent 这种"一步耗时长"的场景,流式不是锦上添花,是必需品。
流式就像餐厅里"厨师边炒边上菜":非流式是"整桌菜全做好才一起端上来",你饿着干等;流式是"先上一道、香味先到",你边吃边等,还能中途喊"这道太咸了别做了"(提前叫停跑偏的 Agent)。
流式还配合 confirmation mode(Day 17)——你能实时看到 Agent 要干什么,在高危动作真正执行前就有机会介入。
L04

系统提示的组成

系统提示(system prompt)是"给 Agent 的岗位说明书",通过 Day 03 的 SystemPromptEvent 在会话开头发出。它大致由几块拼成:

  • 角色与总则:你是一个软件工程 Agent,如何行事(来自 SDK 的默认提示 + Instruction 配置)。
  • 工具清单:所有可用工具的定义(Day 12)——告诉 LLM "你有这些手"。
  • 后缀 system_message_suffix:本仓在 AgentContext 里追加的定制内容(Day 11 见过),比如 planning 场景的额外指示(_apply_server_agent_overrides:1382)。
  • 环境信息:工作目录、仓库信息等。
系统提示 = Agent 行为的"宪法"。它决定 Agent 的风格(谨慎还是激进)、流程(要不要先看代码再改)、边界(哪些不能做)。OpenHands 的系统提示是长期打磨的成果——好的系统提示能显著提升任务成功率。这也是为什么 microagents(Day 18)能通过"往上下文注入知识片段"来定制 Agent 行为。
L05

用量与成本统计

Day 03 提过 ConversationStateUpdateEvent 里带 TokenUsage/LLMMetrics。每次 LLM 调用的 token 数、花费都被记录、累加,通过事件推给前端显示,也用于 max_budget_per_task(Day 04)的熔断判断。

为什么成本统计是刚需? 自主 Agent 会自己反复调大模型——一个复杂任务几百步,每步都烧 token。如果不统计、不设上限,可能一觉醒来账单爆炸(尤其它陷入无效循环时)。所以:① 每步记录用量(usage_id='agent' 便于归类);② 实时累加、显示给你看;③ 超 max_budget_per_task 强制停。"让 AI 自主花钱"必须配"实时计费 + 硬性上限"——这是我们在 eino 教程反复强调的成本经济学,在这里再次出现。
L06

多模型与运行时切换

OpenHands 支持配多个模型 profile,Agent 甚至能运行中自己切换模型。本仓在构建 Agent 时会按需附加 SwitchLLMTool:1800,≥2 个 profile 才有意义):

# 有多个模型 profile 时,给 Agent 加一个"切换模型"工具
# Agent 可以自己决定:"这步简单,用便宜的小模型;这步难,切到强模型"
为什么让 Agent 自己切模型? 成本与能力的权衡。强模型(如 GPT-4/Claude Opus)聪明但贵,弱模型(如 mini/haiku)便宜但笨。让 Agent 按任务难度自己选:简单的文件浏览用便宜模型,难的架构决策切到强模型。这就像生活中"杀鸡不用牛刀、砍柴不用剃刀"——列个购物清单用小算盘就够,做复杂财务报表才请注册会计师;让 Agent 学会"看菜下饭",把贵的算力花在刀刃上。这样既省钱又不牺牲关键步骤的质量。切换本身也是一个动作(会产生 SwitchLLMObservation,Day 08 的 webhook 会处理它更新会话记录)。模型路由([model_routing] 配置段)是省钱的高级玩法。
L07

Profile 与密钥的下发

用户的 LLM profiles(模型配置 + API key)在 SaaS 场景下不在沙箱容器里,需要在启动时同步进沙箱——这就是 Day 07 提到的 _seed_sandbox_profiles:958)。API key 这类机密走 Day 09 的"按需下发"或环境变量转发(get_agent_server_env 转发 LLM_* 前缀变量)。

串起来看:你在 Settings 配了模型和 key → app_server 启动会话时把 profile 同步进沙箱(_seed_sandbox_profiles)+ 转发 LLM_* 环境变量(get_agent_server_env)→ 沙箱里的 Agent 用 litellm + 这些配置调模型。机密始终小心处理(Day 09 的最小暴露原则),模型配置则按需同步。
L08

今日小结 + 动手

🧠 今天你应该能回答

  • litellm 解决什么问题?和 eino 的模型适配哪里像?
  • _configure_llm 强制的两个设置是什么?各为什么?
  • 流式为什么对 Agent 体验是必需品?
  • 系统提示由哪几块组成?为什么是 Agent 的"宪法"?
  • 为什么让 Agent 自己切换模型?成本怎么统计和熔断?

✋ 动手

grep 'litellm' pyproject.toml
grep -n 'def _configure_llm\|stream\|usage_id\|SwitchLLMTool\|_seed_sandbox_profiles' \
  openhands/app_server/app_conversation/live_status_app_conversation_service.py | head
sed -n '/\[model_routing\]/,/^\[/p' config.template.toml
明天预告 · Day 15(第3周收官)CodeAct 范式精读——OpenHands 的招牌思想:把"写并执行代码"作为 Agent 的统一动作空间。为什么"用代码当动作"比"一堆固定工具"更强大?这是理解 OpenHands 灵魂的一天。
← Day 13 Runtime Day 15 · CodeAct 精读 →