行业资讯

基于TypeScript与Skills生态构建可扩展的AI编程Agent实战指南

发布时间:2026/8/18 12:55:00
基于TypeScript与Skills生态构建可扩展的AI编程Agent实战指南 最近在尝试将AI编程助手集成到开发流程中时发现很多工具要么功能单一要么配置复杂难以形成一个高效、可定制的自动化工作流。特别是对于前端和TypeScript项目如何让AI不仅能写代码片段还能理解项目上下文、执行构建命令甚至自动修复问题成为了提升效率的关键。本文将围绕“Pi编程Agent”这一概念结合TypeScript和Skills生态为你拆解如何从零搭建一个属于自己的、免费的智能编程助手。无论你是想探索AI Agent开发还是希望打造一个专属于你技术栈的自动化编程伙伴这篇教程都将提供完整的思路、可运行的代码示例以及避坑指南。1. 什么是Pi编程Agent—— 概念与价值解析在开始动手之前我们首先要厘清几个核心概念。这有助于我们理解正在构建的是什么以及为什么需要它。1.1 Agent、AI编程与SkillsAgent智能体在编程语境下特指能够感知环境、自主决策并执行任务以达成目标的程序。一个“编程Agent”就是一个专门用于辅助或执行编程任务的AI智能体。它不同于简单的代码补全工具它应该具备更复杂的能力比如理解自然语言需求、分析现有代码库、规划执行步骤如安装依赖、运行测试、修复Bug等。AI编程是指利用人工智能技术来辅助或自动化软件开发过程。当前主流形式包括代码补全与建议如GitHub Copilot根据上下文预测下一行代码。代码解释与重构解释复杂代码块或将其重构为更清晰的形式。自动化任务根据指令创建文件、运行命令、调试程序等。Skills技能是赋予Agent具体能力的模块。你可以将其理解为Agent的“插件”或“工具箱”。一个Agent的强大与否很大程度上取决于它集成了哪些Skills。例如代码理解Skill让Agent能阅读并总结一个TypeScript文件的功能。命令行Skill让Agent可以在项目根目录下执行npm install、git commit等命令。文件操作Skill让Agent可以创建、读取、编辑或删除项目文件。网络搜索Skill让Agent能联网获取最新的文档或解决方案。Pi编程Agent可以理解为一种构建模式或项目理念它强调利用现有开源工具和框架可能包括但不限于名为“Pi”的某个项目或工具链构建一个专注于编程任务的、可扩展的AI Agent。其核心是“Agent框架 TypeScript 自定义Skills”。1.2 为什么选择TypeScript在构建复杂的、需要与各种API和系统交互的Agent时TypeScript提供了显著优势类型安全Agent会操作文件系统、调用外部API、处理结构化数据如JSON。TypeScript的静态类型检查可以在编码阶段就发现潜在的错误这对于构建稳定可靠的自动化工具至关重要。强大的生态Node.js生态中有海量的NPM包可用于实现各种Skills如fs、child_process、axios等TypeScript能很好地与它们集成。良好的可维护性随着Skills数量的增加清晰的接口和类型定义能让代码结构更清晰便于团队协作和长期维护。与AI模型协作友好当使用大型语言模型LLM生成代码时明确的类型约束可以让生成的代码更准确减少运行时错误。1.3 核心架构预览一个典型的Pi编程Agent可能包含以下层次大脑LLM Core负责理解用户指令、规划任务步骤、生成代码或命令。通常通过API调用云端LLM如GPT-4、Claude或本地模型实现。技能库Skills Registry一个集中管理所有可用Skills的系统。每个Skill都有明确的描述、参数和调用方法。执行引擎Execution Engine负责安全地调用Skills。这里需要特别注意安全性比如限制文件访问范围、沙箱化命令行执行等。工作记忆Working Memory保存当前会话的上下文例如已读的文件内容、之前的操作历史、项目结构等供LLM在决策时参考。用户接口Interface可以是命令行工具CLI、IDE插件如VSCode扩展或Web界面。本文将重点放在大脑、技能库和执行引擎的构建上使用TypeScript实现一个可在命令行中运行的原型。2. 环境准备与项目初始化我们将从零开始创建一个TypeScript项目并逐步添加核心依赖。2.1 开发环境要求Node.js版本 18 或更高。这是运行TypeScript和所有NPM包的基础。NPM 或 Yarn 或 PNPM包管理器本文使用NPM示例。代码编辑器强烈推荐Visual Studio Code它对TypeScript和Node.js有极佳的支持。OpenAI API 密钥或其他LLM服务密钥用于为Agent提供“大脑”。我们将使用OpenAI GPT模型作为示例。你也可以替换为其他兼容OpenAI API的模型服务。2.2 创建项目并安装核心依赖打开终端执行以下命令# 1. 创建项目目录并进入 mkdir pi-programming-agent cd pi-programming-agent # 2. 初始化npm项目 npm init -y # 3. 安装TypeScript及相关开发依赖 npm install -D typescript ts-node types/node nodemon # 4. 安装核心运行时依赖OpenAI SDK、命令行交互工具、文件系统操作等 npm install openai dotenv commander chalk inquirer figletopenai官方Node.js SDK用于调用GPT API。dotenv用于加载环境变量如API密钥避免硬编码在代码中。commander用于构建强大的命令行界面定义Agent的命令和参数。chalk用于在终端输出彩色文字提升可读性。inquirer用于创建交互式命令行提示。figlet用于生成ASCII艺术字做个炫酷的开场。2.3 配置TypeScript和项目结构初始化TypeScript配置npx tsc --init这会生成一个tsconfig.json文件。我们需要对其进行修改以适配我们的项目。用编辑器打开tsconfig.json确保包含以下关键配置{ compilerOptions: { target: ES2022, module: commonjs, lib: [ES2022], outDir: ./dist, rootDir: ./src, strict: true, esModuleInterop: true, skipLibCheck: true, forceConsistentCasingInFileNames: true, resolveJsonModule: true, declaration: true, declarationMap: true, sourceMap: true }, include: [src/**/*], exclude: [node_modules, dist] }创建项目基础目录结构pi-programming-agent/ ├── src/ │ ├── core/ │ ├── skills/ │ ├── index.ts │ └── cli.ts ├── .env ├── .gitignore ├── package.json └── tsconfig.json在.gitignore文件中添加node_modules/ dist/ .env .DS_Store在.env文件中添加你的OpenAI API密钥请勿将此文件提交到GitOPENAI_API_KEYsk-your-actual-api-key-here3. 构建Agent核心大脑与技能调度这是Agent最核心的部分我们将实现一个简单的Agent运行循环。3.1 定义Skill接口首先我们需要定义所有Skill都必须遵守的契约。创建src/core/types.ts// src/core/types.ts export interface Skill { // Skill的唯一标识符用于在指令中调用 name: string; // 对人类和LLM的描述说明这个Skill能做什么 description: string; // Skill所需的参数列表及其描述 parameters?: Array{ name: string; type: string | number | boolean; description: string; required?: boolean; }; // 执行Skill的核心函数 execute: (args: Recordstring, any, context: AgentContext) PromiseSkillResult; } export interface SkillResult { success: boolean; output: string; // 执行结果的文本描述 data?: any; // 额外的结构化数据 } export interface AgentContext { // 当前工作目录 cwd: string; // 会话历史用于提供上下文给LLM history: Array{ role: user | assistant | system; content: string }; // 可以扩展其他上下文信息如已打开的文件等 [key: string]: any; }3.2 实现技能注册与管理创建src/core/skillRegistry.ts// src/core/skillRegistry.ts import { Skill } from ./types; export class SkillRegistry { private skills: Mapstring, Skill new Map(); register(skill: Skill): void { if (this.skills.has(skill.name)) { throw new Error(Skill with name ${skill.name} is already registered.); } this.skills.set(skill.name, skill); } getSkill(name: string): Skill | undefined { return this.skills.get(name); } getAllSkills(): Skill[] { return Array.from(this.skills.values()); } getSkillDescriptions(): string { return this.getAllSkills() .map(skill { let desc - ${skill.name}: ${skill.description}; if (skill.parameters skill.parameters.length 0) { desc \n 参数: ${skill.parameters.map(p ${p.name}(${p.type})).join(, )}; } return desc; }) .join(\n); } }3.3 实现LLM大脑OpenAI集成创建src/core/llmCore.ts。这里LLM的任务是理解用户请求并决定调用哪个Skill以及传递什么参数。// src/core/llmCore.ts import OpenAI from openai; import { SkillRegistry } from ./skillRegistry; import { AgentContext } from ./types; import dotenv from dotenv; dotenv.config(); export class LLMCore { private openai: OpenAI; private skillRegistry: SkillRegistry; constructor(skillRegistry: SkillRegistry) { const apiKey process.env.OPENAI_API_KEY; if (!apiKey) { throw new Error(OPENAI_API_KEY is not set in environment variables.); } this.openai new OpenAI({ apiKey }); this.skillRegistry skillRegistry; } async decideAction(userInput: string, context: AgentContext): Promise{ skillName: string; args: Recordstring, any } | { response: string } { // 构建系统提示词告诉LLM可用的Skills和它的角色 const systemPrompt 你是一个智能编程助手可以调用以下工具Skills来帮助用户 ${this.skillRegistry.getSkillDescriptions()} 请根据用户请求决定是直接回复还是调用一个Skill。 如果你决定调用Skill请严格按照以下JSON格式回复 { action: call_skill, skillName: 技能名称, args: {参数名: 参数值} } 如果你决定直接回复请按以下格式回复 { action: direct_response, response: 你的回复内容 } ; const messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] [ { role: system, content: systemPrompt }, ...context.history, { role: user, content: userInput } ]; try { const completion await this.openai.chat.completions.create({ model: gpt-4o-mini, // 或使用 gpt-3.5-turbo 以降低成本 messages, temperature: 0.1, // 低温度让输出更确定更适合工具调用 response_format: { type: json_object }, // 强制返回JSON }); const content completion.choices[0]?.message?.content; if (!content) { throw new Error(No response content from LLM.); } const decision JSON.parse(content); if (decision.action call_skill) { return { skillName: decision.skillName, args: decision.args }; } else { return { response: decision.response }; } } catch (error) { console.error(LLM决策错误:, error); return { response: 抱歉我在处理你的请求时遇到了问题${error.message} }; } } }3.4 实现Agent执行引擎创建src/core/agentEngine.ts它负责协调LLM决策和Skill执行。// src/core/agentEngine.ts import { LLMCore } from ./llmCore; import { SkillRegistry } from ./skillRegistry; import { AgentContext, SkillResult } from ./types; import chalk from chalk; export class AgentEngine { private llmCore: LLMCore; private skillRegistry: SkillRegistry; private context: AgentContext; constructor(skillRegistry: SkillRegistry, initialCwd: string process.cwd()) { this.skillRegistry skillRegistry; this.llmCore new LLMCore(skillRegistry); this.context { cwd: initialCwd, history: [], }; } async processInput(userInput: string): Promisestring { console.log(chalk.blue(\n[用户] ${userInput})); // 1. 将用户输入加入历史 this.context.history.push({ role: user, content: userInput }); // 2. 让LLM决定做什么 const decision await this.llmCore.decideAction(userInput, this.context); let assistantResponse: string; // 3. 执行决策 if (skillName in decision) { const { skillName, args } decision; const skill this.skillRegistry.getSkill(skillName); if (!skill) { assistantResponse 错误找不到名为 ${skillName} 的技能。; } else { console.log(chalk.yellow([Agent] 正在执行技能: ${skillName}参数: ${JSON.stringify(args)})); try { const result: SkillResult await skill.execute(args, this.context); assistantResponse result.output; // 可以在这里根据result.data更新context console.log(chalk.green([技能 ${skillName}] 执行成功。输出: ${result.output})); } catch (error) { assistantResponse 执行技能 ${skillName} 时出错: ${error.message}; console.error(chalk.red([技能 ${skillName}] 执行失败:, error)); } } } else { assistantResponse decision.response; console.log(chalk.cyan([Agent] ${assistantResponse})); } // 4. 将助手回复加入历史 this.context.history.push({ role: assistant, content: assistantResponse }); return assistantResponse; } getContext(): AgentContext { return { ...this.context }; // 返回副本 } }4. 开发实战实现核心编程Skills有了引擎现在我们来打造几个真正有用的编程Skill。4.1 技能一读取文件内容创建src/skills/readFileSkill.ts// src/skills/readFileSkill.ts import fs from fs/promises; import path from path; import { Skill, SkillResult, AgentContext } from ../core/types; export const readFileSkill: Skill { name: read_file, description: 读取指定文件的内容。, parameters: [ { name: filePath, type: string, description: 相对于当前工作目录的文件路径, required: true } ], async execute(args: { filePath: string }, context: AgentContext): PromiseSkillResult { const fullPath path.resolve(context.cwd, args.filePath); try { const content await fs.readFile(fullPath, utf-8); return { success: true, output: 文件 ${args.filePath} 的内容如下\n\\\\n${content}\n\\\, data: { content, filePath: args.filePath } }; } catch (error: any) { return { success: false, output: 无法读取文件 ${args.filePath}: ${error.message} }; } } };4.2 技能二执行Shell命令安全版重要允许AI执行任意命令非常危险。我们必须进行严格限制。这里我们实现一个只能在项目目录下执行预定义白名单命令的Skill。创建src/skills/runCommandSkill.ts// src/skills/runCommandSkill.ts import { exec } from child_process; import { promisify } from util; import { Skill, SkillResult, AgentContext } from ../core/types; const execAsync promisify(exec); // 命令白名单只允许运行这些命令 const ALLOWED_COMMANDS [npm, node, npx, ls, pwd, echo, git status, git add, git commit -m]; export const runCommandSkill: Skill { name: run_command, description: 在项目目录下执行安全的Shell命令如 npm install, ls。, parameters: [ { name: command, type: string, description: 要执行的Shell命令, required: true } ], async execute(args: { command: string }, context: AgentContext): PromiseSkillResult { const { command } args; // 1. 安全检查检查命令是否在白名单内 const isAllowed ALLOWED_COMMANDS.some(allowedCmd command.startsWith(allowedCmd)); if (!isAllowed) { return { success: false, output: 出于安全考虑不允许执行命令: ${command}。当前仅支持${ALLOWED_COMMANDS.join(, )}。 }; } // 2. 安全检查防止目录遍历等攻击简单示例 if (command.includes(..) || command.includes(/etc/passwd) || command.includes(rm -rf)) { return { success: false, output: 命令包含潜在危险字符已被阻止。 }; } console.log(执行命令: ${command} (位于: ${context.cwd})); try { const { stdout, stderr } await execAsync(command, { cwd: context.cwd }); const output stderr ? STDERR:\n${stderr}\n\nSTDOUT:\n${stdout} : stdout; return { success: true, output: 命令执行完成\n\\\bash\n${output}\n\\\, data: { stdout, stderr } }; } catch (error: any) { return { success: false, output: 命令执行失败: ${error.message}\n${error.stderr || } }; } } };4.3 技能三分析代码简单版这个Skill会调用LLM来分析给定的代码片段。创建src/skills/analyzeCodeSkill.ts// src/skills/analyzeCodeSkill.ts import { Skill, SkillResult, AgentContext } from ../core/types; import OpenAI from openai; import dotenv from dotenv; dotenv.config(); const openai new OpenAI({ apiKey: process.env.OPENAI_API_KEY! }); export const analyzeCodeSkill: Skill { name: analyze_code, description: 分析提供的代码片段解释其功能、潜在问题或提出改进建议。, parameters: [ { name: code, type: string, description: 需要分析的代码字符串, required: true }, { name: language, type: string, description: 编程语言如 javascript, typescript, python, required: false } ], async execute(args: { code: string; language?: string }, context: AgentContext): PromiseSkillResult { const prompt 请分析以下${args.language || 代码}\n\\\${args.language || }\n${args.code}\n\\\\n请提供1. 简要功能说明2. 潜在的bug或不良实践3. 改进建议如有。; try { const completion await openai.chat.completions.create({ model: gpt-4o-mini, messages: [{ role: user, content: prompt }], temperature: 0.7, }); const analysis completion.choices[0]?.message?.content || 未能生成分析结果。; return { success: true, output: 代码分析结果\n${analysis}, data: { analysis } }; } catch (error: any) { return { success: false, output: 代码分析请求失败: ${error.message} }; } } };5. 组装并运行你的第一个Pi编程Agent5.1 创建主程序入口创建src/index.ts在这里我们将所有组件组装起来。// src/index.ts import { SkillRegistry } from ./core/skillRegistry; import { AgentEngine } from ./core/agentEngine; import { readFileSkill } from ./skills/readFileSkill; import { runCommandSkill } from ./skills/runCommandSkill; import { analyzeCodeSkill } from ./skills/analyzeCodeSkill; import chalk from chalk; import figlet from figlet; import inquirer from inquirer; async function main() { console.log(chalk.blue(figlet.textSync(Pi Agent, { horizontalLayout: full }))); console.log(chalk.yellow( 你的智能编程助手已启动 \n)); // 1. 初始化技能注册表 const skillRegistry new SkillRegistry(); skillRegistry.register(readFileSkill); skillRegistry.register(runCommandSkill); skillRegistry.register(analyzeCodeSkill); // 2. 初始化Agent引擎 const agent new AgentEngine(skillRegistry); // 3. 交互式循环 while (true) { const { userInput } await inquirer.prompt([ { type: input, name: userInput, message: chalk.green(你:), prefix: , } ]); if (userInput.toLowerCase() exit || userInput.toLowerCase() quit) { console.log(chalk.yellow(再见)); break; } await agent.processInput(userInput); } } // 启动程序 if (require.main module) { main().catch(error { console.error(chalk.red(程序运行出错:), error); process.exit(1); }); }5.2 配置启动脚本和运行在package.json中添加启动脚本{ scripts: { start: nodemon --exec ts-node src/index.ts, build: tsc, agent: node dist/index.js } }现在在终端运行你的Agentnpm start你会看到ASCII艺术字和提示符。现在你可以尝试与你的Agent对话了示例对话你: 帮我看看当前目录下有什么文件 [Agent] 正在执行技能: run_command参数: {command:ls} [技能 run_command] 执行成功。输出: 命令执行完成bash package.json tsconfig.json src .env .gitignore你: 读取package.json的内容 [Agent] 正在执行技能: read_file参数: {filePath:package.json} [技能 read_file] 执行成功。输出: 文件 package.json 的内容如下{...}你: 分析这段代码function add(a, b) { return a b; } [Agent] 正在执行技能: analyze_code参数: {code:function add(a, b) { return a b; },language:javascript} [技能 analyze_code] 执行成功。输出: 代码分析结果 1. 功能说明这是一个简单的JavaScript函数名为add接收两个参数a和b返回它们的和。 2. 潜在问题缺少参数类型检查和错误处理。如果传入非数字参数如字符串会进行字符串拼接而非数值相加。 3. 改进建议建议使用TypeScript添加类型注解或运行时进行类型检查。6. 常见问题与排查思路在开发和运行过程中你可能会遇到以下问题问题现象常见原因解决思路启动时报错OPENAI_API_KEY is not set.env文件未创建或API_KEY未正确设置。1. 确保项目根目录下有.env文件。2. 检查.env文件中OPENAI_API_KEY的赋值是否正确且没有多余空格。3. 确保.env文件已添加到.gitignore。运行npm start提示ts-node未找到开发依赖未安装完全。执行npm install重新安装所有依赖。Agent无法识别“read_file”等技能Skill没有正确注册到SkillRegistry。检查src/index.ts中是否通过skillRegistry.register()注册了你定义的Skill。执行命令被拒绝命令不在ALLOWED_COMMANDS白名单中。1. 检查src/skills/runCommandSkill.ts中的白名单。2. 对于安全的开发命令可以将其添加到白名单数组。生产环境务必谨慎LLM返回的JSON解析失败LLM没有严格按照要求的JSON格式回复。1. 检查src/core/llmCore.ts中的systemPrompt确保指令清晰。2. 可以尝试降低temperature到0.1让输出更稳定。3. 在代码中添加更健壮的JSON解析错误处理。程序运行缓慢每次请求都调用GPT API网络延迟高。1. 考虑使用更快的模型如gpt-3.5-turbo。2. 实现简单的本地缓存对相同的问题缓存LLM回复。3. 优化提示词减少不必要的上下文。7. 进阶优化与最佳实践至此一个基础的Pi编程Agent已经可以工作。但要将其用于实际项目还需要考虑以下方面7.1 安全性强化沙箱化命令执行使用docker run或在严格限制权限的独立进程中执行命令而不是直接使用child_process.exec。更精细的白名单不仅检查命令前缀还可以使用正则表达式匹配完整的命令模式。文件路径限制在文件操作Skill中使用path.resolve和path.relative确保操作不超出项目根目录。API密钥管理使用密钥管理服务而不是明文存储在.env文件中。7.2 扩展更多实用Skills代码生成Skill根据描述生成特定功能的函数或组件代码。测试运行Skill运行npm test或jest并解析测试结果。依赖检查Skill使用npm outdated或npm audit检查项目依赖状态。Git操作Skill封装git diff,git log,git checkout等常用操作。VSCode集成将Agent封装成VSCode扩展实现右键菜单、代码片段生成等功能。7.3 提升上下文管理向量数据库集成使用ChromaDB或Pinecone存储和检索项目文档、代码片段让Agent拥有长期记忆。代码库索引集成Tree-sitter或Sourcegraph让Agent能快速理解大型代码库的结构。7.4 工程化与部署配置化将Skills列表、LLM模型参数、安全规则等提取到外部配置文件如config.yaml。日志与监控集成Winston或Pino记录详细的运行日志便于调试和审计。Web服务化使用Express或Fastify将Agent包装成HTTP API供其他系统调用。错误处理与重试为LLM API调用和Skill执行添加完善的错误处理和指数退避重试机制。构建一个强大的编程Agent是一个迭代的过程。从本文这个最小可行产品MVP开始你可以根据自己的具体需求选择一个方向深入逐步添加功能、完善安全性和用户体验。记住核心思想是“让AI成为你工作流中一个可预测、可控制、可扩展的自动化环节”而不是一个黑盒。通过亲手搭建你不仅能获得一个实用工具更能深入理解AI Agent的工作原理为未来更复杂的智能应用开发打下坚实基础。