与模型对话:LLM API 工作模式
本章摘要:
LLM API 是 Agent Loop 的"网络层",本质上是消息历史与可用工具的契约。好的框架把各家协议差异收敛在适配层,让 Agent Loop 看不见这些细节。

上一节讲了 Agent Loop:一个不断重复的循环,每一轮循环的背后就是一次 LLM API 调用。模型读消息历史、给出下一步动作,这就是一次完整的"思考"。
本章不会深入 API 的具体设计上(想了解可以参考 LLM API 协议参考),主要为了讲清楚几个核心问题:
- 请求-响应的本质是什么
- 消息历史怎么组织
- 工具调用怎么走通
- 各家协议为什么会有差异、为什么框架要加适配层。
一、请求-响应的本质
无论哪家 LLM API,调用形态都可以抽象为:
- 请求:把"到目前为止发生了什么"(历史消息)和"接下来你有哪些选择"(工具列表)告诉模型
- 响应:模型给出"我决定做什么"(可能是继续说话,也可能是调用某个工具)
一个最小化的请求包含三件事:
- 使用哪个模型——模型版本决定能力边界和价格
- 到目前为止的对话历史——messages 列表
- 可调用的工具列表——tools,告诉模型"你能做什么"
其他字段(temperature、max_tokens、是否流式)都是控制参数,决定模型的生成风格和长度。本章不深入这些参数的具体数值,因为它们因 API 而异、随版本调整。理解"消息历史 + 工具"这两个核心,就抓住了 LLM API 的灵魂。
二、消息历史:模型唯一的记忆
模型本身没有持久记忆——它每次调用都从零开始读你传给它的消息列表。这意味着 Agent 维护的"对话历史"就是模型的全部视野:
- 用户的原始任务是什么
- 模型之前每一轮的思考、行动、观察
- 工具返回了什么结果
消息历史通常是按时间顺序排列的列表,每条消息带有"角色"和"内容"。 Agent Loop 每跑一轮,就往这个列表里追加一组"思考+行动+观察",然后整体作为下一次请求的输入。
其中,角色的定义一般包含:
| 角色 | 谁说的 | 作用 |
|---|---|---|
system | 开发者 | 定义模型的行为边界 |
user | 用户 | 提出需求或问题 |
assistant | 模型 | 模型的回复/工具调用 |
tool | 工具执行结果 | 把工具的执行结果回传给模型 |
一段"三轮对话"的消息历史长这样(用户提需求 → 模型调工具 → 工具回结果 → 模型总结):
{
"messages": [
// 1. 用户发起请求
{ "role": "user", "content": "统计 sales.csv 中 2024 年 Q1 的总销售额" },
// 2. 模型返回工具调用请求
{ "role": "assistant", "content": null,
"tool_calls": [{ "id": "c1", "function": { "name": "read_file",
"arguments": "{\"path\": \"/data/sales.csv\"}" }}] },
// 3. 系统返回工具调用结果
{ "role": "tool", "tool_call_id": "c1",
"content": "日期,销售额\n2024-01,10000\n2024-02,12000\n2024-03,15000" },
// 4. 模型根据结果给出回答
{ "role": "assistant", "content": "Q1 总销售额 37000 元。" }
],
// 工具定义(下一节详细描述)
"tools": [{
"type": "function",
"function": {
"name": "read_file",
"description": "读取指定路径的文件内容",
"parameters": {
"type": "object",
"properties": {
"path": { "type": "string", "description": "文件的绝对路径" }
},
"required": ["path"]
}
}
}]
}下一轮 Loop 启动时,Agent 把这四行原封不动作为 messages 字段发给模型,模型就知道之前发生了什么。
这就是为什么"上下文管理"会成为 Agent 系统里极其重要的一章——消息列表越长,模型视野越广,但成本和延迟也越高。当列表超出模型的窗口限制时,Agent 必须主动决定"哪些要保留、哪些要压缩、哪些要丢弃"。
三、工具调用:让模型"动手"
只会生成文本的模型是个聪明的聊天者,但不会影响世界。工具调用补上了"动手"的能力——它允许模型在响应中声明要调用哪个工具、传什么参数,由 Agent 框架负责真正执行,然后把结果回填到消息历史里。
一个工具的描述通常包含三件事(详见上一小节的示例):
- 名称:工具叫什么
- 描述:它是做什么的(给模型读的)
- 参数 schema:调用它需要什么参数、参数是什么类型
模型在响应中不直接执行工具,而是声明它要调用。框架(系统)收到声明后才真正执行(权限检查、解析参数、调用底层 API),然后把执行结果以"工具消息"的身份塞回消息历史。这种"声明-执行-回填"的三段式,让权限和审计成为可能——这是后续"权限管线"章节的基础。
工具调用是 Agent 区别于聊天机器人的关键能力,也是后续"工具系统""权限管线"等章节展开的原点。
四、流式响应:边生成边观察
模型生成回复是一个 token 一个 token 进行的。流式响应允许服务端在生成过程中边产出边推送给客户端,而不是等全部生成完一次性返回(一般使用 SSE 协议,Server-Sent Events,不是本章的重点,不再展开详情)。
对 Agent Loop 来说,流式不影响"思考-行动-观察"的循环逻辑——循环的每一步仍然以"完整的一次 API 调用"为单位。但流式改变了用户和框架能多早看到结果:
- 边生成边展示给用户,体感更流畅
- 边生成边检测停止信号(比如模型决定调用工具),可以更早进入下一步
一个常见的误解是把"流式"和"循环"混为一谈。它们是两件事:循环是 Agent 的工作节奏,流式是网络层的传输优化。Agent Loop 的每一步仍然以"一次完整调用"为单位,流式只是让这一次的中间过程可见。
五、协议差异:为什么会有不同
不同 LLM 厂商的 API 协议不完全一样——消息结构、工具声明格式、流式事件类型、错误码、安全 Header 都不相同。
为什么会这样?因为 LLM 还在快速演化,协议也在随之调整。早期的协议没考虑工具调用,后来的协议加入多模态、再加入结构化输出、再加入提示词缓存……每家厂商的演进路径不同,最终协议形态也不同。
这意味着同一段 Agent 代码,往往不能直接跑在两个不同的 LLM 厂商上——除非有适配层做转换。
六、适配层:框架的翻译官
正因为协议有差异,一个成熟的 Agent 框架会做一件事:把各家 API 收敛到框架内部的统一接口上。
框架写一次,模型换一家就跑——这是适配层的核心价值。
具体做法通常是:
- 框架内部定义"统一消息""统一工具"等抽象
- 每个 LLM 厂商有一个适配器,负责把框架抽象翻译成该厂商的 API 格式
- Agent Loop 只和框架的抽象打交道,从不直接碰任何厂商的私有字段
这个设计让 Agent 系统的代码可以独立于具体模型演化。今天用 A 厂商,明天想换 B 厂商,改的是适配器,不是 Agent 逻辑。
本章要点
- LLM API 的本质是消息历史 + 可用工具的契约
- 模型没有持久记忆,消息列表就是它的全部视野
- 工具调用是"声明-执行-回填"三段式,给权限和审计留出空间
- 流式是网络层优化,不影响 Agent Loop 的循环结构
- 协议差异是历史演进的产物,适配层让 Agent 代码与具体模型解耦