Skip to content

fix(provider): strip GLM non-standard special tokens in zhipu adapter#5675

Open
NayukiChiba wants to merge 3 commits intoAstrBotDevs:masterfrom
NayukiChiba:fix/glm-non-standard-chars
Open

fix(provider): strip GLM non-standard special tokens in zhipu adapter#5675
NayukiChiba wants to merge 3 commits intoAstrBotDevs:masterfrom
NayukiChiba:fix/glm-non-standard-chars

Conversation

@NayukiChiba
Copy link
Contributor

@NayukiChiba NayukiChiba commented Mar 2, 2026

GLM models (e.g. glm-4.6v-flash) can leak internal control tokens
into the visible reply content:

  • <None> – model's null-response signal
  • <|endoftext|> – end-of-sequence token
  • <|user|>, <|assistant|>, <|system|>, <|observation|> – role tokens

Fix by overriding _normalize_content and _parse_openai_completion in
ProviderZhipu to apply a regex cleaning pass that removes these tokens
before the response is returned to the user.

Also corrects a wrong import (star.func_toolsagent.tool) that was
present in the original stub.

Closes #5556

Summary by Sourcery

从 Zhipu 提供方的响应中剥离 GLM 特有的非标准特殊标记,防止泄露的控制标记出现在用户可见的输出中。

Bug Fixes:

  • 从 Zhipu 适配器响应中清理 GLM 角色和空响应标记(例如 <None><|endoftext|>),包括组装后的流式补全结果,确保它们不再出现在助手回复中。

Tests:

  • 为 ProviderZhipu 中的 GLM 特殊标记清理逻辑添加全面的单元测试和集成测试,覆盖独立清理器、内容规范化重写以及最终补全解析行为。
Original summary in English

Summary by Sourcery

Strip GLM-specific non-standard special tokens from Zhipu provider responses to prevent leaked control markers from appearing in user-visible output.

Bug Fixes:

  • Clean GLM role and null-response tokens (such as and <|endoftext|>) from Zhipu adapter responses, including assembled streaming completions, so they no longer appear in assistant replies.

Tests:

  • Add comprehensive unit and integration tests for GLM special-token cleaning in ProviderZhipu, covering the standalone cleaner, content normalization override, and final completion parsing behavior.
Original summary in English

Summary by Sourcery

从 Zhipu 提供方的响应中剥离 GLM 特有的非标准特殊标记,防止泄露的控制标记出现在用户可见的输出中。

Bug Fixes:

  • 从 Zhipu 适配器响应中清理 GLM 角色和空响应标记(例如 <None><|endoftext|>),包括组装后的流式补全结果,确保它们不再出现在助手回复中。

Tests:

  • 为 ProviderZhipu 中的 GLM 特殊标记清理逻辑添加全面的单元测试和集成测试,覆盖独立清理器、内容规范化重写以及最终补全解析行为。
Original summary in English

Summary by Sourcery

Strip GLM-specific non-standard special tokens from Zhipu provider responses to prevent leaked control markers from appearing in user-visible output.

Bug Fixes:

  • Clean GLM role and null-response tokens (such as and <|endoftext|>) from Zhipu adapter responses, including assembled streaming completions, so they no longer appear in assistant replies.

Tests:

  • Add comprehensive unit and integration tests for GLM special-token cleaning in ProviderZhipu, covering the standalone cleaner, content normalization override, and final completion parsing behavior.

GLM models (e.g. glm-4.6v-flash) can leak internal control tokens
into the visible reply content:

- <None>          – model's null-response signal
- <|endoftext|>   – end-of-sequence token
- <|user|>, <|assistant|>, <|system|>, <|observation|> – role tokens

Fix by overriding _normalize_content and _parse_openai_completion in
ProviderZhipu to apply a regex cleaning pass that removes these tokens
before the response is returned to the user.

Also corrects a wrong import (star.func_tools -> agent.tool) that was
present in the original stub.

Closes AstrBotDevs#5556
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, 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 addresses an issue where GLM models, specifically when used via the Zhipu provider, would occasionally include internal control tokens in their output, which are not intended for end-users. The changes introduce robust cleaning logic at multiple points in the response processing pipeline to ensure these non-standard tokens are removed, resulting in cleaner and more user-friendly model outputs. This significantly improves the quality of responses from GLM models.

Highlights

  • GLM Special Token Stripping: Implemented a mechanism to strip non-standard GLM special tokens (e.g., , <|endoftext|>, role tokens) from Zhipu model responses to prevent them from leaking into user-facing content.
  • Overridden Content Normalization: Overrode the _normalize_content static method in ProviderZhipu to apply an initial regex-based cleaning pass for GLM tokens.
  • Post-Completion Cleaning Pass: Added a second cleaning pass within the _parse_openai_completion method to handle GLM special tokens that might span multiple streaming chunks and survive the initial per-chunk normalization.
  • Import Correction: Corrected an incorrect import statement from star.func_tools to agent.tool within the zhipu_source.py file.
  • Comprehensive Unit Tests: Introduced a new test file with extensive unit and integration tests to verify the correct functionality of the GLM special token cleaning at various stages of response processing.
Changelog
  • astrbot/core/provider/sources/zhipu_source.py
    • Added imports for re, Any, ChatCompletion, ToolSet, MessageChain, and LLMResponse.
    • Defined two regular expression patterns, _GLM_ROLE_TOKEN_RE and _GLM_NULL_TOKEN_RE, to identify GLM-specific control and null-response tokens.
    • Implemented a new static method _clean_glm_special_tokens to remove identified GLM tokens and collapse resulting extra whitespace.
    • Overrode the _normalize_content static method to apply the GLM token cleaning after the base normalization.
    • Overrode the _parse_openai_completion asynchronous method to perform an additional GLM token cleaning pass on the final assembled completion text, ensuring tokens spanning streaming chunks are also removed.
  • tests/test_zhipu_source.py
    • Added a new test file to cover the GLM non-standard special token handling.
    • Included helper functions _make_provider and _make_llm_response for test setup.
    • Created TestCleanGLMSpecialTokens with unit tests for the _clean_glm_special_tokens method, verifying removal of null tokens, role tokens, and preservation of normal text.
    • Developed TestNormalizeContent to confirm that the overridden _normalize_content correctly applies GLM cleaning.
    • Implemented TestParseOpenAICompletionCleaning with integration tests for the _parse_openai_completion method's second cleaning pass, ensuring tokens are stripped and other response fields remain intact.
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

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 a fix to strip non-standard special tokens from Zhipu GLM provider responses. The implementation is robust, using a two-pass cleaning approach to handle both regular and streaming responses, which is a thoughtful way to address potential issues with tokens split across chunks. The accompanying tests are comprehensive and well-structured, covering various edge cases. I have one suggestion to improve the test code structure by using a pytest fixture for resource management, which would reduce boilerplate and enhance maintainability. Overall, this is a high-quality contribution.

将 TestParseOpenAICompletionCleaning 中的 provider fixture 从
@pytest.fixture 改为 @pytest_asyncio.fixture,以兼容 asyncio
STRICT 模式,同时移除各测试方法中重复的 try...finally 样板代码
@NayukiChiba NayukiChiba marked this pull request as ready for review March 2, 2026 12:03
@auto-assign auto-assign bot requested review from Raven95676 and advent259141 March 2, 2026 12:03
@dosubot dosubot bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Mar 2, 2026
@dosubot
Copy link

dosubot bot commented Mar 2, 2026

Related Documentation

Checked 1 published document(s) in 1 knowledge base(s). No updates required.

How did I do? Any feedback?  Join Discord

@dosubot dosubot bot added the area:provider The bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner. label Mar 2, 2026
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - 我在这里给出一些整体性的反馈:

  • _clean_glm_special_tokens 中,<None> 的正则(re.compile(r"<None>", re.IGNORECASE)) 会匹配到更长的 token 中的子串,比如 <NoneThing>;建议通过锚点、单词边界,或显式限定两侧尖括号的方式,只匹配真正需要处理的内容,避免误删仅仅包含该子串的合法内容。
  • _parse_openai_completion 中的额外清洗逻辑,会用一个新的 MessageChain().message(cleaned) 覆盖 llm_response.result_chain,这会丢弃基础解析器生成的更丰富结构或多段内容(例如工具调用或非文本部分);建议仅原地更新相关消息内容,而不是重建整个链。
给 AI Agent 的提示
Please address the comments from this code review:

## Overall Comments
- In `_clean_glm_special_tokens`, the `<None>` regex (`re.compile(r"<None>", re.IGNORECASE)`) will match inside larger tokens like `<NoneThing>`; consider anchoring or using word boundaries/explicit surrounding angle brackets to avoid stripping legitimate content that merely contains that substring.
- The extra cleaning in `_parse_openai_completion` overwrites `llm_response.result_chain` with a new `MessageChain().message(cleaned)`, which will discard any richer structure or multiple segments the base parser produced (e.g. tool calls or non-text parts); consider updating only the relevant message content in-place instead of reconstructing the chain.

Sourcery 对开源项目是免费的——如果你觉得我们的代码审查有帮助,欢迎分享 ✨
帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进后续的代码审查。
Original comment in English

Hey - I've left some high level feedback:

  • In _clean_glm_special_tokens, the <None> regex (re.compile(r"<None>", re.IGNORECASE)) will match inside larger tokens like <NoneThing>; consider anchoring or using word boundaries/explicit surrounding angle brackets to avoid stripping legitimate content that merely contains that substring.
  • The extra cleaning in _parse_openai_completion overwrites llm_response.result_chain with a new MessageChain().message(cleaned), which will discard any richer structure or multiple segments the base parser produced (e.g. tool calls or non-text parts); consider updating only the relevant message content in-place instead of reconstructing the chain.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `_clean_glm_special_tokens`, the `<None>` regex (`re.compile(r"<None>", re.IGNORECASE)`) will match inside larger tokens like `<NoneThing>`; consider anchoring or using word boundaries/explicit surrounding angle brackets to avoid stripping legitimate content that merely contains that substring.
- The extra cleaning in `_parse_openai_completion` overwrites `llm_response.result_chain` with a new `MessageChain().message(cleaned)`, which will discard any richer structure or multiple segments the base parser produced (e.g. tool calls or non-text parts); consider updating only the relevant message content in-place instead of reconstructing the chain.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@NayukiChiba
Copy link
Contributor Author

我改一下re逻辑,有点太复杂了

根据 sourcery-ai review 意见进行以下修复:

1. _GLM_NULL_TOKEN_RE 移除 re.IGNORECASE:GLM 只输出精确的
   <None>(大写 N),去掉不必要的大小写不敏感标志使正则更精确,
   避免误匹配非 GLM 控制 token 的合法内容。

2. _parse_openai_completion 改用 completion_text setter 原地
   更新文本,不再重建整个 result_chain,从而保留 tool call
   等非文本组件,防止丢弃基础解析器生成的更丰富结构。

3. 同步更新测试:<none>/<NONE> 不再被清除,断言改为保留原文。
@NayukiChiba
Copy link
Contributor Author

加了一个None识别的玩意

@NayukiChiba
Copy link
Contributor Author

@Soulter 可以merge了

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:provider The bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner. size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]glm-4.6v-flash模型返回思考内容

1 participant