fix(core): keep tool parameters on the MiniMax chat-completions wire - #11842
Conversation
Since #11431, convertLlmToolsToOpenAI() drops the `parameters` key for any tool that declares an empty argument list, so JSON.stringify sends a function declaration without it. `list_agents` is registered unconditionally with `{type:'object',properties:{},additionalProperties:false}`, which means every default interactive request carries one. MiniMax rejects that with `400 invalid params, function parameters is empty (2013)`, so a bare greeting fails before any tool call exists in the conversation (#11834). The omission is load-bearing for llama.cpp (#10080) and for strict OpenAI-contract validators such as LM Studio (#11410), so it stays the default. Add an opt-out that the MiniMax routing turns on, emitting `{type:'object',properties:{}}` -- the shape the Anthropic wire already substitutes for a missing `inputSchema`. The --openai-logging reconstruction follows the same gate so the logged body matches the body actually sent. Fixes #11834 Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-issue-patrol/jmu12tvxm45
|
✅ Qwen Triage finished — CI landed green on ✅ Qwen Triage 已完成 —— |
|
Thanks for the PR — and for going back to fix your own regression rather than leaving it for someone else to untangle. Template looks good ✓ Problem. Observed, not theoretical. #11834 is open, the reporter pasted the exact gateway rejection ( Direction. Aligned. Per-routing wire-shape gating is already how this file handles gateway disagreement — Size. Core paths are touched. 36 production lines ( Approach. The scope feels right — an optional trailing Risk. Elevated. Moving on to code review. 🔍 中文说明感谢贡献 —— 也感谢你回头修自己引入的回归,而不是留给别人收拾。 模板完整 ✓ 问题:已观测,不是理论性的。#11834 仍处于 open,报告者贴出了确切的网关拒绝信息( 方向:对齐。按路由区分 wire 形态本来就是本文件处理网关分歧的既有做法 —— 规模:触及核心路径。生产代码 36 行( 方案:范围合理 —— 一个默认 风险:偏高。 进入代码审查 🔍 — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
Code reviewI wrote down my own approach before reading the diff: put a capability on the What I verified rather than assumed, all read at the base commit:
One finding worth your attention — not a blocker, but it sharpens the risk you already flagged. The shape this PR restores is not the shape MiniMax received before #11431. Tracing "parameters": {"type": "object"}— which is precisely the shape #11410 reports LM Studio rejecting, and which is why omitting the key satisfied both backends at once. Your That cuts both ways, and I think net in your favour. If MiniMax accepted This is exactly the discriminator the triage on #11834 asked @wangvhero for — the Small factual correction to the description. macOS and Windows are not "left to CI": Testing evidenceUnattended CI run — I did not build, run, or execute anything from this PR. Evidence below is the PR's own CI, read through the API for commit Nothing is red. Three substantive jobs are still in flight, so there is no test result to report yet: Final CI results for
One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。 The 579 passing tests quoted in the description are the author's own local run on Linux — attributed, not independently re-run here, and not a substitute for the Ubuntu job above. The Sandboxed verification would settle part of this: 中文说明代码审查 读 diff 之前我先写下了自己的方案:在 以下都是我在 base commit 上实际读过、而不是默认成立的:
一个值得你注意的发现 —— 不是阻塞项,但它让你已经点出的风险更精确。 本 PR 补回的形态,不是 #11431 之前 MiniMax 收到的那个形态。在 base 代码上跟一遍 "parameters": {"type": "object"}—— 这恰好就是 #11410 里 LM Studio 拒绝的那个形态,也正是「省略整个键」能同时满足两类后端的原因。你选的 这一点两面都成立,但我认为净效果对你有利。如果 MiniMax 在 v0.23.2 之前能接受 这正是 #11834 的 triage 向 @wangvhero 索要的判别信息( 描述里一个小的事实更正。 macOS 和 Windows 并不是「交给 CI」: 测试证据:本次为无人值守 CI 运行,我没有构建、运行或执行本 PR 的任何代码。下表是针对 commit 沙箱验证可以定论其中一部分: — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
|
Confidence: 4/5 — the mechanism, the blast radius and the default path are all verified; the only thing nobody can verify from here is which of two literals MiniMax's validator actually wants. Stepping back: my independent proposal was a provider capability method, and it died on the logging path — you cannot reach a provider instance from Everything else is proportionate. A ternary and a hostname gate at two sites, 36 production lines, no drive-by refactors, no formatting churn, nothing unrelated in the diff. The 148 test lines are not padding for a wire-shape regression that already shipped once in v0.23.2 — asserting on I re-derived the problem rather than accepting the framing: Two reservations, both stated plainly:
I am not approving this because I ran out of reasons to say no. Holding a guaranteed-broken P1 regression for an external validator none of us can query would be worse than shipping the canonical shape — the same one both sibling wires already use — with a retest request attached. Approval is deferred until CI lands green on the commit below. 中文说明置信度:4/5 —— 机制、影响范围与默认路径都已验证;唯一无法从这里验证的,是 MiniMax 的校验器究竟要两个字面量中的哪一个。 退一步看:我自己的方案是在 provider 上加能力位,而它死在日志路径上 —— 不额外铺管线, 其余部分都很得体。一个三元表达式加两处 hostname gate,36 行生产代码,没有顺手重构,没有格式化噪音,diff 里没有无关改动。148 行测试不是凑数 —— 一个已经在 v0.23.2 里真实发布过一次的 wire 形态回归,断言打在 我是重新推导了问题,而不是接受它的表述: 两点保留意见,直说:
我不是因为想不出反对理由才批准。为一个我们谁都查询不了的外部校验器,扣住一个必然损坏的 P1 回归,比发出这个规范形态(两条同级 wire 已在用的同一个)并附上一份重测请求要更糟。批准推迟到下面这个 commit 的 CI 全绿之后。 — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship — CI landed green after the review. ✅
…hint
Aggregating gateways proxy MiniMax backends under their own hostname and
forward the `invalid params, function parameters is empty (2013)`
rejection verbatim, so the hostname-only gate left those routings
broken: verified live against such a gateway, where the hostname-only
build still 400s on a bare greeting while the widened gate (hostname OR
model id containing "minimax") gets a normal response. Measured on the
same gateway: MiniMax-direct model ids reject the parameterless shape
and accept `parameters: {type: object, properties: {}}`, while
DashScope-backed routings accept both, so widening is side-effect free
there; llama.cpp / LM Studio model ids never contain the string and
keep the omission they require (#11834).
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
Scope ledger — round 3, scope corrected after explicit maintainer direction. Original baseline ( Current head Verification: provider test 10/10; focused ESLint and Prettier clean; core build and core typecheck clean. Full repository build was attempted but stopped in untouched Scope verdict: corrected. Generic gateway detection is an explicit non-goal and can be handled separately if evidence warrants it. |
The gate read `contentGeneratorConfig.model`, but requests can carry a model override (`client.ts` `modelOverride`, set by the core tool scheduler), so a MiniMax-named config answering a non-MiniMax request flipped the shape while a MiniMax request under a non-MiniMax config kept the omission that 400s. Gate on the wire model instead — the same source the `enable_thinking` gate in pipeline.ts already documents for exactly this reason: the pipeline passes `context.model`, and the logging mirror passes `request.model` and falls back to the config model inside the predicate. Covered by new pins for both directions (#11834). Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
Two design corrections on top of the first commit, both from live measurement rather than review: 1. The gate was too narrow ( 2. The gate read the wrong model ( 584 tests pass across the five affected files. Blast radius is unchanged from the original patch — |
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-conflict/jmu1aot5f4g
Local verification against a real bundled CLI — merge referenceI built both arms and ran them end to end against a host that really answers on Verified at head RigDocker 1. The reported failure, reproduced and fixedReal TUI in tmux, typed 2. What changes on the wire, and where
3. Does the suite hold the fix down?Six of seven mutants die. The one that matters: on the first head ( Also in that figure, and the reason to touch the branch once more:
One 4. Two corrections to claims made around the fix
Checks
Residuals (all consistent with your own risk section)
Machine: macOS 26.6.2 (arm64), Node v24.18.1, containers on 中文说明本地真实环境验证 —— 供合并参考我没有停在序列化层,而是把两条臂都打成 验证基于 head 装置Docker 1. 报告的故障:复现并修复tmux 里的真 TUI,输入 2. wire 上变了什么、在哪里变
3. 测试能不能把修复钉住7 个变异体杀 6。关键的一个:在第一个 head( 同一张图里还有必须再动一次分支的原因:
对该文件跑一次 4. 两处需要更正的说法
检查项
残留(与你自己的风险一节一致)
机器:macOS 26.6.2(arm64)、Node v24.18.1,容器用 |
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
All three points acted on:
On hunk 2: agreed — the operative reason is keeping the no-capture fallback consistent, not the log/wire divergence I claimed. Keeping the hunk as-is. |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. Suggestions are inline. 1 Suggestion-level finding(s) could not be anchored to a changed line and were dropped; nothing further to act on here.
Test Plan (not a blocker): src/core/openaiContentGenerator/converter-parameterless-parameters.test.ts — no such file or directory; src/core/openaiContentGenerator/converter.test.ts — no such file or directory; src/core/openaiContentGenerator/pipeline.test.ts — no such file or directory; src/core/openaiContentGenerator/provider/minimax.test.ts — no such file or directory; src/core/loggingContentGenerator/loggingContentGenerator.test.ts — no such file or directory; and 1 more.
中文说明
已审查——无阻断问题。 建议见行内评论。 1 条建议级发现无法锚定到改动行,已丢弃;此处无需进一步处理。
Test Plan(非阻断):src/core/openaiContentGenerator/converter-parameterless-parameters.test.ts — no such file or directory; src/core/openaiContentGenerator/converter.test.ts — no such file or directory; src/core/openaiContentGenerator/pipeline.test.ts — no such file or directory; src/core/openaiContentGenerator/provider/minimax.test.ts — no such file or directory; src/core/loggingContentGenerator/loggingContentGenerator.test.ts — no such file or directory; and 1 more。
— qwen3.8-max via Qwen Code /review (v0.23.3)
Reverting the MiniMax opt-out in the OpenAI request logger left the suite green, so both mutations survived: dropping the options object, and reading the configured model instead of the wire model. One case with two calls pins both directions, and each revert now fails it.
…ax-tool-parameters
|
Qwen Code review did not complete successfully. The review pipeline failed before a review could be posted. A transient error is retried automatically; if you are seeing this, retry with |
Picks up main's review-runner-schedule helper-test fix (#11933), which writes a commonjs package.json into the fake-gh temp dir so the Lint lane's helper tests stop failing with 'require is not defined in ES module scope' when TMPDIR resolves inside the repo. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-conflict/jmu2luc9n6d
doudouOUC
left a comment
There was a problem hiding this comment.
Verified the wire change and its blast radius at the PR head. No issues found.
- Default path is byte-identical.
optionsdefaults to{}, sokeepParameterlessParametersis falsy everywhere except the MiniMax gate, and theparameters = undefineddrop from #11431 is preserved for every other provider — the two pre-existingconverter.test.tspins for llama.cpp/LM Studio are unedited, and the opt-in literal ({type:'object',properties:{}}) is the same shape the Anthropic wire substitutes for a missinginputSchema. - Exactly two production call sites, both updated.
git grep convertLlmToolsToOpenAIfindspipeline.ts:1035(the only chat-completions wire builder) andloggingContentGenerator.ts:1046(the log-reconstruction path) — both pass the flag, so logged bodies match sent bodies. There is no third serializer. - The gate reads the right model.
pipeline.tspassescontext.model, whichcreateRequestContextsets torequest.model || contentGeneratorConfig.model— the true wire model, so a request-level override that points at MiniMax is honoured; the hostname branch and the/minimax/imodel-id fallback inisMiniMaxRoutingare both safe on a missing/invalidbaseUrl(try/catch insideisMiniMaxProvider). - The absent-tools case is unchanged. The
tools.length > 0guard still skips an emptytools: [], and nothing downstream mutatesbaseRequest.toolsafter conversion. - The
/minimax/imodel-id match also catching an llama.cpp/LM-Studio server that happens to serve a MiniMax-named model is disclosed and measured under Risk & Scope, and the restored literal'sadditionalProperties: falsedrop is disclosed as a static inference — both read as accepted scope rather than defects.
qqqys
left a comment
There was a problem hiding this comment.
APPROVE
核对基线:head fdf880a4ec41(8 个文件,+431/-1,最后提交 12:03:54Z)。
历史阻塞问题:无
本 PR 历史上没有出现过 CHANGES_REQUESTED:三次 review 分别是 DISMISSED(占位)、COMMENTED(2026-09-14T18:14:09Z)与 doudouOUC 在当前 head 上的 COMMENTED(2026-09-15T12:18:32Z)。1 条 review thread 且已 isResolved: true,Critical 级未解决数 0。
本轮独立扫描:未发现 Critical
生产改动共 4 个文件 +63 行,修的是 #11834:MiniMax 对不带 parameters 的零参数工具返回 400 invalid params, function parameters is empty (2013),而始终注册的 list_agents 正是这种工具。这类改动最大的风险是「为了一个 provider 把其他 provider 弄坏」,我按这条主线核对:
- 默认行为逐字未变,两个既有修复没有被回退。
convertLlmToolsToOpenAI新增的第三参是options: { keepParameterlessParameters?: boolean } = {},分支写成不传 option 时parameters = options.keepParameterlessParameters ? { type: 'object', properties: {} } : undefined;
undefined为 falsy,走parameters = undefined,与改动前完全一致。因此 llama.cpp 无法在空properties上编译 grammar(#10080)与 LM Studio 等严格校验器拒绝无properties的parameters(#11410)这两条修复都保持有效——新行为是显式 opt-in,不是改默认值。代码注释把这三个 issue 的相互制约关系写得很清楚,这一点做得好。 - 门禁的判定来源选对了。 两个调用点都传「线上实际使用的模型」而非配置模型:
pipeline.ts传context.model,loggingContentGenerator.ts传request.model,注释说明理由——请求级 model override 决定由哪个后端应答,与同文件里enable_thinking门禁用的是同一个来源。这也保证了日志侧与实际发送侧用同一个谓词,调试日志里的 body 就是真正发出去的 body,不会出现「日志显示省略、实际发送保留」的偏差。 isMiniMaxRouting的误判面足够窄。provider/minimax.ts:52-60是主机侧沿用既有的return MiniMaxOpenAICompatibleProvider.isMiniMaxProvider(config) || /minimax/i.test(wireModel ?? config.model ?? '');
isMiniMaxProvider(精确匹配api.minimaxi.com/api.minimax.io,外加.minimaxi.com/.minimax.io后缀,且该后缀宽松的取舍与其风险在:14-23已有注释说明,本 PR 未改动它);模型侧是对 wire model 做大小写不敏感的minimax子串匹配,用于覆盖「聚合网关用自己的域名代理 MiniMax 后端、只把 2013 拒绝原样转发」这种主机名毫无线索的情形。误判需要「模型 id 含 minimax 但后端不是 MiniMax」,而这种组合恰好就是网关代理 MiniMax 的常见命名;反过来,真正会被{type:'object',properties:{}}拒绝的 llama.cpp / LM Studio 是本地服务,其模型 id 不会含 minimax。?? ''的兜底也让 model 缺失时安全落到不匹配,不会抛错。- 改动没有越过 converter 的契约。 返回类型仍是
Promise<OpenAI.Chat.ChatCompletionTool[]>,替换出的{ type: 'object', properties: {} }正是 Anthropic wire 在缺少inputSchema时使用的形状(注释已点明),属于合法 JSON Schema,不引入新的类型分支。
测试侧新增 4 个文件共 +368 行,其中 converter-parameterless-parameters.test.ts(+148)是为本行为专设的;当前 head 的 Lint & Static、Test (ubuntu-latest, Node 22.x)、Integration Tests (no-AK, No Sandbox) 全部 pass,说明这些用例确实通过。
CI:上述三项 pass,review-pr pending,按策略不作为门禁,我没有等待或轮询;无失败项。
结论:无历史阻塞问题,本轮未发现可证明的 Critical,提交 APPROVE。
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Not explored to full depth (tool budget reached): "agent 1e": none — no check was cut short.; "agent 6c": I did not read baseLlmClient.ts:558 / client.ts:5042 to confirm whether request.model can actually be '' in production, which is the reachability premis….
Test Plan (not a blocker): src/core/openaiContentGenerator/converter-parameterless-parameters.test.ts — no such file or directory; src/core/openaiContentGenerator/converter.test.ts — no such file or directory; src/core/openaiContentGenerator/pipeline.test.ts — no such file or directory; src/core/openaiContentGenerator/provider/minimax.test.ts — no such file or directory; src/core/loggingContentGenerator/loggingContentGenerator.test.ts — no such file or directory; and 1 more.
Deferred under the convergence posture (round 2, not a blocker) — recorded, not requested in this round:
packages/core/src/core/openaiContentGenerator/provider/minimax.ts:57 — [review] host-blind model-id leg also changes the first-party Alibaba Coding Plan / Token Plan routing (MiniMax-M2.5 on coding.dashscope.aliyuncs.com), which the PR body…
中文说明
未探索到全部深度(达到工具调用预算):"agent 1e":none — no check was cut short.;"agent 6c":I did not read baseLlmClient.ts:558 / client.ts:5042 to confirm whether request.model can actually be '' in production, which is the reachability premis…。
Test Plan(非阻断):src/core/openaiContentGenerator/converter-parameterless-parameters.test.ts — no such file or directory; src/core/openaiContentGenerator/converter.test.ts — no such file or directory; src/core/openaiContentGenerator/pipeline.test.ts — no such file or directory; src/core/openaiContentGenerator/provider/minimax.test.ts — no such file or directory; src/core/loggingContentGenerator/loggingContentGenerator.test.ts — no such file or directory; and 1 more。
收敛姿态下延后(第 2 轮,非阻断)——已记录,本轮不要求修改:共 1 条(原文未翻译,列表见上方英文部分)。
— qwen3.8-max via Qwen Code /review (v0.23.3)
qwen-code-review-bot
left a comment
There was a problem hiding this comment.
COMMENT
Independent pass over head fdf880a4ec41 (+431/-1, CI all green). I reviewed the two open threads from the round-2 review rather than duplicating them — both reproduce, and I would not approve while R2-1 stands.
R2-1 (Critical) reproduces from first principles. The gate is isMiniMaxProvider(config) || /minimax/i.test(wireModel), and the model-id leg carries no host condition. A self-hosted MiniMax deployment — llama.cpp serving MiniMax-M2-GGUF, where the served model id derives from the filename or --alias, so containing "minimax" is the normal spelling — is exactly the deployment #10080 stripped the empty parameters key for: llama.cpp cannot compile a grammar over an empty properties map. This PR switches that deployment back onto the empty-object shape, i.e. it fixes a 400 from the hosted MiniMax API by re-introducing the grammar-compile failure on a local engine. Reachable, a regression, and the fails-closed direction (the local engine breaks, not the cloud one). The deferred note about the same leg also firing for MiniMax-M2.5 on first-party Alibaba Coding Plan routing belongs in the same fix.
The hard part is real: hosted MiniMax rejects the omission, llama.cpp rejects the presence, and the hostname genuinely carries no hint behind an aggregating gateway. But "model id matches /minimax/i" cannot distinguish "gateway proxying hosted MiniMax" from "local llama.cpp running MiniMax weights" — both look identical at this layer. The fix probably wants one of: (a) exclude loopback/private/known-local hosts from the model-id leg, accepting that a remote self-hosted engine still misfires; (b) make the model-id leg opt-in via config for gateway users; or (c) key off a stronger signal than the bare id. Any of the three beats the current unconditional leg.
R2-2 (Suggestion) also reproduces. Both new gate tests reach keepParameterlessParameters: true only through the model-id leg; the host leg — isMiniMaxProvider, which covers the official api.minimaxi.com / api.minimax.io routings, i.e. the most common way this gate fires — has no test at either gated call site. Narrowing the predicate to a bare model-id regex would stay green. One case with baseUrl: https://api.minimaxi.com/v1 and a non-MiniMax model id expecting true pins it.
Everything else checks out: the default path stays byte-identical (options defaults to {}, the two pre-existing llama.cpp/LM Studio pins are unedited), the converter assertions are made on the serialized wire body rather than the returned object (the right level for a JSON.stringify bug), the logging mirror correctly uses the request-level model so the logged body matches the sent body, and the R1-1 thread is resolved with the test it asked for.
CI note: Test / Lint / Integration / web-shell E2E / Desktop x2 all green on this head.
…ride Address review on #11842: add a negative default-provider case (a parameterless tool must serialize without a parameters key), strengthen the MiniMax buildRequest tests (whole-shape equality, converter-shaped present-but-undefined parameters, declared-schema pass-through), and document why the override reverses the converter's #11431 invariant and must stay MiniMax-scoped. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmu34f2au75
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed. Suggestions are inline.
Not explored to full depth (tool budget reached): "agent 1c": repo-wide npm run typecheck was not run (only the two vitest suites above); the type-compatibility claim rests on reading openai@5.11.0 's ChatCompletionToo….
Not reviewed: reverse audit — stopped before round 4 by the review time budget.
Convergence: round 3 posted 3 inline comment(s), 3 of them reported for the first time; the previous round posted 2 (2 new). Findings keep coming back to the same files: packages/core/src/core/openaiContentGenerator/provider/minimax.ts (findings in round 2; 2 more now). The rate of new findings is not falling. A cluster that keeps producing siblings usually means the fixes are treating instances of a shared root cause — triaging that cause before the next round, or splitting an independent cluster into its own pull request, tends to end the loop faster than fixing them one at a time. Batching the remaining fixes and verifying them before the next push, or dropping this PR's reviews to --severity-floor critical, keeps the loop from re-deriving the same set. No Critical finding is open on this round, so merging and moving the remaining Suggestion threads to a follow-up issue is available as an ending — a merged pull request cannot diverge further. (Observation only — nothing was withheld from this review because of this observation.)
中文说明
仅完成部分审查,审查缺口已披露。 建议见行内评论。
未探索到全部深度(达到工具调用预算):"agent 1c":repo-wide npm run typecheck was not run (only the two vitest suites above); the type-compatibility claim rests on reading openai@5.11.0 's ChatCompletionToo…。
未审查:反向审计——评审时间预算不足,未能开始第 4 轮。
收敛情况:第 3 轮发布了 3 条行内评论,其中 3 条是首次提出;上一轮发布了 2 条(其中 2 条首次提出)。发现反复回到同一批文件:packages/core/src/core/openaiContentGenerator/provider/minimax.ts(第 2 轮已出过发现,本轮又有 2 条)。新发现的产出速度没有下降。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。把剩余修复攒成一批、验证后再推送,或将本 PR 的评审降到 --severity-floor critical,可以避免循环反复推导同一组发现。本轮没有未决的 Critical,因此"合入后把剩余 Suggestion 线程转到后续 issue"是一个可选的结束方式——已合入的 PR 不会继续发散。(仅为观察——本轮评审未因此扣留任何内容。)
— qwen3.8-max via Qwen Code /review (v0.23.3)
|
latest vllm openai compatible endpoint works without this issue |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
No blocking issues. LGTM! ✅
Not explored to full depth (tool budget reached): "agent reverse-audit (round 3)": did not run the packages/core suite outside src/core/openaiContentGenerator , nor packages/cli tests, so a cross-package consumer of determineProvider / M….
6 Suggestion(s) were drafted inline past the resolved critical posting floor — the floor engaged early: the first-time-finding rate has not fallen for 2 consecutive round(s); the CLI moved them into the deferral list below (floor enforcement).
Deferred under the convergence posture (round 4, not a blocker) — the floor engaged early: the first-time-finding rate has not fallen for 2 consecutive round(s) — recorded, not requested in this round:
packages/core/src/core/openaiContentGenerator/provider/default.test.ts:310 — [review] R3-1: (fix-induced) The negative pin this round added is satisfied by any serialized tools value that lacks the substring "parameters" — including an …packages/core/src/core/openaiContentGenerator/provider/minimax.ts:50 — [review] R3-3: (fix-induced) This comment was added to be the constraint record the previous round asked for, but it names vLLM as a backend that rejects the empty-objec…packages/core/src/core/openaiContentGenerator/provider/minimax.ts:43 — [review] R4-1: The shipped fix is narrower than what this PR published on the target issue, and #11834 still carries an un-corrected promise — addressed to the reporter …packages/core/src/core/openaiContentGenerator/provider/minimax.test.ts:153 — [review] R4-2: After this round the injection is pinned only by unit tests that call provider.buildRequest with hand-made tools. Nothing asserts that a MiniMax-r…packages/core/src/core/openaiContentGenerator/provider/minimax.test.ts:165 — [review] R4-3: All three new buildRequest cases pass a single-element tools array, so nothing pins that the injection is applied *per element* rather than to t…packages/core/src/core/openaiContentGenerator/provider/minimax.test.ts:146 — [review] R4-4: This new describe exercises three tools-carrying shapes only. The *toolless* request — the shape pipeline.ts deliberately sends for compaction, …packages/core/src/core/openaiContentGenerator/provider/minimax.ts:56 — [probe] the permissive MINIMAX_HOST_SUFFIXES match now also drives an outbound tool-schema rewrite, but its recorded risk budget at minimax.ts:15-24 still names only thi…
中文说明
无阻断问题。LGTM!✅
未探索到全部深度(达到工具调用预算):"agent reverse-audit (round 3)":did not run the packages/core suite outside src/core/openaiContentGenerator , nor packages/cli tests, so a cross-package consumer of determineProvider / M…。
6 条 Suggestion 在已解析的 critical 发布下限之外被起草为行内评论——发布下限因首次发现速率连续 2 轮未下降而提前生效;CLI 已将其移入下方延后清单(下限强制执行)。
收敛姿态下延后(第 4 轮,非阻断)——发布下限因首次发现速率连续 2 轮未下降而提前生效——已记录,本轮不要求修改:共 7 条(原文未翻译,列表见上方英文部分)。
— qwen3.8-max via Qwen Code /review (v0.23.3)
独立验证报告 — #11842 @
|
| 工具形状 | base 臂(PR base) | head 臂 |
|---|---|---|
零参工具(list_agents 形状,无 parameters 键) |
无 parameters → 400 |
注入 {type:'object',properties:{}} → 200 |
生产形状(converter 输出:键存在、值为 undefined,序列化时丢失) |
无 parameters → 400 |
注入 → 200 |
| 已声明 schema 的工具 | 原样透传 → 200 | 原样透传 → 200 |
| 对照:默认 provider(非 MiniMax) | 无 parameters(未注入) |
无 parameters(未注入) |
- 第 2 行是真正会上线的形状:
converter.ts给零参工具写的是「键存在、值undefined」,所以注入判定必须看值而不是键是否存在——head 的parameters === undefined判据恰好对这两种形状都成立,两行都是 400 → 200。 - 最后一行是对「为了一个 provider 把别的 provider 弄坏」这一主线的对照:改动没有泄漏到默认 provider,其报文两臂逐字节相同(仍然不带
parameters,对上面这个严格网关依然是 400——这是 fix(core): omit parameterless OpenAI tool schemas #11431/fix(core): normalize tool schemas for grammar-based providers #10080 记录的既有取舍,本 PR 未改动它)。 - 变异对照(证明探针不是空洞的): 在 head 之上把注入判据改成恒
false,MiniMax 的两个形状立刻回到 400、有 schema 的工具仍是 200 ⇒ 上表的两个 200 确实由这次改动造成。
测量 2 — CI(逐 lane,head 264da57ab8e8)
commits/264da57ab8e8/check-runs:total_count=131 == items_fetched=131,桶 {skipped:114, success:15, cancelled:2};按 lane 名取最新一次,33 个 lane 零 failure。GraphQL statusCheckRollup.state = SUCCESS。注意两点诚实说明:一是 cancelled 不是 failure,本 head 上这 2 个被取代的 cancelled run 也没有把 rollup 拉到 FAILURE;二是 reviewDecision = REVIEW_REQUIRED 不代表没有批准——本仓库没有 classic branch protection,rollup 不记录 review 行,所以门禁的批准侧只按 review 行读(这里读的是 head 上最新的 verdict 行)。
测量 3 — PR 自带用例在隔离树中的运行
default.test.ts(head blob 07032ea68065,含本轮新增的那条负向 pin「forwards a parameterless tool without a parameters key」)在隔离树里 56 passed。minimax.test.ts(head blob 7646cc02005c)在我的隔离树里无法收集:ajv/dist/2020.js、ajv-formats 在「拷贝的 src + 符号链接的 node_modules」布局下解析失败。这是我这套 lift 臂的环境限制,不是 PR 的问题(同一 head 的 CI lane 跑的是仓库自己的配置);我把它写在这里而不是隐去,是因为它意味着本轮我没有独立复跑这 3 条 MiniMax pin,而是按下面的方式间接覆盖了同一语义。
测量 4 — tmux TUI(真实 CLI)
真实 tmux 会话跑本机构建产物 dist/cli.js(v0.23.4 / 10dafaf796),隔离 HOME + 工作区级 .qwen/settings.json:捕获到面板与输入框,/about 显示 Auth API Key - openai、Model deepseek-v4.1-flash、Sandbox no sandbox、本机 MaaS base URL,/quit 后会话干净退出(无残留孤儿;pgrep -f dist/cli.js 命中的两个进程经 /proc/<pid>/cmdline 确认为已有的 serve daemon 与一个 ACP 子进程,未触碰)。
未发现阻断问题的理由,以及一条已由 ci-bot 提出的非阻断项
- 没有发现 Critical。 改动面只有一个
override buildRequest:先调super.buildRequest,再对tools做一次map,只对function.parameters === undefined的元素注入空对象 schema。父类同样只接受 2 个参数,所以这个 2 参 override 没有丢掉接口第三参reattachBlockCount的语义(该语义在default.ts里本就不存在)。注入产生新数组与新 tool 对象,不动调用方传入的对象;tools缺省时?.map得到undefined,序列化后与改动前一致。 - 日志与线上报文的一致性仍然成立(旧方案的这条不变量在重写后需要重新核对):
pipeline.ts:1594的 capture 明确在buildRequest之后、SDK 之前把报文交给 logger,loggingContentGenerator.ts:702-707优先用这个捕获值,只有捕获缺失时才回落到自己重建的buildOpenAIRequestForLogging(那条回落路径不经过 provider,因此对 MiniMax 会是旧形状)。我没有为这条回落路径构造出可达路径,故只作记录、不作发现。 - 一条非阻断项,已由 ci-bot 在本 head 提出(R4-1,Suggestion):本 head 的修复比 issue > 你好 ✕ [API Error: 400 invalid params, function parameters is empty (2013)] > /update ●︎ Qwen Code 0.23.3 已是最新! #11834 上的承诺窄——注入只在 provider 工厂按主机名命中 MiniMax 时生效,旧方案里那条「自有域名网关代理 MiniMax」的 model-id 兜底腿随重写被移除。对这类配置,合并前后行为相同(都是 400),所以不是回归,属范围与承诺问题。此处仅记录我独立复核该描述为真,不重复贴回评论。
诚实边界: 本报告的证据面是「生产 buildRequest 的输出在真实 HTTP 服务端上被接受/拒绝」与「CI 逐 lane 状态」;没有做 Docker/真机到 api.minimaxi.com 的真实联网验证(那是 wenshao 在他那条旧方案评论里做过的),也没有构造出 CLI 主循环级别、端到端触达 MiniMax provider 的臂。
本评论只发布上述验证报告,不携带 approve 投票;下一次轮次若 head 未变、门禁两侧仍成立,可据此判定。
— qqqys 独立验证(本轮 r271,tmux + 报文 A/B;基于 head 264da57ab8e8)
qqqys
left a comment
There was a problem hiding this comment.
批准(approve)head 264da57ab8e8bec3b337b12371ad27d7abe21256,依据是本 PR 上方那份独立验证报告(issue comment 5689575323)。
以下为写入前即时重读的状态:该 head 上最新的 verdict 行是 qwen-code-ci-bot 的 APPROVED,且其 submittedAt 晚于该 head 提交的 committer date(即在该 head 上取得,不是被重新锚定的旧行);该 head 的 check-run 普查完整(131/131),零 failure、无 lane 仍在进行,产品 lane 全部 success;既有 [Critical] 仅挂在旧 head fdf880a4ec41 上,本 head 无 [Critical]。
本条评论不重复任何既有发现:MiniMax 报文断言已由真实 HTTP 服务端上的 A/B(400 → 200,含变异对照)确认,默认路径未被泄漏,未发现阻断问题。
Post-merge verification of the rewritten fix (
|
doudouOUC
left a comment
There was a problem hiding this comment.
Verified the option threading and its two call sites at the PR head. No blocking issues found.
- The opt-in is exactly as narrow as the bug it fixes:
keepParameterlessParametersdefaults to falsy, so the parameterless-schema drop that llama.cpp (#10080) and strict OpenAI-contract validators (#11410) depend on is unchanged for every other routing; only MiniMax opts into the explicit{type:'object', properties:{}}shape the Anthropic wire already substitutes. - Both call sites pass it —
pipeline.ts:1047andloggingContentGenerator.ts:1054— so the request the log records and the request that goes on the wire agree, which matters because a divergence would misattribute a later 400 to the wrong body. - Both gate on the wire model (
context.model/ the request's model) rather than the config model, matching the stated reasoning that a request-level model override decides which backend answers — the same source the neighbouringenable_thinkinggate uses. A gateway proxying MiniMax under its own hostname is therefore caught by the model id, and a routed-away request is not caught by a stale config. isMiniMaxRoutingORs the provider check with the model-id test, so the existing hostname/provider path keeps working and the new path is purely additive.
|
Released in v0.24.0. |
convertLlmToolsToOpenAI omits `parameters` for every tool that declares an empty argument list (QwenLM#11431), so the wire carried a function declaration with no schema at all. OpenRouter rejects that on the first tool-using turn with `JSON error injected into SSE stream`, which hit every prompt since `list_agents` is registered unconditionally with `{type:'object',properties:{},additionalProperties:false}`. The omission stays the default: llama.cpp, LM Studio and vLLM (QwenLM#10080, QwenLM#11410) reject the empty-object shape. Restore the schema at the wire boundary instead, gated on the OpenRouter hostname — the same shape the MiniMax provider already injects (QwenLM#11842). Both providers now share `ensureToolParameters`; the injection deliberately stays out of the converter, where `relaxSchemaForFunctionCalling` would strip the empty `properties` and emit the bare `{"type":"object"}` that QwenLM#11410 reports as a 400.








What this PR does
Keeps an explicit empty
parametersobject on zero-argument tools only after the request has been routed to the MiniMax provider. The shared OpenAI-compatible serializer and every other provider keep their existing behavior.Why it's needed
#11834 reports that MiniMax rejects requests containing a function declaration without a
parameterskey with400 invalid params, function parameters is empty (2013). Qwen Code intentionally omits that key on the shared path because llama.cpp and LM Studio require the omission, so the two contracts must be handled at the provider boundary.Reviewer Test Plan
How to verify
"parameters":{"type":"object","properties":{}}.Evidence (Before & After)
N/A — request serialization only.
Tested on
Environment (optional)
Focused provider tests, core build and core typecheck on macOS.
Risk & Scope
Linked Issues
Fixes #11834
Related: #10080 and #11410 require the shared OpenAI-compatible path to continue omitting empty tool parameters.
中文说明
这个 PR 做了什么
仅在请求已经被路由到 MiniMax provider 之后,为零参数工具保留显式的空
parameters对象。共享的 OpenAI-compatible 序列化逻辑和其他所有 provider 都保持现有行为。为什么需要
#11834 报告 MiniMax 会拒绝包含无
parameters键的函数声明,并返回400 invalid params, function parameters is empty (2013)。Qwen Code 的共享路径会有意省略该键,因为 llama.cpp 与 LM Studio 需要这种形态,因此两种不兼容要求应在 provider 边界分别处理。Reviewer Test Plan
如何验证
"parameters":{"type":"object","properties":{}}。前后对比证据
N/A —— 仅修改请求序列化。
测试平台
环境(可选)
在 macOS 上运行了聚焦 provider 测试、core build 与 core typecheck。
风险与范围
关联 Issue
Fixes #11834
相关:#10080 与 #11410 要求共享的 OpenAI-compatible 路径继续省略空工具参数。