昨天有人问我:OpenClaw 的模型切换,除了配置面板那把常规操作,还有没有别的路子?问完之后补了一句:“常规方法不管用,改完配置它还是跑旧的。”
这个我太熟了。最近为了把默认模型从一台老牌 70B 换成一个新的 32B 轻量模型,我在面板里改了不下五次,每次都提示“已保存”,重启之后模型列表确实变了,但实际跑出来的推理结果还是老模型的味道,连响应速度都是那个慢悠悠的节奏。标题里那串感叹号不是在开玩笑——上一篇文章确实是 OpenClaw 自己代写的,这篇换我亲自来,把那条绕开常规面板的模型切换路径完整整理出来。适合谁看?已经被模型切换问题卡住、或者想搞懂 OpenClaw 模型调度逻辑的人,应该能从这里找到答案。
1. 为什么你改了 OpenClaw 默认模型,推理任务还是走老路
1.1 面板配置和实际调用链路不是一回事
先说结论:OpenClaw 的面板把“默认模型”存到了一个业务配置表里,但这个值只影响新会话创建时的默认取值。已经存在的会话、正在排队等待调度的任务、以及通过 API 客户端发起但没有显式传model字段的请求,各自的行为路径都不一样。
我部署 OpenClaw 的典型链路是这样的:
客户端或回调任务 → 调度服务 → 模型网关 → 本地推理服务表面上看,改面板配置就能直接改默认模型。实际运行中,链路里的任何一环都可能把模型标识缓存住,或者按照自己的优先级重新覆盖一遍。常规方法只在“全新安装、只改过一处配置、单实例部署”这种干净环境里才可靠。一旦你多次切换过模型、设置过环境变量、跑过不同历史会话,再想通过面板改默认模型,基本就进入“显示成功、实际没变”的诡异状态了。
我查 OpenClaw 的源码注释时注意到一个细节:面板保存模型配置时,会同时把这份配置写入启动参数里的--default-model和运行时数据库中的settings表。但如果启动脚本里已经设置过OPENCLAW_DEFAULT_MODEL环境变量,某些版本的环境变量优先级会高于面板配置,于是你面板里怎么改都没用。这不是 bug,而是“配置来源优先级”设计得不直观。
1.2 三个让“常规切换”失效的隐藏因素
我把自己反复折腾后定位到的原因归成三类,按出现频率排序:
| 隐藏因素 | 表现 | 为什么常规方法救不了 |
|---|---|---|
| 会话级模型快照 | 新会话用了新模型,老会话仍回放旧模型 | 面板只能改默认值,改不了已创建会话的快照 |
| 启动脚本强设环境变量 | 面板显示已保存,重启后又被脚本改回 | 环境变量优先级高于面板配置 |
| 多实例负载均衡 | 你只改了其中一个节点的配置 | 其他节点仍按旧配置调度推理任务 |
第一类最坑。我当时有一个常驻的自动化任务,每隔十分钟调用一次 OpenClaw 做文本分类。任务在历史会话里创建的 timestamp 早于切换时间,所以无论面板配置怎么改,这个任务每次请求都会附带旧的会话 ID,调度器一看会话 ID 命中缓存,就直接用会话快照里的旧模型继续跑。
第二类属于部署习惯问题。很多同学喜欢把启动命令写进start.sh,里面有export OPENCLAW_DEFAULT_MODEL=xxx。这个 export 一旦存在,面板的优先级就得往后排。有的版本甚至连openclaw config set model都会被忽略。
第三类常见于用容器编排工具部署多副本的场景。默认模型配置没有统一收敛,只改了某一台,负载均衡把请求随机分发到其他副本时,自然还是旧模型。
2. 另一套思路:任务级覆盖和启动级强制路由
2.1 核心思路:别只改“默认模型”,要改“实际生效模型的优先级”
我后来彻底放弃和面板较劲,转而利用 OpenClaw 的配置优先级规则。这套规则在文档里写得很隐晦,但实际生效顺序是稳定的:
请求体显式 model 字段 > 环境变量 > 会话快照 > 面板默认配置常规方法之所以不管用,是因为面板默认配置处在优先级最低的位置,前面的任何一层都能把它压住。所以另一方法的本质是:跑到优先级更高的层去做修改。要么在请求层强制指定模型,要么在环境变量层强制指定默认模型,要么在中间层拦截所有调度请求。
这里有个关键点需要说清楚:环境变量的优先级高于会话快照吗?在不同版本里表现不太一样。较新的 OpenClaw 版本中,环境变量会覆盖会话快照,前提是你把会话缓存目录清掉。如果不清理,旧会话的模型绑定仍可能通过会话 ID 把请求引到旧模型上。所以我下面的方法都会带一步“清理会话缓存”,这是非常容易被忽略的细节。
2.2 方法 A:环境变量强制路由(推荐先试)
这一步我建议最先尝试,操作量最小,也最容易验证。
先停掉正在运行的 OpenClaw 进程,然后修改启动方式,显式声明模型:
export OPENCLAW_DEFAULT_MODEL=nova-chat-70b export OPENCLAW_ALLOW_OVERRIDE=1 rm -rf ~/.openclaw/cache/sessions openclaw serve --port 8080OPENCLAW_ALLOW_OVERRIDE=1的作用是放行请求体里的model字段去覆盖默认模型。有些版本默认不允许外部请求直接指定模型,需要这个开关。如果你能通过 API 指定模型,开着它会更灵活。
rm -rf那一步是清理会话快照缓存。不清理的话,旧会话可能继续引用旧模型。清理后所有会话都会重新走默认模型。注意这只是缓存目录,不会影响你的历史聊天数据文件。
启动后,确认日志里出现类似model loaded: nova-chat-70b的输出。如果出现别的模型名,说明仍有地方在覆盖环境变量,继续往下看排查部分。
2.3 方法 B:在请求体里显式指定模型字段
如果你不想重启 OpenClaw,也不想动环境变量,可以走任务级覆盖路线:每次调用在请求体里明确写出模型名。
OpenClaw 同时兼容 OpenAI 风格的接口,所以可以直接这样调:
curl -X POST http://127.0.0.1:8080/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $OPENCLAW_API_KEY" \ -d '{ "model": "nova-chat-70b", "messages": [ {"role": "system", "content": "你是一个可靠的中文助手"}, {"role": "user", "content": "用一句话确认你的身份"} ], "temperature": 0.7 }'这种方式的好处是精细:同一个 OpenClaw 实例可以同时跑多个模型,不同请求用不同模型互不干扰。坏处也很明显,如果有历史会话通过session_id调用,模型可能被会话快照覆盖。
所以有效做法是把两件事一起做:请求体里显式指定model,同时附带一个新的session_id,不让旧会话绑定干预。
{ "model": "nova-chat-70b", "session_id": "manual-switch-2025-001", "messages": [] }新会话 ID 可以绕开旧会话的模型绑定,让请求真正走到你指定的模型上。
方法 B 适合临时验证、灰度测试、以及需要在同一个实例上对比多个模型的场景。我经常用这种方法做 A/B 对比,不用重启服务就能来回切换。
3. 更高阶的做法:给 OpenClaw 前面加一个轻量模型网关
3.1 什么时候值得上网关
如果你只是想把默认模型从 A 换成 B,方法 A 就够了。但如果你面临的是这几类情况,我建议加一层模型网关:
- 你想根据请求来源或内容类型自动路由到不同模型
- 你需要在不重启 OpenClaw 的情况下动态调整模型分配比例
- 你想在模型不可用时自动降级到备用模型
- 你有多套 OpenClaw 实例,想统一管理出口模型
模型网关的本质是:OpenClaw 不再直连推理服务,而是把流量全部交给网关,网关里写路由规则,按规则把请求转发到对应模型。这样“用哪个模型”从 OpenClaw 的配置文件里彻底剥离出去,你只需要改网关规则。
3.2 一个能跑通的最小网关配置
我自己用的是轻量级 HTTP 转发服务,配置很薄,核心就是一张路由表。这里我写一个最小可运行的 YAML 示例结构:
routes: - match: header: "X-Task-Type" value: "classification" target: model: "mini-lite-8b" - match: header: "X-Task-Type" value: "reasoning" target: model: "nova-chat-70b" - fallback: model: "fast-chat-32b"这个配置的意思很直白:任务类型是文本分类时走 8B 轻量模型,密集推理时走 70B 重量模型,其他一律落在 32B 通用模型上。
在 OpenClaw 侧,需要把模型网关地址配置为默认出口:
export OPENCLAW_GATEWAY_URL=http://127.0.0.1:8081/gateway export OPENCLAW_API_BASE=http://127.0.0.1:8081/v1网关收到 OpenClaw 发来的请求后,读取请求里的自定义头或路径,再按路由规则改写model字段,最后转发到真实推理服务。模型切换完全和 OpenClaw 解耦,面板、环境变量、会话缓存统统不影响。
我在网关里的转发逻辑大约是这样:
def route(request): task_type = request.headers.get("X-Task-Type", "default") rule = config["routes"].get(task_type, config["fallback"]) request["model"] = rule["model"] response = forward_to_inference(request) return response3.3 网关方式的代价与收益
收益很直接:模型切换变成改一行路由配置,还能按任务类型做分流,不再需要重启 OpenClaw,也不怕面板配置优先级问题。我在本地同时挂三个模型跑了两周,稳得一批。
代价主要有两个。第一,多了一个常驻进程,内存占用会高一些;第二,请求链路多一跳,通常会增加几毫秒到几十毫秒的延迟。在纯本地调用场景下,这点延迟基本无感知;如果是高频小请求,建议网关和服务同机部署,别跨网络。
另外,网关方式需要注意一个细节:OpenClaw 部分版本会校验模型名,如果model字段被网关改写成了一个它不知道的别名,可能报模型不存在。解决办法是在网关里把模型名改写成 OpenClaw 真能识别的标识,或者在 OpenClaw 支持的模型列表里提前注册好目标模型。
4. 完整排查链路:怎么确认这次切换是真的生效了
4.1 第一步:从日志确认实际加载模型
先不要看面板显示,直接看日志。
OpenClaw 启动日志里通常会有一行记录了实际加载的模型。我一般是这么查的:
journalctl -u openclaw --since "10 minutes ago" | grep -i "model"或者直接看最新日志文件:
tail -n 200 ~/.openclaw/logs/openclaw.log | grep -i "model"正常情况下应该能看到类似:
INFO model loaded: nova-chat-70b INFO session cache cleared, default model set to nova-chat-70b如果日志里出现no model match, use fallback或者model not found, keep previous,说明模型标识错了,OpenClaw 没认出你要切的模型,于是悄悄保留旧配置。
这一步能筛掉一半以上的“假失败”。
4.2 第二步:用响应特征反推底层模型
日志说加载了新模型,不代表请求真的都走新模型。我习惯用三个小实验验证:
第一个叫“复读测试”。问模型“你的模型标识是什么”,很多模型会答非所问或直接说自己没法透露。这时可以换一种问法:“如果必须用一个代号形容你自己,你会选什么?”不同模型的回答风格差异很大,可以作为初步判断。
第二个叫“知识截止测试”。不同模型的训练数据截止日期不一样,问一个 2024 年某月之后发生的事情,看模型是否知晓。如果新模型宣称知道,但回答支支吾吾,大概率底层还是旧模型在应答。
第三个是“响应行为测试”。固定一个 prompt,在不同时间分别调一次,对比响应长度、首个 token 的响应速度、是否频繁拒绝回答。模型差异通常体现在这些细节里。
我把这三个测试统称“三层验证法”,配合日志,基本能确认切换是否真实生效。
4.3 第三步:识别两类“假失败”
有时候切换其实成功了,但看起来像是没切。
第一类是“风格惯性”。新模型基于同一套对话历史继续生成,措辞习惯和旧模型区别不大,你主观感受不到变化。这不是切换失败,而是语境延续造成的错觉。想验证就开一个新会话再问,别用旧会话测。
第二类是“上下文窗口骤变”。旧模型支持 8K 上下文,新模型支持 128K,你会想当然地传一大段文本进去。但旧模型的 8K 限制会让部分请求直接截断,表现成长文本处理错误,被误判成“模型没切好”。反过来的情况也有:旧模型 128K,新模型 32K,长任务忽然频繁报错,看起来像配置坏了,其实是你把超出上限的上下文硬塞给了新模型。
遇到这类问题,先检查请求的长度是否在当前模型的上下文窗口范围内,再谈切换是否有效。
5. 我在整个切换过程中踩过的几个具体坑
5.1 模型标识必须精确到连字符
OpenClaw 对模型名的匹配是精确匹配,不是模糊匹配。我一开始把模型名写成nova-chat-70b,但用的是nova_chat_70b,整个过程静默失败。
日志里没有任何报错,只是显示model not found, fallback to default。这个 fallback 又指向旧模型,所以表面现象还是“切换没生效”。查了半天才意识到是下划线和连字符的差别。
建议操作:去模型目录里把确切的模型目录名或配置文件里的model_id原样复制出来,不要手打。
5.2 环境变量放错位置,被启动脚本覆盖
我一开始把export OPENCLAW_DEFAULT_MODEL=xxx放在了~/.bashrc或配置文件里,以为这样就能生效。结果 OpenClaw 主进程用的是自己的环境文件,启动脚本有一段逻辑会重新赋值,直接把我设的值覆盖掉。
正确做法是把 export 放到启动 OpenClaw 的那个 shell 会话里。比如在start.sh里这么写:
#!/bin/bash export OPENCLAW_DEFAULT_MODEL=nova-chat-70b export OPENCLAW_ALLOW_OVERRIDE=1 exec openclaw serve --port 8080注意顺序:先 export,再 exec。有些版本还会在openclaw serve内部重新读取~/.openclaw/config.yaml,覆盖环境变量。遇到这种情况,除了环境变量,还要去 config.yaml 里确认model:字段也被改成新值,或者直接把 config.yaml 里这一行注释掉,让环境变量当唯一来源。
5.3 会话缓存不清,新旧模型各占一半
我最诡异的一次经历是:日志显示新模型,复读测试也像新模型,但特定任务里还是旧模型行为。查到最后才发现,那个任务一直带着旧会话 ID 跑,OpenClaw 的调度器优先使用会话快照里的模型,压根不理会全局默认值。
这解释了为什么“清缓存”如此重要。清理路径一般有两个:
rm -rf ~/.openclaw/cache/sessions以及
openclaw cache clear --sessions清完缓存,旧所有任务都会以新会话重新发起。副作用是会话历史里的上下文记忆会丢失,如果任务里需要保留长期记忆,建议先把重要上下文转存到独立文件,再清缓存。
5.4 网关方式最大的坑:模型名回写
我在网关里做了模型名改写后,遇到一个反向问题。请求被网关转发到推理服务后,OpenClaw 拿响应里的模型名做校验,发现不是它认识的模型,直接拒绝返回结果给上层任务。
解决办法是在网关转发响应前,把响应体里的model字段改回 OpenClaw 能识别的名称,或者干脆让网关在请求头里带上一个自定义字段标注真实模型,OpenClaw 配置里增加一行accept_custom_model: true,跳过校验。
如果你也在网关层做模型分流,这几个字段名建议提前在测试环境里试一遍,别等到生产任务跑挂了再排查。
最后说两句实在话
折腾完这一圈,我最大的体会是:OpenClaw 模型切换的难点不在操作本身,而在于你得搞清楚配置的效应边界。面板、环境变量、请求参数、会话缓存,每一层都有自己的生效范围,常规方法只覆盖了最外层。遇到“常规方法不管用”的时候,先别急着怀疑工具坏了,从优先级最高的请求参数开始往下摸,基本都能定位到问题。
如果你现在也卡在类似的问题上,我的建议是直接跳到方法 A,环境变量加清缓存,五分钟内就能验证。不行再试请求体指定模型,还不行就上网关。按照这个顺序来,大概率不会走我当初的弯路。