Skip to content

Pass reasoning format through Skippy chat templates - #947

Merged
ndizazzo merged 2 commits into
mainfrom
fix-gemma4-channel-reasoning
Jul 3, 2026
Merged

Pass reasoning format through Skippy chat templates#947
ndizazzo merged 2 commits into
mainfrom
fix-gemma4-channel-reasoning

Conversation

@michaelneale

@michaelneale michaelneale commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

🤖 Opened by AI agent.

Summary:

  • Pass Skippy chat-template reasoning_format into llama.cpp's established reasoning parser via the C ABI.
  • Default embedded OpenAI chat templating to hidden reasoning parsing so Gemma 4 thought channels are removed from content but not exposed as reasoning_content unless configured.
  • Add UI fallback coverage for real Gemma channel markers.

Validation:

  • scripts/prepare-llama.sh pinned with a clean temp LLAMA_WORKDIR
  • just build
  • cargo fmt --all --check
  • cargo test -p skippy-server --lib
  • cargo test -p skippy-runtime --lib
  • cargo test -p mesh-llm --lib
  • cargo check -p mesh-llm
  • cargo clippy -p skippy-server --all-targets -- -D warnings
  • cargo clippy -p mesh-llm --all-targets -- -D warnings
  • pnpm test -- src/features/chat/components/thinking-segments.test.ts

Summary by CodeRabbit

  • New Features
    • Added configurable reasoning/thinking visibility for chat templates, including support for formats that hide or expose reasoning content.
  • Bug Fixes
    • Streaming and non-streaming parsing now consistently respects the selected reasoning visibility, preventing hidden reasoning from being emitted.
    • Improved Gemma “thought” section splitting to handle additional channel variants and newline-prefixed thought content.
    • Chat template rendering now passes through an explicit reasoning-format setting for more accurate template behavior.

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5977e36f-6c3c-4f43-b993-cb5bbf370e9d

📥 Commits

Reviewing files that changed from the base of the PR and between 5f92f76 and b1054d8.

📒 Files selected for processing (2)
  • crates/skippy-ffi/src/lib.rs
  • crates/skippy-runtime/src/lib.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/skippy-runtime/src/lib.rs

📝 Walkthrough

Walkthrough

This PR threads a new reasoning-format option from request defaults through runtime and FFI into chat-template rendering, then updates server parsing and streaming to hide or emit reasoning content accordingly. It also broadens Gemma thinking-segment parsing and tests.

Changes

Reasoning format feature

Layer / File(s) Summary
Runtime contract and options
crates/skippy-runtime/src/lib.rs, crates/skippy-bench/src/token_lengths.rs, crates/skippy-prompt/src/prompt_cli/prompt_format.rs
Adds ChatReasoningFormat, extends chat template options with reasoning_format, and passes explicit None at existing call sites.
FFI bridge and ABI checks
crates/skippy-ffi/src/lib.rs
Bumps the ABI patch version, verifies runtime ABI compatibility, extends skippy_apply_chat_template_json, and adds ABI tests.
Native reasoning-format passthrough
third_party/llama.cpp/patches/0011-Pass-reasoning-format-through-stage-chat-templates.patch
Updates the native chat-template patch to accept and map reasoning_format_name, and bumps the native ABI macro.
Request defaults and prompt rendering
crates/skippy-server/src/frontend/request.rs, crates/skippy-server/src/frontend/prompting.rs
Maps embedded defaults into reasoning_format and forwards it into JSON template rendering.
Reasoning visibility and streaming
crates/skippy-server/src/frontend.rs, crates/skippy-server/src/frontend/backend.rs
Adds reasoning visibility gating, conditionally emits streamed reasoning, and threads the new flag through backend chat completion paths.
Reasoning format tests
crates/skippy-server/src/frontend/tests.rs
Adds and updates tests covering parser requirements, visibility behavior, and reasoning-format defaults and overrides.

Gemma thinking-segment tag parsing

Layer / File(s) Summary
Gemma tag constants and tests
crates/mesh-llm-ui/src/features/chat/components/thinking-segments.ts, crates/mesh-llm-ui/src/features/chat/components/thinking-segments.test.ts
Expands Gemma thought/boundary tag variants and updates the Gemma thinking-segment tests, including a legacy channel case.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant StageOpenAiBackend
  participant SkippyRuntime
  participant SkippyFFI as skippy-ffi/native
  participant ChatOutputStreamParser

  Client->>StageOpenAiBackend: chat_completion_stream(request)
  StageOpenAiBackend->>StageOpenAiBackend: chat_template_options(request_defaults)
  StageOpenAiBackend->>SkippyRuntime: apply_chat_template_json(reasoning_format)
  SkippyRuntime->>SkippyFFI: skippy_apply_chat_template_json(reasoning_format ptr)
  SkippyFFI-->>SkippyRuntime: rendered prompt
  StageOpenAiBackend->>StageOpenAiBackend: emit_reasoning from template_options
  StageOpenAiBackend->>ChatOutputStreamParser: new(emit_reasoning)
  ChatOutputStreamParser-->>StageOpenAiBackend: ReasoningDelta events when enabled
  StageOpenAiBackend-->>Client: streamed chat completion
Loading

Possibly related PRs

  • Mesh-LLM/mesh-llm#841: Both PRs modify Gemma channel/thought parsing logic in thinking-segments.ts and its tests.
  • Mesh-LLM/mesh-llm#890: Both PRs modify crates/skippy-server/src/frontend.rs logic controlling when chat output parsing or reasoning deltas are required or emitted.

Suggested reviewers: i386

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: threading reasoning format through Skippy chat templates and related plumbing.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-gemma4-channel-reasoning

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.

❤️ Share

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

@michaelneale

Copy link
Copy Markdown
Collaborator Author

@i386 someone smarter than me needs to review this!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
crates/skippy-server/src/frontend/backend.rs (1)

431-465: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider bundling stream-generation flags into a config struct.

run_generation_stream now has two adjacent same-typed booleans (parse_chat_output: bool, emit_reasoning: bool) on top of an already #[allow(clippy::too_many_arguments)]-flagged signature. Adjacent identically-typed positional bools are easy to transpose at a call site without a compiler error. Bundling parse_chat_output/emit_reasoning (and potentially include_usage) into a small named struct would remove this footgun and shrink the argument list.

♻️ Proposed direction
-    async fn run_generation_stream(
-        &self,
-        prompt: PreparedGenerationPrompt,
-        max_tokens: GenerationTokenLimit,
-        stop: Option<openai_frontend::StopSequence>,
-        sampling: SamplingConfig,
-        include_usage: bool,
-        hook_request: Option<ChatCompletionRequest>,
-        parse_chat_output: bool,
-        emit_reasoning: bool,
-        context: OpenAiRequestContext,
-        ids: OpenAiGenerationIds,
-    ) -> OpenAiResult<GenerationStream> {
+    struct ChatOutputStreamOptions {
+        parse_chat_output: bool,
+        emit_reasoning: bool,
+    }
+
+    async fn run_generation_stream(
+        &self,
+        prompt: PreparedGenerationPrompt,
+        max_tokens: GenerationTokenLimit,
+        stop: Option<openai_frontend::StopSequence>,
+        sampling: SamplingConfig,
+        include_usage: bool,
+        hook_request: Option<ChatCompletionRequest>,
+        chat_output: ChatOutputStreamOptions,
+        context: OpenAiRequestContext,
+        ids: OpenAiGenerationIds,
+    ) -> OpenAiResult<GenerationStream> {
🤖 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 `@crates/skippy-server/src/frontend/backend.rs` around lines 431 - 465, The
`run_generation_stream` signature in `backend.rs` has adjacent positional
booleans (`parse_chat_output`, `emit_reasoning`, and possibly `include_usage`)
that are easy to mix up and contribute to the too-many-arguments smell. Refactor
these stream-generation flags into a small named config struct, update
`run_generation_stream` to accept that struct, and adjust the
`ChatOutputStreamParser::new` call and all call sites to read flags from the new
type so the intent is explicit and the parameter list is shorter.
🤖 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.

Inline comments:
In `@crates/skippy-ffi/src/lib.rs`:
- Line 3: The ABI patch was bumped for the new chat-template arity, but
`skippy_apply_chat_template_json` can still be resolved on older runtimes and
then called with an incompatible C signature. Keep `ABI_VERSION_PATCH` at the
new value, and add an early runtime ABI check in `crates/skippy-ffi/src/lib.rs`
so pre-28 runtimes are rejected before any wrapper that uses
`skippy_apply_chat_template_json` can execute.

---

Nitpick comments:
In `@crates/skippy-server/src/frontend/backend.rs`:
- Around line 431-465: The `run_generation_stream` signature in `backend.rs` has
adjacent positional booleans (`parse_chat_output`, `emit_reasoning`, and
possibly `include_usage`) that are easy to mix up and contribute to the
too-many-arguments smell. Refactor these stream-generation flags into a small
named config struct, update `run_generation_stream` to accept that struct, and
adjust the `ChatOutputStreamParser::new` call and all call sites to read flags
from the new type so the intent is explicit and the parameter list is shorter.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 09f59d99-666e-4b63-8088-f980d9979a24

📥 Commits

Reviewing files that changed from the base of the PR and between 9547ac2 and 5f92f76.

📒 Files selected for processing (12)
  • crates/mesh-llm-ui/src/features/chat/components/thinking-segments.test.ts
  • crates/mesh-llm-ui/src/features/chat/components/thinking-segments.ts
  • crates/skippy-bench/src/token_lengths.rs
  • crates/skippy-ffi/src/lib.rs
  • crates/skippy-prompt/src/prompt_cli/prompt_format.rs
  • crates/skippy-runtime/src/lib.rs
  • crates/skippy-server/src/frontend.rs
  • crates/skippy-server/src/frontend/backend.rs
  • crates/skippy-server/src/frontend/prompting.rs
  • crates/skippy-server/src/frontend/request.rs
  • crates/skippy-server/src/frontend/tests.rs
  • third_party/llama.cpp/patches/0011-Pass-reasoning-format-through-stage-chat-templates.patch

Comment thread crates/skippy-ffi/src/lib.rs
@michaelneale

Copy link
Copy Markdown
Collaborator Author

🤖 Context on why this is larger than Goose:

Goose already has this knob exposed by its llama.cpp Rust binding: it sets OpenAIChatTemplateParams.reasoning_format and then calls apply_chat_template_oaicompat directly. Examples:

Mesh's Skippy path had the same conceptual requirement, but the C ABI boundary did not expose reasoning_format yet. This PR adds that missing ABI/runtime/server plumbing so Mesh uses llama.cpp's established reasoning parser instead of adding model-specific string stripping.

@ndizazzo

ndizazzo commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Mesh's Skippy path had the same conceptual requirement, but the C ABI boundary did not expose reasoning_format yet. This PR adds that missing ABI/runtime/server plumbing so Mesh uses llama.cpp's established reasoning parser instead of adding model-specific string stripping.

@michaelneale Yeah, I'd keep this and pass it directly into the template/parser... I think this is where we should pick apart model family markers.

We should still keep skippy-server using the parsed chat metadata, so the server can decide if it wants to parse, hide, or expose reasoning before it leaves the OpenAI backend.

One thing we should do though is update skippy-ffi to guard and reject runtimes > 0.1.28 before wrappers can call that function, since the skippy_apply_chat_template_json changed signatures.

Some tests that would help here would be to see hidden reasoning markers get stripped out from the content, and that the reasonsing_content gets populated

@ndizazzo ndizazzo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

see above

@michaelneale

Copy link
Copy Markdown
Collaborator Author

@ndizazzo how does this look now?

@ndizazzo ndizazzo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks good now!

@ndizazzo
ndizazzo merged commit 49cf034 into main Jul 3, 2026
29 checks passed
@ndizazzo
ndizazzo deleted the fix-gemma4-channel-reasoning branch July 3, 2026 23:03
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.

3 participants