fix(guardrails): lower tool permission guardrail log noise

The tool permission guardrail emitted expected, non-actionable events at WARNING; the no-tools skip path and every denied-by-rule message now log at DEBUG and INFO respectively so normal operation stops flooding logs
This commit is contained in:
Devin AI 2026-07-10 14:11:07 +00:00
parent bf02a4a47f
commit 7596f48779
2 changed files with 71 additions and 4 deletions

View file

@ -665,7 +665,7 @@ class ToolPermissionGuardrail(CustomGuardrail):
new_tools = self._collect_request_tools(data)
if not new_tools:
verbose_proxy_logger.warning(
verbose_proxy_logger.debug(
"Tool Permission Guardrail: not running guardrail. No tools or functions in data"
)
return data
@ -676,7 +676,7 @@ class ToolPermissionGuardrail(CustomGuardrail):
is_allowed, _, message = self._check_tool_permission(tool_name, tool_type)
if not is_allowed and message is not None:
verbose_proxy_logger.warning(f"Tool Permission Guardrail: {message}")
verbose_proxy_logger.info(f"Tool Permission Guardrail: {message}")
if self.on_disallowed_action == "block":
raise HTTPException(
status_code=400,
@ -734,7 +734,7 @@ class ToolPermissionGuardrail(CustomGuardrail):
is_allowed, rule_id, message = self._get_permission_for_tool_call(tool_call)
if not is_allowed and message is not None:
verbose_proxy_logger.warning(f"Tool Permission Guardrail: {message}")
verbose_proxy_logger.info(f"Tool Permission Guardrail: {message}")
if self.on_disallowed_action == "block":
raise GuardrailRaisedException(
@ -813,7 +813,7 @@ class ToolPermissionGuardrail(CustomGuardrail):
is_allowed, rule_id, message = self._get_permission_for_tool_call(tool_call)
if not is_allowed and message is not None:
verbose_proxy_logger.warning(f"Tool Permission Guardrail: {message}")
verbose_proxy_logger.info(f"Tool Permission Guardrail: {message}")
if self.on_disallowed_action == "block":
raise GuardrailRaisedException(

View file

@ -3,6 +3,7 @@ Unit tests for Tool Permission Guardrail (OpenAI tool_calls semantics)
"""
import json
import logging
import os
import re
import sys
@ -612,6 +613,72 @@ class TestToolPermissionGuardrail:
assert excinfo.value.status_code == 400
assert excinfo.value.detail.get("detection_message") == "blocked Read by policy"
@pytest.mark.asyncio
async def test_async_pre_call_hook_no_tools_logs_at_debug(self, caplog):
user_api_key_dict = UserAPIKeyAuth()
cache = DualCache(default_in_memory_ttl=1)
with patch.object(self.guardrail, "should_run_guardrail", return_value=True):
with caplog.at_level(logging.DEBUG, logger="LiteLLM Proxy"):
await self.guardrail.async_pre_call_hook(
user_api_key_dict=user_api_key_dict,
cache=cache,
data={},
call_type="completion",
)
matching = [r for r in caplog.records if "not running guardrail" in r.message]
assert matching, "expected the no-tools message to be logged"
assert all(r.levelno == logging.DEBUG for r in matching)
@pytest.mark.asyncio
async def test_async_pre_call_hook_denied_tool_logs_at_info(self, caplog):
data = {"tools": [{"type": "function", "function": {"name": "Read"}}]}
user_api_key_dict = UserAPIKeyAuth()
cache = DualCache(default_in_memory_ttl=1)
with patch.object(self.guardrail, "should_run_guardrail", return_value=True):
with caplog.at_level(logging.DEBUG, logger="LiteLLM Proxy"):
with pytest.raises(HTTPException):
await self.guardrail.async_pre_call_hook(
user_api_key_dict=user_api_key_dict,
cache=cache,
data=data,
call_type="completion",
)
matching = [
r
for r in caplog.records
if r.message.startswith("Tool Permission Guardrail:")
and "denied by rule" in r.message
]
assert matching, "expected the denied-by-rule message to be logged"
assert all(r.levelno == logging.INFO for r in matching)
@pytest.mark.asyncio
async def test_async_post_call_success_hook_denied_tool_logs_at_info(self, caplog):
tool_call = {"function": {"name": "Read", "arguments": "{}"}, "type": "function"}
response = ModelResponse(choices=[Choices(message={"tool_calls": [tool_call]})])
user_api_key_dict = UserAPIKeyAuth()
data = {"guardrails": ["test-tool-permission"]}
with patch.object(self.guardrail, "should_run_guardrail", return_value=True):
with caplog.at_level(logging.DEBUG, logger="LiteLLM Proxy"):
with pytest.raises(GuardrailRaisedException):
await self.guardrail.async_post_call_success_hook(
data=data, user_api_key_dict=user_api_key_dict, response=response
)
matching = [
r
for r in caplog.records
if r.message.startswith("Tool Permission Guardrail:")
and "denied by rule" in r.message
]
assert matching, "expected the denied-by-rule message to be logged"
assert all(r.levelno == logging.INFO for r in matching)
@pytest.mark.asyncio
async def test_async_pre_call_hook_rewrite_mode(self):
guardrail = ToolPermissionGuardrail(