[Fix] CI/CD #1 - mypy | check_code_and_doc_quality | guardrails_testing (#18195)

This commit is contained in:
Alexsander Hamir 2025-12-18 06:31:01 -08:00 committed by GitHub
parent c94f61b1da
commit 28821427ce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 691 additions and 704 deletions

View File

@ -413,6 +413,12 @@ router_settings:
| AZURE_FEDERATED_TOKEN_FILE | File path to Azure federated token
| AZURE_FILE_SEARCH_COST_PER_GB_PER_DAY | Cost per GB per day for Azure File Search service
| AZURE_SCOPE | For EntraID Auth, Scope for Azure services, defaults to "https://cognitiveservices.azure.com/.default"
| AZURE_SENTINEL_DCR_IMMUTABLE_ID | Immutable ID of the Data Collection Rule for Azure Sentinel logging
| AZURE_SENTINEL_STREAM_NAME | Stream name for Azure Sentinel logging
| AZURE_SENTINEL_CLIENT_SECRET | Client secret for Azure Sentinel authentication
| AZURE_SENTINEL_ENDPOINT | Endpoint for Azure Sentinel logging
| AZURE_SENTINEL_TENANT_ID | Tenant ID for Azure Sentinel authentication
| AZURE_SENTINEL_CLIENT_ID | Client ID for Azure Sentinel authentication
| AZURE_KEY_VAULT_URI | URI for Azure Key Vault
| AZURE_OPERATION_POLLING_TIMEOUT | Timeout in seconds for Azure operation polling
| AZURE_STORAGE_ACCOUNT_KEY | The Azure Storage Account Key to use for Authentication to Azure Blob Storage logging
@ -541,6 +547,8 @@ router_settings:
| DOCS_TITLE | Title of the documentation pages
| DOCS_URL | The path to the Swagger API documentation. **By default this is "/"**
| EMAIL_LOGO_URL | URL for the logo used in emails
| EMAIL_BUDGET_ALERT_TTL | Time-to-live for email budget alerts in seconds
| EMAIL_BUDGET_ALERT_MAX_SPEND_ALERT_PERCENTAGE | Maximum spend percentage for triggering email budget alerts
| EMAIL_SUPPORT_CONTACT | Support contact email address
| EMAIL_SIGNATURE | Custom HTML footer/signature for all emails. Can include HTML tags for formatting and links.
| EMAIL_SUBJECT_INVITATION | Custom subject template for invitation emails.
@ -596,6 +604,8 @@ router_settings:
| GREENSCALE_ENDPOINT | Endpoint URL for Greenscale service
| GRAYSWAN_API_BASE | Base URL for GraySwan API. Default is https://api.grayswan.ai
| GRAYSWAN_API_KEY | API key for GraySwan Cygnal service
| GRAYSWAN_REASONING_MODE | Reasoning mode for GraySwan guardrail
| GRAYSWAN_VIOLATION_THRESHOLD | Violation threshold for GraySwan guardrail
| GOOGLE_APPLICATION_CREDENTIALS | Path to Google Cloud credentials JSON file
| GOOGLE_CLIENT_ID | Client ID for Google OAuth
| GOOGLE_CLIENT_SECRET | Client secret for Google OAuth
@ -825,6 +835,7 @@ router_settings:
| SMTP_TLS | Flag to enable or disable TLS for SMTP connections
| SMTP_USERNAME | Username for SMTP authentication (do not set if SMTP does not require auth)
| SENDGRID_API_KEY | API key for SendGrid email service
| RESEND_API_KEY | API key for Resend email service
| SENDGRID_SENDER_EMAIL | Email address used as the sender in SendGrid email transactions
| SPEND_LOGS_URL | URL for retrieving spend logs
| SPEND_LOG_CLEANUP_BATCH_SIZE | Number of logs deleted per batch during cleanup. Default is 1000

View File

@ -692,12 +692,15 @@ class ModelResponseIterator:
text = content_block_start["content_block"]["text"]
elif content_block_start["content_block"]["type"] == "tool_use" or content_block_start["content_block"]["type"] == "server_tool_use":
self.tool_index += 1
# Some server_tool_use blocks (e.g. web_search) may omit `input` at start;
# default to {} to avoid KeyError and let deltas populate arguments.
tool_input = content_block_start["content_block"].get("input", {})
tool_use = ChatCompletionToolCallChunk(
id=content_block_start["content_block"]["id"],
type="function",
function=ChatCompletionToolCallFunctionChunk(
name=content_block_start["content_block"]["name"],
arguments=str(content_block_start["content_block"]["input"]),
arguments=str(tool_input),
),
index=self.tool_index,
)

View File

@ -21,13 +21,7 @@ from litellm._logging import verbose_proxy_logger
from litellm.llms.base_llm.guardrail_translation.base_translation import BaseTranslation
from litellm.main import stream_chunk_builder
from litellm.types.llms.openai import ChatCompletionToolParam
from litellm.types.utils import (
Choices,
GenericGuardrailAPIInputs,
ModelResponse,
ModelResponseStream,
StreamingChoices,
)
from litellm.types.utils import Choices, GenericGuardrailAPIInputs, ModelResponse, ModelResponseStream, StreamingChoices
if TYPE_CHECKING:
from litellm.integrations.custom_guardrail import CustomGuardrail

View File

@ -31097,7 +31097,8 @@
"input_cost_per_token": 2e-07,
"output_cost_per_token": 2e-07,
"litellm_provider": "fireworks_ai",
"mode": "chat"
"mode": "chat",
"supports_reasoning": true
},
"fireworks_ai/accounts/fireworks/models/qwen3-coder-30b-a3b-instruct": {
"max_tokens": 262144,

View File

@ -1461,6 +1461,7 @@ class UpdateTeamRequest(LiteLLMPydanticObjectBase):
team_member_key_duration: Optional[str] = None
allowed_passthrough_routes: Optional[list] = None
secret_manager_settings: Optional[dict] = None
prompts: Optional[List[str]] = None
model_rpm_limit: Optional[Dict[str, int]] = None
model_tpm_limit: Optional[Dict[str, int]] = None
allowed_vector_store_indexes: Optional[List[AllowedVectorStoreIndexItem]] = None

View File

@ -31,7 +31,6 @@ from litellm.integrations.custom_guardrail import CustomGuardrail
if TYPE_CHECKING:
from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj
from litellm.types.utils import GenericGuardrailAPIInputs
from litellm.proxy._types import UserAPIKeyAuth
from litellm.types.guardrails import (
BlockedWord,
ContentFilterAction,
@ -42,8 +41,6 @@ from litellm.types.guardrails import (
from litellm.types.proxy.guardrails.guardrail_hooks.litellm_content_filter import (
ContentFilterCategoryConfig,
)
from litellm.types.utils import ModelResponseStream
from .patterns import get_compiled_pattern
@ -243,16 +240,16 @@ class ContentFilterGuardrail(CustomGuardrail):
continue
try:
category = self._load_category_file(category_file_path)
self.loaded_categories[category_name] = category
category_config_obj = self._load_category_file(category_file_path)
self.loaded_categories[category_name] = category_config_obj
# Use action from config, or default from category file
category_action = ContentFilterAction(
action if action else category.default_action
action if action else category_config_obj.default_action
)
# Add keywords from this category
for keyword_data in category.keywords:
for keyword_data in category_config_obj.keywords:
keyword = keyword_data["keyword"].lower()
severity = keyword_data["severity"]
@ -266,7 +263,7 @@ class ContentFilterGuardrail(CustomGuardrail):
verbose_proxy_logger.info(
f"Loaded category {category_name}: "
f"{len(category.keywords)} keywords"
f"{len(category_config_obj.keywords)} keywords"
)
except Exception as e:
verbose_proxy_logger.error(
@ -534,10 +531,10 @@ class ContentFilterGuardrail(CustomGuardrail):
# Check category keywords
category_keyword_match = self._check_category_keywords(text, all_exceptions)
if category_keyword_match:
keyword, category, severity, action = category_keyword_match
keyword, category_name, severity, action = category_keyword_match
if action == ContentFilterAction.BLOCK:
error_msg = (
f"Content blocked: {category} category keyword '{keyword}' detected "
f"Content blocked: {category_name} category keyword '{keyword}' detected "
f"(severity: {severity})"
)
verbose_proxy_logger.warning(error_msg)
@ -545,7 +542,7 @@ class ContentFilterGuardrail(CustomGuardrail):
status_code=403,
detail={
"error": error_msg,
"category": category,
"category": category_name,
"keyword": keyword,
"severity": severity,
},
@ -559,7 +556,7 @@ class ContentFilterGuardrail(CustomGuardrail):
flags=re.IGNORECASE,
)
verbose_proxy_logger.info(
f"Masked category keyword '{keyword}' from {category} (severity: {severity})"
f"Masked category keyword '{keyword}' from {category_name} (severity: {severity})"
)
# Check regex patterns - process ALL patterns, not just first match
@ -690,8 +687,10 @@ class ContentFilterGuardrail(CustomGuardrail):
responses = await asyncio.gather(*tasks)
descriptions = []
for response in responses:
if response.choices[0].message.content:
image_description = response.choices[0].message.content
choice = response.choices[0]
message = getattr(choice, "message", None)
if message and getattr(message, "content", None):
image_description = message.content
verbose_proxy_logger.debug(
f"Image description: {image_description}"
)

View File

@ -37,39 +37,72 @@ from litellm.secret_managers.main import get_secret
def _resolve_os_environ_variables(params: dict) -> dict:
"""
Resolve os.environ/ environment variables in litellm_params.
This function recursively processes dictionary values that start with "os.environ/"
by replacing them with the actual environment variable values.
Args:
params: Dictionary containing litellm_params that may have os.environ/ values
Returns:
Dictionary with os.environ/ values resolved to actual environment variable values
Resolve ``os.environ/`` environment variables in ``litellm_params``.
This walks the input dict/list structure iteratively (no Python recursion) to
avoid unbounded recursion / stack overflows on deeply nested inputs.
"""
if not isinstance(params, dict):
return params
resolved_params = {}
for key, value in params.items():
if isinstance(value, str) and value.startswith("os.environ/"):
# Resolve the environment variable
resolved_value = get_secret(value)
resolved_params[key] = resolved_value
elif isinstance(value, dict):
# Recursively resolve nested dictionaries
resolved_params[key] = _resolve_os_environ_variables(value)
elif isinstance(value, list):
# Handle lists that might contain dictionaries with os.environ/ values
resolved_params[key] = [
_resolve_os_environ_variables(item) if isinstance(item, dict) else item
for item in value
]
else:
resolved_params[key] = value
return resolved_params
# Use an explicit stack to avoid recursion and handle nested dicts/lists.
# We also keep a `seen` set to guard against accidental cycles.
resolved_root: dict = {}
stack: list[tuple[object, object]] = [(params, resolved_root)]
seen: set[int] = {id(params)}
while stack:
src, dst = stack.pop()
if isinstance(src, dict) and isinstance(dst, dict):
for key, value in src.items():
# Direct string replacement for os.environ/ references
if isinstance(value, str) and value.startswith("os.environ/"):
dst[key] = get_secret(value)
elif isinstance(value, dict):
if id(value) in seen:
# Cycle detected keep a shallow copy reference to prevent infinite loops
dst[key] = {}
continue
seen.add(id(value))
new_dict: dict = {}
dst[key] = new_dict
stack.append((value, new_dict))
elif isinstance(value, list):
if id(value) in seen:
dst[key] = []
continue
seen.add(id(value))
new_list: list = []
dst[key] = new_list
stack.append((value, new_list))
else:
dst[key] = value
elif isinstance(src, list) and isinstance(dst, list):
for item in src:
if isinstance(item, str) and item.startswith("os.environ/"):
dst.append(get_secret(item))
elif isinstance(item, dict):
if id(item) in seen:
dst.append({})
continue
seen.add(id(item))
new_dict = {}
dst.append(new_dict)
stack.append((item, new_dict))
elif isinstance(item, list):
if id(item) in seen:
dst.append([])
continue
seen.add(id(item))
new_list = []
dst.append(new_list)
stack.append((item, new_list))
else:
dst.append(item)
return resolved_root
router = APIRouter()

View File

@ -678,15 +678,14 @@ async def new_team( # noqa: PLR0915
- model_aliases: Optional[dict] - Model aliases for the team. [Docs](https://docs.litellm.ai/docs/proxy/team_based_routing#create-team-with-model-alias)
- guardrails: Optional[List[str]] - Guardrails for the team. [Docs](https://docs.litellm.ai/docs/proxy/guardrails)
- disable_global_guardrails: Optional[bool] - Whether to disable global guardrails for the key.
- prompts: Optional[List[str]] - List of prompts that the team is allowed to use.
- object_permission: Optional[LiteLLM_ObjectPermissionBase] - team-specific object permission. Example - {"vector_stores": ["vector_store_1", "vector_store_2"], "agents": ["agent_1", "agent_2"], "agent_access_groups": ["dev_group"]}. IF null or {} then no object permission.
- team_member_budget: Optional[float] - The maximum budget allocated to an individual team member.
- team_member_rpm_limit: Optional[int] - The RPM (Requests Per Minute) limit for individual team members.
- team_member_tpm_limit: Optional[int] - The TPM (Tokens Per Minute) limit for individual team members.
- team_member_key_duration: Optional[str] - The duration for a team member's key. e.g. "1d", "1w", "1mo"
- prompts: Optional[List[str]] - List of allowed prompts for the team. If specified, the team will only be able to use these specific prompts.
- allowed_passthrough_routes: Optional[List[str]] - List of allowed pass through routes for the team.
- allowed_vector_store_indexes: Optional[List[dict]] - List of allowed vector store indexes for the key. Example - [{"index_name": "my-index", "index_permissions": ["write", "read"]}]. If specified, the key will only be able to use these specific vector store indexes. Create index, using `/v1/indexes` endpoint.
- secret_manager_settings: Optional[dict] - Secret manager settings for the team. [Docs](https://docs.litellm.ai/docs/secret_managers/overview)
@ -1201,7 +1200,6 @@ async def update_team(
- model_aliases: Optional[dict] - Model aliases for the team. [Docs](https://docs.litellm.ai/docs/proxy/team_based_routing#create-team-with-model-alias)
- guardrails: Optional[List[str]] - Guardrails for the team. [Docs](https://docs.litellm.ai/docs/proxy/guardrails)
- disable_global_guardrails: Optional[bool] - Whether to disable global guardrails for the key.
- prompts: Optional[List[str]] - List of prompts that the team is allowed to use.
- object_permission: Optional[LiteLLM_ObjectPermissionBase] - team-specific object permission. Example - {"vector_stores": ["vector_store_1", "vector_store_2"], "agents": ["agent_1", "agent_2"], "agent_access_groups": ["dev_group"]}. IF null or {} then no object permission.
- team_member_budget: Optional[float] - The maximum budget allocated to an individual team member.
- team_member_rpm_limit: Optional[int] - The RPM (Requests Per Minute) limit for individual team members.
@ -1212,6 +1210,7 @@ async def update_team(
- model_tpm_limit: Optional[Dict[str, int]] - The TPM (Tokens Per Minute) limit per model for this team. Example: {"gpt-4": 10000, "gpt-3.5-turbo": 20000}
Example - update team TPM Limit
- allowed_vector_store_indexes: Optional[List[dict]] - List of allowed vector store indexes for the key. Example - [{"index_name": "my-index", "index_permissions": ["write", "read"]}]. If specified, the key will only be able to use these specific vector store indexes. Create index, using `/v1/indexes` endpoint.
- secret_manager_settings: Optional[dict] - Secret manager settings for the team. [Docs](https://docs.litellm.ai/docs/secret_managers/overview)
```

View File

@ -31103,7 +31103,8 @@
"input_cost_per_token": 2e-07,
"output_cost_per_token": 2e-07,
"litellm_provider": "fireworks_ai",
"mode": "chat"
"mode": "chat",
"supports_reasoning": true
},
"fireworks_ai/accounts/fireworks/models/qwen3-coder-30b-a3b-instruct": {
"max_tokens": 262144,

View File

@ -14,6 +14,7 @@ SEARCH_PROVIDERS = [
"exa_ai",
"firecrawl",
"searxng",
"linkup",
]
ALLOWED_FILES_IN_LLMS_FOLDER = [

View File

@ -10,7 +10,7 @@ sys.path.insert(0, os.path.abspath("../.."))
from litellm.proxy.guardrails.guardrail_hooks.dynamoai import DynamoAIGuardrails
from litellm.proxy._types import UserAPIKeyAuth
from litellm.caching.caching import DualCache
from unittest.mock import AsyncMock, MagicMock
from unittest.mock import AsyncMock, MagicMock, patch
@pytest.mark.asyncio
@ -48,26 +48,25 @@ async def test_dynamoai_blocks_content_with_block_action():
]
}
mock_response.raise_for_status = MagicMock()
guardrail.async_handler.post = AsyncMock(return_value=mock_response)
with patch.object(guardrail.async_handler, "post", AsyncMock(return_value=mock_response)):
request_data = {
"model": "gpt-4",
"messages": [
{"role": "user", "content": "This is harmful content"}
],
}
request_data = {
"model": "gpt-4",
"messages": [
{"role": "user", "content": "This is harmful content"}
],
}
# Mock should_run_guardrail to return True
guardrail.should_run_guardrail = MagicMock(return_value=True)
# Mock should_run_guardrail to return True
guardrail.should_run_guardrail = MagicMock(return_value=True)
# Test that the guardrail raises ValueError for blocked content
with pytest.raises(ValueError) as exc_info:
await guardrail.async_pre_call_hook(
data=request_data,
user_api_key_dict=UserAPIKeyAuth(),
call_type="completion",
cache=MagicMock(spec=DualCache),
)
# Test that the guardrail raises ValueError for blocked content
with pytest.raises(ValueError) as exc_info:
await guardrail.async_pre_call_hook(
data=request_data,
user_api_key_dict=UserAPIKeyAuth(),
call_type="completion",
cache=MagicMock(spec=DualCache),
)
# Verify the error message contains policy information
error_message = str(exc_info.value)
@ -98,25 +97,24 @@ async def test_dynamoai_allows_content_with_none_action():
"appliedPolicies": []
}
mock_response.raise_for_status = MagicMock()
guardrail.async_handler.post = AsyncMock(return_value=mock_response)
with patch.object(guardrail.async_handler, "post", AsyncMock(return_value=mock_response)):
request_data = {
"model": "gpt-4",
"messages": [
{"role": "user", "content": "Hello, how are you?"}
],
}
request_data = {
"model": "gpt-4",
"messages": [
{"role": "user", "content": "Hello, how are you?"}
],
}
# Mock should_run_guardrail to return True
guardrail.should_run_guardrail = MagicMock(return_value=True)
# Mock should_run_guardrail to return True
guardrail.should_run_guardrail = MagicMock(return_value=True)
# Test that the guardrail allows the content (no exception raised)
result = await guardrail.async_pre_call_hook(
data=request_data,
user_api_key_dict=UserAPIKeyAuth(),
call_type="completion",
cache=MagicMock(spec=DualCache),
)
# Test that the guardrail allows the content (no exception raised)
result = await guardrail.async_pre_call_hook(
data=request_data,
user_api_key_dict=UserAPIKeyAuth(),
call_type="completion",
cache=MagicMock(spec=DualCache),
)
# Should return the request data unchanged
assert result == request_data

View File

@ -282,8 +282,6 @@ async def test_bedrock_guardrail_status_blocked():
aws_region_name="us-east-1",
)
# Mock Bedrock API response indicating content was blocked
# action="GUARDRAIL_INTERVENED" means the guardrail blocked the request
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = {
@ -295,33 +293,32 @@ async def test_bedrock_guardrail_status_blocked():
}
}]
}
bedrock_guard.async_handler.post = AsyncMock(return_value=mock_response)
request_data = {
"model": "gpt-4o",
"messages": [{"role": "user", "content": "harmful content"}],
"mock_response": "Hello",
"metadata": {}
}
# Mock should_run_guardrail to ensure guardrail logic executes
with patch.object(bedrock_guard, 'should_run_guardrail', return_value=True):
# Call guardrail pre_call hook - this will raise an exception when content is blocked
try:
await bedrock_guard.async_pre_call_hook(
user_api_key_dict=UserAPIKeyAuth(),
cache=None,
data=request_data,
call_type="completion"
)
except Exception:
# Expected exception when guardrail blocks content
pass
# Call litellm.acompletion to trigger logging callbacks
# This populates the standard_logging_payload in our custom logger
response = await litellm.acompletion(**request_data)
await asyncio.sleep(1)
with patch.object(bedrock_guard.async_handler, "post", AsyncMock(return_value=mock_response)):
request_data = {
"model": "gpt-4o",
"messages": [{"role": "user", "content": "harmful content"}],
"mock_response": "Hello",
"metadata": {}
}
# Mock should_run_guardrail to ensure guardrail logic executes
with patch.object(bedrock_guard, 'should_run_guardrail', return_value=True):
# Call guardrail pre_call hook - this will raise an exception when content is blocked
try:
await bedrock_guard.async_pre_call_hook(
user_api_key_dict=UserAPIKeyAuth(),
cache=None,
data=request_data,
call_type="completion"
)
except Exception:
# Expected exception when guardrail blocks content
pass
# Call litellm.acompletion to trigger logging callbacks
# This populates the standard_logging_payload in our custom logger
response = await litellm.acompletion(**request_data)
await asyncio.sleep(1)
# Verify the standard logging payload was captured
assert test_custom_logger.standard_logging_payload is not None
@ -383,27 +380,26 @@ async def test_bedrock_guardrail_status_success():
"outputs": [{"text": "Safe content"}],
"assessments": []
}
bedrock_guard.async_handler.post = AsyncMock(return_value=mock_response)
request_data = {
"model": "gpt-4o",
"messages": [{"role": "user", "content": "safe content"}],
"mock_response": "Hello",
"metadata": {}
}
# Mock should_run_guardrail to return True
with patch.object(bedrock_guard, 'should_run_guardrail', return_value=True):
await bedrock_guard.async_pre_call_hook(
user_api_key_dict=UserAPIKeyAuth(),
cache=None,
data=request_data,
call_type="completion"
)
# Call litellm.acompletion to trigger logging
response = await litellm.acompletion(**request_data)
await asyncio.sleep(1)
with patch.object(bedrock_guard.async_handler, "post", AsyncMock(return_value=mock_response)):
request_data = {
"model": "gpt-4o",
"messages": [{"role": "user", "content": "safe content"}],
"mock_response": "Hello",
"metadata": {}
}
# Mock should_run_guardrail to return True
with patch.object(bedrock_guard, 'should_run_guardrail', return_value=True):
await bedrock_guard.async_pre_call_hook(
user_api_key_dict=UserAPIKeyAuth(),
cache=None,
data=request_data,
call_type="completion"
)
# Call litellm.acompletion to trigger logging
response = await litellm.acompletion(**request_data)
await asyncio.sleep(1)
# Check standard logging payload status fields
assert test_custom_logger.standard_logging_payload is not None
@ -456,34 +452,31 @@ async def test_bedrock_guardrail_status_failure():
)
# Mock network failure (endpoint down)
bedrock_guard.async_handler.post = AsyncMock(
side_effect=httpx.ConnectError("Connection failed")
)
request_data = {
"model": "gpt-4o",
"messages": [{"role": "user", "content": "test content"}],
"mock_response": "Hello",
"metadata": {}
}
# Mock should_run_guardrail to return True
with patch.object(bedrock_guard, 'should_run_guardrail', return_value=True):
# Call guardrail (will raise exception on network failure)
try:
await bedrock_guard.async_pre_call_hook(
user_api_key_dict=UserAPIKeyAuth(),
cache=None,
data=request_data,
call_type="completion"
)
except Exception:
# Expected exception when endpoint is down
pass
# Call litellm.acompletion to trigger logging
response = await litellm.acompletion(**request_data)
await asyncio.sleep(1)
with patch.object(bedrock_guard.async_handler, "post", AsyncMock(side_effect=httpx.ConnectError("Connection failed"))):
request_data = {
"model": "gpt-4o",
"messages": [{"role": "user", "content": "test content"}],
"mock_response": "Hello",
"metadata": {}
}
# Mock should_run_guardrail to return True
with patch.object(bedrock_guard, 'should_run_guardrail', return_value=True):
# Call guardrail (will raise exception on network failure)
try:
await bedrock_guard.async_pre_call_hook(
user_api_key_dict=UserAPIKeyAuth(),
cache=None,
data=request_data,
call_type="completion"
)
except Exception:
# Expected exception when endpoint is down
pass
# Call litellm.acompletion to trigger logging
response = await litellm.acompletion(**request_data)
await asyncio.sleep(1)
# Check standard logging payload status fields
assert test_custom_logger.standard_logging_payload is not None
@ -544,31 +537,30 @@ async def test_noma_guardrail_status_blocked():
}
}
mock_response.raise_for_status = MagicMock()
noma_guard.async_handler.post = AsyncMock(return_value=mock_response)
request_data = {
"model": "gpt-4o",
"messages": [{"role": "user", "content": "harmful content"}],
"mock_response": "Hello",
"metadata": {}
}
# Mock should_run_guardrail to return True
with patch.object(noma_guard, 'should_run_guardrail', return_value=True):
# Call guardrail (will raise exception on block)
try:
await noma_guard.async_pre_call_hook(
user_api_key_dict=UserAPIKeyAuth(),
cache=None,
data=request_data,
call_type="completion"
)
except Exception:
pass
# Call litellm.acompletion to trigger logging
response = await litellm.acompletion(**request_data)
await asyncio.sleep(1)
with patch.object(noma_guard.async_handler, "post", AsyncMock(return_value=mock_response)):
request_data = {
"model": "gpt-4o",
"messages": [{"role": "user", "content": "harmful content"}],
"mock_response": "Hello",
"metadata": {}
}
# Mock should_run_guardrail to return True
with patch.object(noma_guard, 'should_run_guardrail', return_value=True):
# Call guardrail (will raise exception on block)
try:
await noma_guard.async_pre_call_hook(
user_api_key_dict=UserAPIKeyAuth(),
cache=None,
data=request_data,
call_type="completion"
)
except Exception:
pass
# Call litellm.acompletion to trigger logging
response = await litellm.acompletion(**request_data)
await asyncio.sleep(1)
# Check standard logging payload status fields
assert test_custom_logger.standard_logging_payload is not None
@ -625,27 +617,26 @@ async def test_noma_guardrail_status_success():
"originalResponse": {"prompt": {}}
}
mock_response.raise_for_status = MagicMock()
noma_guard.async_handler.post = AsyncMock(return_value=mock_response)
request_data = {
"model": "gpt-4o",
"messages": [{"role": "user", "content": "safe content"}],
"mock_response": "Hello",
"metadata": {}
}
# Mock should_run_guardrail to return True
with patch.object(noma_guard, 'should_run_guardrail', return_value=True):
await noma_guard.async_pre_call_hook(
user_api_key_dict=UserAPIKeyAuth(),
cache=None,
data=request_data,
call_type="completion"
)
# Call litellm.acompletion to trigger logging
response = await litellm.acompletion(**request_data)
await asyncio.sleep(1)
with patch.object(noma_guard.async_handler, "post", AsyncMock(return_value=mock_response)):
request_data = {
"model": "gpt-4o",
"messages": [{"role": "user", "content": "safe content"}],
"mock_response": "Hello",
"metadata": {}
}
# Mock should_run_guardrail to return True
with patch.object(noma_guard, 'should_run_guardrail', return_value=True):
await noma_guard.async_pre_call_hook(
user_api_key_dict=UserAPIKeyAuth(),
cache=None,
data=request_data,
call_type="completion"
)
# Call litellm.acompletion to trigger logging
response = await litellm.acompletion(**request_data)
await asyncio.sleep(1)
# Check standard logging payload status fields
assert test_custom_logger.standard_logging_payload is not None

View File

@ -495,13 +495,13 @@ async def test_bedrock_guardrail_make_api_request_passes_api_key():
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {"action": "NONE", "outputs": []}
guardrail_hook.async_handler.post = AsyncMock(return_value=mock_response)
test_request_data = {
"api_key": "test-api-key-789"
}
with patch.object(guardrail_hook, "_load_credentials") as mock_load_creds, \
with patch.object(guardrail_hook.async_handler, "post", AsyncMock(return_value=mock_response)), \
patch.object(guardrail_hook, "_load_credentials") as mock_load_creds, \
patch.object(guardrail_hook, "convert_to_bedrock_format") as mock_convert, \
patch.object(guardrail_hook, "get_guardrail_dynamic_request_body_params") as mock_get_params, \
patch.object(guardrail_hook, "add_standard_logging_guardrail_information_to_request_data"), \