fix: prevent duplicate processing of quoted images by multimodal main providers when no dedicated image caption provider is configured (#8401)

Co-authored-by: C₂₂H₂₅NO₆ <Sisyphbaous-DT-Project@users.noreply.github.com>
Co-authored-by: Soulter <905617992@qq.com>
This commit is contained in:
千岚之夏
2026-05-29 00:47:35 +08:00
committed by Soulter
parent e960c1495e
commit 022a5dd9f8
2 changed files with 75 additions and 5 deletions

View File

@@ -752,6 +752,7 @@ async def _process_quote_message(
plugin_context: Context,
quoted_message_settings: QuotedMessageParserSettings = DEFAULT_QUOTED_MESSAGE_SETTINGS,
config: MainAgentBuildConfig | None = None,
main_provider_supports_image: bool = False,
) -> None:
quote = None
for comp in event.message_obj.message:
@@ -781,13 +782,21 @@ async def _process_quote_message(
image_seg = comp
break
if image_seg:
if image_seg and main_provider_supports_image:
logger.debug(
"Skipping quote image captioning because the main provider supports image input."
)
elif image_seg and not img_cap_prov_id:
logger.debug(
"No dedicated image caption provider configured. "
"Skipping quote image captioning."
)
elif image_seg:
try:
prov = None
path = None
compress_path = None
if img_cap_prov_id:
prov = plugin_context.get_provider_by_id(img_cap_prov_id)
prov = plugin_context.get_provider_by_id(img_cap_prov_id)
if prov is None:
prov = plugin_context.get_using_provider(event.unified_msg_origin)
@@ -876,6 +885,7 @@ async def _decorate_llm_request(
req: ProviderRequest,
plugin_context: Context,
config: MainAgentBuildConfig,
provider: Provider | None = None,
) -> None:
cfg = config.provider_settings or plugin_context.get_config(
umo=event.unified_msg_origin
@@ -883,11 +893,15 @@ async def _decorate_llm_request(
_apply_prompt_prefix(req, cfg)
main_provider_supports_image = provider is not None and _provider_supports_modality(
provider, "image"
)
if req.conversation:
await _ensure_persona_and_skills(req, cfg, plugin_context, event)
img_cap_prov_id: str = cfg.get("default_image_caption_provider_id") or ""
if img_cap_prov_id and req.image_urls:
if img_cap_prov_id and req.image_urls and not main_provider_supports_image:
await _ensure_img_caption(
event,
req,
@@ -905,6 +919,7 @@ async def _decorate_llm_request(
plugin_context,
quoted_message_settings,
config,
main_provider_supports_image=main_provider_supports_image,
)
tz = config.timezone
@@ -1418,7 +1433,7 @@ async def build_main_agent(
else:
return None
await _decorate_llm_request(event, req, plugin_context, config)
await _decorate_llm_request(event, req, plugin_context, config, provider=provider)
await _apply_kb(event, req, plugin_context, config)

View File

@@ -1085,6 +1085,61 @@ class TestBuildMainAgent:
assert result is not None
@pytest.mark.asyncio
async def test_build_main_agent_skips_caption_when_main_provider_supports_images(
self, mock_event, mock_context, mock_provider
):
"""Test image-capable chat providers receive quoted images directly."""
module = ama
mock_image = Image(file="file:///tmp/quoted.jpg")
mock_reply = Reply(
id="reply-1",
chain=[Plain(text="quoted text"), mock_image],
sender_nickname="",
message_str="quoted text",
)
mock_event.message_obj.message = [Plain(text="Hello"), mock_reply]
mock_context.get_provider_by_id.return_value = None
mock_context.get_using_provider.return_value = mock_provider
mock_context.get_config.return_value = {}
conv_mgr = mock_context.conversation_manager
_setup_conversation_for_build(conv_mgr)
with (
patch("astrbot.core.astr_main_agent.AgentRunner") as mock_runner_cls,
patch("astrbot.core.astr_main_agent.AstrAgentContext"),
patch.object(
Image,
"convert_to_file_path",
AsyncMock(return_value="/tmp/quoted.jpg"),
),
):
mock_runner = MagicMock()
mock_runner.reset = AsyncMock()
mock_runner_cls.return_value = mock_runner
result = await module.build_main_agent(
event=mock_event,
plugin_context=mock_context,
config=module.MainAgentBuildConfig(
tool_call_timeout=60,
provider_settings={
"default_image_caption_provider_id": "caption-provider",
},
),
provider=mock_provider,
)
assert result is not None
assert result.provider_request.image_urls == ["/tmp/quoted.jpg"]
assert not any(
"Image Caption" in part.text or "<image_caption>" in part.text
for part in result.provider_request.extra_user_content_parts
)
mock_provider.text_chat.assert_not_called()
@pytest.mark.asyncio
async def test_build_main_agent_uses_image_fallback_provider(
self, mock_event, mock_context