Skip to content

fix(chat): name the chat-template rejection instead of blaming model/temperature - #5309

Merged
senamakel merged 1 commit into
tinyhumansai:mainfrom
M3gA-Mind:fix/5291-lmstudio-template
Aug 2, 2026
Merged

senamakel merged 1 commit into
tinyhumansai:mainfrom
M3gA-Mind:fix/5291-lmstudio-template

Conversation

@M3gA-Mind

@M3gA-Mind M3gA-Mind commented Jul 31, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Problem

From the user log (openhuman.2026-07-28.log), LM Studio serving qwen/qwen3.5-9b with supports_native_tools=false:

lmstudio returned: Engine protocol predict request returned 400:
{"error":{"code":400,"message":"Unable to generate parser for this template.
Automatic parser generation failed: While executing CallExpression at line 79,
column 24 in source: ...multi_step_tool %}  {{- raise_exception('No user query
found in messages.') }}...Error: Jinja Exception: No user query found in
messages.","type":"invalid_request_error"}}

Tracing the arm order in classify_inference_error turned 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:

Rendering Claimed by What the user was told
Retry aggregate (format_failure_aggregate) auth_error — the aggregate ends with "Connections → API keys → LLM", and that arm matches the bare substring "api key" "There's an authentication issue… check your API key"
Same aggregate, if it got past the auth arm config-rejection — "may not be available on your provider" "rejected the request's model or temperature setting" (#5291's reported symptom)

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 convention web_errors.rs already 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 template anywhere in src/ or app/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_reason keys off the raw error string, not error_type, and this is not added to expected_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

  • Tests added or updated (happy path + at least one failure / edge case) — 3 in web_tests.rs: the verbatim #5291 body classifies and names the template; the retry aggregate does not fall to the auth or config-rejection copy; and a genuine invalid temperature body still classifies as model_unavailable (guards the arm above it from over-reaching). Plus 4 unit tests in the new classifier module.
  • Diff coverage ≥ 80% — N/A locally: could not run pnpm test:rust / pnpm test:coverage in 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.
  • Coverage matrix updated — N/A: behaviour-only change (error copy/classification, no new feature row).
  • All affected feature IDs from the matrix are listed under ## Related — N/A: no matrix feature IDs affected.
  • No new external network dependencies introduced — none; the classifier is a pure string predicate.
  • Manual smoke checklist updated if this touches release-cut surfaces — N/A: no release-cut surface touched.
  • Linked issue closed via Closes #NNN in the ## Related section.

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_type token a user sees.

ChatErrorEvent.error_type in app/src/services/chatService.ts gains 'chat_template_rejected'. No other frontend change is needed: ChatRuntimeProvider forwards the server message for every type and only special-cases 'cancelled'.

Related


AI Authored PR Metadata (required for Codex/Linear PRs)

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: fix/5291-lmstudio-template
  • Commit SHA: see ## Related / branch head

Validation Run

  • pnpm --filter openhuman-app format:check — not run (no node_modules in this environment)
  • pnpm typecheck — not run (same)
  • Focused tests: not run locally — see Validation Blocked
  • Rust fmt/check (if changed): rustfmt --edition 2021 --check clean on all 4 changed Rust files (formatter only, no compilation)
  • Tauri fmt/check (if changed): N/A — no app/src-tauri change

Validation Blocked

  • command: cargo test --lib / pnpm test:rust / pnpm typecheck
  • error: local disk at capacity — a Rust build in this repo produces a 15–30 GB target/, and the frontend workspace has no installed node_modules
  • impact: 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 existing is_budget_exhausted_message (same PHRASES + .any() shape, so the contains(&&str) pattern resolves); the arm's provider / fallback_available moves are valid in an exclusive if/else if chain, as every neighbouring arm already does the same; extract_backend_error_code anchors 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

  • Intended behavior change: a chat-template 400 is classified as chat_template_rejected instead of auth_error / model_unavailable.
  • User-visible effect: the chat error names the chat template as the cause and offers two remediations that can actually work (start a new chat to reset the history; pick a model with native tool support), instead of sending the user to their API key or their model/temperature settings.

Parity Contract

  • Legacy behavior preserved: every other classification arm is untouched; a genuine 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.
  • Guard/fallback/dispatch parity checks: sentry_suppression_reason (raw-string keyed) and expected_error_kind are both untouched, so no Sentry volume change in either direction; should_drop_session_agent keys off provider_request_rejected and is unaffected.

Duplicate / Superseded PR Handling

  • Duplicate PR(s): none
  • Canonical PR: this one
  • Resolution: N/A

…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

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

M3gA-Mind has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 18 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 069e6a6b-5e26-4120-a6d5-7da51244e6cf

📥 Commits

Reviewing files that changed from the base of the PR and between 43cc1b4 and 49fe1a5.

📒 Files selected for processing (5)
  • app/src/services/chatService.ts
  • src/openhuman/inference/provider/chat_template.rs
  • src/openhuman/inference/provider/mod.rs
  • src/openhuman/web_chat/web_errors.rs
  • src/openhuman/web_chat/web_tests.rs

Comment @coderabbitai help to get the list of available commands.

@senamakel
senamakel merged commit 7a87e9d into tinyhumansai:main Aug 2, 2026
23 of 24 checks passed
senamakel pushed a commit to nocstah/openhuman that referenced this pull request Sep 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants