mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-07 07:58:09 +00:00
fix(wrapper): drop mutable call-kwargs tracker, assert exceptions at dispatch time
This commit is contained in:
parent
ed795b10da
commit
67219b4f80
3 changed files with 17 additions and 10 deletions
|
|
@ -2198,7 +2198,6 @@ def client(original_function):
|
|||
kwargs.pop("_is_litellm_internal_call", None) # discard if injected
|
||||
_is_litellm_internal_call: Final = is_internal_call.get()
|
||||
_deployment_call_end_time: datetime.datetime | None = None
|
||||
uncached_call_kwargs: dict | None = None
|
||||
|
||||
try:
|
||||
if logging_obj is None:
|
||||
|
|
@ -2309,8 +2308,6 @@ def client(original_function):
|
|||
and _caching_handler_response.embedding_uncached_input is not None
|
||||
else kwargs
|
||||
)
|
||||
if call_kwargs is not kwargs:
|
||||
uncached_call_kwargs = call_kwargs
|
||||
try:
|
||||
result = await original_function(*args, **call_kwargs)
|
||||
except Exception as deployment_error:
|
||||
|
|
@ -2323,6 +2320,11 @@ def client(original_function):
|
|||
)
|
||||
except BaseException: # noqa: S110, BLE001 # hook dispatch - including cancellation mid-await - must never replace the real deployment failure, so there is nothing to do with what it raises
|
||||
pass
|
||||
if call_kwargs is not kwargs:
|
||||
# The uncached-input dict is a wrapper-private copy of kwargs whose
|
||||
# request never left this frame except as part of the failure
|
||||
# traceback below, so its logging reference is released here.
|
||||
call_kwargs.pop("litellm_logging_obj", None)
|
||||
raise
|
||||
end_time = datetime.datetime.now()
|
||||
|
||||
|
|
@ -2505,8 +2507,6 @@ def client(original_function):
|
|||
if logging_obj is not None and not _is_litellm_internal_call:
|
||||
logging_obj.model_call_details.pop("exception", None)
|
||||
kwargs.pop("litellm_logging_obj", None)
|
||||
if uncached_call_kwargs is not None:
|
||||
uncached_call_kwargs.pop("litellm_logging_obj", None)
|
||||
raise e
|
||||
|
||||
finally:
|
||||
|
|
|
|||
|
|
@ -222,6 +222,9 @@ class _CallbackCapture(CustomLogger):
|
|||
self.async_success: Final = AsyncMock()
|
||||
self.sync_failure: Final = Mock()
|
||||
self.async_failure: Final = AsyncMock()
|
||||
# Exception seen at dispatch time; litellm releases it from the call-details
|
||||
# dict once the failure handlers have run (issue #48649).
|
||||
self.async_failure_exceptions: Final = []
|
||||
|
||||
def log_success_event(self, kwargs, response_obj, start_time, end_time) -> None:
|
||||
self.sync_success(kwargs=kwargs, response_obj=response_obj)
|
||||
|
|
@ -234,6 +237,7 @@ class _CallbackCapture(CustomLogger):
|
|||
self.sync_failure(kwargs=kwargs, response_obj=response_obj)
|
||||
|
||||
async def async_log_failure_event(self, kwargs, response_obj, start_time, end_time) -> None:
|
||||
self.async_failure_exceptions.append(kwargs.get("exception"))
|
||||
await self.async_failure(kwargs=kwargs, response_obj=response_obj)
|
||||
|
||||
|
||||
|
|
@ -501,7 +505,7 @@ async def test_async_embedding_failure_reaches_failure_callback(
|
|||
)
|
||||
assert len(failure_events) == 1
|
||||
assert failure_events[0]["model"] == "text-embedding-3-small"
|
||||
assert isinstance(failure_events[0]["exception"], litellm.AuthenticationError)
|
||||
assert isinstance(callback_capture.async_failure_exceptions[0], litellm.AuthenticationError)
|
||||
assert callback_capture.async_success.call_args_list == []
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ class _HookEvent(NamedTuple):
|
|||
model: object
|
||||
kwargs: Mapping[str, object]
|
||||
response: object
|
||||
exception: object
|
||||
|
||||
|
||||
def _router_context_problems(kwargs: Mapping[str, object]) -> tuple[str, ...]:
|
||||
|
|
@ -156,7 +157,9 @@ class _HookRecorder(CustomLogger):
|
|||
if not _is_from_this_test(kwargs):
|
||||
return
|
||||
self.errors.extend(f"{state}: {problem}" for problem in problems)
|
||||
self.events.append(_HookEvent(state, model, kwargs, response))
|
||||
# Snapshot the exception at dispatch time: litellm no longer keeps it on the
|
||||
# call-details dict after the failure handlers have run (issue #48649).
|
||||
self.events.append(_HookEvent(state, model, kwargs, response, kwargs.get("exception")))
|
||||
if self.loop is not None:
|
||||
self.loop.call_soon_threadsafe(self._notify)
|
||||
|
||||
|
|
@ -344,7 +347,7 @@ async def test_router_chat_success_streaming_and_failure_fire_the_hooks_in_order
|
|||
failed: Final = recorder.events[6:]
|
||||
assert [event.state for event in failed] == ["sync_pre_api_call", "post_api_call", "async_failure"]
|
||||
assert failed[2].response is None
|
||||
assert isinstance(failed[2].kwargs["exception"], litellm.AuthenticationError)
|
||||
assert isinstance(failed[2].exception, litellm.AuthenticationError)
|
||||
assert recorder.errors == []
|
||||
|
||||
|
||||
|
|
@ -372,7 +375,7 @@ async def test_router_embedding_success_and_failure_fire_the_hooks_in_order(
|
|||
await recorder.until(_settled(2, posts=2))
|
||||
assert recorder.states[3:] == ["sync_pre_api_call", "post_api_call", "async_failure"]
|
||||
assert recorder.events[5].response is None
|
||||
assert isinstance(recorder.events[5].kwargs["exception"], litellm.AuthenticationError)
|
||||
assert isinstance(recorder.events[5].exception, litellm.AuthenticationError)
|
||||
assert recorder.errors == []
|
||||
|
||||
|
||||
|
|
@ -425,7 +428,7 @@ async def test_router_fallback_fires_failure_then_success_hooks(
|
|||
]
|
||||
assert [_model_group(event) for event in recorder.events] == ["primary-group"] * 3 + ["fallback-group"] * 3
|
||||
assert _model_id(recorder.events[0]) != _model_id(recorder.events[3])
|
||||
assert isinstance(recorder.events[2].kwargs["exception"], litellm.AuthenticationError)
|
||||
assert isinstance(recorder.events[2].exception, litellm.AuthenticationError)
|
||||
assert _completion(recorder.events[5]).choices[0].message.content == "hello"
|
||||
assert recorder.errors == []
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue