给 DeepSeek 写第一个插件,我踩的 5 个坑
最近我给 DeepSeek 写了一个插件。
起因是这样的。现在让 AI 聊股票,它基本是靠训练数据里的记忆在回答。你问它茅台现在多少钱,它报一个不知道哪年的旧价格,再一本正经地分析市盈率、市值,数字全是它自己补的。一旦被拆穿,它就道歉,说知识截止到某某时间。
我希望改变这件事,让模型真的去查实时行情,而不是靠记忆。
DeepSeek 官方提供了一个框架,叫 DeepSeek Harness,简称 DSH。它是一个跑在本地的 agent 框架,最核心的能力是支持插件。装上浏览器插件,它就能上网。装上文件系统插件,它就能读写本地文件。
所以我要做的很明确,写一个行情插件。
听起来不难。一个数据接口,几行界面,三个工具。
真正做起来,我才发现里面坑不少,而且大多来自框架文档没写清楚、或者写了但容易被忽略的地方。我把它们整理出来,如果你也要给这类框架写插件,应该能少走一些弯路。
先简单交代一下插件的结构。
一个 DSH 插件,本质上是一个 npm 包。但它分两面。一面跑在 Node 端,负责数据获取和工具注册。一面跑在浏览器端,负责界面。中间靠一个叫 cordis.patch.yml 的文件,声明自己怎么被框架加载。
我写的插件叫 dsh-plugin-quote-cn。功能是,在设置页加一个「行情」入口,可以加自选股,看现价、涨跌幅、分时图、K 线。同时给模型注册三个工具,quote_get、quote_search、quote_kline。这样在对话里说「帮我查下茅台」,它就会真的去调接口,把实时数据拉回来。
图片
(设置页「行情」自选列表示意)
第一个坑,file: 安装的是快照,不是软链。
我按文档写完代码,打包,然后安装。官方给的是这句。
dsh plugin --profile web add file:$(pwd)装完之后,面板正常出现,自选股能加,价格也刷出来了。看起来一切正常。
然后我开始改代码。改了个样式,重启。
没生效。
再重启一次。还是没生效。
重新打包,还是没生效。清浏览器缓存,硬刷新,依然没生效。
最后我发现,只有重新执行一次 add,重新装一遍,改动才会生效。也就是说,每改一行代码都要重装一次插件再重启,开发基本没法进行。
后来翻了 DSH 源码才明白。file: 后面那个路径,不是把目录软链过去,而是把当前目录的构建产物整个拷贝一份,快照进 profile 里。从那以后,你改的是你的源码,框架跑的是它手里那份旧快照,两者已经没关系了。
正确做法是直接传目录。
dsh plugin --profile web add .多一个点,就会生成 link 软链,profile 里的 node_modules 直接指向你正在开发的目录。改代码,重启,就是最新的。
所以这一条,装目录,别装快照。看到 file: 就避开。
第二个坑,改包名导致插件加载失败。
这个坑是上一个坑的延伸。
我一开始写插件时,包名随手填了个 @your-org/dsh-plugin-quote-cn,占位用。后来想正式一点,把 scope 改成了 @zhuyeqi。
就改了名字。
然后重启,报错。
报错信息是 Cannot find package '@your-org/...'。
我当时很困惑。全项目搜索了一遍,一个 @your-org 都没有。package.json 改了,代码也改了,构建产物也重新出了。这个旧名字到底从哪来的。
又是翻源码才定位到,问题就出在上一个坑的快照上。
因为当初是 file: 安装的,profile 里留着一份旧快照。旧快照里的 cordis.patch.yml,记录的还是旧包名。现在代码改了名,重新打包了,但 profile 里那份快照没变,它还是拿着旧名字去加载,自然找不到。
解法不复杂。把 profile 里那个 scope 目录删掉重装,或者更直接,切到 link 模式。link 模式没有快照这回事,源码什么样,跑的就是什么样。
这两个坑连起来的教训是,工具帮你做的默认行为,一旦和你的直觉不一致,就容易出问题,而且往往在你没想到的地方暴露。
第三个坑,patch 文件重复注入。
这个是我自己造成的。
前面说过,每个插件靠 cordis.patch.yml 声明自己怎么被加载。我装好插件后,看到文档里有一节,讲「如何手工把插件挂进配置」。
我以为是必要步骤,就把 cordis.patch.yml 里的内容又抄了一遍,写进了 profile 的 cordis.patch.yml。
重启,报错,id 重复。
实际上,add 命令已经把 patch 文件自动注入成 bundle 层了。我再抄一遍,同样的 id 出现两次,框架直接报错。
这个坑本身很简单,但挺典型。文档写的是「只有在你禁用了 bundle、想手工挂的时候,才需要下面这段」。我只看到了后面的操作,没看到前面的前提。
很多时候踩坑,不是文档没写,是文档写了,我们只挑了想看的看。后来我在 README 里专门加了一行,不要把 cordis.patch.yml 再抄进 profile 里。因为我知道,下一个自己还会犯这个错。
第四个坑,免费行情源各有短板。
前三个是工程问题,这个是数据源问题。
我一开始想,行情数据网上免费的很多,随便找一个接口就行。雪球、东方财富、新浪、腾讯,总有一个能用。
实际试下来,每个都有问题。
雪球,裸请求直接 403。查了一下,是 IP 被拉黑名单。雪球的接口要先拿 cookie 换 token,而且 token 会过期。就算搞定了,过一段时间也会失效。放弃。
新浪,能用,但字段不完整。我需要的是 PE、PB、市值这些,它要么没有,要么不准。
东方财富,搜索接口好用,K 线也全,但快照字段有缺。
腾讯,快照字段最全,PE、PB、市值、换手率都有,还有独一份的当日分时。但返回的是 GBK 编码,需要自己转码,否则是一堆乱码。
试下来我的结论是,免费行情源没有一个是完整的,各自缺的东西不一样。
所以最后我做了一条降级链。腾讯打头,东方财富补位,新浪兜底。谁挂了或者返回空,就自动切下一个。这样哪怕某一家出问题,面板也不会白屏。
这一条也值得记住,做第三方数据,不要依赖单一上游。
第五个坑,两个小问题。
第一个是热更新。装完插件,会以为能热更新,改代码马上生效。DSH 的 web 端确实有 HMR,但它是精简版,只热重组配置部分。业务代码受 ESM 模块缓存影响,改了还是得重启。所以别指望热更新,老老实实重启,省得白等。
第二个更隐蔽。插件装上了,dump 配置也能看到,但界面上什么都没发生,跟没装一样。
这种症状,多半是 fiber 卡在 PENDING。意思是插件里 inject 声明了要某个服务,但系统里没人提供这个服务,它就一直在那等。
排查办法是用 ctx.registry 枚举 fiber 状态,看它卡在哪一步。这个我也是被「装了跟没装一样」的问题困扰了一阵,才摸到 fiber 这条线。
五个坑讲完了。
回头想,写这个插件的大部分时间,其实没花在写业务代码上,而是花在跟这些看不见的机制较劲。一个安装命令背后的快照逻辑,一个 patch 文件的加载顺序,一个 fiber 的挂起状态。这些东西,文档里要么一笔带过,要么没提。
我也理解了为什么很多开源项目,作者自己用得好好的,别人一上手就出问题。作者脑子里那些理所当然的东西,对新手来说,是一堵堵看不见的墙。
给 AI 写插件这件事,本质就是在给 AI 装手装脚,让它从只会说话,变成能自己动手。这个过程,就是不断去撞那些看不见的墙,再把墙推平。
墙推平了,后来的人就不用再撞。
我把这次撞出来的五面墙,都标记在这里了。
回到开头那件事。
插件跑起来之后,我在对话框里打了句,帮我查下茅台和宁德时代的最新价。
它没有编数字,真的去调了接口,返回了实时价格,还有一张红红绿绿的卡片。
那一刻我觉得,值了。
如果你也在给什么框架写插件,或者正打算写,希望这五个坑能让你少走一点弯路。
作者:KK
夜雨聆风