1. 项目概述为智能体构建技能与解释器驱动的工作流最近在折腾AI智能体Agents开发特别是围绕Claude Code、Cursor这类新一代AI编程工具我发现一个核心痛点如何让一个智能体不只是简单地调用API而是能像一名真正的开发者一样拥有连贯、可复用、可组合的“技能”Skills并基于这些技能执行复杂的工作流Workflows。这不仅仅是写几个提示词Prompt那么简单它涉及到如何将自然语言指令结构化、如何让不同的技能模块化地协作、以及如何让智能体理解并执行多步骤任务。这正是“Building workflows for agents with Skills and Interpreters”这个主题要解决的核心问题。简单来说它就是一套方法论和工具集用于赋予AI智能体“工具箱”和“使用说明书”让它们能自主或半自主地完成从需求分析、代码编写、测试到部署等一系列开发任务。如果你正在使用TypeScript进行AI应用开发或者对如何将大型语言模型LLM的能力更深度地集成到你的开发流程中感兴趣那么理解技能与解释器驱动的智能体工作流将是提升开发效率、构建更强大AI助手的关键。这不仅仅是Claude Code或Cursor用户的专属话题任何希望利用AI自动化重复性编码任务、构建智能开发副驾的开发者都能从中获得启发和可直接落地的实践方案。2. 核心理念拆解技能、解释器与工作流的三位一体要构建一个强大的智能体我们需要先厘清三个核心概念技能Skills、解释器Interpreters和工作流Workflows。它们之间的关系好比一个工匠智能体的工具箱技能、大脑解释器和施工图纸工作流。2.1 技能智能体的模块化工具箱技能是智能体能够执行的最小可复用操作单元。它不是一个模糊的“写代码”指令而是一个封装良好、功能明确的动作。例如一个文件操作技能readFile(path: string): Promisestring 它封装了读取指定路径文件内容的所有逻辑。一个代码分析技能analyzeCodeComplexity(code: string): { cyclomaticComplexity: number, maintainabilityIndex: number } 它调用特定的代码分析库如typhonjs-escomplex并返回结构化结果。一个网络请求技能fetchGitHubRepoInfo(owner: string, repo: string): PromiseRepoInfo 它处理GitHub API的认证、请求和错误重试。技能的设计原则单一职责一个技能只做一件事并且做好。这保证了技能的高内聚和易测试。明确接口输入和输出必须是清晰、类型化的。在TypeScript中这通过接口Interface或类型别名Type Alias来定义这是与智能体“沟通”的契约。无状态性理想情况下技能本身不维护状态。状态应该由工作流或调用者管理。这使得技能可以在不同上下文中安全复用。可描述性技能需要提供机器可读的描述如名称、功能、输入参数说明、输出示例以便智能体的“大脑”解释器能够理解在什么情况下调用它。注意不要将技能设计得过于庞大。一个“实现用户登录功能”的技能就过于复杂它应该被拆分为“验证用户输入”、“查询数据库”、“生成JWT令牌”、“设置HTTP Cookie”等多个更细粒度的技能。2.2 解释器智能体的决策与调度中心解释器是智能体的“大脑”。它的核心职责是理解用户意图将用户的自然语言指令如“帮我在src/utils目录下创建一个格式化日期的函数”解析为结构化任务。规划与调度根据当前任务、可用技能和上下文规划出一个或多个技能的执行序列即工作流。它需要决定先做什么后做什么以及如果某一步失败该如何处理重试、回滚或报错。技能调用与结果整合按照规划依次调用相应的技能并将上一个技能的输出作为下一个技能的输入如果需要最终将整合后的结果返回给用户。解释器通常由一个大语言模型LLM驱动例如通过OpenAI的GPT、Anthropic的Claude或本地的开源模型。我们通过精心设计的系统提示词System Prompt和上下文管理来“教导”LLM如何扮演好这个调度员的角色。在TypeScript实现中解释器可能是一个封装了LLM调用、上下文管理和技能路由的类。2.3 工作流技能执行的蓝图工作流是解释器规划出的、由一系列技能按特定逻辑顺序顺序、并行、条件分支、循环组成的执行蓝图。它是动态生成的而不是静态定义的。例如针对“创建格式化日期函数”的指令一个可能的工作流是技能A分析现有代码结构- 确定src/utils目录是否存在以及日期相关函数的命名惯例。技能B读取相关模板或示例- 从知识库或指定路径读取日期格式化函数的通用模板。技能C生成函数代码- 结合模板和具体需求如需要支持YYYY-MM-DD和ISO格式生成TypeScript函数代码。技能D代码风格检查- 调用ESLint或Prettier技能对生成的代码进行格式化。技能E写入文件- 将最终代码写入src/utils/dateFormatter.ts。这个工作流在运行时才被解释器动态创建出来。更高级的工作流可能包含错误处理分支如果技能D检查失败则触发“代码修正”技能然后重新检查。3. 基于TypeScript的实战架构设计理解了理念之后我们来看如何用TypeScript构建这样一个系统。下面是一个高内聚、低耦合的参考架构。3.1 核心模块定义我们将系统分为以下几个核心模块并使用TypeScript接口来定义契约// 1. 技能接口定义 interface Skill { name: string; // 技能唯一标识如 file.read description: string; // 给LLM看的自然语言描述 inputSchema: JSONSchema; // 输入参数JSON Schema用于验证和提示LLM outputSchema: JSONSchema; // 输出结构JSON Schema execute: (params: any, context: AgentContext) Promiseany; // 执行函数 } // 2. 技能注册表 class SkillRegistry { private skills: Mapstring, Skill new Map(); register(skill: Skill): void { this.skills.set(skill.name, skill); } getSkill(name: string): Skill | undefined { return this.skills.get(name); } getAllSkillDescriptions(): Array{name: string, description: string, inputSchema: JSONSchema} { // 返回所有技能的描述用于构造给LLM的提示词 return Array.from(this.skills.values()).map(s ({ name: s.name, description: s.description, inputSchema: s.inputSchema })); } } // 3. 解释器核心 class Interpreter { constructor( private llmClient: LLMClient, // LLM客户端如OpenAI, Anthropic private skillRegistry: SkillRegistry ) {} async interpret(userInput: string, context: AgentContext): PromiseWorkflow { // 步骤1构造提示词包含可用技能列表和当前上下文 const prompt this.constructPlanningPrompt(userInput, context); // 步骤2调用LLM让其生成一个规划JSON格式 const llmResponse await this.llmClient.createChatCompletion({ messages: [{ role: system, content: prompt }], temperature: 0.1, // 低随机性保证规划稳定性 }); // 步骤3解析LLM返回的JSON将其转换为Workflow对象 const plan JSON.parse(llmResponse.content); return this.parsePlanToWorkflow(plan); } private constructPlanningPrompt(userInput: string, context: AgentContext): string { const skillDescriptions this.skillRegistry.getAllSkillDescriptions(); // 这里构建一个详细的提示词指导LLM如何规划 return 你是一个智能开发助手。你的任务是将用户请求分解为一系列可执行的步骤技能。 以下是你可以调用的技能列表 ${JSON.stringify(skillDescriptions, null, 2)} 当前上下文 - 工作目录${context.cwd} - 已打开文件${context.openFiles.join(, )} 用户请求${userInput} 请以JSON格式输出你的计划格式如下 { thought: 你的推理过程, steps: [ { skill: skill.name, params: { ... }, reason: 为什么使用这个技能 }, ... ] } ; } } // 4. 工作流执行引擎 class WorkflowEngine { async execute(workflow: Workflow, context: AgentContext): PromiseWorkflowResult { const results: any[] []; for (const step of workflow.steps) { const skill this.skillRegistry.getSkill(step.skill); if (!skill) { throw new Error(Skill ${step.skill} not found); } try { // 执行技能并将上一步的结果如果有注入参数 const params this.injectPreviousResult(step.params, results); const result await skill.execute(params, context); results.push(result); context.log(Step ${step.skill} completed.); } catch (error) { // 错误处理可以重试、回滚或终止工作流 context.error(Step ${step.skill} failed: ${error.message}); // 这里可以触发一个“错误处理”工作流 throw new WorkflowExecutionError(step.skill, error); } } return { success: true, outputs: results }; } }3.2 技能的具体实现示例让我们实现两个具体的技能感受一下细节// 文件读取技能 const readFileSkill: Skill { name: fs.readFile, description: 读取指定路径文件的内容。, inputSchema: { type: object, properties: { path: { type: string, description: 文件的相对或绝对路径 } }, required: [path] }, outputSchema: { type: object, properties: { content: { type: string, description: 文件内容 }, exists: { type: boolean } } }, async execute(params: { path: string }, context: AgentContext) { const fs await import(fs/promises); const path await import(path); const fullPath path.resolve(context.cwd, params.path); try { const content await fs.readFile(fullPath, utf-8); return { content, exists: true }; } catch (error: any) { if (error.code ENOENT) { return { content: , exists: false }; } throw error; // 其他错误向上抛出 } } }; // 代码生成技能调用LLM const generateCodeSkill: Skill { name: code.generate, description: 根据描述和上下文生成代码片段。, inputSchema: { type: object, properties: { instruction: { type: string, description: 代码生成指令 }, contextCode: { type: string, description: 相关的上下文代码, default: } }, required: [instruction] }, outputSchema: { type: object, properties: { code: { type: string, description: 生成的代码 }, explanation: { type: string, description: 对生成代码的简要说明 } } }, async execute(params: { instruction: string; contextCode?: string }, context: AgentContext) { // 调用LLM生成代码 const prompt 你是一个TypeScript专家。请根据以下指令生成代码。 指令${params.instruction} ${params.contextCode ? 相关上下文代码\n\\\typescript\n${params.contextCode}\n\\\ : } 请只输出代码块如果需要解释请在代码块后的注释中简要说明。 ; const response await context.llmClient.chat([{ role: user, content: prompt }]); // 简单地从响应中提取代码块实际应用需要更健壮的解析 const code this.extractCodeBlock(response); return { code, explanation: Generated based on instruction. }; } };实操心得在实现generateCodeSkill时最关键的是设计好给LLM的提示词。指令必须清晰上下文要相关且简洁。一个常见的技巧是在提示词中明确指定输出格式例如“请输出一个完整的TypeScript函数函数名为formatDate”这能极大提高生成代码的可用性减少后续修正的工作量。4. 工作流动态规划与执行的深入解析解释器生成的工作流是动态的这意味着同样的用户指令在不同上下文中可能产生不同的执行计划。这是智能体“智能”的体现。4.1 规划提示词工程解释器的核心能力很大程度上取决于我们如何构造给LLM的规划提示词Planning Prompt。上面示例中是一个基础版本一个成熟的提示词应该包含更多细节约束条件明确告诉LLM什么不能做。例如“你不能直接修改node_modules目录下的文件”、“所有文件操作必须基于context.cwd指定的工作目录”。最佳实践指引例如“创建新文件时优先检查是否已存在同名文件”、“生成代码后应自动调用代码格式化技能”。上下文丰富化不仅传递当前目录和打开的文件还可以传递项目类型如“Next.js”, “React Native”、包管理器npm/yarn/pnpm、以及最近的操作历史。输出格式强化严格要求LLM输出指定格式的JSON并可以提供多个示例Few-shot Learning让LLM更好地模仿。一个增强版的提示词开头可能是这样的你是一个经验丰富的软件开发助手。你的目标是将用户请求安全、高效地转化为一系列原子操作技能。 **安全规则** 1. 绝不执行任何可能破坏系统或数据的操作如rm -rf /。 2. 所有文件路径必须是相对于当前工作目录(${context.cwd})的。 3. 在覆盖任何现有文件前必须通过fs.readFile技能检查其内容。 **可用的技能是你唯一能执行的操作**。你必须从以下列表中选择并组合它们来完成用户请求 [技能列表JSON] **输出格式必须严格遵循此JSON Schema** { thought: ..., steps: [{skill: ..., params: {...}, reason: ...}] } **示例1** 用户请求“看看src/index.ts里有什么” 输出{ thought: 用户想查看文件内容我需要使用文件读取技能。, steps: [ {skill: fs.readFile, params: {path: src/index.ts}, reason: 读取指定路径的文件内容} ] } 现在请处理以下请求 用户请求${userInput} 当前上下文${JSON.stringify(context)}4.2 工作流的执行与状态管理工作流引擎在执行时需要妥善管理状态和依赖。参数注入后一个技能的输入可能依赖于前一个技能的输出。引擎需要提供一种方式让技能参数可以引用之前步骤的结果。一种常见的方法是使用模板语法例如在参数中写{{steps[0].output.content}}引擎在执行前将其替换为实际值。错误处理与重试不是所有错误都需要终止整个工作流。对于网络波动等临时性错误可以配置自动重试。对于技能执行失败可以设计备选路径。例如如果code.generate生成的代码无法通过code.lint检查可以触发一个code.fix技能尝试自动修复修复后再重新检查。并行执行优化如果工作流中的某些步骤之间没有依赖关系引擎可以尝试并行执行它们以提高效率。例如在初始化一个新项目时“安装依赖”和“创建配置文件”可能可以同时进行。这需要解释器在规划时就能识别出这种并行可能性或者引擎在执行时进行动态分析。// 一个支持简单参数注入的执行步骤示例 class WorkflowEngine { private injectPreviousResult(params: any, previousResults: any[]): any { const paramString JSON.stringify(params); // 简单的模板替换例如将 {{steps[0].output.filePath}} 替换为实际值 const injectedParamString paramString.replace( /\{\{steps\[(\d)\]\.output\.(\w)\}\}/g, (match, stepIndex, key) { const result previousResults[parseInt(stepIndex)]; return result ? result[key] : match; } ); return JSON.parse(injectedParamString); } }5. 高级主题技能组合、测试与调试5.1 复合技能与技能链基础技能是原子操作但我们可以组合它们形成更高级的“复合技能”或“技能链”。这类似于编程中的函数组合。显式组合创建一个新的技能在其execute方法内部按顺序调用其他技能。这种方式逻辑清晰但灵活性较差。const createReactComponentSkill: Skill { name: react.createComponent, async execute(params, context) { // 1. 生成组件代码 const codeResult await generateCodeSkill.execute({ instruction: 创建一个名为${params.name}的React函数组件使用TypeScript包含props类型定义。 }, context); // 2. 格式化代码 const formattedCode await formatCodeSkill.execute({ code: codeResult.code }, context); // 3. 写入文件 await writeFileSkill.execute({ path: src/components/${params.name}.tsx, content: formattedCode.code }, context); return { success: true, filePath: src/components/${params.name}.tsx }; } };动态组合更优雅的方式是让解释器在规划阶段就识别出常见的模式并自动应用。这需要更强大的LLM和更丰富的上下文。例如当用户请求“创建一个新的React组件并添加样式”时解释器应该能自动规划出“生成TSX代码” - “生成CSS/SASS代码” - “创建文件” - “更新导出索引”这一连串技能。5.2 技能的测试策略技能作为原子操作必须易于测试。由于它们通常是纯函数或接近纯函数依赖注入外部服务我们可以为每个技能编写单元测试。// 使用Jest测试readFileSkill import { readFileSkill } from ./skills/filesystem; import { AgentContext } from ./types; describe(readFileSkill, () { let mockContext: AgentContext; beforeEach(() { mockContext { cwd: /test, log: jest.fn(), error: jest.fn() }; // 可以mock fs模块避免真实IO }); it(should read file content successfully, async () { // 假设我们mock了fs.readFile返回hello world const result await readFileSkill.execute({ path: test.txt }, mockContext); expect(result.content).toBe(hello world); expect(result.exists).toBe(true); }); it(should handle non-existent file gracefully, async () { // 假设我们mock了fs.readFile抛出ENOENT错误 const result await readFileSkill.execute({ path: nonexistent.txt }, mockContext); expect(result.content).toBe(); expect(result.exists).toBe(false); }); it(should validate input parameters, () { // 可以使用ajv等库根据inputSchema进行验证测试 const invalidInput {}; expect(() someValidationFunction(invalidInput, readFileSkill.inputSchema)).toThrow(); }); });对于解释器和工作流引擎则需要更复杂的集成测试或端到端测试模拟用户输入验证最终输出的正确性。5.3 调试与可观测性当智能体行为不符合预期时强大的调试工具至关重要。详细日志在每个技能执行前后、解释器规划前后记录详细的日志包括输入、输出、耗时和可能发生的错误。这些日志应该结构化如JSON格式方便查询和分析。工作流可视化将解释器生成的Workflow对象可视化展示技能执行的顺序、状态等待、执行中、成功、失败和中间结果。这对于理解复杂任务的执行路径非常有帮助。思维链追溯保存并展示解释器在规划时产生的“thought”字段。这是理解LLM决策过程的关键如果规划出错可以首先检查这里的推理逻辑是否合理。交互式修正当某一步骤失败时不应总是让整个工作流产。可以提供一种“交互式修复”模式将错误和上下文反馈给用户或一个更高级的“修复智能体”由它来决定是重试、跳过还是手动介入。6. 常见问题与实战避坑指南在实际构建和使用这类系统时你会遇到一些典型问题。以下是我从多个项目中总结出的经验。6.1 规划失败LLM不按格式输出或逻辑混乱问题解释器调用LLM后返回的不是有效的JSON或者规划的步骤完全不合理比如试图用fs.readFile技能去安装npm包。排查与解决强化提示词在提示词中更严厉地强调输出格式并使用JSON Schema来描述要求的格式。可以提供2-3个非常清晰的示例Few-shot。降低Temperature将LLM调用的temperature参数设低如0.1减少随机性使输出更稳定。后处理与重试在代码中捕获JSON解析错误。如果解析失败可以将错误信息和原始提示再次发送给LLM要求它修正。通常设置1-2次重试就能解决大部分格式问题。使用结构化输出如果使用的LLM API支持如OpenAI的JSON Mode或Anthropic的Claude有相应功能务必开启。这能从根本上保证输出格式。6.2 技能执行错误权限、依赖或环境问题问题技能本身逻辑正确但在特定环境下执行失败如文件权限不足、网络请求超时、缺少某个命令行工具。排查与解决技能设计要健壮技能内部应有完善的错误处理区分“预期内错误”如文件不存在和“意外错误”如权限拒绝并抛出带有明确类型的错误。上下文注入环境信息在AgentContext中提供环境信息如操作系统、Node.js版本、当前用户权限等。技能可以根据这些信息调整行为或提前报出友好错误。依赖检查技能创建一个system.checkDependency技能在工作流开始前检查必要的命令行工具、环境变量或网络连通性。实施重试与回退机制在工作流引擎层对网络类技能配置指数退避重试。对于某些操作可以提供“回退”技能比如git.reset技能可以在文件操作失败后尝试恢复。6.3 性能问题工作流执行缓慢问题涉及多个LLM调用的复杂工作流如规划一次生成代码又调用一次耗时很长。排查与解决技能缓存对于纯函数且输入相同的技能如代码格式化、静态分析可以对其结果进行缓存。并行化如前所述分析工作流步骤间的依赖关系对无依赖的步骤进行并行执行。LLM调用优化考虑使用更快的模型、配置更低的max_tokens如果不需要长输出、或使用流式响应streaming让用户感知更快。对于非关键路径的LLM调用如生成解释文本可以尝试使用更小、更快的模型。异步与超时确保所有技能调用和LLM请求都是异步的并设置合理的超时时间避免单个步骤卡死整个流程。6.4 安全风险任意代码执行与数据泄露问题这是最危险的一类问题。智能体如果拥有文件读写、命令执行等强大技能可能被恶意提示词诱导执行危险操作。排查与解决技能权限隔离实现一个权限系统。为每个技能标记所需的权限级别如read,write,exec。在解释器规划阶段或引擎执行阶段进行权限检查。一个处理用户消息的聊天智能体绝不应该被赋予exec权限。输入验证与净化对所有从用户输入或LLM规划中产生的参数进行严格的验证和净化。特别是传递给exec类技能的命令行参数必须进行白名单过滤或转义。沙箱环境对于执行不确定代码如运行生成的代码片段的技能必须在安全的沙箱环境如Docker容器、vm2模块中运行并严格限制资源CPU、内存、网络。审计日志所有技能的调用特别是高危操作必须记录不可篡改的审计日志包括操作者、时间、参数和结果。构建一个由技能和解释器驱动的智能体工作流系统是一个迭代和演化的过程。从实现几个核心技能开始逐步完善解释器的提示词和规划逻辑再根据实际使用反馈增加更多的技能和更健壮的错误处理。这套架构的核心优势在于其模块化和可扩展性——每增加一个新的技能就相当于为你的智能体增加了一种新的“超能力”。随着技能库的丰富你的智能体能够自动化处理的任务会变得越来越复杂最终成为一个真正理解你项目和编码习惯的强力助手。