Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
change
sglang
Commits
5cfbb4c1
Unverified
Commit
5cfbb4c1
authored
Aug 20, 2025
by
Chang Su
Committed by
GitHub
Aug 20, 2025
Browse files
[router] add glm and step3 reasoning parser (#9415)
parent
e6523102
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
265 additions
and
8 deletions
+265
-8
sgl-router/src/reasoning_parser/factory.rs
sgl-router/src/reasoning_parser/factory.rs
+18
-7
sgl-router/src/reasoning_parser/mod.rs
sgl-router/src/reasoning_parser/mod.rs
+2
-1
sgl-router/src/reasoning_parser/parsers/glm45.rs
sgl-router/src/reasoning_parser/parsers/glm45.rs
+118
-0
sgl-router/src/reasoning_parser/parsers/mod.rs
sgl-router/src/reasoning_parser/parsers/mod.rs
+4
-0
sgl-router/src/reasoning_parser/parsers/step3.rs
sgl-router/src/reasoning_parser/parsers/step3.rs
+123
-0
No files found.
sgl-router/src/reasoning_parser/factory.rs
View file @
5cfbb4c1
...
...
@@ -5,7 +5,8 @@ use std::collections::HashMap;
use
std
::
sync
::{
Arc
,
Mutex
,
RwLock
};
use
crate
::
reasoning_parser
::
parsers
::{
BaseReasoningParser
,
DeepSeekR1Parser
,
KimiParser
,
Qwen3Parser
,
QwenThinkingParser
,
BaseReasoningParser
,
DeepSeekR1Parser
,
Glm45Parser
,
KimiParser
,
Qwen3Parser
,
QwenThinkingParser
,
Step3Parser
,
};
use
crate
::
reasoning_parser
::
traits
::{
ParseError
,
ParserConfig
,
ReasoningParser
};
...
...
@@ -153,15 +154,21 @@ impl ParserFactory {
// Register Kimi parser with Unicode tokens (starts with in_reasoning=false)
registry
.register_parser
(
"kimi"
,
||
Box
::
new
(
KimiParser
::
new
()));
// Register GLM45 parser (same format as Qwen3 but separate for debugging)
registry
.register_parser
(
"glm45"
,
||
Box
::
new
(
Glm45Parser
::
new
()));
// Register Step3 parser (same format as DeepSeek-R1 but separate for debugging)
registry
.register_parser
(
"step3"
,
||
Box
::
new
(
Step3Parser
::
new
()));
// Register model patterns
registry
.register_pattern
(
"deepseek-r1"
,
"deepseek_r1"
);
registry
.register_pattern
(
"qwen3-thinking"
,
"qwen3_thinking"
);
registry
.register_pattern
(
"qwen-thinking"
,
"qwen3_thinking"
);
registry
.register_pattern
(
"qwen3"
,
"qwen3"
);
registry
.register_pattern
(
"qwen"
,
"qwen3"
);
registry
.register_pattern
(
"glm45"
,
"
qwen3"
);
// GLM45 uses same format as Qwen3
registry
.register_pattern
(
"glm45"
,
"
glm45"
);
registry
.register_pattern
(
"kimi"
,
"kimi"
);
registry
.register_pattern
(
"step3"
,
"
deepseek_r1"
);
// Step3 alias for DeepSeek-R1
registry
.register_pattern
(
"step3"
,
"
step3"
);
Self
{
registry
}
}
...
...
@@ -281,13 +288,17 @@ mod tests {
}
#[test]
fn
test_
alias
_model
s
()
{
fn
test_
step3
_model
()
{
let
factory
=
ParserFactory
::
new
();
let
step3
=
factory
.create
(
"step3-model"
)
.unwrap
();
let
glm45
=
factory
.create
(
"glm45-v2"
)
.unwrap
();
assert_eq!
(
step3
.model_type
(),
"step3"
);
}
assert_eq!
(
step3
.model_type
(),
"deepseek_r1"
);
assert_eq!
(
glm45
.model_type
(),
"qwen3"
);
#[test]
fn
test_glm45_model
()
{
let
factory
=
ParserFactory
::
new
();
let
glm45
=
factory
.create
(
"glm45-v2"
)
.unwrap
();
assert_eq!
(
glm45
.model_type
(),
"glm45"
);
}
#[test]
...
...
sgl-router/src/reasoning_parser/mod.rs
View file @
5cfbb4c1
...
...
@@ -4,6 +4,7 @@ pub mod traits;
pub
use
factory
::{
ParserFactory
,
ParserRegistry
,
PooledParser
};
pub
use
parsers
::{
BaseReasoningParser
,
DeepSeekR1Parser
,
KimiParser
,
Qwen3Parser
,
QwenThinkingParser
,
BaseReasoningParser
,
DeepSeekR1Parser
,
Glm45Parser
,
KimiParser
,
Qwen3Parser
,
QwenThinkingParser
,
Step3Parser
,
};
pub
use
traits
::{
ParseError
,
ParserConfig
,
ParserResult
,
ReasoningParser
};
sgl-router/src/reasoning_parser/parsers/glm45.rs
0 → 100644
View file @
5cfbb4c1
// GLM45 specific reasoning parser.
// Uses the same format as Qwen3 but has its own implementation for debugging.
use
crate
::
reasoning_parser
::
parsers
::
BaseReasoningParser
;
use
crate
::
reasoning_parser
::
traits
::{
ParseError
,
ParserConfig
,
ParserResult
,
ReasoningParser
};
/// GLM45 reasoning parser.
///
/// This parser uses the same format as Qwen3 (<think>...</think>) but has
/// its own implementation for better debugging and potential future customization.
pub
struct
Glm45Parser
{
base
:
BaseReasoningParser
,
}
impl
Glm45Parser
{
/// Create a new GLM45 parser.
pub
fn
new
()
->
Self
{
let
config
=
ParserConfig
{
think_start_token
:
"<think>"
.to_string
(),
think_end_token
:
"</think>"
.to_string
(),
stream_reasoning
:
true
,
max_buffer_size
:
65536
,
initial_in_reasoning
:
false
,
// Requires explicit start token like Qwen3
};
Self
{
base
:
BaseReasoningParser
::
new
(
config
)
.with_model_type
(
"glm45"
.to_string
()),
}
}
}
impl
Default
for
Glm45Parser
{
fn
default
()
->
Self
{
Self
::
new
()
}
}
impl
ReasoningParser
for
Glm45Parser
{
fn
detect_and_parse_reasoning
(
&
mut
self
,
text
:
&
str
)
->
Result
<
ParserResult
,
ParseError
>
{
self
.base
.detect_and_parse_reasoning
(
text
)
}
fn
parse_reasoning_streaming_incremental
(
&
mut
self
,
text
:
&
str
,
)
->
Result
<
ParserResult
,
ParseError
>
{
self
.base
.parse_reasoning_streaming_incremental
(
text
)
}
fn
reset
(
&
mut
self
)
{
self
.base
.reset
()
}
fn
model_type
(
&
self
)
->
&
str
{
self
.base
.model_type
()
}
}
#[cfg(test)]
mod
tests
{
use
super
::
*
;
#[test]
fn
test_glm45_initial_state
()
{
let
mut
parser
=
Glm45Parser
::
new
();
// Should NOT treat text as reasoning without start token
let
result
=
parser
.detect_and_parse_reasoning
(
"This is normal content"
)
.unwrap
();
assert_eq!
(
result
.normal_text
,
"This is normal content"
);
assert_eq!
(
result
.reasoning_text
,
""
);
}
#[test]
fn
test_glm45_with_tokens
()
{
let
mut
parser
=
Glm45Parser
::
new
();
// Should extract reasoning with proper tokens
let
result
=
parser
.detect_and_parse_reasoning
(
"<think>reasoning content</think>answer"
)
.unwrap
();
assert_eq!
(
result
.normal_text
,
"answer"
);
assert_eq!
(
result
.reasoning_text
,
"reasoning content"
);
}
#[test]
fn
test_glm45_streaming
()
{
let
mut
parser
=
Glm45Parser
::
new
();
// First chunk - normal text
let
result1
=
parser
.parse_reasoning_streaming_incremental
(
"normal text "
)
.unwrap
();
assert_eq!
(
result1
.normal_text
,
"normal text "
);
assert_eq!
(
result1
.reasoning_text
,
""
);
// Second chunk - enters reasoning
let
result2
=
parser
.parse_reasoning_streaming_incremental
(
"<think>reasoning"
)
.unwrap
();
assert_eq!
(
result2
.normal_text
,
""
);
assert_eq!
(
result2
.reasoning_text
,
"reasoning"
);
// Third chunk - exits reasoning
let
result3
=
parser
.parse_reasoning_streaming_incremental
(
"</think>answer"
)
.unwrap
();
assert_eq!
(
result3
.normal_text
,
"answer"
);
assert_eq!
(
result3
.reasoning_text
,
""
);
}
#[test]
fn
test_model_type
()
{
let
parser
=
Glm45Parser
::
new
();
assert_eq!
(
parser
.model_type
(),
"glm45"
);
}
}
sgl-router/src/reasoning_parser/parsers/mod.rs
View file @
5cfbb4c1
pub
mod
base
;
pub
mod
deepseek_r1
;
pub
mod
glm45
;
pub
mod
kimi
;
pub
mod
qwen3
;
pub
mod
step3
;
pub
use
base
::
BaseReasoningParser
;
pub
use
deepseek_r1
::
DeepSeekR1Parser
;
pub
use
glm45
::
Glm45Parser
;
pub
use
kimi
::
KimiParser
;
pub
use
qwen3
::{
Qwen3Parser
,
QwenThinkingParser
};
pub
use
step3
::
Step3Parser
;
sgl-router/src/reasoning_parser/parsers/step3.rs
0 → 100644
View file @
5cfbb4c1
// Step3 specific reasoning parser.
// Uses the same format as DeepSeek-R1 but has its own implementation for debugging.
use
crate
::
reasoning_parser
::
parsers
::
BaseReasoningParser
;
use
crate
::
reasoning_parser
::
traits
::{
ParseError
,
ParserConfig
,
ParserResult
,
ReasoningParser
};
/// Step3 reasoning parser.
///
/// This parser uses the same format as DeepSeek-R1 (<think>...</think>) but has
/// its own implementation for better debugging and potential future customization.
pub
struct
Step3Parser
{
base
:
BaseReasoningParser
,
}
impl
Step3Parser
{
/// Create a new Step3 parser.
pub
fn
new
()
->
Self
{
let
config
=
ParserConfig
{
think_start_token
:
"<think>"
.to_string
(),
think_end_token
:
"</think>"
.to_string
(),
stream_reasoning
:
true
,
max_buffer_size
:
65536
,
initial_in_reasoning
:
true
,
// Assumes reasoning from start like DeepSeek-R1
};
Self
{
base
:
BaseReasoningParser
::
new
(
config
)
.with_model_type
(
"step3"
.to_string
()),
}
}
}
impl
Default
for
Step3Parser
{
fn
default
()
->
Self
{
Self
::
new
()
}
}
impl
ReasoningParser
for
Step3Parser
{
fn
detect_and_parse_reasoning
(
&
mut
self
,
text
:
&
str
)
->
Result
<
ParserResult
,
ParseError
>
{
self
.base
.detect_and_parse_reasoning
(
text
)
}
fn
parse_reasoning_streaming_incremental
(
&
mut
self
,
text
:
&
str
,
)
->
Result
<
ParserResult
,
ParseError
>
{
self
.base
.parse_reasoning_streaming_incremental
(
text
)
}
fn
reset
(
&
mut
self
)
{
self
.base
.reset
()
}
fn
model_type
(
&
self
)
->
&
str
{
self
.base
.model_type
()
}
}
#[cfg(test)]
mod
tests
{
use
super
::
*
;
#[test]
fn
test_step3_initial_state
()
{
let
mut
parser
=
Step3Parser
::
new
();
// Should treat text as reasoning even without start token
let
result
=
parser
.detect_and_parse_reasoning
(
"This is reasoning content"
)
.unwrap
();
assert_eq!
(
result
.normal_text
,
""
);
assert_eq!
(
result
.reasoning_text
,
"This is reasoning content"
);
}
#[test]
fn
test_step3_with_end_token
()
{
let
mut
parser
=
Step3Parser
::
new
();
// Should handle text with end token
let
result
=
parser
.detect_and_parse_reasoning
(
"reasoning content</think>answer"
)
.unwrap
();
assert_eq!
(
result
.normal_text
,
"answer"
);
assert_eq!
(
result
.reasoning_text
,
"reasoning content"
);
}
#[test]
fn
test_step3_with_both_tokens
()
{
let
mut
parser
=
Step3Parser
::
new
();
// Should handle both start and end tokens
let
result
=
parser
.detect_and_parse_reasoning
(
"<think>reasoning content</think>answer"
)
.unwrap
();
assert_eq!
(
result
.normal_text
,
"answer"
);
assert_eq!
(
result
.reasoning_text
,
"reasoning content"
);
}
#[test]
fn
test_step3_streaming
()
{
let
mut
parser
=
Step3Parser
::
new
();
// First chunk - treated as reasoning (initial_in_reasoning=true)
let
result1
=
parser
.parse_reasoning_streaming_incremental
(
"reasoning text "
)
.unwrap
();
assert_eq!
(
result1
.normal_text
,
""
);
assert_eq!
(
result1
.reasoning_text
,
"reasoning text "
);
// Second chunk - continues reasoning until end token
let
result2
=
parser
.parse_reasoning_streaming_incremental
(
"more reasoning</think>answer"
)
.unwrap
();
assert_eq!
(
result2
.normal_text
,
"answer"
);
assert_eq!
(
result2
.reasoning_text
,
"more reasoning"
);
}
#[test]
fn
test_model_type
()
{
let
parser
=
Step3Parser
::
new
();
assert_eq!
(
parser
.model_type
(),
"step3"
);
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment