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它的仓库根目录结构如下:

bash 复制代码
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 字段指向一个配置文件。示例如下:

json 复制代码
{
  "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和名称。

yaml 复制代码
# hello-plugin/cordis.patch.yml
- insert:
    - id: hello
      name: 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 插件树,然后启动运行时。

以下是 web Profile 相关的配置文件:

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

可以看到web profile组合了两个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/ 目录,其结构如下:

text 复制代码
scratch-plugin/
├── cordis.yml
└── src/
    └── my-plugin.ts

4.2 text_stats核心代码编写

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

typescript 复制代码
import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'

export const name = 'text-stats'
export const inject = ['tools']

export function apply(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: value as string }],
      },
      async execute(args: any) {
        const text = args.text
        const charCount = text.length
        const wordCount = text.trim().split(/\s+/).filter(Boolean).length
        return `字符数: ${charCount},单词数: ${wordCount}`
      },
    } as any),
  )
}

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

  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必须是绝对路径:

python 复制代码
- insert:
    - id: test-stats
      name: '/home/sandui/deepseek-harness/scratch-plugin/src/my-plugin.ts'

4.3 patch插件调试

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

bash 复制代码
pnpm dsh web --patch ./scratch-plugin/cordis.yml

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

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

text 复制代码
请统计如下内容的字数: 本文从零搭建完整加工流水线: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 的模块解析器会从相关链接中查找该包,而不是直接从文件系统路径查找:

yaml 复制代码
- insert:
    - id: text-stats
      name: dsh-text-stats

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

bash 复制代码
pnpm add @deepseek-ai/cordis @deepseek-ai/dsh-tools typescript

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

json 复制代码
{
  "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 字段用于控制打包内容:

json 复制代码
{
  "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/目录下执行如下命令:

bash 复制代码
dsh plugin --profile web add .

这条命令做了三件事:

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

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

bash 复制代码
dsh --profile web --dump-config | grep text-stats

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

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

bash 复制代码
pnpm dsh web

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

以上就是本篇文章的全部内容,本期内容完整源代码大家可关注笔者的同名微信公众号大模型真好玩 ,并私信dsh插件即可获得。

五、小结

本篇文章笔者先从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 从入门到进阶》,后续更新不错过。下篇见!更多精彩内容,如果大家感兴趣,欢迎关注笔者的掘金账号与专栏,也可关注笔者的同名微信公众号大模型真好玩,每期分享涉及的代码均可在公众号私信。

相关推荐
一切皆是因缘际会1 小时前
同源世界物理系统
人工智能·ai·同源世界·同源系统
ar01231 小时前
AR眼镜设备管网巡检:从人工看管线走向AI空间智能巡检
人工智能·ar
howdoyoudo2026061 小时前
AI审计手记 #30:从“政府网站”到“商业平台”——腾讯云沙箱AI代理越界事件的审计分析
大数据·人工智能·安全·机器学习·语言模型·云计算·腾讯云
产品设计大观1 小时前
AI生成冷链仓储温控异常处置后台实战:从告警到质控复核的完整案例
人工智能
data analyse 4561 小时前
页面卸载时埋点上报总丢失?sendBeacon 上报与补偿方案
大数据·运维·服务器·前端·数据库·人工智能·数据分析
企查查数据服务2 小时前
充电桩集采准入:用企业数据做供应商失信批量核验
大数据·数据库·人工智能
xier_ran2 小时前
【infra之路】AI 编译器学习笔记(八):向量化——把小块变成 SIMD 指令
人工智能·笔记·学习
·云扬·2 小时前
大模型幻觉:为什么会编造,如何缓解
人工智能·ai
chaoyuanl2 小时前
超元力VR大空间CS项目落地解决方案:五大选购要点,开店全程扶持
大数据·人工智能·vr