DeepSeek Harness 能力三角色:Service Definition / Service Provider / Consumer(能力接缝 Seam)
在 DeepSeek Harness 的 Cordis 微内核体系中,有一套被称为Three‑Role Capability Pattern(三角色能力模式)的官方架构规范,完整的可替换能力被称作Capability Seam(能力接缝)。
一套完整的 Seam 必须同时包含:Service Definition(服务定义)、Service Provider(服务提供方)、Consumer(消费方),三者缺一不可;单独某一个角色,不能构成完整能力接缝。
该模式解决传统 Agent 框架的强耦合痛点:工具契约、底层执行实现、模型调用逻辑混杂在一起,更换沙箱、更换后端时需要大面积修改工具代码、Prompt、参数逻辑。借助三角色模式,仅替换 Provider,上层契约与面向模型的工具完全不用改动。
一、三个角色核心定义
| 角色 | 中文名称 | 核心定位 | 关键特征 |
|---|---|---|---|
| Service Definition | 服务定义 | 定义抽象接口契约、请求/响应数据类型,抽象服务基类 | 只定义“能力是什么”,不写业务实现;版本尽量稳定,很少变更 |
| Service Provider | 服务提供方 | 继承抽象服务,编写具体底层实现,向上下文注册服务实例 | 定义“底层怎么跑”;同一个 Definition 允许多套 Provider,运行时切换;同一时刻只能激活一个 Provider |
| Consumer | 消费方 | 将服务封装为模型可调用 Tool,对接 Agent 循环 | 面向大模型,定义工具描述、参数、输出渲染;完全不感知底层 Provider 的具体实现 |
依赖铁律:
- Service Provider 只依赖 Service Definition
- Consumer 只依赖 Service Definition
- Provider 和 Consumer 互相不能直接 import、不能硬编码依赖,全部通过 Cordis 的服务注入
inject:[]获取实例
不是所有插件都必须拆分为三个独立 NPM 包。官方明确准则:只有该能力需要多套底层实现、需要独立迭代演进,才拆分为不同包;简单一次性业务工具,可在同一个包内同时实现三个角色。
二、官方示例:Bash 命令执行能力(Seam)
Bash 执行是 Harness 最经典的能力接缝案例,拆分为三个独立包:
@deepseek‑ai/dsh‑shell— Service Definition:定义 shell 服务抽象接口、命令入参、执行结果类型;无任何执行逻辑。@deepseek‑ai/dsh‑bash‑local— Service Provider:本地子进程执行 bash;还可以替换为dsh‑bash‑docker、远程沙箱 Provider。@deepseek‑ai/dsh‑tool‑bash— Consumer:包装成模型可调用的execute_bash工具,定义工具描述、参数,内部通过inject: ['shell']拿到服务实例调用执行能力。
切换执行环境,仅修改 cordis.yml 更换 Provider,Definition 和 Consumer 完全不变:
# 使用本地 bash plugins: - name: '@deepseek‑ai/dsh‑tool‑bash' - name: '@deepseek‑ai/dsh‑bash‑local'切换为 docker 沙箱,只替换 Provider 一行即可 plugins: - name: '@deepseek‑ai/dsh‑tool‑bash' - name: '@deepseek‑ai/dsh‑bash‑docker'
更换提供方之后,模型调用工具的方式、参数、返回格式完全不变。文件系统、PTY、LSP 会跟随 Provider 整体迁移到沙箱环境,不需要修改多个插件配置。
三、实战开发:从零实现一套完整 Seam
我们实现一套 AST 代码静态解析能力,完整演示 Service Definition、Provider、Consumer 的编写规范,参考官方示例代码。
步骤1:编写 Service Definition(dsh‑ast‑parser)
职责:定义请求、返回 TS 类型、抽象服务基类;扩展 Cordis Context 类型声明;不引入重型第三方库,保持包轻量。
import { Service, type Context } from '@deepseek‑ai/cordis';// 请求结构体
export interface ParseRequest {
code: string;
language: 'typescript' | 'javascript' | 'python';
} // 返回结构体
export interface ParseResult {
astNodeCount: number;
functions: string[];
imports: string[];
hasSyntaxErrors: boolean;
} // 扩展上下文类型,让 ctx.astParser 获得类型提示
declare module '@deepseek‑ai/cordis' {
interface Context {
astParser: AstParserService;
}
} // 抽象服务基类,只声明抽象方法,不实现逻辑
export abstract class AstParserService extends Service {
constructor(ctx: Context) {
super(ctx, 'astParser');
}
abstract parse(req: ParseRequest): Promise<ParseResult>;
}
步骤2:编写 Service Provider(dsh‑ast‑local)
继承抽象服务,实现 parse 业务逻辑;将服务注册进 Cordis 上下文。重型依赖(Babel、编译器SDK)全部放在 Provider 包,不污染 Definition。
import type { Context } from '@deepseek‑ai/cordis'; import { AstParserService, ParseRequest, ParseResult } from 'dsh‑ast‑parser';// 实现抽象服务
export class LocalAstProvider extends AstParserService {
async parse(req: ParseRequest): Promise<ParseResult> {
// 实际项目此处接入 babel / typescript compiler
const funcMatches = req.code.match(/function\s+(\w+)/g) ?? [];
return {
astNodeCount: req.code.length,
functions: funcMatches.map(f => f.replace('function ','')),
imports: [],
hasSyntaxErrors: false
};
}
} export const name = 'dsh‑ast‑local';
export function apply(ctx: Context) {
// 将实现注册进上下文,对外提供 astParser 服务
ctx.plugin(LocalAstProvider);
}
步骤3:编写 Consumer(dsh‑tool‑ast)
消费方,通过 inject 声明依赖服务标识,使用 defineTool 封装为模型工具;不 import Provider 实现类,只依赖定义。
import type { Context } from '@deepseek‑ai/cordis';
import { defineTool } from '@deepseek‑ai/dsh‑tools';export const name = 'dsh‑tool‑ast';
// 声明依赖服务标识,内核保证服务就绪再加载此插件
export const inject = ['tools', 'astParser'];
export function apply(ctx: Context) {
ctx.tools.register(defineTool({
name: 'analyze_code_ast',
description: '静态分析代码,提取函数、导入,检查语法错误。',
parameters: {
code: { type: 'string', required: true, description: '待分析的代码片段' },
language: { type: 'string', required: true, description: '代码语言' }
},
output: {
render: (_args, result) => [
{ type: 'text', text: 识别函数:${result.functions.join(',')} }
]
},
async execute(args) {
// 通过上下文获取服务实例,完全不关心底层是哪个 Provider
return await ctx.astParser.parse({
code: args.code,
language: args.language
});
}
}));
}
步骤4:在 cordis.yml 组合整套能力接缝
plugins: - name: dsh‑ast‑local # Provider 实现 - name: dsh‑tool‑ast # Consumer 面向模型工具
内核读取配置,依靠 inject 做依赖拓扑排序,保证 astParser 服务就绪后,再加载 Consumer 工具插件。
四、三角色模式带来的架构收益
- Provider 可替换:同一套接口契约,可以切换本地实现、容器实现、远程服务实现;上层工具、模型侧零修改。
- 模块独立演进
- Definition 契约尽量稳定,减少破坏性变更;
- Provider 团队专注性能、隔离、安全;
- Consumer 团队专注模型体验:优化工具描述、输出渲染,不触碰底层执行逻辑。
- 依赖彻底解耦:Provider 与 Consumer 互不导入,全部依赖抽象 Service Definition,依靠 Cordis 的服务注入完成协作,实现控制反转 IoC。
- 统一治理拦截:所有调用走统一服务契约,可以在服务层增加权限校验、日志埋点、限流,对所有 Provider 统一生效。
五、常见问题 FAQ(官方整理)
Q:所有工具都要拆成三个独立包吗?
A:不需要。只有能力需要多种底层实现、需要独立迭代演进时才拆包;普通业务一次性工具,单包内实现全部角色即可,不要过度设计做预防性拆分。
Q:为什么 Consumer 不能直接 import Provider 的类?
A:一旦直接 import,就产生物理硬依赖。后续在配置文件更换 Provider 包时会出现代码层面报错,破坏可替换的控制反转机制。Consumer 只能通过 inject + ctx.xxx 获取服务实例。
Q:Service Definition 包可以放重型第三方依赖吗?
A:强烈不建议。Definition 只放类型定义、抽象类;Babel、Docker‑SDK 这类重型库全部下沉到 Provider 包,避免所有使用者被动引入大量依赖。
Q:开发调试整套三角色能力怎么做?
A:monorepo 工程下,使用 npm link / pnpm workspace 本地链接;在测试 Profile 的 cordis.yml 同时注册 Provider、Consumer 插件包,即可联调。
六、开发设计约束与最佳实践
- Definition 只定义接口、数据类型、抽象类,禁止写业务执行逻辑。
- Provider 继承抽象服务基类,完成真实业务实现,向 ctx 注册服务。
- Consumer 使用
inject声明依赖服务标识,只通过上下文访问服务实例,禁止硬编码导入 Provider。 - 不要做预防性拆包:只有明确未来存在多实现的场景,才拆分为多个 npm 包。
- 同一时间,一个 Service Definition 只允许激活一个 Service Provider,多个 Provider 会互斥,后加载覆盖先加载。
- 区分两套三角色概念:
- 业务视角:决策者/调度者/执行者,描述一次 Agent 任务业务流转;
- 架构视角:Definition/Provider/Consumer(Seam),描述一项系统能力的插件拆分方案。
本篇小结
Capability Seam(能力接缝)三角色模式 是 DeepSeek Harness「一切皆插件,一切皆可替换」理念的关键落地范式。
Service Definition 负责定契约,保证接口稳定统一;Service Provider 负责实现底层,支持多种实现自由切换;Consumer 负责面向模型,封装成 Agent 可调用工具。三者配合,实现上层业务与底层执行完全解耦。
理解这套模式,在开发复杂插件时可以避免写出高耦合单体工具,构建可测试、可替换、适合企业生产环境的智能体能力。
0 条笔记