门户首页
Agent 协议讲义 · 手写 Agent 第一课

大模型对话协议讲义:手写 Agent 的第一课

循环好写,协议难——手写 Agent 必须精确知道消息长什么样、tool_calls 怎么接、流式怎么拼。本讲义以 Chat Completions 为主线,对照 Responses 与 Messages,最后落到"手写 Agent 协议 Checklist"。

生成时间:2026-09-02 · 生成 Agent:MiniMax Code · 载体:agentsoft-research-platform docs

概览:这份讲义的核心

14
章节
4
message 角色
4
finish_reason 状态
10
Checklist 条目
必读心法:2026 年的现实是 OpenAI Chat Completions API 已成为行业事实标准——Ollama、vLLM、DeepSeek、阿里 DashScope、Together、Groq 等几乎所有推理服务都提供"OpenAI 兼容端点"。吃透这一个协议,你就获得了最大的模型可替换性。

序章 · 什么是大模型的对话协议

序章 · 先修概念(What is a Chat Protocol)
回答"对话协议到底是什么、它约定了哪几件事、为什么手写 Agent 必须从它学起"。
卡片 0a · 定义

对话协议 = 你与模型服务之间的"消息结构契约"

在比较"三种协议哪家强"之前,先回答更根本的问题:大模型(LLM,Large Language Model)的对话协议(Chat Protocol)到底是什么?一句话——它是客户端与 LLM 推理服务(通过 API——Application Programming Interface,应用程序接口——调用)之间预先约定的一套消息结构:你按这个结构组装 JSON(JavaScript Object Notation,一种文本数据格式)发过去,服务端才能读懂你;服务端按约定的结构返回,你的程序才能读懂模型。

核心知识点
  • 一句话定义:对话协议是一层 JSON 应用结构约定,约定四件事——消息怎么写、结果怎么收、工具怎么调、流式怎么传
  • 它不在传输层发明新东西:底层就是普通 HTTP POST + JSON 请求/响应体;"协议"指的是这层结构契约,而不是 TCP/HTTP 那种网络协议
  • 契约的双方:你的程序(客户端:组装请求、维护历史、执行工具)与推理服务(服务端:理解输入、生成输出、报告结束原因)
  • 为什么它排第一课:手写 Agent 的主循环每一步都在执行协议语义——分支看 finish_reason、记忆靠 messages 回放、动手靠 tool_calls、打字机效果靠 delta
  • 边界预告:与它并列的还有 MCP(连工具生态)、A2A(Agent 互联)——那是另外两层的"协议",口语里常被混为一谈,第一篇卡片 2 会展开
spec §1 全景 spec §10 协议层次全景
卡片 0b · 最小示例

最小的一问一答:5 个字段看懂协议骨架

概念说完,把协议拿在手里看一次。以 Chat Completions 为例,一次最小对话只需要认识 5 个字段——后面所有章节(工具调用、流式、协议差异)都是在这副骨架上做扩展。

核心知识点
  • 请求:POST /v1/chat/completions,body 里 model 指定用哪个模型,messages 携带对话历史
  • 一条消息 = 角色 + 内容:{"role": "user", "content": "你好"}——system / user / assistant / tool 四种角色撑起整个对话结构
  • 响应:生成的文本在 choices[0].message.content,结束原因在 finish_reason("stop" 表示自然说完)
  • 多轮没有魔法:把 assistant 的回答 append 进 messages 再整体重发一次——对话的"记忆"就是客户端里的这个数组(第二篇展开)
  • 骨架之上的扩展:tool_calls(第三篇)让模型能"动手",stream + delta(第四篇)让输出变成流式
spec §2 核心机制 OpenAI Chat API Reference

第一篇 · 全景:2026 年的三种协议

第一篇 · 全景(先建立坐标系)
回答"三家协议各自定位什么、何时选哪个、别把三层'协议'混为一谈"。
卡片 1 · 三协议定位

事实标准 / Agent 原语 / Claude 生态

2026 年的三种主流协议各司其职:新项目对接 OpenAI 新模型用 Responses;跨厂商兼容仍走 Chat Completions;用 Claude 用 Messages。

核心知识点
  • OpenAI Chat Completions:POST /v1/chat/completions——"大家都模仿的普通话",全生态兼容
  • OpenAI Responses API:POST /v1/responses——OpenAI 官方新一代(2025.03 起),GPT-5 起新模型只在它上提供,Chat 进入维护模式
  • Anthropic Messages API:POST /v1/messages——Claude 生态标准,Agent-oriented 但保持无状态
  • 关键时间线:2023.06 Chat Completions 诞生;2025.03 Responses 发布;2026 年 GPT-5 之后只通过 Responses 提供;Assistants API 已于 2026-08-26 正式下线
spec §1 全景 spec §10 协议层次全景
卡片 2 · 协议层次

LLM API / MCP / A2A:别把三层协议混为一谈

口语里说"Agent 协议"的人,有的指 MCP、有的指 LLM API——先确认对方说的是哪一层。三者是互补关系。

核心知识点
  • LLM API:解决"怎么把 prompt 发给模型、怎么拿回生成结果和 tool_calls"——本讲义主题
  • MCP(Anthropic 提出):解决"Agent 怎么以标准化方式连接成百上千个外部工具/数据源"——Anthropic 主推
  • A2A(Google 提出):解决 Agent 之间互相协作——还在早期
  • 互补关系:连模型用 LLM API,接工具生态可上 MCP,跨 Agent 协作才需要 A2A
flowchart TB L1["顶层 · LLM API(应用 ↔ 模型)
职责:把 prompt 发给模型、拿回生成结果与 tool_calls
Chat Completions · Responses · Messages —— 本讲义主题"] L2["中层 · MCP(Agent ↔ 工具/资源)
职责:Agent 以标准化方式连接成百上千个外部工具与数据源
Anthropic 提出并主推"] L3["底层 · A2A(Agent ↔ Agent)
职责:Agent 之间互相协作
Google 提出 · 仍在早期"] L1 --> L2 L2 --> L3 L1 -.互补关系:三层各管一段,不互相替代.-> L3
图 1 · 别把三层协议混为一谈——LLM API 管"连模型",MCP 管"接工具生态",A2A 管"Agent 互联",三者互补
spec §10 协议层次全景 paper: MCP spec (Anthropic)

第二篇 · Chat Completions 核心机制

第二篇 · Chat 核心(必吃透的主线)
回答"为什么是无状态、为什么全量回放、循环怎么用 finish_reason 分支"。
卡片 3 · 心智模型

无状态 + 全量回放:服务端不记忆任何东西

每轮请求都要把完整对话历史重新发一遍。可以类比:每次进餐厅都要把全部点餐历史从头报一遍,服务员听完做好这一道菜,然后立刻把你忘干净。

核心知识点
  • 维护 messages 数组:"记忆"就是你客户端里的这个 list,服务端完全不存
  • token 计数与预算:服务端只报数(usage),不替你管超限
  • 上下文压缩/截断:超窗必须自己截——压缩永远在 harness 侧强制执行(AgentDiet 原则)
  • 持久化/恢复:断点续跑 = 把数组存盘再读回
课堂实训
  • 本仓 shell_runner.py 的 BudgetGuard(OLLAMA_SHELL_CTX_BUDGET)就是"超预算保留 system + 最近 K 轮"的产品化实现
spec §2.3 心智模型 wiki/19 PRA 体系
卡片 4 · 状态机

finish_reason 四态机:循环分支唯一依据

手写 Agent 的主循环只认 finish_reason 做分支,四种取值必须全覆盖——任何一态漏处理都会让循环陷死或丢工具结果。

核心知识点
  • stop:自然结束 → 收尾、提交答案、退出循环
  • tool_calls:模型要求调工具 → 执行 → role:"tool" 回填 → 再次请求(核心循环)
  • length:撞 max_tokens 被截断 → 扩预算或续写,不要盲目重试
  • content_filter:触发安全策略 → 降级 / 改写 / 上报
stateDiagram-v2 [*] --> 请求中 请求中 --> stop: finish_reason = stop 请求中 --> tool_calls: finish_reason = tool_calls 请求中 --> length: finish_reason = length 请求中 --> content_filter: finish_reason = content_filter stop --> 收尾退出: 提交答案、收尾 收尾退出 --> [*] tool_calls --> 执行工具: tool_call_id 严格配对 执行工具 --> 回填结果: role:tool 消息回填 回填结果 --> 请求中: ★ 核心循环回边 · 再次请求 length --> 扩预算续写: 扩预算或续写,不盲目重试 扩预算续写 --> 请求中 content_filter --> 降级改写上报: 降级 / 改写 / 上报 降级改写上报 --> [*]
图 2 · finish_reason 四态机——tool_calls 支路的"执行→回填→再次请求"回边是手写 Agent 的核心循环,四态缺一不可
思考与讨论
  • 用 if response.choices[0].message.content: 判循环结束?反模式:模型完全可能返回空 content + 有效 tool_calls,会把工具循环掐死
spec §3.2 状态机

第三篇 · Tool Calling:Agent 的心脏

第三篇 · Tool Calling(核心循环)
回答"循环时序长什么样、arguments 字符串怎么防、并行调用怎么回填"。
卡片 5 · 完整循环 + 三大易错点

Tool Loop:arguments 是 JSON 字符串、并行要全回填、id 严格配对

40 行核心循环就能跑通手写 Agent,但 90% 的事故都来自三个边界:tool_call_id 配错、arguments 没 try/except、并行调用少回填一个。

核心知识点
  • tool_call_id 必须严格配对:tool 消息的 tool_call_id 与 assistant tool_calls[].id 一一对应,错一个服务端直接 400
  • arguments 是 JSON 字符串不是对象:"arguments": "{\"suite\": \"all\"}"——收到后要 json.loads,且必须 try/except:弱模型输出非法 JSON 很常见
  • 并行调用要全部执行完再回填:一条响应可能带多个 tool_calls,逐个执行、逐个 append,全部完成后再发起下一轮请求
  • 解析失败的正确处置:把错误信息作为 tool 结果回填(ERROR: invalid JSON in arguments),让模型下一轮自我修正,而非崩溃
课堂实训
  • 用 experiment_modules/solving/adapters/ollama/_client.py(30 行裸协议客户端)+ 上面 40 行循环,组合成 70 行最小可跑 Agent 骨架
spec §4 完整循环 spec §4.2 Python 参考 spec §4.3 三大易错点

第四篇 · 流式协议(SSE)

第四篇 · 流式(SSE)
回答"delta 怎么拼、index 怎么聚合、[DONE] 哨兵在哪、finish_reason 在哪一帧"。
卡片 6 · SSE 解析规则

delta 聚合四条规则 + [DONE] 哨兵 + finish_reason 在末尾

"stream": true 时服务端返回 SSE 流。增量在 delta 而非 message,流式 / 非流式协议语义等价(拼完 delta 等于非流式 message),但超时/心跳处理需要流式特有机制。

核心知识点
  • 增量在 delta 而非 message:把每个 chunk 的 delta.content 顺序拼接才是完整文本
  • tool_calls 的 arguments 同样分片到达 → 按 delta.tool_calls[].index 聚合,先拼 name 再拼 arguments
  • arguments 拼接完成后再一次性 json.loads,中途解析必然失败
  • [DONE] 是终止哨兵,收到即关闭连接;finish_reason 出现在最后一个 chunk的 delta 旁
sequenceDiagram participant C as Client(你的程序) participant L as LLM 推理服务 C->>L: POST /v1/chat/completions(stream: true) loop 逐 chunk 到达(delta 逐 token) L-->>C: chunk 1 · delta.content = 测 L-->>C: chunk 2 · delta.content = 试 L-->>C: chunk N · delta.tool_calls 分片(按 index 聚合) end Note over C: 按 index 聚合 delta,拼成完整 message;
arguments 拼完再一次性 json.loads L-->>C: 最后一个 chunk · delta 旁带 finish_reason L-->>C: data: [DONE] 终止哨兵 → 关闭连接
图 3 · SSE 流式时序——delta 逐 token 到达、按 index 聚合拼成完整 message,[DONE] 哨兵收尾
课堂实训
  • Web 前端消费 SSE 用浏览器原生 EventSource;FastAPI 侧用 sse-starlette。本仓 swebench-exp-web 的 /api/jobs/{id}/events + Last-Event-ID 回放就是同一套 W3C SSE 语义的工程化范例
spec §5 流式协议 paper: W3C SSE (HTML5 spec)

第五篇 · 协议差异与兼容端点陷阱

第五篇 · 协议差异 + OpenAI 兼容方言
回答"Messages / Responses 跟 Chat 的关键差异在哪、跨厂商兼容端点的方言陷阱"。
卡片 7 · 协议差异

system 位置 / 工具回填 / max_tokens 必填 / 缓存

Messages 与 Chat 三个结构性差异一起决定了协议严格度上限。Responses 进一步把状态管理从客户端挪到服务端。

核心知识点
  • system 位置:Chat 用 messages[0].role="system";Anthropic 用顶层 system 参数(专门缓存与优先级语义)
  • 工具调用响应:Chat 用 message.tool_calls[];Messages 用 content[] 中的 tool_use block
  • 工具结果回填:Chat 用独立 role:"tool";Messages 用 role:"user" + 内嵌 tool_result block
  • max_tokens:OpenAI 可选 / Anthropic 必填——做兼容层时给 Anthropic 侧补默认上限
  • 上下文缓存:OpenAI 自动 / Anthropic cache_control 显式断点
spec §6 Anthropic Messages spec §7 Responses
卡片 8 · 方言陷阱

OpenAI 兼容是方言级的,不是语言级的

提供 /v1/chat/completions 兼容端点的服务有 Ollama、vLLM、DeepSeek、阿里 DashScope、Together、Groq 等。但每个 provider 都在标准字段之外私货扩展参数,且默认值各异——静默失效比报错更危险。

核心知识点
  • Ollama:options.num_ctx 默认 4096,长 prompt 被静默截断
  • OpenAI:reasoning_effort,推理模型专属
  • Anthropic:thinking / cache_control,扩展思考与显式缓存
  • DashScope:enable_thinking,思考开关
课堂实训
  • 本仓 one_shot_runner.py 的 D4 决策:Ollama 默认 4096,SWE-bench(SWE = Software Engineering,软件工程;bench = benchmark,基准测试)长 prompt 会被静默截断 → 透传 options.num_ctx=32768。旧版本 Ollama 对未知字段静默忽略不报错——这就是"方言"的典型代价:兼容端点不等于行为一致
spec §8 OpenAI 兼容方言 spec §8.1 生态采用面

第六篇 · 设计律与手写 Checklist

第六篇 · 设计律 + 10 条 Checklist
回答"API 协议和 scaffold 协议怎么分层、写 Agent 前要核对哪些事"。
卡片 9 · 两层协议律

API 协议 ≠ scaffold 协议:可靠性梯度决定设计

手写 Agent 必须分清两层协议:API 协议是服务端保证(JSON 结构可靠);scaffold 协议取决于模型能力。弱模型走纯文本协议 + 正则解析比强行 JSON tool_call 更可靠。

核心知识点
  • 结构可靠性梯度:unified diff(需算 @@ 行号)最差 / JSON tool_call(需嵌套转义)差 / 纯文本 bash 块(抄写+填空)最好
  • 设计律:给模型的输出格式按"抄写+填空"设计,绝不按"计算+对齐"设计
  • 本仓实测:模型生成内容结构可靠性梯度——纯文本 bash 块实测 100% 可靠(弱模型 60 次调用),JSON tool_call 是失效点
  • 弱模型走纯文本协议 + harness 侧正则解析(空壳模式),比强行走 JSON tool_call 更可靠。这是 scaffold 协议层的自由度——API 协议层没有这个自由
思考与讨论
  • 如果让你设计一个能跨强弱模型跑 SWE-bench 的混合方案,你会在 scaffold 层加哪些护栏?
spec §9 两层协议 spec §12 实战阅读路线
卡片 10 · Checklist

手写 Agent 协议 10 条 Checklist

按此清单逐项核对,可覆盖 90% 的协议层事故。

核心知识点
  • 状态管理:明确自己维护 messages 数组(Chat/Messages)还是用 previous_response_id(Responses)
  • 循环状态机:以 finish_reason / stop_reason 为唯一分支依据,4 种取值全覆盖
  • tool_calls 解析防御:arguments 是 JSON 字符串 → try/except → 失败时把错误信息回填让模型重试
  • 并行调用:一次响应多个 tool_calls,全部执行、逐个带 id 回填,缺一不可
  • 回放完整性:assistant 轮的 tool_calls 字段原样保留进历史;tool_call_id 严格配对
  • 上下文预算:每轮读 usage 记账,超限走截断/压缩策略(压缩永远在 harness 侧做,AgentDiet 原则)
  • 流式拼接:delta 按 index 聚合,arguments 拼完再 json.loads;[DONE] 哨兵收尾
  • 重试纪律:429/5xx 指数退避;length 截断不要盲目重试(先扩预算)
  • 方言参数:num_ctx(Ollama) / reasoning_effort(OpenAI) / thinking(Anthropic)逐 provider 验证,勿信默认值
  • scaffold 按模型强弱选型:强模型可上 JSON tool_call;弱模型优先纯文本协议 + 正则解析
spec §11 Checklist

第七篇 · FAQ 与实战阅读路线

第七篇 · FAQ + 代码地图
回答"为什么要裸协议 / 学 Chat 是否白学 / SDK 与裸协议取舍 / 流式对逻辑影响 / 多模态怎么传"。
卡片 11 · FAQ 速答

常见问题六答:为什么裸协议、Chat 是否白学、多模态怎么传

读者实操时的高频问题。Q1 问取舍,Q2 问投入回报,Q3-6 问工程落地细节。

核心知识点
  • 为什么不直接用 SDK?可以且推荐生产使用。手写裸协议的价值是教学与调试:出问题时你能分清是 SDK 的锅、协议理解的锅还是模型的锅。本仓 _client.py 坚持纯标准库,另一动机是零依赖可移植
  • Chat Completions 会不会被 Responses 淘汰?新功能确实只迭代在 Responses,但生态兼容层短期仍以 Chat 为通用语言,学它是一次投入、处处能用
  • system prompt 放 messages[0] 和放顶层有区别吗?行为上各 provider 有细微权重差异;工程上更大区别是缓存命中与计费——大 system 每轮重放很贵,Anthropic 的 cache_control 可显著降低成本
  • tool calling 和 function calling 是一个东西吗?是。早期 OpenAI 叫 function calling(2023),后来统一为更通用的 tool calling(tools 数组)。老文档/老代码里两个名字混用
  • 流式和非流式对 Agent 逻辑有影响吗?协议语义等价(拼完 delta 等于非流式 message),但有超时差异:流式下"模型在生成"和"连接死了"需要心跳/超时区分,本仓 shell_runner 的三级超时(CMD_TIMEOUT ⊂ MAX_STEPS ⊂ TIMEOUT)就是为此设计
  • 多模态(图片)怎么传?content 从字符串升级为 blocks 数组:[{"type":"text","text":"..."}, {"type":"image_url","image_url":{"url":"data:image/png;base64,..."}}]。Anthropic 用 {"type":"image","source":{...}}。结构差异同 §6 思路
课堂实训
  • 实战阅读路线 1→7:_client.py → one_shot_runner.py → shell_runner.py → shell_prompt.py → shell_guards.py → swebench-exp-web SSE → SPEC-ollama-shell-runner 决策记录
spec §13 FAQ spec §12 实战阅读路线

附录 · 术语速查

术语含义
Chat CompletionsOpenAI 事实标准对话 API(/v1/chat/completions)
Messages APIAnthropic 原生协议(/v1/messages)
Responses APIOpenAI 新一代 Agent 原语 API(/v1/responses)
tool calling / function calling模型请求调用你定义的工具的机制
tool_call_id / tool_use_id工具调用的配对标识(OpenAI / Anthropic 命名)
finish_reason / stop_reason响应结束原因(OpenAI / Anthropic 命名)
SSEServer-Sent Events,流式返回的传输层
delta流式响应中的增量片段
OpenAI-compatible第三方服务实现 OpenAI 协议格式的兼容端点
MCPModel Context Protocol,Agent 连接工具生态的标准
A2AAgent-to-Agent 协议,Agent 互联标准
scaffoldAgent 循环的外壳(提示词 + 协议 + 护栏),区别于模型本身
num_ctxOllama 方言参数,控制上下文窗口(默认 4096 易截断)