行业资讯

从零构建多智能体协作框架:TypeScript实现AI驱动的软件开发范式

发布时间:2026/8/14 4:24:04
从零构建多智能体协作框架:TypeScript实现AI驱动的软件开发范式 1. 从“单兵作战”到“团队协作”为什么我们需要多智能体架构最近在折腾AI应用开发的朋友估计没少被“Agent”这个词刷屏。从AutoGPT到CrewAI再到各种开源框架仿佛一夜之间AI应用的核心就从调用一个API变成了指挥一群“智能体”协同工作。我自己在尝试用Claude Code这类工具时也常常在想它处理一个文件、修复一个bug确实很溜但如果我想让它帮我规划一个完整的项目模块比如“设计一个用户登录系统包含前端表单、后端API和数据库模型”它给出的答案往往是一个庞大的、线性的代码块缺乏清晰的模块划分和协作逻辑。这其实就是“单智能体”的局限性。它像一个全能的超级程序员但再全能一次也只能聚焦于一个任务流。而真实的软件开发尤其是稍具规模的项目本质是并行的、模块化的、需要分工与协作的。前端工程师、后端工程师、架构师、测试工程师各司其职通过清晰的接口和协议进行沟通。多智能体架构Multi-Agent Architecture要模拟的正是这种现实世界的协作范式。那么Claude Code在这个范式里扮演什么角色你可以把它看作一个能力极强的“基础智能体单元”。它精通代码生成、解释、重构和调试。但如果我们想构建一个更复杂的系统比如一个能自动根据产品需求文档分解出前端页面、后端服务、部署脚本并让不同的“Claude Code实例”分别负责最后再组装测试的系统该怎么办这就需要一套框架来管理这些智能体分配任务、定义它们之间的沟通方式比如通过消息队列或共享状态、处理它们执行中的依赖和冲突。这就是我动手从零用TypeScript实现一个多Agent协作框架的初衷。我不想仅仅停留在使用现成的框架而是想彻底搞明白一个多智能体系统的“骨架”到底由哪些核心部件构成智能体之间如何高效、可靠地“对话”任务如何被分解和调度TypeScript的强类型系统恰好能为这个充满动态交互的系统提供一层坚实的“契约”保障让智能体的输入、输出、消息格式都在编译期就尽可能明确减少运行时“扯皮”的可能。接下来我就把自己搭建这个框架的核心思路、关键设计以及踩过的坑毫无保留地分享出来。2. 核心蓝图定义我们的多智能体协作框架在开始写第一行代码之前我们必须想清楚这个框架要解决的根本问题以及它的核心组成部分。经过反复推敲我将其抽象为以下几个核心概念这构成了我们框架的“宪法”。2.1 核心概念定义Agent智能体框架中的基本执行单元。每个Agent都是一个独立的、具备特定能力的函数或对象。在我们的设计中一个Agent至少需要name: 唯一标识如 “FrontendDeveloper”。role: 角色描述定义了它的职责边界如 “负责将UI设计稿转化为React组件”。execute(task: Task, context: Context): PromiseResult: 核心执行方法接收任务和上下文返回执行结果。关键设计点Agent应该是无状态的吗实践中我倾向于让Agent是无状态的。它的所有“记忆”和“知识”都来自于传入的context上下文和task任务描述。这大大简化了Agent的管理、复用和水平扩展。状态由外部的Context对象管理。Task任务描述需要完成的工作单元。它不应该是一个简单的字符串而是一个结构化的对象。interface Task { id: string; // 唯一ID用于追踪 description: string; // 自然语言描述如“创建用户登录API” expectedOutput?: string; // 期望输出的格式或示例如“一个Express.js路由处理器” dependencies?: string[]; // 依赖的其他Task ID用于编排执行顺序 assignedAgent?: string; // 被分配执行的Agent名称 }Context上下文这是整个系统的“共享记忆体”或“工作区”。所有Agent都可以从中读取信息并将自己的产出写入其中。它通常是一个键值存储但值可以是任何结构化的数据代码片段、设计决策、API文档等。interface Context { set(key: string, value: any): void; getT(key: string): T | undefined; // 可能还包括历史消息、项目结构等 }为什么需要Context如果没有它Agent A生成的API接口定义如何传递给Agent B去实现具体逻辑难道靠A在结果字符串里说“请B去看某某文件”Context提供了一个中心化的、结构化的信息交换场所。Message消息Broker消息代理Agent之间如何通信直接函数调用会形成紧耦合。更优雅的方式是采用消息驱动模型。每个Agent可以向一个Broker消息代理类似一个内部的事件总线或消息队列发布publish消息或订阅subscribe特定主题topic的消息。Agent A完成数据库模型设计后发布一条消息到topic: ‘database/schema/ready’内容包含模型定义。Agent B负责生成CRUD API的Agent订阅了该主题收到消息后触发其执行逻辑。 这种松耦合的设计让系统更容易扩展新的Agent可以随时加入只需订阅它关心的主题即可。Orchestrator编排器这是框架的“大脑”或“项目经理”。它负责解析顶层目标如“构建一个博客系统”将其分解Decompose成一系列有依赖关系的Task然后根据Agent的role和能力将任务分配给最合适的AgentAssign并监控整个执行流程处理错误和重试。一个简单的Orchestrator工作流可能是线性的而复杂的可以实现循环、条件分支等。2.2 框架的顶层架构图逻辑层面用文字描述就是用户向Orchestrator提交一个目标Goal。Orchestrator使用某种策略可以是规则也可以调用一个LLM将目标分解为任务图Task Graph。Orchestrator遍历任务图将就绪的依赖已满足的Task通过Broker分配给注册的Agent。Agent从Context中获取所需输入执行Task将结果写回Context并通过Broker发布任务完成的消息。Orchestrator监听这些完成消息更新任务状态并触发下一个就绪的Task。所有任务完成后Orchestrator从Context中整合最终结果返回给用户。这个蓝图不依赖任何具体的AI模型它首先是一个任务编排与通信框架。Claude Code、GPT或其他模型是作为具体Agent的“大脑”被集成进来的。例如一个CodeReviewAgent的内部会封装对Claude Code API的调用并将API返回的代码建议格式化为框架定义的Result结构。3. 用TypeScript搭建骨架定义接口与核心类有了蓝图我们就可以开始用TypeScript“浇筑”地基了。强类型是我们的核心优势它能极大提升框架的可靠性和开发体验。3.1 定义核心接口Interfaces首先在src/core/interfaces.ts中定义所有核心契约。// 任务状态枚举 export enum TaskStatus { PENDING PENDING, ASSIGNED ASSIGNED, IN_PROGRESS IN_PROGRESS, COMPLETED COMPLETED, FAILED FAILED } // 任务接口 export interface ITask { id: string; description: string; expectedOutput?: string; dependencies: string[]; // 依赖的任务ID status: TaskStatus; assignedAgent?: string; result?: any; // 存储任务执行结果 } // 智能体接口 export interface IAgent { name: string; role: string; description: string; // 执行任务上下文和消息总线作为参数传入 execute(task: ITask, context: IContext, broker: IMessageBroker): PromiseIAgentResult; } // 智能体执行结果 export interface IAgentResult { success: boolean; output: any; // 执行产出可以是字符串、对象等 error?: string; nextTasks?: ITask[]; // 动态生成子任务可选 } // 上下文接口 export interface IContext { getT(key: string): T | undefined; set(key: string, value: any): void; has(key: string): boolean; // 可以扩展更多方法如合并、快照等 } // 消息接口 export interface IMessage { topic: string; payload: any; sender: string; // 发送者Agent名称 timestamp: number; } // 消息代理接口 export interface IMessageBroker { publish(message: IMessage): void; subscribe(topic: string, callback: (message: IMessage) void): void; unsubscribe(topic: string, callback: (message: IMessage) void): void; } // 编排器接口 export interface IOrchestrator { registerAgent(agent: IAgent): void; submitGoal(goal: string): Promiseany; }3.2 实现基础类Base Classes接着我们实现一些基础的、通用的类。在src/core/context.ts中实现一个简单的内存上下文import { IContext } from ./interfaces; export class InMemoryContext implements IContext { private store: Mapstring, any new Map(); getT(key: string): T | undefined { return this.store.get(key) as T; } set(key: string, value: any): void { this.store.set(key, value); } has(key: string): boolean { return this.store.has(key); } // 提供一个快照方便调试 snapshot(): Recordstring, any { return Object.fromEntries(this.store.entries()); } }在src/core/broker.ts中实现一个简单的事件发布订阅模式的消息代理import { IMessageBroker, IMessage } from ./interfaces; type SubscriptionCallback (message: IMessage) void; export class SimpleMessageBroker implements IMessageBroker { private subscriptions: Mapstring, SubscriptionCallback[] new Map(); publish(message: IMessage): void { const callbacks this.subscriptions.get(message.topic) || []; // 异步执行回调避免阻塞发布者 setImmediate(() { callbacks.forEach(callback callback(message)); }); } subscribe(topic: string, callback: SubscriptionCallback): void { if (!this.subscriptions.has(topic)) { this.subscriptions.set(topic, []); } this.subscriptions.get(topic)!.push(callback); } unsubscribe(topic: string, callback: SubscriptionCallback): void { const callbacks this.subscriptions.get(topic); if (!callbacks) return; const index callbacks.indexOf(callback); if (index -1) { callbacks.splice(index, 1); } } }3.3 实现一个简单的编排器这是框架的核心。我们先实现一个基础版本它只负责按顺序执行没有依赖的任务。在src/core/orchestrator.ts中import { IOrchestrator, IAgent, ITask, TaskStatus, IContext, IMessageBroker, IMessage } from ./interfaces; export class SimpleOrchestrator implements IOrchestrator { private agents: Mapstring, IAgent new Map(); private taskQueue: ITask[] []; private context: IContext; private broker: IMessageBroker; constructor(context: IContext, broker: IMessageBroker) { this.context context; this.broker broker; // 监听任务完成消息 this.broker.subscribe(task.completed, this.handleTaskCompleted.bind(this)); this.broker.subscribe(task.failed, this.handleTaskFailed.bind(this)); } registerAgent(agent: IAgent): void { this.agents.set(agent.name, agent); console.log(Agent registered: ${agent.name} (${agent.role})); } async submitGoal(goal: string): Promiseany { console.log(Processing goal: ${goal}); // 第一步目标分解这里简化直接创建预设任务 // 在实际项目中这里可以集成LLM如Claude Code进行智能分解 const tasks: ITask[] this.decomposeGoal(goal); this.taskQueue.push(...tasks); // 第二步执行就绪任务无依赖的任务 await this.executeReadyTasks(); // 注意这个简单版本不会等待所有任务更复杂的需要Promise链或循环 return this.context.get(final_result); } private decomposeGoal(goal: string): ITask[] { // 这是一个硬编码的示例。高级实现应调用LLM。 if (goal.includes(登录)) { return [ { id: 1, description: 设计用户数据库表结构, dependencies: [], status: TaskStatus.PENDING }, { id: 2, description: 实现用户注册后端API, dependencies: [1], status: TaskStatus.PENDING }, { id: 3, description: 实现用户登录后端API, dependencies: [1], status: TaskStatus.PENDING }, { id: 4, description: 创建前端登录页面组件, dependencies: [], status: TaskStatus.PENDING }, ]; } return []; } private async executeReadyTasks(): Promisevoid { const readyTasks this.taskQueue.filter(task task.status TaskStatus.PENDING task.dependencies.every(depId this.taskQueue.find(t t.id depId)?.status TaskStatus.COMPLETED ) ); for (const task of readyTasks) { // 任务分配这里简化按角色匹配第一个Agent // 高级实现可以有更复杂的路由逻辑 const agent Array.from(this.agents.values()).find(a a.role.toLowerCase().includes(developer)); if (!agent) { console.error(No agent found for task: ${task.description}); task.status TaskStatus.FAILED; continue; } task.assignedAgent agent.name; task.status TaskStatus.IN_PROGRESS; console.log(Assigning task ${task.description} to agent ${agent.name}); try { const result await agent.execute(task, this.context, this.broker); task.status TaskStatus.COMPLETED; task.result result.output; // 发布任务完成消息 this.broker.publish({ topic: task.completed, payload: { taskId: task.id, result: result.output }, sender: orchestrator, timestamp: Date.now() }); } catch (error) { console.error(Task ${task.id} failed:, error); task.status TaskStatus.FAILED; this.broker.publish({ topic: task.failed, payload: { taskId: task.id, error: String(error) }, sender: orchestrator, timestamp: Date.now() }); } } } private handleTaskCompleted(message: IMessage): void { console.log(Task completed: ${message.payload.taskId}); // 检查是否有新的任务就绪可以继续执行 this.executeReadyTasks(); } private handleTaskFailed(message: IMessage): void { console.error(Task failed: ${message.payload.taskId}, message.payload.error); // 可以实现重试逻辑或错误处理策略 } }这个SimpleOrchestrator虽然简陋但它已经具备了多智能体框架最核心的循环分解 - 分配 - 执行 - 通知。你可以看到Agent的execute方法被异步调用并且通过Broker发布消息来驱动流程。4. 创建第一个“真实”的Agent集成Claude Code框架的骨架有了现在需要注入“灵魂”——真正的AI能力。我们来创建一个ClaudeCodeAgent它将封装对Claude Code API或类似大模型代码生成接口的调用。4.1 设计Agent与LLM的交互模式一个关键决策是Agent内部如何处理与LLM的交互直接在每个execute方法里写HTTP调用吗这会导致代码重复且难以维护。更好的模式是引入一个LLMService抽象层。首先定义LLM服务接口src/llm/llm-service.tsexport interface LLMResponse { content: string; usage?: { prompt_tokens: number; completion_tokens: number }; } export interface LLMOptions { model?: string; temperature?: number; maxTokens?: number; } export interface ILLMService { generateCode(prompt: string, options?: LLMOptions): PromiseLLMResponse; chat(messages: Array{role: string; content: string}, options?: LLMOptions): PromiseLLMResponse; // 其他可能的方法如分析、总结等 }然后实现一个基于 Claude Code API 的服务src/llm/claude-code-service.ts。这里假设我们有一个模拟的客户端。import { ILLMService, LLMResponse, LLMOptions } from ./llm-service; export class ClaudeCodeService implements ILLMService { private apiKey: string; private baseURL: string; constructor(apiKey: string, baseURL: string https://api.anthropic.com) { this.apiKey apiKey; this.baseURL baseURL; } async generateCode(prompt: string, options: LLMOptions {}): PromiseLLMResponse { // 这里是一个简化的模拟实现。真实情况需要调用实际的API。 console.log([ClaudeCodeService] Generating code for prompt: ${prompt.substring(0, 100)}...); // 模拟API调用延迟 await new Promise(resolve setTimeout(resolve, 500)); // 模拟返回一段代码 const mockCode // Generated by Claude Code Agent\n// Task: ${prompt}\nconsole.log(Hello, this is mock code for: ${prompt});; return { content: mockCode, usage: { prompt_tokens: prompt.length, completion_tokens: mockCode.length } }; } async chat(messages: Array{role: string; content: string}, options?: LLMOptions): PromiseLLMResponse { // 类似实现... return { content: Mock chat response }; } }4.2 实现ClaudeCodeAgent现在我们可以创建具体的Agent了。src/agents/claude-code-agent.tsimport { IAgent, ITask, IContext, IMessageBroker, IAgentResult } from ../core/interfaces; import { ILLMService } from ../llm/llm-service; export class ClaudeCodeAgent implements IAgent { name: string; role: string; description: string; private llmService: ILLMService; constructor(name: string, role: string, description: string, llmService: ILLMService) { this.name name; this.role role; this.description description; this.llmService llmService; } async execute(task: ITask, context: IContext, broker: IMessageBroker): PromiseIAgentResult { console.log([${this.name}] Starting task: ${task.description}); // 1. 从上下文中收集相关信息 const projectContext context.getstring(project.overview) || ; const relevantCode context.getstring(relevant.code) || ; // 2. 构建给LLM的提示词Prompt Engineering是关键 const prompt this.buildPrompt(task, projectContext, relevantCode); // 3. 调用LLM服务 let llmResponse; try { llmResponse await this.llmService.generateCode(prompt, { model: claude-code, temperature: 0.2, // 低温度代码生成需要确定性 maxTokens: 2000 }); } catch (error) { return { success: false, output: null, error: LLM调用失败: ${error} }; } const generatedCode llmResponse.content; // 4. 后处理可能包括代码格式化、语法检查等 const finalOutput this.postProcessCode(generatedCode, task); // 5. 将结果写回上下文键名可以按约定如 task.id.result const resultKey task.${task.id}.result; context.set(resultKey, finalOutput); // 6. 如果此任务生成了新的信息如API接口定义可以发布消息通知其他Agent if (task.id 1) { // 假设任务1是设计数据库 broker.publish({ topic: database.schema.ready, payload: { schema: finalOutput }, sender: this.name, timestamp: Date.now() }); } console.log([${this.name}] Task completed: ${task.description}); return { success: true, output: finalOutput }; } private buildPrompt(task: ITask, projectContext: string, relevantCode: string): string { // 这是一个非常基础的提示词模板。实际应用中需要精心设计。 return 你是一个专业的${this.role}。 项目背景 ${projectContext} 相关现有代码 ${relevantCode} 你的任务 ${task.description} 期望输出格式 ${task.expectedOutput || 请生成完整、可运行的代码片段。} 请开始你的工作 ; } private postProcessCode(code: string, task: ITask): string { // 这里可以集成Prettier、ESLint等进行代码格式化 // 或者简单的字符串清理 return code.trim(); } }4.3 组装并运行一个简单示例最后我们在src/index.ts或一个示例文件中把一切组装起来import { InMemoryContext } from ./core/context; import { SimpleMessageBroker } from ./core/broker; import { SimpleOrchestrator } from ./core/orchestrator; import { ClaudeCodeAgent } from ./agents/claude-code-agent; import { ClaudeCodeService } from ./llm/claude-code-service; async function main() { // 1. 初始化核心组件 const context new InMemoryContext(); const broker new SimpleMessageBroker(); const orchestrator new SimpleOrchestrator(context, broker); // 2. 初始化LLM服务使用模拟服务实际需填入真实API Key const llmService new ClaudeCodeService(your-mock-api-key); // 3. 创建并注册多个具有不同角色的Agent const backendAgent new ClaudeCodeAgent( BackendDev, 后端开发工程师, 负责设计和实现服务器端API、数据库模型等。, llmService ); const frontendAgent new ClaudeCodeAgent( FrontendDev, 前端开发工程师, 负责实现用户界面和交互逻辑。, llmService ); orchestrator.registerAgent(backendAgent); orchestrator.registerAgent(frontendAgent); // 4. 设置初始项目上下文 context.set(project.overview, 构建一个简单的用户登录系统使用Node.jsExpress后端和React前端。); // 5. 提交目标启动协作流程 const finalResult await orchestrator.submitGoal(实现用户登录系统); console.log(流程执行完毕。上下文最终状态, context.snapshot()); } main().catch(console.error);运行这个示例你会在控制台看到各个Agent被分配任务、执行、发布消息的日志。虽然现在用的是模拟的LLM响应但整个多智能体协作的流程已经完整地跑通了。5. 从玩具到工具关键进阶设计与踩坑实录上面的基础框架能跑但离一个健壮、可用的工具还有很大距离。在实际深化开发中我遇到了几个核心挑战并摸索出一些解决方案。5.1 任务依赖与动态工作流SimpleOrchestrator的任务分解是硬编码的这显然不实用。一个真正的多智能体系统应该能动态生成和调整任务。这里有两个思路专用“规划Agent”第一个Agent不是执行具体任务而是接收目标利用LLM强大的分析和规划能力生成一个结构化的任务列表甚至是一个有向无环图。这个任务列表再交给Orchestrator去执行。这个“规划Agent”的输出格式需要严格定义比如JSON Schema以便解析。Agent动态提议在每个Agent的execute方法返回的IAgentResult中包含一个可选的nextTasks?: ITask[]字段。当一个Agent完成它的工作后它可以根据当前结果提议接下来需要做什么。例如“数据库设计Agent”完成后可以提议“现在需要创建用户模型对应的CRUD API”和“需要创建用户身份验证中间件”两个新任务。Orchestrator负责评估和采纳这些提议并将其加入执行队列。5.2 上下文管理避免信息过载与冲突随着任务进行Context会变得非常庞大。所有Agent都读写同一个全局上下文容易导致信息混乱和冲突。命名空间Namespace为不同的数据领域设置命名空间。例如context.set(‘database.schema.user’, …)context.set(‘api.endpoints.login’, …)。这提供了清晰的数据组织。上下文版本化与快照重要的中间状态如“已确认的API设计V1”可以打上标签或保存快照防止被后续的、未经验证的修改覆盖。上下文修剪并非所有中间数据都需要永久保存。可以设计策略只保留最终产出和关键决策链路。5.3 智能体路由把任务交给对的“人”我们的简单分配逻辑是“找到第一个角色匹配的Agent”。这不够智能。更高级的路由策略包括基于能力的路由每个Agent在注册时除了role还可以声明一组capabilities如[‘nodejs’, ‘express’, ‘mongodb’]和limitations。Orchestrator根据任务描述和上下文选择能力最匹配的Agent。基于负载的路由记录每个Agent正在执行的任务数优先分配给空闲的Agent实现简单的负载均衡。基于历史的路由记录每个Agent处理同类任务的成功率和质量优先选择“熟手”。5.4 错误处理与自我修复多步骤流程中任何一个环节失败都可能导致整个流程卡住。框架必须具备韧性。任务重试对于因网络或API限流导致的临时失败Orchestrator可以实现指数退避的重试机制。备用Agent如果一个Agent多次失败可以将任务重新路由给另一个具有相似能力的备用Agent。人工干预点对于关键决策或无法自动处理的错误框架应该能暂停流程并通过预设的渠道如发送通知、生成报告请求人工介入。5.5 测试与调试让黑盒变得透明调试一个由多个AI智能体协作的系统是极具挑战的。必须建立强大的可观测性Observability体系。结构化日志所有组件Orchestrator, Agent, Broker的日志必须结构化JSON格式包含统一的traceId以便串联单个请求的完整生命周期。上下文快照导出在关键节点任务开始/完成/失败自动导出上下文快照便于事后复盘看看到底是哪条数据出了问题。消息总线监控可以创建一个MonitoringAgent订阅所有消息主题将消息流可视化帮助理解智能体间的协作时序。6. 实战演练构建一个需求到代码的迷你流水线让我们构想一个更贴近真实场景的用例看看框架如何扩展。目标用户输入一段自然语言需求自动生成一个可运行的微型项目骨架。我们需要扩展以下组件需求分析Agent (RequirementAnalyzerAgent)接收原始需求调用LLM进行分析输出结构化的项目规格Spec包括功能列表、技术栈建议、模块划分。它将规格写入Context。架构设计Agent (ArchitectAgent)读取项目规格负责设计高层次的文件/目录结构并创建初始的package.json、README.md等文件。它发布project.structure.ready消息。多个专项开发AgentBackendAgent订阅project.structure.ready负责生成后端主文件、路由等。FrontendAgent负责生成前端入口文件、主组件。DatabaseAgent负责生成数据模型或SQL脚本。集成与验证Agent (IntegrationAgent)等待所有开发Agent完成任务后它尝试运行npm install和npm start在一个安全沙盒中检查是否有明显的启动错误并生成一个简单的验证报告。这个流程中Orchestrator的分解逻辑可以很简单先运行RequirementAnalyzerAgent然后并行运行ArchitectAgent和其他可以独立开始的Agent最后运行IntegrationAgent。依赖关系通过消息总线来协调而不是硬编码在任务里。实现这个流程的关键在于设计好Agent之间的“契约”即它们通过Context和Message交换的数据格式。例如ArchitectAgent输出的项目结构应该是一个定义好的JSON接口这样BackendAgent才知道应该在哪个目录创建server.js。通过这个例子你可以看到多智能体框架的价值在于标准化了复杂AI工作流的组装方式。你可以像搭积木一样组合不同的AI能力代码生成、分析、测试来完成一个宏大的目标而无需把所有逻辑都写在一个巨大的、难以维护的Prompt里。从零开始实现这样一个框架的过程让我对Claude Code这类工具的理解不再局限于“一个更好的代码补全工具”而是看到了它作为可编程的、可协作的AI组件的潜力。TypeScript的类型安全在构建此类复杂系统时提供了无与伦比的信心。当然这个框架还有很多可以深化的方向比如引入图形化的工作流设计器、支持分布式Agent、集成更强大的异常恢复机制等。但最重要的是这个核心架构为你提供了一个坚实的起点让你可以基于它去探索和构建属于自己的、智能的软件工程未来。