DSH plugin 能力拆三个包:Service Definition、Provider 与 Consumer
DSH plugin 里一项足够通用的能力,官方推荐拆成三个角色:Service Definition 定义契约、Service Provider 实现能力、Consumer 把能力暴露出去。 这不是为了架构好看,而是为了让「换实现」只改一行配置——调用方只认 Definition,所以 Provider 换成另一个包时,Definition 和 Consumer 都不用动。判断该不该拆的标准也很清楚:三个角色是否需要独立演进。
不需要,就放一个包里;需要,才拆。以 Bash 能力为例,dsh-shell 是定义、dsh-bash-local 是提供方、dsh-tool-bash 是消费方——三者构成一个 seam,少了任何一个都不完整。
DSH plugin 能力的三种角色:Definition、Provider 与 Consumer
一句话结论:Definition 管契约、Provider 管实现、Consumer 管暴露——三者依赖单向,Provider 与 Consumer 互不依赖。
| 角色 | 职责 | 依赖谁 | 例(Bash 能力) |
|---|---|---|---|
| Service Definition | 定义服务与请求/结果类型 | 无 | dsh-shell |
| Service Provider | 真正执行能力 | Definition | dsh-bash-local |
| Consumer | 暴露为工具 / 命令 | Definition | dsh-tool-bash |
- 调用方只认契约:Consumer 通过
inject: ['shell']拿到服务,不关心背后是本地执行还是远程执行。 - 替换只发生在组合层:换 Provider 就是改
cordis.yml里的一行,Definition 与 Consumer 原样不动。 - 三角色不等于一个包:角色是逻辑分工,物理上可以一个包承担多个角色,也可以拆成三个包。
为什么要把 DSH plugin 能力拆成三个包
一句话结论:三个好处——提供方可替换、三角色可独立演进、依赖解耦。
- 提供方可替换:同一个 Definition 可以有多个 Provider,通过组合配置选择。换实现时调用约定不变。
- 独立演进:Definition 一旦被调用方依赖就很少改;Provider 可以独立优化性能与安全;Consumer 可以调整能力向模型呈现的方式。
- 依赖解耦:Provider 与 Consumer 互不依赖,任何一方改动都不会牵动另一方。
- 诊断更清晰:出问题时先定位是哪一角色坏了——契约不符、实现异常、还是暴露方式不对。
实战:三步写出一项 DSH plugin 能力
一句话结论:先写 Definition(含 Request/Result 类型),再写 Provider,最后写 Consumer,三步的顺序不能反。
- 写 Service Definition:
// packages/my-cap/my-cap/src/index.ts
import { Service, type Context } from '@deepseek-ai/cordis'
declare module '@deepseek-ai/cordis' {
interface Context { myCap: MyCapService }
}
export abstract class MyCapService extends Service {
constructor(ctx: Context) { super(ctx, 'myCap') }
abstract execute(request: MyCapRequest): Promise<MyCapResult>
}
export interface MyCapRequest { input: string }
export interface MyCapResult { output: string }
- 写 Service Provider:
import type { Context } from '@deepseek-ai/cordis'
import { MyCapService, type MyCapRequest, type MyCapResult } from '@deepseek-ai/dsh-my-cap'
class MyCapLocal extends MyCapService {
async execute(request: MyCapRequest): Promise<MyCapResult> {
return { output: request.input.toUpperCase() }
}
}
export const name = 'my-cap-local'
export function apply(ctx: Context) { ctx.plugin(MyCapLocal) }
- 写 Consumer(以工具形式暴露):
import { defineTool } from '@deepseek-ai/dsh-tools'
export const name = 'tool-my-cap'
export const inject = ['tools', 'myCap']
export function apply(ctx: Context) {
ctx.tools.register(defineTool({
name: 'my_cap',
description: 'Execute my capability.',
parameters: { input: { type: 'string', required: true } },
output: { schema: { type: 'string' }, render: (_a, v) => [{ type: 'text', text: v }] },
async execute(args) { return (await ctx.myCap.execute({ input: args.input })).output },
}))
}
- 在组合里挂两条:
- name: '@deepseek-ai/dsh-my-cap-local'
- name: '@deepseek-ai/dsh-tool-my-cap'
- 验证替换性:把 Provider 换成另一个实现,只改第一行;Consumer 与工具名保持不变,模型侧调用方式完全不变。
DSH plugin 三角色拆分的边界:什么时候不要拆
一句话结论:不要预防性拆分——只有角色需要独立演进时,才值得付出多包的维护成本。
- 一个简单工具插件不需要拆:它没有可替换的实现,拆开只会增加发布与依赖管理成本。
- 只有一个 Provider 且短期不会换:Definition 与 Provider 放一个包更省事。
- 组合数超过收益:包越多,安装、版本对齐、排查的成本越高;先单包跑通,再按真实需求拆。
- 拆分的信号:出现第二个 Provider 的需求、或 Consumer 需要被多个场景复用时,才动手拆。
- 另一种信号:团队边界——不同人维护不同角色时,拆包能让发布节奏互不阻塞。
DSH plugin 组合与替换:seam 在配置层收口
一句话结论:三角色的组合发生在组合配置里,替换 Provider 是配置改动而不是代码改动。
- 按包名挂载:
cordis.yml里按顺序列 Provider 与 Consumer。 - 换实现只换一行:注释掉旧 Provider、换上新的同契约包即可。
- 验证:替换后调用一次该工具,确认结果仍符合 Definition 的 Result 形状。
- 别忘了能力 seam 参考:内置能力已经有现成 seam 划分,扩展前先看它,避免重复造一套。
DSH plugin 排查注意事项
一句话结论:三角色架构的问题先问「哪一层坏了」,再问「代码哪里错了」。
- 调用报「服务不存在」 → 检查 Provider 是否挂载、
inject是否写对。 - 类型不符 → 检查 Request/Result 类型是否只有 Definition 一处定义。
- 换了 Provider 行为变了 → 说明实现违反了契约,回到 Definition 对齐。
- Consumer 里直接 new 了实现 → 破坏了依赖方向,替换 Provider 时会失效。
- 过早拆分 → 先单包跑通,再按真实变化拆。
- 修复状态如实:三角色定义、依赖方向与三步示例均来自官方「能力的三种角色设计」页面;
defineTool的完整字段与Service基类签名以官方 reference 与你所用版本为准。
来源:能力的三种角色设计、服务与依赖、能力 seam 参考。
常见问题
DSH plugin 的三角色分工是:Service Definition 定义服务与请求/结果类型,Provider 实现这项能力,Consumer 把它暴露给模型或其它插件。调用方只依赖 Definition,所以更换 Provider 时 Definition 与 Consumer 都不用改。
DSH plugin 拆成三个包的前提是角色需要独立演进或替换;只为了「看起来架构清晰」而拆是过度设计。判断标准很简单:如果 Provider 与 Consumer 不会各自变化,放一个包里更好维护。
DSH plugin 更换 Provider 通常只改组合配置里的一行,Definition 与 Consumer 保持不变。这也正是把三者拆开的核心收益:替换实现不影响调用约定。
DSH plugin 的 Service Definition 拥有 Request 与 Result 类型,Provider 和 Consumer 都只依赖这个包。这样类型只有一个来源,避免两端各自定义导致字段漂移。
DSH plugin 的三角色依赖是单向的:Provider 依赖 Definition,Consumer 同样依赖 Definition,而 Provider 与 Consumer 互不依赖。这种方向保证替换任一角色都不会牵动另一个。
相关术语
- Service Definition(服务定义)
- Service Definition 是 DSH plugin 能力的契约包,定义 Cordis 服务接口以及请求与结果类型。Provider 与 Consumer 都只依赖它,因此它是三者中变化最慢的一层。— https://deepseek-harness.github.io/deepseek-harness/develop/practice/
- Service Provider(服务提供方)
- Service Provider 是 DSH plugin 能力的实现包,负责真正执行请求。同一个 Service Definition 可以有多个 Provider,通过组合配置选择其中一个。— https://deepseek-harness.github.io/deepseek-harness/develop/practice/
- Consumer(消费方)
- Consumer 是把 DSH plugin 能力暴露给模型或其它调用方的包,通常以工具或命令的形式出现。它只依赖 Service Definition,不直接依赖 Provider。— https://deepseek-harness.github.io/deepseek-harness/develop/practice/
- capability seam(能力接缝)
- capability seam 是一项能力的三个角色共同构成的整体边界,任何单一角色都不等于 seam。它决定了这项能力在哪些位置可以替换实现。— https://deepseek-harness.github.io/deepseek-harness/reference/capability-seams
来源
- DeepSeek Harness 文档 — 能力的三种角色设计(Definition / Provider / Consumer)· deepseek-harness.github.io
- DeepSeek Harness 文档 — 服务与依赖(ctx.plugin、inject 与服务注册)· deepseek-harness.github.io
- DeepSeek Harness 文档 — 能力 seam 参考· deepseek-harness.github.io