From 803762c99abf8adfba01d7f662021ba9190adfec Mon Sep 17 00:00:00 2001 From: alicesainta Date: Fri, 17 Apr 2026 00:52:08 +0800 Subject: [PATCH] feat: add buffered intermediate messages for non-streaming agent loop --- astrbot/core/astr_agent_run_util.py | 32 +++++++++++++++++++ astrbot/core/config/default.py | 13 ++++++++ .../method/agent_sub_stages/internal.py | 7 ++++ 3 files changed, 52 insertions(+) diff --git a/astrbot/core/astr_agent_run_util.py b/astrbot/core/astr_agent_run_util.py index eca24699a..1795b6e5b 100644 --- a/astrbot/core/astr_agent_run_util.py +++ b/astrbot/core/astr_agent_run_util.py @@ -94,10 +94,12 @@ async def run_agent( show_tool_call_result: bool = False, stream_to_general: bool = False, show_reasoning: bool = False, + buffer_intermediate_messages: bool = False, ) -> AsyncGenerator[MessageChain | None, None]: step_idx = 0 astr_event = agent_runner.run_context.context.event tool_name_by_call_id: dict[str, str] = {} + buffered_llm_chains: list[MessageChain] = [] while step_idx < max_step + 1: step_idx += 1 @@ -197,6 +199,31 @@ async def run_agent( continue if stream_to_general or not agent_runner.streaming: + if ( + buffer_intermediate_messages + and not stream_to_general + and not agent_runner.streaming + and resp.type == "llm_result" + ): + buffered_llm_chains.append(resp.data["chain"]) + if not agent_runner.done(): + continue + + merged_chain = MessageChain() + for chain in buffered_llm_chains: + merged_chain.chain.extend(chain.chain) + buffered_llm_chains.clear() + + astr_event.set_result( + MessageEventResult( + chain=merged_chain.chain, + result_content_type=ResultContentType.LLM_RESULT, + ), + ) + yield + astr_event.clear_result() + continue + content_typ = ( ResultContentType.LLM_RESULT if resp.type == "llm_result" @@ -288,6 +315,7 @@ async def run_live_agent( show_tool_use: bool = True, show_tool_call_result: bool = False, show_reasoning: bool = False, + buffer_intermediate_messages: bool = False, ) -> AsyncGenerator[MessageChain | None, None]: """Live Mode 的 Agent 运行器,支持流式 TTS @@ -311,6 +339,7 @@ async def run_live_agent( show_tool_call_result=show_tool_call_result, stream_to_general=False, show_reasoning=show_reasoning, + buffer_intermediate_messages=buffer_intermediate_messages, ): yield chain return @@ -343,6 +372,7 @@ async def run_live_agent( show_tool_use, show_tool_call_result, show_reasoning, + buffer_intermediate_messages, ) ) @@ -430,6 +460,7 @@ async def _run_agent_feeder( show_tool_use: bool, show_tool_call_result: bool, show_reasoning: bool, + buffer_intermediate_messages: bool, ) -> None: """运行 Agent 并将文本输出分句放入队列""" buffer = "" @@ -441,6 +472,7 @@ async def _run_agent_feeder( show_tool_call_result=show_tool_call_result, stream_to_general=False, show_reasoning=show_reasoning, + buffer_intermediate_messages=buffer_intermediate_messages, ): if chain is None: continue diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index 0237b176d..7b08dc961 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -134,6 +134,7 @@ DEFAULT_CONFIG = { "streaming_response": False, "show_tool_use_status": False, "show_tool_call_result": False, + "buffer_intermediate_messages": False, "sanitize_context_by_modalities": False, "max_quoted_fallback_images": 20, "quoted_message_parser": { @@ -2764,6 +2765,9 @@ CONFIG_METADATA_2 = { "show_tool_call_result": { "type": "bool", }, + "buffer_intermediate_messages": { + "type": "bool", + }, "unsupported_streaming_strategy": { "type": "string", }, @@ -3530,6 +3534,15 @@ CONFIG_METADATA_3 = { "provider_settings.show_tool_use_status": True, }, }, + "provider_settings.buffer_intermediate_messages": { + "description": "合并 Agent 中间消息(非流式)", + "type": "bool", + "hint": "开启后,非流式模式下多步工具调用过程中产生的中间文本将缓冲,待 Agent 完成后合并为一条回复发送。", + "condition": { + "provider_settings.agent_runner_type": "local", + "provider_settings.streaming_response": False, + }, + }, "provider_settings.sanitize_context_by_modalities": { "description": "按模型能力清理历史上下文", "type": "bool", diff --git a/astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py b/astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py index e0ba2463c..0d43e4da9 100644 --- a/astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py +++ b/astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py @@ -66,6 +66,10 @@ class InternalAgentSubStage(Stage): self.max_step = 30 self.show_tool_use: bool = settings.get("show_tool_use_status", True) self.show_tool_call_result: bool = settings.get("show_tool_call_result", False) + self.buffer_intermediate_messages: bool = settings.get( + "buffer_intermediate_messages", + False, + ) self.show_reasoning = settings.get("display_reasoning_text", False) self.sanitize_context_by_modalities: bool = settings.get( "sanitize_context_by_modalities", @@ -280,6 +284,7 @@ class InternalAgentSubStage(Stage): self.show_tool_use, self.show_tool_call_result, show_reasoning=self.show_reasoning, + buffer_intermediate_messages=self.buffer_intermediate_messages, ), ), ) @@ -310,6 +315,7 @@ class InternalAgentSubStage(Stage): self.show_tool_use, self.show_tool_call_result, show_reasoning=self.show_reasoning, + buffer_intermediate_messages=self.buffer_intermediate_messages, ), ), ) @@ -340,6 +346,7 @@ class InternalAgentSubStage(Stage): self.show_tool_call_result, stream_to_general, show_reasoning=self.show_reasoning, + buffer_intermediate_messages=self.buffer_intermediate_messages, ): yield