乐于分享
好东西不私藏

给 DeepSeek 写第一个插件,我踩的 5 个坑

给 DeepSeek 写第一个插件,我踩的 5 个坑

给 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

#DeepSeek #dsh