Repository navigation
fix(chat): name the chat-template rejection instead of blaming model/temperature - #5309
Conversation
…temperature
A local runtime renders every request through the model's own Jinja chat
template. When the template raises, it returns a 400 before the model is
called and the body describes a template failure — LM Studio, Qwen 3.5:
Unable to generate parser for this template. […]
Error: Jinja Exception: No user query found in messages.
The user was told "Your AI provider rejected the request's model or
temperature setting. Check your model and routing in Settings → LLM."
Nothing in the raw body matches the config-rejection classifier, but the
retry aggregate that wraps the failed attempts does, via "may not be
available on your provider". The same aggregate ends with "Connections →
API keys → LLM", which the auth arm claims on the bare "api key"
substring — so the other rendering of this failure reads as a credential
problem. The model, the temperature and the key are all fine, and every
remediation offered is a dead end.
Classify the template failure on its own anchors (the raised guard text,
the Jinja exception marker, LM Studio's parser-generation prefix), above
both arms that would otherwise claim it, and say what actually happened.
Sentry behaviour is unchanged: classification here is user-facing only,
and `sentry_suppression_reason` keys off the raw error string.
Refs tinyhumansai#5291
There was a problem hiding this comment.
M3gA-Mind has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 18 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
Comment |
Summary
400before the model is called — and the body describes a template failure, not a bad model or a bad sampling parameter.is_chat_template_rejection_message+ achat_template_rejectedarm inclassify_inference_errorthat says what actually happened.Problem
From the user log (
openhuman.2026-07-28.log), LM Studio servingqwen/qwen3.5-9bwithsupports_native_tools=false:Tracing the arm order in
classify_inference_errorturned up something the issue did not capture, and it changed where this arm belongs. Both renderings of this failure were already being claimed by an arm that misdiagnoses them:format_failure_aggregate)auth_error— the aggregate ends with"Connections → API keys → LLM", and that arm matches the bare substring"api key""may not be available on your provider"Nothing in the raw per-attempt body matches the config-rejection phrase list at all — it is the aggregate wrapper that trips both.
Solution
Classify the template failure on its own anchors — the raised guard text (
no user query found in messages), the Jinja exception marker, and LM Studio's parser-generation prefix — and place the arm with the other specific-anchor arms, above the whole broad substring ladder, so neither the auth arm nor the config-rejection arm can claim it. That placement follows the conventionweb_errors.rsalready documents for specific anchors (empty_response,max_iterations,turn_timeout).The anchors are template-engine strings that no unrelated provider error emits (verified: zero other occurrences of
jinja/no user query found/generate parser for this templateanywhere insrc/orapp/src/), so claiming this early is safe.Marked retryable: the failure needs a tool-step history with no resolvable user turn, and a fresh turn starts from the user's new message. tinyhumansai/tinyagents#86 removes the shape that triggers it at all.
Sentry is deliberately untouched.
sentry_suppression_reasonkeys off the raw error string, noterror_type, and this is not added toexpected_error_kind— a template rejecting our own message shape is a defect on our side and should stay visible until the harness normalization ships everywhere.Submission Checklist
web_tests.rs: the verbatim#5291body classifies and names the template; the retry aggregate does not fall to the auth or config-rejection copy; and a genuineinvalid temperaturebody still classifies asmodel_unavailable(guards the arm above it from over-reaching). Plus 4 unit tests in the new classifier module.N/A locally: could not runpnpm test:rust/pnpm test:coveragein this environment (see Validation Blocked). The changed Rust lines are a predicate + one classifier arm, both directly exercised by the 7 new tests; CI is the authority.N/A: behaviour-only change(error copy/classification, no new feature row).## Related—N/A: no matrix feature IDs affected.N/A: no release-cut surface touched.Closes #NNNin the## Relatedsection.Impact
Desktop/CLI, chat error surface only. No runtime, performance, security, or migration impact. Routing, retry, and fallback behaviour are all unchanged — only the copy and the
error_typetoken a user sees.ChatErrorEvent.error_typeinapp/src/services/chatService.tsgains'chat_template_rejected'. No other frontend change is needed:ChatRuntimeProviderforwards the servermessagefor every type and only special-cases'cancelled'.Related
vendor/tinyagentspin; it stands alone.auth_errorarm matches the bare substring"api key", which any error text mentioning "API keys" trips. Pre-existing and left alone here; worth tightening separately.openhumanmaincurrently pinsvendor/tinyagentsat3781540, which is not an ancestor of tinyagentsmain(and is missing two commits that are on it). Whoever bumps the pin for part (a) will need to reconcile that.AI Authored PR Metadata (required for Codex/Linear PRs)
Linear Issue
Commit & Branch
fix/5291-lmstudio-template## Related/ branch headValidation Run
pnpm --filter openhuman-app format:check— not run (nonode_modulesin this environment)pnpm typecheck— not run (same)rustfmt --edition 2021 --checkclean on all 4 changed Rust files (formatter only, no compilation)N/A— noapp/src-taurichangeValidation Blocked
command:cargo test --lib/pnpm test:rust/pnpm typecheckerror:local disk at capacity — a Rust build in this repo produces a 15–30 GBtarget/, and the frontend workspace has no installednode_modulesimpact:part (b) is verified by inspection, with CI as the authority on compilation. What was checked by reading: the new predicate is structurally identical to the existingis_budget_exhausted_message(samePHRASES+.any()shape, so thecontains(&&str)pattern resolves); the arm'sprovider/fallback_availablemoves are valid in an exclusiveif/else ifchain, as every neighbouring arm already does the same;extract_backend_error_codeanchors on"errorCode"and so does not trip on LM Studio's"code":400; and no arm above the insertion point matches the body. Part (a) (fix(harness): guarantee a resolvable user turn for non-native-tool models tinyagents#86) was fully built and tested locally — 1232/1232, clippy clean.Behavior Changes
400is classified aschat_template_rejectedinstead ofauth_error/model_unavailable.Parity Contract
invalid temperature/ unknown-model / auth body classifies exactly as before (pinned by a test). Sentry capture, retry, fallback, and session-eviction behaviour are all unchanged.sentry_suppression_reason(raw-string keyed) andexpected_error_kindare both untouched, so no Sentry volume change in either direction;should_drop_session_agentkeys offprovider_request_rejectedand is unaffected.Duplicate / Superseded PR Handling