diff --git a/basedpyright-code-budget.json b/basedpyright-code-budget.json index b3df1829650..04c447290da 100644 --- a/basedpyright-code-budget.json +++ b/basedpyright-code-budget.json @@ -1,9 +1,9 @@ { "reportAny": { - "limit": 31306 + "limit": 30112 }, "reportArgumentType": { - "limit": 2641 + "limit": 2633 }, "reportAssignmentType": { "limit": 329 @@ -24,7 +24,7 @@ "limit": 42 }, "reportExplicitAny": { - "limit": 10089 + "limit": 9839 }, "reportFunctionMemberAccess": { "limit": 11 @@ -54,10 +54,10 @@ "limit": 0 }, "reportMissingParameterType": { - "limit": 5858 + "limit": 5836 }, "reportMissingTypeArgument": { - "limit": 15833 + "limit": 15777 }, "reportMissingTypeStubs": { "limit": 41 @@ -99,19 +99,19 @@ "limit": 0 }, "reportUnknownArgumentType": { - "limit": 45345 + "limit": 45301 }, "reportUnknownLambdaType": { "limit": 113 }, "reportUnknownMemberType": { - "limit": 40390 + "limit": 40216 }, "reportUnknownParameterType": { - "limit": 20299 + "limit": 20221 }, "reportUnknownVariableType": { - "limit": 31974 + "limit": 31834 }, "reportUnnecessaryCast": { "limit": 177 @@ -123,7 +123,7 @@ "limit": 7 }, "reportUnnecessaryIsInstance": { - "limit": 1204 + "limit": 1202 }, "reportUntypedBaseClass": { "limit": 165 diff --git a/litellm/proxy/management_endpoints/key_management_endpoints.py b/litellm/proxy/management_endpoints/key_management_endpoints.py index 9aaedfc8d42..a928df7da14 100644 --- a/litellm/proxy/management_endpoints/key_management_endpoints.py +++ b/litellm/proxy/management_endpoints/key_management_endpoints.py @@ -2311,7 +2311,7 @@ async def _validate_mcp_servers_for_key_update( return normalized_object_permission -async def _validate_update_key_data( +async def _validate_update_key_data( # noqa: C901 # prisma_client None-guard needed now that it's typed Optional data: UpdateKeyRequest, existing_key_row: LiteLLM_VerificationToken, user_api_key_dict: UserAPIKeyAuth, diff --git a/litellm/proxy/management_endpoints/team_endpoints.py b/litellm/proxy/management_endpoints/team_endpoints.py index 108e4266902..d56352df92e 100644 --- a/litellm/proxy/management_endpoints/team_endpoints.py +++ b/litellm/proxy/management_endpoints/team_endpoints.py @@ -498,7 +498,9 @@ def _get_default_team_param(field: str) -> _DefaultTeamParamValue | None: if raw_value is None: return None if isinstance(raw_value, list): - return tuple(v.value if hasattr(v, "value") else v for v in raw_value) + # mutable-ok: callers compare this against list literals (e.g. team_member_permissions + # equality checks), so it must stay a real list, not a tuple, to match prior behavior. + return [v.value if hasattr(v, "value") else v for v in raw_value] return _DEFAULT_TEAM_PARAM_ADAPTER.validate_python(raw_value) diff --git a/litellm/responses/streaming_iterator.py b/litellm/responses/streaming_iterator.py index c9dfe39e939..dedb4626ac0 100644 --- a/litellm/responses/streaming_iterator.py +++ b/litellm/responses/streaming_iterator.py @@ -1687,7 +1687,10 @@ class ResponsesWebSocketStreaming: for output_item in response_obj.get("output") or []: if not isinstance(output_item, dict): continue - for content_block in output_item.get("content") or []: + content = output_item.get("content") or [] + if not isinstance(content, list): + continue + for content_block in content: if not isinstance(content_block, dict): continue text = content_block.get("text") @@ -1750,21 +1753,26 @@ class ResponsesWebSocketStreaming: if masked_args != arguments: output_item["arguments"] = masked_args modified = True - for summary_block in output_item.get("summary") or []: - if not isinstance(summary_block, dict): - continue - summary_text = summary_block.get("text") - if isinstance(summary_text, str): - masked_summary = await cb.check_pii( - text=summary_text, - output_parse_pii=False, - presidio_config=presidio_config, - request_data=self.request_data, - ) - if masked_summary != summary_text: - summary_block["text"] = masked_summary - modified = True - for content_block in output_item.get("content") or []: + summary = output_item.get("summary") or [] + if isinstance(summary, list): + for summary_block in summary: + if not isinstance(summary_block, dict): + continue + summary_text = summary_block.get("text") + if isinstance(summary_text, str): + masked_summary = await cb.check_pii( + text=summary_text, + output_parse_pii=False, + presidio_config=presidio_config, + request_data=self.request_data, + ) + if masked_summary != summary_text: + summary_block["text"] = masked_summary + modified = True + content = output_item.get("content") or [] + if not isinstance(content, list): + continue + for content_block in content: if not isinstance(content_block, dict): continue text = content_block.get("text") diff --git a/ruff-strict-budget.json b/ruff-strict-budget.json index 8867d5e6f37..c4785fe7444 100644 --- a/ruff-strict-budget.json +++ b/ruff-strict-budget.json @@ -1,6 +1,6 @@ { "ANN001": { - "limit": 3107 + "limit": 3085 }, "ANN002": { "limit": 69 @@ -9,13 +9,13 @@ "limit": 831 }, "ANN201": { - "limit": 2135 + "limit": 2129 }, "ANN202": { - "limit": 943 + "limit": 941 }, "ANN204": { - "limit": 723 + "limit": 721 }, "ANN205": { "limit": 127 @@ -24,7 +24,7 @@ "limit": 130 }, "ANN401": { - "limit": 1931 + "limit": 1773 }, "ASYNC230": { "limit": 14 @@ -135,7 +135,7 @@ "limit": 30 }, "PERF401": { - "limit": 140 + "limit": 136 }, "PERF402": { "limit": 9 @@ -222,7 +222,7 @@ "limit": 38 }, "RET504": { - "limit": 712 + "limit": 704 }, "RUF010": { "limit": 874 @@ -273,7 +273,7 @@ "limit": 6 }, "SIM114": { - "limit": 110 + "limit": 108 }, "SIM115": { "limit": 5 @@ -324,7 +324,7 @@ "limit": 879 }, "UP006": { - "limit": 12075 + "limit": 11949 }, "UP007": { "limit": 2526 @@ -360,9 +360,9 @@ "limit": 4 }, "UP037": { - "limit": 104 + "limit": 102 }, "UP045": { - "limit": 17797 + "limit": 17781 } } diff --git a/tests/test_litellm/responses/test_responses_websocket_all_providers.py b/tests/test_litellm/responses/test_responses_websocket_all_providers.py index 4509abc7749..6153f9adbcc 100644 --- a/tests/test_litellm/responses/test_responses_websocket_all_providers.py +++ b/tests/test_litellm/responses/test_responses_websocket_all_providers.py @@ -1084,6 +1084,44 @@ class TestNativeWebSocketGuardrails: assert handler._unmask_response_event(event) == event assert await handler._mask_response_completed(event) == event + @pytest.mark.asyncio + async def test_completed_event_with_non_list_content_and_summary_passes_through( + self, + ): + """Regression test: an output item whose "content"/"summary" is a truthy + non-iterable value (malformed upstream payload) must be skipped, not raise.""" + from unittest.mock import MagicMock + + from litellm.responses.streaming_iterator import ResponsesWebSocketStreaming + + class Guardrail: + def get_presidio_settings_from_request_data(self, request_data): + return None + + def _unmask_pii_text(self, text, pii_tokens): + return text + + event = json.dumps( + { + "type": "response.completed", + "response": { + "output": [{"type": "message", "content": 42, "summary": True}] + }, + } + ) + guardrail = Guardrail() + handler = ResponsesWebSocketStreaming( + websocket=MagicMock(), + backend_ws=MagicMock(), + logging_obj=MagicMock(), + request_data={"metadata": {"pii_tokens": {"": "secret"}}}, + guardrail_callbacks=[guardrail], + output_guardrail_callbacks=[guardrail], + ) + + assert handler._unmask_response_event(event) == event + assert await handler._mask_response_completed(event) == event + @pytest.mark.asyncio async def test_output_masking_suppresses_delta_without_calling_presidio(self): import json diff --git a/type-discipline-budget.json b/type-discipline-budget.json index cedee786d0d..d103dbc69f4 100644 --- a/type-discipline-budget.json +++ b/type-discipline-budget.json @@ -1,9 +1,9 @@ { "LIT001": { - "limit": 23151 + "limit": 22947 }, "LIT002": { - "limit": 27409 + "limit": 27375 }, "LIT003": { "limit": 292 @@ -24,6 +24,6 @@ "limit": 1004 }, "LIT009": { - "limit": 2466 + "limit": 2450 } }