个人AI助手插件开发实战:从零打造天气/翻译/待办三个实用插件

发布时间:2026/10/4 0:40:50

个人AI助手插件开发实战:从零打造天气/翻译/待办三个实用插件 本文是"个人AI助手平台"系列第五篇。建议先阅读前四篇了解基础架构后再看本文。一、为什么需要插件前面的文章里我们搭好了架构、部署上了云、打通了四个IM通道。但一个只会聊天的 AI 助手太单薄了——用户问"明天会下雨吗",你需要查询天气;用户说"帮我记下这条待办",你需要持久化存储。插件的价值:能力扩展:让 AI 能查天气、做翻译、管理待办——不再是一个只会聊天的花瓶热加载:新增能力不用重启服务,开发者写好插件、丢进插件目录就生效社区生态:参照 VS Code 插件市场模式,任何人都可以贡献插件本文带你从零写出三个完整可运行的插件,并掌握插件开发范式。二、插件规范(Plugin Specification)先定义插件接口——所有插件必须遵守的契约:// plugin-spec.tsexportinterfacePluginManifest{/** 插件唯一标识,如 "weather" */id:string;/** 显示名称 */name:string;/** 版本号 */version:string;/** 作者 */author:string;/** 一句话描述 */description:string;/** 所需权限 */permissions:string[];/** 触发关键词,如 ["天气", "weather"] */triggers:string[];}exportinterfacePluginContext{/** 用户ID */userId:string;/** 所属群聊ID */channelId?:string;/** 插件数据存储路径(持久化用) */dataDir:string;/** 日志函数 */logger:PluginLogger;/** 发送消息到当前通道 */sendMessage:(text:string)=Promisevoid;}exportinterfacePluginLogger{info:(msg:string)=void;warn:(msg:string)=void;error:(msg:string,err?:Error)=void;}/** * 插件基类——所有插件必须继承 */exportabstractclassBasePlugin{abstractreadonlymanifest:PluginManifest;/** * 初始化(插件加载时调用一次) */abstractonInit(ctx:PluginContext):Promisevoid;/** * 收到消息时调用 * @returns true 表示已处理该消息(不再向下传递) */abstractonMessage(message:string,ctx:PluginContext):Promise{handled:boolean;reply?:string};/** * 卸载插件时调用(清理资源) */abstractonDestroy():Promisevoid;}三、插件管理器(动态加载)插件管理器负责扫描插件目录、加载、卸载、热更新:// plugin-manager.tsimport*asfsfrom'fs';import*aspathfrom'path';import{BasePlugin,PluginContext,PluginManifest}from'./plugin-spec';interfaceLoadedPlugin{instance:BasePlugin;manifest:PluginManifest;filePath:string;lastModified:number;}exportclassPluginManager{privateplugins:Mapstring,LoadedPlugin=newMap();privatectx:PluginContext;privatehotWatchInterval:NodeJS.Timeout|null=null;constructor(ctx:PluginContext,privatepluginsDir:string){this.ctx=ctx;}/** * 启动:扫描并加载所有插件、启动热监听 */asyncstart():Promisevoid{awaitthis.loadAll();this.startHotWatch();this.ctx.logger.info(`插件管理器已启动,加载${this.plugins.size}个插件`);}/** * 停止:卸载所有插件 */asyncstop():Promisevoid{if(this.hotWatchInterval)clearInterval(this.hotWatchInterval);for(const[id,loaded]ofthis.plugins){awaitloaded.instance.onDestroy();this.ctx.logger.info(`插件${id}已卸载`);}this.plugins.clear();}/** * 处理消息——遍历所有插件,第一个处理的生效 */asynchandleMessage(message:string):Promisestring|null{for(const[id,loaded]ofthis.plugins){try{constresult=awaitloaded.instance.onMessage(message,this.ctx);if(result.handledresult.reply){this.ctx.logger.info(`插件 [${id}] 处理了消息`);returnresult.reply;}}catch(err){this.ctx.logger.error(`插件 [${id}] 处理消息出错`,errasError);}}returnnull;// 无插件处理}/** * 扫描并加载所有插件 */privateasyncloadAll():Promisevoid{if(!fs.existsSync(this.pluginsDir)){fs.mkdirSync(this.pluginsDir,{recursive:true});return;}constfiles=fs.readdirSync(this.pluginsDir);for(constfileoffiles){if(file.endsWith('.js')||file.endsWith('.ts')){awaitthis.loadPlugin(path.join(this.pluginsDir,file));}}}/** * 加载单个插件 */privateasyncloadPlugin(filePath:string):Promisevoid{try{// 清除 require 缓存以支持热更新constresolvedPath=require.resolve(filePath);deleterequire.cache[resolvedPath];constPluginClass=require(filePath).default;constinstance:BasePlugin=newPluginClass();constmanifest=instance.manifest;if(this.plugins.has(manifest.id)){this.ctx.logger.warn(`插件${manifest.id}已存在,跳过重复加载`);return;}awaitinstance.onInit(this.ctx);conststats=fs.statSync(filePath);this.plugins.set(manifest.id,{instance,manifest,filePath,lastModified:stats.mtimeMs,});this.ctx.logger.info(`插件 [${manifest.id}] v${manifest.version}加载成功`);}catch(err){this.ctx.logger.error(`加载插件失败:${filePath}`,errasError);}}/** * 启动热监听:每 3 秒检查插件文件是否变化 */privatestartHotWatch():void{this.hotWatchInterval=setInterval(async()={for(const[id,loaded]ofthis.plugins){try{conststats=fs.statSync(loaded.filePath);if(stats.mtimeMsloaded.lastModified){this.ctx.logger.info(`检测到插件 [${id}] 文件变化,热更新中...`);awaitloaded.instance.onDestroy();this.plugins.delete(id);awaitthis.loadPlugin(loaded.filePath);}}catch(err){// 文件可能被删除,忽略}}},3000);}/** * 获取已加载插件列表 */getLoadedPlugins():PluginManifest[]{returnArray.from(this.plugins.values()).map(p=p.manifest);}}四、实战一:天气查询插件4.1 设计思路触发词:天气、weather、会下雨、多少度依赖:调用公开天气 API(Open-Meteo,免费无需 Key)返回格式:城市名 + 温度 + 天气状况 + 穿衣建议4.2 完整代码
延伸阅读

更多相关文章

2026/10/4 0:01:02

多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系

1. 从"单兵作战"到"集群协同":多智能体编排到底在解决什么问题如果你最近在折腾 Agent 相关的东西,大概率会有一种感觉:单个 Agent 能做的事情,其实很快就摸到天花板了。你给它一个提示词,挂几个工…

2026/10/4 0:01:02

Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化

1. 从“Jev”说起:为什么我要把Agent接进浏览器“Jev”这个词最近在圈子里出现的频率越来越高,很多人第一次听到会以为是某个新模型的名字,其实它更像是一种思路——把Jev模型的能力当作底座,通过Agent的方式去接管浏览器&#xf…

2026/10/3 23:56:01

从零搭建AI工程:从模型接入到Agent编排的完整实践指南

1. 项目概述:当你说“从零开始做AI工程”的时候,到底在说什么“ai-engineering-from-scratch”这个标题,我第一眼看到的时候其实挺感慨的。市面上讲“从零开始学AI”的文章多到泛滥,但绝大多数要么是教你怎么装个库跑个demo&#…

2026/10/3 23:56:01

Carsim与Simulink联合仿真的车辆换道轨迹规划与跟踪

提起自动驾驶、智能网联汽车方向的课题,只要是涉及车辆运动控制的,几乎绕不开 Carsim 和 MATLAB/Simulink 这对黄金搭档。我之前做过一套基于 Carsim 与 Simulink 联合仿真的车辆换道轨迹规划与轨迹跟踪模型,跑了两个月,踩了不少坑…

2026/10/3 23:56:01

鸿业市政道路软件避坑指南:版本匹配、横断面与土方计算常见问题

简介:针对鸿业市政道路软件用户的常见问题解答文档,内容覆盖软件运行、土方、平面、纵断、横断、交叉口设计及其他模块,面向市政道路设计人员与相关专业学生,帮助解决菜单加载失败、土方计算异常、图面显示错乱等高频问题。压缩包…

2026/10/3 23:56:01

超级多智能体架构实战:DeepAgents编排、MCP工具接入与A2A通信

1. 从单体到集群:为什么我们需要超级多智能体1.1 一个真实的需求场景去年下半年我接手了一个企业内部知识助手的项目,需求听起来不复杂:帮员工查制度文档、走审批流程、生成周报。一开始我用的是单体 Agent 方案,一个模型加一堆工…

2026/10/4 0:01:02

Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化

1. 从“Jev”说起:为什么我要把Agent接进浏览器“Jev”这个词最近在圈子里出现的频率越来越高,很多人第一次听到会以为是某个新模型的名字,其实它更像是一种思路——把Jev模型的能力当作底座,通过Agent的方式去接管浏览器&#xf…

2026/10/4 0:01:02

多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系

1. 从"单兵作战"到"集群协同":多智能体编排到底在解决什么问题如果你最近在折腾 Agent 相关的东西,大概率会有一种感觉:单个 Agent 能做的事情,其实很快就摸到天花板了。你给它一个提示词,挂几个工…

2026/10/4 0:01:02

Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化

1. 从“Jev”说起:为什么我要把Agent接进浏览器“Jev”这个词最近在圈子里出现的频率越来越高,很多人第一次听到会以为是某个新模型的名字,其实它更像是一种思路——把Jev模型的能力当作底座,通过Agent的方式去接管浏览器&#xf…

2026/10/4 0:01:02

多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系

1. 从"单兵作战"到"集群协同":多智能体编排到底在解决什么问题如果你最近在折腾 Agent 相关的东西,大概率会有一种感觉:单个 Agent 能做的事情,其实很快就摸到天花板了。你给它一个提示词,挂几个工…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑