Skip to content

[Bug]send_message_to_user 工具在普通对话中导致消息重复发送 #6402

@Luna-channel

Description

@Luna-channel

What happened / 发生了什么

问题描述

当 AI 在普通对话中调用 send_message_to_user 工具发送消息后,又返回相同内容的文本回复,导致用户收到两次相同的消息:

  1. 第一次:通过 send_message_to_user 工具发送的完整消息
  2. 第二次:通过正常的消息流程发送的相同内容(可能经过 splitter插件或原生正则 分段,经过分析判断并非插件的问题。这个非常难复现,因为据我所知只有这一个用户遇到了这个问题,并且他本人没有什么技术基础)

Reproduce / 如何复现?

复现条件

  • 平台支持主动消息(platform_meta.support_proactive_message = True
  • AI 模型在普通对话中选择使用 send_message_to_user 工具(非预期行为)
  • 工具执行后,AI 又返回了相同内容的文本回复

日志示例

[12:12:03.089] completion: ... finish_reason='tool_calls', tool_calls=[...name='send_message_to_user'...]
[12:12:03.167] Agent 使用工具: ['send_message_to_user']
[12:12:03.904] Tool `send_message_to_user` Result: Message sent to session...

# 工具发送完消息后,AI 又返回了相同内容的文本回复
[12:12:18.455] completion: ... finish_reason='stop', message=ChatCompletionMessage(content=' (轻笑一声...')

问题根源

astrbot/core/astr_main_agent.py 第 1166-1169 行:

if event.platform_meta.support_proactive_message:
    if req.func_tool is None:
        req.func_tool = ToolSet()
    req.func_tool.add_tool(SEND_MESSAGE_TO_USER_TOOL)

只要平台支持主动消息,send_message_to_user 工具就会被注册到普通对话的工具集中。

但根据工具描述,这个工具应该 "Only use this tool when you need to proactively message the user",即主动消息场景(如定时任务触发、后台任务完成通知等),而不是普通对话。

建议解决方案

方案 1:添加配置项控制

# 只在主动型 Agent 场景下注册此工具,普通对话不注册
proactive_agent_enabled = config.get("proactive_agent_settings", {}).get("enable", False)
if event.platform_meta.support_proactive_message and proactive_agent_enabled:
    if req.func_tool is None:
        req.func_tool = ToolSet()
    req.func_tool.add_tool(SEND_MESSAGE_TO_USER_TOOL)

方案 2:在 Agent Runner 层面处理

tool_loop_agent_runner.py 中,当 send_message_to_user 工具执行后,标记该会话已发送消息,后续的文本回复如果内容相同则跳过发送。

方案 3:修改工具描述

强化工具描述,明确告知 AI 在普通对话中不应使用此工具:

description: str = (
    "Directly send message to the user. "
    "IMPORTANT: Only use this tool in proactive agent scenarios (cron jobs, background tasks). "
    "In normal conversations, do NOT use this tool - just output your reply directly."
)

AstrBot version, deployment method (e.g., Windows Docker Desktop deployment), provider used, and messaging platform used. / AstrBot 版本、部署方式(如 Windows Docker Desktop 部署)、使用的提供商、使用的消息平台适配器

环境信息

  • AstrBot 版本: v4.19.5
  • 平台: aiocqhttp
  • AI 模型: gemini-3.1-pro-preview

OS

Linux

Logs / 报错日志

日志示例

# 第一次 LLM 响应:AI 决定调用 send_message_to_user 工具
[2026-03-15 12:12:03.089] [Core] [DBUG] [sources.openai_source:262]: completion: ChatCompletion(
    id='chatcmpl-xxx', 
    choices=[Choice(finish_reason='tool_calls', index=0, logprobs=None, 
        message=ChatCompletionMessage(content='', refusal=None, role='assistant', 
            tool_calls=[ChatCompletionMessageFunctionToolCall(
                id='call_xxx',
                function=Function(
                    arguments='{"messages":[{"text": " (某段回复内容) \\n\\n...","type":"plain"}]}', 
                    name='send_message_to_user'
                ), 
                type='function'
            )]
        )
    )], 
    model='gemini-3.1-pro-preview-high'
)

# 多个插件在 on_decorating_result 阶段清空消息结果(因为这是工具调用,没有直接内容)
[2026-03-15 12:12:03.151] [Core] [DBUG]: hook(on_decorating_result) -> memes - on_decorating_result 将消息结果清空。
[2026-03-15 12:12:03.155] [Core] [DBUG]: hook(on_decorating_result) -> astrbot_plugin_soulmap - on_decorating_result 将消息结果清空。
[2026-03-15 12:12:03.160] [Core] [DBUG]: hook(on_decorating_result) -> astrbot_plugin_splitter - on_decorating_result 将消息结果清空。

# 框架发送空消息(因为 result 被清空了)
[2026-03-15 12:12:03.161] [Core] [INFO] [respond.stage:184]: Prepare to send - xxx: 

# 工具被执行
[2026-03-15 12:12:03.167] [Core] [INFO] [runners.tool_loop_agent_runner:657]: Agent 使用工具: ['send_message_to_user']
[2026-03-15 12:12:03.168] [Core] [INFO] [runners.tool_loop_agent_runner:703]: 使用工具:send_message_to_user,参数:{'messages': [{'text': ' (某段回复内容) ...', 'type': 'plain'}]}
[2026-03-15 12:12:03.904] [Core] [INFO] [runners.tool_loop_agent_runner:881]: Tool `send_message_to_user` Result: Message sent to session xxx

# ========= 问题关键:工具执行后,LLM 又返回了相同内容的普通文本回复 =========

# 第二次 LLM 响应:AI 返回普通文本(finish_reason='stop'),内容与工具发送的相同
[2026-03-15 12:12:18.455] [Core] [DBUG] [sources.openai_source:262]: completion: ChatCompletion(
    id='chatcmpl-yyy', 
    choices=[Choice(finish_reason='stop', index=0, logprobs=None,
        message=ChatCompletionMessage(
            content=' (某段回复内容) \\n\\n...',  # <-- 与工具发送的内容相同!
            refusal=None, role='assistant', 
            tool_calls=None  # 这次没有工具调用
        )
    )], 
    model='gemini-3.1-pro-preview-high'
)

# Splitter 插件对这个文本回复进行分段发送
[2026-03-15 12:12:18.522] [Plug] [INFO] [astrbot_plugin_splitter.main:129]: [Splitter] 消息被分为 12 段。
[2026-03-15 12:12:18.523] [Plug] [INFO] [astrbot_plugin_splitter.main:229]: [Splitter] 第 1/12 段 (主动发送): (某段回复内容)
...
[2026-03-15 12:12:46.875] [Plug] [INFO] [astrbot_plugin_splitter.main:229]: [Splitter] 第 12/12 段 (交给框架): ...

# 结果:用户收到了两次相同内容的消息
# 1. 通过 send_message_to_user 工具发送的完整消息
# 2. 通过 Splitter 分段发送的同样内容

Are you willing to submit a PR? / 你愿意提交 PR 吗?

  • Yes!

Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:coreThe bug / feature is about astrbot's core, backendbugSomething isn't workingfeature:pluginThe bug / feature is about AstrBot plugin system.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions