fix: stream reasoning_content in LM Studio provider - #1175
Conversation
The LM Studio handler only read `delta.content` and relied on TagMatcher to extract <think>/<thought> tags. Reasoning models served by LM Studio (Qwen3, DeepSeek-R1, QwQ) stream thinking in a dedicated `reasoning_content` field, which TagMatcher never sees, so the whole thought stream was dropped. The same model via the OpenAI Compatible provider worked because that handler already calls the shared `extractReasoningFromDelta` helper — LM Studio was the last provider missing it. Reasoning text is now also counted toward output tokens; thinking models previously reported ~0 output tokens for turns that were mostly reasoning. Fixes Zoo-Code-Org#1075
📝 WalkthroughWalkthroughLM Studio streaming now extracts reasoning from ChangesLM Studio reasoning output
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related issues
Possibly related PRs
Suggested labels: Suggested reviewers: Sequence Diagram(s)sequenceDiagram
participant LMStudio
participant Provider
participant TagMatcher
participant Output
LMStudio->>Provider: Send streaming delta
Provider->>Provider: Extract reasoning_content or reasoning
Provider->>Output: Emit reasoning event
Provider->>TagMatcher: Process visible content
TagMatcher->>Output: Emit text or tagged reasoning event
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
src/api/providers/__tests__/lmstudio.spec.tsESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox. src/api/providers/lm-studio.tsESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
src/api/providers/__tests__/lmstudio.spec.ts (2)
129-133: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winCover the new output-token contract.
Add a deterministic assertion for the
usage.outputTokenschunk. It must include both dedicated reasoning text and visible content; otherwise the accounting change insrc/api/providers/lm-studio.tsLines 165-167 can regress without failing these tests.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/api/providers/__tests__/lmstudio.spec.ts` around lines 129 - 133, Add an assertion in the test covering handler.createMessage to verify the usage.outputTokens chunk includes both dedicated reasoning text and visible content, using deterministic expected values from the mocked stream. Preserve the existing reasoning and text chunk assertions.
129-133: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winAssert the emitted chunk order.
src/test-utils/stream.tsyields fixture chunks in order, buttoContainEqualchecks only membership. In thereasoning_contentand<think>cases, compare the filteredreasoningandtextchunks as an ordered array so a text-before-reasoning regression fails.Also applies to: 164-166
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/api/providers/__tests__/lmstudio.spec.ts` around lines 129 - 133, Update the assertions in the affected reasoning_content and <think> test cases around handler.createMessage to filter the emitted chunks to reasoning and text entries and compare the resulting array in order, expecting reasoning before text. Replace the membership-only toContainEqual checks while preserving the existing chunk values and test coverage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/api/providers/__tests__/lmstudio.spec.ts`:
- Around line 129-133: Add an assertion in the test covering
handler.createMessage to verify the usage.outputTokens chunk includes both
dedicated reasoning text and visible content, using deterministic expected
values from the mocked stream. Preserve the existing reasoning and text chunk
assertions.
- Around line 129-133: Update the assertions in the affected reasoning_content
and <think> test cases around handler.createMessage to filter the emitted chunks
to reasoning and text entries and compare the resulting array in order,
expecting reasoning before text. Replace the membership-only toContainEqual
checks while preserving the existing chunk values and test coverage.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: c6e16a66-69ca-4e14-bc60-a059e33b59f5
📒 Files selected for processing (2)
src/api/providers/__tests__/lmstudio.spec.tssrc/api/providers/lm-studio.ts
Related GitHub Issue
Closes: #1075
Description
Reasoning models served by LM Studio stream their thinking in a dedicated
reasoning_content(orreasoning) delta field. The LM Studio handler only readdelta.contentand passed it throughTagMatcherto extract<think>/<thought>tags, so the entire thought stream was silentlydropped for these models.
The repo already has a shared helper for exactly this —
extractReasoningFromDeltainsrc/api/providers/utils/extract-reasoning.ts— used by 13 providers (openai,deepseek,lite-llm,kenari,mimo,qwen-code,requesty,unbound,opencode-go,base-openai-compatible-provider, …). LM Studio was the only one still missing it. That is whythe reporter saw the same model + same LM Studio instance display thinking correctly through the
OpenAI Compatible provider but not through the LM Studio provider.
The fix mirrors the existing call site in
openai.tsrather than introducing a new pattern:Test Procedure
Automated. Three tests added to
src/api/providers/__tests__/lmstudio.spec.ts, following theexisting patterns in
openai.spec.ts:streams reasoning chunks from delta.reasoning_content— the issue reproduction; asserts both thereasoningchunk and thetextchunk are emitted, so a regression that breakscontenthandlingis also caught
falls back to delta.reasoning when reasoning_content is absent— the helper's fallback pathstill parses <think> tags embedded in content— regression guard for models that inline reasoningas tags (this one passes before the fix too; it exists to pin existing behaviour)
Verified these are genuine regression tests, not just passing tests: with the handler reverted to its
pre-fix state the reasoning tests fail (
0reasoning chunks emitted); with the fix they pass.cd src npx vitest run api/providers/__tests__/lmstudio.spec.tsManual verification was not possible — I don't have an LM Studio instance with a reasoning model
in this environment. The tests exercise the delta shapes the issue describes, but a reviewer with the
reporter's setup (LM Studio 0.4.20 + Qwen3.6 35B A3B) confirming thinking now renders would be
valuable before merge.
Pre-Submission Checklist
Visual Snapshots
N/A
Videos (interaction / animation only)
N/A
Documentation Updates
Additional Notes
N/A
Get in Touch
hehegwk_23849
Summary by CodeRabbit
<think>tags.