Honor purpose when an auxiliary call names no reasoning level
#7109
Replies: 5 comments
|
Verified against master, plus one correction that changes the shape of the fix. The gap is real. In const requested = defaulted.reasoningEffort
// …
const effective = requested ?? reasoning.defaultEffort // packages/llm/llm/src/index.ts:894But the suggested patch breaks configurations that work today. A named if (reasoning === undefined) {
if (requested !== undefined) throw new LlmError(`… does not support reasoning effort "${requested}"`, 'UNSUPPORTED_REASONING_EFFORT') // :886-891
} else {
const effective = requested ?? reasoning.defaultEffort
if (effective !== undefined && !reasoning.efforts.some(e => e.id === effective)) throw … // :894-901
}The claim the patch rests on — "every reasoning model the harness can route lists
Measured, not inferred. Three pi-ai routes declared in YAML, then the auxiliary-call shape (
One precision worth having, because it changes what "worst case" means: The fix therefore has to ask the capability rather than assume it: Meanwhile this is mountable from outside, so I shipped it rather than only describing it: npm install @argszero/cordis-plugin-aux-reasoningIt joins the public Evidence — wire probe against the real
Two things this adds to the report:
The core fix is still worth doing: a plugin only covers calls that flow through the waterfall, and the default belongs where defaults are resolved. If the seam-level default is preferred, the shape above (query, then name) is what keeps it from turning three currently-working configurations into silent no-ops. A profile field such as |
|
Thank you — this is a more careful review than the original ask deserved, and the correction is right. I re-verified both failure cases against the installed Confirmed: The ask is rewritten around the shape you prescribed: query Two things from your reply are now in our record that were not in the original report:
One question, if you have a view: for the seam fix, should the guard live inside |
|
Thanks for the rewrite. Straight to your question, with the caveat that I am not a maintainer and cannot predict what one would take. My read: the second home — where the level is selected and written into
On the acceptance criterion, you are right that the second home leaves a gap: a caller that names a level explicitly is not covered. I would treat that as a different rule with a different owner — the resolver is the only place that knows the route's One caveat on the whole framing: I can only say which home is likelier to survive review in my reading of the code. The test that decides it is whether the rule is expressible without widening |
|
That is a more useful answer than the question deserved, and the deciding test — expressible without widening Recorded on our side: the selection home, with a resolver-side capability check normalizing rather than rejecting if it is added at all. Your reason 4 is the one we would not have weighed correctly — a guard that rejects there converts a should-not-think call into no call, which is worse than the behaviour it corrects. Thank you also for the composition point: it makes the acceptance criterion honest about what the selection home does not cover — an explicitly named level — without pretending the resolver should own the policy. That is the shape we will carry if a maintainer takes the ask. No further questions from me; the linked plugin is the right stopgap meanwhile. |
|
接着 argszero 那条实测补一半:插件侧的现场。 上面讨论的两类辅助调用都是 harness 自己发的。第三方插件自己发起的调用不在这套分类里 —— purpose 的取值就是 compaction / session-title,插件自造一个不在任何白名单里,借不到这个机制。所以插件侧没法声明「我是辅助调用」,只能自己在调用点查能力再决定档位。 插件侧的失败样子也比 error finish 难查。error finish 至少把 code 带回来了;插件这边是请求正常返回、不抛异常、code 也没有,你就是拿不到 JSON,然后下游一看「没报错」就不重试了。我们两次踩的都是这个,第二次还在「没报错」上多绕了一轮。 顺着这个,我们得改自己之前给过的建议。之前在 #3468 / #6797 我们说过插件侧「代码里直接传 reasoningEffort: off 就够了」—— 按你们上面这份实测,这句话只在路由确实列了 off 档的时候成立,否则撞同一堵墙,而且是更难查的那堵。正确写法就是你们修正后的形状:先查能力再写档位。那两条帖的建议需要同步更正。 现场记录在 #6857。 Adding the plugin-side half to the measurements above. The two auxiliary purposes discussed here are the ones the harness emits itself (compaction, session-title); a third-party plugin's own call is not in that classification and cannot borrow it, so the plugin side has to query the capability and name a level at its own call site. Its failure shape is also harder to diagnose than an error finish. An error finish at least returns the code; here the request returns normally, throws nothing, and carries no code — you simply never get the JSON, and a downstream caller that keys retries on "did it throw" never retries. We hit this twice, and the second time lost another round to the "no error" part. This also corrects advice we gave earlier: "just pass reasoningEffort: off in code" (#3468, #6797) holds only when the route genuinely lists off — otherwise it hits the same wall, the harder-to-diagnose one. The shape you arrived at, capability query then name the level, is the correct one. Those two threads need the same correction. Field notes: #6857. |
Uh oh!
There was an error while loading. Please reload this page.
What happens now. The harness marks its auxiliary model calls with a
purpose, and they name noreasoningEffort:@deepseek-ai/dsh-compaction-basic—summarizeWithLlm(lib/index.js:299) streams withpurpose: "compaction"and noreasoningEffort;@deepseek-ai/dsh-session-title-llm(lib/index.js:216) streams withpurpose: "session-title"and noreasoningEffort.@deepseek-ai/dsh-llmthen fills the gap from the route:resolveCallWithInfo(lib/index.js:2147) takesSo on any route whose profile default level is a thinking level, compaction and session titles think.
Why that is wrong rather than merely expensive.
maxTokens). Thinking spends that cap first, which is the failure the old Qwen plugin's author described: a summariser whose thinking eats the cap truncates the checkpoint.@deepseek-ai/dsh-llm-deepseek'sresolveThinking(lib/index.js:32) returns{ thinking: "disabled" }forpurpose === "session-title", and the compaction call carriesx-deepseek-harness-compact: 1for the server to act on. A pi-ai-backed route has no equivalent, so the same harness behaves two ways depending on which adapter serves the model.Suggested patch (one place, no schema change): treat an auxiliary purpose as naming
offwhen the call names no level, in whichever layer resolves the default — e.g. indsh-llm'sresolveCallWithInfo:Every reasoning model the harness can route lists
offamong its efforts, so the existingUNSUPPORTED_REASONING_EFFORTcheck stays the guard it is today. Alternatively a profile field (auxiliaryReasoning, defaulting tooff) would let a deployment opt out; the seam-level default seems truer to the intent.Acceptance. With a thinking route, a session's first prompt issues a
purpose: "session-title"request with no thinking (noreasoning_content, no reasoning tokens in usage), and a compaction call behaves the same, while an ordinary turn still thinks at the route's default.Local mitigation meanwhile. TinyTitan ships
plugins/dsh-tinytitan, a thin bundle that mounts a compaction backend forcingofffor those calls; the route's ownreasoningdefault still decides ordinary turns.(Re-verified against
@deepseek-ai/dsh0.1.6-alpha.2; the line numbers above are that version's.)All reactions