[model-gateway]: Tool parser for glm47#15520
Conversation
Summary of ChangesHello @UbeCc, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request integrates support for the GLM-4.7 model into the model gateway by introducing a new tool parser. The primary goal is to accurately interpret tool calls from GLM-4.7, which has a slightly modified format compared to its predecessor, ensuring seamless communication and functionality within the system. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request adds a tool parser for GLM-4.7 by creating a new Glm47MoeParser. The implementation is largely a copy of the existing Glm4MoeParser with a modified regex to support the new tool call format. While the approach is functional and the accompanying tests are comprehensive, it introduces significant code duplication, which could pose maintenance challenges in the future. I've provided a suggestion to refactor this into a more generic parser. Additionally, I've identified a couple of critical issues in the new parser's implementation that would likely lead to compilation errors and should be addressed.
| ) -> ParserResult<StreamingParseResult> { | ||
| // Python logic: Wait for complete tool call, then parse it all at once | ||
| self.buffer.push_str(chunk); | ||
| let current_text = &self.buffer.clone(); |
There was a problem hiding this comment.
The expression &self.buffer.clone() creates a reference to a temporary value. While this may compile in some cases due to temporary lifetime extension, it is an anti-pattern and can be confusing or lead to bugs. It's much clearer and safer to bind the cloned String to a variable first and then take references to it.
Additionally, cloning the entire buffer on each call to parse_incremental can be inefficient, especially for large inputs. If possible, consider a refactor that would allow you to modify the buffer in-place (e.g., using drain()) to improve performance.
| let current_text = &self.buffer.clone(); | |
| let current_text = self.buffer.clone(); |
| helpers::reset_current_tool_state( | ||
| &mut self.buffer, | ||
| &mut false, // glm47_moe doesn't track name_sent per tool | ||
| &mut self.streamed_args_for_tool, | ||
| &self.prev_tool_call_arr, | ||
| ); |
There was a problem hiding this comment.
Passing &mut false to helpers::reset_current_tool_state will cause a compilation error, as it's not possible to create a mutable reference to a literal false.
To fix this, you should declare a local mutable variable and pass a reference to it instead. This issue also suggests a potential design flaw, as the helper function seems to have different requirements than what this parser provides (i.e., this parser doesn't use current_tool_name_sent).
| helpers::reset_current_tool_state( | |
| &mut self.buffer, | |
| &mut false, // glm47_moe doesn't track name_sent per tool | |
| &mut self.streamed_args_for_tool, | |
| &self.prev_tool_call_arr, | |
| ); | |
| let mut name_sent_dummy = false; | |
| helpers::reset_current_tool_state( | |
| &mut self.buffer, | |
| &mut name_sent_dummy, // glm47_moe doesn't track name_sent per tool | |
| &mut self.streamed_args_for_tool, | |
| &self.prev_tool_call_arr, | |
| ); |
| /// - XML-style tags for tool calls | ||
| /// - Key-value pairs for arguments | ||
| /// - Support for multiple sequential tool calls | ||
| pub struct Glm47MoeParser { |
There was a problem hiding this comment.
This Glm47MoeParser is almost an exact copy of Glm4MoeParser, with the only significant difference being the func_detail_extractor regex. This approach introduces a large amount of duplicated code, which will make maintenance more difficult and error-prone.
I strongly recommend refactoring to avoid this duplication. A better approach would be to create a single, generic GlmParser struct that can be initialized with the specific regex pattern required for a given GLM version. This would centralize the parsing logic and make it easier to support future versions.
For example, you could have a generic parser and instantiate it in factory.rs like so:
// In factory.rs
registry.register_parser("glm4_moe", || Box::new(GlmParser::new(r"(?s)<tool_call>([^
]*)
(.*)</tool_call>")));
registry.register_parser("glm47_moe", || Box::new(GlmParser::new(r"(?s)<tool_call>\s*([^<\s]+)\s*(.*?)</tool_call>")));This would eliminate nearly 300 lines of duplicated code.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request successfully implements a tool parser for GLM-4.7 by refactoring the existing Glm4MoeParser to support both the old and new tool call formats. The changes are well-structured, with corresponding updates to tests, benchmarks, and documentation. The use of factory methods to configure the parser for different GLM versions is a clean approach.
My review comments focus on improving documentation consistency and API clarity. I've identified a few places in the reasoning_parser's README that are inconsistent with the implementation, and I've suggested a couple of minor API improvements in the tool_parser to enhance safety and reduce potential confusion. Overall, this is a great contribution, and addressing these points will further improve the code's maintainability.
| PP --> QWT[Qwen3-Thinking] | ||
| PP --> KM[Kimi] | ||
| PP --> GL[GLM45] | ||
| PP --> GL47[GLM47] |
There was a problem hiding this comment.
| - `glm45`: GLM-4.5 / GLM-4.6 parser | ||
| - `glm47`: GLM-4.7 parser |
There was a problem hiding this comment.
This documentation is inconsistent with the implementation in reasoning_parser/factory.rs, which maps the glm47 model pattern to the existing glm45 parser. A new glm47 parser is not actually introduced. To avoid confusion, I recommend merging the glm47 information into the glm45 description.
| - `glm45`: GLM-4.5 / GLM-4.6 parser | |
| - `glm47`: GLM-4.7 parser | |
| - `glm45`: GLM-4.5 / GLM-4.6 / GLM-4.7 parser |
| "qwen3" → "qwen3" | ||
| "qwen" → "qwen3" | ||
| "glm45" → "glm45" | ||
| "glm47" → "glm47" |
| /// - `func_detail_pattern`: Regex pattern for extracting function name and arguments | ||
| /// - For GLM-4: `r"(?s)<tool_call>([^\n]*)\n(.*)</tool_call>"` | ||
| /// - For GLM-4.7: `r"(?s)<tool_call>\s*([^<\s]+)\s*(.*?)</tool_call>"` | ||
| pub fn new(func_detail_pattern: &str) -> Self { |
There was a problem hiding this comment.
This new function is now a generic constructor that takes a regex pattern. To create a safer and cleaner public API, consider making this function private to the crate (pub(crate) fn new(...)) and only exposing the specific glm4() and glm47() factory methods. This would prevent users from providing invalid regex patterns that could cause a panic at runtime due to the .expect() call.
| pub fn new(func_detail_pattern: &str) -> Self { | |
| pub(crate) fn new(func_detail_pattern: &str) -> Self { |
| pub use pythonic::PythonicParser; | ||
| pub use qwen::QwenParser; | ||
| pub use step3::Step3Parser; | ||
| pub type Glm47MoeParser = Glm4MoeParser; |
There was a problem hiding this comment.
This type alias can be confusing. It suggests that Glm47MoeParser is a distinct type, but it's just an alias for Glm4MoeParser. This allows for confusing calls like Glm47MoeParser::glm4().
To improve clarity, I'd recommend removing this alias and using Glm4MoeParser directly in all places, including the new test file tests/tool_parser_glm47_moe.rs. The parser's behavior is determined by the factory method used (glm4() vs. glm47()), and relying on that is clearer than using a type alias.


Motivation
This pr implements tool parser for GLM-4.7.
GLM-4.7 removes '\n' in template from GLM-4.6.
before (GLM-4.6)
after (GLM-4.7)
Reasoning parser is the same as glm45
Modifications
Add Glm47MoeParser, inherited from Glm4MoeParser
Accuracy Tests
failure for non-existing mcp server (do not affect correctness)
Benchmarking and Profiling
Checklist