Claude Code 源码分析:轮次上下文、事件注入与会话恢复
Claude Code 中主 Agent 和子 Agent 的异步协作机制:通过事件队列实现动态上下文管理,而非直接共享消息流。核心机制包括:1) 工具采用懒加载策略,基于历史发现结果增量注入;2) 每轮推理重新整理上下文,在固定节点统一处理外部事件;3) 通过随机 ID 标识子 Agent,并在运行时硬拦截关键递归路径(如 fork worker 不得再派生子任务)。这种设计既保持了协作灵活性,
Claude Code 源码分析:轮次上下文、事件注入与会话恢复
在设计一个支持多 Agent 协作的系统时,我们通常会面临几个核心的工程挑战,大家可以先思考一下自己会怎么解:
- 工具上下文超载:当接入了上百个 MCP 外部工具时,如果每轮都把全量 Schema 带给 LLM,会导致极高的 Token 成本和选择噪声,该如何动态管控?
- 长耗时子任务的阻塞与回调:当主 Agent 派发了一个需要运行十分钟的后台分析任务,主进程显然不能一直挂起死等。但如果放后台跑,随之而来的问题是:
- 如果主 Agent 还在和用户对话(没有退出本轮循环),回调怎么优雅插入才不会打断当前推理?
- 如果主 Agent 已经回复完毕并退出了本轮循环,任务完成时怎么重新拉起主 Agent?
- 回复的消息要怎么注入,才能既被大模型注意到,又不会导致上下文历史混乱?
- 递归派生的失控:如果大模型产生幻觉,遇到报错就不停地派生新的子 Agent 去“外包”问题,如何防止系统资源被瞬间耗尽?
- 会话中断与恢复:用户随时可能关掉终端或者按
Ctrl+C退出,进程死掉后,系统怎么保证重启时能一字不差地把之前的对话上下文(包括各种隐藏工具调用和系统事件)重新拼出来?
针对这些问题,Claude Code 给出的答案并不是“写更复杂的 Prompt 去警告模型”,而是从底层打造了一个事件驱动的异步运行时。
它放弃了简单的 while(true) 循环,改为按 turn(轮次)动态组装上下文;它通过全局队列来接收异步长耗时任务的回调,并在安全的轮次边界把结果作为附件(Attachment)注入;最后,它将一切事件序列化到持久层的 transcript 里,使得会话可以在任何时候精准重建。
1. 动态工具发现(应对上下文超载)
如果每一轮都把所有工具 schema 都发给模型,不仅浪费 token,还会导致模型注意力稀释。Claude Code 采用的是**“先声明搜索入口,后按发现结果增量注入”**的策略。
算法与流程 (How)
- 首轮请求:只带上核心常驻工具(如 Bash、Read)和
ToolSearchTool,其余 MCP 工具(deferred tools)被隐藏。 - 模型搜索:模型如果发现缺少能力,调用
ToolSearchTool。 - 返回引用:搜索结果不返回完整的工具 Schema,而是返回一个轻量级的
tool_reference占位符。 - 历史扫描:在组装下一轮请求前,
extractDiscoveredToolNames()会遍历历史消息,读取tool_result里的tool_reference,提取出“已经发现过的工具”,toolSearch.ts#L545-L592。- 注:即使历史消息被压缩,系统也会从
compact_boundary捞回这部分事实。
- 注:即使历史消息被压缩,系统也会从
- 按需注入:
claude.ts过滤工具列表,只有被扫描到的 deferred tools 才会被完整打包发给 API,claude.ts#L1154-L1267。
原理与复杂度 (Why)
- 总复杂度:
O(C + D)(C 为常驻核心工具数,D 为当前任务已发现的工具数)。扫描历史消息的动作纯本地执行,耗时仅数毫秒,成功将主导项(网络与推理成本)大幅削减。 - 具象化类比:就像去图书馆,你手里只有一台“检索终端”(
ToolSearchTool)。查到需要的书后,终端只吐出一张“借书小票”(tool_reference)。下次你去柜台发 API 请求时,系统看到小票,才会把厚厚的实体书(完整的 Tool Schema)搬给你。
2. 上下文与事件队列(应对并发与竞态)
Claude Code 发送给模型的请求,messages 不只是聊天记录,它还承载系统事件。子任务完成、工具结果返回、运行时插入的提醒,本质上都会进入同一条时间线。
为了更直观,可以先把一轮请求抽象成下面这个结构:
{
"system": [
"System Prompt",
"System Context",
"Memory Rules"
],
"tools": [
"tool definitions (full or deferred-filtered)"
],
"messages": [
{ "role": "user", "content": "用户输入和环境上下文" },
{ "role": "assistant", "content": "模型回复或 tool_use" },
{ "role": "user", "content": "tool_result" },
{ "role": "user", "content": "<task-notification>...</task-notification>" }
]
}
这里最重要的一点是:messages 不只是聊天记录,它还承载系统事件。Claude Code 自定义了几种特殊的 XML 结构,并把它们伪装成 user 角色发给大模型。
举几个真实的注入例子:
1. Task Notification(后台子任务完成通知)
这里有一个很核心的问题:大模型怎么知道这个通知对应的是它之前发出的哪个任务?
答案是:通过双向 ID 绑定。
当大模型最初调用 AgentTool 时(比如带有一个 tool_use_id="tool_123"),AgentTool 会立即返回一个包含了新生成 agentId(比如 a3f2c1...)的执行结果,告诉大模型“任务已后台启动,请记住这个 ID”。
当任务真正跑完时,系统构造的通知里,不仅会带上 task-id,还会带上原始的 tool_use_id,LocalAgentTask.tsx#L248-L253。这样大模型一看到这个通知,就能瞬间回忆起它的前因后果:
{
"role": "user",
"content": "<task-notification>\n<task-id>a3f2c1b4d5e6</task-id>\n<tool_use_id>tool_123</tool_use_id>\n<output-file>/path/to/output.jsonl</output-file>\n<status>completed</status>\n<summary>Agent \"Analyze logs\" completed</summary>\n<result>Found 3 errors.</result>\n</task-notification>"
}
防压缩保护与抗遗忘设计(The “Grep vs Attention” Problem):
这里有一个更深层的问题:Attention 机制在长上下文中寻找这种无意义的随机 ID(如 toolu_01A...),其准确度是远不如传统程序的 grep 的。因为无意义字符会被 Tokenizer 撕裂,且中间的历史记录极易发生 “Lost in the Middle” 遗忘现象。
如果子任务跑了很久,期间主对话不断进行,触发了自动压缩(Compact),这个 ID 甚至会被彻底丢弃。
为了把“软性”的 Attention 提升到 grep 级别的可靠性,Claude Code 在工程上做了两层极强的防御:
-
物理距离拉近(对抗 Lost in the Middle):
既然模型记不住中间的 Token,Claude Code 的compact.ts就强行把所有pending和running状态的任务 ID 提取出来,打包成 Attachment,永远塞在离当前生成位置最近的地方(新一轮输入的末尾),compact.ts#L541-L544。
这相当于系统用确定性的grep(代码里的filter和map)帮模型把“针”从大海里捞了出来,直接摆在模型的眼皮底下。同时在摘要指令里强制要求保留正在运行的任务线索,prompt.ts#L157。 -
强语义锚点(对抗 Token 撕裂):
系统不只是把孤零零的 ID 扔过去,而是用 XML 标签<task-notification>、<tool_use_id>将其紧紧包裹,并附带了<summary>(任务的自然语言描述,如“分析日志”)。
这样,Attention 机制在匹配时,不仅匹配那个被撕裂的随机 ID 序列,还能匹配到具有极强语义的 XML 标签和任务描述。这极大地放大了这个区块在向量空间里的特征,让 Attention 更容易“抓”住它。
2. File Attachment(文件附件)
比如用户用 /add 命令,或者拖拽文件进来,或者系统主动附带的上下文文件。
{
"role": "user",
"content": "File: src/utils/logger.ts\n```typescript\nexport function log(msg) { console.log(msg); }\n```"
}
3. Memory Attachment(记忆规则附件)
系统会把 project_memory.md 里的内容作为隐藏上下文附加进去。
{
"role": "user",
"content": "<memory-attachment>\n<file-path>/Users/xxx/.trae-cn/memory/projects/.../project_memory.md</file-path>\n<content>\n- 始终使用 TypeScript 严格模式\n- 优先使用 React Hooks\n</content>\n</memory-attachment>"
}
这意味着:子任务完成、记忆加载、文件挂载,本质上都会变成这种“伪造的 user 消息”进入同一条时间线。
2.1 上下文如何组装
上下文组装分为两层。
2.1.1 进入循环前的静态准备
进入 query 循环前,会先准备变化较少的内容:
System Prompt:角色、安全边界、基础行为约束。User Context:工作目录、环境状态、会话附加信息。
这一层决定“你是谁、当前在哪个环境里工作”。
2.1.2 循环内的动态注入
每一轮推理都会重新整理上下文,同时推进本轮 tool_use 的执行、异步 task 的创建,以及已完成 task 通知的回流。仅用“调用 LLM -> 执行工具 -> 排干队列”概括这一过程,会遗漏 task 生命周期中的三个关键环节:创建点、并行关系和回流顺序。
更接近真实实现的主流程可分成三段:
- 先执行本轮 assistant 产出的 tool calls。
- 如果其中有
AgentTool的异步分支,就在这一轮里创建 task 并启动后台 agent。 - 等这一轮工具阶段结束后,再统一 drain 队列,把已完成 task 的通知作为 attachment 注入。
主流程可概括为:
func QueryLoop(state State) {
for {
systemPrompt := appendSystemContext(state.SystemPrompt)
compacted := compactHistory(state.Messages)
input := prependUserContext(compacted, state.UserContext)
response := callLLM(systemPrompt, input, state.Tools)
if response.HasToolCalls {
for _, call := range response.ToolCalls {
if call.IsAgentTool && call.ShouldRunAsync {
task := createAgentTask(call)
startTask(task)
state.Messages = append(state.Messages, createToolAck(task))
} else {
result := executeTool(call)
state.Messages = append(state.Messages, result)
}
}
}
queued := getQueuedEventsForCurrentAgent(state.AgentID)
for _, event := range queued {
attachment := createAttachmentMessage(event)
state.Messages = append(state.Messages, attachment)
}
if shouldStop(response) {
break
}
}
}
这里要注意三件事。
第一件事:task 不是循环自己“长出来”的,而是在工具执行阶段创建的
一轮 assistant 响应里,只要出现 AgentTool 的 tool_use,真正的 task 创建就发生在工具执行阶段,而不是发生在后面的 getQueuedEvents()。异步分支下,AgentTool 会先生成 agentId,再调用 registerAsyncAgent(...) 把后台 task 注册进状态,并启动异步生命周期,AgentTool.tsx#L686-L764。
所以从时序上说:
- assistant 先产出
tool_use。 - 主循环进入工具编排阶段。
AgentTool在这里把异步子 Agent 包装成 task。- task 自己在后台继续跑。
后面的队列阶段处理的不是“创建 task”,而是“消费已经完成的 task 通知”。
第二件事:工具编排是简单的贪心分批,而非复杂的 DAG
要理解工具是怎么调用的,首先要明白大模型(LLM)输出的格式:大模型本身并没有显式的“并发”或“串行”标记,它只是按推理顺序,在一轮响应里一口气输出一个包含多个 tool_use 对象的数组(这就是 Parallel Tool Use)。
但这些工具究竟该同时跑还是排队跑?这完全由 Claude Code 本地的 runTools() 函数通过“贪心分组”来决定:
- 什么是并发安全的 Tool? 源码中,凡是实现里写死
isConcurrencySafe() { return true; }的工具(如FileReadTool和AgentTool)都是安全的。凡是没有写或者返回 false 的工具(如FileEditTool这种可能写坏文件的,或BashTool这种可能污染环境的)就是不安全的。 - 贪心切片规则:系统按顺序遍历 LLM 输出的工具数组:
- 如果当前工具是安全的,且上一个也是安全的,就把它们塞进同一个并发 Batch。
- 只要遇到不安全的工具,就立刻打断当前分组,把它单独包成一个串行 Batch。
- 这意味着,串行 Batch 里永远只有一个工具。如果大模型连续输出了三个不安全的工具,系统会把它们切成三个独立的串行 Batch,toolOrchestration.ts#L86-L116。
举个具象化的例子:如果大模型依次输出了 [读文件A, 读文件B, 改文件C, 跑Bash D],系统会切成三个 Batch:
- Batch 1(并发):
[读文件A, 读文件B]-> 使用Promise.all机制同时触发,不需要等前一个返回。 - Batch 2(串行):
[改文件C]-> 必须等 Batch 1 彻底跑完,再单独跑 C。 - Batch 3(串行):
[跑Bash D]-> 必须等 Batch 2 彻底跑完,再单独跑 D。
这并不是一个复杂的有向无环图(DAG)调度,而是一个非常轻量的线性扫描。
这里最容易产生误解的问题是:Tool 和 Task 是一个概念吗?长耗时任务怎么做到不阻塞主循环的?
并不是。 Tool 和 Task 是完全不同的层级。
普通的 Tool(如 FileReadTool 或 FileEditTool)就是一个普通的函数调用,不管它是并发发出去的还是串行发出去的,主循环都会严格 await 它的真实结果。
但 Task 是一个有完整生命周期、有独立输出文件、能通过队列发送通知的后台运算实体,Task.ts#L6-L14。
在 Claude Code 里,只有 AgentTool(以及特定模式下的 BashTool)才会孵化出 Task。
秘密就在这里:AgentTool 源码里写死了 isConcurrencySafe=true。如果模型一轮里输出了三个 AgentTool:
- 它们会被主循环放进并发 batch,用
Promise.all同时调度。 - 调度后,
AgentTool并没有在当前函数里死等子 Agent 跑完,而是用void runAsyncAgentLifecycle(...)把真实的子 Agent 循环抛到了后台,并向主循环瞬间秒回{ status: 'async_launched' },AgentTool.tsx#L686-L764。 - 主循环拿到这三个秒回的
async_launched,认为“工具执行完毕”,于是开开心心地进入了下一轮(响应用户)。 - 而此时,后台其实已经长出了三个并行的 Task,它们跑完后会通过队列把通知回调给主线程。
所以,并发的根源不是主循环有魔法,而是 LLM 决定了一次要调用多个工具 + AgentTool 把死循环推到了后台。
第三件事:task 通知的回流顺序由队列决定,不由创建顺序直接决定
task 跑完后,不会直接修改主线程 messages,而是构造 <task-notification> 消息并调用 enqueuePendingNotification(...) 入统一队列,LocalAgentTask.tsx#L194-L262。
这个队列本身有明确的顺序规则:
- 先按优先级出队:
now > next > later。 - 同一优先级内按 FIFO。
- task notification 默认是
later,刻意排在普通用户输入之后,避免系统通知饿死用户输入,messageQueueManager.ts#L137-L155 、messageQueueManager.ts#L157-L193。
所以一个 task 从创建到被主线程看到,至少经历三层顺序:
- tool_use 顺序:assistant 在本轮里按什么顺序发出调用。
- 完成顺序:task 实际何时跑完。
- 消费顺序:主线程何时按优先级和作用域把通知取出来。
这套顺序保证了多 Agent 协作中的上下文一致性,也把事件注入时机限制在可控的轮次边界上。
3. 主子 Agent 的异步互动与防递归机制
主 Agent 派出一个子任务后,双方不是共享一份可随时改写的 messages,而是通过事件队列衔接。
3.1 异步交互的三个场景
这就回答了开头提出的“长耗时任务”问题:
- 场景 A(主 Agent 还在和用户对话):子任务完成后,通知先进入队列,不会打断当前主 Agent 的推理或工具调用。等主 Agent 这一轮正常结束,运行到“排干队列”阶段时,才会取出通知,并包装成一条
user角色的attachment元消息追加到末尾。这种“轮次边界注入”保证了当前推理的原子性。 - 场景 B(主 Agent 已经空闲/退出):主 Agent 已结束上一轮推理,退出了循环。此时后台的
queueProcessor发现队列里有属于主 Agent 的完成通知,它会把这条通知当作一条新的用户输入,在当前会话的上下文中重新启动下一轮query。这并非强行原地唤醒旧执行栈,而是基于历史上下文的正常续写。 - 场景 C(用户插话冲突):如果用户刚发了一条新消息,同时后台子任务也刚好完成。这两条信息都会作为
attachment加入新一轮的输入尾部。系统通过把通知降级为later优先级,并在提示词中显式约束,确保模型不会因为只盯着子任务结果而漏掉用户的插话。
3.2 Agent ID 与防止无限递归 (How & Why)
系统没有把“禁止无限递归”只交给 Prompt,而是做了严格的运行时硬拦截。
- 稳定身份:
agentId并非顺序分配,而是每次 spawn 时通过crypto.randomBytes(8)早期生成的随机短 ID,uuid.ts#L19-L27。它不仅标识子任务,还被用作隔离资源(如 worktree)和队列通知的作用域标记。 - 状态染色与硬拒绝:当主 Agent 派生(Fork)子 Agent 时,系统会在初始上下文中注入一段强约束文本(告知其是 Fork 角色),forkSubagent.ts#L171-L198。更关键的是,在执行
AgentTool前,系统会检查当前环境是否已有 Fork 标记。如果有,直接抛出Error: Fork is not available inside a forked worker,从入口处掐断递归,AgentTool.tsx#L318-L356。 - 拓扑限制:同进程的
Teammate不能派生后台代理,以维持团队扁平结构并确保生命周期安全,AgentTool.tsx#L266-L280。
为什么这样做:大模型在处理报错时极易产生幻觉,试图不断转包任务。这种物理拦截将原本可能呈 O(B^N) 指数级爆炸的派生链强制锁定为 O(1),确保了算力和进程资源的绝对安全。
3.3 主 Agent 会退出吗
会,这里的“退出”要分清两个层次:
- 退出本轮 query 循环:这是常态,不是异常。
- 退出整个会话进程 / 丢失内存态:这是另一个问题,要靠 transcript 恢复。
先看第一层。Claude Code 的主循环不是一个永不返回的常驻 worker,而是“一次 turn 的执行器”。在 query.ts 里,如果这一轮模型流式输出结束后没有新的 tool_use,needsFollowUp 为假,就会走完成分支并返回,query.ts#L1062-L1182。另外,aborted_streaming、prompt_too_long、image_error、max_turns、aborted_tools 这些路径也都会让本轮提前结束,query.ts#L1051-L1051 、query.ts#L1711-L1711。
所以主 Agent 并不是“一直在后台跑一个 while 不停监听”,而是:
- 用户提交一条输入。
- 启动一轮 query。
- 该轮把该处理的工具调用、附件注入、后续追问处理完。
- 没有 follow-up 时,本轮返回,主线程回到 idle。
什么场景下会看起来像“没退出”
有两类常见情况:
- 本轮里还在持续 follow-up:比如模型还在调用工具,或者工具结果回来后又触发下一轮推理,这时候 query 还没结束。
- 长耗时任务被后台化:例如主会话被用户 background 到
LocalMainSessionTask,原来的前台 UI 会退出当前交互面,但真正的任务在后台 task 里继续跑,完成后再发通知,LocalMainSessionTask.ts#L1-L10 、LocalMainSessionTask.ts#L164-L218。
退出本轮后,消息怎么处理
如果主线程已经 idle,新消息不会去“唤醒旧的 JS 调用栈”,而是走队列和下一次执行:
- 正在执行时,新的 prompt / task notification 会先入队,handlePromptSubmit.ts#L313-L350。
- 主线程空闲后,
queueProcessor会只取属于 main thread 的命令,按 mode 分批交给新的executeInput(),也就是新开一轮处理,而不是恢复旧栈帧,queueProcessor.ts#L52-L87。 - 如果是子 Agent 通知,则在下一轮 query 的 attachment 阶段被 drain 并注入,query.ts#L1547-L1643。
所以更准确地说,Claude Code 的主 Agent 是“会话级常驻,轮次级退出”。
4. 为什么要把事件做成 Attachment
Attachment 不是普通聊天内容,而是“让模型可见、让界面可隐藏”的系统载体。
它有三个作用:
- 结构化承载事件:任务通知、错误信息、远端资源状态都能统一表示。
- 隔离 UI 和模型:模型能读到,终端界面不一定直接展示。
- 支持多轮演进:系统可以在后续轮次继续追加、折叠或移除这类元信息。
如果没有这层抽象,系统事件就只能硬塞进普通对话文本里,后续既难管理,也容易污染用户视图。
5. 会话如何持久化
5.1 不是退出时一次性保存
Claude Code 不是等会话结束后再把整段 messages 全量写盘,而是持续追加到 JSONL 文件。
- 每产生一轮有效消息,就进入待刷盘队列。
- 后台以高频追加写的方式,把新消息写到会话文件末尾。
这样做有两个直接收益:
- 崩溃损失小,因为大部分历史已经落盘。
- 长会话性能更稳定,因为不需要每次重写整个文件。
5.2 恢复会话时怎么做
恢复时不需要构造一套额外的快照协议,直接读取 JSONL 并回放即可:
- 读出历史消息和历史附件。
- 重新解析成
messages。 - 再次送回
QueryEngine。
因为会话里记录的不只是对话,还有系统事件,所以恢复出来的是一条完整的运行时间线。
这里还要区分两种“续上之前的对话”:
场景 A:同一个进程里的下一轮对话
在 SDK / 会话引擎层,QueryEngine 明确是 “one QueryEngine per conversation”,每次 submitMessage() 只是同一会话里的新一轮 turn,messages、文件缓存、usage 等状态会直接沿用,QueryEngine.ts#L176-L183 、QueryEngine.ts#L209-L242。
这时候并不存在“重新拼历史”的成本,因为历史本来就在内存里的 mutableMessages 上继续追加,QueryEngine.ts#L430-L456。
场景 B:进程结束后,再次恢复会话
如果整个进程结束,内存里的 agent 实例当然不会永久常驻。Claude Code 依赖 transcript 把会话重新拼回来:
- 每次新用户消息先落盘,再进入 query,这样即使 API 还没返回也可以 resume,QueryEngine.ts#L436-L463。
recordTranscript()会把新增消息追加到 JSONL,并用parentUuid串起链路,sessionStorage.ts#L1408-L1449。- 恢复时先加载 transcript 文件,找到最新 leaf,再沿着
parentUuid逆向回溯,重建整条 conversation chain,sessionStorage.ts#L2316-L2355 、sessionStorage.ts#L2069-L2206。 - 对于并行 tool use 产生的分叉,恢复逻辑还会做一次补救,把同组 assistant 和 orphaned tool_result 再拼回去,避免 resume 后历史缺块,sessionStorage.ts#L2096-L2206。
所以“每个用户再次发起对话,agent 是否一直保持”这个问题的答案是:
- 同一会话、同一进程内:会话对象和消息状态通常还在,直接开新 turn。
- 进程已结束或显式 resume:不是把旧 agent 原地复活,而是从 transcript 重建出等价的会话状态,再继续后面的 turn。
5.3 长上下文如何控制
为了避免历史无限增长,运行时通常还会配合三层控制:
- 滑动窗口或摘要压缩,保留最近关键轮次。
- 追加元数据,标记哪些历史已经被折叠或摘要化。
- 把长期稳定规则放进系统级上下文,避免被历史裁剪掉。
这三层分工很清楚:
- 对话历史可以压缩。
- 事件日志可以回放。
- 长期规则必须稳定保留。
4. 批判性总结:LLM 时代的系统架构折衷
纵观 Claude Code 的这套运行时设计,它触及了当前 AI Agent 架构的最底层矛盾:大模型的强项在于“模糊的语义理解与生成”,而传统软件工程的基石是“确定性的符号匹配与状态流转”。
当前的妥协(Claude Code 的做法)
Claude Code 选择的路线是:用极度厚重的传统工程代码给大模型做“外骨骼装甲”。
- 用确定性的代码管理状态:TypeScript 写的各种队列、异步 Task、事件注入、摘要保留、文件读写,把所有容易产生幻觉的“脏活累活”全部接管。
- 把状态“喂”到模型嘴边:通过
Attachment和task-notification,系统像保姆一样把大模型需要的状态精准地摆在它眼皮底下,然后只让模型用 Attention 去做它最擅长的“语义缝合”。 - 避免竞态与作用域隔离:子任务完成不直接改写
messages,而是先入队、后排干,保证注入时机可控。每个 Agent 只消费属于自己的事件,结果不会互相串线。
这是当前受限于 LLM 能力瓶颈下,最务实、最可靠的解法。
代价与局限性
这种设计的代价也是极其高昂的:系统的“胶水代码”极其厚重。
我们需要写大量的逻辑去“猜测”模型的注意力会在哪里衰减(比如对抗 “Lost in the Middle”),然后手动去干预上下文的组装;我们要写大量的正则和权限校验去防范模型的幻觉调用;我们要维护复杂的 JSONL 追加流来保证模型的状态不会因为崩溃而丢失。
整个框架有超过一半的代码,是在为大模型的“不可靠”打补丁。
未来的更优解探讨
如果从 Unix 哲学的“做一件事并做好”来看,目前的 LLM 架构是臃肿的。
未来的模型也许会内化这些能力,比如内置一个特殊的 [SEARCH] token:当模型在生成过程中遇到一个陌生的 ID(如 toolu_01A...)时,它不是去算庞大的、不精确的 Attention 矩阵,而是直接在底层触发硬编码的搜索算法(类似真正的 grep 或数据库索引),瞬间拿到匹配结果再继续生成。
只有当模型能原生融合**“冯·诺依曼架构的确定性计算”与“神经网络的泛化能力”**,我们才能摆脱今天这种厚重的胶水代码,迎来终极的 Agent 架构形态。
更多推荐



所有评论(0)