* fix(middleware): replace BaseHTTPMiddleware with pure ASGI middleware
BaseHTTPMiddleware wraps streaming responses with receive_or_disconnect
per chunk, blocking the event loop and causing severe throughput
degradation under concurrent streaming load (53% of CPU in profiling).
Converts PrometheusAuthMiddleware to a pure ASGI middleware using the
__call__(scope, receive, send) protocol.
* fix(streaming): remove expensive debug logging and optimize usage stripping
- Remove print_verbose calls that format chunk/response Pydantic objects,
triggering millions of __repr__ calls (8% of CPU in profiling)
- Guard remaining verbose_logger.debug with isEnabledFor(DEBUG) and use
lazy %s formatting instead of f-strings
- Replace usage stripping round-trip (model_dump + delete + reconstruct)
with a _usage_stripped flag, deferring exclusion to serialization time
* fix(proxy): remove per-chunk debug log and use _usage_stripped flag
- Remove verbose_proxy_logger.debug that formatted every streaming chunk
- Honor _usage_stripped flag from streaming handler to exclude usage
during model_dump_json serialization instead of reconstructing objects
* fix(proxy): remove per-chunk debug log in async_data_generator
Remove verbose_proxy_logger.debug that formatted every streaming chunk,
which triggered expensive Pydantic serialization on the hot path.
* fix indentation and add clarifying comment for usage stripping
* fix: guard calculate_total_usage against None usage in chunks
* fix: store chunk copy to preserve usage for calculate_total_usage
- test_litellm_pre_call_utils.py: wrap test body in try/finally so
litellm.callbacks is always restored even when an assertion fails,
addressing greptile review comment
- test_langfuse_otel.py: resolve trivial merge conflict in comment
("unpatched" vs "unpatch-ed"), keeping correct spelling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
test_reload_model_cost_map_admin_access calls the /reload/model_cost_map
HTTP endpoint with get_model_cost_map mocked to return a single-entry
dict. The endpoint handler does a direct module-level assignment
(litellm.model_cost = new_model_cost_map) which persists after the
patch context manager exits, stripping all models except gpt-3.5-turbo
from the in-memory cost map and causing subsequent tests that rely on
models like gemini-1.5-flash, multimodalembedding@001, and gpt-4o to
fail with "model not mapped" errors or zero-cost spend payloads.
Fix: save litellm.model_cost before the test and restore it (along with
invalidating the case-insensitive lookup cache) in a finally block.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: guard against None metadata in prometheus metrics
Use get_litellm_metadata_from_kwargs and get_metadata_variable_name_from_kwargs
helpers to properly resolve metadata from both 'metadata' and 'litellm_metadata'
keys, with None safety.
* test: add test for None metadata in prometheus metrics
Three test isolation issues fixed:
1. test_mcp_debug.py: Replace deprecated asyncio.get_event_loop().run_until_complete()
with asyncio.run() in TestWrapSendWithDebugHeaders. In Python 3.10+,
get_event_loop() raises RuntimeError when no event loop is set in the
current thread, causing test_injects_headers and test_body_messages_unchanged
to fail in isolation.
2. test_mcp_server_manager.py: After _reload_mcp_manager_module() creates a new
global_mcp_server_manager instance, server.py still holds a stale reference
to the old instance. Tests in test_mcp_server.py that populate the new
manager's registry and then call server.py functions (e.g. _get_tools_from_mcp_servers)
get empty results because server.py reads from the old manager. Fix: update
server.py's module-level reference after each reload.
3. test_litellm_pre_call_utils.py: test_add_litellm_metadata_from_request_headers
sets litellm.callbacks without restoring it afterward. Add cleanup to restore
original callbacks after the test to prevent state leaking to subsequent tests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Four finally blocks in test_internal_user_endpoints.py and one in
test_ui_sso.py used the pattern:
if original_default_params is not None:
litellm.default_internal_user_params = original_default_params
else:
delattr(litellm, "default_internal_user_params")
Since the attribute is defined in litellm/__init__.py with a default of
None, `getattr(litellm, "default_internal_user_params", None)` returns
None. The else branch then calls delattr(), permanently removing the
attribute from the module for the rest of the process.
Subsequent tests in the same pytest-xdist worker (e.g.
test_add_new_member_* in test_management_helpers_utils.py) then fail
with: AttributeError: module 'litellm' has no attribute
'default_internal_user_params'
Fix: replace all five flawed finally blocks with a simple assignment:
litellm.default_internal_user_params = original_default_params
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The file had two unresolved git merge conflict markers from a merge of
litellm_oss_staging_02_17_2026 into main, causing a SyntaxError when
pytest tried to collect the test module.
Kept the instance-level mocking approach (from litellm_oss_staging) for
test_get_complete_url and test_validate_environment, which is consistent
with the rest of the file and avoids class-reference issues caused by
importlib.reload(litellm) in conftest.py.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The model_prices_and_context_window_backup.json file has 'inference_geo'
fields (e.g. on 'us/claude-sonnet-4-6') for geo-prefixed Anthropic models
used in cost calculation, but the JSON schema validator in test_utils.py
did not include 'inference_geo' as an allowed property.
This caused test_aaamodel_prices_and_context_window_json_is_valid to fail
with: Additional properties are not allowed ('inference_geo' was unexpected)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The test was using setattr() to set module-level attributes (including
proxy_logging_obj = MagicMock()) on the real litellm.proxy.proxy_server
module, but the finally block only had `pass` — no cleanup.
This left proxy_logging_obj as a MagicMock in subsequent tests running
in the same pytest-xdist worker, causing TypeError when log_db_metrics
decorator called asyncio.create_task(proxy_logging_obj.service_logging_obj
.async_service_success_hook(...)) — a MagicMock is not a coroutine.
Fix: save original attribute values before the test and restore them in
the finally block to ensure test isolation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The implementation correctly preserves tool_call order: existing results first
(call_1), then dummy results for missing ones (call_2). The test was asserting
the reverse order with incorrect comments. Fix the assertions to match the
actual correct behavior.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add routing prefixes bedrock/nova/<ARN> and bedrock/nova-2/<ARN> so
LiteLLM can identify the base model family for custom/imported Nova
models and enable the correct supported params (tools, web_search,
reasoning_effort).
Changes:
- Route nova/ and nova-2/ prefixed models to converse API
- Strip spec prefix before sending ARN to Bedrock
- Return sentinel base models (amazon.nova-custom, amazon.nova-2-custom)
so downstream Nova checks work
- Recognize nova-2/ prefix in _is_nova_2_model() for reasoning support
- Handle nova/nova-2 in get_bedrock_model_id() for proper ARN encoding
- Add unit tests for all new behavior
- Use custom_endpoint=False so Databricks SDK auth fallback works
(custom_endpoint=True was blocking it). The api_base returned by
databricks_validate_environment is discarded since get_complete_url
builds the URL separately.
- Remove unused verbose_logger import
- Remove unused json import in tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Databricks supports the Responses API natively for GPT models, but litellm
was falling back to the completion transformation handler which converts
responses requests to chat completion calls, losing response schema enforcement.
This adds DatabricksResponsesAPIConfig that passes responses API requests
directly to Databricks' /responses endpoint for GPT models, while non-GPT
models (Claude, Llama, etc.) continue using the completion transformation path.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>