接入任意模型:
provider 与凭据。
这套 harness 的名字里有 DeepSeek,但它并不绑定任何一家模型厂商。 GPT、Kimi、Claude 在这里都是可替换的 provider——换一个模型,是改一份配置, 而不是改一行源码。这一课学会在 Web UI 里配置模型、接入 OpenAI 兼容端点, 并用凭据引用把密钥管在安全的地方。
- 说清「模型只是插件」的机制:llm 能力缝隙的三角色与
ctx.llm注册表 - 在 Web UI 的「设置 → 模型」页配置 DeepSeek 与目录提供方(Anthropic / OpenAI 等)
- 用「添加自定义提供方」接入任意 OpenAI 兼容端点,并说清
DEEPSEEK_BASE_URL的作用 - 用凭据引用管理密钥:
.env归属、引用而非明文、永不提交凭据
模型只是插件:llm 能力缝隙
在这套 harness 里,「模型」不是写死在循环里的组件,而是能力缝隙(capability seam)上的一个注册项。 每条缝隙由三个角色组成:Service Definition 定义与厂商无关的词汇表与服务接口; Provider 把某个具体服务接进来;Consumer 只面向接口编程,不关心对面是谁。
llm 这条缝隙的三角色是:llm 包提供消息与流式词汇表,加上适配器注册表 ctx.llm;
llm-deepseek、llm-pi-ai 这类 provider 把 DeepSeek 官方 API、OpenAI 兼容端点等
具体服务注册成适配器;agent-loop 与 compaction-basic 是 consumer——
它们发起模型请求时,根本不需要知道对面是哪家厂商。
架构文档把扩展方法浓缩成一句话:添加模型提供方 = 在 ctx.llm 上注册其适配器。
GPT、Kimi、Claude 对 agent loop 而言没有任何区别——它们都只是注册表里的一个条目。
这就是为什么「换模型」在这套框架里是运维动作(改配置),而不是开发动作(改代码)。
这一课你只操作现成的适配器;L9 会亲手写一个模型适配器挂到 ctx.llm 上。
现在先把直觉建立起来:模型服务之于 harness,就像外设之于操作系统——插拔自由,内核不动。
在模型选择器里选中一个模型,会同时把它设为新会话的默认值;已经发送过请求的会话则保留自己日志里记录的模型。 换 provider 不会改写历史会话的上下文——会话日志才是一切的源头(L6 展开)。
在 Web UI 配置 provider
启动 pnpm dsh web 后打开设置 → 模型,这就是模型管理的全部入口。
所有模型变更都会在下一次请求时生效,不需要重启服务器。
- DeepSeek 卡片:一个 API 密钥字段,输入并保存即可。密钥是只写的——保存后页面只会收到脱敏描述符,永远收不到明文;密钥本体存在
$DSH_HOME/.credentials.yaml,settings 里只保留它的凭据引用(04 节详解)。 - 添加提供方(目录提供方):选取 Anthropic 或 OpenAI 等提供方,输入 API 密钥并保存。已安装目录自带端点、协议和模型列表,不需要手填。
- 原生认证的例外:Bedrock、Vertex、Azure、Codex 分别要 AWS 凭据与区域、ADC 项目、
api-version、OAuth——只填 API 密钥字段无法完成配置。 - 选择模型:已配置的提供方会出现在模型选择器中;选择模型同时把它设为新会话的默认值。如果保存的默认值指向了已删除的提供方,输入框会显示「选择模型」并阻止输入,直到你另选一个。
MISSING_CREDENTIAL:通过模型页存储提供方密钥,或提供被引用的环境变量。
UNKNOWN_MODEL:选择已配置的模型,或向自定义提供方添加缺失的模型。看到这两个码先查配置,不要怀疑网络。
自定义 OpenAI 兼容端点
OpenAI 兼容 API 是一套 HTTP 约定:chat completions 的请求/响应格式,外加一个 GET /models 发现端点。
任何服务只要讲这套「语言」——OpenAI 官方、Kimi 等厂商的兼容入口、公司网关、自建代理——
harness 里现成的 openai-completions 适配器就能直接驱动它。
「接入任意模型」的真正含义是:复用同一个适配器,指向不同的 baseURL,而不是为每家厂商写代码。
目录里没有的服务(公司网关、自建服务器),走添加自定义提供方。表单字段各有讲究:
- Provider ID:小写,且永久——请求、已保存会话、模型默认值和凭据引用都会使用它。想改名只能加新提供方再删旧的;显示名称、基础 URL、协议、凭据和模型则随时可编辑。
- 基础 URL 与 API 协议:例如
https://gateway.example/v1配openai-completions。 - 凭据与至少一个模型:模型可以手动录入 id,也可以在「模型目录」里点获取可用模型——它用表单当前的 baseURL 和凭据去查询 OpenAI 兼容的
GET /models。候选项只更新草稿,保存前不落盘;目录提供方用已安装目录,不发起网络请求。返回 401 就查密钥;端点不提供/models就手动输入。
另一个入口是环境变量 DEEPSEEK_BASE_URL(L1 排错时见过):它把内置 DeepSeek 路由的请求指向你的兼容端点。
与自定义提供方分工不同——一个改内置路由的指向,一个新增一条完全独立的 provider 记录。
# .env(仓库根目录,gitignored)
DEEPSEEK_API_KEY=sk-...
# 可选:把内置 DeepSeek 路由指向公司网关 / 代理
DEEPSEEK_BASE_URL=https://your-gateway.example/v1
习惯直接编辑配置的话,$DSH_HOME/settings.yaml 里的等价写法长这样——注意 apiKeyEnv 是环境变量名,不是密钥本身:
# $DSH_HOME/settings.yaml
llm-pi-ai:
providers:
my-gateway:
apiKeyEnv: GATEWAY_API_KEY # 凭据引用,不是明文
api: openai-completions
baseURL: https://gateway.example/v1
models:
- id: legacy-chat凭据管理:引用而非明文
上一节的 apiKeyEnv 已经展示了核心思想:配置里只放引用,不放机密。
这背后是另一条能力缝隙 dsh-credentials——settings 分节与 cordis.yml 条目携带的是
凭据引用(POSIX 风格的环境变量名),值归 dsh-credentials-local 这类 provider 所有。
- 按操作解析:消费方每个操作重新
resolve(ref),绝不跨操作缓存——LLM 适配器每次模型请求解析一次,所以轮换后的凭据无需重启即可作用于紧随其后的下一次请求。 - 描述不泄露:
describe(ref)只回答「是否已配置 / 来自哪一层 / 当前能否写入」,绝不暴露值——配置界面的「已配置」徽标由此而来。由当前进程环境供值的引用会被报告为writable: false,因为那样的写入会表面成功、解析却持续返回遮蔽值,seam 直接拒绝。 - 本地 provider 的来源层:
env、file、project-env、user-env——仓库根目录的.env就是 project-env 层。一条 seam 级规则约束所有 provider:空的存储值在任何地方都视为不存在。
对你的日常操作,结论只有三条:密钥写进根目录 .env(已被 gitignore);
配置里永远只写环境变量名;永不提交凭据——不进 git 历史,不贴进任何会提交的 YAML。
把明文密钥写进 cordis.yml 或 settings 并提交,等于把它永久刻进 git 历史——事后删文件救不回来,只能轮换密钥。
正确姿势永远是引用:apiKeyEnv: GATEWAY_API_KEY,值留在 gitignored 的 .env 里。
动手试:接入一个兼容端点并验证
两条路径任选其一(有兼容端点就接真实的;没有就用公共 DeepSeek API 走一遍流程):
- Web UI 路径:
pnpm dsh web→ 设置 → 模型 → 添加自定义提供方 → 填小写 Provider ID、基础 URL(https://…/v1)、API 协议openai-completions、API 密钥、至少一个模型 id → 保存 → 在模型选择器选中它 → 发一句话。 - .env 路径:在仓库根目录
.env写入DEEPSEEK_API_KEY与DEEPSEEK_BASE_URL(指向你的兼容端点),然后用 headless 跑一句话验证:
pnpm dsh --profile headless "用一句话介绍你自己"验证标准:看到模型的正常回答,而不是 TRANSPORT 或 MISSING_CREDENTIAL 报错,即接入成功。
MISSING_CREDENTIAL → 密钥没配,或被引用的环境变量不存在;获取可用模型返回 401 → 密钥本身有问题;
UNKNOWN_MODEL → 模型 id 没录进这个 provider。三个码都不涉及 harness 本身的缺陷。
课堂练习
DEEPSEEK_BASE_URL 的用途是什么?正文 03 节:DEEPSEEK_BASE_URL 是环境变量版的「自定义端点」,把内置 DeepSeek provider 的请求指向你的网关或代理。模型 id 在 provider 的 models 列表里配置,超时与日志不归它管。
正文 04 节:settings 与 cordis.yml 只携带凭据引用(环境变量名),值归 credentials provider 的来源层——.env 属于 project-env 层且已被 gitignore。其余三项都会把机密泄露给所有能看到代码或聊天记录的人。
D 与「模型只是插件」直接矛盾:agent-loop 是 consumer,只面向与厂商无关的 ctx.llm 接口编程(01 节)。A、B 走兼容端点或目录,C 走 Anthropic 目录提供方,都不需要动一行 loop 源码。
A、B 是 04 节原话:引用而非明文,且 credentials 本身就是一条完整缝隙。C 错:.env 已 gitignore,任何 key 都永不提交——明文进 git 历史只能靠轮换补救。D 错:cordis.yml 元数据保持字面量,机密以引用形式出现,写明文恰是反模式。
问答为什么说在这套 harness 下模型只是一个插件?这对用户意味着什么?写下答案后点击对照›
参考要点:① 机制上,llm 是 capability seam——ctx.llm 适配器注册表 + provider 插件 + 只面向接口的 consumer(agent-loop),模型服务只是注册表里的一个条目;② 可替换:换模型是改配置而非改代码;③ 多模型并存:目录提供方与自定义兼容端点可同时配置,模型选择器随时切换,历史会话保留各自日志里的模型;④ 不被锁定:厂商差异被收敛为 baseURL + 凭据 + 协议,迁移成本约等于填一张表单。
问答描述接入一个新的 OpenAI 兼容 provider 的完整步骤与验证方法。写下答案后点击对照›
参考要点:① 准备三样东西:端点 baseURL、API 密钥、至少一个模型 id;② Web UI → 设置 → 模型 → 添加自定义提供方,填小写 Provider ID(永久)、基础 URL、openai-completions 协议、凭据、模型并保存(或改内置路由:.env 里 DEEPSEEK_BASE_URL 指向端点);③ 可用「获取可用模型」对 GET /models 先校验端点与凭据;④ 验证:模型选择器选中后发一句话,或 pnpm dsh --profile headless "…" 跑通且无 TRANSPORT / MISSING_CREDENTIAL 报错即成功;失败按错误码排查(401 查密钥、UNKNOWN_MODEL 补录模型)。
本课小结
模型在这套 harness 里不再是黑盒组件,而是 ctx.llm 上一个可替换的插件:
你会在「设置 → 模型」里配置目录提供方,用自定义提供方接入任意 OpenAI 兼容端点,
也懂得了凭据引用与「永不提交」的红线。模块 A 到此收官——你已经会用这套框架了。
下一课进入模块 B,潜入 Cordis 微内核,读懂「一切皆插件」究竟是怎么运转的。