news 2026/10/17 14:40:08

DeepSeek Harness 进阶——认识DeepSeek Harness插件机制,并从0到1实现一个插件

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
DeepSeek Harness 进阶——认识DeepSeek Harness插件机制,并从0到1实现一个插件

前言

入门篇中笔者已经和大家一起运行起来DeepSeek Harness,并学习了如何配置模型、接入MCP和Skill,还初步了解了 DeepSeek Harness 独特的插件机制。如果大家只是想把它当作 Codex、Claude Code 等智能体工具的日常平替,那么目前掌握的内容已经基本够用。

但如果大家是对 DeepSeek Harness 底层实现机制感兴趣的开发者,或者具备一定前端、 Node.js开发经验、希望进一步深入探索 DeepSeek Harness的大模型爱好者,那么进阶篇的内容就是为大家准备的。笔者会尽量清晰、系统地讲清DeepSeek Harness的核心特性和插件机制。

本期笔者将用通俗易懂的语言拆解DeepSeek Harness的插件机制,厘清插件相关的基本概念,最后实现一个简单插件,并将其挂载到 DeepSeek Harness系统中。大家一起来看看吧~

一、DeepSeek Harness的底座:Cordis框架

首先笔者需要明确,DeepSeek Harness的插件能力并非从零自研,而是建立在Cordis框架之上。Cordis是一个TypeScript编写的插件框架,最初为Koishi聊天机器人服务。

DeepSeek Harness对Cordis的使用非常纯粹:Cordis只负责插件的加载、卸载、依赖管理和生命周期管理,并不关心智能体具体如何工作。而智能体的所有功能包括会话管理、工具使用、智能体循环,甚至UI界面都由插件来实现。

换句话说,Cordis提供的是插件运行底座,而DeepSeek Harness的智能体能力则构建在这个底座之上并通过插件来实现。

二、 从DeepSeek Harness源码目录理解插件机制

要想理解DeepSeek Harness的插件机制在工程上是如何实现的,笔者有必要先简单分析一下它的源码结构。D它的仓库根目录结构如下:

deepseek-harness/ ├── apps/ # 最终可运行的应用 ├── packages/ # dsh 的插件包(主体) ├── vendor/ # Vendored Cordis 源码 ├── python/ # Python SDK 和运行时 ├── native/ # 原生扩展 ├── examples/ # 用户可运行的 cordis.yml 示例 ├── docs/ # 架构、子系统和开发文档 ├── website/ # VitePress 文档站 ├── scripts/ # 构建、检查、生成器 └── .agents/ # Agent 工作流和 Agent Notes

在上述目录中,主体是packages/。该目录下的每个子目录通常是一个能力分组,内部包含一个或多个独立的npm workspace包,包名遵循@deepseek-ai/dsh-<name>规范。更直观地说,这些workspace包就是DeepSeek Harness的一个个插件;它们挂载在Cordis之上,为DeepSeek Harness提供各种能力。

在packages/的插件集合中,最核心的一组位于packages/core,主要包含六个核心npm包(插件):

  • session:事件溯源的会话日志,对应ctx.sessions服务
  • system-prompt:系统提示词组装
  • tools:工具注册表,对应ctx.tools服务
  • agent:Agent 类型与接口定义
  • agent-loop:驱动循环
  • scope:作用域隔离

这六个包可以看作挂载在DeepSeek Harness上的六个核心插件。它们会把自己提供的服务注册到DeepSeek Harness插件体系的共享上下文(context)中,其他插件便可以借助这些服务实现更丰富的能力。packages/core之外的目录通常按能力系列分组,例如llm/、shell/、client/等。这些目录本身通常不包含代码,而是把相关的插件包聚合在一起。

这里再解释一下DeepSeek Harness“一切皆插件”的设计理念到底指什么。除了底层的Cordis框架负责插件的安装与管理之外,其余能力都由插件提供:插件挂载到上下文中并对外提供服务,其他插件再依赖这些服务扩展出更多能力。例如tool-skill相关插件依赖tools、session和skill插件,实现了对Skill技能目录下工具函数的注册与执行。看到这里,相信大家都已经理解DeepSeek Harness插件体系的核心设计理念啦~

三、插件作用的生命周期

通过源码大家已经理解了插件是如何组织的。但还有一个关键问题:大家写好的插件代码,究竟是如何在 DeepSeek Harness上运转起来的? 这就需要理解 DSH 插件体系中最容易混淆的三个概念:插件、Bundle和Profile。

3.1 三者的定位

插件是开发者编写的,通过apply(ctx)函数暴露能力的代码模块。

Bundle是将一个或多个插件源码,连同配置文件一起打包成的npm包,通过package.json中的dsh.bundle字段声明。

Profile是位于$DSH_HOME/profiles/<name>下的组合方案目录,通过package.json中的dsh.profile字段声明的,用来描述该组合方案是由哪些bundle按什么顺序组合的。

3.2 Bundle:将插件代码变为可分发单元

当大家写好一个DeepSeek Harness的插件后,它本身只是一个代码文件,其他用户无法直接安装使用。这时就需要把它打包成 Bundle。

打包的核心是创建一个package.json,并用dsh.bundle字段指向一个配置文件。示例如下:

{"name":"dsh-hello-plugin","version":"0.1.0","type":"module","main":"index.js","files":["index.js","cordis.patch.yml"],"dsh":{"bundle":{"patch":"./cordis.patch.yml"}}}

注意配置文件中的cordis.patch.yml,它可以说是Bundle的灵魂:描述该 Bundle 要向系统配置树中插入哪些插件,通常需要指定插件的id和名称。

# hello-plugin/cordis.patch.yml-insert:-id:helloname:dsh-hello-plugin

3.3 Profile: 运行时的组合方案

Profile 是用户实际启动时的组合方案。大家常用的dsh web命令,本质上就是启动一个名为web的 Profile,其本质是一个目录。

每个Profile目录中都有一个package.json配置文件,其dsh.profile字段列出了该Profile要加载的Bundle列表(按顺序)。当用户启动DeepSeek Harness时,系统会按照这个列表的顺序,依次应用每个 Bundle 的cordis.patch.yml,最终构建出一棵完整的 Cordis 插件树,然后启动运行时。

以下是webProfile 相关的配置文件:

{"name":"dsh-profile-web","private":true,"dsh":{"profile":{"bundles":["@deepseek-ai/dsh-base","@deepseek-ai/dsh-web-app",],"patchReload":"live"}}}

可以看到webprofile组合了两个Bundle。其中一个名为@deepseek-ai/dsh-base,它是多个插件的集合,提供Agent运行所需的核心能力,覆盖大模型、会话、持久化、工具、权限、Skill等基础功能。另一个Bundle名为@deepseek-ai/dsh-web-app,它在基础层之上叠加 Web 相关插件,如web-startup、webserver、web-runtime、api-gateway等,以及浏览器侧所需的前端静态资源。

3.4 插件的完整开发流程

在讲清插件完整开发流程之前,笔者先为大家理清一个容易混淆的点:把插件挂载到配置树中有两种方法。

第一种是patch,用于本地调试,专门快速验证插件源码。

另一种是add,用于打包分发,先把插件打包为 Bundle,然后通过命令将 Bundle 加入目标 Profile,插件才会正式生效。

现在,一个完整的插件开发流程就非常清晰了:

  1. 编写插件。
  2. 使用--patch在本地快速验证插件逻辑。
  3. 将插件打包为 Bundle(包含package.json和cordis.patch.yml)。
  4. 通过dsh plugin --profile <name> add <你的-bundle>命令,将 Bundle 安装到某个 Profile 中。
  5. 启动 Profile,加载插件。

四、实战:写一个带Tool注册的插件

理论部分已经讲完。笔者向来推崇通过实战代码练习来巩固知识。下面就来写一个真正能用的插件:一个可以统计一段文字字符数和单词数的text_stats工具插件。

4.1 环境准备

首先大家先根据 DeepSeek Harness 入门很简单(一)——认识DeepSeek Harness并安装文章,通过源码方式安装好Deepseek Harness。

在仓库根目录下创建一个scratch-plugin/目录,其结构如下:

scratch-plugin/ ├── cordis.yml └── src/ └── my-plugin.ts

4.2text_stats核心代码编写

在my-plugin.ts中编写核心代码。因为需要使用 DeepSeek Harness 的工具功能,所以要引入tools插件提供的能力作为服务。完整代码如下:

importtype{Context}from'@deepseek-ai/cordis'import{defineTool}from'@deepseek-ai/dsh-tools'exportconstname='text-stats'exportconstinject=['tools']exportfunctionapply(ctx:any){console.log('[text-stats] plugin loaded!')ctx.tools.register(defineTool({name:'text_stats',description:'统计一段文字的字符数和单词数',parameters:{text:{type:'string',required:true,description:'要统计的文字',},},output:{schema:{type:'string'},render:(_args:any,value:any)=>[{type:'text',text:valueasstring}],},asyncexecute(args:any){consttext=args.textconstcharCount=text.lengthconstwordCount=text.trim().split(/\s+/).filter(Boolean).lengthreturn`字符数:${charCount},单词数:${wordCount}`},}asany),)}

以上代码有几个需要注意的地方:

  1. inject = ['tools']声明该插件依赖ctx.tools服务。如果tools服务尚未就绪,apply不会执行,插件会一直等待。
  2. ctx.tools.register()把工具注册进ToolsService。该注册通过ctx.effect()管理,插件卸载时工具会自动消失。有了ctx.effect(),开发者就不需要在代码层面过多关心插件卸载和副作用撤销。
  3. defineTool来自@deepseek-ai/dsh-tools,它帮大家把工具定义转换成ToolsService能识别的 schema 格式,并定义输入参数、输出格式和执行函数。

完成了源码编写之后,下面来编写cordis.yml配置文件,内容如下,包含插件的id和名称,注意这里的name必须是绝对路径:

-insert:-id:test-stats name:'/home/sandui/deepseek-harness/scratch-plugin/src/my-plugin.ts'

4.3patch插件调试

下面使用patch方法启动并验证。在deepseek-harness仓库根目录下执行以下命令:

pnpmdsh web--patch./scratch-plugin/cordis.yml

执行结果如下,可以看到插件已被正确加载:

接下来测试插件功能,输入以下提示词:

请统计如下内容的字数: 本文从零搭建完整加工流水线:MinerU 统一解析为 Markdown,多模态模型为图片生成描述,再经去重、质量过滤、隐私脱敏三道清洗工序,带大家手把手把“原料”炼成可直接用于大模型训练的干净数据集。

输出结果如下图所示,DeepSeek Harness会在可用工具列表中发现text_stats,传入原文,再按照规定的格式返回结果。可以看到 Tool call 日志里出现text_stats,说明“插件加载 → 工具注册 → 模型发现 → 调用执行”的整个链路已经走通。

到这里,插件的基本代码开发已经完成,并通过--patch完成了初步调试。但这种方式只适合开发阶段。如果想分享插件或在正式Profile中长期使用,就需要把它打包成Bundle,走add路径。

4.4 插件打包为bundle

Cordis 插件机制加载 Bundle 时,默认会加载编译后文件夹下的index.js。所以首先将my-plugin.ts改名为index.ts,同时按规范将cordis.yml改名为cordis.patch.yml。

修改 Bundle 的cordis.patch.yml中的内容。这里的name不再是绝对路径,而是包名dsh-text-stats。打包分发后,Node 的模块解析器会从相关链接中查找该包,而不是直接从文件系统路径查找:

-insert:-id:text-statsname:dsh-text-stats

然后在scratch-plugin/目录下安装依赖:

pnpmadd@deepseek-ai/cordis @deepseek-ai/dsh-tools typescript

在scratch-plugin/目录下新建tsconfig.json文件,用于把TypeScript代码编译为可执行的 JavaScript 代码。如果学习过TS开发,这部分会非常熟悉:

{"compilerOptions":{"target":"ESNext","module":"ESNext","moduleResolution":"Bundler","outDir":"./dist","rootDir":"src","strict":true,"esModuleInterop":true,// 关键!补齐内置API类型 WeakRef、Symbol.dispose、ErrorOptions"lib":["ES2021","DOM"],// 跳过外部库的类型检查(兜底,解决cosmokit Volatile 类型缺失)"skipLibCheck":true},"include":["src/**/*.ts"],"exclude":["dist"]}

在scratch-plugin/目录下执行命令pnpm tsc编译代码。执行完成后,该目录下会生成dist/index.js文件:

在scratch-plugin/目录下新建package.json,配置如下,其中的关键字段是dsh.bundle.patch,它告诉DeepSeek Harness:“这个包贡献一个配置层,层的内容在cordis.patch.yml里。”files字段用于控制打包内容:

{"name":"dsh-text-stats","version":"0.1.0","type":"module","main":"dist/index.js","files":["dist","cordis.patch.yml"],"dsh":{"bundle":{"patch":"./cordis.patch.yml"}}}

在scratch-plugin/目录下执行如下命令:

dsh plugin--profilewebadd.

这条命令做了三件事:

  1. 把包作为依赖安装到webProfile 的目录中;
  2. 把dsh-text-stats追加到该 Profile 的dsh.profile.bundles列表;
  3. 下次启动该Profile时,该插件的cordis.patch.yml会被应用,插件正式生效。

下面验证该Bundle是否已经挂载成功。首先执行以下命令,查看deepseek harness web的配置树里有没有这个插件行:

dsh--profileweb --dump-config|greptext-stats

dsh-text-stats出现在最终配置树里,说明add成功。

然后打开Web再测一次:请帮我统计下面这句话有多少个字符和多少个单词:Everything is a Plugin。结果依然正确。

pnpmdsh web


到这里一个完整的插件就完成了从开发、调试、打包到安装验证的全过程。如果大家希望把这个插件分享给其他人使用,只需将dsh-text-stats这个包发布到 npm 平台(例如在scratch-plugin/目录下执行npm publish)。发布成功后,其他用户就可以通过dsh plugin --profile <name> add dsh-text-stats命令,将该插件安装到自己的Profile中。这样插件就从本地开发走向了真正的公开分发。

五、小结

本篇文章笔者先从Cordis的插件机制讲起,认识了DeepSeek Harness的插件底座Cordis;接着解析了源码目录,理解了packages/core/六个核心服务包和packages/按能力分组的组织方式;然后厘清了插件、Bundle、Profile 三者的定位,以及--patch本地调试和add打包分发两条挂载路径,也明白了dsh web本质上是组合dsh-base与dsh-web-app两个 Bundle 的内置 Profile;最后通过一个带 Tool 注册的插件,走通了“插件加载 → 服务注入 → 工具注册 → 模型调用”的完整链路。至此,从底层机制到工程实现,再到实战验证,DeepSeek Harness插件体系的核心脉络就全部串起来了。

如果大家觉得有收获,欢迎点赞、收藏,关注我的专栏《DeepSeek Harness 从入门到进阶》,后续更新不错过。下篇见!更多精彩内容,如果大家感兴趣,欢迎关注笔者的CSDN账号与专栏。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 14:38:49

Keras-BERT实现英文序列标注:轻量级工程落地方案

1. 项目概述&#xff1a;为什么序列标注是NLP落地的“基本功”&#xff0c;而keras-bert是轻量级工程化的务实选择你有没有遇到过这样的场景&#xff1a;写好一个英文句子&#xff0c;系统却只能告诉你“这是个句子”&#xff0c;却无法指出“Apple”是公司名、“2023”是时间、…

作者头像 李华
网站建设 2026/10/17 14:38:44

DMAD开源:4步蒸馏统一视频与原生音频生成

最近看到 DMAD 开源的动静&#xff0c;我第一时间把这条消息里里外外捋了一遍。这套方案把 MiniMax-H3 蒸馏到了 4 步&#xff0c;推理时一次生成视频和原生音频。翻译成大白话&#xff1a;你丢给它一句提示词&#xff0c;它不光是出画面&#xff0c;连人声、环境音、语气节奏、…

作者头像 李华
网站建设 2026/10/17 14:37:04

需求分析+思维导图:从需求拆解到PRD的完整实操指南

1. 为什么我把“需求分析”和“思维导图”绑在一起做需求分析这行越久&#xff0c;我越发现一个尴尬的事实&#xff1a;需求文档写得再工整&#xff0c;客户和开发团队照样能对着同一份文档&#xff0c;理解出两个完全不同的产品。问题通常不在文档格式&#xff0c;而在需求拆解…

作者头像 李华
网站建设 2026/10/17 14:35:23

8位量化如何缓解灾难性遗忘:持续学习中的隐式正则化

说实话&#xff0c;我第一次看到这个标题的时候&#xff0c;是带着一点怀疑点的。持续学习&#xff08;Continual Learning&#xff09;和灾难性遗忘&#xff08;Catastrophic Forgetting&#xff09;这摊子事&#xff0c;我自己断断续续折腾了两三年&#xff0c;试过重放、正则…

作者头像 李华
网站建设 2026/10/17 14:35:03

矩阵跃动+GEO:国产AI搜索优化与多平台内容占位实操指南

做内容流量这行的都知道&#xff0c;过去一年最大的变数就是AI搜索开始重新分配入口。以前用户搜东西&#xff0c;第一屏基本是百度百家号、知乎、CSDN这些老面孔的天下&#xff1b;现在你再搜一个问题&#xff0c;最上面那段直接是AI生成的答案&#xff0c;下面才轮到传统网页…

作者头像 李华
网站建设 2026/10/17 14:34:41

开源图像模型2512版实战:从本地部署到选型对比的全方位指南

国产大模型的这波迭代&#xff0c;最让我意外的不是又卷出了多高的榜单分数&#xff0c;而是某头部云厂商终于把压箱底的视觉生成模型彻底放开了。上个月看到那款代号2512的开源图像模型正式挂出仓库时&#xff0c;我第一反应是“这波诚意有点足”——完整权重、推理脚本、微调…

作者头像 李华