diff --git a/litellm/caching/caching_handler.py b/litellm/caching/caching_handler.py index 7089ff78835..43dea11dd7d 100644 --- a/litellm/caching/caching_handler.py +++ b/litellm/caching/caching_handler.py @@ -182,14 +182,19 @@ def _should_defer_streaming_cache_hit_callbacks(*, cached_result: object) -> boo spend and callback records. A plain (non-stream) replay logs here, since nothing else will. """ - from litellm.llms.anthropic.pass_through.messages.response_cache import ( - CachedAnthropicMessagesStreamIterator, - ) from litellm.responses.streaming_iterator import BaseResponsesAPIStreamingIterator - return isinstance( - cached_result, (CustomStreamWrapper, BaseResponsesAPIStreamingIterator, CachedAnthropicMessagesStreamIterator) - ) + base_replay_classes: Final = (CustomStreamWrapper, BaseResponsesAPIStreamingIterator) + try: + from litellm.llms.anthropic.pass_through.messages.response_cache import ( + CachedAnthropicMessagesStreamIterator, + ) + except ModuleNotFoundError: + # The Anthropic Messages replay is only reachable through proxy-only + # extras (fastapi). A base SDK install can never hold one, so the + # isinstance check can safely go ahead without it. + return isinstance(cached_result, base_replay_classes) + return isinstance(cached_result, (*base_replay_classes, CachedAnthropicMessagesStreamIterator)) def _prompt_tokens_details_as_mapping(details: "PromptTokensDetailsWrapper") -> Mapping[str, object]: diff --git a/tests/unit/caching/test_caching_handler.py b/tests/unit/caching/test_caching_handler.py index 51754c80dd6..1843692045c 100644 --- a/tests/unit/caching/test_caching_handler.py +++ b/tests/unit/caching/test_caching_handler.py @@ -3231,3 +3231,21 @@ async def test_a_cache_hit_inside_an_internal_sub_call_is_free_and_fires_no_call assert async_logging.async_success_handler.called is not internal assert async_logging.handle_sync_success_callbacks_for_async_calls.called is not internal assert sync_logging.handle_sync_cache_hit_success_callbacks.called is not internal + +def test_should_defer_streaming_cache_hit_callbacks_without_proxy_extras(monkeypatch): + """Base SDK installs lack the proxy-only extras (fastapi); the defer check must not require them.""" + import sys + + for mod in list(sys.modules): + if mod.startswith(("fastapi", "litellm.llms.anthropic.pass_through", "litellm.proxy")): + monkeypatch.delitem(sys.modules, mod, raising=False) + monkeypatch.setitem(sys.modules, "fastapi", None) + + logging_obj = MagicMock() + logging_obj.model_call_details = {} + stream_replay = CustomStreamWrapper( + completion_stream=iter(()), model="gpt-4o", logging_obj=logging_obj + ) + assert _should_defer_streaming_cache_hit_callbacks(cached_result=stream_replay) is True + assert _should_defer_streaming_cache_hit_callbacks(cached_result=ModelResponse()) is False + assert _should_defer_streaming_cache_hit_callbacks(cached_result={"id": "msg_1"}) is False