新闻详情

新闻详情

首页 / 资讯中心 / 详情

惊鸿一瞥:从Claude源码拆解顶级 AI Agent 的 TypeScript 系统架构

发布时间:2026/9/27 22:41:19来源:尧图网络
惊鸿一瞥:从Claude源码拆解顶级 AI Agent 的 TypeScript 系统架构
1. 为什么值得花时间拆解 Claude Code 的 TypeScript 架构如果你正在用 TypeScript 写 AI Agent大概率遇到过这几个问题对话轮次一多上下文就爆工具权限给大了怕删库给小了又干不了活多任务并行时子任务之间互相污染上下文。这些不是模型能力问题而是系统架构问题。Claude Code 的源码之所以值得看是因为它用一套非常“工程化”的方式回答了这些问题。整个代码库中真正跟 LLM 调用和 Prompt 相关的逻辑占比极低绝大部分代码都在做确定性的事消息管道、权限网关、工具注册、子进程隔离、记忆分页。换句话说它把 Agent 当成一个操作系统来设计而不是一个“会调 API 的脚本”。这篇文章面向想理解 Agent 系统设计的 TypeScript 开发者。我会带你从目录结构开始逐层拆解它的分层方式然后给出可复制的模块骨架和本地验证步骤。你不需要拿到完整源码跟着骨架就能在自己的项目里复现核心设计思路。如果你在接入过程中需要快速验证模型行为可以用 TaoToken 的模型对话 做对照测试省去自己搭代理层的时间。2. 前置准备用 TaoToken 打通模型调用层在拆架构之前得先让模型能跑起来。Claude Code 的架构再漂亮底层还是要调 LLM。我建议你先用 TaoToken 把 API 通道打通这样后面验证工具调用和消息管道时不会卡在网络层。TaoToken 的接入方式跟标准 OpenAI 兼容接口一致你只需要拿到 API Key然后把 base URL 指向https://taotoken.net/api即可。具体操作第一步打开 TaoToken 控制台注册后进入 API Keys 页面。第二步创建一个新的 Key复制保存。注意 Key 只在创建时显示一次。第三步在你的 TypeScript 项目里配置环境变量# .env TAOTOKEN_API_KEYsk-你的key TAOTOKEN_BASE_URLhttps://taotoken.net/api第四步安装依赖并写一个最小调用测试npm init -y npm install openai dotenv npm install -D typescript ts-node types/node// src/llm/client.ts import OpenAI from openai; import dotenv from dotenv; dotenv.config(); export const llmClient new OpenAI({ apiKey: process.env.TAOTOKEN_API_KEY, baseURL: process.env.TAOTOKEN_BASE_URL, }); export async function pingModel() { const res await llmClient.chat.completions.create({ model: claude-sonnet-4-20250514, messages: [{ role: user, content: 回复 OK 两个字母 }], max_tokens: 10, }); return res.choices[0]?.message?.content; }跑一下npx ts-node src/llm/client.ts如果返回 OK说明通道没问题。这一步很关键因为后面拆解消息管道和工具网关时你需要频繁发请求验证行为。如果你更习惯用命令行交互来观察模型输出也可以直接走 TaoToken 模型对话 页面手动测试。3. 目录结构三层记忆与微内核工具树怎么落地Claude Code 的架构核心可以概括为用文件系统做骨架用工具网关做免疫系统用压缩管道保护上下文预算。下面是我根据其设计思路整理的可复制目录结构你可以直接在自己的项目里建出来。agent-core/ ├── src/ │ ├── memory/ │ │ ├── l1-context.ts # 活动窗口记忆管理当前对话消息 │ │ ├── l2-pointer.ts # 指针记忆读写 memory.md 元数据 │ │ └── l3-external.ts # 外部文件记忆封装 FileRead/FileWrite │ ├── pipeline/ │ │ ├── budget-reducer.ts # 第一阶预算缩减 │ │ ├── snipper.ts # 第二阶片段裁剪 │ │ ├── microcompactor.ts # 第三阶微压缩 │ │ ├── context-collapse.ts # 第四阶上下文折叠 │ │ └── auto-compact.ts # 第五阶自动压缩 │ ├── tools/ │ │ ├── registry.ts # 工具注册中心 │ │ ├── base-tool.ts # 工具基类含 Schema 校验 │ │ ├── permission-gate.ts # 权限网关 │ │ ├── bash-tool.ts │ │ ├── file-read-tool.ts │ │ ├── file-write-tool.ts │ │ └── agent-tool.ts # 子智能体拉起工具 │ ├── orchestrator/ │ │ ├── coordinator.ts # 主控节点 │ │ ├── subagent-fork.ts # Fork 模式子节点 │ │ ├── subagent-teammate.ts # Teammate 模式子节点 │ │ └── subagent-worktree.ts # Worktree 模式子节点 │ ├── llm/ │ │ └── client.ts # 模型调用封装 │ └── index.ts ├── memory.md # L2 指针文件 ├── docs/ │ ├── decisions.md │ └── frontend-rules.md ├── .env ├── package.json └── tsconfig.json这个结构的关键在于分层隔离。memory/只管记忆的读写和寻址pipeline/只管消息在发给模型前的整形tools/只管工具的注册、校验和权限orchestrator/只管多智能体的调度。每一层都可以独立测试不会互相耦合。L2 指针记忆是整个设计的枢纽。memory.md里不存长文只存结构化指针# Project Memory Index ## Architecture Decisions - path: docs/decisions.md - summary: 记录所有架构决策及其上下文 ## Frontend Rules - path: docs/frontend-rules.md - summary: 前端路由与状态管理规范 ## Active Tasks - path: docs/tasks/current.md - summary: 当前迭代任务清单当 Agent 需要了解项目全局时只加载这个文件Token 消耗极小。遇到具体问题时再通过FileRead工具触发一次“缺页中断”把目标文件换入 L1 上下文。这就是它节省 Token 的核心机制。4. 核心模块配置骨架消息管道与权限网关4.1 五阶压缩管道的 TypeScript 骨架消息管道是 Claude Code 最值得抄的部分。它在把消息发给 LLM 之前会依次执行五个阶段的压缩按计算成本从低到高递进。下面是可运行的骨架// src/pipeline/types.ts export interface Message { role: user | assistant | system | tool; content: string; timestamp: number; toolName?: string; resolved?: boolean; } export interface PipelineStage { name: string; process(messages: Message[], budget: number): Message[]; }// src/pipeline/budget-reducer.ts import { Message, PipelineStage } from ./types; export class BudgetReducer implements PipelineStage { name budget-reduction; process(messages: Message[], budget: number): Message[] { const totalChars messages.reduce((sum, m) sum m.content.length, 0); if (totalChars budget) return messages; // 从最旧的非 system 消息开始截断 const result [...messages]; while ( result.reduce((sum, m) sum m.content.length, 0) budget result.length 2 ) { const idx result.findIndex((m) m.role ! system); if (idx -1) break; result.splice(idx, 1); } return result; } }// src/pipeline/snipper.ts import { Message, PipelineStage } from ./types; export class Snipper implements PipelineStage { name snip; private maxToolOutput 2000; process(messages: Message[]): Message[] { return messages.map((m) { if (m.role ! tool || m.content.length this.maxToolOutput) return m; const half Math.floor(this.maxToolOutput / 2); return { ...m, content: m.content.slice(0, half) \n...[已裁剪]...\n m.content.slice(-half), }; }); } }// src/pipeline/microcompactor.ts import { Message, PipelineStage } from ./types; export class MicroCompactor implements PipelineStage { name microcompact; process(messages: Message[]): Message[] { const now Date.now(); const TTL 5 * 60 * 1000; // 5 分钟 return messages.filter((m) { if (m.role ! tool) return true; if (m.resolved now - m.timestamp TTL) return false; return true; }); } }// src/pipeline/context-collapse.ts import { Message, PipelineStage } from ./types; import { llmClient } from ../llm/client; export class ContextCollapse implements PipelineStage { name context-collapse; private threshold 20; async process(messages: Message[]): PromiseMessage[] { if (messages.length this.threshold) return messages; const toSummarize messages.slice(0, messages.length - 6); const recent messages.slice(-6); const summaryRes await llmClient.chat.completions.create({ model: claude-haiku-4-20250514, messages: [ { role: user, content: 将以下对话历史压缩为一段不超过 300 字的摘要保留关键决策和未完成任务\n toSummarize.map((m) ${m.role}: ${m.content}).join(\n), }, ], max_tokens: 500, }); const summary: Message { role: system, content: [历史摘要] ${summaryRes.choices[0]?.message?.content}, timestamp: Date.now(), }; return [summary, ...recent]; } }// src/pipeline/index.ts import { Message, PipelineStage } from ./types; import { BudgetReducer } from ./budget-reducer; import { Snipper } from ./snipper; import { MicroCompactor } from ./microcompactor; import { ContextCollapse } from ./context-collapse; export class MessagePipeline { private stages: PipelineStage[] [ new BudgetReducer(), new Snipper(), new MicroCompactor(), new ContextCollapse(), ]; async run(messages: Message[], budget 80000): PromiseMessage[] { let result messages; for (const stage of this.stages) { result await stage.process(result, budget); console.log([pipeline] ${stage.name} - ${result.length} messages); } return result; } }这套管道的设计哲学是主动管理模型应该忘记什么比塞给它记住什么更重要。每一阶都在做减法而不是加法。4.2 拒绝优先的权限网关工具权限是 Agent 安全的核心。Claude Code 采用“拒绝优先”策略默认拒绝只有明确授权的操作才放行。下面是权限网关的骨架// src/tools/permission-gate.ts export type PermissionLevel read-only | plan | auto | yolo; export interface ToolRequest { toolName: string; args: Recordstring, unknown; level: PermissionLevel; } const DANGEROUS_PATTERNS [ /rm\s-rf/, /npm\spublish/, /git\spush\s--force/, /\s*\/dev\/sd/, ]; export class PermissionGate { private level: PermissionLevel; constructor(level: PermissionLevel plan) { this.level level; } check(req: ToolRequest): { allowed: boolean; reason?: string } { if (this.level read-only this.isWriteOperation(req.toolName)) { return { allowed: false, reason: 只读模式不允许写操作 }; } if (this.level plan this.isDestructive(req)) { return { allowed: false, reason: 计划模式拦截高危操作 }; } if (this.level auto this.isDestructive(req)) { return { allowed: false, reason: 需要人工确认 }; } return { allowed: true }; } private isWriteOperation(toolName: string): boolean { return [FileWrite, Bash, AgentTool].includes(toolName); } private isDestructive(req: ToolRequest): boolean { const cmd String(req.args.command ?? ); return DANGEROUS_PATTERNS.some((p) p.test(cmd)); } }配合工具注册中心使用// src/tools/registry.ts import { PermissionGate, ToolRequest } from ./permission-gate; export interface ToolDefinition { name: string; description: string; schema: Recordstring, unknown; execute: (args: Recordstring, unknown) Promisestring; } export class ToolRegistry { private tools new Mapstring, ToolDefinition(); private gate: PermissionGate; constructor(gate: PermissionGate) { this.gate gate; } register(tool: ToolDefinition) { this.tools.set(tool.name, tool); } async invoke(req: ToolRequest): Promisestring { const tool this.tools.get(req.toolName); if (!tool) throw new Error(未知工具: ${req.toolName}); const verdict this.gate.check(req); if (!verdict.allowed) { return [权限拦截] ${verdict.reason}; } return tool.execute(req.args); } list(): string[] { return Array.from(this.tools.keys()); } }这套设计的精髓在于每个工具自带独立的 Schema 校验和权限检查而不是在全局做统一判断。这样新增工具时不会影响已有工具的权限逻辑。5. 本地验证跑通一次带工具调用的完整请求骨架搭好后需要验证它真的能跑。下面是一个完整的验证脚本模拟 Agent 收到用户请求后经过消息管道整形、工具调用、权限检查、结果回流的全过程。// src/index.ts import { llmClient } from ./llm/client; import { MessagePipeline } from ./pipeline; import { ToolRegistry } from ./tools/registry; import { PermissionGate } from ./tools/permission-gate; import { Message } from ./pipeline/types; async function main() { const gate new PermissionGate(plan); const registry new ToolRegistry(gate); registry.register({ name: FileRead, description: 读取指定路径的文件内容, schema: { path: string }, execute: async (args) { const fs await import(fs/promises); return fs.readFile(String(args.path), utf-8); }, }); registry.register({ name: Bash, description: 执行 shell 命令, schema: { command: string }, execute: async (args) { const { exec } await import(child_process); return new Promise((resolve) { exec(String(args.command), (err, stdout) { resolve(err ? 错误: ${err.message} : stdout); }); }); }, }); const pipeline new MessagePipeline(); const history: Message[] [ { role: system, content: 你是一个代码助手。, timestamp: Date.now() }, { role: user, content: 读取 memory.md 并告诉我项目有哪些模块, timestamp: Date.now() }, ]; const shaped await pipeline.run(history, 4000); console.log(管道输出消息数:, shaped.length); const res await llmClient.chat.completions.create({ model: claude-sonnet-4-20250514, messages: shaped.map((m) ({ role: m.role as any, content: m.content })), tools: [ { type: function, function: { name: FileRead, description: 读取指定路径的文件内容, parameters: { type: object, properties: { path: { type: string } }, required: [path], }, }, }, ], max_tokens: 500, }); const choice res.choices[0]?.message; console.log(模型回复:, choice?.content); if (choice?.tool_calls?.length) { for (const call of choice.tool_calls) { const args JSON.parse(call.function.arguments); console.log(调用工具: ${call.function.name}, args); const result await registry.invoke({ toolName: call.function.name, args, level: plan, }); console.log(工具返回:, result.slice(0, 200)); } } } main().catch(console.error);运行npx ts-node src/index.ts你应该能看到管道逐阶处理消息的日志以及模型发起FileRead工具调用的过程。如果模型没有发起工具调用可以调整 system prompt明确告诉它“需要读取文件时请调用 FileRead 工具”。验证成功的标志有三个管道日志显示消息数逐阶减少模型返回了tool_calls工具执行结果被正确打印。如果卡在某一步往下看排障部分。6. 本篇常见错排查问题一模型不发起工具调用只返回文本。最常见的原因是 tools 参数格式不对或者 system prompt 没有引导。检查tools数组里每个元素的type是否为functionfunction.parameters是否为合法的 JSON Schema。另外部分模型对工具描述很敏感description要写清楚“什么时候该用这个工具”。问题二管道跑完后消息为空或只剩 system。大概率是BudgetReducer的 budget 设得太小把所有消息都截断了。先打印每阶处理后的消息数定位是哪一阶把消息删光了。如果是MicroCompactor误删检查resolved字段是否被错误标记为 true。问题三权限网关拦截了正常操作。PermissionGate的isDestructive用的是正则匹配如果你的命令里包含rm -rf但实际是安全路径也会被拦截。生产环境建议把正则匹配换成更精细的 AST 解析或者引入一个轻量分类器做意图打分。调试阶段可以先把 level 设为yolo确认逻辑通不通再逐级收紧。问题四ContextCollapse 调用小模型超时。摘要阶段用的是 Haiku 这类小模型如果网络不稳定会拖慢整个管道。建议给这一步加超时和降级超时后直接跳过折叠保留原始消息。代码里可以用Promise.race包一层。问题五TaoToken 返回 401 或 404。401 通常是 Key 没配对检查.env里的TAOTOKEN_API_KEY是否有多余空格。404 一般是 base URL 写错了确认是https://taotoken.net/api而不是带其他路径。如果反复失败去 TaoToken API Keys 页面 重新生成一个 Key 试试。接入细节可以参考 TaoToken 接入文档。问题六子智能体 Fork 模式上下文污染主控。Fork 模式下子节点继承父节点上下文如果子节点把中间推理过程写回了共享的 L1主控就会被污染。解决办法是子节点只返回最终摘要中间过程写到独立的临时文件里主控只读摘要不读过程。7. 从架构到落地下一步怎么走拆完这套架构你会发现 Claude Code 的核心竞争力不在 Prompt而在那套确定性的系统工程。三层记忆解决 Token 预算五阶管道解决上下文质量权限网关解决安全边界多智能体编排解决任务隔离。这四件事做好了模型本身的智力才能被稳定地释放出来。如果你想继续深入建议按这个顺序推进先把消息管道跑通并加上日志观察每一阶对消息的实际影响然后接入真实的文件读写工具用权限网关控制风险最后再尝试 Fork 和 Worktree 两种子智能体模式对比它们在长任务下的表现差异。如果你打算把这套架构用到长期编码或 Agent 项目里可以考虑用 TaoToken Coding Plan 来管理模型调用配额避免在频繁调试管道时被限流打断。架构搭好了通道稳了剩下的就是不断迭代你的工具集和压缩策略。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

网站被黑挂马怎么办?一文搞懂wordpresshtml标签安全 2026/9/27 23:32:57

网站被黑挂马怎么办?一文搞懂wordpresshtml标签安全

网站被黑挂马怎么办?一文搞懂wordpresshtml标签安全 昨晚三点,手机突然弹出一条短信:您的域名已被监管局标记为高危。我抓起电脑一看,后台一片惨白,首页变成了博彩广告,源码里多了一堆看不懂的 <script>…

阅读更多 →
【Unity UI 进阶】仿 Element UI 打造企业级 Unity UI 组件库(10) 2026/9/27 23:32:57

【Unity UI 进阶】仿 Element UI 打造企业级 Unity UI 组件库(10)

【Unity UI 进阶】仿 Element UI 打造企业级 Unity UI 组件库&#xff08;10&#xff09; 环境与工具说明项说明代码生成本系列组件库代码由 Cursor&#xff08;AI 编程助手&#xff09;辅助生成与迭代&#xff0c;再结合工程内联调、重构落地Unity 版本2022.3.50f1c1&#xff…

阅读更多 →
减少AI视频抽卡的7种3D预演技术,从人物走位到镜头调度一次讲透 2026/9/27 23:32:57

减少AI视频抽卡的7种3D预演技术,从人物走位到镜头调度一次讲透

大家好&#xff0c;我是抖知书&#xff01; AI视频生成最让人崩溃的场景&#xff0c;不是模型能力不够&#xff0c;而是提示词写了一大段&#xff0c;生成结果和脑子里想的完全不是一回事。 人物从左边出来&#xff0c;模型让他从右边进来&#xff1b;镜头想拍侧面特写&#xf…

阅读更多 →
WebAssembly 与 ESP32:为什么一个 .wasm 文件不等于完整应用 2026/9/27 23:32:50

WebAssembly 与 ESP32:为什么一个 .wasm 文件不等于完整应用

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
量化策略使用不复权数据会出现什么问题?从回测收益失真看价格口径 2026/9/27 23:32:50

量化策略使用不复权数据会出现什么问题?从回测收益失真看价格口径

一句话结论&#xff1a;量化策略直接使用不复权价格并不一定错误&#xff0c;但如果策略需要比较跨除权事件前后的价格、计算历史收益率或技术指标&#xff0c;却没有明确处理复权口径&#xff0c;就可能让回测结果与策略实际想表达的价格变化产生偏差。摘要 在股票量化回测中&…

阅读更多 →
Vim配置SystemVerilog高亮:从语法识别到语义着色 2026/9/27 23:32:50

Vim配置SystemVerilog高亮:从语法识别到语义着色

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉