deepseek-harness/docs/user/develop/practice/index.zh.md

156 lines
5 KiB
Markdown
Raw Normal View History

# 能力的三种角色设计
2026-07-09 16:07:58 +08:00
[English](index.md) | 中文
本文分为两部分:先参考三种角色能力模式的概念,再通过高级教程构建一项能力。请先完成[基础插件路径](../basic/index.zh.md)和[服务教程](../framework/service.zh.md)。
## 概念参考
当一项能力足够通用,需要支持可替换的提供方时(例如 Bash 执行),harness 会区分三种角色:**Service Definition**、**Service Provider** 和 **Consumer**。角色需要独立演进或替换时,将它们放入不同包;否则一个包可以承担多个角色。完整能力构成其 seam。任何单一角色都不是 seam。
2026-07-09 16:07:58 +08:00
## 以 Bash 为例
以 Bash 执行能力为例:
2026-07-09 16:07:58 +08:00
- **Service Definition** (`dsh-shell`):定义 Cordis 服务以及 Bash 请求和结果类型
- **Service Provider** (`dsh-bash-local`):在本地计算机上执行命令
- **Consumer** (`dsh-tool-bash`):将该能力公开为模型可调用的工具
2026-07-09 16:07:58 +08:00
```
┌─────────────┐ ┌──────────────────┐ ┌──────────────┐
│ dsh-shell │────▶│ dsh-bash-local │ │ dsh-tool-bash│
│(definition) │ │ (provider) │ │(consumer/tool)│
2026-07-09 16:07:58 +08:00
└─────────────┘ └──────────────────┘ └──────────────┘
▲ │
└────────────────────────────────────────────┘
2026-08-13 13:43:43 +08:00
inject: ['shell']
2026-07-09 16:07:58 +08:00
```
## 拆分的好处
### 提供方可替换
2026-07-09 16:07:58 +08:00
同一个 Service Definition 可以有多个提供方,可通过 `cordis.yml` 选择:
2026-07-09 16:07:58 +08:00
```yaml
# Local execution
2026-07-09 16:07:58 +08:00
- name: '@deepseek-ai/dsh-bash-local'
# Replace this row with another package that provides the same service.
2026-07-09 16:07:58 +08:00
```
更换提供方时,Service Definition 和工具均保持不变。
2026-07-09 16:07:58 +08:00
### 独立演进
2026-08-09 15:27:21 +08:00
- 调用方开始依赖 Service Definition 的约定后,Service Definition 很少改动。
- Service Provider 可以独立优化性能和安全性。
- Consumer 可以调整能力向模型呈现的方式。
2026-07-09 16:07:58 +08:00
### 依赖解耦
- Service Provider 依赖 Service Definition。
- Consumer 依赖 Service Definition。
- Service Provider 和 Consumer **互不依赖**。
2026-07-09 16:07:58 +08:00
当前内置系列及其包链接由[能力 seam 参考](../../../capability-seams.zh.md)负责。
2026-07-09 16:07:58 +08:00
## 教程:开发三种角色的能力
2026-07-09 16:07:58 +08:00
### 第一步:编写 Service Definition
2026-07-09 16:07:58 +08:00
```ts ignore-check
2026-07-09 16:07:58 +08:00
// packages/my-cap/my-cap/src/index.ts
build(vendor): rescope the vendored Cordis packages into @deepseek-ai Machine-produced by `pnpm run rescope-vendor --apply` plus the regeneration it prints: `pnpm install` for the lockfile, `pnpm run gen-third-party-notices`, `verify-translation-pairing --write` for the touched bilingual pairs, `gen-doc-graphs`, and one typert snapshot whose ids embed character offsets. `pnpm run rescope-vendor --check` verifies the result. Renames nine vendored packages (cordis, cosmokit, schemastery and the six @cordisjs plugins) and every reference that resolves them: manifest names and dependency keys, module specifiers including declare-module merges, cordis.yml plugin names, tsconfig paths, every Markdown fence, and `docs/` prose. Directory names, upstream versions, and dependency ranges are unchanged, so vendor/README.md still reads as an upstream snapshot; its manifest table gains an upstream-name column so THIRD_PARTY_NOTICES keeps MIT attribution pointed at each fork's origin. The tutorial tier follows the rename end to end: its yaml fences named plugins the Loader can no longer resolve, its `ts ignore-check` fences disagreed with the compiled fences beside them, and its prose quoted both. The contracts that told readers to keep upstream names — the root convention and the vendoring cookbook's tree comment and manifest invariant — now say to rescope instead. Two rules read `@deepseek-ai/` as "another workspace plugin": the client bundle purity gate now names the vendored libraries a browser bundle inlines, and the files where a bare `cordis` is an agent-preset id keep that product data.
2026-08-10 22:04:06 +08:00
import { Service, type Context } from '@deepseek-ai/cordis'
2026-07-09 16:07:58 +08:00
build(vendor): rescope the vendored Cordis packages into @deepseek-ai Machine-produced by `pnpm run rescope-vendor --apply` plus the regeneration it prints: `pnpm install` for the lockfile, `pnpm run gen-third-party-notices`, `verify-translation-pairing --write` for the touched bilingual pairs, `gen-doc-graphs`, and one typert snapshot whose ids embed character offsets. `pnpm run rescope-vendor --check` verifies the result. Renames nine vendored packages (cordis, cosmokit, schemastery and the six @cordisjs plugins) and every reference that resolves them: manifest names and dependency keys, module specifiers including declare-module merges, cordis.yml plugin names, tsconfig paths, every Markdown fence, and `docs/` prose. Directory names, upstream versions, and dependency ranges are unchanged, so vendor/README.md still reads as an upstream snapshot; its manifest table gains an upstream-name column so THIRD_PARTY_NOTICES keeps MIT attribution pointed at each fork's origin. The tutorial tier follows the rename end to end: its yaml fences named plugins the Loader can no longer resolve, its `ts ignore-check` fences disagreed with the compiled fences beside them, and its prose quoted both. The contracts that told readers to keep upstream names — the root convention and the vendoring cookbook's tree comment and manifest invariant — now say to rescope instead. Two rules read `@deepseek-ai/` as "another workspace plugin": the client bundle purity gate now names the vendored libraries a browser bundle inlines, and the files where a bare `cordis` is an agent-preset id keep that product data.
2026-08-10 22:04:06 +08:00
declare module '@deepseek-ai/cordis' {
2026-07-09 16:07:58 +08:00
interface Context {
myCap: MyCapService
}
}
export abstract class MyCapService extends Service {
constructor(ctx: Context) {
super(ctx, 'myCap')
}
/** Execute the capability. */
2026-07-09 16:07:58 +08:00
abstract execute(request: MyCapRequest): Promise<MyCapResult>
}
export interface MyCapRequest {
input: string
}
export interface MyCapResult {
output: string
}
```
### 第二步:编写 Service Provider
2026-07-09 16:07:58 +08:00
```ts ignore-check
2026-07-09 16:07:58 +08:00
// packages/my-cap/my-cap-local/src/index.ts
build(vendor): rescope the vendored Cordis packages into @deepseek-ai Machine-produced by `pnpm run rescope-vendor --apply` plus the regeneration it prints: `pnpm install` for the lockfile, `pnpm run gen-third-party-notices`, `verify-translation-pairing --write` for the touched bilingual pairs, `gen-doc-graphs`, and one typert snapshot whose ids embed character offsets. `pnpm run rescope-vendor --check` verifies the result. Renames nine vendored packages (cordis, cosmokit, schemastery and the six @cordisjs plugins) and every reference that resolves them: manifest names and dependency keys, module specifiers including declare-module merges, cordis.yml plugin names, tsconfig paths, every Markdown fence, and `docs/` prose. Directory names, upstream versions, and dependency ranges are unchanged, so vendor/README.md still reads as an upstream snapshot; its manifest table gains an upstream-name column so THIRD_PARTY_NOTICES keeps MIT attribution pointed at each fork's origin. The tutorial tier follows the rename end to end: its yaml fences named plugins the Loader can no longer resolve, its `ts ignore-check` fences disagreed with the compiled fences beside them, and its prose quoted both. The contracts that told readers to keep upstream names — the root convention and the vendoring cookbook's tree comment and manifest invariant — now say to rescope instead. Two rules read `@deepseek-ai/` as "another workspace plugin": the client bundle purity gate now names the vendored libraries a browser bundle inlines, and the files where a bare `cordis` is an agent-preset id keep that product data.
2026-08-10 22:04:06 +08:00
import type { Context } from '@deepseek-ai/cordis'
2026-07-09 16:07:58 +08:00
import { MyCapService, type MyCapRequest, type MyCapResult } from '@deepseek-ai/dsh-my-cap'
class MyCapLocal extends MyCapService {
async execute(request: MyCapRequest): Promise<MyCapResult> {
// Local provider behavior.
2026-07-09 16:07:58 +08:00
return { output: request.input.toUpperCase() }
}
}
export const name = 'my-cap-local'
export function apply(ctx: Context) {
ctx.plugin(MyCapLocal)
}
```
### 第三步:编写消费方
2026-07-09 16:07:58 +08:00
```ts ignore-check
2026-07-09 16:07:58 +08:00
// packages/my-cap/tool-my-cap/src/index.ts
build(vendor): rescope the vendored Cordis packages into @deepseek-ai Machine-produced by `pnpm run rescope-vendor --apply` plus the regeneration it prints: `pnpm install` for the lockfile, `pnpm run gen-third-party-notices`, `verify-translation-pairing --write` for the touched bilingual pairs, `gen-doc-graphs`, and one typert snapshot whose ids embed character offsets. `pnpm run rescope-vendor --check` verifies the result. Renames nine vendored packages (cordis, cosmokit, schemastery and the six @cordisjs plugins) and every reference that resolves them: manifest names and dependency keys, module specifiers including declare-module merges, cordis.yml plugin names, tsconfig paths, every Markdown fence, and `docs/` prose. Directory names, upstream versions, and dependency ranges are unchanged, so vendor/README.md still reads as an upstream snapshot; its manifest table gains an upstream-name column so THIRD_PARTY_NOTICES keeps MIT attribution pointed at each fork's origin. The tutorial tier follows the rename end to end: its yaml fences named plugins the Loader can no longer resolve, its `ts ignore-check` fences disagreed with the compiled fences beside them, and its prose quoted both. The contracts that told readers to keep upstream names — the root convention and the vendoring cookbook's tree comment and manifest invariant — now say to rescope instead. Two rules read `@deepseek-ai/` as "another workspace plugin": the client bundle purity gate now names the vendored libraries a browser bundle inlines, and the files where a bare `cordis` is an agent-preset id keep that product data.
2026-08-10 22:04:06 +08:00
import type { Context } from '@deepseek-ai/cordis'
2026-07-09 16:07:58 +08:00
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 },
},
2026-07-21 03:08:35 +08:00
output: {
schema: { type: 'string' },
render: (_args, value) => [{ type: 'text', text: value }],
},
2026-07-09 16:07:58 +08:00
async execute(args) {
const result = await ctx.myCap.execute({ input: args.input })
2026-07-21 03:08:35 +08:00
return result.output
2026-07-09 16:07:58 +08:00
},
}))
}
```
### 在 cordis.yml 中组合
```yaml
- name: '@deepseek-ai/dsh-my-cap-local'
- name: '@deepseek-ai/dsh-tool-my-cap'
```
## 设计要点
- **不要预防性拆分**:只有角色需要独立演进时,才使用不同包。简单的工具插件无需拆分。
- **Service Definition 拥有 Request/Result 类型**:Service Provider 和 Consumer 只依赖 Service Definition 包。
- **显式优于隐式**:实现应通过显式的 `resolve(request): Spec` 步骤处理默认值,而不是在 `run()` 中隐藏 `?? default`。
2026-07-09 16:07:58 +08:00
## 下一步
- [LLM(大语言模型)适配器](./llm-adapter.zh.md):实现一个 LLM 提供方