Skip to content

feat: show quoted message content in group chat context#8634

Open
ScarletPupil wants to merge 3 commits into
AstrBotDevs:masterfrom
ScarletPupil:feat/group-context-reply-quote
Open

feat: show quoted message content in group chat context#8634
ScarletPupil wants to merge 3 commits into
AstrBotDevs:masterfrom
ScarletPupil:feat/group-context-reply-quote

Conversation

@ScarletPupil
Copy link
Copy Markdown

@ScarletPupil ScarletPupil commented Jun 6, 2026

Include Reply component content in _format_message so the LLM can see what message was quoted when someone replies to the bot.

Problem

群聊中,当有人引用(Reply)机器人的消息时,group_chat_context.py_format_message() 方法只处理 Plain、Image、At 三种消息段,跳过了 Reply。虽然 Reply 组件通过 OneBot API 已经拿到了被引用消息的完整信息(sender_nicknamemessage_strchain 等),但这些内容没有传递给 LLM。

结果:LLM 知道有人回复了我,但不知道被引用的消息具体说了什么。

Solution

_format_message 中新增 Reply 消息段的处理:

  • 文本引用:展示 [引用消息(发送者: 文本内容)]
  • 非文本引用(图片、语音等):通过 _describe_chain() 辅助函数描述引用内容

Before:

[小明/14:30:20]: 再解释一下

After:

[小明/14:30:20]:  [引用消息(机器人: 今天天气怎么样)] 再解释一下

Modifications

  • This is NOT a breaking change.

仅修改一个文件:astrbot/builtin_stars/astrbot/group_chat_context.py

  • import 添加 Reply
  • _format_message(): 新增 elif isinstance(comp, Reply) 分支
  • 新增 _describe_chain() 辅助函数

Test Results

引用纯文本: 今天天气怎么样
引用图片: [图片]
引用混合: 看这个图[图片]
测试通过

Summary by Sourcery

Include quoted reply message content in formatted group chat context so the LLM can see what was replied to.

New Features:

  • Display quoted reply message content, including sender and text or a summarized description of non-text content, in formatted group chat messages for LLM context.

Enhancements:

  • Introduce a helper to generate concise descriptions of mixed-content message chains when showing quoted replies in group chats.

Include Reply component content in _format_message so the LLM can
see what message was quoted when someone replies to the bot.

- Add Reply import
- Handle Reply in _format_message with message_str or chain fallback
- Add _describe_chain helper for non-text quoted content (images, etc.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@dosubot dosubot Bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Jun 6, 2026
Copy link
Copy Markdown
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 issue, and left some high level feedback:

  • Consider truncating very long Reply contents (both message_str and _describe_chain output) to avoid excessively inflating the group chat context sent to the LLM.
  • _describe_chain` largely re-implements formatting logic for message components; you might want to refactor common formatting into a shared helper so normal messages and quoted messages stay consistent as new component types are added.
  • The reply descriptions mix English markers like [At: ...] with Chinese markers like [图片] and [语音]; it may be worth standardizing the language and format of these tags to keep the context output consistent.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider truncating very long `Reply` contents (both `message_str` and `_describe_chain` output) to avoid excessively inflating the group chat context sent to the LLM.
- _describe_chain` largely re-implements formatting logic for message components; you might want to refactor common formatting into a shared helper so normal messages and quoted messages stay consistent as new component types are added.
- The reply descriptions mix English markers like `[At: ...]` with Chinese markers like `[图片]` and `[语音]`; it may be worth standardizing the language and format of these tags to keep the context output consistent.

## Individual Comments

### Comment 1
<location path="astrbot/builtin_stars/astrbot/group_chat_context.py" line_range="236-237" />
<code_context>
+def _describe_chain(chain: list) -> str:
+    """简要描述消息链内容,用于引用消息的展示"""
+    desc = []
+    for c in chain:
+        cls_name = c.__class__.__name__
+        if cls_name == "Plain" and getattr(c, "text", None):
+            desc.append(c.text)
</code_context>
<issue_to_address>
**suggestion:** Using class-name string checks may be brittle; consider `isinstance` against the concrete types instead.

This approach will fail silently if class names change due to refactors, subclassing, or alternative implementations. If the concrete types are available (e.g. `Plain`, `Image`, `At`), preferring `isinstance(c, Plain)` etc. makes the check more stable without changing behavior.

Suggested implementation:

```python
def _describe_chain(chain: list) -> str:
    """简要描述消息链内容,用于引用消息的展示"""
    desc = []
    for c in chain:
        if isinstance(c, Plain) and getattr(c, "text", None):
            desc.append(c.text)
        elif isinstance(c, Image):
            desc.append("[图片]")
        elif isinstance(c, At):
            name = getattr(c, "name", "") or getattr(c, "qq", "")
            desc.append(f"[At: {name}]")
        elif isinstance(c, Record):
            desc.append("[语音]")
        elif isinstance(c, Video):
            desc.append("[视频]")
        elif isinstance(c, File):

```

If `Plain`, `Image`, `At`, `Record`, `Video`, and `File` are not already imported in this module, add the appropriate imports (e.g. from the library providing the message segment types) at the top of `group_chat_context.py`. Keep the import style consistent with how other message element types are imported elsewhere in the file or codebase.
</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 thread astrbot/builtin_stars/astrbot/group_chat_context.py Outdated
Copy link
Copy Markdown
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 support for formatting replied messages in group chat contexts by handling the Reply component. The reviewer suggested reusing the existing _outline_chain method from AstrMessageEvent instead of introducing a new _describe_chain helper function to avoid code duplication and ensure consistent formatting across the application.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +217 to +253
elif isinstance(comp, Reply):
if comp.message_str:
parts.append(
f" [引用消息({comp.sender_nickname}: {comp.message_str})]"
)
elif comp.chain:
chain_desc = _describe_chain(comp.chain)
parts.append(
f" [引用消息({comp.sender_nickname}: {chain_desc})]"
)
else:
parts.append(" [引用消息]")

return "".join(parts)


def _describe_chain(chain: list) -> str:
"""简要描述消息链内容,用于引用消息的展示"""
desc = []
for c in chain:
cls_name = c.__class__.__name__
if cls_name == "Plain" and getattr(c, "text", None):
desc.append(c.text)
elif cls_name == "Image":
desc.append("[图片]")
elif cls_name == "At":
name = getattr(c, "name", "") or getattr(c, "qq", "")
desc.append(f"[At: {name}]")
elif cls_name == "Record":
desc.append("[语音]")
elif cls_name == "Video":
desc.append("[视频]")
elif cls_name == "File":
desc.append(f"[文件: {getattr(c, 'name', '') or ''}]")
else:
desc.append(f"[{cls_name}]")
return "".join(desc) or "[未知内容]"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

为了避免代码重复并保持消息格式化逻辑的一致性,建议直接复用 AstrMessageEvent 中已有的 _outline_chain 方法来描述引用消息链的内容。这样可以完全省去新增的 _describe_chain 辅助函数,并确保所有消息组件(如 FaceAtAllForward 等)的展示格式在整个应用中保持一致。

Suggested change
elif isinstance(comp, Reply):
if comp.message_str:
parts.append(
f" [引用消息({comp.sender_nickname}: {comp.message_str})]"
)
elif comp.chain:
chain_desc = _describe_chain(comp.chain)
parts.append(
f" [引用消息({comp.sender_nickname}: {chain_desc})]"
)
else:
parts.append(" [引用消息]")
return "".join(parts)
def _describe_chain(chain: list) -> str:
"""简要描述消息链内容,用于引用消息的展示"""
desc = []
for c in chain:
cls_name = c.__class__.__name__
if cls_name == "Plain" and getattr(c, "text", None):
desc.append(c.text)
elif cls_name == "Image":
desc.append("[图片]")
elif cls_name == "At":
name = getattr(c, "name", "") or getattr(c, "qq", "")
desc.append(f"[At: {name}]")
elif cls_name == "Record":
desc.append("[语音]")
elif cls_name == "Video":
desc.append("[视频]")
elif cls_name == "File":
desc.append(f"[文件: {getattr(c, 'name', '') or ''}]")
else:
desc.append(f"[{cls_name}]")
return "".join(desc) or "[未知内容]"
elif isinstance(comp, Reply):
if comp.message_str:
parts.append(
f" [引用消息({comp.sender_nickname}: {comp.message_str})]"
)
elif comp.chain:
chain_desc = event._outline_chain(comp.chain).strip()
parts.append(
f" [引用消息({comp.sender_nickname}: {chain_desc})]"
)
else:
parts.append(" [引用消息]")
return "".join(parts)
References
  1. When implementing similar functionality for different cases (e.g., direct vs. quoted attachments), refactor the logic into a shared helper function to avoid code duplication.

ScarletPupil and others added 2 commits June 7, 2026 02:59
- Replace string class-name checks with isinstance() for robustness
- Add _truncate_reply_text to prevent long quoted content inflating context
- Unify markers to English ([Image], [Voice], [Quote] etc.)
- Import Record, Video, File for isinstance checks

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant