From 7beab796bbb4714d2ce6ee29c1f8730513bf3c21 Mon Sep 17 00:00:00 2001
From: Yufeng He <40085740+he-yufeng@users.noreply.github.com>
Date: Sat, 21 Mar 2026 01:11:34 +0800
Subject: [PATCH] fix: skills-like re-query missing extra_user_content_parts
causes image_caption not to be injected (#6710)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
当使用 skills-like tool mode 时,_resolve_tool_exec 的 re-query 调用没有
传递 extra_user_content_parts,导致图片描述等附加内容丢失。
fixes #6702
Co-authored-by: Soulter <37870767+Soulter@users.noreply.github.com>
---
.../agent/runners/tool_loop_agent_runner.py | 1 +
tests/test_tool_loop_agent_runner.py | 81 +++++++++++++++++++
2 files changed, 82 insertions(+)
diff --git a/astrbot/core/agent/runners/tool_loop_agent_runner.py b/astrbot/core/agent/runners/tool_loop_agent_runner.py
index cb7e22f34..93f45475e 100644
--- a/astrbot/core/agent/runners/tool_loop_agent_runner.py
+++ b/astrbot/core/agent/runners/tool_loop_agent_runner.py
@@ -930,6 +930,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
func_tool=param_subset,
model=self.req.model,
session_id=self.req.session_id,
+ extra_user_content_parts=self.req.extra_user_content_parts,
abort_signal=self._abort_signal,
)
if requery_resp:
diff --git a/tests/test_tool_loop_agent_runner.py b/tests/test_tool_loop_agent_runner.py
index f983c5b4d..ccab26735 100644
--- a/tests/test_tool_loop_agent_runner.py
+++ b/tests/test_tool_loop_agent_runner.py
@@ -699,6 +699,87 @@ async def test_follow_up_ticket_not_consumed_when_no_next_tool_call(
assert ticket.consumed is False
+@pytest.mark.asyncio
+async def test_skills_like_requery_passes_extra_user_content_parts():
+ """skills-like 模式 re-query 时应传递 extra_user_content_parts(如 image_caption)"""
+ from astrbot.core.agent.message import TextPart
+
+ captured_kwargs = {}
+
+ class SkillsLikeProvider(MockProvider):
+ async def text_chat(self, **kwargs) -> LLMResponse:
+ self.call_count += 1
+ if self.call_count == 1:
+ # 第一次调用:返回工具选择(light schema)
+ return LLMResponse(
+ role="assistant",
+ completion_text="选择工具",
+ tools_call_name=["test_tool"],
+ tools_call_args=[{"query": "test"}],
+ tools_call_ids=["call_1"],
+ usage=TokenUsage(input_other=10, output=5),
+ )
+ if self.call_count == 2:
+ # 第二次调用:re-query with param schema
+ captured_kwargs.update(kwargs)
+ return LLMResponse(
+ role="assistant",
+ completion_text="调用工具",
+ tools_call_name=["test_tool"],
+ tools_call_args=[{"query": "actual"}],
+ tools_call_ids=["call_2"],
+ usage=TokenUsage(input_other=10, output=5),
+ )
+ # 后续调用:正常回复
+ return LLMResponse(
+ role="assistant",
+ completion_text="最终回复",
+ usage=TokenUsage(input_other=10, output=5),
+ )
+
+ provider = SkillsLikeProvider()
+ tool = FunctionTool(
+ name="test_tool",
+ description="测试",
+ parameters={"type": "object", "properties": {"query": {"type": "string"}}},
+ handler=AsyncMock(),
+ )
+ tool_set = ToolSet(tools=[tool])
+
+ caption_part = TextPart(text="一张猫的照片")
+ req = ProviderRequest(
+ prompt="看看这张图",
+ func_tool=tool_set,
+ contexts=[],
+ extra_user_content_parts=[caption_part],
+ )
+
+ event = MockEvent(umo="test_umo", sender_id="test_sender")
+ ctx = MockAgentContext(event)
+ run_context = ContextWrapper(context=ctx)
+ runner = ToolLoopAgentRunner()
+
+ await runner.reset(
+ provider=provider,
+ request=req,
+ run_context=run_context,
+ tool_executor=MockToolExecutor(),
+ agent_hooks=MockHooks(),
+ tool_schema_mode="skills_like",
+ )
+
+ async for _ in runner.step():
+ pass
+
+ # 验证 re-query 调用包含了 extra_user_content_parts
+ assert "extra_user_content_parts" in captured_kwargs, (
+ "re-query 应该传递 extra_user_content_parts"
+ )
+ parts = captured_kwargs["extra_user_content_parts"]
+ assert len(parts) == 1
+ assert parts[0].text == "一张猫的照片"
+
+
@pytest.mark.asyncio
async def test_follow_up_accepted_when_active_and_not_stopping(
runner, mock_provider, provider_request, mock_tool_executor, mock_hooks