Skip to content

sync master-﹥dev#6397

Merged
LIghtJUNction merged 29 commits intodevfrom
master
Mar 16, 2026
Merged

sync master-﹥dev#6397
LIghtJUNction merged 29 commits intodevfrom
master

Conversation

@LIghtJUNction
Copy link
Member

@LIghtJUNction LIghtJUNction commented Mar 15, 2026

Modifications / 改动点

  • This is NOT a breaking change. / 这不是一个破坏性变更。

Screenshots or Test Results / 运行截图或测试结果


Checklist / 检查清单

  • 😊 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。/ If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
  • 👀 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”。/ My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
  • 🤓 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到了 requirements.txtpyproject.toml 文件相应位置。/ I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in requirements.txt and pyproject.toml.
  • 😮 我的更改没有引入恶意代码。/ My changes do not introduce malicious code.

Summary by Sourcery

Sync recent changes from master into dev, updating provider behavior, messaging adapters, configuration, and tooling.

New Features:

  • Add MiniMax as a configurable chat provider in the default provider templates.

Bug Fixes:

  • Ensure Groq assistant history payloads drop reasoning fields that the API rejects.
  • Preserve and store OpenAI reasoning content while keeping assistant history content clean, with safer handling of empty choices.
  • Correct QQ official adapter to parse and render face/emoji messages into readable text in normal and mention flows.
  • Handle Telegram GIF images as animations with appropriate chat actions instead of photos.
  • Deduplicate QQ official webhook callback events by tracking recently seen event IDs.
  • Adjust SendMessageToUserTool description to clarify supported message types and when the tool should be used.
  • Constrain Neo skill payload schema so array payloads must contain objects.
  • Fix Chinese documentation link for configuring AI model services to point to the updated providers path.

Enhancements:

  • Switch dashboard provider icon URLs to use jsDelivr-hosted @lobehub/icons-static-svg assets.
  • Add tests covering reasoning-content handling in OpenAI and Groq providers.

Tests:

  • Extend OpenAI source tests and add Groq provider tests to verify reasoning content handling in assistant history payloads.

Chores:

  • Introduce a script to detect and optionally close duplicate plugin-publish GitHub issues, keeping only the latest issue per title.

ccsang and others added 11 commits March 15, 2026 21:05
Fixes #6294

QQ official bot receives emoji/sticker messages as raw XML-like tags:
`<faceType=4,faceId="",ext="eyJ0ZXh0IjoiW+a7oeWktOmXruWPt10ifQ==">`

This made the LLM unable to understand the emoji content.

Changes:
- Added `_parse_face_message()` method to parse face message format
- Decode base64 `ext` field to get emoji description text
- Replace face tags with `[表情:描述]` format for readability

Example:
- Input: `<faceType=4,faceId="",ext="eyJ0ZXh0IjoiW+a7oeWktOmXruWPt10ifQ==">`
- Output: `[表情:[满头问号]]`

Co-authored-by: ccsang <ccsang@users.noreply.github.com>
* fix(telegram): route GIF files to send_animation instead of send_photo

* fix: narrow exception in _is_gif to OSError

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* refactor: simplify image send dispatch in send_with_client

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* refactor: simplify image dispatch in _process_chain_items

* ruff format

---------

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Soulter <905617992@qq.com>
* feat(provider): add MiniMax

* feat(provider): reintroduce MiniMax provider configuration and remove deprecated source

---------

Co-authored-by: Soulter <905617992@qq.com>
…kills-like". (#6317)

* fix: improve send_message_to_user tool description for skills_like mode

* fix: enhance description for send_message_to_user tool to clarify usage

---------

Co-authored-by: Soulter <905617992@qq.com>
* Fix CreateSkillPayloadTool array schema missing items field

The payload parameter's anyOf array variant lacked an items field,
causing Gemini API to reject the tool declaration with 400 Bad Request:
'parameters.properties[payload].any_of[1].items: missing field.'

Add items: {type: object} to the array variant to satisfy the Gemini
API requirement for array type schemas.

Fixes #6279

* Fix TypeError when OpenAI-compatible API returns null choices

Some providers (e.g. OpenRouter) may return a completion where
choices is None rather than an empty list — for instance on rate
limiting, content filtering, or transient errors. The existing code
used len(completion.choices) which throws TypeError on None.

Replace all len(...choices) == 0 checks with 'not ... .choices' which
handles both None and empty list. Affects _query_stream, _parse_openai_completion,
and _extract_reasoning_content.

Fixes #6252
Co-authored-by: Stable Genius <259448942+stablegenius49@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 15, 2026 17:05
@dosubot dosubot bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Mar 15, 2026
@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 integrates a range of enhancements and fixes across the AstrBot project, focusing on improving platform compatibility, refining tool functionalities, and enhancing overall system robustness. Key updates include better handling of diverse message types and media across different platforms, ensuring API compatibility with new and existing AI providers, and streamlining internal processes through schema improvements and automated scripting. The changes aim to provide a more stable and feature-rich experience for users and developers alike.

Highlights

  • Tool Description Enhancement: The SendMessageToUserTool description was expanded to clearly define its purpose, including support for various message types (plain, image, record, video, file, mention_user) and specific use cases like sending media or proactive messages, distinguishing it from direct text replies.
  • Schema Validation Improvement: The CreateSkillPayloadTool's payload parameter schema was refined to explicitly permit arrays of objects, enhancing the accuracy and strictness of schema validation for skill payloads.
  • New Chat Provider Integration: Configuration for a new chat provider, 'MiniMax', was added to the default settings, enabling support for this new service within the system.
  • QQ Official Face Message Parsing: A new static method _parse_face_message was introduced to convert QQ official face message formats (which use base64-encoded JSON for emojis) into human-readable text, improving message interpretation.
  • Webhook Event Deduplication: Webhook event deduplication logic was implemented for QQ official webhooks, utilizing a cache and a time-to-live (TTL) to prevent reprocessing of duplicate events caused by retry mechanisms.
  • Telegram GIF Handling: Telegram message sending was enhanced to correctly identify and send GIF images as animations rather than static photos, improving media fidelity.
  • Groq API Payload Compatibility: The Groq provider was updated to remove reasoning_content and reasoning fields from assistant messages before sending the payload, ensuring compatibility with Groq's API which rejects these fields.
  • OpenAI API Response Robustness: Several conditional checks in the OpenAI provider were refactored from len(chunk.choices) == 0 to not chunk.choices for conciseness, and a null check for delta was added before accessing delta.content to prevent potential errors in streaming responses.
  • Provider Icon CDN Update: The base URL for provider icons in the dashboard was updated from registry.npmmirror.com to cdn.jsdelivr.net/npm, leveraging a more robust CDN for icon delivery.
  • Documentation Link Correction: A broken internal link in the Chinese documentation (docs/zh/what-is-astrbot.md) related to configuring AI models was corrected.
  • Automated Duplicate Issue Closing Script: A new Python script was added to automate the process of closing duplicate GitHub issues labeled 'plugin-publish', ensuring only the most recent issue with a given title remains open.
  • New Provider Payload Tests: New test cases were added for the Groq provider to verify the correct removal of reasoning fields from assistant messages, and for the OpenAI provider to confirm the retention of reasoning content.
Changelog
  • astrbot/core/astr_main_agent_resources.py
    • Updated SendMessageToUserTool description to include supported message types and usage guidelines.
  • astrbot/core/computer/tools/neo_skills.py
    • Refined CreateSkillPayloadTool's payload schema to explicitly allow arrays of objects.
  • astrbot/core/config/default.py
    • Added configuration for the new 'MiniMax' chat provider.
  • astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py
    • Implemented a new method to parse QQ official face messages into readable text.
    • Integrated face message parsing into the main message processing logic.
  • astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py
    • Added webhook event deduplication logic to prevent processing of repeated events.
  • astrbot/core/platform/sources/telegram/tg_event.py
    • Added functionality to detect and send GIFs as animations in Telegram.
  • astrbot/core/provider/sources/groq_source.py
    • Implemented a method to remove reasoning_content and reasoning fields from assistant messages for Groq API compatibility.
  • astrbot/core/provider/sources/openai_source.py
    • Refactored conditional checks for empty choices from len(chunk.choices) == 0 to not chunk.choices.
    • Added a null check for delta before accessing delta.content in streaming responses.
  • dashboard/src/utils/providerUtils.js
    • Updated CDN URLs for provider icons from registry.npmmirror.com to cdn.jsdelivr.net/npm.
  • docs/zh/what-is-astrbot.md
    • Corrected an internal link in the Chinese documentation related to configuring AI models.
  • scripts/close_duplicate_plugin_publish_issues.py
    • Added a new Python script to automate the closing of duplicate GitHub issues with the 'plugin-publish' label.
  • tests/test_openai_source.py
    • Added new test cases for Groq provider payload handling to verify reasoning field removal.
    • Added new test cases for OpenAI provider payload handling to confirm reasoning content retention.
Activity
  • This pull request represents a synchronization merge from the master branch into the dev branch, indicating an integration of recent stable changes.
  • The author, LIghtJUNction, has provided a checklist in the description covering aspects like non-breaking changes, testing, dependency management, and code safety, suggesting adherence to project contribution guidelines.
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.

@dosubot dosubot bot added area:core The bug / feature is about astrbot's core, backend area:platform The bug / feature is about IM platform adapter, such as QQ, Lark, Telegram, WebChat and so on. area:provider The bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner. area:webui The bug / feature is about webui(dashboard) of astrbot. labels Mar 15, 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 - I've found 1 security issue, 2 other issues, and left some high level feedback:

Security issues:

  • Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)

General comments:

  • In QQOfficialPlatformAdapter._parse_face_message, consider moving the re, base64, and json imports and the face-tag regex to module scope and narrowing the except Exception around the base64/JSON decode to more specific exceptions (e.g., binascii.Error, json.JSONDecodeError) to avoid masking unexpected errors and to reduce per-call overhead.
  • The webhook dedup cache in QoWebhookServer currently uses a plain dict with lazy eviction; if this process runs long-lived or at higher volume, consider encapsulating this into a small TTL cache helper (or using collections.OrderedDict) to make eviction logic clearer and guarantee bounded memory growth.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `QQOfficialPlatformAdapter._parse_face_message`, consider moving the `re`, `base64`, and `json` imports and the face-tag regex to module scope and narrowing the `except Exception` around the base64/JSON decode to more specific exceptions (e.g., `binascii.Error`, `json.JSONDecodeError`) to avoid masking unexpected errors and to reduce per-call overhead.
- The webhook dedup cache in `QoWebhookServer` currently uses a plain dict with lazy eviction; if this process runs long-lived or at higher volume, consider encapsulating this into a small TTL cache helper (or using `collections.OrderedDict`) to make eviction logic clearer and guarantee bounded memory growth.

## Individual Comments

### Comment 1
<location path="astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py" line_range="414-423" />
<code_context>
+        import json
+        import re
+
+        def replace_face(match):
+            face_tag = match.group(0)
+            # Extract ext field from the face tag
+            ext_match = re.search(r'ext="([^"]*)"', face_tag)
+            if ext_match:
+                try:
+                    ext_encoded = ext_match.group(1)
+                    # Decode base64 and parse JSON
+                    ext_decoded = base64.b64decode(ext_encoded).decode("utf-8")
+                    ext_data = json.loads(ext_decoded)
+                    emoji_text = ext_data.get("text", "")
+                    if emoji_text:
+                        return f"[表情:{emoji_text}]"
+                except Exception:
+                    pass
+            # Fallback if parsing fails
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Narrow the exception handling when decoding the `ext` field to avoid masking unrelated errors.

Catching `Exception` here hides all parsing and programming errors, making real bugs hard to detect and debug. Limit this to the specific, expected failures (e.g. `binascii.Error`, `json.JSONDecodeError`, `UnicodeDecodeError`) so unexpected issues surface instead of always falling back to `[表情]`.

Suggested implementation:

```python
        import base64
        import binascii
        import json
        import re

```

```python
                except (binascii.Error, json.JSONDecodeError, UnicodeDecodeError):
                    # Fallback to generic placeholder on expected decoding/parsing errors
                    pass

```
</issue_to_address>

### Comment 2
<location path="scripts/close_duplicate_plugin_publish_issues.py" line_range="98-99" />
<code_context>
+    ]
+
+
+def normalize_title(title: str) -> str:
+    return " ".join(title.split()).strip()
+
+
</code_context>
<issue_to_address>
**suggestion:** Consider normalizing titles case-insensitively to catch more duplicates.

`normalize_title` only normalizes whitespace, so titles that differ only by case (e.g., `"My Plugin"` vs `"my plugin"`) won’t be grouped together. If you want semantic deduplication, also normalize case, e.g.:

```python
return " ".join(title.split()).strip().casefold()
```

```suggestion
def normalize_title(title: str) -> str:
    # Normalize whitespace and case so titles differing only by spacing or case are treated as duplicates
    return " ".join(title.split()).strip().casefold()
```
</issue_to_address>

### Comment 3
<location path="scripts/close_duplicate_plugin_publish_issues.py" line_range="52-57" />
<code_context>
        completed = subprocess.run(
            args,
            check=True,
            capture_output=True,
            text=True,
        )
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

*Source: opengrep*
</issue_to_address>

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.

Comment on lines +414 to +423
def replace_face(match):
face_tag = match.group(0)
# Extract ext field from the face tag
ext_match = re.search(r'ext="([^"]*)"', face_tag)
if ext_match:
try:
ext_encoded = ext_match.group(1)
# Decode base64 and parse JSON
ext_decoded = base64.b64decode(ext_encoded).decode("utf-8")
ext_data = json.loads(ext_decoded)
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (bug_risk): Narrow the exception handling when decoding the ext field to avoid masking unrelated errors.

Catching Exception here hides all parsing and programming errors, making real bugs hard to detect and debug. Limit this to the specific, expected failures (e.g. binascii.Error, json.JSONDecodeError, UnicodeDecodeError) so unexpected issues surface instead of always falling back to [表情].

Suggested implementation:

        import base64
        import binascii
        import json
        import re
                except (binascii.Error, json.JSONDecodeError, UnicodeDecodeError):
                    # Fallback to generic placeholder on expected decoding/parsing errors
                    pass

Comment on lines +98 to +99
def normalize_title(title: str) -> str:
return " ".join(title.split()).strip()
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: Consider normalizing titles case-insensitively to catch more duplicates.

normalize_title only normalizes whitespace, so titles that differ only by case (e.g., "My Plugin" vs "my plugin") won’t be grouped together. If you want semantic deduplication, also normalize case, e.g.:

return " ".join(title.split()).strip().casefold()
Suggested change
def normalize_title(title: str) -> str:
return " ".join(title.split()).strip()
def normalize_title(title: str) -> str:
# Normalize whitespace and case so titles differing only by spacing or case are treated as duplicates
return " ".join(title.split()).strip().casefold()

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 syncs a variety of changes from the master branch to dev, including new features, bug fixes, and enhancements across different parts of the application. The changes are generally well-implemented and improve the codebase. I have one suggestion regarding exception handling and import placement in the new QQ face message parsing logic to improve maintainability and robustness.

Comment on lines +419 to +428
try:
ext_encoded = ext_match.group(1)
# Decode base64 and parse JSON
ext_decoded = base64.b64decode(ext_encoded).decode("utf-8")
ext_data = json.loads(ext_decoded)
emoji_text = ext_data.get("text", "")
if emoji_text:
return f"[表情:{emoji_text}]"
except Exception:
pass
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The except Exception: is too broad and can hide unexpected errors. It's better to catch specific exceptions that you expect to occur, such as json.JSONDecodeError, ValueError (for base64 decoding issues), or UnicodeDecodeError.

Also, the import statements for base64, json, and re should be moved to the top of the file.

Suggested change
try:
ext_encoded = ext_match.group(1)
# Decode base64 and parse JSON
ext_decoded = base64.b64decode(ext_encoded).decode("utf-8")
ext_data = json.loads(ext_decoded)
emoji_text = ext_data.get("text", "")
if emoji_text:
return f"[表情:{emoji_text}]"
except Exception:
pass
try:
ext_encoded = ext_match.group(1)
# Decode base64 and parse JSON
ext_decoded = base64.b64decode(ext_encoded).decode("utf-8")
ext_data = json.loads(ext_decoded)
emoji_text = ext_data.get("text", "")
if emoji_text:
return f"[表情:{emoji_text}]"
except (json.JSONDecodeError, ValueError, UnicodeDecodeError):
pass
References
  1. Import statements should be placed at the top of the file, one import per line, as per PEP 8 guidelines. (link)

@dosubot
Copy link

dosubot bot commented Mar 15, 2026

Related Documentation

1 document(s) may need updating based on files changed in this PR:

AstrBotTeam's Space

pr4697的改动
View Suggested Changes
@@ -373,12 +373,21 @@
 - 支持多平台:Telegram、Slack、Discord 等
 - 支持多种消息类型:`plain`(文本)、`image`(图片)、`record`(语音消息)、`video`(视频)、`file`(文件)、`mention_user`(提及用户)
 
+#### 工具描述增强(PR #6397)
+
+[PR #6397](https://github.com/AstrBotDevs/AstrBot/pull/6397) 增强了 `send_message_to_user` 工具的描述信息,明确列出所有支持的消息类型和使用场景:
+
+- **工具描述更新**:工具描述现在明确说明支持多种消息类型(`plain`、`image`、`record`、`video`、`file`、`mention_user`)
+- **使用场景说明**:工具描述清晰指出适用场景:
+  - 发送媒体文件(`image`、`record`、`video`、`file`)时应使用此工具
+  - 主动推送消息(如定时任务 cron job)时应使用此工具
+  - 普通文本回复可直接输出,无需调用此工具
+- **避免绕路**:增强的描述确保 AI 能够正确理解该工具的功能,避免使用 Python 工具等绕弯路的方式处理媒体文件
+
 #### 使用场景
 - **发送多媒体文件**:使用该工具发送图片、语音、视频、文件等媒体内容
 - **主动推送消息**:在定时任务或后台任务中主动向用户发送消息
 - **普通文本回复**:对于普通文本回复,可以直接在对话中输出,无需使用此工具
-
-> **注意**:在 `skills_like` 工具调用模式下,该工具描述已明确列出所有支持的消息类型,确保 AI 能够正确理解该工具支持发送多媒体消息,避免使用 Python 工具等绕弯路的方式处理媒体文件。
 
 #### 使用示例
 在定时任务触发后,主动调用 `send_message_to_user` 工具,将生成的日报、图片或其他内容直接发送给用户。
@@ -1385,7 +1394,156 @@
 
 ---
 
-### 14. 其他优化
+### 14. 平台支持增强(PR #6397)
+
+#### Telegram GIF 图片支持
+
+[PR #6397](https://github.com/AstrBotDevs/AstrBot/pull/6397) 增强了 Telegram 平台对 GIF 图片的支持。修复后,GIF 图片会被正确识别并作为动画发送,而非静态图片:
+
+**检测机制**:
+- 通过文件扩展名(`.gif`)或文件头部魔数(`GIF87a` / `GIF89a`)识别 GIF 图片
+- 系统自动检测图片类型,无需用户手动指定
+
+**发送行为**:
+- **GIF 图片**:使用 `send_animation` API 发送,确保 GIF 在 Telegram 中正常播放动画
+- **静态图片**:使用 `send_photo` API 发送,保持原有行为
+- **聊天状态**:发送 GIF 时显示 `UPLOAD_VIDEO` 聊天状态,而非 `UPLOAD_PHOTO`
+
+**影响范围**:
+- `TelegramPlatformEvent.send_with_client()` 方法:消息链发送逻辑
+- `TelegramPlatformEvent._process_chain_items()` 方法:流式消息处理逻辑
+
+该修复确保 Telegram 平台的 GIF 图片体验与原生 Telegram 客户端一致,提升多媒体消息的交互质量。
+
+#### QQ 官方平台表情解析
+
+[PR #6397](https://github.com/AstrBotDevs/AstrBot/pull/6397) 新增了 QQ 官方平台的表情(emoji)消息解析功能,将 base64 编码的表情数据转换为可读文本格式:
+
+**表情消息格式**:
+- QQ 官方表情消息格式为:`<faceType=4,faceId="",ext="eyJ0ZXh0IjoiW+a7oeWktOmXruWPt10ifQ==">`
+- `ext` 字段包含 base64 编码的 JSON 数据,其中 `text` 字段描述表情内容(如 `[满头问号]`)
+
+**解析机制**:
+- 新增 `_parse_face_message()` 静态方法,提取并解析 `ext` 字段
+- 自动解码 base64 数据并提取 JSON 中的 `text` 字段
+- 转换格式:`<faceType=...>` → `[表情:满头问号]`
+- 解析失败时回退到 `[表情]` 占位符
+
+**影响范围**:
+- `QQOfficialPlatformAdapter._parse_from_qqofficial()` 方法:消息解析逻辑
+- 支持私聊和群聊场景下的表情消息
+
+该功能改善了 QQ 官方平台的消息处理和显示,使表情消息在日志、历史记录和对话中以可读格式呈现,提升用户体验。
+
+#### QQ 官方 Webhook 重复事件去重
+
+[PR #6397](https://github.com/AstrBotDevs/AstrBot/pull/6397) 为 QQ 官方 Webhook 模式新增了事件去重机制,防止重复事件被多次处理:
+
+**去重机制**:
+- 维护 `_seen_event_ids` 缓存,记录已处理的事件 ID 和时间戳
+- 使用 60 秒的 TTL(`_dedup_ttl`),自动清理过期的事件 ID
+- 惰性淘汰策略:在检查新事件时自动清理过期记录,防止缓存无限增长
+
+**处理逻辑**:
+- 接收到 webhook 事件时,检查事件 ID 是否在缓存中
+- 如果事件 ID 已存在,记录调试日志并返回 `{"opcode": 12}`,跳过处理
+- 如果事件 ID 不存在,将其添加到缓存并继续处理
+
+**适用场景**:
+- QQ 官方 Webhook 重试回调(retry callback)
+- 网络不稳定导致的重复推送
+- 防止消息重复处理和响应
+
+该机制提升了 QQ 官方 Webhook 模式的健壮性,确保事件在重复推送场景下仅被处理一次。
+
+---
+
+### 15. AI 提供商支持更新(PR #6397)
+
+#### MiniMax 提供商支持
+
+[PR #6397](https://github.com/AstrBotDevs/AstrBot/pull/6397) 新增了 MiniMax 作为支持的 AI 提供商:
+
+**配置模板**:
+- **提供商 ID**:`minimax`
+- **提供商类型**:`openai_chat_completion`
+- **API 地址**:`https://api.minimaxi.com/v1`
+- **超时时间**:120 秒
+- **默认状态**:已启用(`enable: true`)
+
+**使用方式**:
+- 在配置文件中添加 MiniMax API 密钥(`key` 字段)
+- 支持标准的 OpenAI Chat Completion 接口
+- 可通过 `/provider` 命令切换到 MiniMax 提供商
+- 可通过 `/model` 命令选择 MiniMax 模型
+
+**Dashboard 集成**:
+- 提供商图标使用 LobeHub 官方 SVG(`https://cdn.jsdelivr.net/npm/@lobehub/icons-static-svg@latest/icons/minimax.svg`)
+- 在提供商选择下拉菜单中显示为 "MiniMax"
+
+该支持扩展了 AstrBot 的 AI 提供商生态,为用户提供更多模型选择。
+
+#### Groq 提供商兼容性修复
+
+[PR #6397](https://github.com/AstrBotDevs/AstrBot/pull/6397) 修复了 Groq 提供商在处理消息历史时的兼容性问题:
+
+**问题背景**:
+- Groq API 拒绝包含 `reasoning_content` 字段的 assistant 历史消息
+- 当使用推理模型(如 DeepSeek R1)生成的历史记录发送到 Groq 时,会导致请求失败
+
+**修复实现**:
+- 新增 `ProviderGroq._finally_convert_payload()` 方法,重写父类的 payload 转换逻辑
+- 在发送请求前,自动移除 assistant 消息中的 `reasoning_content` 和 `reasoning` 字段
+- 仅影响 Groq 提供商,不影响其他提供商的行为
+
+**技术细节**:
+```python
+def _finally_convert_payload(self, payloads: dict) -> None:
+    """Groq rejects assistant history items that include reasoning_content."""
+    super()._finally_convert_payload(payloads)
+    for message in payloads.get("messages", []):
+        if message.get("role") == "assistant":
+            message.pop("reasoning_content", None)
+            message.pop("reasoning", None)
+```
+
+**影响范围**:
+- 使用 Groq 提供商时,assistant 历史消息中的推理内容会被自动过滤
+- 用户消息和其他字段不受影响
+- 确保 Groq API 请求成功,避免因 `reasoning_content` 字段导致的拒绝错误
+
+该修复提升了 Groq 提供商与推理模型(reasoning models)的互操作性,确保用户可以在不同提供商之间无缝切换。
+
+#### OpenAI 提供商代码质量优化
+
+[PR #6397](https://github.com/AstrBotDevs/AstrBot/pull/6397) 优化了 OpenAI 提供商的代码质量,采用更 Pythonic 的布尔值检查方式:
+
+**优化内容**:
+- 使用 `if not chunk.choices:` 替代 `if len(chunk.choices) == 0:`
+- 使用 `if delta and delta.content:` 替代显式的 `if delta.content:` 检查
+- 使用 `if not completion.choices:` 替代 `if len(completion.choices) == 0:`
+
+**代码改进示例**:
+```python
+# 修复前
+if len(chunk.choices) == 0:
+    continue
+
+# 修复后
+if not chunk.choices:
+    continue
+```
+
+**影响范围**:
+- `_query_stream()` 方法:流式查询逻辑
+- `_extract_reasoning_content()` 方法:推理内容提取
+- `_parse_openai_completion()` 方法:响应解析
+
+该优化遵循 Python 最佳实践,提升代码可读性和可维护性,但不改变功能行为。
+
+---
+
+### 16. 其他优化
 - JWT 处理和错误处理机制增强,提升系统安全性和稳定性
 - UI 细节优化,提升用户体验
 - 日志与异常处理增强,便于问题追踪

[Accept] [Decline]

Note: You must be authenticated to accept/decline updates.

How did I do? Any feedback?  Join Discord

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR appears to be a “master → dev” sync that bundles several platform/provider fixes plus small dashboard/docs/config updates across the AstrBot framework.

Changes:

  • Provider payload handling: preserve OpenAI reasoning_content while stripping it for Groq, with new tests.
  • Platform adapters: send Telegram GIFs as animations and add QQ Official webhook dedup + face-tag parsing.
  • Misc updates: provider icon CDN switch, docs link fix, MiniMax provider template, Neo skill schema tightening, and a maintenance script for closing duplicate plugin-publish issues.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
astrbot/core/provider/sources/openai_source.py Minor safety tweaks around choices/delta handling.
astrbot/core/provider/sources/groq_source.py Drop reasoning_content/reasoning from assistant history after payload conversion.
tests/test_openai_source.py Adds tests covering OpenAI vs Groq assistant-history reasoning behavior.
astrbot/core/platform/sources/telegram/tg_event.py Detect GIFs and send via send_animation (also adjusts chat actions).
astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py Adds an in-memory event-id dedup cache for webhook retries.
astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py Adds parsing for <faceType=...> tags into readable text.
dashboard/src/utils/providerUtils.js Switches provider icon URLs from npmmirror to jsdelivr.
docs/zh/what-is-astrbot.md Fixes the “连接模型服务” link path.
astrbot/core/config/default.py Adds a MiniMax chat provider template entry.
astrbot/core/computer/tools/neo_skills.py Tightens JSON schema for payload array items to objects.
astrbot/core/astr_main_agent_resources.py Updates tool description text for send_message_to_user.
scripts/close_duplicate_plugin_publish_issues.py New CLI script to close duplicate open issues by title (dry-run by default).

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +115 to +128
now = time.monotonic()
# Lazily evict expired entries to prevent unbounded growth.
expired = [
k
for k, ts in self._seen_event_ids.items()
if now - ts > self._dedup_ttl
]
for k in expired:
del self._seen_event_ids[k]
if event_id in self._seen_event_ids:
logger.debug(f"Duplicate webhook event {event_id!r}, skipping.")
return {"opcode": 12}
self._seen_event_ids[event_id] = now

Comment on lines +426 to +429
return f"[表情:{emoji_text}]"
except Exception:
pass
# Fallback if parsing fails
Comment on lines +29 to +35
if path.lower().endswith(".gif"):
return True
try:
with open(path, "rb") as f:
return f.read(6) in (b"GIF87a", b"GIF89a")
except OSError:
return False
"Send message to the user. "
"Supports various message types including `plain`, `image`, `record`, `video`, `file`, and `mention_user`. "
"Use this tool to send media files (`image`, `record`, `video`, `file`), "
"or when you need to proactively message the user(such as cron job). For normal text replies, you can output directly."
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

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: 64e0183b55

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

"provider_type": "chat_completion",
"enable": True,
"key": [],
"api_base": "https://api.minimaxi.com/v1",

Choose a reason for hiding this comment

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

P1 Badge Fix MiniMax default API base domain typo

The new MiniMax provider preset sets api_base to https://api.minimaxi.com/v1, which has an extra i in the hostname; users who rely on this default will fail to connect (DNS/connection errors) until they manually override it. This looks unintended because the MiniMax TTS preset in the same config uses the api.minimax.chat domain, so the chat preset should use a valid MiniMax endpoint as well.

Useful? React with 👍 / 👎.

Copy link
Member Author

Choose a reason for hiding this comment

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

@LIghtJUNction
Copy link
Member Author

@copilot 分析冲突严重程度

Copy link
Contributor

Copilot AI commented Mar 15, 2026

@LIghtJUNction I've opened a new pull request, #6398, to work on those changes. Once the pull request is ready, I'll request review from you.

Duplicated checklist items in the pull request template for clarity and emphasis.
Refactor checklist items in PR template
Copilot AI and others added 6 commits March 15, 2026 17:28
Co-authored-by: LIghtJUNction <106986785+LIghtJUNction@users.noreply.github.com>
Updated the checklist validation script and modified the comment for PR closure.
…ions-summary

feat: auto-close PRs when author checks "did not read" checklist item
@dosubot dosubot bot added size:XL This PR changes 500-999 lines, ignoring generated files. and removed size:L This PR changes 100-499 lines, ignoring generated files. labels Mar 15, 2026
LIghtJUNction and others added 10 commits March 16, 2026 02:21
Update session ID extraction to handle group and single chat types.
Fixes #6283

When adding a new embedding provider, the knowledge base creation page
did not show the new provider until restart.

Root cause: create_provider() did not update self.providers_config,
which is used by get_provider_config_list() to return provider lists.

This fix syncs the in-memory config after loading the new provider,
consistent with how reload() handles config updates.

Co-authored-by: ccsang <ccsang@users.noreply.github.com>
Added a note about using a backup address if the management panel cannot be accessed.
@LIghtJUNction LIghtJUNction merged commit 2685528 into dev Mar 16, 2026
6 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:core The bug / feature is about astrbot's core, backend area:platform The bug / feature is about IM platform adapter, such as QQ, Lark, Telegram, WebChat and so on. area:provider The bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner. area:webui The bug / feature is about webui(dashboard) of astrbot. size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.