Skip to content

feat(tool_parser,reasoning_parser): add Step-3.5 tool and reasoning parsers#1817

Open
slin1237 wants to merge 1 commit into
mainfrom
feat/step3p5-parser
Open

feat(tool_parser,reasoning_parser): add Step-3.5 tool and reasoning parsers#1817
slin1237 wants to merge 1 commit into
mainfrom
feat/step3p5-parser

Conversation

@slin1237

@slin1237 slin1237 commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

Description

Problem

SMG has no parser for Step-3.5, so its XML-framed tool calls and <think> reasoning are not extracted.

Solution

Add Step-3.5 tool and reasoning parsers, registered so Step-3.5 model IDs route to them ahead of the broad step3 patterns (first substring match wins, and step3.5/step3p5 both contain step3).

Changes

  • Tool parser (crates/tool_parser/src/parsers/step3p5.rs): XML framing — <tool_call><function=name><parameter=key>value</parameter></function></tool_call> — with schema-typed value coercion (string/int/num/bool/object families, literal null) and a JSON-first inference fallback when no schema is available; incremental streaming with partial-token hold-back. The single .expect() is on compile-time regex literals (#[expect(reason=…)]).
  • Reasoning parser (crates/reasoning_parser/src/parsers/step3p5.rs): <think> / </think> via BaseReasoningParser with always_in_reasoning=true (the template prefills the think prefix, matching step3).
  • Registration: both factories register step3p5 and route step3.5 / step-3.5 / Step-3.5* / stepfun-ai/Step-3.5* to it, before the broad step3 match.

Test Plan

  • cargo +nightly fmt --all — clean
  • cargo clippy -p tool-parser -p reasoning-parser --all-targets -- -D warnings — clean
  • cargo test -p tool-parser -p reasoning-parser — all pass, incl. the new tool_parser_step3p5 suite (36 cases: single/parallel/multiple-function blocks, schema coercion + inference branches, streaming across chunk/char/multibyte boundaries and split inside function/parameter tags, malformed/incomplete/invalid input, reset, factory routing step3.5 → step3p5 not step3).
Checklist
  • cargo +nightly fmt passes
  • cargo clippy --all-targets -- -D warnings passes (parser crates; default features)
  • (Optional) Documentation updated

Summary by CodeRabbit

Release Notes

  • New Features
    • Added full support for Step-3.5 model variants with automatic model ID recognition and routing.
    • Step-3.5 reasoning parser now available for processing thinking and reasoning blocks with proper token handling.
    • Step-3.5 tool parser now available for extracting tool calls and parameters from Step-3.5 model outputs.

…arsers

Tool parser handles Step-3.5's XML framing (<tool_call><function=name>
<parameter=key>value</parameter></function></tool_call>) with schema-typed value
coercion (JSON-first inference fallback) and incremental streaming. Reasoning
parser uses <think>/</think> with always_in_reasoning=true (template prefills the
think prefix, matching step3). Both register Step-3.5 patterns ahead of the
broad step3 match so Step-3.5 IDs are not captured by the step3 parser.

Signed-off-by: Simo Lin <linsimo.mark@gmail.com>
@slin1237 slin1237 requested a review from CatherineSue as a code owner June 22, 2026 12:28
@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: aa700e47-1bfe-433c-86fa-d1b8b79ad958

📥 Commits

Reviewing files that changed from the base of the PR and between 6c3ce8e and 205643b.

📒 Files selected for processing (9)
  • crates/reasoning_parser/src/factory.rs
  • crates/reasoning_parser/src/lib.rs
  • crates/reasoning_parser/src/parsers/mod.rs
  • crates/reasoning_parser/src/parsers/step3p5.rs
  • crates/tool_parser/src/factory.rs
  • crates/tool_parser/src/lib.rs
  • crates/tool_parser/src/parsers/mod.rs
  • crates/tool_parser/src/parsers/step3p5.rs
  • crates/tool_parser/tests/tool_parser_step3p5.rs

📝 Walkthrough

Walkthrough

Adds Step3p5Parser to both the reasoning_parser and tool_parser crates. The reasoning parser wraps BaseReasoningParser with always_in_reasoning=true. The tool parser implements XML-based <tool_call>/<function=...>/<parameter=...> parsing with schema-driven value coercion and an incremental streaming state machine. Both parsers are wired into their respective factory registries with Step-3.5 model-ID pattern mappings.

Changes

Step-3.5 Parser Support

Layer / File(s) Summary
Step3p5 reasoning parser struct and trait impl
crates/reasoning_parser/src/parsers/step3p5.rs, crates/reasoning_parser/src/parsers/mod.rs, crates/reasoning_parser/src/lib.rs
Defines Step3p5Parser wrapping BaseReasoningParser with always_in_reasoning=true and <think>/</think> tokens, implements ReasoningParser by full delegation, adds Default, and exports the type.
Reasoning parser factory registration and routing
crates/reasoning_parser/src/factory.rs
Registers "step3p5" in ParserFactory::new(), adds step3.5/step-3.5/step3p5 pattern entries before step3 to prevent substring misrouting, and adds a confirming unit test.
Step3p5 tool parser struct, value coercion, and complete parsing
crates/tool_parser/src/parsers/step3p5.rs (lines 1–253)
Defines Step3p5Parser with regex-based state fields. Implements coerce_value (schema-driven: null, strings, int/uint/float, bool, object/array), infer_value (JSON-first, Python literals, string fallback), parse_xml_format (function+parameter extraction), and extract_all (complete-mode scanning).
Step3p5 incremental streaming state machine
crates/tool_parser/src/parsers/step3p5.rs (lines 255–504)
Implements parse_and_stream_parameters emitting JSON deltas, reset_streaming_state, and the full ToolParser trait with parse_incremental: buffers input, detects tool-call start, validates function names, streams parameter deltas, performs brace-balancing at tool-call end, and advances tool indices.
Tool parser factory wiring
crates/tool_parser/src/factory.rs, crates/tool_parser/src/parsers/mod.rs, crates/tool_parser/src/lib.rs
Registers Step3p5Parser in ParserFactory::new(), adds step3.5/step-3.5/stepfun-ai model-ID mappings, declares the step3p5 module, and re-exports the type.
Unit and integration tests
crates/reasoning_parser/src/parsers/step3p5.rs, crates/tool_parser/src/parsers/step3p5.rs, crates/tool_parser/tests/tool_parser_step3p5.rs
Unit tests cover coerce_value/infer_value branches, reasoning model-type/state/reset. Integration tests cover factory routing, complete/streaming parsing, type coercion, split-boundary robustness, special characters, schema fallback, multiline values, and float precision.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • lightseekorg/smg#1505: Adds a new parser variant (NoneParser) through the same factory.rs + lib.rs + parsers/mod.rs wiring pattern in crates/reasoning_parser, directly parallel to the Step-3.5 parser registration added here.

Suggested labels

reasoning-parser, tests, tool-parser

Suggested reviewers

  • CatherineSue
  • key4ng

Poem

🐇 Hippity-hop, a new parser arrives,
Step-3.5 now thinks and derives!
<think> tags stream, parameters flow,
Coercion is neat, brace-balance in tow.
The factory routes with no substring snares —
This rabbit approves: no mismatching cares! ✨

🚥 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 and concisely summarizes the main change: adding Step-3.5 parsers for both tool and reasoning parsing across two crates.
Docstring Coverage ✅ Passed Docstring coverage is 89.87% which is sufficient. The required threshold is 80.00%.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 feat/step3p5-parser

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

@github-actions github-actions Bot added tests Test changes tool-parser Tool/function call parser changes reasoning-parser Reasoning parser changes labels Jun 22, 2026

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces support for Step-3.5 models by adding dedicated reasoning and tool parsers. The new Step3p5Parser in reasoning_parser handles reasoning outputs that start immediately inside the thinking state, while the Step3p5Parser in tool_parser parses XML-framed tool calls with schema-based type coercion and JSON-first inference. Extensive integration and unit tests are also added to verify routing, streaming, and edge cases. Feedback on the changes suggests improving integer parsing in the tool parser by attempting to parse as u64 when i64 parsing fails, which prevents large unsigned integers from unnecessarily degrading to strings.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +93 to +97
if let Ok(n) = trimmed.parse::<i64>() {
return Some(Value::Number(n.into()));
}
// vLLM degrades to string on parse failure.
return Some(Value::String(raw.to_string()));

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.

medium

When parsing integer types (such as uint64 or unsigned long), values exceeding i64::MAX will fail to parse as i64 and degrade to a string representation. To better support large unsigned integers, we can attempt to parse as u64 if the i64 parsing fails.

        if let Ok(n) = trimmed.parse::<i64>() {
            return Some(Value::Number(n.into()));
        }
        if let Ok(n) = trimmed.parse::<u64>() {
            return Some(Value::Number(n.into()));
        }
        // vLLM degrades to string on parse failure.
        return Some(Value::String(raw.to_string()));

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 205643b504

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +456 to +460
let current_args = &self.streamed_args_for_tool[self.current_tool_id as usize];
if !current_args.is_empty() {
let open_braces = current_args.matches('{').count();
let close_braces = current_args.matches('}').count();
if open_braces > close_braces {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Close streamed argument objects unconditionally

When a streamed string argument contains a } before </tool_call> (for example code snippets or text like foo }), this raw brace count can make open_braces == close_braces, so the parser skips appending the outer JSON object's closing brace. The emitted argument delta remains malformed (e.g. {"text": "foo }"), and the final unstreamed-args fallback cannot repair it because it compares against compact serde_json output with different spacing; since this parser always opens the argument object in parse_and_stream_parameters and never streams the outer close elsewhere, close it based on parser state rather than counting braces inside serialized values.

Useful? React with 👍 / 👎.

Comment on lines +198 to +199
// Step-3.5 must be registered BEFORE step3: first substring match wins and
// "step3.5"/"step3p5" both contain "step3".

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Nit: This comment is indented to align with the trailing // on the kimi line above, making it visually appear as a continuation of the kimi comment. Moving it to a standalone block comment before line 200 would make the association with the step3.5 registrations clearer.

Suggested change
// Step-3.5 must be registered BEFORE step3: first substring match wins and
// "step3.5"/"step3p5" both contain "step3".
// Step-3.5 must be registered BEFORE step3: first substring match wins and
// "step3.5"/"step3p5" both contain "step3".

@claude claude 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.

Clean, well-structured PR. The Step-3.5 tool and reasoning parsers correctly port the vLLM XML framing, the type coercion logic faithfully mirrors _convert_param_value, and factory routing is correct in both registries (first-substring-match for reasoning, longest-prefix-wins for tool parser). Test coverage is thorough at 36 cases spanning coercion branches, streaming boundary conditions, malformed input, and factory routing disambiguation vs step3.

0 🔴 Important · 1 🟡 Nit · 0 🟣 Pre-existing

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

This pull request has been automatically marked as stale because it has not had any activity within 14 days. It will be automatically closed if no further activity occurs within 16 days. Leave a comment if you feel this pull request should remain open. Thank you!

@github-actions github-actions Bot added the stale PR has been inactive for 14+ days label Jul 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

reasoning-parser Reasoning parser changes stale PR has been inactive for 14+ days tests Test changes tool-parser Tool/function call parser changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant