Skip to content

Commit 7787e6d

Browse files
jgreer013claudealexander-alderman-webb
authored
fix(litellm): Store span off-band, not in forwarded metadata (#6598)
Store LiteLLM spans in a shared dictionary reference under `_sentry_span`. The same reference is passed to all LiteLLM callbacks. This prevents a `TypeError` caused by trying to serialize a `Span` when preparing an LLM request, as the `metadata` in which the span was previously stored can be part of a request. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Alex Alderman Webb <alexander.webb@sentry.io>
1 parent ad67968 commit 7787e6d

1 file changed

Lines changed: 18 additions & 15 deletions

File tree

sentry_sdk/integrations/litellm.py

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,21 @@
3131
raise DidNotEnable("LiteLLM not installed")
3232

3333

34-
def _get_metadata_dict(kwargs: "Dict[str, Any]") -> "Dict[str, Any]":
35-
"""Get the metadata dictionary from the kwargs."""
36-
litellm_params = kwargs.setdefault("litellm_params", {})
34+
# Stash the span on a top-level key of the per-request kwargs dict litellm passes
35+
# to every callback, so it lives and dies with the request.
36+
_SPAN_KEY = "_sentry_span"
3737

38-
# we need this weird little dance, as metadata might be set but may be None initially
39-
metadata = litellm_params.get("metadata")
40-
if metadata is None:
41-
metadata = {}
42-
litellm_params["metadata"] = metadata
43-
return metadata
38+
39+
def _store_span(kwargs: "Dict[str, Any]", span: "Any") -> None:
40+
kwargs[_SPAN_KEY] = span
41+
42+
43+
def _peek_span(kwargs: "Dict[str, Any]") -> "Any":
44+
return kwargs.get(_SPAN_KEY)
45+
46+
47+
def _pop_span(kwargs: "Dict[str, Any]") -> "Any":
48+
return kwargs.pop(_SPAN_KEY, None)
4449

4550

4651
def _convert_message_parts(messages: "List[Dict[str, Any]]") -> "List[Dict[str, Any]]":
@@ -117,8 +122,7 @@ def _input_callback(kwargs: "Dict[str, Any]") -> None:
117122
)
118123
span.__enter__()
119124

120-
# Store span for later
121-
_get_metadata_dict(kwargs)["_sentry_span"] = span
125+
_store_span(kwargs, span)
122126

123127
# Set basic data
124128
set_data_normalized(span, SPANDATA.GEN_AI_SYSTEM, provider)
@@ -198,8 +202,7 @@ def _success_callback(
198202
) -> None:
199203
"""Handle successful completion."""
200204

201-
metadata = _get_metadata_dict(kwargs)
202-
span = metadata.get("_sentry_span")
205+
span = _peek_span(kwargs)
203206
if span is None:
204207
return
205208

@@ -259,7 +262,7 @@ def _success_callback(
259262
or "complete_streaming_response" in kwargs
260263
or "async_complete_streaming_response" in kwargs
261264
):
262-
span = metadata.pop("_sentry_span", None)
265+
span = _pop_span(kwargs)
263266
if span is not None:
264267
span.__exit__(None, None, None)
265268

@@ -285,7 +288,7 @@ def _failure_callback(
285288
end_time: "datetime",
286289
) -> None:
287290
"""Handle request failure."""
288-
span = _get_metadata_dict(kwargs).get("_sentry_span")
291+
span = _pop_span(kwargs)
289292
if span is None:
290293
return
291294

0 commit comments

Comments
 (0)