OpenAI-compatible API 最大的好处,是你可以把模型服务放在不同入口后面:自建网关、Azure OpenAI、OpenRouter、公司代理、或者 DeepAI API 中转站。
但兼容接口有一个常见陷阱:
参数规则跟模型有关,不一定跟域名有关。
NousResearch/hermes-agent issue #13901 就踩在这里。
Hermes 在选择 max_tokens 还是 max_completion_tokens 时,旧逻辑主要看 base URL host:
是不是 api.openai.com?
如果不是,就发送旧参数:
{"max_tokens": 4096}
问题是,很多自定义 OpenAI-compatible endpoint 后面跑的正是新模型族:
gpt-4ogpt-4.1gpt-5.xo1o3o4
这些模型会拒绝 max_tokens,要求使用:
{"max_completion_tokens": 4096}
于是第一轮请求就炸:
{
"error": {
"code": "unsupported_parameter",
"message": "Unsupported parameter: 'max_tokens' is not supported with this model. Use 'max_completion_tokens' instead.",
"param": "max_tokens",
"type": "invalid_request_error"
}
}
这类错误很适合用一句话概括:
服务端按模型验参数,客户端却按域名猜参数。
复现场景:不是 api.openai.com,但模型是新模型族
issue 里的复现方式很典型:
export OPENAI_BASE_URL=https://my-openai-gateway.example.com/v1
export OPENAI_API_KEY=sk-...
export OPENAI_MODEL=gpt-5.4
然后启动:
hermes chat
随便发一条消息,请求就失败。
原因不是 API key 错,也不是 base URL 不能访问,而是 Hermes 发出了模型不接受的参数:
{"max_tokens": ...}
对新 OpenAI 模型族来说,应该发:
{"max_completion_tokens": ...}
根因:参数选择只看 URL host
issue 指向的核心函数是:
AIAgent._max_tokens_param
旧逻辑大致是:
def _max_tokens_param(self, value: int) -> dict:
if self._is_direct_openai_url():
return {"max_completion_tokens": value}
return {"max_tokens": value}
也就是说:
- 如果 base URL 是
api.openai.com:用max_completion_tokens - 如果 base URL 是其他域名:用
max_tokens
这在旧模型时代看起来能工作。
但到了 OpenAI-compatible 网关时代,这个判断就不够了。
因为自定义域名后面完全可能是:
gpt-5.4
gpt-4o-mini
o3-mini
openai/gpt-4.1
模型族要求并不会因为你换了域名而改变。
同一个 bug 不只在主对话链路
评论里还补充了一个关键点:这个问题不只存在于主 agent path。
相同的 host-based assumption 也出现在 auxiliary path:
agent/auxiliary_client.py::auxiliary_max_tokens_paramagent/auxiliary_client.py::_build_call_kwargs
所以如果只修主对话路径,可能出现一种更隐蔽的状态:
聊天正常了,但标题生成、总结、后台任务、辅助模型调用仍然报 unsupported_parameter。
这也是 Agent 框架排错时最烦人的地方:主路径和辅助路径不一定共享同一套参数构造逻辑。
Azure OpenAI 也确认受影响
评论中有人确认,Azure OpenAI / Azure AI Foundry 自定义 endpoint 也能复现。
典型 endpoint 类似:
https://<resource>.openai.azure.com/openai/v1
受影响模型包括:
gpt-5.4-hermesgpt-5.4-nano-hermesgpt-4.1-hermes
Azure 返回的错误同样是:
Unsupported parameter: 'max_tokens' is not supported with this model. Use 'max_completion_tokens' instead.
在 Telegram / gateway 模式下,这个错误会放大成更差的用户体验:
- repeated retries;
- fallback switching;
Primary model failed — switching to fallback;- 任务被中断;
- 用户以为模型不稳定。
但根因其实只是一个参数名选错。
为什么这个问题很容易误诊?
因为报错位置在请求参数,但用户看到的是“模型调用失败”。
很多人会先怀疑:
- API key 无效;
- base URL 写错;
- 网关不兼容;
- 模型不存在;
- OpenRouter / Azure / 自建代理不稳定;
- fallback 模型配置错;
- Gateway 网络异常。
但 unsupported_parameter 这个错误非常明确。
只要看到:
Unsupported parameter: 'max_tokens'
Use 'max_completion_tokens' instead
就应该先查:
客户端是不是对新模型族仍然发送了 max_tokens?
更合理的判断方式:模型族 + URL,而不是只看 URL
issue 原始建议是增加一个共享 helper:
def model_forces_max_completion_tokens(model: str) -> bool:
m = (model or "").strip().lower()
if not m:
return False
if "/" in m:
m = m.rsplit("/", 1)[-1]
return (
m.startswith("gpt-4o")
or m.startswith("gpt-4.1")
or m.startswith("gpt-5")
or m.startswith("o1")
or m.startswith("o3")
or m.startswith("o4")
)
然后在各个参数构造点里判断:
if self._is_direct_openai_url() or model_forces_max_completion_tokens(self.model):
return {"max_completion_tokens": value}
这个思路的价值在于:
模型约束优先,域名只是辅助信号。
并且它兼容 OpenRouter 风格的模型名:
openai/gpt-4o-mini
通过去掉 / 前缀后,仍然能识别 gpt-4o-mini。
官方后续:问题被部分绕开,但不是所有场景都消失
issue 后续关闭时提到,原始复现环境已经不再失败,原因是几个相关改动陆续合入:
gpt-5.x现在无论 base URL 是什么,都会走 Responses API;- Azure OpenAI 加入 URL allowlist;
- GitHub Copilot 加入 URL allowlist;
- auxiliary calls 遇到
unsupported_parameter400 会 reactive retry。
所以对 gpt-5.x、Azure、Copilot、辅助调用来说,实际影响被明显降低。
但评论也明确提到仍有窄口径缺口:
main agent path + custom non-Azure/non-Copilot endpoint + gpt-4o/4.1/o-series
也就是说,如果你用的是普通自定义 OpenAI-compatible endpoint,不是 Azure,不是 Copilot,并且后面挂的是 gpt-4o / gpt-4.1 / o-series,仍然要警惕这个参数选择问题。
对 DeepAI API 中转站用户的排查建议
DeepAI API 中转站提供 OpenAI-compatible API 入口,这类场景最容易遇到类似问题。
如果你在 Hermes 或其他 Agent 客户端里配置:
OPENAI_BASE_URL=https://your-openai-compatible-endpoint/v1
OPENAI_API_KEY=...
OPENAI_MODEL=gpt-4o-mini
然后看到:
unsupported_parameter: max_tokens
不要第一时间怀疑中转站不可用。
更应该检查:
1. 客户端是否把新模型族误判成旧模型; 2. 是否仍然发送 max_tokens; 3. 是否支持配置 max_completion_tokens; 4. 是否有 Responses API / Chat Completions API 的自动切换逻辑; 5. auxiliary model / title generation / summarization 是否也走同一套参数规则。
如果你使用 DeepAI 作为 OpenAI-compatible 接入层,重点是让客户端按模型能力构造请求,而不是按域名猜。
临时规避方案
如果暂时不能升级 Hermes,可以考虑这些方式。
1. 换成已被客户端识别的新模型路径
如果客户端对某些模型会自动走 Responses API,可以使用对应模型名,避免旧参数分支。
2. 在网关层做参数转换
如果你的代理可控,可以把请求中的:
{"max_tokens": 4096}
转换成:
{"max_completion_tokens": 4096}
但这要小心,不要对所有模型无脑转换,否则旧模型可能反向报错。
3. 使用客户端支持的 provider preset
Azure、Copilot 这类 provider 后续被加入 allowlist。
如果你的服务实际是 Azure OpenAI,就尽量走 Azure provider 配置,而不是泛化 custom OpenAI endpoint。
4. 升级到包含相关修复的 Hermes 版本
重点关注:
- gpt-5.x Responses API routing;
- Azure OpenAI URL allowlist;
- Copilot URL allowlist;
- auxiliary unsupported_parameter reactive retry。
FAQ
为什么 api.openai.com 正常,自定义 base URL 就报错?
因为旧逻辑把 api.openai.com 当作是否使用 max_completion_tokens 的主要判断依据。自定义 URL 即使后面是新模型,也会被发送 max_tokens。
这是 DeepAI API 中转站的问题吗?
通常不是。unsupported_parameter: max_tokens 说明服务端正在按模型规则校验参数,客户端发错了参数名。DeepAI 作为 OpenAI-compatible 入口时,关键是客户端要使用正确参数。
为什么新模型不用 max_tokens?
OpenAI 新模型族和 reasoning / Responses API 相关链路更倾向使用 max_completion_tokens,某些模型会直接拒绝 max_tokens。
只改主 agent path 够吗?
不够。issue 中指出 auxiliary client 里也有类似逻辑,标题生成、总结、后台任务等辅助调用也可能触发同类错误。
最稳的修复思路是什么?
不要只按 URL 判断。应该结合模型名、provider 类型、API mode,以及服务端 400 错误做 fallback / retry。
总结
#13901 的教训很直接:
OpenAI-compatible endpoint 的参数选择,不能只看域名。
当自定义网关、Azure、OpenRouter、DeepAI API 中转站这类入口后面承载新模型族时,客户端必须按模型能力选择:
max_tokens
还是:
max_completion_tokens
否则用户看到的就不是“参数名错了”,而是整个 Agent 第一轮对话 400、反复重试、fallback 乱跳。
对 Agent 开发者来说,这不是一个小 if 判断,而是 provider abstraction 是否靠谱的分水岭。