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"])