DeepSeek Harness 蓝皮书
开发者指南

实操手册

动手做:添加一个 workspace 包、添加一个模型工具、接入一个 LLM 适配器

三个动手配方:添加一个 workspace 包、添加一个模型工具、接入一个 LLM(大语言模型)适配器。它们都要求一个源码 checkout(见安装与启动的「从源码运行」)。

配方一:添加一个 workspace 包

1. 创建包

packages/<group>/<pkg>/
  package.json     # 从 packages/core/tools 复制,调整 name/description/deps
  tsconfig.json    # extends ../../../tsconfig.base.json,rootDir src,outDir lib/types
  src/index.ts     # 服务默认导出,或插件(name/inject/apply/Config)
  README.md        # 服务 API、事件、扩展点与设计说明

package.json 的关键不变式:private: trueversion 与根 package.json 一致、type: module@deepseek-ai/cordis 同时出现在 peerDependencies 与 devDependencies(相同范围)、@deepseek-ai/schemastery 放在 dependencies(它是运行时校验器)。

2. 在根配置中注册

  • Host 包在 tsconfig.host.json、Client 包在 tsconfig.client.jsonreferences 里加 { "path": "./packages/<group>/<pkg>" }。普通包恰好属于一个 aggregate,绝不两个都加。
  • Client 插件包额外声明 dsh.client,并 extends tsconfig.base.client.json

3. 确定包拓扑

可替换能力按 Service Definition / Service Provider / Consumer 三种角色拆分到不同包(需要独立演进时);单一用途的插件保持一个包。角色名要描述当前稳定职责,不用首个实现或未来扩展命名。

4. 验证

pnpm install
pnpm run constraints && pnpm run typecheck && pnpm run lint
pnpm run build && pnpm run hygiene

配方二:添加一个模型工具

创建一个 greet 工具。替换插件文件为:

import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'

export const name = 'greet-tool'
export const inject = ['tools']

export function apply(ctx: Context) {
  ctx.tools.register(defineTool({
    name: 'greet',
    description: 'Greet someone by name.',
    parameters: {
      name: { type: 'string', required: true, description: 'The name to greet' },
    },
    output: {
      schema: { type: 'string' },
      render: (_args, value) => [{ type: 'text', text: value }],
    },
    async execute(args) {
      return `Hello, ${args.name}!`
    },
  }))
}

inject 让 Cordis 等待工具注册表就绪;defineTool 根据 parameters 推导并校验 args

pnpm dsh web --patch ./scratch-plugin/cordis.yml

打开 http://127.0.0.1:3080,输入 Use the greet tool to greet Ada.,模型即可调用 greet。工具约定的完整细节见注册模型工具

配方三:接入一个 LLM 适配器

适配器把 Harness 提供方无关的请求翻译成提供方 API 调用,再把响应翻译回 Harness 的分片:

import type { Context } from '@deepseek-ai/cordis'
import Schema from '@deepseek-ai/schemastery'
import { LlmAdapter, type GenerateOptions, type StreamChunk } from '@deepseek-ai/dsh-llm'

class MyAdapter extends LlmAdapter {
  private apiKey: string

  constructor(apiKey: string) {
    super()
    this.apiKey = apiKey
  }

  async *stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
    // 1. 把 options.messages 转成提供方格式。
    // 2. 调用流式 API。
    // 3. 把响应转成 StreamChunk 序列。
  }
}

export interface Config {
  apiKey: string
  providers: string[]
}

export const Config: Schema<Config> = Schema.object({
  apiKey: Schema.string().required(),
  providers: Schema.array(Schema.string()).required(),
})

export const name = 'my-llm-adapter'
export const inject = ['llm']

export function apply(ctx: Context, config: Config) {
  ctx.llm.registerAdapter(config.providers, new MyAdapter(config.apiKey))
}

流式分片的硬规则:每个 block-start 都要有配对的 block-endindex 从 0 递增标识内容块顺序;工具调用的 arguments 全程是原始 JSON 字符串;usage 必须在 finish 之前发出;finish 是最后一块。无法满足的字段抛 LlmError 而不是静默丢弃。

cordis.yml 中使用:

- id: my-llm
  name: './src/my-llm-adapter.ts'
  config:
    apiKey: !!js process.env.MY_API_KEY
    providers:
      - my-provider

- id: agent-loop
  name: '@deepseek-ai/dsh-agent-loop'
  config:
    agents:
      - id: main
        provider: my-provider
        model: my-model-v1

仓库里的参考实现:packages/llm/llm-deepseek(直接 HTTP)与 packages/llm/llm-pi-ai(封装 LLM 库)。

下一步

本页目录