第十二篇:MessageBuilder 深度解析 —— Claude Code 如何构建对话消息
第十二篇:MessageBuilder 深度解析 —— Claude Code 如何构建对话消息
📚 系列文章 第12篇/共100篇 · 2026年7月 · ⏱️ 阅读时间约 13 分钟
源码位置:src/services/anthropic/MessageBuilder.ts
一、引言:为什么需要 MessageBuilder?
上一篇我们拆解了 AnthropicClient.ts,它负责"连接" Anthropic API。但在这之前还有一个关键问题:
API 需要的不是零散的字符串,而是结构化的消息对象。 这些消息从哪来?如何把用户的文本、系统提示、历史对话、工具调用结果、甚至图片,拼装成符合 Anthropic 规范的
messages数组?
答案就是 MessageBuilder.ts。
它是 Claude Code 对话流水线的第一道加工车间:
- 🧱 把多源输入(用户输入、系统提示、工具结果、图片)统一成标准
Message结构 - 🖼️ 处理多模态:把图片转成
base64或 URL 引用的imagecontent block - 📐 管理对话上下文:裁剪过长历史、压缩旧消息、控制 token 预算
- 🔗 串联工具调用:把
tool_use/tool_result配对成合法对话轮次
可以说,没有 MessageBuilder,AnthropicClient 拿到的就是一堆无法发送的数据。
二、MessageBuilder 架构概览
┌─────────────────────────────────────────────────────────────┐
│ MessageBuilder │
├─────────────────────────────────────────────────────────────┤
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ Text Builder │ │ Image Builder│ │ System Builder │ │
│ │ (纯文本块) │ │ (多模态) │ │ (系统提示) │ │
│ └──────────────┘ └──────────────┘ └──────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ History Mgr │ │ Token Budget │ │ Tool Round Mgr │ │
│ │ (历史裁剪) │ │ (预算控制) │ │ (工具轮次配对) │ │
│ └──────────────┘ └──────────────┘ └──────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
AnthropicClient.sendMessage(messages)
数据流:
用户输入 + 系统提示 + 工具结果 + 图片
│
▼
MessageBuilder.build()
│
├── 文本 → { type: 'text', text }
├── 图片 → { type: 'image', source }
├── 工具 → { type: 'tool_use' / 'tool_result' }
▼
标准 Message[] ──► AnthropicClient ──► /v1/messages
三、核心类型定义
3.1 Anthropic 消息协议
MessageBuilder 的产物必须严格符合 Anthropic Messages API 的 schema:
interface Message {
role: 'user' | 'assistant';
content: ContentBlock[]; // 注意:content 是数组,不是字符串
}
type ContentBlock =
| { type: 'text'; text: string }
| { type: 'image'; source: ImageSource }
| { type: 'tool_use'; id: string; name: string; input: unknown }
| { type: 'tool_result'; tool_use_id: string; content: ContentBlock[] };
interface ImageSource {
type: 'base64';
media_type: 'image/jpeg' | 'image/png' | 'image/gif' | 'image/webp';
data: string; // base64 编码的图片数据
}
关键认知: Anthropic 的 content 是一个块数组,而不是单个字符串。这正是多模态和工具调用的基础。MessageBuilder 的核心职责,就是正确地生成这个数组。
3.2 Builder 配置
interface MessageBuilderConfig {
maxTokens: number; // 总 token 预算(如 200000)
systemPrompt: string; // 系统提示
reserveTokens: number; // 为回复预留的 token 数
imageCompression?: boolean; // 是否压缩大图
truncateStrategy: 'head' | 'tail' | 'smart'; // 裁剪策略
}
四、文本与系统提示构建
4.1 构建系统提示
系统提示(system prompt)在 Anthropic API 中是顶层参数,不是 messages 的一部分:
buildSystem(): string {
const blocks: string[] = [];
// 1. 基础身份提示
blocks.push(this.config.systemPrompt);
// 2. 注入当前环境信息(OS、shell、cwd、日期)
blocks.push(this.buildEnvironmentContext());
// 3. 注入工具定义摘要
blocks.push(this.buildToolCatalog());
return blocks.join('\n\n');
}
private buildEnvironmentContext(): string {
const env = this.envCollector.collect();
return [
`<environment>`,
`OS: ${env.os}`,
`Shell: ${env.shell}`,
`CWD: ${env.cwd}`,
`Date: ${env.isoDate}`,
`</environment>`
].join('\n');
}
设计要点: 系统提示是动态拼装的,会实时注入环境上下文,让模型"知道自己在哪台机器上工作"。
4.2 构建用户文本消息
addUserText(text: string): this {
this.pendingUserBlocks.push({ type: 'text', text });
return this; // 支持链式调用
}
// 用法
builder
.addUserText('帮我重构这个函数')
.addUserText('注意保留测试用例')
.build();
五、多模态:图片处理
这是 MessageBuilder 最精彩的部分——把图片塞进对话。
5.1 从文件路径读取并编码
async addUserImage(filePath: string): Promise<this> {
const buffer = await fs.readFile(filePath);
const mediaType = this.detectMediaType(filePath);
// 大图压缩(可选)
const data = this.config.imageCompression
? await this.compressImage(buffer)
: buffer;
const base64 = data.toString('base64');
this.pendingUserBlocks.push({
type: 'image',
source: {
type: 'base64',
media_type: mediaType,
data: base64
}
});
return this;
}
5.2 从 URL 引用图片
Claude 也支持直接传 URL(无需 base64):
addUserImageUrl(url: string): this {
this.pendingUserBlocks.push({
type: 'image',
source: {
type: 'url',
url: url
}
});
return this;
}
5.3 多模态消息示例
const message = await builder
.addUserText('这张截图里有什么错误?')
.addUserImage('./error-screenshot.png')
.build();
// 生成的 content 块:
// [
// { type: 'text', text: '这张截图里有什么错误?' },
// { type: 'image', source: { type: 'base64', media_type: 'image/png', data: 'iVBOR...' } }
// ]
💡 实战价值: 这就是为什么 Claude Code 能"看"截图、读取设计稿、分析图表。MessageBuilder 把图片转成模型可消费的块,是多模态能力的根基。
六、工具调用轮次管理
工具调用在 Anthropic 协议里是一对配对块,必须严格交替:
assistant: { type: 'tool_use', id: 't1', name: 'Read', input: {...} }
user: { type: 'tool_result', tool_use_id: 't1', content: [...] }
6.1 配对管理
addToolUse(block: ToolUseBlock): this {
// tool_use 必须来自 assistant 消息
if (this.lastRole !== 'assistant') {
this.flush(); // 先结束当前轮次
}
this.pendingAssistantBlocks.push(block);
this.lastRole = 'assistant';
return this;
}
addToolResult(toolUseId: string, content: ContentBlock[]): this {
// tool_result 必须来自 user 消息
this.pendingUserBlocks.push({
type: 'tool_result',
tool_use_id: toolUseId,
content
});
this.lastRole = 'user';
return this;
}
6.2 关键约束校验
validateToolPairs(): void {
// 每个 tool_use 必须有对应的 tool_result
const useIds = this.collectToolUseIds();
const resultIds = this.collectToolResultIds();
for (const id of useIds) {
if (!resultIds.has(id)) {
throw new MessageBuildError(
`工具调用 ${id} 缺少对应的 tool_result`
);
}
}
}
⚠️ 血的教训: Anthropic API 对工具配对极其严格。如果
tool_use没有配对的tool_result,或反之,API 会直接返回 400。MessageBuilder 的校验逻辑正是为了拦截这类错误。
七、上下文与 Token 预算管理
这是 MessageBuilder 最复杂、也最能体现工程功力的部分。
7.1 Token 估算
private estimateTokens(blocks: ContentBlock[]): number {
let total = 0;
for (const block of blocks) {
switch (block.type) {
case 'text':
total += this.tokenizer.count(block.text);
break;
case 'image':
// 图片按像素估算 token(约 每 512x512 ≈ 85 token)
total += this.estimateImageTokens(block.source);
break;
case 'tool_use':
case 'tool_result':
total += this.tokenizer.count(
JSON.stringify(block.input ?? block.content)
);
break;
}
}
return total;
}
7.2 智能裁剪
当历史超出预算时,按策略裁剪:
private fitToBudget(messages: Message[]): Message[] {
const budget = this.config.maxTokens - this.config.reserveTokens;
let used = this.estimateMessagesTokens(messages);
if (used <= budget) return messages;
// 从最旧的消息开始裁剪(保留最近对话)
const result = [...messages];
while (used > budget && result.length > 1) {
const removed = result.shift()!; // 移除最旧的一条
used -= this.estimateMessagesTokens([removed]);
}
// 在开头插入截断标记
result.unshift(this.buildTruncationNotice());
return result;
}
7.3 三种裁剪策略对比
| 策略 | 行为 | 适用场景 |
|---|---|---|
head |
丢弃最旧消息 | 长对话,最近内容最重要 |
tail |
丢弃最新消息 | 罕见,保留早期上下文 |
smart |
保留首尾,压缩中间 | 需要全局上下文的复杂任务 |
💡 设计哲学: Claude Code 默认用
head策略——因为编程对话中,最近的指令和上下文通常最关键。但smart策略会在处理超大文件/跨文件重构时启用,避免丢失早期的重要约束。
八、完整构建流程
build(): BuiltMessages {
// 1. 收尾当前 pending 的块
this.flush();
// 2. 组装 messages 数组
const messages = this.assembleMessages();
// 3. 校验工具配对
this.validateToolPairs();
// 4. Token 预算裁剪
const fitted = this.fitToBudget(messages);
// 5. 构建系统提示
const system = this.buildSystem();
return { messages: fitted, system };
}
// 最终交给 AnthropicClient
const { messages, system } = builder.build();
const response = await client.sendMessage(messages, { system });
完整 ASCII 时序:
用户/工具 ──► MessageBuilder
│
├─ addUserText() ─┐
├─ addUserImage() ├─ 累积 pending 块
├─ addToolUse() │
├─ addToolResult() ─┘
│
▼
build()
├─ flush() 收尾轮次
├─ assemble() 组装 messages
├─ validateToolPairs() 校验
├─ fitToBudget() token 裁剪
└─ buildSystem() 系统提示
│
▼
{ messages, system } ──► AnthropicClient
九、使用示例
9.1 最简用法
const builder = new MessageBuilder({
maxTokens: 200000,
systemPrompt: '你是一个编程助手',
reserveTokens: 4096,
truncateStrategy: 'head'
});
const { messages, system } = builder
.addUserText('用 TypeScript 写一个快速排序')
.build();
client.sendMessage(messages, { system });
9.2 多模态 + 工具混合
const { messages, system } = await builder
.addUserText('这个 UI 有问题,看图')
.addUserImage('./ui-bug.png') // 多模态
.addToolUse({ // 工具调用
type: 'tool_use',
id: 't1',
name: 'Read',
input: { file_path: './App.tsx' }
})
// (tool_result 会在下一轮由工具执行结果填入)
.build();
9.3 长对话自动裁剪
// 假设已经累积了 300 轮对话,远超 200k token 预算
const { messages } = builder
.addUserText('继续')
.build(); // 内部自动裁剪最旧的消息,保留最近上下文
十、源码亮点总结
| 特性 | 实现方式 | 价值 |
|---|---|---|
| 多模态块 | content 块数组 + image source |
支持图片输入 |
| 动态系统提示 | 实时注入环境/工具上下文 | 模型"身临其境" |
| 工具配对校验 | validateToolPairs() |
避免 400 错误 |
| Token 预算 | fitToBudget() 智能裁剪 |
永不超窗口 |
| 链式 API | addX().addY().build() |
调用优雅 |
| 策略可配 | head/tail/smart | 灵活适配场景 |
十一、下一篇预告
第13篇我们将深入 流式编排(StreamProcessor / query.ts),看看 Claude Code 如何把消息推送到 API、处理流式响应中的 tool_use 增量块、并实时渲染终端 UI。敬请期待!
📚 Claude Code 源码解析系列
- 源码泄露事件深度解析
- 开发环境搭建指南
- CLI入口与命令系统
- AI对话引擎全解析
- 工具系统深度解析
- CLI命令行解析核心
- Handler处理器链
- QueryEngine查询引擎
- query.ts API对话层
- callModel.ts API调用层
- AnthropicClient API客户端
- MessageBuilder 消息构建器 ← 本文
- StreamProcessor 流式编排(待续)
💡 如果这篇文章对你有帮助,欢迎点赞、收藏、关注!
❓ 有问题欢迎在评论区讨论!
#ClaudeCode #源码分析 #MessageBuilder #多模态 #TypeScript #AI编程
更多推荐

所有评论(0)