读完本章,你应能说明:一份 Anthropic 请求怎样变成消息列表与工具定义,并交回 Slime 的共享处理流程。
本节局部数据流
本页沿用现有 Action 2 材料中的同一个请求示例。阅读范围从已经取得的 body 开始,到 _run_turn() 接收到两份转换结果为止;模型生成接到下一章。
这张图和下面的输入、输出使用同一个完整示例。
沿图从输入 body 向下看:先转换消息,再转换工具定义,最后把 translated 和 tools_schema 交回共享流程。图中的两路表示职责分工。
本章阅读语境
正在跟随 CLI 发来的一次模型请求;请求和会话已定位,开始转换输入。
- 当前主线
BaseAdapter._run_turn()- 本章展开
- 将客户端的消息历史与可用工具定义整理成共享格式;本章不生成 token。
- 读完返回
- 回到 _run_turn(),将 translated 与 tools_schema 交给动作 3 编码。
把图接到真实调用
共享流程用下面这句调用当前适配器,并接收两份转换结果:
translated, tools_schema = self._translate(body)
查看源码出处与行号
| 定位内容 | 文件与行号 |
|---|---|
| _run_turn 定义 | slime/agent/adapters/common.py:318 |
| 调用 _translate 并接收返回值 | slime/agent/adapters/common.py:341 |
AnthropicAdapter._translate() 的实现很短,可以直接看出执行顺序:
def _translate(self, body: dict) -> tuple[list[dict], list[dict] | None]:
translated = _translate_messages(body.get("messages") or [], body.get("system"))
tools_schema = _tools_to_chat_tools(body.get("tools"))
return translated, tools_schema
查看源码出处与行号
| 定位内容 | 文件与行号 |
|---|---|
| _translate 定义 | slime/agent/adapters/anthropic.py:58 |
| 消息转换 | slime/agent/adapters/anthropic.py:59 |
| 工具定义转换 | slime/agent/adapters/anthropic.py:60 |
| 返回二元组 | slime/agent/adapters/anthropic.py:61 |
这里的 self 是当前适配器对象,body 是请求字典。实际顺序是先消息转换,再工具转换,最后一起返回;图中的两路表达职责分工。
1.入口数据:body 包含什么
下面一直沿用同一份输入,完整请求可以展开或打开 JSON 示例。
此时 BaseAdapter._run_turn() 已读取请求 JSON 并完成预处理,body 是 Python dict。本例没有消息列表中途插入的 system 消息,预处理没有改变它。
| body 中的字段 | 这一步的去向 |
|---|---|
body["system"] + body["messages"] | 系统指令与对话历史;进入 _translate_messages(),成为 translated |
body["tools"] | 可用工具定义;进入 _tools_to_chat_tools(),成为 tools_schema |
body["max_tokens"] | 本例为 128,表示生成长度限制;留在 body 中,供后续采样参数使用 |
查看源码出处与行号
| 定位内容 | 文件与行号 |
|---|---|
| 读取请求 JSON | slime/agent/adapters/common.py:325 |
| 预处理 | slime/agent/adapters/common.py:326 |
| 本例预处理入口 | slime/agent/adapters/anthropic.py:55 |
| 中途 system 消息处理 | slime/agent/adapters/anthropic.py:291 |
| 生成长度字段配置 | slime/agent/adapters/anthropic.py:45 |
| 后续采样读取 | slime/agent/adapters/common.py:425 |
展开完整输入 body:下面各步骤一直使用这一份请求
{
"system": [
{
"type": "text",
"text": "你是代码助手。"
}
],
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": "读取 README.md 并概括内容。"
}
]
},
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "我先读取文件。"
},
{
"type": "tool_use",
"id": "toolu_demo",
"name": "read_file",
"input": {
"path": "README.md"
}
}
]
},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_demo",
"content": [
{
"type": "text",
"text": "# Demo\n这是一个学习项目。"
}
]
},
{
"type": "text",
"text": "请用一句话概括。"
}
]
}
],
"tools": [
{
"name": "read_file",
"description": "读取文件",
"input_schema": {
"type": "object",
"properties": {
"path": {
"type": "string"
}
},
"required": [
"path"
]
}
}
],
"max_tokens": 128
}
read_file 已经在客户端执行过。本次请求把历史 tool_use 和对应 tool_result 一起带回来;这里做的是历史记录规范化。工具定义则告诉后续模型:本轮仍然可以选择调用 read_file。
2.消息路:按历史顺序转换消息
这一步做什么: _translate() 先把对话历史交给 _translate_messages(),整理角色、文本和历史工具记录。
查看源码出处与行号
| 定位内容 | 文件与行号 |
|---|---|
| _translate_messages 定义 | slime/agent/adapters/anthropic.py:81 |
| 返回消息列表 | slime/agent/adapters/anthropic.py:120 |
它收到上面 body["messages"] 的三条消息,以及 body["system"]。辅助函数处理的是其中的局部值,不会让整个 body 依次穿过所有 helper。
2.1 system 文本块 → 一条 system 消息
这一步做什么: 将系统文本块整理为字符串,放入一条系统消息。
查看源码出处与行号
| 定位内容 | 文件与行号 |
|---|---|
| flatten_content 定义 | slime/agent/adapters/common.py:76 |
| 处理系统指令 | slime/agent/adapters/anthropic.py:84 |
| 展开系统文本 | slime/agent/adapters/anthropic.py:85 |
flatten_content() 收到的是 body["system"]:
[
{
"type": "text",
"text": "你是代码助手。"
}
]
实际输出是一段字符串:
"你是代码助手。"
_translate_messages() 把这段输出放进第一条消息:
{
"role": "system",
"content": "你是代码助手。"
}
2.2 第一条 user 文本块 → 一条 user 消息
这一步做什么: 保留用户角色,提取文本块中的内容。
查看源码出处与行号
| 定位内容 | 文件与行号 |
|---|---|
| user 分支 | slime/agent/adapters/anthropic.py:90 |
| 普通文本处理 | slime/agent/adapters/anthropic.py:95 |
输入:
{
"role": "user",
"content": [
{
"type": "text",
"text": "读取 README.md 并概括内容。"
}
]
}
实际输出:
{
"role": "user",
"content": "读取 README.md 并概括内容。"
}
本例的 content 已经是文本块列表,转换函数直接取块中的 text。这里没有调用 flatten_content()。
2.3 assistant 历史 → 文本 + tool_calls
这一步做什么: 分别收集助手正文和历史工具调用,把工具名称和参数整理到 tool_calls 中。
查看源码出处与行号
| 定位内容 | 文件与行号 |
|---|---|
| assistant 分支 | slime/agent/adapters/anthropic.py:99 |
| tool_call_dict 定义 | slime/agent/adapters/common.py:110 |
| 转换历史工具调用 | slime/agent/adapters/anthropic.py:111 |
| 构造助手消息 | slime/agent/adapters/anthropic.py:112 |
这一条消息的输入:
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "我先读取文件。"
},
{
"type": "tool_use",
"id": "toolu_demo",
"name": "read_file",
"input": {
"path": "README.md"
}
}
]
}
文本块提供 content: "我先读取文件。"。处理 tool_use 块时,转换函数只把工具名和输入参数交给 tool_call_dict():
{
"name": "read_file",
"arguments": {
"path": "README.md"
}
}
上面两项对应实际调用的两个参数:name = "read_file",arguments = {"path": "README.md"}。helper 的实际输出是:
{
"type": "function",
"function": {
"name": "read_file",
"arguments": {
"path": "README.md"
}
}
}
这个对象装入 tool_calls 列表后,整条 assistant 消息成为:
{
"role": "assistant",
"content": "我先读取文件。",
"tool_calls": [
{
"type": "function",
"function": {
"name": "read_file",
"arguments": {
"path": "README.md"
}
}
}
]
}
注意两个具体变化:id: "toolu_demo" 被丢弃;input 改为 arguments,其值仍然是 Python dict,没有变成 JSON 字符串。
2.4 第二条 user 历史 → 一条 tool + 一条 user
这一步做什么: 区分工具结果和继续发给助手的用户文本,分别生成对应角色的消息。
查看源码出处与行号
| 定位内容 | 文件与行号 |
|---|---|
| 工具结果分支 | slime/agent/adapters/anthropic.py:93 |
| 调用 flatten_content | slime/agent/adapters/anthropic.py:94 |
| flatten_content 定义 | slime/agent/adapters/common.py:76 |
这一条输入消息同时带了工具结果和后续文本:
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_demo",
"content": [
{
"type": "text",
"text": "# Demo\n这是一个学习项目。"
}
]
},
{
"type": "text",
"text": "请用一句话概括。"
}
]
}
遇到 tool_result 时,flatten_content() 收到的是 该块的 content:
[
{
"type": "text",
"text": "# Demo\n这是一个学习项目。"
}
]
实际输出:
"# Demo\n这是一个学习项目。"
转换函数先追加一条 role: tool 消息,再把紧接着的文本块追加成 role: user 消息:
[
{
"role": "tool",
"content": "# Demo\n这是一个学习项目。"
},
{
"role": "user",
"content": "请用一句话概括。"
}
]
因此,一条 Anthropic user 消息在这里拆成了两条内部消息。tool_use_id: "toolu_demo" 也没有进入输出。
2.5 消息路的完整返回值:translated
到这里,_translate_messages() 已按原来的历史顺序组装出 5 条消息。这是它返回的完整 Python 列表,用 JSON 形式展示:
展开完整的 5 条 translated 消息
[
{
"role": "system",
"content": "你是代码助手。"
},
{
"role": "user",
"content": "读取 README.md 并概括内容。"
},
{
"role": "assistant",
"content": "我先读取文件。",
"tool_calls": [
{
"type": "function",
"function": {
"name": "read_file",
"arguments": {
"path": "README.md"
}
}
}
]
},
{
"role": "tool",
"content": "# Demo\n这是一个学习项目。"
},
{
"role": "user",
"content": "请用一句话概括。"
}
]
3.工具路:转换可用工具的定义
这一步做什么: 消息转换结束后,再转换可用工具的定义;历史工具调用已经在上一节处理。
查看源码出处与行号
| 定位内容 | 文件与行号 |
|---|---|
| _tools_to_chat_tools 定义 | slime/agent/adapters/anthropic.py:123 |
| 组装 function 工具定义 | slime/agent/adapters/anthropic.py:137 |
| 调用工具定义转换 | slime/agent/adapters/anthropic.py:60 |
消息路返回后,_translate() 再把 body["tools"] 交给 _tools_to_chat_tools()。这一支不经过上面的历史消息 helper。
完整输入:
[
{
"name": "read_file",
"description": "读取文件",
"input_schema": {
"type": "object",
"properties": {
"path": {
"type": "string"
}
},
"required": [
"path"
]
}
}
]
实际输出 tools_schema:
[
{
"type": "function",
"function": {
"name": "read_file",
"description": "读取文件",
"parameters": {
"type": "object",
"properties": {
"path": {
"type": "string"
}
},
"required": [
"path"
]
}
}
}
]
name、description 和参数内容保留下来;外面增加 type: "function" 与 function 包装,input_schema 改名为 parameters。这份定义与 assistant 历史里的 tool_calls 是两种数据:前者声明可用工具,后者记录已经发生的调用。
4.交回共享流程:两份数据怎样继续使用
这一步做什么: 把消息列表和工具定义一起交回共享流程,让后面的模型输入准备继续进行。
查看源码出处与行号
| 定位内容 | 文件与行号 |
|---|---|
| 返回两份结果 | slime/agent/adapters/anthropic.py:61 |
| 共享流程接收 | slime/agent/adapters/common.py:341 |
| 调用 token 渲染 | slime/agent/adapters/common.py:342 |
| _render_token_ids 定义 | slime/agent/adapters/common.py:58 |
AnthropicAdapter._translate(body) 的完整输入就是第 1 步的 body。它返回一个二元组:
(translated, tools_schema)
两个元素分别是上面完整展示的 5 条消息列表 和 1 个工具定义列表。本例两者都是 Python list;没有有效工具时,第二项才会是 None。
AnthropicAdapter 继承 BaseAdapter。共享的 _run_turn() 在同一个适配器实例上调用 self._translate(body),把返回值接收到 translated 和 tools_schema 两个变量中。这就是图中“Forward”的实际交接方式:Python 函数调用与返回值,没有独立的 Core HTTP 请求。
**动作 2 到这里完成。**它的成果是两份规范化数据;此时尚未生成 token,也没有模型响应。body["max_tokens"] 仍为 128,并未塞进消息或工具定义里。
下一步定位到 _render_token_ids():它使用两份列表和当前模型的聊天模板准备 token IDs;具体数值取决于 tokenizer 和模板,本例不虚构。
回到局部图,检查是否能独立解释
body["messages"]与body["tools"]分别交给哪个函数?为什么历史tool_calls与可用工具定义要分开?- 一条包含工具结果的 user 历史,为什么在例子里变成两条内部消息?
translated与tools_schema最后由谁接收?这里是否又发起了一次 HTTP 请求?
说不清哪一步,就回到对应的输入和输出核对。随后进入 Action 3:Generate Tokens,继续看共享流程如何把这些数据变成模型输入。
本页复用原有图、示例和固定源码入口。完整示例沿用原阅读材料记录的输出;本次页面整理没有重新执行 Agent 或模型服务。
集中查看源码校验清单
源码出处与校验清单 汇总本章的固定源码位置、核对要点及校验方法。