From c9e62edf9ead4e7fe5d8e839759cf551979e1105 Mon Sep 17 00:00:00 2001
From: Yufeng He <40085740+he-yufeng@users.noreply.github.com>
Date: Fri, 20 Mar 2026 23:37:25 +0800
Subject: [PATCH] =?UTF-8?q?fix:=20skills-like=20re-query=20=E4=B8=A2?=
=?UTF-8?q?=E5=A4=B1=20extra=5Fuser=5Fcontent=5Fparts=20=E5=AF=BC=E8=87=B4?=
=?UTF-8?q?=20image=5Fcaption=20=E4=B8=8D=E6=B3=A8=E5=85=A5?=
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
---
.../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 743b280070..b70feb9562 100644
--- a/astrbot/core/agent/runners/tool_loop_agent_runner.py
+++ b/astrbot/core/agent/runners/tool_loop_agent_runner.py
@@ -945,6 +945,7 @@ async def _resolve_tool_exec(
func_tool=param_subset,
model=self.req.model,
session_id=self.req.session_id,
+ extra_user_content_parts=self.req.extra_user_content_parts,
)
if requery_resp:
llm_resp = requery_resp
diff --git a/tests/test_tool_loop_agent_runner.py b/tests/test_tool_loop_agent_runner.py
index 38c601cee5..3e1476cbb6 100644
--- a/tests/test_tool_loop_agent_runner.py
+++ b/tests/test_tool_loop_agent_runner.py
@@ -536,6 +536,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 == "一张猫的照片"
+
+
if __name__ == "__main__":
# 运行测试
pytest.main([__file__, "-v"])