Skip to content

Commit 88de4fc

Browse files
committed
Merge remote-tracking branch 'origin/master' into potel-base
2 parents ee01369 + 9b66f3b commit 88de4fc

File tree

12 files changed

+540
-170
lines changed

12 files changed

+540
-170
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,16 @@ for your feedback. How was the migration? Is everything working as expected? Is
4040
[on GitHub](https://github.com/getsentry/sentry-python/discussions/3936) or
4141
[on Discord](https://discord.com/invite/Ww9hbqr).
4242

43+
## 2.33.0
44+
45+
### Various fixes & improvements
46+
47+
- feat(langchain): Support `BaseCallbackManager` (#4486) by @szokeasaurusrex
48+
- Use `span.data` instead of `measurements` for token usage (#4567) by @antonpirker
49+
- Fix custom model name (#4569) by @antonpirker
50+
- fix: shut down "session flusher" more promptly (#4561) by @bukzor
51+
- chore: Remove Lambda urllib3 pin on Python 3.10+ (#4549) by @sentrivana
52+
4353
## 2.32.0
4454

4555
### Various fixes & improvements

sentry_sdk/ai/monitoring.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -96,26 +96,37 @@ async def async_wrapped(*args: Any, **kwargs: Any) -> Any:
9696

9797
def record_token_usage(
9898
span: Span,
99-
prompt_tokens: Optional[int] = None,
100-
completion_tokens: Optional[int] = None,
99+
input_tokens: Optional[int] = None,
100+
input_tokens_cached: Optional[int] = None,
101+
output_tokens: Optional[int] = None,
102+
output_tokens_reasoning: Optional[int] = None,
101103
total_tokens: Optional[int] = None,
102104
) -> None:
105+
# TODO: move pipeline name elsewhere
103106
ai_pipeline_name = get_ai_pipeline_name()
104107
if ai_pipeline_name:
105108
span.set_attribute(SPANDATA.AI_PIPELINE_NAME, ai_pipeline_name)
106109

107-
if prompt_tokens is not None:
108-
span.set_attribute(SPANDATA.GEN_AI_USAGE_INPUT_TOKENS, prompt_tokens)
110+
if input_tokens is not None:
111+
span.set_attribute(SPANDATA.GEN_AI_USAGE_INPUT_TOKENS, input_tokens)
109112

110-
if completion_tokens is not None:
111-
span.set_attribute(SPANDATA.GEN_AI_USAGE_OUTPUT_TOKENS, completion_tokens)
113+
if input_tokens_cached is not None:
114+
span.set_attribute(
115+
SPANDATA.GEN_AI_USAGE_INPUT_TOKENS_CACHED,
116+
input_tokens_cached,
117+
)
112118

113-
if (
114-
total_tokens is None
115-
and prompt_tokens is not None
116-
and completion_tokens is not None
117-
):
118-
total_tokens = prompt_tokens + completion_tokens
119+
if output_tokens is not None:
120+
span.set_data(SPANDATA.GEN_AI_USAGE_OUTPUT_TOKENS, output_tokens)
121+
122+
if output_tokens_reasoning is not None:
123+
span.set_data(
124+
SPANDATA.GEN_AI_USAGE_OUTPUT_TOKENS_REASONING,
125+
output_tokens_reasoning,
126+
)
127+
128+
if total_tokens is None and input_tokens is not None and output_tokens is not None:
129+
total_tokens = input_tokens + output_tokens
119130

120131
if total_tokens is not None:
121132
span.set_attribute(SPANDATA.GEN_AI_USAGE_TOTAL_TOKENS, total_tokens)

sentry_sdk/integrations/anthropic.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,13 @@ def _calculate_token_usage(result: Messages, span: Span) -> None:
6262
output_tokens = usage.output_tokens
6363

6464
total_tokens = input_tokens + output_tokens
65-
record_token_usage(span, input_tokens, output_tokens, total_tokens)
65+
66+
record_token_usage(
67+
span,
68+
input_tokens=input_tokens,
69+
output_tokens=output_tokens,
70+
total_tokens=total_tokens,
71+
)
6672

6773

6874
def _get_responses(content: list[Any]) -> list[dict[str, Any]]:
@@ -129,7 +135,12 @@ def _add_ai_data_to_span(
129135
[{"type": "text", "text": complete_message}],
130136
)
131137
total_tokens = input_tokens + output_tokens
132-
record_token_usage(span, input_tokens, output_tokens, total_tokens)
138+
record_token_usage(
139+
span,
140+
input_tokens=input_tokens,
141+
output_tokens=output_tokens,
142+
total_tokens=total_tokens,
143+
)
133144
span.set_attribute(SPANDATA.AI_STREAMING, True)
134145

135146

sentry_sdk/integrations/cohere.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,14 +114,14 @@ def collect_chat_response_fields(
114114
if hasattr(res.meta, "billed_units"):
115115
record_token_usage(
116116
span,
117-
prompt_tokens=res.meta.billed_units.input_tokens,
118-
completion_tokens=res.meta.billed_units.output_tokens,
117+
input_tokens=res.meta.billed_units.input_tokens,
118+
output_tokens=res.meta.billed_units.output_tokens,
119119
)
120120
elif hasattr(res.meta, "tokens"):
121121
record_token_usage(
122122
span,
123-
prompt_tokens=res.meta.tokens.input_tokens,
124-
completion_tokens=res.meta.tokens.output_tokens,
123+
input_tokens=res.meta.tokens.input_tokens,
124+
output_tokens=res.meta.tokens.output_tokens,
125125
)
126126

127127
if hasattr(res.meta, "warnings"):
@@ -258,7 +258,7 @@ def new_embed(*args: Any, **kwargs: Any) -> Any:
258258
):
259259
record_token_usage(
260260
span,
261-
prompt_tokens=res.meta.billed_units.input_tokens,
261+
input_tokens=res.meta.billed_units.input_tokens,
262262
total_tokens=res.meta.billed_units.input_tokens,
263263
)
264264
return res

sentry_sdk/integrations/huggingface_hub.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,10 @@ def new_text_generation(*args: Any, **kwargs: Any) -> Any:
108108
[res.generated_text],
109109
)
110110
if res.details is not None and res.details.generated_tokens > 0:
111-
record_token_usage(span, total_tokens=res.details.generated_tokens)
111+
record_token_usage(
112+
span,
113+
total_tokens=res.details.generated_tokens,
114+
)
112115
span.__exit__(None, None, None)
113116
return res
114117

@@ -141,7 +144,10 @@ def new_details_iterator() -> Iterable[ChatCompletionStreamOutput]:
141144
span, SPANDATA.AI_RESPONSES, "".join(data_buf)
142145
)
143146
if tokens_used > 0:
144-
record_token_usage(span, total_tokens=tokens_used)
147+
record_token_usage(
148+
span,
149+
total_tokens=tokens_used,
150+
)
145151
span.__exit__(None, None, None)
146152

147153
return new_details_iterator()

sentry_sdk/integrations/langchain.py

Lines changed: 43 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from langchain_core.callbacks import (
2525
manager,
2626
BaseCallbackHandler,
27+
BaseCallbackManager,
2728
Callbacks,
2829
)
2930
from langchain_core.agents import AgentAction, AgentFinish
@@ -302,15 +303,15 @@ def on_llm_end(
302303
if token_usage:
303304
record_token_usage(
304305
span_data.span,
305-
token_usage.get("prompt_tokens"),
306-
token_usage.get("completion_tokens"),
307-
token_usage.get("total_tokens"),
306+
input_tokens=token_usage.get("prompt_tokens"),
307+
output_tokens=token_usage.get("completion_tokens"),
308+
total_tokens=token_usage.get("total_tokens"),
308309
)
309310
else:
310311
record_token_usage(
311312
span_data.span,
312-
span_data.num_prompt_tokens,
313-
span_data.num_completion_tokens,
313+
input_tokens=span_data.num_prompt_tokens,
314+
output_tokens=span_data.num_completion_tokens,
314315
)
315316

316317
self._exit_span(span_data, run_id)
@@ -499,12 +500,20 @@ def new_configure(
499500
**kwargs,
500501
)
501502

502-
callbacks_list = local_callbacks or []
503-
504-
if isinstance(callbacks_list, BaseCallbackHandler):
505-
callbacks_list = [callbacks_list]
506-
elif not isinstance(callbacks_list, list):
507-
logger.debug("Unknown callback type: %s", callbacks_list)
503+
local_callbacks = local_callbacks or []
504+
505+
# Handle each possible type of local_callbacks. For each type, we
506+
# extract the list of callbacks to check for SentryLangchainCallback,
507+
# and define a function that would add the SentryLangchainCallback
508+
# to the existing callbacks list.
509+
if isinstance(local_callbacks, BaseCallbackManager):
510+
callbacks_list = local_callbacks.handlers
511+
elif isinstance(local_callbacks, BaseCallbackHandler):
512+
callbacks_list = [local_callbacks]
513+
elif isinstance(local_callbacks, list):
514+
callbacks_list = local_callbacks
515+
else:
516+
logger.debug("Unknown callback type: %s", local_callbacks)
508517
# Just proceed with original function call
509518
return f(
510519
callback_manager_cls,
@@ -514,28 +523,38 @@ def new_configure(
514523
**kwargs,
515524
)
516525

517-
inheritable_callbacks_list = (
518-
inheritable_callbacks if isinstance(inheritable_callbacks, list) else []
519-
)
526+
# Handle each possible type of inheritable_callbacks.
527+
if isinstance(inheritable_callbacks, BaseCallbackManager):
528+
inheritable_callbacks_list = inheritable_callbacks.handlers
529+
elif isinstance(inheritable_callbacks, list):
530+
inheritable_callbacks_list = inheritable_callbacks
531+
else:
532+
inheritable_callbacks_list = []
520533

521534
if not any(
522535
isinstance(cb, SentryLangchainCallback)
523536
for cb in itertools.chain(callbacks_list, inheritable_callbacks_list)
524537
):
525-
# Avoid mutating the existing callbacks list
526-
callbacks_list = [
527-
*callbacks_list,
528-
SentryLangchainCallback(
529-
integration.max_spans,
530-
integration.include_prompts,
531-
integration.tiktoken_encoding_name,
532-
),
533-
]
538+
sentry_handler = SentryLangchainCallback(
539+
integration.max_spans,
540+
integration.include_prompts,
541+
integration.tiktoken_encoding_name,
542+
)
543+
if isinstance(local_callbacks, BaseCallbackManager):
544+
local_callbacks = local_callbacks.copy()
545+
local_callbacks.handlers = [
546+
*local_callbacks.handlers,
547+
sentry_handler,
548+
]
549+
elif isinstance(local_callbacks, BaseCallbackHandler):
550+
local_callbacks = [local_callbacks, sentry_handler]
551+
else: # local_callbacks is a list
552+
local_callbacks = [*local_callbacks, sentry_handler]
534553

535554
return f(
536555
callback_manager_cls,
537556
inheritable_callbacks,
538-
callbacks_list,
557+
local_callbacks,
539558
*args,
540559
**kwargs,
541560
)

0 commit comments

Comments
 (0)