fix: skills-like re-query missing extra_user_content_parts causes image_caption not to be injected (#6710)

当使用 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>
This commit is contained in:
Yufeng He
2026-03-21 01:11:34 +08:00
committed by GitHub
parent b2797b6f16
commit 7beab796bb
2 changed files with 82 additions and 0 deletions

View File

@@ -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:

View File

@@ -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="<image_caption>一张猫的照片</image_caption>")
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 == "<image_caption>一张猫的照片</image_caption>"
@pytest.mark.asyncio
async def test_follow_up_accepted_when_active_and_not_stopping(
runner, mock_provider, provider_request, mock_tool_executor, mock_hooks