5 件事专章:1 卡 1 件事
在 fundamentals 我们用 1 张卡讲了 5 件事——读者一刷而过,记不住。 本页把每件事展开成独立卡片,每张配 JSON 示例 + 关键陷阱 + 后续阅读。 可以从任何一张切入读,不依赖顺序。
概览
第一篇 · 5 件事详解
① 请求-响应骨架:JSON 进,JSON 出
所有 LLM(Large Language Model,大语言模型)协议都建立在一个最简的形状上——你按格式发一个 JSON(JavaScript Object Notation,一种文本数据格式),服务端按约定回一个 JSON。这一步任何花哨的"工具调用 / 流式 / 状态"都要先在骨架上跑通。骨架错了,后面 4 件事都没意义。
{
"model": "gpt-4",
"messages": [
{"role": "user", "content": "Hello"}
]
}
{
"id": "chatcmpl-abc",
"model": "gpt-4",
"choices": [{
"message": {"role": "assistant", "content": "Hi there!"},
"finish_reason": "stop",
"index": 0
}],
"usage": {"prompt_tokens": 8, "completion_tokens": 4, "total_tokens": 12}
}
{model, messages} S-->>C: 200 OK + JSON
{choices, finish_reason, usage}
② 工具调用:Agent 的心脏(3-message 模式)
工具调用把协议从"对话"升级为"委托执行"——你声明能做什么,模型决定要不要做,做了之后把结果塞回对话。3 个 message 角色协同:你的 user → 模型的 assistant(含 tool_calls) → 你的 tool(执行结果)。
{
"model": "gpt-4",
"messages": [{"role": "user", "content": "北京今天几度?"}],
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询某城市当前天气",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
}
}]
}
{
"choices": [{
"message": {
"role": "assistant",
"content": null,
"tool_calls": [{
"id": "call_abc",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\": \"北京\"}"
}
}]
},
"finish_reason": "tool_calls"
}]
}
{
"messages": [
{"role": "user", "content": "北京今天几度?"},
{"role": "assistant", "tool_calls": [{"id": "call_abc", "type": "function", "function": {"name": "get_weather", "arguments": "{\"city\": \"北京\"}"}}]},
{"role": "tool", "tool_call_id": "call_abc", "content": "晴天 25°C"}
]
}
- function.arguments 是字符串不是对象——必须 json.loads() 一次才能用(详见 message-structure)
- tool_call_id 必须精确匹配上一步的 id,不能自己编
- 第二次请求的 messages 必须把 assistant 的 tool_calls 完整复制回去——只塞 tool 结果不复制 assistant 的 tool_calls,模型会"不知道刚才自己说了要调啥"
③ 流式输出:SSE 逐 token 推
骨架那条路径是"等模型全部生成完再返回"——慢。流式让你建一次连接,模型每生成一个 token 就推一段,前端边收边渲染,给用户打字机效果。传输层从"HTTP 同步"切到"SSE"——其他都不变。
{
"model": "gpt-4",
"messages": [{"role": "user", "content": "你好"}],
"stream": true
}
data: {"id":"chatcmpl-abc","object":"chat.completion.chunk","choices":[{"delta":{"role":"assistant","content":""},"index":0}]}
data: {"id":"chatcmpl-abc","object":"chat.completion.chunk","choices":[{"delta":{"content":"你"},"index":0}]}
data: {"id":"chatcmpl-abc","object":"chat.completion.chunk","choices":[{"delta":{"content":"好"},"index":0}]}
data: {"id":"chatcmpl-abc","object":"chat.completion.chunk","choices":[{"delta":{},"finish_reason":"stop"}],"usage":{...}}
data: [DONE]
- 对象结构变了:非流式返回 choices[0].message.content,流式返回 choices[0].delta.content(增量)
- 终止信号变了:非流式 finish_reason 在 choices[0] 直接出现;流式在最后一个 chunk 出现 + data: [DONE] 终止行
- HTTP 头变了:Content-Type: text/event-stream,不再是 application/json
④ 状态延续:messages 是累加的列表
LLM 服务端不保存你的对话——多轮对话的"记忆"全靠客户端每轮把整个 history 塞进 messages 重新发一遍。这就是"协议层无状态"的真相——服务端不背锅,客户端负责把上下文带过去。
{
"messages": [
{"role": "user", "content": "你好"},
{"role": "assistant", "content": "你好!有什么可以帮你的?"},
{"role": "user", "content": "讲个冷笑话"},
{"role": "assistant", "content": "为什么程序员总是穿黑衣?因为他们不 commit。"},
{"role": "user", "content": "再来一个"}
]
}
- 服务端实现简单:不用持久化会话,省钱;但 token 消耗线性增长,第 N 轮 ≈ N 轮前文总和
- 上下文窗口是天堑:当 messages 总 token 数超模型上限(GPT-4: 8k / 32k / 128k),必须截断或压缩——这是 Agent 工程里最常被低估的复杂度
- 工具调用结果也是 history:上一步 assistant 的 tool_calls + tool 结果都算"上文",下一轮必须带上
⑤ 错误与终止:finish_reason + HTTP 状态码
协议的"结束"分两层:HTTP(Hypertext Transfer Protocol,超文本传输协议)传输是否成功(200 / 400 / 500)+ 模型是否正常完成(finish_reason)。两层正交,组合出 4 种结果——只看一层就是 bug 源头。
| 值 | 含义 | 客户端处理 |
|---|---|---|
| stop | 自然结束 | 显示完整响应,正常 |
| length | 触顶 max_tokens,被截断 | 提示用户"回复被截断",可续推 |
| tool_calls | 模型要调工具 | 执行 tool,结果塞回 messages 继续(见 第 2 件) |
| content_filter | 触发内容审核 | 回退或重试,不要给用户看部分内容 |
- 400 Bad Request:客户端错(参数错、JSON 格式错)——不要重试,修了再发
- 401 Unauthorized:API(Application Programming Interface,应用程序接口)key 错或过期——检查 key
- 429 Too Many Requests:限流——指数退避后重试(看 Retry-After header)
- 500 / 503:服务端错——可重试(同样指数退避,最多 3 次)
读完 5 件事,去哪儿?
- 代码路径:llm-chat-protocol-guide.html —— 7 讲讲义,从"消息长什么样"到"流式怎么拼"到"手写 Agent 协议 Checklist"
- 字段路径:llm-api-schema-reference.html —— 3 协议 schema 详解 + 翻译词典
- 结构路径:llm-protocol-message-structure.html —— 消息结构层专章(为什么 JSON、何时用 Protobuf)