第十二篇: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:
interfaceMessage{role:'user'|'assistant';content:ContentBlock[];// 注意:content 是数组,不是字符串}typeContentBlock=|{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[]};interfaceImageSource{type:'base64';media_type:'image/jpeg'|'image/png'|'image/gif'|'image/webp';data:string;// base64 编码的图片数据}关键认知:Anthropic 的content是一个块数组,而不是单个字符串。这正是多模态和工具调用的基础。MessageBuilder 的核心职责,就是正确地生成这个数组。
3.2 Builder 配置
interfaceMessageBuilderConfig{maxTokens:number;// 总 token 预算(如 200000)systemPrompt:string;// 系统提示reserveTokens:number;// 为回复预留的 token 数imageCompression?:boolean;// 是否压缩大图truncateStrategy:'head'|'tail'|'smart';// 裁剪策略}四、文本与系统提示构建
4.1 构建系统提示
系统提示(system prompt)在 Anthropic API 中是顶层参数,不是 messages 的一部分:
buildSystem():string{constblocks:string[]=[];// 1. 基础身份提示blocks.push(this.config.systemPrompt);// 2. 注入当前环境信息(OS、shell、cwd、日期)blocks.push(this.buildEnvironmentContext());// 3. 注入工具定义摘要blocks.push(this.buildToolCatalog());returnblocks.join('\n\n');}privatebuildEnvironmentContext():string{constenv=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});returnthis;// 支持链式调用}// 用法builder.addUserText('帮我重构这个函数').addUserText('注意保留测试用例').build();五、多模态:图片处理
这是 MessageBuilder 最精彩的部分——把图片塞进对话。
5.1 从文件路径读取并编码
asyncaddUserImage(filePath:string):Promise<this>{constbuffer=awaitfs.readFile(filePath);constmediaType=this.detectMediaType(filePath);// 大图压缩(可选)constdata=this.config.imageCompression?awaitthis.compressImage(buffer):buffer;constbase64=data.toString('base64');this.pendingUserBlocks.push({type:'image',source:{type:'base64',media_type:mediaType,data:base64}});returnthis;}5.2 从 URL 引用图片
Claude 也支持直接传 URL(无需 base64):
addUserImageUrl(url:string):this{this.pendingUserBlocks.push({type:'image',source:{type:'url',url:url}});returnthis;}5.3 多模态消息示例
constmessage=awaitbuilder.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';returnthis;}addToolResult(toolUseId:string,content:ContentBlock[]):this{// tool_result 必须来自 user 消息this.pendingUserBlocks.push({type:'tool_result',tool_use_id:toolUseId,content});this.lastRole='user';returnthis;}6.2 关键约束校验
validateToolPairs():void{// 每个 tool_use 必须有对应的 tool_resultconstuseIds=this.collectToolUseIds();constresultIds=this.collectToolResultIds();for(constidofuseIds){if(!resultIds.has(id)){thrownewMessageBuildError(`工具调用${id}缺少对应的 tool_result`);}}}⚠️ 血的教训:Anthropic API 对工具配对极其严格。如果
tool_use没有配对的tool_result,或反之,API 会直接返回 400。MessageBuilder 的校验逻辑正是为了拦截这类错误。
七、上下文与 Token 预算管理
这是 MessageBuilder 最复杂、也最能体现工程功力的部分。
7.1 Token 估算
privateestimateTokens(blocks:ContentBlock[]):number{lettotal=0;for(constblockofblocks){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;}}returntotal;}7.2 智能裁剪
当历史超出预算时,按策略裁剪:
privatefitToBudget(messages:Message[]):Message[]{constbudget=this.config.maxTokens-this.config.reserveTokens;letused=this.estimateMessagesTokens(messages);if(used<=budget)returnmessages;// 从最旧的消息开始裁剪(保留最近对话)constresult=[...messages];while(used>budget&&result.length>1){constremoved=result.shift()!;// 移除最旧的一条used-=this.estimateMessagesTokens([removed]);}// 在开头插入截断标记result.unshift(this.buildTruncationNotice());returnresult;}7.3 三种裁剪策略对比
| 策略 | 行为 | 适用场景 |
|---|---|---|
head | 丢弃最旧消息 | 长对话,最近内容最重要 |
tail | 丢弃最新消息 | 罕见,保留早期上下文 |
smart | 保留首尾,压缩中间 | 需要全局上下文的复杂任务 |
💡 设计哲学:Claude Code 默认用
head策略——因为编程对话中,最近的指令和上下文通常最关键。但smart策略会在处理超大文件/跨文件重构时启用,避免丢失早期的重要约束。
八、完整构建流程
build():BuiltMessages{// 1. 收尾当前 pending 的块this.flush();// 2. 组装 messages 数组constmessages=this.assembleMessages();// 3. 校验工具配对this.validateToolPairs();// 4. Token 预算裁剪constfitted=this.fitToBudget(messages);// 5. 构建系统提示constsystem=this.buildSystem();return{messages:fitted,system};}// 最终交给 AnthropicClientconst{messages,system}=builder.build();constresponse=awaitclient.sendMessage(messages,{system});完整 ASCII 时序:
用户/工具 ──► MessageBuilder │ ├─ addUserText() ─┐ ├─ addUserImage() ├─ 累积 pending 块 ├─ addToolUse() │ ├─ addToolResult() ─┘ │ ▼ build() ├─ flush() 收尾轮次 ├─ assemble() 组装 messages ├─ validateToolPairs() 校验 ├─ fitToBudget() token 裁剪 └─ buildSystem() 系统提示 │ ▼ { messages, system } ──► AnthropicClient九、使用示例
9.1 最简用法
constbuilder=newMessageBuilder({maxTokens:200000,systemPrompt:'你是一个编程助手',reserveTokens:4096,truncateStrategy:'head'});const{messages,system}=builder.addUserText('用 TypeScript 写一个快速排序').build();client.sendMessage(messages,{system});9.2 多模态 + 工具混合
const{messages,system}=awaitbuilder.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块数组 +imagesource | 支持图片输入 |
| 动态系统提示 | 实时注入环境/工具上下文 | 模型"身临其境" |
| 工具配对校验 | 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编程
