dsh(DeepSeek Harness)是 DeepSeek 官方开源的 Agent Harness,原生走 OpenAI Chat Completions 接口,配置集中在 ~/.dsh/ 目录。a4api 自 0.1.4 起支持 dsh 目标:配置方案勾选 dsh 后,切换动作写入 settings.yaml 与 .credentials.yaml 两个文件;自 0.1.5 起 dsh 统一经本地翻译代理的透传端点连接上游,由代理归一上游流式分片格式。配置文件被 watcher 热加载,切换后新会话即生效、无需重启。本文章介绍 dsh 的配置结构、写入内容、max_tokens 输出上限策略与常见问题。
1. dsh 的两个配置文件
dsh 的数据目录默认为 ~/.dsh(可用环境变量 DSH_HOME 覆盖),与切换直接相关的是两个文件:
| 文件 | 角色 |
|---|---|
settings.yaml |
全局设置,按 namespace 分段;本工具托管其中两段 |
.credentials.yaml |
凭证文件,明文存放 API Key 环境变量对应的值 |
两者都可用独立环境变量覆盖路径(A4API_DSH_SETTINGS_PATH 与 A4API_DSH_CREDENTIALS_PATH),主要用于测试。
2. 切换写入了什么
2.1 settings.yaml 的两段
llm-deepseek:
baseURL: http://127.0.0.1:17890
apiKeyEnv: DEEPSEEK_API_KEY
maxTokens: 131072
agent-default-model:
provider: deepseek-official
model: deepseek-v4-flash
| 字段 | 说明 |
|---|---|
llm-deepseek.baseURL |
指向本地翻译代理的地址(dsh 经代理的 /chat/completions 透传端点连接上游) |
llm-deepseek.apiKeyEnv |
固定为 DEEPSEEK_API_KEY,即凭证文件中的键名 |
llm-deepseek.maxTokens |
单次输出上限,取值策略见第 3 节 |
agent-default-model.provider |
固定为 deepseek-official——dsh 只注册这一个 provider 路由 |
agent-default-model.model |
配置方案中的模型名 |
settings.yaml 中其他段落(如 ui-onboarding)原样保留,切换只重写上述两段。
2.2 .credentials.yaml 的凭证
DEEPSEEK_API_KEY: 本地代理鉴权token
由于 dsh 统一经本地代理连接,这里写入的是代理鉴权 token而非真实 API Key——真实 Key 由代理从加密数据库解密持有并在转发时使用,不落盘明文。仅在代理不可用的防御性直连场景下才写真实 Key。
3. max_tokens 输出上限
dsh 适配器默认的单次输出上限是 256000,而多数上游(如智谱)的输出上限是 131072,超限请求会被直接打回 INVALID_REQUEST。因此切换时对 maxTokens 执行三级优先:
填写了 max_tokens?} B -->|是| C[使用显式值] B -->|否| D{用户曾手动在
settings.yaml 设置?} D -->|是| E[保留手动值] D -->|否| F[兜底安全值
131072] style A fill:#e8f4f8,stroke:#1a6b8a,stroke-width:2px style B fill:#ebdef0,stroke:#8e44ad,stroke-width:2px style C fill:#d5f5e3,stroke:#27ae60,stroke-width:2px style D fill:#ebdef0,stroke:#8e44ad,stroke-width:2px style E fill:#d6eaf8,stroke:#1a6b8a,stroke-width:2px style F fill:#fdebd0,stroke:#b7950b,stroke-width:2px
显式填写的值永远优先;没有显式值时绝不能放行 dsh 默认的 256000,这就是配置方案的 max_tokens 字段存在的意义(见配置方案与一键切换)。
4. 为什么必须经本地代理透传
dsh 原生走 Chat Completions,理论上可以直连上游;但部分上游(如 opencode zen)会在流式分片的 tool_calls 里用 null 填充后续分片缺失的字段,dsh 的适配器会把工具名与 ID 覆盖为空,表现为 unknown tool "" 报错、工具调用失效。
0.1.5 起 a4api 让 dsh 统一经本地翻译代理的 /chat/completions 透传端点连接上游:透传时把流式分片中 tool_calls 的 null 字段归一为省略键,从根上规避该问题。这是 dsh 目标始终需要代理运行的原因。
chat/completions 透传] B --> C[tool_calls null 归一
为省略键] C --> D[OpenAI 兼容上游] style A fill:#e8f4f8,stroke:#1a6b8a,stroke-width:2px style B fill:#ebdef0,stroke:#8e44ad,stroke-width:2px style C fill:#ffecd6,stroke:#e67e22,stroke-width:2px style D fill:#d5f5e3,stroke:#27ae60,stroke-width:2px
代理的鉴权方式、进程生命周期等机制与 Claude Code / Codex 场景完全共用,详见本地翻译代理。
5. 生效方式与备份
- 热加载生效:dsh 对 settings.yaml 与 credentials 文件都有 watcher,切换后无需重启任何进程,新开的会话立即使用新服务商与模型;正在运行中的旧会话不受影响。
- 双文件备份:两个文件在修改前分别备份到数据目录(命名
dsh.settings.时间戳.yaml.bak与dsh.credentials.时间戳.yaml.bak,各自滚动保留 5 份),写入均为临时文件加原子替换。
6. 常见问题
问:为什么 dsh 目标不能选 Anthropic 类型的服务商?
答:dsh 只注册 deepseek-official 一个 provider 路由,原生说 Chat Completions 协议,无法消费 Anthropic 端点。给 dsh 用请选择 OpenAI 兼容类型服务商;选择不符时切换会在写入前直接报错提示。
问:切换后需要重启 dsh 吗?
答:不需要。watcher 热加载让配置改动即时可见,开一个新会话就是新模型;正在跑的旧会话继续用旧配置直到结束,互不干扰。
问:之前偶发的 unknown tool 报错还会出现吗?
答:0.1.5 起 dsh 流量统一经代理透传并做 null 归一,该问题已从根源解决;若你从旧版本升级,确认版本号后重新切换一次 dsh 配置即可,见版本更新说明。
问:我的 dsh 装在自定义目录怎么办?
答:设置环境变量 DSH_HOME 指向你的 dsh 数据目录即可,a4api 会跟随它定位 settings.yaml 与凭证文件;也可以用更细粒度的两个路径变量分别覆盖两个文件的完整路径。
举手提问