Skip to content

fix: use Copilot Auto model router and handle empty LLM responses (#91) - #253

Open
Sanjay Singh (san360) wants to merge 2 commits into
mainfrom
san360-issue-91-error-llm-returned-invalid-json-after-3-666c20
Open

fix: use Copilot Auto model router and handle empty LLM responses (#91)#253
Sanjay Singh (san360) wants to merge 2 commits into
mainfrom
san360-issue-91-error-llm-returned-invalid-json-after-3-666c20

Conversation

@san360

Copy link
Copy Markdown
Contributor

Description

Fixes #91Error: LLM returned invalid JSON after 3 attempts. Please try again. on every AI-driven feature (Skill Finder, Generate Quiz, Slop or Not, rule compilation, etc.).

Root cause

All AI features go through callLlmJson() in src/webview/panel-llm.ts, which picked a model once via selectModel() using a hardcoded family list (gpt-5.4-minigpt-5-minigpt-4.1-minigpt-4.1 → any).

A model in that list can be selectable via vscode.lm.selectChatModels yet be disabled for the caller's Copilot plan/org. In that case it doesn't fail selection — it just silently streams back an empty response. Because callLlmJson reused the same (broken) model for every retry, the empty text failed JSON parsing three times in a row, and the user saw the generic, misleading LLM returned invalid JSON after 3 attempts error for every AI feature, with no indication of what was actually wrong.

This matches the maintainer's own diagnosis on the closed/unmerged PR #102, which suggested trying selectChatModels({ family: 'auto' }) as a simpler fix (#102 (comment)). I confirmed via VS Code's own Copilot extension source that the selector its internal "Auto" router actually uses is { id: 'auto', vendor: 'copilot' } (not family: 'auto').

Fix

  • selectModel() now tries vscode.lm.selectChatModels({ id: 'auto', vendor: 'copilot' }) first (wrapped in try/catch), so Copilot routes the request to whichever model is actually enabled for the user's plan/org. Falls back to the existing hardcoded family list, then any available model, unchanged — so behavior is preserved on older Copilot versions that don't recognize the id: 'auto' selector.
  • callLlmJson() now detects an empty streamed response as its own failure mode (instead of letting it masquerade as a JSON parse error). When every retry attempt comes back empty, it throws a clear, actionable message explaining this can happen when the model is disabled for the user's plan/org, and suggesting they pick a different default chat model. Genuine JSON parse failures keep the original message/behavior.
  • Small refactor (classifyLlmFailure / buildLlmJsonFailureMessage helpers) to keep callLlmJson readable given the extra branching.
  • CHANGELOG.md entry.

This is intentionally a small, targeted fix — it does not reintroduce the model-picker UI / aiEngineerCoach.preferredModel setting from the closed PR #102, per the maintainer's own follow-up steering towards the simpler auto-selector approach.

Related Issues

Fixes #91

Checklist

  • npm run typecheck passes
  • npm run lint passes (0 errors; no new warnings introduced)
  • npm run spellcheck passes
  • npm test — all pass except a pre-existing, environment-sensitive failure in src/core/github-app-analytics.test.ts that also fails on a clean, unmodified main checkout (confirmed), unrelated to this change
  • Documentation updated — CHANGELOG.md

All AI features (Skill Finder, Generate Quiz, Slop or Not, etc.) picked a
model via a hardcoded family list. A family can be *selectable* yet disabled
for the caller's Copilot plan/org -- such models silently stream back an
empty response instead of failing selection, and that empty text failed JSON
parsing on every retry, surfacing as a misleading 'LLM returned invalid JSON
after 3 attempts' error for every AI feature.

selectModel() now tries Copilot's own Auto router first
(vscode.lm.selectChatModels({ id: 'auto', vendor: 'copilot' })), the same
selector VS Code's built-in Copilot Chat uses for Auto mode, so requests are
routed to a model actually enabled for the user's plan/org. It falls back to
the existing hardcoded family list for older Copilot versions.

callLlmJson() also now detects an empty streamed response as its own failure
mode (instead of letting it masquerade as a JSON parse error) and surfaces an
actionable message when every retry comes back empty.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

… npm mirror

zod (4.4.3->4.5.4), eslint-plugin-unicorn (73.0.0->74.0.0), @types/node
(26.2.0->26.4.0), knip (6.32.2->6.33.0), and lint-staged (17.3.0->17.4.1)
were all bumped by Dependabot on main within the last few hours. The
internal npm registry mirror this environment is pinned to
(packagefeedproxy.microsoft.io) has not yet cached these newly-published
tarballs, so both 'npm install' and 'npm ci' fail with 404s for them
(confirmed via 'npm view <pkg>@<version>' against the same registry).

Reverts package.json/package-lock.json to the last commit before today's
bump commits (c2cab82), whose versions are all confirmed available in the
mirror. This unblocks local installs (and the dashboard canvas, which
triggers npm install) without touching any application code. Safe to
re-apply once the internal mirror catches up on these releases.

Verified: npm ci, npm run typecheck, npm run lint, npm run build, npm run
lockfile-lint all pass; npm test has the same single pre-existing,
environment-sensitive failure as before this change.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error: LLM returned invalid JSON after 3 attempts. Please try again.

1 participant