自学教程

DeepSeek Harness 能力三角色

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 工具插件。

四、三角色模式带来的架构收益

  1. Provider 可替换:同一套接口契约,可以切换本地实现、容器实现、远程服务实现;上层工具、模型侧零修改。
  2. 模块独立演进
    • Definition 契约尽量稳定,减少破坏性变更;
    • Provider 团队专注性能、隔离、安全;
    • Consumer 团队专注模型体验:优化工具描述、输出渲染,不触碰底层执行逻辑。
  3. 依赖彻底解耦:Provider 与 Consumer 互不导入,全部依赖抽象 Service Definition,依靠 Cordis 的服务注入完成协作,实现控制反转 IoC。
  4. 统一治理拦截:所有调用走统一服务契约,可以在服务层增加权限校验、日志埋点、限流,对所有 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 插件包,即可联调。

六、开发设计约束与最佳实践

  1. Definition 只定义接口、数据类型、抽象类,禁止写业务执行逻辑。
  2. Provider 继承抽象服务基类,完成真实业务实现,向 ctx 注册服务。
  3. Consumer 使用 inject 声明依赖服务标识,只通过上下文访问服务实例,禁止硬编码导入 Provider。
  4. 不要做预防性拆包:只有明确未来存在多实现的场景,才拆分为多个 npm 包。
  5. 同一时间,一个 Service Definition 只允许激活一个 Service Provider,多个 Provider 会互斥,后加载覆盖先加载。
  6. 区分两套三角色概念:
    • 业务视角:决策者/调度者/执行者,描述一次 Agent 任务业务流转;
    • 架构视角:Definition/Provider/Consumer(Seam),描述一项系统能力的插件拆分方案。

本篇小结

Capability Seam(能力接缝)三角色模式 是 DeepSeek Harness「一切皆插件,一切皆可替换」理念的关键落地范式。

Service Definition 负责定契约,保证接口稳定统一;Service Provider 负责实现底层,支持多种实现自由切换;Consumer 负责面向模型,封装成 Agent 可调用工具。三者配合,实现上层业务与底层执行完全解耦。

理解这套模式,在开发复杂插件时可以避免写出高耦合单体工具,构建可测试、可替换、适合企业生产环境的智能体能力。

标签:

0 条笔记