Skip to content

@deepseek-ai/dsh-llm-deepseek 是 harness LLM 接缝的参考实现:一个直接 fetch、OpenAI 兼容的 chat-completions 适配器,在 ctx.llm 上注册唯一的 provider 路由 deepseek-official。它纯粹是传输层——序列化、SSE 解码与流翻译——而连接事实与凭据通过注册插件所拥有的 thunk 按请求解析。

包 ​

包描述
@deepseek-ai/dsh-llm-deepseek"DeepSeek chat-completions adapter for the DeepSeek Harness LLM seam"

它依赖 eventsource-parser@^3.1.0(SSE 分帧)与 @deepseek-ai/schemastery(配置 schema);peer 依赖覆盖 dsh-llm、dsh-credentials、dsh-settings、dsh-timeout、dsh-launch-environment、dsh-anonymous-user-id、dsh-invariants 与 cordis。它用原生 fetch 与 DeepSeek 平台通信——没有独立的平台 API 客户端包(BFF/"remotes"包是另一回事;见 API 网关)。

适配器 ​

DeepSeekAdapter(packages/llm/llm-deepseek/src/adapter.ts)继承 LlmAdapter。它为注册名下的每个模型服务:harness 模型 id 就是线上模型 id(listModels/resolveModel 原样返回所配置的内容)。构造一个实例并绑定到按请求的解析钩子:

ts
export interface DeepSeekAdapterOptions {
  options: () => DeepSeekConnectionOptions   // 校验过的连接事实,按操作解析
  resolveApiKey: (connection) => Promise<string>  // 无 key 时抛 LlmError MISSING_CREDENTIAL
  resolveUserId: () => AnonymousUserId
}

DeepSeekConnectionOptions 冻结一次解析的端点事实:baseURL(追加 /chat/completions)、一个 apiKeyEnv 凭据引用(绝不是字面 key)、defaults(thinking/effort)、maxTokens、defaultContextWindow、咨询性 models 目录、streamIdleTimeoutMs 与已解析的 retryPolicy。每次 stream() 都从同一份快照重新运行 options() 与 resolveApiKey(connection),因此进行中的流永远不会观察到配置变更,端点也绝不会与另一代的 key 配对。

Base URL 解析 ​

apply()(在 packages/llm/llm-deepseek/src/index.ts)按此优先级解析 baseURL:

  1. 显式 config.baseURL(来自 cordis.yml 或 llm-deepseek 设置段);
  2. 来自可信 launch-environment 层的 $DEEPSEEK_BASE_URL(让 checkout 可以把它的 agent 指向其 checkout 所针对的网关);
  3. 公共常量 PUBLIC_BASE_URL = 'https://api.deepseek.com'。

resolveAdapterOptions() 即使在构造绕过 Schemastery 时(编程式构建)也会重新校验每个默认值/绑定,加载时响亮失败,或在实时编辑违反 schema 之外的绑定时保留最后一个好的设置快照(keep the last good configuration)。

暴露的模型 ​

默认解析为三个 V4 模型,共享 1M-token 上下文窗口:

模型 id显示名上下文
deepseek-v4-flashDeepSeek-V4-Flash1,000,000
deepseek-v4-proDeepSeek-V4-Pro1,000,000
deepseek-v4-flash-vision-expDeepSeek-V4-Flash-Vision-Exp1,000,000(text + image)

目录中可覆盖每个模型的 maxTokens 与 contextWindow;没有精确值的被选模型回退到 defaultContextWindow(默认 1,000,000)与 defaultMaxTokens(默认 256,000)。视觉路由(deepseek-v4-flash-vision-exp)声明 inputModalities: ['text', 'image'],外加 imagePixelBudget(默认 640 000 像素,'low' 则 512×512 上限)与 imageMaxBytes(默认每图 1 MiB)——旧修订里「纯文本」的说法已过时。其余路由保持纯文本,纯文本路由确实收到图片块时仍会按请求大声拒绝。

请求映射:harness → DeepSeek 线上 ​

serializeRequest / serializeMessages(serialize.ts)把 harness 词汇映射到 POST {baseURL}/chat/completions 的 OpenAI 兼容请求体:

Harness 概念DeepSeek 线上
Message.rolemessages[].role(system/user/assistant/tool)
用户消息中的一条工具结果展开为独立的 {role:'tool', tool_call_id, content} 条目
assistant 内容content;无文本的 turn 发送 ""——绝不发送 null
assistant 推理reasoning_content(CoT 回传),只在工具调用 turn 上
assistant 工具调用tool_calls[](id、type:'function'、function{name,arguments})
GenerateOptions.tools带 JSON-Schema parameters 的 tools[]
thinking/reasoningEffort顶层 thinking:{type} + reasoning_effort(`'low'
temperature / maxTokens / stoptemperature、max_tokens、stop
流式总是 stream:true、stream_options:{include_usage:true}

图片:先 Files API,再内联 base64 兜底 ​

图片内容不再被一律拒绝——路由具备视觉能力时,适配器经 Files API 解析图片块。packages/llm/llm-deepseek/src/files-api.ts 把图片上传到 provider 的文件存储(#/components/schemas/FileObject):每次请求最多 128 MiB(MAX_REQUEST_FILES_BYTES)、10 000 个已存文件(MAX_STORED_FILE_COUNT,总计 25 GiB),带显式 filesApiTimeoutMs(默认 60 秒)与 fileExpiresAfterSeconds(默认七天)寿命、索引化上传缓存(upload-index.ts,按 fileRefreshMarginSeconds 刷新)、配额恢复清理(fileQuotaCleanupBatch)。文件解析失败时适配器回退到内联 base64(adapter.ts:565-617),受单独的 maxInlineRequestImageBytes 上限(默认 20 MiB)约束。旧的按图 imageDetail 旋钮已移除——目录会拒绝仍点名它的模型(index.ts:217-218,「改用 imagePixelBudget」)。收到图片的纯文本路由仍在任何文本扁平化可能静默抹掉它们之前抛 UNSUPPORTED_CONTENT(assertTextOnly)。resolveThinking 映射适配器自有的 effort:off ⇒ thinking:{type:'disabled'};low/high/max ⇒ thinking:{type:'enabled'} + effort(禁用 thinking 时只接受 off);session-title 用途总是强制关闭 thinking,让标题调用快速返回可见文本。

流式与翻译流水线 ​

text
 fetch(baseURL/chat/completions)
   → HTTP 200 text/event-stream body
   → parseSse(): TextDecoder → EventSourceParserStream(eventsource-parser)
        · 产出每个事件的 `data` payload;最后产出字面量 "[DONE]"
        · 没有 [DONE] 就 EOF  ⇒  抛 LlmError('STREAM_CLOSED')
   → translate(): 每个 content/reasoning/tool 索引一个带状态的 harness block
        · reasoning_content → reasoning-delta(首个空帧不打开 block)
        · content           → text-delta
        · tool_calls[]      → tool-call-delta(片段按索引拼接)
        · finish_reason     → finish reason(stop|tool_calls|length)
        · usage             → usage chunk(推迟到 [DONE];保留最新)
   → adapter.stream(): 用空闲看门狗 AbortSignal 包裹读取
        · streamIdleTimeoutMs(默认 300_000)内无读取  ⇒  LlmError('TIMEOUT')
        · 调用方 abort                                     ⇒  LlmError('ABORTED')
        · 传输失败                                         ⇒  LlmError('TRANSPORT')

mapFinishReason 把 stop → stop、tool_calls → tool-calls、length → max-tokens,任何无法识别的 reason(如 content_filter)映射为 error finish,错误码为大写后的 reason。

Token 核算 ​

mapUsage 把 DeepSeek 的 prompt_tokens(包含缓存命中)转换为 harness 的不相交约定:

ts
// translate.ts —— DeepSeek 的 prompt_tokens = 缓存命中 + 缓存未命中
const cacheRead = usage.prompt_tokens_details?.cached_tokens ?? usage.prompt_cache_hit_tokens
{
  inputTokens: usage.prompt_tokens - (cacheRead ?? 0),
  outputTokens: usage.completion_tokens,
  ...cacheRead !== undefined ? { cacheReadTokens: cacheRead } : {},
  ...reasoning !== undefined ? { reasoningTokens: reasoning } : {},
}

API key 从何而来 ​

provider 按环境变量名引用凭据——apiKeyEnv,默认 DEEPSEEK_API_KEY——绝不把字面 key 存在配置里。每次请求,resolveApiKey(connection):

  1. 向 ctx.credentials(凭据接缝)请求那个 CredentialRef;能解析就用它;
  2. 否则在未挂载凭据接缝时回退到 launchEnvironmentOf(ctx).get(ref)(shell 导出的 .env 层);
  3. 否则抛 LlmError('MISSING_CREDENTIAL')。

assertUsableApiKey 会 trim 并拒绝空白/非 header 安全 key(错误码 INVALID_CREDENTIAL),且不回显任何秘密。文件存储的故事见 凭据管理 与 credentials-local provider($DSH_HOME 下的 .credentials.yaml + .env)。

错误处理摘要 ​

线上信号LlmError 错误码
HTTP 401 / 403AUTH
配额超限响应体(error.code/type/message)QUOTA
HTTP 429RATE_LIMIT
HTTP 400 + 上下文容量特征CONTEXT_WINDOW_EXCEEDED
HTTP 400 其他INVALID_REQUEST
请求扩展准备/接受失败REQUEST_EXTENSION(线上调用前或调用周际)
完成但无内容EMPTY_RESPONSE
HTTP ≥ 500SERVER
其他非 2xxHTTP_<status>

两个错误码加入了表面:REQUEST_EXTENSION(生命周期拥有的请求扩展——见下方三件套——准备失败、与基础请求字段冲突或被拒时抛出;adapter.ts:628,632,697)与 EMPTY_RESPONSE(适配器的无响应体/无内容完成;它也是重试策略 retryableCodes 的默认成员之一)。

Retry-After 变成 providerRetryAfterMs;x-request-id/x-deepseek-request-id 变成 requestId,两者都被运行中的重试策略使用。每次线上请求都会注入属性与匿名用户头(attributionHeaders()、x-deepseek-harness-user-id,外加 sessionId/compact 标记)。

配置键(cordis.yml / llm-deepseek 设置段) ​

键默认含义
apiKeyEnvDEEPSEEK_API_KEY凭据引用(环境变量名)
baseURL$DEEPSEEK_BASE_URL → 公共端点端点 base
thinkingprovider 默认'enabled' | 'disabled'
reasoningEfforthigh'off' | 'low' | 'high' | 'max'
maxTokens256,000每次请求的输出上限
defaultContextWindow1,000,000回退上下文容量
modelsV4 Flash + V4 Pro + V4 Flash Vision Exp咨询性目录(逐模型 inputModalities、imagePixelBudget、imageMaxBytes)
maxRequestFilesBytes128 MiB每次请求累积的文件引用图片字节
maxInlineRequestImageBytes20 MiBFiles API 兜底后累积的 base64 图片载荷
maxImagesPerRequest600每次请求最多表示的图片数
imageOffloadByteQuantum64 MiB越过文件上限后的原始字节移除步长
inlineImageOffloadByteQuantum10 MiB越过内联上限后的 base64 字节移除步长
imageOffloadCountQuantum20越过数量上限后的图片数移除步长
filesApiTimeoutMs60,000一次请求图片 Files API 解析的预算
fileExpiresAfterSeconds604,800(7 天)每张已上传图片的显式寿命
fileRefreshMarginSeconds3,600(1 小时)剩余寿命低于此值时替换已索引文件
fileQuotaCleanupBatch100一次配额恢复上传重试前删除的最旧的 harness 自有文件
streamIdleTimeoutMs300,000空闲读取看门狗
retryPolicynormal,五次重试provider 自有重试策略

通过 Config schemastery schema 注册(见 packages/llm/llm-deepseek/src/index.ts),经 installSection(owner, ns, schema, entry, hooks)(由 installSettingsSection 更名;packages/settings/settings/src/index.ts:472)挂载,使插件的 config 与 llm-deepseek 设置段同形。

官方 API 扩展三件套 ​

三个兄弟包在被组合进官方路由时与 llm-deepseek 共享这场对话:

  • packages/llm/deepseek-llm-api-extensions(@deepseek-ai/dsh-deepseek-llm-api-extensions)——注册表 ctx.deepseekLlmApiExtensions(DeepSeekLlmApiExtensionRegistry):provider 插件向官方请求贡献的生命周期拥有的顶层 API 字段。它就是上面的 REQUEST_EXTENSION 缝。
  • packages/llm/plugin-package-inventory-deepseek——向官方请求贡献 dsh_plugin_packages({ version, packages }),默认开:部署请求元数据。
  • packages/session/session-log-deepseek——贡献 dsh_session_log(增量规范会话日志上传),默认关:增量上传会话日志,并把 session-log-deepseek/delivery-accepted 水印事件({ sessionId, throughSeq })写回同一日志,让重启恢复从已接受序列续传。

延伸阅读 ​

  • LLM 层——本 provider 实现的 LlmAdapter/LlmRuntime 契约。
  • 设置系统——schemastery Config schema 及其注册所经的设置接缝。
  • Token 计量——mapUsage 的不相交用量如何喂给基线。
  • packages/llm/llm-deepseek/src/adapter.ts——DeepSeekAdapter、DeepSeekConnectionOptions、错误码映射。
  • packages/llm/llm-deepseek/src/serialize.ts、sse.ts、translate.ts——请求/SSE/流流水线。
  • packages/llm/llm-deepseek/src/index.ts——apply()、Config、resolveAdapterOptions、PUBLIC_BASE_URL。