LLM 抽象与提示词
大模型是 Agent 的"智力"。昨天(Day 13)讲清了 Action 由谁在哪执行(Runtime);今天补上"智力从哪来"——OpenHands 怎么用 litellm 统一对接上百种模型、系统提示怎么组织、流式为什么强制开、成本怎么算,为明天(Day 15)收官 CodeAct 备好最后一块拼图。
litellm:统一上百种模型
OpenHands 不自己写"怎么调 OpenAI/怎么调 Claude"——它用 litellm(pyproject.toml:55,固定 1.84.1)作为统一层。你换模型只改配置,代码不动。
👶 小白:OpenHands 为什么不自己写"怎么调 OpenAI、怎么调 Claude",非要靠 litellm?
👨🏫 老师:因为各家 API 的请求格式、参数名、返回结构都不一样,还经常变。如果自己一家家对接,等于家里每种电器都配一个专用插头——出国就抓瞎。litellm 就是那个万能转换插座:你只认一个统一接口,插座负责适配墙上各国插孔。少写一堆胶水代码,还能一行切换模型。
_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 或配置)。为什么强制流式(stream=True)
回忆 Day 03 的 StreamingDeltaEvent——大模型一个字一个字生成,流式推给前端做"打字机效果"。_configure_llm 强制 stream=True 就是为了这个。
流式就像餐厅里"厨师边炒边上菜":非流式是"整桌菜全做好才一起端上来",你饿着干等;流式是"先上一道、香味先到",你边吃边等,还能中途喊"这道太咸了别做了"(提前叫停跑偏的 Agent)。
系统提示的组成
系统提示(system prompt)是"给 Agent 的岗位说明书",通过 Day 03 的 SystemPromptEvent 在会话开头发出。它大致由几块拼成:
- 角色与总则:你是一个软件工程 Agent,如何行事(来自 SDK 的默认提示 +
Instruction配置)。 - 工具清单:所有可用工具的定义(Day 12)——告诉 LLM "你有这些手"。
- 后缀 system_message_suffix:本仓在
AgentContext里追加的定制内容(Day 11 见过),比如 planning 场景的额外指示(_apply_server_agent_overrides,:1382)。 - 环境信息:工作目录、仓库信息等。
用量与成本统计
Day 03 提过 ConversationStateUpdateEvent 里带 TokenUsage/LLMMetrics。每次 LLM 调用的 token 数、花费都被记录、累加,通过事件推给前端显示,也用于 max_budget_per_task(Day 04)的熔断判断。
usage_id='agent' 便于归类);② 实时累加、显示给你看;③ 超 max_budget_per_task 强制停。"让 AI 自主花钱"必须配"实时计费 + 硬性上限"——这是我们在 eino 教程反复强调的成本经济学,在这里再次出现。多模型与运行时切换
OpenHands 支持配多个模型 profile,Agent 甚至能运行中自己切换模型。本仓在构建 Agent 时会按需附加 SwitchLLMTool(:1800,≥2 个 profile 才有意义):
# 有多个模型 profile 时,给 Agent 加一个"切换模型"工具
# Agent 可以自己决定:"这步简单,用便宜的小模型;这步难,切到强模型"
SwitchLLMObservation,Day 08 的 webhook 会处理它更新会话记录)。模型路由([model_routing] 配置段)是省钱的高级玩法。Profile 与密钥的下发
用户的 LLM profiles(模型配置 + API key)在 SaaS 场景下不在沙箱容器里,需要在启动时同步进沙箱——这就是 Day 07 提到的 _seed_sandbox_profiles(:958)。API key 这类机密走 Day 09 的"按需下发"或环境变量转发(get_agent_server_env 转发 LLM_* 前缀变量)。
_seed_sandbox_profiles)+ 转发 LLM_* 环境变量(get_agent_server_env)→ 沙箱里的 Agent 用 litellm + 这些配置调模型。机密始终小心处理(Day 09 的最小暴露原则),模型配置则按需同步。今日小结 + 动手
🧠 今天你应该能回答
- 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