fix(llm-pi-ai): describing a model must not fail on a bad profile level

`resolveModel` validated the profile's reasoning level against the exact
model and threw when it did not fit. That call builds the model catalog,
and the catalog build catches per PROVIDER — so one mis-set field took the
whole provider out of every picker behind a single error row, hiding even
the models that do support the level. Measured: `anthropic` set to `max`
threw for six of its eight models.

Describing what a model can do now reports an unusable profile level as no
default rather than throwing; the request path still refuses it, which is
where a bad configuration belongs. The existing spec asserted the old
throw and now asserts both halves of that split.

Known gap, left deliberately: a model that cannot take the route's level
still fails its first request while the picker shows 「Default」 for it,
because the request path keeps using the profile level as the fallback.
Reaching that needs a hand-written `settings.yaml` — the Models page no
longer writes the field — and the error names the model and the level, so
selecting a supported level is a way out. Closing it properly means giving
`AgentOptions` a `reasoningEffort` so compositions without a model picker
keep an entry point, then dropping the provider-scoped field altogether;
that is its own change.
This commit is contained in:
Yichen Jiang
2026-08-07 18:07:15 +08:00
parent 2dc1406dfd
commit f3049e5663
5 changed files with 41 additions and 7 deletions

View File

@@ -94,6 +94,29 @@ function profileOptions(
}
}
/**
* The profile default this exact model can actually take, for DESCRIBING it.
* A configured level the model does not support yields none rather than
* throwing: `resolveModel` builds the model catalog, and a catalog that fails
* takes its whole provider out of every picker — so one mis-set profile field
* would hide every model on the route, including the ones that support the
* level. The request path still refuses, which is where a bad configuration
* belongs: describing what a model can do must not fail because a deployment
* asked it for something it cannot.
* @param model - the resolved model descriptor.
* @param effort - the profile's configured level, if any.
* @returns the level when this model supports it, otherwise undefined.
*/
function describableReasoningLevel(
model: Model<Api>,
effort: ReasoningEffortIdType | ModelThinkingLevel | undefined,
): ModelThinkingLevel | undefined {
if (effort === undefined) return undefined
return getSupportedThinkingLevels(model).some(level => level === effort)
? effort as ModelThinkingLevel
: undefined
}
/** Validate an explicit Harness/profile effort without invoking pi-ai's clamp. */
function resolveReasoningLevel(
model: Model<Api>,
@@ -229,7 +252,7 @@ export class PiAiAdapter extends LlmAdapter {
const snapshot = this.current()
const profile = this.profileOf(snapshot, provider)
const resolvedModel = this.modelOf(snapshot, provider, model)
const defaultLevel = resolveReasoningLevel(resolvedModel, profile.reasoning)
const defaultLevel = describableReasoningLevel(resolvedModel, profile.reasoning)
// Only a cap the deployment configured is a request default; the
// catalog's `maxTokens` sizes the model and stops there.
const configuredMaxTokens = profile.configuredMaxTokens.get(model)