Hermes 升级后,调用本地 LiteLLM 代理时全部模型返回 400。后来接入 Azure GPT 时又遇到相似错误,但两者的正确解法并不相同。


问题现象

Hermes 升级后,调用本地模型时所有请求都返回 HTTP 400:

1
2
3
4
5
HTTP 400: litellm.UnsupportedParamsError: openai does not support parameters: ['reasoning_effort'],
for model=GLM-5.2. To drop these, set `litellm.drop_params=True` or for proxy:

`litellm_settings:
drop_params: true`

主模型和 fallback 模型全部报同样的错,会话完全无法使用。


背景

自建的 litellm 代理转发到内部模型服务。升级前使用不同厂商的独立 endpoint(Kimi、Qwen、GLM 各自有独立地址),升级后切换到了统一的 litellm 代理 endpoint。

Hermes 使用 reasoning_effort 控制推理力度。当时该 Profile 显式配置为 medium;当前 Hermes 新配置并不固定默认启用它。这个参数不是所有模型和接口都支持,LiteLLM 提示要么在代理端设置 drop_params: true,要么从请求端去掉该参数。


排查过程

第一步:对比升级前后配置

升级前(state-snapshots/20260722-021530-pre-update/config.yaml):

1
2
3
4
model:
provider: custom:local-kimi
default: Kimi-K2.6
base_url: ${LOCAL_KIMI_URL}

使用的是 custom:local-kimi 这种命名自定义 provider,通过 custom_providers 列表定义,provider 名称带前缀。

升级后(当前 config.yaml):

1
2
3
4
model:
provider: custom
default: GLM-5.2
base_url: http://192.168.205.185:9876/v1

切换成了裸 provider: custom + 直接写 base_url

第二步:查阅源码找到真正根因

从 Hermes git history 里找到关键 commit(67df958db,2026-07-04):

1
fix(custom-provider): emit reasoning_effort at the live profile path

这个改动的说明很关键:

PR #57601’s original branch added a top-level reasoning_effort emit to the LEGACY build_kwargs path (agent/transports/chat_completions.py), but provider=custom resolves to CustomProfile (plugins/model-providers/custom/), so chat_completion_helpers takes the profile path and returns early — the added branch was unreachable dead code for every custom endpoint.

Move the fix to its real site, CustomProfile.build_api_kwargs_extras()

也就是说,在这个 commit 之前,provider=customreasoning_effort 参数根本不会发给 API(因为代码在 profile path 里是无效的 dead code)!而 2026-07-04 这个 commit 把 reasoning_effort 的发射逻辑移到了 CustomProfile 里,终于让它对 custom provider 生效了。

这个 commit 对应的 Hermes 版本是 v0.18.x 到 v0.19.0 之间。升级后代码生效了,所以之前不存在的 behavior 突然冒出来了。

第三步:验证 reasoning_overrides 无效

查到 Hermes 有 agent.reasoning_overrides 可以按模型覆盖该参数:

1
2
hermes config set agent.reasoning_overrides '{"GLM-5.2": null, "Kimi-K2.6": null}'
hermes gateway restart

但按这个方法执行后,问题依然存在。后续通过源码分析发现 null 被解析为 “未设置”,直接回退到全局 reasoning_effort: medium

第四步:对照实验找到正确解法

做了三组对照实验:

  • 场景 1:reasoning_effort: medium + reasoning_overrides: null400 报错
  • 场景 2:reasoning_effort: "" + reasoning_overrides: null正常
  • 场景 3:reasoning_effort: "" + reasoning_overrides: {}仍然正常

从 Hermes 源码里找到关键路径(hermes_constants.py):

1
2
3
4
5
6
def resolve_reasoning_config(cfg, model=""):
overrides = agent_cfg.get("reasoning_overrides") or {}
per_model = resolve_per_model_reasoning_effort(model, overrides)
if per_model is not None:
return per_model # 有覆盖才生效
# 没命中 → 回退到全局 reasoning_effort

reasoning_overrides 里写 null 时:

  • parse_reasoning_effort(None)None
  • 被判定为”未设置”,直接跳过
  • 最终回退到全局 reasoning_effort: medium,仍然发射该参数 → 报错

所以 null 在这个设计里代表”未设置/回退”,而不是”禁用”。另一台电脑上之所以能工作,也是因为那台电脑的 agent.reasoning_effort 本身已经是空的。


根本原因

直接原因:Hermes v0.19.0 中 CustomProfilebuild_api_kwargs_extras() 方法会把已配置的 reasoning_effort 发送给 provider=custom 的端点,而之前版本这个逻辑是死代码,不会触发。

本质原因:这个 Profile 的 agent.reasoning_effortmedium,而 LiteLLM 后面的目标模型不接受该字段,因此返回 400。

对于 provider: custom 的端点,reasoning_overrides: null 会回退到全局值;"none" 则会关闭推理,但仍可能发出 reasoning_effort: "none"。如果 LiteLLM 连字段本身都拒绝,这两种写法仍会报错。

对这类上游,最直接的办法是将全局值留空,让 Hermes 不发送该字段。


原案例的解决方案

直接清空全局默认值:

1
hermes config set agent.reasoning_effort ""

验证:

1
2
3
4
$ hermes config get agent.reasoning_effort

$ hermes config get agent.reasoning_overrides
{}

输出为空即表示生效。新开会话后,请求不再带 reasoning_effort 参数,litellm 代理正常转发。


第二类 400:API 协议选错

后来通过统一 New API 网关调用 Azure gpt-5.6-sol 时,又遇到相似错误:

1
2
Function tools with reasoning_effort are not supported
in /v1/chat/completions. Please use /v1/responses instead.

这次模型支持 reasoning,问题是 Chat Completions 不支持它与 Function Tools 的组合。实测结果:

请求 结果
Chat Completions,普通文本 HTTP 200
Chat Completions,Tools + reasoning HTTP 400
Responses,Tools + reasoning HTTP 200

这里不应删除 reasoning,而应让 GPT 类模型使用 Responses:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
custom_providers:
- name: timo-api-responses
base_url: ${TIMO_API_BASE_URL}
key_env: TIMO_API_API_KEY
api_mode: codex_responses
models:
- gpt-5.6-sol-azure
- gpt-5.6-sol-codex

- name: timo-api-anthropic
base_url: ${TIMO_API_BASE_URL}
key_env: TIMO_API_API_KEY
api_mode: anthropic_messages
models:
- claude-opus-5-copilot
- claude-sonnet-4-6-copilot

普通自定义网关不会自动探测每个模型应使用 /responses/chat/completions 还是 /messages。一个网关承载多种协议时,应按协议拆分 named Provider。


快速判断

400 错误 正确处理
上游不接受 reasoning_effort 字段 清空该配置,或让 LiteLLM drop_params
Tools + reasoning 不支持 Chat Completions 改用 Responses,不要牺牲 reasoning
内容安全过滤 修改提示词,与 API Mode 无关

reasoning_overrides: null 表示“未设置并回退全局”,不是禁用。false 或字符串 "none" 表示关闭推理,但 Custom Provider 仍可能发送 reasoning_effort: "none";若上游连字段本身都拒绝,应使用空字符串彻底省略。


经验总结

  1. 看到 reasoning_effort 相关 400 时,先读完整错误:参数不支持和协议不匹配是两类问题。
  2. null 表示回退,不表示禁用;空字符串用于省略字段,false/"none" 用于关闭推理。
  3. 多协议统一网关应按 wire protocol 拆分 Provider,不能给所有模型固定同一个 api_mode
  4. 验证至少覆盖普通文本、Tools、Tools + reasoning,并检查真实请求路径。
  5. Fallback 可能重新计算 API Mode;Fallback 成功不代表主 Provider 配置正确。

参考资料


创建时间:2026-07-22 · 更新时间:2026-08-02 · 本文档由 Hermes 用户排查并记录