Skip to content

Demo 8 · 组装自己的 Profile

目标:把 Mock 适配器的实验从"每次敲 --patch"沉淀成"一个名字":dsh --profile demo8 "任务"。理解 profile 目录结构、用户层 cordis.patch.yml 与分层覆盖语义。对应原理篇 08

运行

sh
cd demos/08-profile
DSH_HOME="$PWD/.dsh-home" npx dsh --profile demo8 "你好,自定义 profile"

输出:

text
(MockAdapter,无需网络与 API Key)收到你的消息:"你好,自定义 profile"。当前 provider=mock,model=mock-1。

没有 --patch,没有多余 flag。你的产品形态有了自己的名字。

profile 目录长什么样

text
.dsh-home/profiles/demo8/
├── package.json        # ① dsh.profile 清单:有序 bundle 列表
└── cordis.patch.yml    # ② 用户覆盖层

package.json

json
{
  "name": "demo8",
  "private": true,
  "dsh": {
    "profile": {
      "bundles": ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-headless"]
    }
  }
}

cordis.patch.yml

yaml
# demo8 profile 的用户层
- id: agent-default-model
  config:
    provider: mock
    model: mock-1
- insert:
    - id: mock-adapter
      name: ../../../plugins/mock-adapter.ts

与 Demo 5 的 overlay 内容几乎一样,区别只在位置:它住在 profile 目录里,是分层组合中稳定的一层(bundle 层之上、home 层与 --patch 之下),而不是每次命令行携带的临时覆盖。

分层验证:dump 组合树

sh
DSH_HOME="$PWD/.dsh-home" npx dsh --profile demo8 --dump-config | tail -8
yaml
# == /…/.dsh-home/profiles/demo8/cordis.patch.yml
- id: agent-default-model
  config:
    provider: mock
    model: mock-1
- id: mock-adapter
  name: ../../../plugins/mock-adapter.ts

--dump-config 的注释行告诉你每一段配置来自哪一层——组合结果可溯源。你可以再用 --patch 叠加一个 overlay 观察"后写覆盖":

sh
# 临时把 model 改成 mock-2:overlay 层高于 profile 层
DSH_HOME="$PWD/.dsh-home" npx dsh --profile demo8 --dump-config --patch /dev/stdin <<'EOF' | tail -6
- id: agent-default-model
  config: { provider: mock, model: mock-2 }
EOF

覆盖语义:整行替换

这是本章最重要的坑。agent-default-model 行的原始 config 是:

yaml
config:
  provider: deepseek-official
  model: deepseek-v4-flash

如果你的覆盖层只写 config: { model: mock-1 }provider 字段就丢了——整行替换,没有深合并。所以 demo 的 patch 里 providermodel 都复述了。这就是原理篇 8.3说的已知限制:"A user patch replaces the whole matched config."

完整分层回顾

你的 mock 改造在第 3 层。如果想让它对所有 profile 生效,移到第 4 层($DSH_HOME/cordis.patch.yml);想临时实验,用第 5 层(--patch)。同一份 patch 内容,放在不同层,影响范围不同。

亲手做实验

实验 1:home 层

把 mock 改造复制到 .dsh-home/cordis.patch.yml,然后跑 --profile headless "你好"——headless 也被 mock 接管了。体会 home 层的"机器级"影响。做完记得删掉。

实验 2:整行替换的坑

把 profile 的 patch 改成只写 config: { model: mock-1 }--dump-config 看 provider 变成了什么。

实验 3:web 版 profile

把 bundles 列表里的 @deepseek-ai/dsh-headless 换成 @deepseek-ai/dsh-web-appnpx dsh --profile demo8-web 启动 Web UI。在浏览器里体验同一条 mock 链路(启动后到设置页把模型切到 mock)。这是"产品形态 = 配置"最直接的体验。

常见错误

现象原因
no profile named xxx目录不存在或 package.json 缺 dsh.profile.bundles
覆盖后行为异常整行替换丢了字段(复述保留字段)
failed to import loader entry插件路径层级算错(baseUrl 是 profile 目录)

阶段小结

到这里你完成了全部 8 个 Demo:Cordis 原语 → LLM 接缝 → 真实链路 → 工具 → 拦截 → 产品化组合。下一部分教学版项目把这条认知路线反过来走一遍:从零亲手实现一个 mini-harness——那时你写的每一行代码都能在 dsh 里找到对应物。

基于 DeepSeek Harness(开发者预览版 0.1.0-rc.6)与 Cordis 撰写