From 9e333e58b27e6d69a58f1fc52b8bb999ef6f5aee Mon Sep 17 00:00:00 2001 From: splendor023 Date: Thu, 20 Aug 2026 15:37:15 +0800 Subject: [PATCH] fix(guardrails): support extracting public image URLs from tool messages. --- .../guardrails/guardrail_hooks/aliyun/base.py | 6 ++--- .../aliyun/test_aliyun_ai_guardrail.py | 26 +++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/litellm/proxy/guardrails/guardrail_hooks/aliyun/base.py b/litellm/proxy/guardrails/guardrail_hooks/aliyun/base.py index 1d1f6073511..b50884b3de2 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/aliyun/base.py +++ b/litellm/proxy/guardrails/guardrail_hooks/aliyun/base.py @@ -71,8 +71,8 @@ class AliyunGuardrailBase: return user_prompt or None def _iter_public_image_urls(self, messages: Sequence[AllMessageValues]) -> Iterator[str]: - """Yield the publicly reachable image URLs of every user message, in order.""" - for content in (message.get("content") for message in self._iter_user_messages(messages)): + """Yield publicly reachable image URLs from user and tool messages.""" + for content in (message.get("content") for message in self._iter_audited_text_messages(messages)): if not isinstance(content, list): continue for url in (self._extract_image_url(part) for part in content): @@ -83,7 +83,7 @@ class AliyunGuardrailBase: def get_image_urls(self, messages: Sequence[AllMessageValues]) -> tuple[str, ...]: """ - Extract image URLs from every user message in the request. + Extract image URLs from every user and tool message in the request. Only publicly accessible http(s) URLs are collected (in order, de-duplicated). Uses the same message range as ``get_user_prompt``. Example: diff --git a/tests/test_litellm/proxy/guardrails/guardrail_hooks/aliyun/test_aliyun_ai_guardrail.py b/tests/test_litellm/proxy/guardrails/guardrail_hooks/aliyun/test_aliyun_ai_guardrail.py index 13367e75296..cc27f4cbf08 100644 --- a/tests/test_litellm/proxy/guardrails/guardrail_hooks/aliyun/test_aliyun_ai_guardrail.py +++ b/tests/test_litellm/proxy/guardrails/guardrail_hooks/aliyun/test_aliyun_ai_guardrail.py @@ -836,6 +836,32 @@ class TestPreCallHook: ) assert "工具输出里的违规内容" in scanned + @pytest.mark.asyncio + async def test_scans_responses_api_function_call_output_image(self): + g = _make_guardrail(level="medium") + clean = _make_aliyun_api_response(suggestion="pass", detail=[]) + data = { + "input": [ + { + "type": "function_call_output", + "call_id": "call_1", + "output": [ + {"type": "input_image", "image_url": IMG_A, "detail": "auto"}, + ], + } + ] + } + with patch.object(g.async_handler, "post", new_callable=AsyncMock, return_value=clean) as mock_post: + await g.async_pre_call_hook( + user_api_key_dict=UserAPIKeyAuth(api_key="test"), + cache=MagicMock(), + data=data, + call_type="responses", + ) + + sent = [json.loads(call.kwargs["data"]["ServiceParameters"]) for call in mock_post.call_args_list] + assert any(IMG_A in (service_parameters.get("imageUrls") or []) for service_parameters in sent) + @pytest.mark.asyncio async def test_scans_responses_api_function_call_arguments(self): g = _make_guardrail(level="medium")