痛定思痛:我花了一个月重构 AI 助手,只为了解决这三个问题

写在前面

说实话,第一次用官方 Claude Code 的时候,我是真兴奋。终端里跑着 AI,能帮我改代码、跑命令,感觉像有了个 24 小时在线的高级工程师搭档。

但用了两周后,我开始纠结。

为什么每次切换模型都要重启会话?

为什么一个模型要包揽所有活儿,明明有些任务它并不擅长?

为什么补全系统这么笨,输个dq都匹配不到dao-qi

这些问题像苍蝇一样在我脑子里嗡嗡响。直到某天晚上,我盯着终端里那个"请重启会话以切换模型"的提示,突然一拍桌子——不行,我得自己搞一个。

这就是 Kode 诞生的故事。今天我想聊聊,在重构这个 AI 助手的过程中,我们踩过的坑、做出的取舍,以及那些让我半夜三点还在兴奋地敲键盘的设计决策。


一、多模型协作:从"单打独斗"到"团队作战"

1.1 问题的本质

官方 Claude Code 的设计很简单:一个会话绑定一个模型。这听起来没问题,直到你真实使用:

  • 你想让o3帮你设计系统架构(它的推理能力是真的强)

  • 然后让Opus帮你写具体代码(它对工程细节的把握更扎实)

  • 最后用Haiku快速处理一些简单任务(省成本啊!)

但在官方实现里,这三个需求要开三个会话。你的上下文、你的思考、你之前的讨论……全部要重来一遍。

这就像你明明有个全能团队,却每次只能派一个人上场。

1.2 我们的方案:模型指针系统

我们设计了一个简单的概念:模型指针

{ "modelPointers": { "main": "claude-sonnet-4-5-20250929", // 主对话用哪个模型 "task": "claude-opus-4-5-20251101", // Subagent 用哪个模型 "reasoning": "o3", // 复杂推理用哪个模型 "quick": "claude-haiku-4-5-20251001" // 快速任务用哪个模型 } }

这四个指针分别对应不同的使用场景。当你启动一个 Subagent 时,它会自动使用task指针指向的模型,而不用你每次手动指定。

但这只是开始。

1.3 真正的突破:运行时切换

我印象最深的是实现运行时模型切换的那天。

我们不想让用户重启会话。但这有个技术难点:AI 模型的上下文窗口大小不一样,API 格式也不一样,甚至有些模型根本不支持某些功能。

怎么在保持对话连续性的前提下切换模型?

我们的解决方案分三步:

  1. 上下文自适应裁剪:每个模型都有自己的maxTokens配置,切换时自动计算需要裁剪多少历史消息

  2. 工具兼容性检查:某些工具可能只在特定模型上可用,切换前要做验证

  3. 成本追踪分离:每个模型的 token 消耗独立统计,用户可以清楚地看到"这个任务用 o3 花了

    0.02"

代码大概长这样(简化版):

// src/utils/model/modelManager.ts (简化示意) class ModelManager { private profiles: Map<string, ModelProfile> private pointers: Record<string, string> resolveModel(pointer: string): ModelProfile { const profileName = this.pointers[pointer] || this.pointers.main const profile = this.profiles.get(profileName) if (!profile) { throw new Error(`Model ${profileName} not configured`) } return profile } // 关键:获取模型的上下文窗口大小 getMaxTokens(modelName: string): number { const profile = this.profiles.get(modelName) return profile?.maxTokens || 200000 // 默认 200k } }

这个设计带来的效果是啥?

你可以这样使用:

# 主对话用 Sonnet "帮我设计一个消息队列系统的架构" # Tab 键切换到 o3,继续对话 "用 o3 的深度推理能力,帮我分析这个架构的瓶颈" # 再切换到 Qwen Coder "现在帮我实现这个架构的代码"

整个过程中,上下文一直在,思考一直在,只有模型在变

1.4 专家咨询:AskExpertModel 的设计哲学

多模型协作的另一个场景是:主模型遇到难题,需要"请教"专家

比如你正在用 Sonnet 写代码,突然遇到一个复杂的算法问题。Sonnet 可能不太擅长,但你不想切换整个会话到 o3(那样会打断你的思路)。

我们的解决方案是AskExpertModel工具:

// 用户输入(在 Sonnet 会话中) @ask-o3 这个算法的时间复杂度怎么分析?我有点卡住了 # AI 内部调用 { "tool": "AskExpertModel", "input": { "question": "完整的问题描述+上下文...", "expert_model": "o3", "chat_session_id": "new" // 可以继续之前的专家会话 } }

这里的坑是什么?

专家模型是"孤立"的——它看不到你当前的对话上下文,除非你显式地传给它。

所以我们设计了一个强制要求:question参数必须是完全自包含的。AI 被训练成必须在问题中包含:

  1. 背景信息(你在做什么)

  2. 当前情况(遇到了什么问题)

  3. 独立问题(你想知道什么)

这不是过度设计,而是血的教训。早期版本没有这个要求,结果专家模型经常给出完全无关的建议(因为它不知道你在说什么)。

1.5 并行处理:多 Subagent 同时工作

最让我兴奋的功能是并行 Subagent。

想象这个场景:你需要重构三个模块。传统方式是一个一个来,但我们的TaskTool支持启动多个后台 Subagent:

// AI 可以在一个响应中发起多个任务 yield* launchAgent("重构模块 A", "模块 A 的具体任务...", "general-purpose") yield* launchAgent("重构模块 B", "模块 B 的具体任务...", "general-purpose") yield* launchAgent("重构模块 C", "模块 C 的具体任务...", "general-purpose") // 这三个 agent 会并行运行

技术上是怎么实现的?

每个 Subagent 都是独立的 async 操作,我们用一个Map来追踪它们的状态:

// src/utils/session/backgroundTasks.ts (简化) const backgroundTasks = new Map<string, AgentTask>() async function upsertBackgroundAgentTask(task: AgentTask) { backgroundTasks.set(task.agentId, task) // UI 可以轮询这个 Map 来显示进度 }

用户可以用TaskOutput工具随时查看结果:

@task-output (block=false) # 查看状态但不等待 @task-output (block=true) # 等待完成

这带来的体验提升是巨大的。

以前重构三个模块要串行等,现在可以同时启动,然后去喝杯咖啡,回来一次性看三个结果。


二、智能补全:从"机械匹配"到"读心术"

2.1 用户的真实痛点

我观察了自己和其他开发者使用 AI 助手的习惯,发现一个有趣的现象:

没人喜欢打完整的命令。

  • 输入dao-qi-harmony-designer?太长,谁记得住?

  • 输入@run-agent-再去翻列表?太慢,打断思路

  • 输入dq希望匹配dao-qi?理所当然的期望

但官方实现的补全是严格前缀匹配:dq只能匹配以dq开头的东西。

这就像你搜索"苹果",结果只给你"苹果电脑",不给你"苹果手机"。

2.2 我们的目标:像读心一样懂用户

我们想要的补全系统是这样的:

  1. 缩写支持dqdao-qi

  2. 连字符感知daoqidao-qi-harmony-designer

  3. 数字后缀智能py3python3(不是python35

  4. 上下文自动识别:输入gp5自动知道是@ask-gpt-5(加前缀)

  5. Unix 命令库:输入git自动提示常见命令(commitpushstatus...)

这需要融合多种匹配算法。

2.3 算法融合的实践

我们最终融合了 7 种算法:

算法用途示例
精确匹配快速路径gitgit
前缀匹配基础需求gigit
连字符分割处理复合词daqidao-qi
缩写展开处理简写dqdao-qi
子串匹配模糊查找harmonydao-qi-harmony-designer
Levenshtein 距离容错gittgit
数字后缀处理版本号智能py3python3(不是python35)

关键是怎么组合这些算法。

我们的策略是优先级管道

// src/utils/fuzzyMatcher.ts (简化示意) function matchCandidate(input: string, candidate: string): Score { // 1. 精确匹配(最高优先级) if (input === candidate) return Score.MAX // 2. 前缀匹配 if (candidate.startsWith(input)) return 0.9 // 3. 连字符分割匹配 const hyphenScore = matchWithHyphenAwareness(input, candidate) if (hyphenScore > 0.7) return hyphenScore // 4. 缩写匹配 const abbrScore = matchAbbreviation(input, candidate) if (abbrScore > 0.6) return abbrScore // 5. 子串 + Levenshtein return matchFuzzy(input, candidate) }

每个算法返回一个 0-1 的分数,我们按分数排序,取前 10 个结果。

数字后缀处理是个特殊坑。

早期版本有个 bug:用户输入py3,结果匹配到了python35而不是python3

问题在于我们的子串匹配算法把3当作了普通字符,没有理解这是"版本号"。

修复方案是添加特殊规则:

function handleNumericSuffix(input: string, candidate: string): Score { // 提取数字后缀 const inputSuffix = extractTrailingNumber(input) const candidateSuffix = extractTrailingNumber(candidate) if (inputSuffix !== null && candidateSuffix !== null) { // 比如输入 "py3",候选是 "python35" // 3 != 35,直接拒绝 if (inputSuffix !== candidateSuffix) { // 但如果是 "python3" 的后缀,那就接受 const candidateWithoutSuffix = candidate.replace(/\d+$/, '') if (candidateWithoutSuffix + inputSuffix === candidate) { return 1.0 // 完美匹配 } return 0 // 不匹配 } } return matchNormal(input, candidate) }

2.4 Unix 命令库:从零开始

补全系统的另一个痛点是:系统命令

用户输入git,希望看到的是git commitgit push这些常用命令,而不是系统 PATH 里所有带git的东西。

但问题是:不同系统的命令不一样。macOS 有brew,Windows 可能没有。

我们的解决方案是:

  1. 预置 500+ 常用命令库(按优先级排序)

  2. 运行时扫描系统 PATH

  3. 取交集:只提示既在库里又在本系统的命令

// src/utils/commonUnixCommands.ts (示意) const COMMON_COMMANDS = [ { name: 'git', priority: 100 }, { name: 'npm', priority: 95 }, { name: 'docker', priority: 90 }, // ... 500+ 条目 ] async function getAvailableCommands(): Promise<string[]> { const systemCommands = await scanSystemPATH() const commonCommands = COMMON_COMMANDS.map(c => c.name) // 取交集 return commonCommands.filter(cmd => systemCommands.includes(cmd)) }

效果如何?

用户输入g,优先看到git(优先级 100),而不是gnome-terminal(优先级 5)。

输入doc,如果系统里没装 Docker,就不会提示docker(即使它在库里)。

2.5 智能上下文:去掉@的负担

这是我最得意的小设计。

在官方实现里,你要调用 agent 或 model,必须手动加@前缀:

@run-agent-architect @ask-claude-sonnet-4

为什么要用户记住这个语法?

我们做了个简单但强大的改动:自动识别 + 自动补全前缀

当用户输入arch时:

  1. 补全系统检测到这是在匹配run-agent-architect

  2. 自动识别这是 agent 类型

  3. 补全结果直接显示@run-agent-architect

  4. 用户按 Tab 或 Enter,自动补全完整格式

代码大概是:

// src/hooks/useUnifiedCompletion.ts (简化) function detectType(candidate: string): 'agent' | 'model' | 'file' | 'command' | null { if (candidate.startsWith('run-agent-')) return 'agent' if (candidate.startsWith('ask-')) return 'model' // ... 其他类型 } function addPrefixIfNeeded(candidate: string, type: string): string { if (type === 'agent') return `@${candidate}` if (type === 'model') return `@${candidate}` return candidate }

用户体验的变化:

  • 以前:输入@run-agent-再翻列表

  • 现在:输入arc直接看到@run-agent-architect

小事?是的。但一天用下来,这些小事能省下几十次 keystroke。


三、流式架构:为什么 AsyncGenerator 是正确选择

3.1 用户的感知

我在测试早期版本时,发现一个让我抓狂的问题:

当 AI 在执行一个长时间任务时,用户完全不知道发生了什么。

比如 AI 在运行一个大型的 bash 命令,或者读取一个大文件。终端就卡在那里,没有进度条,没有提示,没有任何反馈。

用户会想:是死了吗?还是在工作?

更糟糕的是,如果用户等得不耐烦按 Ctrl+C,任务可能已经执行了一半,但没有任何东西保存下来。

3.2 AsyncGenerator 的优势

我们一开始用的是 Promise:

// 早期版本(Promise) async function executeTool(input: Input): Promise<Result> { // 执行任务 const result = await doWork(input) return result } // UI 层 const result = await executeTool(input) renderResult(result) // 只能在结束时显示结果

这有个致命问题:你只能在结束时显示结果

我们改成 AsyncGenerator:

// 当前版本(AsyncGenerator) async function* executeTool(input: Input): AsyncGenerator<Progress> { yield { type: 'progress', content: '开始处理...' } // 执行任务 const result = await doWork(input) yield { type: 'result', data: result } } // UI 层 for await (const progress of executeTool(input)) { if (progress.type === 'progress') { renderProgress(progress.content) // 实时显示进度 } else if (progress.type === 'result') { renderResult(progress.data) // 显示最终结果 } }

效果是立竿见影的:

  1. 用户看到实时进度("正在读取文件... 50%")

  2. 可以随时按 Ctrl+C 取消(Generator 会自动清理)

  3. 增量结果显示(不需要等全部完成)

3.3 进度节流的坑

但 AsyncGenerator 也有个坑:如果你 yield 太频繁,UI 会卡死

早期我们的 Subagent 工具每执行一个工具就 yield 一次进度。结果有个任务启动了 100 个文件操作,UI 直接被进度更新淹没,终端卡死了。

我们加了节流机制:

// src/tools/agent/TaskTool/TaskTool.tsx:650-798 (示意) const PROGRESS_THROTTLE_MS = 200 // 200ms 节流 let lastProgressEmitAt = 0 const recentActions: string[] = [] // 最多记录 6 个最近动作 for await (const message of queryFn(...)) { // 追踪最近的工具调用 if (message.type === 'assistant') { for (const block of message.message.content) { if (block.type === 'tool_use') { recentActions.push(summarizeToolUse(block.name, block.input)) if (recentActions.length > 6) { recentActions.shift() // 只保留最近 6 个 } } } } // 节流:至少 200ms 才 yield 一次 const now = Date.now() if (now - lastProgressEmitAt >= PROGRESS_THROTTLE_MS) { yield { type: 'progress', content: renderProgress(recentActions) } lastProgressEmitAt = now } }

效果:

  • 用户看到的是"每 200ms 更新一次"的平滑进度

  • 进度文本包含最近 6 个动作("Read file A, Edit file B, Grep pattern C...")

  • 不会被大量更新淹没

3.4 取消处理的细节

AsyncGenerator 的另一个优势是取消处理

用户按 Ctrl+C 时,我们需要:

  1. 停止当前的工具调用

  2. 清理临时文件

  3. 保存已完成的工作

用 Promise 这很难做,因为你不知道任务执行到哪一步了。

但 Generator 可以在每个 yield 点检查取消信号:

async function* executeTool(input: Input, abortSignal: AbortSignal) { yield { type: 'progress', content: '开始...' } // 检查取消 if (abortSignal.aborted) { yield { type: 'cancelled' } return } // 执行第一步 const step1 = await doStep1(input) // 再次检查 if (abortSignal.aborted) { cleanupStep1(step1) // 清理 yield { type: 'cancelled' } return } // 执行第二步 const step2 = await doStep2(step1) yield { type: 'result', data: step2 } }

这在处理长时间任务时特别重要。

比如 Subagent 启动了 3 个文件操作,用户在第 2 个时取消了。我们只需要回滚第 1 个操作,第 2 和第 3 个根本没开始。


四、Fork Context:Subagent 的上下文隔离艺术

4.1 问题背景

当主 Agent 启动一个 Subagent 时,有个关键问题:

Subagent 需要多少父对话的上下文?

给太少,Subagent 不知道任务背景;给太多,可能包含不兼容的工具调用(父 Agent 用了某个工具,但 Subagent 工具集里没有)。

4.2 我们的方案:Fork Context

我们设计了一个叫"Fork Context"的机制:

  1. 找到 TaskTool 的调用点在父对话消息日志中

  2. 只取之前的消息作为上下文

  3. 用特殊标记隔离,告诉 Subagent "这些只是背景"

代码示意:

// src/tools/agent/TaskTool/TaskTool.tsx:192-283 function buildForkContextForAgent(options: { enabled: boolean, prompt: string, toolUseId: string, // TaskTool 的调用 ID messageLogName: string, forkNumber: number }) { if (!options.enabled) { return { forkContextMessages: [], promptMessages: [userPrompt] } } // 从日志中找到 TaskTool 的调用点 const mainMessages = readJsonArrayFile(getLogPath(options.messageLogName)) const toolUseIndex = mainMessages.findIndex(msg => msg.message.content.some(block => block.type === 'tool_use' && block.id === options.toolUseId ) ) // 只取之前的消息 const forkContextMessages = mainMessages.slice(0, toolUseIndex) // 添加隔离标记 const forkMarker = createUserMessage(` ### FORKING CONVERSATION CONTEXT ### - The messages above are from main thread - They are provided as context only - Some tools may not be available in sub-agent context ### END FORK CONTEXT ### `) return { forkContextMessages, promptMessages: [forkMarker, userPrompt] } }

这解决了几个问题:

  1. Subagent 不会看到父 Agent 的工具调用结果(避免困惑)

  2. Subagent 有足够的上下文理解任务

  3. 父对话的工具不兼容不会影响 Subagent

4.3 配置化的灵活性

不是所有 Subagent 都需要 Fork Context。有些简单的任务(比如"计算这个表达式的值")根本不需要上下文。

所以我们在 Agent 配置中加了开关:

// agent 配置示例 { "name": "simple-calculator", "forkContext": false, // 不需要上下文 "tools": ["Calculator"] } { "name": "code-reviewer", "forkContext": true, // 需要上下文(比如之前的讨论) "tools": ["Read", "Grep", "Lsp"] }

这让系统既强大又高效。


五、MCP 集成:动态工具的挑战

5.1 MCP 是什么?

MCP (Model Context Protocol) 是 Anthropic 开发的一个协议,允许 AI 助手动态加载外部服务提供的工具。

比如你有个 GitHub MCP 服务器,它可以提供createIssuelistPRs等工具。AI 助手在运行时连接这个服务器,就能使用这些工具。

但这有个技术挑战:TypeScript 的静态类型系统。

5.2 静态 vs 动态

我们的工具系统是基于 TypeScript 接口的:

interface Tool { name: string inputSchema: ZodSchema call(input: z.infer<this['inputSchema']>): AsyncGenerator<...> }

这要求每个工具在编译时就有明确的类型。

但 MCP 工具是运行时动态加载的,我们怎么知道它长什么样?

5.3 我们的方案:运行时桥接

我们用 Zod schema 作为"动态类型"的桥梁:

// MCP 服务器返回工具描述 { "name": "github_create_issue", "inputSchema": { "type": "object", "properties": { "title": { "type": "string" }, "body": { "type": "string" } } } } // 我们把它转成 Zod schema const zodSchema = z.object({ title: z.string(), body: z.string() }) // 创建一个包装工具 const mcpTool: Tool = { name: "github_create_issue", inputSchema: zodSchema, async *call(input) { // 调用 MCP 服务器 const result = await mcpClient.callTool(this.name, input) yield { type: 'result', data: result } } }

这样,动态工具就能无缝融入静态系统。

5.4 错误处理的坑

MCP 工具可能随时失败(网络断开、服务器崩溃、权限问题)。

我们给每个 MCP 工具包装了一层错误处理:

async function* wrapMcpTool(tool: MCPTool, mcpClient: MCPClient) { try { // 心跳检查 if (!await mcpClient.ping()) { throw new Error('MCP server not responding') } const result = await mcpClient.callTool(tool.name, input) yield { type: 'result', data: result } } catch (error) { // 友好的错误提示 yield { type: 'error', message: `MCP tool '${tool.name}' failed: ${error.message}` } } }

用户不会看到原始的 stack trace,而是清晰的错误信息。


六、性能优化的那些细节

6.1 Bun vs Node.js

Kode 优先使用 Bun 运行时,而不是 Node.js。

为什么?

在我们的测试中,Bun 的启动速度比 Node.js 快3-5 倍

$ time node cli.js --version node cli.js --version 0.45s user 0.12s system 102% cpu 0.558 total $ time bun cli.js --version bun cli.js --version 0.12s user 0.08s system 95% cpu 0.210 total

对于一个 CLI 工具,启动速度是用户体验的第一道门槛。没人愿意等半秒钟才看到提示符。

但我们也保留了 Node.js 兼容。

如果用户的系统里没有 Bun,我们会自动降级到 Node.js 20.18.1+:

// cli.js (智能包装器) const hasBun = await commandExists('bun') const runtime = hasBun ? 'bun' : 'node' execSync(`${runtime} src/entrypoints/cli.tsx`, { stdio: 'inherit' })

6.2 缓存策略

我们用了多层缓存来提升性能:

缓存类型位置生命周期用途
模型配置内存进程级避免重复读取配置文件
工具列表内存会话级工具发现成本高,只做一次
文件内容内存会话级AI 可能多次读取同一文件
MCP 连接内存会话级MCP 服务器连接成本高

关键是缓存失效策略

// 文件缓存示例 const fileCache = new Map<string, { content: string, timestamp: number }>() async function readFileWithCache(path: string, freshTime: number = 5000) { const cached = fileCache.get(path) // 如果缓存未过期,直接返回 if (cached && Date.now() - cached.timestamp < freshTime) { return cached.content } // 否则重新读取 const content = await fs.readFile(path, 'utf-8') fileCache.set(path, { content, timestamp: Date.now() }) return content }

freshTime 的选择是关键。

太短(比如 100ms),缓存基本没用;太长(比如 1 分钟),用户修改文件后 AI 可能看不到。

我们选择 5 秒,这是基于观察:大部分情况下,AI 在 5 秒内不会多次读取同一文件,但如果用户修改了文件,5 秒后 AI 就能看到新内容。

6.3 增量渲染

对于大文件或长输出,我们不会一次性渲染全部内容,而是增量渲染

// UI 组件示例 function StreamingOutput({ stream }) { const [output, setOutput] = useState('') useEffect(() => { const reader = stream.getReader() async function read() { while (true) { const { done, value } = await reader.read() if (done) break // 增量更新 setOutput(prev => prev + value) } } read() }, [stream]) return <Text>{output}</Text> }

用户看到的体验是:

  • AI 开始生成答案时,文本就逐步出现

  • 不用等到全部生成完才显示

  • 类似 ChatGPT 的流式效果


七、踩过的那些坑

7.1 Windows 支持的血泪史

最初 Kode 只支持 Unix 系统(Linux/macOS),因为我们大量使用了 Unix 特有的命令和路径。

然后用户开始提 issue:"Windows 上跑不了啊!"

我们踩的第一个坑是路径分隔符。

Unix 用/,Windows 用\。早期代码直接拼接路径:

// 错误示例 const filePath = workspaceDir + '/' + relativePath // Windows 上会出问题

修复方案是使用path模块:

import path from 'path' const filePath = path.join(workspaceDir, relativePath) // 跨平台

第二个坑是 shell 命令。

Unix 的rm -rf在 Windows 上不存在(Windows 用delRemove-Item)。

我们写了一个shell 抽象层

// src/utils/shell.ts (示意) const shellCommands = { removeFile: process.platform === 'win32' ? ['del', '/F'] : ['rm', '-f'], listDirectory: process.platform === 'win32' ? ['dir'] : ['ls', '-la'], // ... } async function removeFile(path: string) { const cmd = shellCommands.removeFile await execa(cmd[0], [...cmd.slice(1), path]) }

第三个坑是终端编码。

Windows CMD 默认用 GBK 编码,而我们用 UTF-8。这会导致中文路径乱码。

解决方案是在启动时设置编码:

// Windows 特殊处理 if (process.platform === 'win32') { process.env.POWERLINE_COMMAND = 'echo' process.env.LANG = 'en_US.UTF-8' }

现在 Kode 在 Windows 上可以原生运行了。

7.2 query.ts 文件过大的教训

我们的query.ts文件(核心 AI 编排逻辑)长到了37KB

这是个严重的代码坏味道。文件太大意味着:

  • 难以理解(一次只能看一部分)

  • 难以测试(依赖太多)

  • 难以维护(改动可能影响其他功能)

我们计划在下一版本拆分它:

query.ts (当前 37KB) ├── query-core.ts # 核心循环 ├── query-tools.ts # 工具调用逻辑 ├── query-context.ts # 上下文管理 ├── query-permissions.ts # 权限检查 └── query-streaming.ts # 流式处理

教训:当一个文件超过 500 行时,就该考虑拆分了。

7.3 Zod Schema 的性能坑

我们大量使用 Zod 来验证工具输入。但有个问题:Zod 的验证成本不低

早期版本,每次工具调用都要重新验证 schema:

// 低效版本 async function callTool(tool: Tool, input: unknown) { // 每次都解析 schema const schema = tool.inputSchema const validated = schema.parse(input) // ... }

优化方案是缓存解析后的 schema

// 高效版本 const schemaCache = new WeakMap<Tool, z.ZodType>() async function callTool(tool: Tool, input: unknown) { let schema = schemaCache.get(tool) if (!schema) { schema = tool.inputSchema schemaCache.set(tool, schema) } const validated = schema.parse(input) // ... }

这减少了约 30% 的工具调用开销。


八、未来的坑和机会

8.1 待解决的挑战

挑战 1:上下文压缩

随着对话增长,上下文会越来越大。即使我们有自动裁剪,但裁剪策略很简单("删掉最旧的消息")。

未来我们想实现更智能的压缩:

  • 识别关键信息(用户的需求、重要的决策)

  • 压缩冗余内容(重复的文件读取、无关的尝试)

  • 保留结构化的摘要

挑战 2:工具组合

目前工具是独立调用的。但有些任务需要组合多个工具:

1. Grep 找到所有 TODO 2. 对每个 TODO,Read 相关文件 3. 用 AskExpertModel 分析每个 TODO 的优先级 4. 用 TodoWrite 更新优先级

我们想让 AI 能自动组合这些步骤,而不是用户一步步指导。

挑战 3:本地模型支持

目前 Kode 只支持 API 模型。但很多用户想要本地模型(隐私、成本、离线使用)。

本地模型的挑战是:

  • 模型文件很大(几 GB)

  • 推理速度慢(没有 GPU)

  • 量化后的质量损失

我们正在调研 Ollama 和 LM Studio 的集成。

8.2 令人兴奋的方向

方向 1:多模态支持

AI 模型开始支持图像输入。我们想让 Kode 能:

  • 读取截图(比如错误信息截图)

  • 分析图表(比如性能监控图)

  • 生成图片(比如架构图)

方向 2:语音交互

在终端里用语音输入指令,听起来很科幻,但在某些场景下很实用:

  • 手不在键盘上

  • 快速记录想法

  • 多任务操作

方向 3:协作功能

如果多个开发者同时用 Kode 在同一个项目上工作,他们可以:

  • 共享 Agent 会话("你看看我让 AI 生成的这个架构")

  • 协作编辑("我改了这个文件,你让 AI review 一下")

  • 知识共享("这个问题的解决方案要保存到项目知识库")

更多AIGC文章

RAG技术全解:从原理到实战的简明指南

更多VibeCoding文章

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1140974.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

异地就医备案-须知内容

1、适用人群 (1)异地长期居住人员,包括异地安置退休人员、异地长期居住人员、常驻异地工作人员等长期在异地居住、生活、工作的人员。 (2)临时外出就医人员,包括异地转诊就医人员,因工作、旅游等原因异地急诊抢救人员以及其他临时外出就医人员。 2、备案材料 (1)异…

基于SpringBoot的拼装模型销售管理系统的设计与实现(源码+lw+部署文档+讲解等)

课题介绍 随着拼装模型爱好者群体不断扩大&#xff0c;模型销售市场规模持续增长&#xff0c;但当前拼装模型销售行业普遍存在商品品类繁杂难管理、订单处理效率低下、库存管控不精准、客户需求响应滞后等问题&#xff0c;制约了商家运营效率与用户购物体验的提升。本课题以搭建…

SAP UI5 概念辨析:namespace,library 和 module 的理解

本文笔者从接到教程学习者的一个实际问题开始。 这位朋友想使用 URLHelper 的 redirect 功能,在 SAP UI5 应用里进行页面跳转。 他查询 SAP UI5 帮助文档,看到了这个 redirect 方法的输入参数说明: 然后看到这个 URLHelper 抬头区域的三个字段: 类型为 namespace libra…

基于SpringBoot的陪诊服务平台系统(源码+lw+部署文档+讲解等)

课题介绍随着人口老龄化加剧、医疗资源分布不均衡&#xff0c;独居老人、行动不便者、异地就医人群等普遍面临就医流程繁琐、无人陪同协助的困境&#xff0c;陪诊服务需求持续增长&#xff0c;但当前陪诊市场存在服务标准不统一、供需对接低效、服务过程缺乏监管、资金结算不规…

基于SpringBoot的农村客运服务系统(源码+lw+部署文档+讲解等)

课题介绍随着乡村振兴战略深入推进&#xff0c;农村地区出行需求持续增长&#xff0c;但当前农村客运普遍存在线路规划不合理、班次信息不透明、票务管理滞后、运营监管低效等问题&#xff0c;制约了客运服务质量提升&#xff0c;难以满足群众便捷出行需求。本课题以优化农村客…

关系系统架构升级指南:从“好友模式”到“心动模式”的平滑迁移

资深后端工程师李峰在连续三周为“相亲对象”调试代码、优化简历、提供职场解决方案后&#xff0c;收到了对方诚挚的感谢&#xff1a;“你真是我见过最靠谱的技术伙伴&#xff01;”——这个评价比线上系统的致命Bug更让他无从排查。一、系统诊断&#xff1a;为何你的情感进程总…

基于SpringBoot的农村留守儿童援助信息系统(源码+lw+部署文档+讲解等)

课题介绍随着乡村振兴战略推进&#xff0c;农村留守儿童关爱援助工作愈发重要&#xff0c;但当前援助工作普遍存在留守儿童信息分散、援助资源对接不畅、帮扶过程缺乏动态跟踪、监管反馈机制不完善等问题&#xff0c;制约了援助服务的精准性与实效性。本课题以搭建高效协同的援…

基于SpringBoot的社区物资交易互助平台毕业设计

博主介绍&#xff1a;✌ 专注于Java,python,✌关注✌私信我✌具体的问题&#xff0c;我会尽力帮助你。一、研究目的本研究旨在构建一个基于SpringBoot框架的社区物资交易互助平台&#xff0c;以实现社区内物资的有效流通和共享&#xff0c;提高社区成员的生活质量。具体研究目的…

C语言 条件编译宏

一、具体代码与分析如下#include <stdio.h>/* 1.C语言的条件编译宏是预处理阶段的指令&#xff0c;用于根据指定条件决定代码段是否参与编译; 2.#ifdef MACRO/#endif&#xff1a;判断 MACRO 是否被#define定义; 3.#ifndef MACRO/#endif&#xff1a;判断 MACRO 是否未被定…

基于SpringBoot的考编论坛网站毕业设计源码

博主介绍&#xff1a;✌ 专注于Java,python,✌关注✌私信我✌具体的问题&#xff0c;我会尽力帮助你。一、研究目的本研究旨在构建一个基于SpringBoot框架的考编论坛网站&#xff0c;以实现以下研究目的&#xff1a; 首先&#xff0c;通过设计并实现一个功能完善的考编论坛网站…

内网渗透中的“眼睛”与“耳朵”:工作组信息收集技术全解析

内网渗透中的“眼睛”与“耳朵”&#xff1a;工作组信息收集技术全解析在授权渗透测试中&#xff0c;高效的信息收集是横向移动的基石。掌握这些命令&#xff0c;你就能像管理员一样“看见”整个系统。前言&#xff1a;为何信息收集如此重要&#xff1f; 在内网渗透测试中&…

GDAL 实现矢量裁剪

前言 ❝ 矢量数据作为数据处理的半壁江山&#xff0c;在日常工作中涉及到多种操作&#xff0c;矢量数据裁剪尤其具有代表性和重要性&#xff0c;是常用操作&#xff0c;核心原理为从指定数据中提取出目标范围。在之前的文章中讲了如何使用GDAL或者ogr2ogr工具将txt以及csv文本数…

至理名言【人生智慧】

做自己认为有价值的事生活中的爱&#xff0c;会让生活变可爱爱不是一种感觉&#xff0c;而是一种决定

基于SpringBoot的足球社区管理系统毕业设计

博主介绍&#xff1a;✌ 专注于Java,python,✌关注✌私信我✌具体的问题&#xff0c;我会尽力帮助你。一、研究目的本研究旨在设计并实现一个基于SpringBoot框架的足球社区管理系统&#xff0c;以满足足球爱好者在信息交流、赛事组织、社区互动等方面的需求。具体研究目的如下&…

基于SpringCloud的在线交易电商平台毕业设计

博主介绍&#xff1a;✌ 专注于Java,python,✌关注✌私信我✌具体的问题&#xff0c;我会尽力帮助你。一、研究目的本研究旨在深入探讨基于SpringCloud架构的在线交易电商平台的设计与实现&#xff0c;以提升电商平台的性能、可扩展性和稳定性。具体研究目的如下&#xff1a; 首…

基于html5的网上团购系统设计与实现毕业论文+PPT(附源代码+演示视频)

文章目录基于html5的网上团购系统设计与实现一、项目简介&#xff08;源代码在文末&#xff09;1.运行视频2.&#x1f680; 项目技术栈3.✅ 环境要求说明4.包含的文件列表&#xff08;含论文&#xff09;数据库结构与测试用例系统功能结构前台运行截图后台运行截图项目部署源码…

基于SpringBoot的养老院管理系统毕设

博主介绍&#xff1a;✌ 专注于Java,python,✌关注✌私信我✌具体的问题&#xff0c;我会尽力帮助你。一、研究目的本研究旨在设计并实现一套基于SpringBoot框架的养老院管理系统&#xff0c;以提升养老院管理效率和服务质量。具体研究目的如下&#xff1a;优化养老院管理流程&…

基于SpringBoot的新能源充电系统毕业设计

博主介绍&#xff1a;✌ 专注于Java,python,✌关注✌私信我✌具体的问题&#xff0c;我会尽力帮助你。一、研究目的本研究旨在深入探讨基于SpringBoot框架的新能源充电系统的设计与实现&#xff0c;以应对当前新能源汽车产业发展中充电基础设施不足、充电效率低下以及用户体验不…

基于SpringBoot的校园失物招领系统毕设

博主介绍&#xff1a;✌ 专注于Java,python,✌关注✌私信我✌具体的问题&#xff0c;我会尽力帮助你。一、研究目的本研究旨在设计并实现一个基于SpringBoot框架的校园失物招领系统&#xff0c;以解决当前校园失物招领过程中存在的诸多问题。具体研究目的如下&#xff1a; 首先…

A.每日一题——1161. 最大层内元素和

题目链接&#xff1a;1161. 最大层内元素和&#xff08;中等&#xff09; 算法原理&#xff1a; 解法&#xff1a;层序遍历 9ms击败53.81% 时间复杂度O(N) 思路很简单&#xff0c;就是层序遍历的同时统计一下每层元素的和&#xff0c;然后利用顺序表找到对应最大值所在层数即可…