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
ffba61a1
"vscode:/vscode.git/clone" did not exist on "979ecaca26e1e2b4096d862f0accac0de3b5ac07"
Unverified
Commit
ffba61a1
authored
Nov 05, 2025
by
Chang Su
Committed by
GitHub
Nov 05, 2025
Browse files
[router][grpc] Make harmony parser checks recipient first before channel (#12713)
parent
3c219eb0
Changes
1
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
76 additions
and
55 deletions
+76
-55
sgl-router/src/routers/grpc/harmony/parser.rs
sgl-router/src/routers/grpc/harmony/parser.rs
+76
-55
No files found.
sgl-router/src/routers/grpc/harmony/parser.rs
View file @
ffba61a1
...
@@ -106,7 +106,7 @@ impl HarmonyParserAdapter {
...
@@ -106,7 +106,7 @@ impl HarmonyParserAdapter {
pub
fn
parse_messages
(
pub
fn
parse_messages
(
messages
:
&
[
openai_harmony
::
chat
::
Message
],
messages
:
&
[
openai_harmony
::
chat
::
Message
],
)
->
(
Option
<
String
>
,
Option
<
Vec
<
ToolCall
>>
,
String
)
{
)
->
(
Option
<
String
>
,
Option
<
Vec
<
ToolCall
>>
,
String
)
{
let
mut
analysis
=
None
;
let
mut
analysis
:
Option
<
String
>
=
None
;
let
mut
commentary
:
Option
<
Vec
<
ToolCall
>>
=
None
;
let
mut
commentary
:
Option
<
Vec
<
ToolCall
>>
=
None
;
let
mut
final_text
=
String
::
new
();
let
mut
final_text
=
String
::
new
();
...
@@ -119,20 +119,13 @@ impl HarmonyParserAdapter {
...
@@ -119,20 +119,13 @@ impl HarmonyParserAdapter {
let
channel
=
msg
.channel
.as_deref
()
.unwrap_or
(
""
);
let
channel
=
msg
.channel
.as_deref
()
.unwrap_or
(
""
);
let
recipient
=
msg
.recipient
.as_deref
();
let
recipient
=
msg
.recipient
.as_deref
();
match
channel
{
// IMPORTANT: Check recipient FIRST before channel
"analysis"
=>
{
// The model sometimes generates tool calls with channel="analysis" + recipient="functions.*"
// Process each content item
// instead of channel="commentary" + recipient="functions.*"
// For Chat API, we join them into a single reasoning_content
// We should trust the recipient field to determine if this is a tool call
let
text
=
Self
::
extract_text_from_content
(
&
msg
.content
);
if
!
text
.is_empty
()
{
analysis
=
Some
(
text
);
}
}
"commentary"
=>
{
// Handle different recipient types
if
let
Some
(
recipient_str
)
=
recipient
{
if
let
Some
(
recipient_str
)
=
recipient
{
if
recipient_str
.starts_with
(
"functions."
)
{
if
recipient_str
.starts_with
(
"functions."
)
{
// This is a tool call, regardless of channel
let
function_name
=
recipient_str
.strip_prefix
(
"functions."
)
.unwrap
();
let
function_name
=
recipient_str
.strip_prefix
(
"functions."
)
.unwrap
();
// Process each content item separately
// Process each content item separately
...
@@ -154,6 +147,8 @@ impl HarmonyParserAdapter {
...
@@ -154,6 +147,8 @@ impl HarmonyParserAdapter {
}
}
}
}
}
}
// Skip further channel processing for this message
continue
;
}
else
if
recipient_str
.starts_with
(
"python"
)
}
else
if
recipient_str
.starts_with
(
"python"
)
||
recipient_str
.starts_with
(
"browser"
)
||
recipient_str
.starts_with
(
"browser"
)
||
recipient_str
.starts_with
(
"container"
)
||
recipient_str
.starts_with
(
"container"
)
...
@@ -172,9 +167,42 @@ impl HarmonyParserAdapter {
...
@@ -172,9 +167,42 @@ impl HarmonyParserAdapter {
None
=>
analysis
=
Some
(
text
),
None
=>
analysis
=
Some
(
text
),
}
}
}
}
// Skip further channel processing
continue
;
}
}
// Now process by channel (only if not already handled by recipient)
match
channel
{
"analysis"
=>
{
// Process each content item
// For Chat API, we join them into a single reasoning_content
let
text
=
Self
::
extract_text_from_content
(
&
msg
.content
);
if
!
text
.is_empty
()
{
analysis
=
Some
(
text
);
}
}
"commentary"
=>
{
// If we reach here, recipient was not "functions.*" or built-in tools
// Commentary channel should always have a recipient
// This is likely a model bug - log warning and treat as reasoning
tracing
::
warn!
(
channel
=
"commentary"
,
recipient
=
?
recipient
,
"Commentary message without valid recipient, treating as reasoning"
);
let
text
=
Self
::
extract_text_from_content
(
&
msg
.content
);
if
!
text
.is_empty
()
{
match
analysis
.as_mut
()
{
Some
(
existing
)
=>
{
existing
.push
(
'\n'
);
existing
.push_str
(
&
text
);
}
None
=>
analysis
=
Some
(
text
),
}
}
// Unknown recipient would raise ValueError
// For now, we silently ignore (can add logging later)
}
}
}
}
"final"
=>
{
"final"
=>
{
...
@@ -215,16 +243,9 @@ impl HarmonyParserAdapter {
...
@@ -215,16 +243,9 @@ impl HarmonyParserAdapter {
)
->
Result
<
HarmonyChannelOutput
,
String
>
{
)
->
Result
<
HarmonyChannelOutput
,
String
>
{
// Feed all tokens to the parser
// Feed all tokens to the parser
for
&
token_id
in
output_ids
{
for
&
token_id
in
output_ids
{
self
.parser
.process
(
token_id
)
.map_err
(|
e
|
{
self
.parser
// Log the full output_ids context on error
.process
(
token_id
)
tracing
::
error!
(
.map_err
(|
e
|
format!
(
"Failed to process token {}: {}"
,
token_id
,
e
))
?
;
token_id
=
token_id
,
output_ids
=
?
output_ids
,
error
=
%
e
,
"Harmony parser failed to process token"
);
format!
(
"Failed to process token {}: {}"
,
token_id
,
e
)
})
?
;
}
}
// Extract all completed messages from the parser
// Extract all completed messages from the parser
...
@@ -240,7 +261,7 @@ impl HarmonyParserAdapter {
...
@@ -240,7 +261,7 @@ impl HarmonyParserAdapter {
let
final_finish_reason
=
if
commentary
.is_some
()
{
let
final_finish_reason
=
if
commentary
.is_some
()
{
"tool_calls"
.to_string
()
"tool_calls"
.to_string
()
}
else
{
}
else
{
finish_reason
finish_reason
.clone
()
};
};
Ok
(
HarmonyChannelOutput
{
Ok
(
HarmonyChannelOutput
{
...
...
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