Kilo 消息流转过程
本文档描述 Kilo 从用户发送消息到 AI 返回结果的完整流程
一、核心流程概览
用户输入 → 消息预处理 → AI 推理循环 → 结果返回 → UI 更新
核心阶段:
- 用户输入处理:接收用户消息,处理 @mentions 和斜杠命令
- AI 推理循环 :构建提示词,调用 LLM,处理流式响应,在循环内部执行工具调用
- 上下文管理:监控 token 使用,自动压缩上下文
- 结果返回:更新 UI,持久化消息历史
二、完整流程的调用链路图
为了更清晰地展示各阶段之间的调用关系,这里提供一个完整的新建任务的调用链路图:
用户输入
↓
Webview UI (vscode.postMessage)
↓
webviewMessageHandler (case "newTask")
↓
provider.createTask(text, images)
↓
new Task() → startTask(text, images)
└─ initiateTaskLoop(userContent)
↓
recursivelyMakeClineRequests() ← [递归入口]
├─ 状态检查、错误限制检查
├─ 构建系统提示词
├─ ensureContextFitsInWindow()
│ ├─ 计算 token 数
│ └─ [如果需要] condenseMessages()
│ ├─ 调用 LLM 生成摘要
│ └─ 更新 apiConversationHistory
├─ 选择 Provider
├─ 添加缓存控制
└─ api.createMessage()
↓
处理流式响应(不同的类型,不同的处理)
├─ 文本 chunk (type: "text")
│ ├─ 累积文本,增量解析为内容块
│ └─ 调用 presentAssistantMessage(处理消息块)
│ ├─ 显示文本块
│ ├─ 执行工具块
│ │ ├─ 重复检测
│ │ ├─ 自动批准检查
│ │ ├─ 执行工具逻辑
│ │ │ ├─ read_file / write_to_file / execute_command 等
│ │ │ └─ attempt_completion(完成任务工具)
│ │ │ ├─ 展示完成结果:say("completion_result")
│ │ │ ├─ 等待用户响应:ask("completion_result")
│ │ │ │ └─ 【阻塞点】pWaitFor(() => askResponse !== undefined)
│ │ │ ├─ 用户在界面发送消息
│ │ │ ├─ WebView → webviewMessageHandler
│ │ │ ├─ handleWebviewAskResponse() 设置 askResponse
│ │ │ ├─ ask() 返回用户反馈
│ │ │ └─ 将反馈添加到 userMessageContent
│ │ └─ 收集结果到 userMessageContent
│ └─ 设置 userMessageContentReady = true(!触发Task继续循环)
│
├─ Token 使用 chunk (type: "usage")
│ ├─ 记录 inputTokens / outputTokens
│ ├─ 记录 cacheWriteTokens / cacheReadTokens
│ ├─ 记录 reasoningTokens(如有)
│ └─ 累加到 totalCost
│
├─ 推理 chunk (type: "reasoning")
│ ├─ 累积推理文本
│ └─ 显示推理过程
│
├─ 工具调用 chunk (type: "tool_call_partial")
│ ├─ 通过 NativeToolCallParser 处理
│ └─ 累积工具调用参数
│
├─ 错误 chunk (type: "error")
│ └─ 记录并显示错误信息
│
└─ 其他 chunk 类型
├─ thinking_complete:思考完成标记
├─ grounding:搜索来源信息
└─ tool_call_start/delta/end:工具调用事件
↓
等待工具执行完成(通过 userMessageContentReady = true 触发循环执行)
await pWaitFor(() => userMessageContentReady)
↓
判断是否继续循环
├─ [如果有工具结果]
│ ├─ 将结果压入栈
│ └─ continue(继续循环)← [返回递归入口]
│
└─ [如果没有工具结果] 结束循环
├─ 保存最终状态
├─ 更新 UI
└─ 等待下一次输入
数据流转关键点:
- 用户消息 →
webviewMessageHandler→provider.createTask()→startTask()→ 保存到apiConversationHistory+clineMessages - AI 响应(流式)→ 增量解析 →
presentAssistantMessage()→ 工具执行 - 工具结果 →
userMessageContent→ 压入栈 →apiConversationHistory(作为用户消息) - 上下文压缩 → 更新
apiConversationHistory,在clineMessages中添加提示 - 用户响应 AI 询问 →
webviewMessageHandler(case "askResponse") →Task.handleWebviewAskResponse()→ 继续循环
三、核心流程时序图
工具系统 AI Provider Task 实例 Webview UI 用户 工具系统 AI Provider Task 实例 Webview UI 用户 阶段 1: 用户输入处理 阶段 2: AI 推理循环 alt [上下文过长] alt [需要用户批准] alt [包含工具调用] loop [处理流式响应] 继续下一轮循环 循环结束 alt [有工具结果] [无工具结果] loop [持续对话直到完成] 阶段 3: 结果返回 输入消息 1 发送消息 2 保存到双轨历史 3 构建系统提示词 4 生成摘要 5 返回摘要 6 压缩上下文 7 发起流式请求 8 Stream Chunk 9 解析内容块 10 实时显示文本 11 执行工具 12 请求批准 13 批准/拒绝 14 返回工具结果 15 将结果压入栈 16 持久化消息历史 17 更新 UI 18 显示完整响应 19
四、详细流程说明
本节将详细描述消息从用户输入到 AI 返回的完整流转过程,重点展示各阶段之间的调用关系和数据流转。
4.1 AI 推理循环:核心对话引擎
核心方法:recursivelyMakeClineRequests()
这是整个系统的心脏,实现了递归式的 AI 对话循环。它会持续运行,直到 AI 完成任务或遇到需要用户干预的情况。
阶段 4.1.1:循环前的准备工作
步骤 1:状态检查
- 检查任务是否被中止(
this.abort)或暂停(this.paused) - 如果是 → 直接返回,结束循环
步骤 2:错误限制检查
- 检查
consecutiveMistakeCount(连续错误次数) - 如果超过限制(默认 3 次)→ 请求用户反馈,暂停循环
步骤 3:构建系统提示词
- 调用
getSystemPrompt()构建完整的系统提示词 - 包含内容:
- 角色定义和基础指令
- 所有可用工具的描述和使用指南
- 自定义指令和规则
- 系统信息(OS、Shell、工作目录、当前时间等)
步骤 4:上下文长度检查与压缩
- 调用
ensureContextFitsInWindow()检查当前对话的 token 数 - 如果超过阈值 (默认 80%):
- 调用
condenseMessages()触发上下文压缩 - 压缩过程会调用 LLM 生成摘要(这是一个嵌套的 API 调用)
- 压缩完成后,更新
apiConversationHistory - 在
clineMessages中添加一条 "上下文已压缩" 的提示消息
- 调用
数据流转:
apiConversationHistory → ensureContextFitsInWindow()
→ [如果需要] condenseMessages() → LLM API(生成摘要)
→ 新的 apiConversationHistory
阶段 4.1.2:发起 API 请求
步骤 1:选择和初始化 Provider
- 根据配置选择 AI Provider(Anthropic、OpenAI、Gemini 等)
- 创建对应的
ApiHandler实例
步骤 2:消息格式转换
- 如果目标 Provider 需要特殊格式(如 OpenAI),调用
convertToOpenAiMessages()进行转换 - Anthropic 使用原生格式,无需转换
步骤 3:添加缓存控制(Anthropic 特有)
- 为系统提示词添加
cache_control: { type: "ephemeral" }标记 - 为最近的用户消息添加缓存标记
- 这样可以在后续请求中复用缓存,降低成本
步骤 4:创建流式请求
- 调用
api.createMessage()发起流式请求 - 传入参数:
systemPrompt:系统提示词messages:对话历史(apiConversationHistory)tools:可用工具列表
数据流转:
systemPrompt + apiConversationHistory + tools
→ ApiHandler.createMessage()
→ 流式 AsyncGenerator
阶段 4.1.3:处理流式响应(包含工具执行)
核心循环 :遍历 AsyncGenerator,实时处理每个 chunk
typescript
for await (const chunk of stream) {
switch (chunk.type) {
case "text":
// 累积文本
assistantMessage += chunk.text
// 🔑 关键:增量解析为内容块(文本块 + 工具调用块)
this.assistantMessageContent =
this.assistantMessageParser.processChunk(chunk.text)
// 🔑 关键:立即展示和执行工具(在循环内部)
presentAssistantMessage(this)
break
case "usage":
// 记录 token 使用
break
}
}
Chunk 类型处理:
-
文本 chunk (
type: "text"):- 累积到
assistantMessage变量 - 增量解析 :调用
assistantMessageParser.processChunk()解析为内容块 - 立即执行 :调用
presentAssistantMessage()展示文本和执行工具 - 数据流转 :
chunk.text→ 解析为内容块 →presentAssistantMessage()→ 工具执行
- 累积到
-
Token 使用 chunk (
type: "usage"):- 记录输入/输出 token 数
- 记录缓存读写情况(Anthropic)
- 计算成本并累加到
this.totalCost - 数据流转 :
chunk.usage→this.totalCost+this.totalInputTokens+this.totalOutputTokens
-
推理 chunk (
type: "reasoning"):- 累积推理文本到专门的变量
- 显示推理过程(DeepSeek R1 等支持推理的模型)
- 可能包含 signature 字段用于验证(Anthropic extended thinking)
-
工具调用 chunk (
type: "tool_call_partial"):- 通过
NativeToolCallParser处理和缓冲 - 累积工具调用的 id、name、arguments
- 最终组装成完整的工具调用块供
presentAssistantMessage()执行
- 通过
-
错误 chunk (
type: "error"):- 记录错误信息
- 显示给用户并中断当前流程
-
其他 chunk 类型:
thinking_complete:标记思考完成,包含验证签名grounding:搜索引擎返回的来源信息tool_call_start/delta/end:工具调用的生命周期事件
关键理解:
- 工具调用的解析和执行都在流式响应循环内部完成
presentAssistantMessage()在每个 text chunk 到达时被调用- 工具执行是同步阻塞的,执行完成后才继续处理下一个 chunk
阶段 4.1.4:presentAssistantMessage() 的工具执行逻辑
核心方法 :presentAssistantMessage(cline: Task)
这个方法在流式响应循环内部被调用,负责:
- 遍历已解析的内容块(
assistantMessageContent) - 显示文本块
- 执行工具调用块
- 收集工具结果到
userMessageContent
执行流程:
typescript
export async function presentAssistantMessage(cline: Task) {
// 遍历所有内容块
for (let i = cline.currentStreamingContentIndex;
i < cline.assistantMessageContent.length;
i++) {
const block = cline.assistantMessageContent[i]
if (block.type === "text") {
// 显示文本块
await cline.say("text", block.content)
}
if (block.type === "tool_use") {
// 🔑 在这里执行工具!
// 1. 安全检查
// 重复检测
const repetitionCheck = cline.toolRepetitionDetector.check(block)
if (!repetitionCheck.allowExecution) {
// 检测到重复,跳过执行
break
}
// 2. 自动批准检查
const needsApproval = await checkAutoApproval(block)
if (needsApproval) {
const { response } = await cline.ask("tool", ...)
if (response !== "yesButtonClicked") {
// 用户拒绝,设置标志并跳过
cline.didRejectTool = true
break
}
}
// 3. 执行工具
switch (block.name) {
case "read_file":
result = await readFileTool(cline, block, ...)
break
case "write_to_file":
result = await writeToFileTool(cline, block, ...)
break
case "execute_command":
result = await executeCommandTool(cline, block, ...)
break
// ... 其他工具
}
// 4. 收集工具结果
cline.userMessageContent.push({
type: "tool_result",
tool_use_id: block.id,
content: result
})
// 标记已使用工具(每次只能执行一个工具)
cline.didAlreadyUseTool = true
}
}
// 5. 所有块处理完成后设置就绪标志
if (所有块都完成) {
cline.userMessageContentReady = true
}
}
阶段 4.1.5:等待工具执行完成并判断是否继续循环
步骤 1:等待工具执行完成
typescript
// 流式响应处理完毕后,等待工具执行完成
await pWaitFor(() => this.userMessageContentReady)
步骤 2:判断是否继续循环
typescript
// 检查是否有工具结果需要返回给 AI
if (this.userMessageContent.length > 0) {
// 将工具结果压入栈,触发下一轮循环
stack.push({
userContent: [...this.userMessageContent],
includeFileDetails: false
})
// 继续下一轮循环
continue
}
// 没有工具结果,循环结束
关键理解:
-
工具执行在流式响应循环内部 通过
presentAssistantMessage()完成 -
工具结果被收集到
userMessageContent -
工具结果作为新的"用户消息"压入栈,触发下一轮 AI 推理
-
这样形成了一个完整的递归循环:
AI 推理 → 流式响应 → 解析工具调用 → 执行工具 → 收集结果
→ 压入栈 → AI 推理 → ... → attempt_completion(最终答案)...
4.2 结果返回与 UI 更新:循环的终点
当 AI 推理循环结束时(无工具调用或任务完成),系统进入最终阶段。
阶段 4.2.1:保存最终状态
持久化内容:
-
消息历史:
api_conversation_history.json:API 层历史ui_messages.json:UI 层历史
-
任务元数据:
- 任务 ID、创建时间、完成时间
- Token 使用统计(输入/输出/缓存)
- 总成本
- 任务摘要(如果有)
-
上下文追踪:
- 已访问的文件列表
- 已执行的命令列表
- 浏览器状态(如果使用了浏览器工具)
数据流转:
apiConversationHistory + clineMessages + taskMetadata
→ 本地文件系统(JSON 文件)
阶段 4.2.2:更新 UI
更新内容:
-
消息列表:
- 显示完整的对话历史(基于
clineMessages) - 包括用户消息、AI 响应、工具执行记录等
- 显示完整的对话历史(基于
-
Token 统计:
- 输入 token 数
- 输出 token 数
- 缓存读取/写入 token 数(Anthropic)
- 总成本
-
任务状态:
- 任务是否完成
- 是否有错误
- 是否需要用户反馈
-
工具执行记录:
- 显示已执行的工具列表
- 每个工具的执行状态(成功/失败)
- 工具执行结果摘要
数据流转:
clineMessages + taskMetadata
→ Webview UI(通过 postMessage)
→ 用户界面更新
阶段 4.2.3:等待下一次输入
状态:
- Task 实例保持活跃(不销毁)
- 等待用户发送新消息
- 用户可以:
- 继续对话(发送新消息)
- 结束任务(关闭对话)
- 查看历史记录
控制流转移:
- 如果用户发送新消息 → 返回阶段 2.1(用户输入处理)
- 如果用户结束任务 → 销毁 Task 实例,清理资源
五、双轨消息系统详解
5.1 双轨制概述
Kilo 维护两套独立但同步的消息历史系统:
api_conversation_history.json- API 层历史(发送给 LLM 的实际对话)ui_messages.json- UI 层历史(在界面显示的所有消息)
存储位置:
{globalStoragePath}/tasks/{taskId}/
├── api_conversation_history.json
└── ui_messages.json
5.2 为什么需要双轨制?
关注点分离
- API 层:专注于与 LLM 的技术交互,确保 API 调用的正确性和上下文管理
- UI 层:专注于用户体验,展示丰富的交互信息和状态
不同的数据需求
- API 层:需要简洁、标准化的消息格式(Anthropic MessageParam)
- UI 层:需要丰富的元数据、状态信息、进度指示等(ClineMessage)
性能优化
- API 层:可以进行上下文压缩以减少 token 使用
- UI 层:保持完整历史,用于调试和审计
5.3 api_conversation_history.json 详解
数据结构
typescript
type ApiMessage = Anthropic.MessageParam & {
ts?: number // 时间戳
isSummary?: boolean // 是否为压缩摘要
}
实际示例
基于 Button 组件创建场景,api_conversation_history.json 的内容:
json
[
{
"role": "user",
"content": [
{
"type": "text",
"text": "帮我创建一个 Button 组件,要支持不同的尺寸和颜色"
}
],
"ts": 1736654400000
},
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "好的,让我先查看一下项目结构,了解现有的组件规范。"
},
{
"type": "tool_use",
"id": "toolu_01A09q90qw90lq917835lq9",
"name": "read_file",
"input": {
"path": "src/components"
}
}
],
"ts": 1736654401000
},
{
"role": "user",
"content": [
{
"type": "text",
"text": "[read_file for 'src/components'] Result:"
},
{
"type": "text",
"text": "<files>\n<file>\n<path>src/components</path>\n<content>\nCard.tsx\nInput.tsx\nModal.tsx\n</content>\n</file>\n</files>"
}
],
"ts": 1736654402000
},
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "我看到了现有的组件。让我查看一下其中一个组件的实现,了解代码风格。"
},
{
"type": "tool_use",
"id": "toolu_01B19r91rw91mr928946mr0",
"name": "read_file",
"input": {
"path": "src/components/Card.tsx"
}
}
],
"ts": 1736654403000
},
{
"role": "user",
"content": [
{
"type": "text",
"text": "[read_file for 'src/components/Card.tsx'] Result:"
},
{
"type": "text",
"text": "<files>\n<file>\n<path>src/components/Card.tsx</path>\n<content lines=\"1-25\">\nimport React from 'react'\nimport { cn } from '@/lib/utils'\n\ninterface CardProps {\n className?: string\n children: React.ReactNode\n}\n\nexport const Card: React.FC<CardProps> = ({ className, children }) => {\n return (\n <div className={cn('rounded-lg border bg-card p-4', className)}>\n {children}\n </div>\n )\n}\n</content>\n</file>\n</files>"
}
],
"ts": 1736654404000
},
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "好的,我了解了代码风格。现在让我创建 Button 组件。"
},
{
"type": "tool_use",
"id": "toolu_01C29s02sw02ns039057ns1",
"name": "write_to_file",
"input": {
"path": "src/components/Button.tsx",
"content": "import React from 'react'\nimport { cn } from '@/lib/utils'\n\ninterface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n variant?: 'primary' | 'secondary' | 'outline'\n size?: 'sm' | 'md' | 'lg'\n children: React.ReactNode\n}\n\nexport const Button: React.FC<ButtonProps> = ({\n variant = 'primary',\n size = 'md',\n className,\n children,\n ...props\n}) => {\n const variantStyles = {\n primary: 'bg-blue-600 text-white hover:bg-blue-700',\n secondary: 'bg-gray-600 text-white hover:bg-gray-700',\n outline: 'border border-gray-300 hover:bg-gray-100'\n }\n\n const sizeStyles = {\n sm: 'px-3 py-1.5 text-sm',\n md: 'px-4 py-2 text-base',\n lg: 'px-6 py-3 text-lg'\n }\n\n return (\n <button\n className={cn(\n 'rounded-lg font-medium transition-colors',\n variantStyles[variant],\n sizeStyles[size],\n className\n )}\n {...props}\n >\n {children}\n </button>\n )\n}"
}
}
],
"ts": 1736654405000
},
{
"role": "user",
"content": [
{
"type": "text",
"text": "[write_to_file for 'src/components/Button.tsx'] Result:"
},
{
"type": "text",
"text": "Successfully created src/components/Button.tsx with 42 lines of code."
}
],
"ts": 1736654406000
},
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "我已经成功创建了 Button 组件!这个组件支持:\n\n1. **三种样式变体:**\n - `primary`:蓝色主按钮\n - `secondary`:灰色次要按钮\n - `outline`:边框按钮\n\n2. **三种尺寸:**\n - `sm`:小尺寸\n - `md`:中等尺寸(默认)\n - `lg`:大尺寸\n\n3. **完全的类型安全:** 继承了所有原生 button 的属性\n\n使用示例:\n```tsx\n<Button variant=\"primary\" size=\"lg\">\n 点击我\n</Button>\n```\n\n还需要我做其他调整吗?"
}
],
"ts": 1736654407000
}
]
特点总结
- 标准格式:严格遵循 Anthropic API 的 MessageParam 格式
- 角色明确 :只有
user和assistant两种角色 - 工具表示 :工具调用作为
assistant消息的content块,工具结果作为user消息 - 时间戳 :每条消息都有
ts字段记录时间 - 压缩支持 :支持
isSummary标记压缩后的摘要消息
5.4 ui_messages.json 详解
数据结构
typescript
type ClineMessage = {
ts: number // 时间戳
type: "ask" | "say" // 消息类型
ask?: ClineAsk // 询问类型
say?: ClineSay // 陈述类型
text?: string // 文本内容
images?: string[] // 图片数组
partial?: boolean // 是否部分消息
reasoning?: string // 推理内容
conversationHistoryIndex?: number // 对话历史索引
checkpoint?: Record<string, unknown> // 检查点
progressStatus?: ToolProgressStatus // 进度状态
contextCondense?: ContextCondense // 上下文压缩信息
isProtected?: boolean // 是否受保护
apiProtocol?: "openai" | "anthropic" // API 协议
isAnswered?: boolean // 是否已回答
metadata?: { // 元数据
gpt5?: Gpt5Metadata
kiloCode?: KiloCodeMetaData
}
}
实际示例
基于同一场景,ui_messages.json 的内容:
json
[
{
"ts": 1736654400000,
"type": "say",
"say": "text",
"text": "帮我创建一个 Button 组件,要支持不同的尺寸和颜色",
"conversationHistoryIndex": 0
},
{
"ts": 1736654401000,
"type": "say",
"say": "api_req_started",
"text": "正在思考..."
},
{
"ts": 1736654401500,
"type": "say",
"say": "text",
"text": "好的,让我先查看一下项目结构,了解现有的组件规范。",
"partial": false
},
{
"ts": 1736654401600,
"type": "ask",
"ask": "tool",
"text": "{\"tool\":\"readFile\",\"path\":\"src/components\",\"content\":\"/Users/user/project/src/components\"}"
},
{
"ts": 1736654402000,
"type": "say",
"say": "text",
"text": "[read_file for 'src/components'] Result:",
"conversationHistoryIndex": 2
},
{
"ts": 1736654402100,
"type": "say",
"say": "command_output",
"text": "<files>\n<file>\n<path>src/components</path>\n<content>\nCard.tsx\nInput.tsx\nModal.tsx\n</content>\n</file>\n</files>"
},
{
"ts": 1736654403000,
"type": "say",
"say": "text",
"text": "我看到了现有的组件。让我查看一下其中一个组件的实现,了解代码风格。",
"partial": false
},
{
"ts": 1736654403100,
"type": "ask",
"ask": "tool",
"text": "{\"tool\":\"readFile\",\"path\":\"src/components/Card.tsx\",\"content\":\"/Users/user/project/src/components/Card.tsx\"}"
},
{
"ts": 1736654404000,
"type": "say",
"say": "text",
"text": "[read_file for 'src/components/Card.tsx'] Result:",
"conversationHistoryIndex": 4
},
{
"ts": 1736654404100,
"type": "say",
"say": "command_output",
"text": "<files>\n<file>\n<path>src/components/Card.tsx</path>\n<content lines=\"1-25\">\nimport React from 'react'\nimport { cn } from '@/lib/utils'\n\ninterface CardProps {\n className?: string\n children: React.ReactNode\n}\n\nexport const Card: React.FC<CardProps> = ({ className, children }) => {\n return (\n <div className={cn('rounded-lg border bg-card p-4', className)}>\n {children}\n </div>\n )\n}\n</content>\n</file>\n</files>"
},
{
"ts": 1736654405000,
"type": "say",
"say": "text",
"text": "好的,我了解了代码风格。现在让我创建 Button 组件。",
"partial": false
},
{
"ts": 1736654405100,
"type": "ask",
"ask": "tool",
"text": "{\"tool\":\"newFileCreated\",\"path\":\"src/components/Button.tsx\",\"diff\":\"+ import React from 'react'\\n+ import { cn } from '@/lib/utils'\\n+ \\n+ interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\\n+ variant?: 'primary' | 'secondary' | 'outline'\\n+ size?: 'sm' | 'md' | 'lg'\\n+ children: React.ReactNode\\n+ }\\n+ \\n+ export const Button: React.FC<ButtonProps> = ({\\n+ variant = 'primary',\\n+ size = 'md',\\n+ className,\\n+ children,\\n+ ...props\\n+ }) => {\\n+ const variantStyles = {\\n+ primary: 'bg-blue-600 text-white hover:bg-blue-700',\\n+ secondary: 'bg-gray-600 text-white hover:bg-gray-700',\\n+ outline: 'border border-gray-300 hover:bg-gray-100'\\n+ }\\n+ \\n+ const sizeStyles = {\\n+ sm: 'px-3 py-1.5 text-sm',\\n+ md: 'px-4 py-2 text-base',\\n+ lg: 'px-6 py-3 text-lg'\\n+ }\\n+ \\n+ return (\\n+ <button\\n+ className={cn(\\n+ 'rounded-lg font-medium transition-colors',\\n+ variantStyles[variant],\\n+ sizeStyles[size],\\n+ className\\n+ )}\\n+ {...props}\\n+ >\\n+ {children}\\n+ </button>\\n+ )\\n+ }\"}"
},
{
"ts": 1736654406000,
"type": "say",
"say": "text",
"text": "[write_to_file for 'src/components/Button.tsx'] Result:",
"conversationHistoryIndex": 6
},
{
"ts": 1736654406100,
"type": "say",
"say": "command_output",
"text": "Successfully created src/components/Button.tsx with 42 lines of code."
},
{
"ts": 1736654407000,
"type": "say",
"say": "text",
"text": "我已经成功创建了 Button 组件!这个组件支持:\n\n1. **三种样式变体:**\n - `primary`:蓝色主按钮\n - `secondary`:灰色次要按钮\n - `outline`:边框按钮\n\n2. **三种尺寸:**\n - `sm`:小尺寸\n - `md`:中等尺寸(默认)\n - `lg`:大尺寸\n\n3. **完全的类型安全:** 继承了所有原生 button 的属性\n\n使用示例:\n```tsx\n<Button variant=\"primary\" size=\"lg\">\n 点击我\n</Button>\n```\n\n还需要我做其他调整吗?",
"partial": false
},
{
"ts": 1736654407100,
"type": "say",
"say": "api_req_finished",
"text": "API 请求完成"
}
]
特点总结
- 丰富类型 :包含
api_req_started、text、tool、command_output、api_req_finished等多种消息类型 - 交互记录 :记录了所有用户交互(
ask类型)和系统响应(say类型) - 工具详情:工具调用包含完整的参数和 diff 预览
- 状态追踪:记录 API 请求状态、工具执行进度等
- 元数据丰富:包含检查点、进度状态、上下文压缩信息等
5.5 两套历史的对比
| 特性 | api_conversation_history.json | ui_messages.json |
|---|---|---|
| 用途 | 发送给 LLM 的对话上下文 | 在 UI 中显示的完整历史 |
| 格式 | Anthropic MessageParam | ClineMessage |
| 角色 | user / assistant | ask / say |
| 工具调用 | 作为 assistant 消息的 content 块 | 作为独立的 ask 消息 |
| 工具结果 | 作为 user 消息的 content 块 | 作为独立的 say 消息 |
| 压缩 | 支持,删除早期消息并替换为摘要 | 不压缩,保留完整历史 |
| 元数据 | 最小化(ts, isSummary) | 丰富(进度、状态、检查点等) |
| 消息数量 | 较少(压缩后) | 较多(完整记录) |
| 文件大小 | 较小 | 较大 |
5.6 双轨同步机制
用户消息同步
typescript
// 在 Task.ts 中
async handleWebviewAskResponse(askResponse: ClineAskResponse, text?: string, images?: string[]) {
// 1. 添加到 UI 历史
await this.say("user_feedback", text, images)
// 2. 添加到 API 历史
await this.addToApiConversationHistory({
role: "user",
content: [{ type: "text", text }]
})
}
AI 响应同步
typescript
// 在 recursivelyMakeClineRequests 中
for await (const chunk of stream) {
if (chunk.type === "text") {
// 1. 实时更新 UI(通过 presentAssistantMessage)
await presentAssistantMessage(this)
// 2. 累积完整响应
assistantMessage += chunk.text
}
}
// 3. 添加到 API 历史
await this.addToApiConversationHistory({
role: "assistant",
content: [{ type: "text", text: assistantMessage }]
})
工具执行同步
typescript
// 在 presentAssistantMessage.ts 中
const pushToolResult = (content: ToolResponse) => {
// 1. 添加到 userMessageContent(稍后会添加到 API 历史)
cline.userMessageContent.push({ type: "text", text: `${toolDescription()} Result:` })
cline.userMessageContent.push({ type: "text", text: content })
// 2. 同时更新 UI 历史
await cline.say("command_output", content)
}
5.7 上下文压缩的双轨处理
背景场景
用户与 AI 进行了长时间对话,实现了一个完整的用户管理系统,包括登录、注册、权限管理等功能。对话历史已经积累了 53 条消息,token 使用率达到 84%,触发了自动压缩。
压缩前
api_conversation_history.json(53 条消息):
json
[
// 前 50 条原始消息(涵盖登录、注册、权限管理等功能的实现)
{ "role": "user", "content": "帮我创建一个用户登录功能", "ts": 1704700000000 },
{ "role": "assistant", "content": "好的,我会帮你创建用户登录功能...", "ts": 1704700001000 },
{ "role": "user", "content": "使用 JWT 进行身份验证", "ts": 1704700002000 },
{ "role": "assistant", "content": "明白了,我会使用 JWT...", "ts": 1704700003000 },
// ... 中间 45 条消息(消息 5-49)涵盖了:
// - 实现 JWT 认证逻辑
// - 创建用户注册接口
// - 实现权限管理系统
// - 添加用户角色功能
{ "role": "user", "content": "添加用户注册功能", "ts": 1704700049000 },
// 最后 3 条消息(保持最新的对话上下文)
{ "role": "user", "content": "现在添加密码重置功能", "ts": 1704700050000 },
{ "role": "assistant", "content": "好的,让我添加密码重置功能...", "ts": 1704700051000 },
{ "role": "user", "content": "发送重置邮件时使用 SendGrid", "ts": 1704700052000 }
]
Token 统计:
- 总消息数:53 条
- Token 使用:45,000 / 50,000 (90%)
- 触发压缩阈值:80%
ui_messages.json(约 150 条消息,包含所有交互细节):
- 用户消息:20 条
- API 请求状态:40 条(started/finished)
- AI 文本响应:30 条
- 工具调用请求:25 条(包含参数和 diff)
- 工具执行结果:25 条
- 其他状态消息:10 条
压缩后
api_conversation_history.json(54 条消息 - 保留所有原始消息 + 新增摘要):
typescript
[
// 前 50 条原始消息(保持不变)
{ role: "user", content: "帮我创建一个用户登录功能", ts: 1704700000000 },
{ role: "assistant", content: "好的,我会帮你创建用户登录功能...", ts: 1704700001000 },
{ role: "user", content: "使用 JWT 进行身份验证", ts: 1704700002000 },
{ role: "assistant", content: "明白了,我会使用 JWT...", ts: 1704700003000 },
// ... 中间 45 条消息(消息 5-49)
{ role: "user", content: "添加用户注册功能", ts: 1704700049000 },
// 新增的摘要消息
{
role: "assistant",
content: "## 对话摘要\n\n### 1. Previous Conversation\n...",
ts: 1704700050000,
isSummary: true // 标记为摘要
},
// 保留的最后 3 条消息(保持不变)
{ role: "user", content: "现在添加密码重置功能", ts: 1704700050000 },
{ role: "assistant", content: "好的,让我添加密码重置功能...", ts: 1704700051000 },
{ role: "user", content: "发送重置邮件时使用 SendGrid", ts: 1704700052000 },
]
传递给 LLM 的消息 (通过 getMessagesSinceLastSummary() 筛选后只有 5 条):
typescript
[
{ role: "user", content: "Please continue from the following summary:" }, // 提示消息
{ role: "assistant", content: "## 对话摘要\n...", isSummary: true }, // 摘要消息
{ role: "user", content: "现在添加密码重置功能" }, // 最近消息 1
{ role: "assistant", content: "好的,让我添加密码重置功能..." }, // 最近消息 2
{ role: "user", content: "发送重置邮件时使用 SendGrid" }, // 最近消息 3
]
ui_messages.json(新增压缩提示):
json
[
// ... 原有的约 xxx 条消息都保留 ...
// 新增压缩提示消息
{
"ts": 1704700053000,
"type": "say",
"say": "condense_context",
"text": "上下文已压缩:从 45,000 tokens 减少到 12,000 tokens",
"contextCondense": {
"summary": "用户请求创建用户管理系统,包括登录、注册、权限管理等功能...",
"cost": 0.15,
"newContextTokens": 12000,
"prevContextTokens": 45000
}
}
]
压缩效果对比
| 维度 | 压缩前 | 压缩后 | 说明 |
|---|---|---|---|
| 本地存储(apiConversationHistory) | |||
| 消息数量 | 53 条 | 54 条 | ↑ 增加 1 条摘要消息 |
| 存储完整性 | ✅ 完整 | ✅ 完整 | 所有原始消息都保留 |
| 传递给 LLM | |||
| 消息数量 | 53 条 | 5 条 | ↓ 减少 48 条(90.6%) |
| Token 使用 | 45,000 | 12,000 | ↓ 减少 33,000(73.3%) |
| 上下文使用率 | 90% | 24% | ↓ 降低 66% |
| 语义信息 | |||
| 关键信息 | ✅ 完整 | ✅ 完整 | 通过摘要保留 |
| 技术细节 | ✅ 完整 | ✅ 完整 | 摘要包含代码和文件 |
| 任务上下文 | ✅ 完整 | ✅ 完整 | 摘要包含下一步计划 |
关键理解
存储层(apiConversationHistory):
- ✅ 所有原始消息都保留(53 条)
- ✅ 新增 1 条摘要消息 (标记
isSummary: true) - ✅ 总消息数:54 条(53 + 1)
- ✅ 完整的对话历史,便于回溯和调试
传输层(传递给 LLM):
- ✅ 只传递必要的上下文(摘要 + 最近 3 条消息)
- ✅ 大幅减少 token 使用(从 53 条减少到 5 条)
- ✅ 通过
getMessagesSinceLastSummary()智能筛选 - ✅ 保持对话连续性和语义完整性
双层架构的优势:
-
存储层:
- 保留所有原始消息和摘要消息
- 完整的对话轨迹,便于回溯和调试
- 消息数量会随着压缩而增加(每次压缩 +1)
-
传输层:
- 只传递必要的上下文(摘要 + 最近消息)
- 大幅减少 token 使用和成本
- 通过智能筛选保持对话质量
为什么不删除消息?
- 完整性:保留完整对话历史,便于用户回顾
- 可追溯:出现问题时可以查看完整上下文
- 灵活性:可以根据需要调整传输策略
- 用户体验:UI 显示完整对话,用户不会感到信息丢失
- 调试友好:开发者可以查看完整的消息流转过程
5.8 双轨系统的核心价值
- API 历史:优化 token 使用,支持上下文压缩,确保高效的 LLM 交互
- UI 历史:保留完整记录,提供丰富的用户体验和调试能力
- 同步机制:确保两套历史始终保持一致,支持消息编辑、任务恢复等功能
- 灵活扩展:两套系统可以独立演进,互不影响
六、关键特性说明
6.1 递归式对话
特点:
- 使用栈结构管理请求队列
- 支持工具执行后自动继续对话
- AI 可以多次调用工具直到完成任务
优势:
- 无需用户干预,AI 自主完成复杂任务
- 支持多步骤推理和决策
- 自然的对话流程
6.2 流式响应
特点:
- 使用 AsyncGenerator 实现流式处理
- 实时显示 AI 生成的文本
- 支持中断和取消
优势:
- 降低用户等待时间
- 提升交互体验
- 及时发现问题
6.3 智能保护机制
连续错误检测:
- 记录连续错误次数
- 超过限制时请求用户反馈
- 防止无限重试
工具重复检测:
- 检测连续相同的工具调用
- 防止 AI 陷入死循环
- 提示用户尝试不同方法
自动批准限制:
- 基于请求次数限制
- 基于成本限制
- 防止过度消耗
6.4 上下文管理
自动压缩:
- 监控 token 使用率
- 自动触发压缩
- 保持对话连续性
双轨制历史:
- API 层:优化的对话历史
- UI 层:完整的交互记录
- 分离关注点
6.5 多 Provider 支持
统一接口:
ApiHandler接口抽象- 支持 Anthropic、OpenAI、Gemini 等
- 自动格式转换
Provider 特性:
- Anthropic:Prompt Caching
- OpenAI:o1 推理模式
- 统一的流式响应处理
七、总结
Kilo 核心消息流转过程体现了以下设计理念:
- 用户体验优先:流式响应、实时反馈、智能保护
- 自主性:递归式对话,AI 自主完成复杂任务
- 可靠性:错误检测、重复检测、自动批准限制
- 可扩展性:多 Provider 支持、工具系统、MCP 协议
- 性能优化:上下文压缩、Worker Pool、懒加载
整个流程从用户输入到 AI 返回结果,形成了一个完整的闭环,支持持续的对话和工具执行,最终帮助用户完成各种编程任务。