🧠 LLM 调用解剖
拆开一次 LLM 调用的"黑盒":token、context window、system/user/assistant 角色,以及让 Agent 成为可能的「工具调用(function calling)」。这是 Agent 推理与行动能力的物理基础。
本章目标
- 理解 token、context window 如何限制 Agent 的"记忆"容量
- 掌握 Chat 模型的三种消息角色(system / user / assistant)及其用途
- 彻底搞懂「工具调用 / function calling」的完整时序——Agent 行动能力的来源
- 把这些底层机制和后续 LangChain 的
AIMessage、bind_tools源码挂钩
一次 LLM 调用的本质
无论 ChatGPT、Claude 还是 Gemini,一次"聊天"调用的本质是:
输入一串 token(messages)→ 模型预测下一个最可能的 token → 重复直到结束
模型本身不记得上一轮说了什么。所谓"多轮对话",是应用层每次都把历史消息全部重新发一遍。这意味着:
- 历史越长,每次调用越贵、越慢。
- 历史不能无限长——受 context window 限制。
- 这就引出了 Agent 的"记忆管理"难题(OpenCode 的 compaction 机制就是为此而生,见 OC5)。
Token 与 Context Window
Token:LLM 的最小单位
模型不直接读"字",而是读 token。token 介于"词"和"字符"之间:一个英文单词约 1~2 个 token,一个中文字约 1~2 个 token。计费、限流、记忆容量都以 token 为单位。
Context Window(上下文窗口)
模型一次能"看进去"的 token 总数有上限,叫 context window。例如 128K、200K。这个窗口要装下所有东西:
Agent 每走一步,对话历史就增长一截(思考 + 工具调用 + 工具结果)。跑久了历史会撑爆 context window。生产级 Agent 必须有压缩/裁剪历史的能力——LangGraph 用 Checkpointer + 消息裁剪,OpenCode 用 compaction agent(OC5)。这是工程上的硬约束。
三种消息角色
Chat 模型的输入是一串消息(message),每条消息有一个 role(角色)。这三个角色是整个 Agent 体系的基石(LangChain 的 HumanMessage / AIMessage / SystemMessage 就是对它们的封装,见 LC1):
| role | 谁说的 | 用途 | LangChain 类 |
|---|---|---|---|
system |
开发者设定 | 给 LLM 定人设、规则、可用工具说明(Agent 的"指令"主要靠它) | SystemMessage |
user |
终端用户 | 用户的提问/指令 | HumanMessage |
assistant |
LLM | 模型的回复,可能带 tool_calls(表示要调工具) | AIMessage |
tool |
系统(工具) | 把工具执行结果喂回给 LLM | ToolMessage |
一个完整的"工具调用往返"在消息流里长这样(注意 role 的交替):
① system : "你是个助手,可以用 search 工具查天气" ← 工具定义也在这
② user : "北京今天天气怎么样?"
③ assistant: "我来查一下" + tool_calls: [search(location="北京")] ← LLM 决定调工具
④ tool : {"temp": 25, "weather": "晴"} ← 工具结果喂回去
⑤ assistant: "北京今天 25 度,晴天。" ← LLM 基于结果作答
工具调用(Function Calling)—— Agent 的行动力来源
这是本章最核心的部分。没有工具调用,就没有 Agent。它的工作机制分两步:
第 1 步:告诉模型"你有哪些工具"
每个工具本质是一段 JSON Schema,描述工具名、用途、参数。这段 schema 被放进 system 部分(或专门的 tools 字段)。模型训练时已经学会了"读懂 schema 并在合适时机调用"。
{
"name": "search_weather",
"description": "查询指定城市的天气",
"parameters": {
"type": "object",
"properties": {
"location": { "type": "string", "description": "城市名,如'北京'" }
},
"required": ["location"]
}
}
第 2 步:模型输出 tool_calls,应用层执行后回传
模型在回复时,可以选择不直接回答,而是输出一个结构化的 tool_calls(而不是普通文本)。应用层(你的代码)负责:① 真正执行这个工具 ② 把结果用 tool 角色消息喂回去 ③ 再次调用模型让它基于结果继续。
LLM 永远不会真的"执行"工具。它只是输出一段"请帮我调用 search_weather(location='北京')"的结构化指令。真正去查天气、改文件、发请求的,是你的应用代码。这个"指路 vs 走路"的分离,正是 Agent 安全控制的基础——OpenCode 的权限系统(OC4)就是卡在这一步:LLM 想调工具,但要不要真执行、要不要先问用户,由应用层决定。
与 LangChain 源码挂钩
上面这些底层机制,在 LangChain 源码里都有对应的封装。这里先建立映射,后面章节会精读:
| 本节概念 | LangChain 源码位置 | 在哪精读 |
|---|---|---|
| 消息角色 system/user/assistant/tool | langchain_core/messages/base.py:93 | LC1 |
| AIMessage 带 tool_calls | langchain_core/messages/ai.py:160 | LC1 |
| 把工具 schema 绑定到模型 | chat_models.py:2338 bind_tools() | LC3 |
| 让模型按 schema 输出结构化数据 | chat_models.py:2357 with_structured_output() | LC3 |
| 模型调用入口 invoke/stream | chat_models.py:463 invoke() | LC3 |
你可以用 OpenAI 兼容的任意 API 验证工具调用机制。下面这段 Python 不依赖任何框架,直接构造请求(pip install openai):
# pip install openai
from openai import OpenAI
client = OpenAI(api_key="sk-...", base_url="https://api.openai.com/v1")
# 第 1 步:定义工具(就是一段 JSON Schema)
tools = [{
"type": "function",
"function": {
"name": "search_weather",
"description": "查询指定城市的天气",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string", "description": "城市名"}
},
"required": ["location"],
},
},
}]
# 第 2 步:第一次调用 —— 模型决定要调工具
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "北京天气如何?"}],
tools=tools,
)
msg = resp.choices[0].message
print("模型输出:", msg) # 注意 content=None, tool_calls=[...]
# 第 3 步:应用层"真正执行"工具(这里假装执行)
import json
args = json.loads(msg.tool_calls[0].function.arguments)
result = {"location": args["location"], "temp": 25, "weather": "晴"} # 你的真实逻辑
# 第 4 步:把工具结果用 role="tool" 喂回去,再调一次
resp2 = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "user", "content": "北京天气如何?"},
msg, # 模型上一轮的 tool_calls
{"role": "tool", "tool_call_id": msg.tool_calls[0].id,
"content": json.dumps(result)}, # 工具结果
],
tools=tools,
)
print("最终回答:", resp2.choices[0].message.content)
# → "北京今天 25 度,晴天。"
这段代码刻意不封装,目的是让你看清:Agent 框架(LangChain/LangGraph/OpenCode)做的所有事,本质上都是在自动化第 2~4 步的循环——自动把工具结果拼回去、自动判断是否继续、自动管理消息历史。
小结
- LLM 是无状态的,多轮对话靠"每次重发全部历史",受 context window 限制。
- 四种消息角色 system/user/assistant/tool 构成对话的全部内容;tool 角色是工具调用的关键。
- 工具调用 = 模型输出结构化指令 + 应用层执行 + 结果回传。模型只"指路",不"走路"。
- 所有 Agent 框架的本质,都是把这套工具调用循环自动化、可管理化。
下一章 F3 · Agent 循环理论,我们把本章的"单次工具调用往返"升级为"思考-行动-观察"的多步循环,画出贯穿全教程的核心图——ReAct 范式与状态机三阶段模型。这是所有框架源码的共同骨架。