DSH plugin 怎么写 LLM 适配器?给 DeepSeek Harness 接入新模型提供方

插件开发发布于 2026-10-02作者: DeepSeek Plugin 插件市场
DSH pluginDeepSeek HarnessLLM 适配器LlmAdapterCordis
DeepSeek Harness 的 DSH plugin LLM 适配器继承 LlmAdapter 并实现 stream(),把提供方无关请求转成厂商 API 再转回 StreamChunk;用 ctx.llm.registerAdapter 注册,并覆写 resolveModel 报模型元数据。

给 DeepSeek Harness 接入新模型提供方的办法就是写一个 LLM 适配器:继承 LlmAdapter、实现 stream(),把提供方无关请求转成厂商 API,再把响应转回 Harness 分片。 最后用 ctx.llm.registerAdapter() 注册到对应路由,并按需覆写 resolveModel() 与 listModels()。这是让 DSH插件 支持任意模型后端的关键开发面。

DSH plugin 的 LLM 适配器是什么:继承 LlmAdapter 实现 stream()

LLM 适配器是一个继承 LlmAdapter 并实现 stream() 的类,负责双向翻译:入站把 Harness 请求转成厂商 API,出站把响应转回 Harness 分片(来源)。 官方最小实现如下:

ts
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. Convert options.messages to the provider format.
    // 2. Call the streaming API.
    // 3. Convert the response into StreamChunk values.
  }
}

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) {
  const adapter = new MyAdapter(config.apiKey)
  ctx.llm.registerAdapter(config.providers, adapter)
}

按四步落一个适配器插件:

  1. 写适配器类 — 继承 LlmAdapter,实现 async *stream(options)。预期:类型提示要求返回 AsyncIterable<StreamChunk>。
  2. 声明 Config 与 inject — 用 schemastery 定义 apiKey 等字段,并声明 inject = ['llm']。预期:ctx.llm 可用,密钥可按官方方式通过 !!js process.env.MY_KEY 注入。
  3. 在 apply 注册 — ctx.llm.registerAdapter(config.providers, adapter)。预期:options.provider 命中该路由时走你的适配器。
  4. 覆写可选方法 — 需要时实现 resolveModel() / listModels()。预期:模型选择器能列出你提供的模型。

注册基于副作用且对 HMR 安全:每个提供方路由只对应一个适配器,重复注册会抛异常,多路由注册要么全部成功要么全部失败(来源)。

DSH plugin 的 stream() 怎么写:StreamChunk 协议与关键规则

stream() 必须按官方 StreamChunk 协议产分片,顺序与配对是硬约束(来源)。 关键规则如下:

  • 每个 block-start 都必须有对应的 block-end;index 从 0 递增标识内容块。
  • usage 必须在 finish 之前发出,finish 必须是最后一个分片,之后不再发出任何内容。
  • tool-call-delta 的 argumentsDelta 是原始 JSON 文本的增量;提供方若返回已解析对象,要在 block-end 时重新 stringify。
  • 稳健做法:缓冲 finish / usage 直到提供方的流结束标记再统一 flush,以处理末尾只带 usage 分片的情况。

协议片段按下面生成:

ts
yield { type: 'block-start', index: 0, blockType: 'text' }
yield { type: 'text-delta', index: 0, text: 'Hello' }
yield { type: 'block-end', index: 0, block: { type: 'text', text: 'Hello world' } }
yield { type: 'usage', usage: { inputTokens: 100, outputTokens: 50 } }
yield { type: 'finish', reason: { kind: 'stop' } }

如果某个 GenerateOptions 字段你的提供方不支持,必须抛 LlmError(..., 'UNSUPPORTED_OPTION'),不得静默丢弃。 官方参考实现是 packages/llm/llm-deepseek/(直接 HTTP,SSE 由 eventsource-parser 分帧)与 packages/llm/llm-pi-ai/。

DSH plugin 怎么注册并提供模型元数据

options.provider 选择适配器、options.model 是提供方模型 id,因此动态模型目录适配器无需重启生命周期就能提供新模型(来源)。 在 cordis.yml 里把适配器与 agent loop 串起来:

yaml
- 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

覆写 resolveModel(provider, model, signal?) 是声明模型能力的地方:返回确切的提供方 / 模型身份,以及可选的 context 与 reasoning 元数据;推理强度是适配器映射到提供方请求的有序不透明 ID,要保留适配器给出的权威列表(含支持时的 off),不要提升为核心枚举,也不要自动调整不支持的值。异步查询必须响应可选 signal,让取消与资源释放停稳。

错误只有两条合法路径:从 stream() 抛出带稳定 code 的 LlmError(传输与协议故障),或以 finish { kind: 'error' | 'aborted' } 结束流(提供方带内故障)。每个 HTTP 请求还要合并 attributionHeaders() 并传递 options.signal:

ts
if (!response.ok) {
  throw new LlmError(`Provider API error: ${response.status}`, 'PROVIDER_HTTP_ERROR')
}

写完自检:usage 是否在 finish 之前、finish 是否最后;不支持的字段是否抛 UNSUPPORTED_OPTION;signal 是否透传;路由是否与别人的适配器冲突。想先了解适配器挂载的 ctx.llm 扩展点属于哪类能力,看 能力 Seams 与核心服务;想对照社区实现,可在 DSH Plugin Hub 找同类适配器插件。

常见问题

DSH plugin 的 LLM 适配器是什么?最小实现要写哪些部分?

DSH plugin 的 LLM 适配器是一个继承 LlmAdapter 并实现 stream() 方法的类,负责把 Harness 的提供方无关请求转成具体厂商 API,再把响应转回 Harness 分片。最小实现包含适配器类、Config schema、inject = ['llm'] 与 apply 里的注册四部分。

DSH plugin 的 stream() 分片协议有哪些必须遵守的规则?

DSH plugin 的 stream() 必须按 StreamChunk 协议产分片:每个 block-start 都要有对应的 block-end,index 从 0 递增,usage 必须在 finish 之前、finish 必须是最后一个分片。工具调用的 arguments 全程是原始 JSON 字符串,流式增量走 argumentsDelta。

DSH plugin 怎么用 ctx.llm.registerAdapter 把适配器注册到模型提供方路由?

DSH plugin 用 ctx.llm.registerAdapter(providers, adapter) 注册,第一个参数是该适配器负责的提供方路由列表。GenerateOptions.provider 用来选中适配器,GenerateOptions.model 则是适配器自己拥有的模型 id;能公布模型选项时再覆写 listModels()。

DSH plugin 适配器遇到提供方不支持的 GenerateOptions 参数该怎么办?

DSH plugin 适配器遇到提供方不支持的字段必须抛出带稳定 code 的 LlmError,不能静默丢弃。官方建议对不支持项抛 UNSUPPORTED_OPTION,让 agent loop 保留错误及其 code 用于诊断与策略处理。

DSH plugin 适配器怎么处理流式错误、请求取消与推理强度声明?

DSH plugin 适配器有且仅有两条错误路径:传输与协议故障从 stream() 抛出 LlmError,提供方带内故障则以 finish {kind: 'error' | 'aborted'} 结束流。取消要把 options.signal 传给 fetch 或 SDK,推理强度则通过覆写 resolveModel() 返回有序的不透明 ID 列表来声明。

相关术语

LlmAdapter
LlmAdapter 是 DSH plugin 里 LLM 适配器的基类,继承它并实现 stream() 即可把新的模型提供方接入 DeepSeek Harness。— https://deepseek-harness.github.io/deepseek-harness/develop/practice/llm-adapter
StreamChunk
StreamChunk 是 DSH plugin 的 LLM 适配器必须产出的分片类型,用 block-start、text-delta、block-end、tool-call-delta、usage、finish 等分片按协议描述一次流式回复。— https://deepseek-harness.github.io/deepseek-harness/develop/practice/llm-adapter
registerAdapter
registerAdapter 是 DSH plugin 把适配器挂到模型提供方路由上的方法,调用形式为 ctx.llm.registerAdapter(providers, adapter),每个提供方路由只能对应一个适配器。— https://deepseek-harness.github.io/deepseek-harness/reference/cookbook/adding-an-llm-adapter
resolveModel
resolveModel 是 DSH plugin 适配器用来公布确切模型元数据的可覆写方法,返回提供方与模型身份以及可选的 context 和 reasoning 字段。— https://deepseek-harness.github.io/deepseek-harness/develop/practice/llm-adapter

来源