modify: inline 插件配置按模式对齐(网关侧兜底,兼容未升级的 worker 包)

现象:cache 模式下应用重启后插件仍按 toml 跑,而前端(读缓存)显示的是缓存值。
根因:跑起来的产物里 chongming-worker 是索引里的发布版(uv run build / uv sync 会把
uv pip install -e utils/worker 的可编辑安装替换回锁文件版本),旧版 Plugin.create_workers
在 on_init 时只拿到 config/workers/<type>/config.toml 展平出来的配置。

改动(两层对齐,规则与 SDK 一致且幂等):
- WorkerManager.align_inline_config:创建 inline 副本之前按 [cache].launch_worker_count
  对齐 [workers.config]——cache 模式取缓存值(缺失才用 toml 引导并写回),toml 模式以
  文件为准并把该段覆盖写入缓存;未配置缓存后端时原样返回。网关侧这层让网关与 worker
  包版本解耦:worker 包仍是旧版时 cache 模式同样生效。
- _discover_plugins 透出 [workers.config] 展平前的原始段;load_workers 在注册 starter/
  创建副本之前完成对齐(插件实例 on_init 用的就是这份配置)。
- test/test_inline_config_align.py:两模式 + 无缓存/无段的边界用例,并校验缓存键与 SDK 一致。
- README:补打包陷阱(可编辑安装会被 uv sync 替换回发布版)与对齐时机说明。
This commit is contained in:
2026-09-18 15:23:13 +08:00
parent d252eb87e1
commit b4a5985fe6
4 changed files with 238 additions and 2 deletions
+16 -1
View File
@@ -113,11 +113,22 @@ uv sync # utils/* 由 uv 按 pyproject 的 git 源自动安装,无需手
**修改 `utils/<name>`(工具包)**:
1. 在 `utils/<name>` 内提交并 push 到对应远程
1. 在 `utils/<name>` 内提交并 push 到对应远程(并发布新版本,见下)
2. 回主仓库执行 `uv lock --upgrade-package <包名>` 刷新版本 pin
3. `uv sync` 后运行相关测试
4. 主仓库提交 `uv.lock`
> ⚠️ **打包陷阱("改了 utils/* 但产物行为没变"几乎都是它)**:`uv run build` 会先按
> 锁文件同步环境,**`uv pip install -e utils/<name>` 的可编辑安装会被替换回锁文件里的
> 发布版**。因此本地改了工具包后,必须走上面 1-4 步(push + 发版 → 刷新 pin → sync),
> 否则 `build/` 产物里仍是旧版代码。仅本地联调时可临时用可编辑安装,但打包前要确认
> venv 指向的是准备发布的版本:
>
> ```bash
> uv pip install -e utils/worker --no-deps # 仅联调:让 venv 用本地源码
> python -c "import chongming_worker, inspect; print(inspect.getsourcefile(chongming_worker))"
> ```
**修改 `src/plugins/` / `config/` / `src/chongming-web/`**:
1. 在对应子仓库内提交并 push
@@ -127,6 +138,8 @@ uv sync # utils/* 由 uv 按 pyproject 的 git 源自动安装,无需手
- [ ] 所有子仓库 `git status` 干净且已 push
- [ ] `uv.lock` 与各 git 源最新提交一致
- [ ] 打包前确认 venv 里 `chongming-worker` 等工具包为目标版本(改了 `utils/*` 却仍是旧版时,
产物行为不会变,见上一节打包陷阱)
- [ ] 配置中无硬编码凭据——密钥一律走环境变量:
`VOLC_ACCESSKEY`(豆包 Ark Key)、`AIBOX_MQTT_URI`(AI Box 外部 Broker URI)
- [ ] `uv run python test_mqtt_e2e.py` 等测试通过
@@ -297,6 +310,8 @@ worker/<type>/<worker_id>/__admin__
两种模式都不回写 `config.toml`:管理接口的改动只进缓存。`cache` 模式下启动日志会打印 toml 与缓存的差异键(以缓存为准);如需让 toml 的改动生效,先删除 `worker:cm:configs:<type>`(运行时配置)或 `worker:cm:counts:<type>`(副本数)缓存键再重启,或改用 `"toml"` 模式。`GET /admin/workers/<type>/config` 的响应带 `source`(cache/toml)与 `mode`,前端据此标注展示值的来路。
- **对齐时机(两层,规则一致且幂等)**:inline 插件由**网关**在创建副本之前对齐——`WorkerManager.align_inline_config()` 在收到展平配置与 `[workers.config]` 原始段后按模式取缓存/写缓存,再交给插件的 `create_workers`;worker SDK 的 `Plugin.create_workers` 在 `_bootstrap()`/`on_init()` 之前用 `resolve_startup_config()` 再解析一次。网关侧这层是为了让网关与 worker 包版本解耦:worker 包仍是旧版时 cache 模式同样生效(否则旧版 `on_init` 只拿到 toml 展平配置,表现为"前端显示缓存值、实际跑 toml")。**external 插件**没有网关这层兜底,由插件进程内的 SDK 解析,需要 worker 包为含该改动的版本。
- **进程插件(external)实例粒度参与分摊**:网关只对 **inline** 插件用 cache 解析启动副本数(见「启动流程」第 5 步 `load_workers`),external 插件的启动副本数由外部编排按自身配置决定;但**运行时的缩放**上,external 并不退化为“每进程全额”,而是同样在**共享 cache 上登记实例参与方**来分摊,避免多实例把副本翻倍:
- **实例登记 + 心跳租约**:每个 external 进程启动时向 `worker:cm:instances:<type>` 登记自身参与方(`[instance_id, ts]`),并每 15s 心跳续约;超过租约(60s)未心跳的死实例会被清理。`divmod` 按存活参与方 N 均分全局 count(余量补靠前实例),`N==1` 时退化为全额(单部署零影响)。
- **实例标识默认唯一**:`--instance` / 环境变量 `CHONGMING_WORKER_INSTANCE` 显式指定(跨重启稳定、逐实例精细扩缩可持续命中);缺省回退 `{hostname}:{pid}`——**同一机器并行多个未指定 `--instance` 的进程也能靠 pid 区分**,不会因为 id 相同被并成一个参与方导致副本翻倍或前端只显示一个实例。注意缺省 id 随进程重启变化。