DeepSeek Harness: 能力三角色
最后更新:2026-08-31
Cordis 的能力系统是"一切皆插件"的底层支撑。它将一个功能拆成三个角色:定义接口的人、实现接口的人、使用接口的人。这种分离让替换实现变得像换电池一样简单——拔掉旧的,插上新的,系统正常运行。
📋 前置知识:已完成 19-service.md,理解 Service 基类
1. 你将学到
- Definition:声明接口
- Provider:实现接口
- Consumer:使用接口
- seam(接缝)概念
- 能力注册图
- 替换 Provider = 替换整个产品行为
2. 能力系统概述
(1) 为什么需要三角色
没有能力系统时,功能直接硬编码在代码中:
// ❌ 硬编码实现
class FileAnalyzer {
analyze(path: string) {
const stat = fs.statSync(path) // 只能用本地文件系统
return { size: stat.size, type: 'local' }
}
}
有了能力系统,功能被拆成三层:
// ✅ 三角色分离
// Definition: 声明接口
interface FileStats {
getSize(path: string): Promise<number>
getType(path: string): Promise<string>
}
// Provider: 实现(本地)
class LocalFileStats implements FileStats { ... }
// Provider: 实现(远程沙箱)
class SandboxFileStats implements FileStats { ... }
// Consumer: 使用(不关心具体实现)
class FileAnalyzer {
constructor(private stats: FileStats) {}
analyze(path: string) {
const size = await this.stats.getSize(path)
return { size }
}
}
(2) ▶ 示例 2
graph LR
DEF[Definition<br/>声明接口] --> PROV[Provider<br/>实现接口]
PROV --> CON[Consumer<br/>使用接口]
CON --> DEF
(3) 类比
| 角色 | 类比 | 现实对应 |
|---|---|---|
| Definition | 电源插座标准 | 国标插座规格 |
| Provider | 插座实现 | 墙上的插座 |
| Consumer | 用电设备 | 电视机、冰箱 |
电视机不关心电从哪个发电站来,只关心插座符合标准。同样,Consumer 不关心 Provider 是谁,只关心 Definition 定义的接口。
3. Definition:声明接口
(1) 定义能力
Definition 声明能力的接口——不包含实现,只描述"这个能力能做什么":
import { defineCapability } from '@deepseek-ai/cordis'
export const FileStats = defineCapability({
name: 'file-stats',
description: 'File statistics and metadata access',
interface: {
getSize(path: string): Promise<number>
getType(path: string): Promise<string>
exists(path: string): Promise<boolean>
list(dir: string): Promise<string[]>
}
})
(2) Definition 的要素
| 要素 | 说明 |
|---|---|
name |
能力标识,全局唯一 |
description |
能力描述 |
interface |
TypeScript 接口定义 |
(3) 为什么分开定义
Definition 独立于 Provider 的好处:
- 类型约束:Provider 必须实现 interface 的所有方法
- 文档价值:Definition 就是能力的使用文档
- 可替换性:只要新 Provider 满足 interface,就能替换
- 编译检查:TypeScript 确保接口一致
(4) 约定
Definition 通常放在独立文件中,与 Provider 分离:
capabilities/
├── file-stats/
│ ├── definition.ts ← Definition
│ ├── local.ts ← Provider (本地实现)
│ └── sandbox.ts ← Provider (沙箱实现)
4. Provider:实现接口
(1) 实现能力
Provider 实现 Definition 声明的接口:
import { FileStats } from './definition'
export default class LocalFileStatsProvider extends Service {
static inject = ['fs']
constructor(ctx: Context) {
super(ctx, 'file-stats')
ctx.implement(FileStats, {
async getSize(path: string) {
const stat = await ctx.fs.stat(path)
return stat.size
},
async getType(path: string) {
const stat = await ctx.fs.stat(path)
return stat.isDirectory ? 'directory' : 'file'
},
async exists(path: string) {
try {
await ctx.fs.stat(path)
return true
} catch {
return false
}
},
async list(dir: string) {
const entries = await ctx.fs.readdir(dir)
return entries.map(e => e.name)
}
})
}
}
(2) ctx.implement()
ctx.implement(capability, implementation) 将实现注册到能力:
ctx.implement(FileStats, {
getSize: async (path) => { ... },
getType: async (path) => { ... },
// 必须实现 interface 的所有方法
})
如果缺少方法,TypeScript 编译时报错。
(3) ▶ 示例 3
本地 Provider:
export default class LocalFileStatsProvider extends Service {
constructor(ctx: Context) {
super(ctx, 'file-stats')
ctx.implement(FileStats, {
async getSize(path) {
const stat = await ctx.fs.stat(path)
return stat.size
},
async getType(path) {
return (await ctx.fs.stat(path)).isDirectory ? 'directory' : 'file'
},
async exists(path) {
try { await ctx.fs.stat(path); return true } catch { return false }
},
async list(dir) {
return (await ctx.fs.readdir(dir)).map(e => e.name)
}
})
}
}
远程沙箱 Provider:
export default class SandboxFileStatsProvider extends Service {
constructor(ctx: Context) {
super(ctx, 'file-stats')
ctx.implement(FileStats, {
async getSize(path) {
const resp = await fetch(`http://sandbox:8080/stat?path=${path}`)
return (await resp.json()).size
},
async getType(path) {
const resp = await fetch(`http://sandbox:8080/stat?path=${path}`)
return (await resp.json()).type
},
async exists(path) {
const resp = await fetch(`http://sandbox:8080/exists?path=${path}`)
return (await resp.json()).exists
},
async list(dir) {
const resp = await fetch(`http://sandbox:8080/ls?dir=${dir}`)
return (await resp.json()).entries
}
})
}
}
5. Consumer:使用接口
(1) 消费能力
Consumer 通过 inject 声明依赖,通过 ctx 使用能力:
export const inject = ['file-stats']
export function apply(ctx: Context) {
ctx.tools.register(defineTool({
name: 'file_info',
description: 'Get file information',
parameters: {
type: 'object',
properties: {
path: { type: 'string', description: 'File path' }
},
required: ['path']
},
async execute({ path }, ctx) {
const size = await ctx['file-stats'].getSize(path)
const type = await ctx['file-stats'].getType(path)
return { path, size, type }
}
}))
}
(2) Consumer 不知道 Provider 是谁
Consumer 只依赖 Definition 定义的接口,不关心具体实现:
// Consumer 代码完全相同,无论底层是 Local 还是 Sandbox
const size = await ctx['file-stats'].getSize(path)
(3) ▶ 示例 3
// 通过类型扩展
declare module '@deepseek-ai/cordis' {
interface Context {
'file-stats': {
getSize(path: string): Promise<number>
getType(path: string): Promise<string>
exists(path: string): Promise<boolean>
list(dir: string): Promise<string[]>
}
}
}
6. seam(接缝)概念
(1) 什么是 seam
seam 是 Definition 和 Provider 之间的接合点——它是系统中可以"拆开替换"的地方:
graph LR
CON[Consumer] -->|依赖| DEF[Definition<br/>seam 接缝]
DEF -->|实现| PROV_A[Provider A<br/>本地实现]
DEF -.->|替换为| PROV_B[Provider B<br/>沙箱实现]
(2) seam 的价值
没有 seam:
Consumer → Provider A (硬编码,无法替换)
有 seam:
Consumer → Definition (seam) → Provider A
(seam) → Provider B (替换!)
seam 让系统在每个能力点都是可替换的。
(3) 识别 seam
判断一个接口是否是好的 seam:
| 标准 | 好 seam | 差 seam |
|---|---|---|
| 抽象度 | 恰到好处 | 过细或过粗 |
| 实现数 | 可能有多种实现 | 只有一种可能 |
| 变更频率 | 实现可能变化 | 实现永远不变 |
| 依赖方向 | Consumer 依赖接口 | Consumer 依赖实现 |
(4) seam 的粒度
粗粒度 seam: FileSystem(整个文件系统可替换)
中粒度 seam: FileStats(文件统计可替换)
细粒度 seam: FileSize(文件大小查询可替换)
粒度太粗 → 替换代价大;粒度太细 → 接口碎片化。选择中等粒度。
7. 能力注册图
(1) 注册流程
graph TB
DEF[defineCapability<br/>声明接口] --> REG[注册 Definition]
REG --> PROV1[Provider A implement]
REG --> PROV2[Provider B implement]
PROV1 --> ACTIVE_A[当前激活: A]
PROV2 --> WAIT_B[等待中: B]
ACTIVE_A --> CONSUMER[Consumer 使用]
(2) 替换流程
graph LR
OLD[Provider A<br/>当前激活] -->|卸载| INACTIVE_A[已停用]
NEW[Provider B<br/>新注册] -->|implement| ACTIVE_B[当前激活]
ACTIVE_B --> CONSUMER[Consumer<br/>自动切换]
(3) 多能力协作
graph TB
FS_DEF[FileStats Definition] --> FS_PROV[FileStats Provider]
DB_DEF[Database Definition] --> DB_PROV[Database Provider]
FS_PROV --> TOOL[file_info Tool]
DB_PROV --> TOOL
TOOL --> AGENT[Agent]
8. 替换一个 Provider = 替换整个产品行为
(1) 核心价值
这是能力系统最强大的特性:
场景: 从本地开发切换到沙箱执行
1. 卸载 LocalFileStatsProvider
2. 加载 SandboxFileStatsProvider
3. 所有 Consumer 自动使用沙箱实现
4. Consumer 代码零修改
(2) 配置切换
# 本地开发
plugins:
file-stats:
$insert: ./providers/local-file-stats
# 沙箱环境(只需改这一行)
plugins:
file-stats:
$replace: ./providers/sandbox-file-stats
(3) 运行时切换
// 通过 $replace 动态切换
ctx.on('config/updated', (config) => {
if (config.environment === 'sandbox') {
// 框架自动重载,切换到 Sandbox Provider
}
})
(4) A/B 测试
# A 组: 本地实现
realms:
group-a:
plugins:
file-stats:
$insert: ./providers/local-file-stats
group-b:
plugins:
file-stats:
$insert: ./providers/sandbox-file-stats
❓ 常见问题
undefined 的情况。📖 小节
- 能力三角色:Definition(声明接口)、Provider(实现接口)、Consumer(使用接口)
- seam 是 Definition 和 Provider 之间的接缝,是系统可替换性的关键
- Provider 替换后,Consumer 自动使用新实现,代码零修改
- 好的 seam 选择中等粒度,抽象度恰到好处
- 能力系统适合多部署环境、A/B 测试、可扩展架构
📝 作业
1. ⭐ 基础题:定义一个 TimeService 能力(Definition),包含 now(): number 方法。实现一个 LocalTimeProvider,注册后通过 Consumer 插件调用。
2. ⭐⭐ 进阶题:再实现一个 MockTimeProvider(返回固定时间戳),用 $replace 切换 Provider。验证 Consumer 的调用结果从真实时间变为固定时间,Consumer 代码无需修改。
3. ⭐⭐⭐ 挑战题:设计一个 SearchEngine 能力,定义 search(query: string): Promise<string[]> 接口。实现两个 Provider:LocalGrepProvider(用 grep 搜索)和 RemoteAPIProvider(调用搜索 API)。用 realm 配置让两个 Agent 分别使用不同的搜索实现,验证隔离效果。