mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-18 10:00:40 +08:00
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:
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user