Skip to content

Commit a7a5af3

Browse files
committed
Merge branch 'antonpirker/openai-overhaul' into antonpirker/openai-responses-api
2 parents 2aa633d + c263e02 commit a7a5af3

File tree

10 files changed

+120
-47
lines changed

10 files changed

+120
-47
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# Changelog
22

3+
## 2.33.1
4+
5+
### Various fixes & improvements
6+
7+
- fix(integrations): allow explicit op parameter in `ai_track` (#4597) by @mshavliuk
8+
- fix: Fix `abs_path` bug in `serialize_frame` (#4599) by @szokeasaurusrex
9+
- Remove pyrsistent from test dependencies (#4588) by @musicinmybrain
10+
- Remove explicit `__del__`'s in threaded classes (#4590) by @sl0thentr0py
11+
- Remove forked from test_transport, separate gevent tests and generalize capturing_server to be module level (#4577) by @sl0thentr0py
12+
- Improve token usage recording (#4566) by @antonpirker
13+
314
## 2.33.0
415

516
### Various fixes & improvements

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
copyright = "2019-{}, Sentry Team and Contributors".format(datetime.now().year)
3232
author = "Sentry Team and Contributors"
3333

34-
release = "2.33.0"
34+
release = "2.33.1"
3535
version = ".".join(release.split(".")[:2]) # The short X.Y version.
3636

3737

requirements-testing.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ pytest-forked
66
pytest-localserver
77
pytest-watch
88
jsonschema
9-
pyrsistent
109
executing
1110
asttokens
1211
responses

sentry_sdk/ai/monitoring.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def decorator(f):
3232
def sync_wrapped(*args, **kwargs):
3333
# type: (Any, Any) -> Any
3434
curr_pipeline = _ai_pipeline_name.get()
35-
op = span_kwargs.get("op", "ai.run" if curr_pipeline else "ai.pipeline")
35+
op = span_kwargs.pop("op", "ai.run" if curr_pipeline else "ai.pipeline")
3636

3737
with start_span(name=description, op=op, **span_kwargs) as span:
3838
for k, v in kwargs.pop("sentry_tags", {}).items():
@@ -61,7 +61,7 @@ def sync_wrapped(*args, **kwargs):
6161
async def async_wrapped(*args, **kwargs):
6262
# type: (Any, Any) -> Any
6363
curr_pipeline = _ai_pipeline_name.get()
64-
op = span_kwargs.get("op", "ai.run" if curr_pipeline else "ai.pipeline")
64+
op = span_kwargs.pop("op", "ai.run" if curr_pipeline else "ai.pipeline")
6565

6666
with start_span(name=description, op=op, **span_kwargs) as span:
6767
for k, v in kwargs.pop("sentry_tags", {}).items():

sentry_sdk/consts.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,12 @@ class SPANDATA:
372372
Example: "chat"
373373
"""
374374

375+
GEN_AI_RESPONSE_MODEL = "gen_ai.response.model"
376+
"""
377+
Exact model identifier used to generate the response
378+
Example: gpt-4o-mini-2024-07-18
379+
"""
380+
375381
GEN_AI_RESPONSE_TEXT = "gen_ai.response.text"
376382
"""
377383
The model's response text messages.
@@ -649,6 +655,7 @@ class OP:
649655
FUNCTION_AWS = "function.aws"
650656
FUNCTION_GCP = "function.gcp"
651657
GEN_AI_CHAT = "gen_ai.chat"
658+
GEN_AI_EMBEDDINGS = "gen_ai.embeddings"
652659
GEN_AI_EXECUTE_TOOL = "gen_ai.execute_tool"
653660
GEN_AI_HANDOFF = "gen_ai.handoff"
654661
GEN_AI_INVOKE_AGENT = "gen_ai.invoke_agent"
@@ -675,8 +682,6 @@ class OP:
675682
MIDDLEWARE_STARLITE = "middleware.starlite"
676683
MIDDLEWARE_STARLITE_RECEIVE = "middleware.starlite.receive"
677684
MIDDLEWARE_STARLITE_SEND = "middleware.starlite.send"
678-
OPENAI_CHAT_COMPLETIONS_CREATE = "ai.chat_completions.create.openai"
679-
OPENAI_EMBEDDINGS_CREATE = "ai.embeddings.create.openai"
680685
HUGGINGFACE_HUB_CHAT_COMPLETIONS_CREATE = (
681686
"ai.chat_completions.create.huggingface_hub"
682687
)
@@ -1182,4 +1187,4 @@ def _get_default_options():
11821187
del _get_default_options
11831188

11841189

1185-
VERSION = "2.33.0"
1190+
VERSION = "2.33.1"

sentry_sdk/integrations/openai.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,8 @@ def _new_chat_completion_common(f, *args, **kwargs):
171171
streaming = kwargs.get("stream")
172172

173173
span = sentry_sdk.start_span(
174-
op=consts.OP.OPENAI_CHAT_COMPLETIONS_CREATE,
175-
name="Chat Completion",
174+
op=consts.OP.GEN_AI_CHAT,
175+
name=f"{consts.OP.GEN_AI_CHAT} {model}",
176176
origin=OpenAIIntegration.origin,
177177
)
178178
span.__enter__()
@@ -181,16 +181,16 @@ def _new_chat_completion_common(f, *args, **kwargs):
181181

182182
with capture_internal_exceptions():
183183
if should_send_default_pii() and integration.include_prompts:
184-
set_data_normalized(span, SPANDATA.AI_INPUT_MESSAGES, messages)
184+
set_data_normalized(span, SPANDATA.GEN_AI_REQUEST_MESSAGES, messages)
185185

186-
set_data_normalized(span, SPANDATA.AI_MODEL_ID, model)
186+
set_data_normalized(span, SPANDATA.GEN_AI_REQUEST_MODEL, model)
187187
set_data_normalized(span, SPANDATA.AI_STREAMING, streaming)
188188

189189
if hasattr(res, "choices"):
190190
if should_send_default_pii() and integration.include_prompts:
191191
set_data_normalized(
192192
span,
193-
SPANDATA.AI_RESPONSES,
193+
SPANDATA.GEN_AI_RESPONSE_TEXT,
194194
list(map(lambda x: x.message, res.choices)),
195195
)
196196
_calculate_token_usage(messages, res, span, None, integration.count_tokens)
@@ -222,7 +222,7 @@ def new_iterator():
222222
)
223223
if should_send_default_pii() and integration.include_prompts:
224224
set_data_normalized(
225-
span, SPANDATA.AI_RESPONSES, all_responses
225+
span, SPANDATA.GEN_AI_RESPONSE_TEXT, all_responses
226226
)
227227
_calculate_token_usage(
228228
messages,
@@ -255,7 +255,7 @@ async def new_iterator_async():
255255
)
256256
if should_send_default_pii() and integration.include_prompts:
257257
set_data_normalized(
258-
span, SPANDATA.AI_RESPONSES, all_responses
258+
span, SPANDATA.GEN_AI_RESPONSE_TEXT, all_responses
259259
)
260260
_calculate_token_usage(
261261
messages,
@@ -353,24 +353,30 @@ def _new_embeddings_create_common(f, *args, **kwargs):
353353
if integration is None:
354354
return f(*args, **kwargs)
355355

356+
model = kwargs.get("model")
357+
356358
with sentry_sdk.start_span(
357-
op=consts.OP.OPENAI_EMBEDDINGS_CREATE,
358-
description="OpenAI Embedding Creation",
359+
op=consts.OP.GEN_AI_EMBEDDINGS,
360+
name=f"{consts.OP.GEN_AI_EMBEDDINGS} {model}",
359361
origin=OpenAIIntegration.origin,
360362
) as span:
361363
if "input" in kwargs and (
362364
should_send_default_pii() and integration.include_prompts
363365
):
364366
if isinstance(kwargs["input"], str):
365-
set_data_normalized(span, SPANDATA.AI_INPUT_MESSAGES, [kwargs["input"]])
367+
set_data_normalized(
368+
span, SPANDATA.GEN_AI_REQUEST_MESSAGES, [kwargs["input"]]
369+
)
366370
elif (
367371
isinstance(kwargs["input"], list)
368372
and len(kwargs["input"]) > 0
369373
and isinstance(kwargs["input"][0], str)
370374
):
371-
set_data_normalized(span, SPANDATA.AI_INPUT_MESSAGES, kwargs["input"])
375+
set_data_normalized(
376+
span, SPANDATA.GEN_AI_REQUEST_MESSAGES, kwargs["input"]
377+
)
372378
if "model" in kwargs:
373-
set_data_normalized(span, SPANDATA.AI_MODEL_ID, kwargs["model"])
379+
set_data_normalized(span, SPANDATA.GEN_AI_REQUEST_MODEL, kwargs["model"])
374380

375381
response = yield f, args, kwargs
376382

sentry_sdk/utils.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -591,9 +591,14 @@ def serialize_frame(
591591
if tb_lineno is None:
592592
tb_lineno = frame.f_lineno
593593

594+
try:
595+
os_abs_path = os.path.abspath(abs_path) if abs_path else None
596+
except Exception:
597+
os_abs_path = None
598+
594599
rv = {
595600
"filename": filename_for_module(module, abs_path) or None,
596-
"abs_path": os.path.abspath(abs_path) if abs_path else None,
601+
"abs_path": os_abs_path,
597602
"function": function or "<unknown>",
598603
"module": module,
599604
"lineno": tb_lineno,

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def get_file_text(file_name):
2121

2222
setup(
2323
name="sentry-sdk",
24-
version="2.33.0",
24+
version="2.33.1",
2525
author="Sentry Team and Contributors",
2626
author_email="[email protected]",
2727
url="https://github.com/getsentry/sentry-python",

tests/integrations/openai/test_openai.py

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -137,14 +137,17 @@ def test_nonstreaming_chat_completion(
137137
tx = events[0]
138138
assert tx["type"] == "transaction"
139139
span = tx["spans"][0]
140-
assert span["op"] == "ai.chat_completions.create.openai"
140+
assert span["op"] == "gen_ai.chat"
141141

142142
if send_default_pii and include_prompts:
143-
assert "hello" in span["data"][SPANDATA.AI_INPUT_MESSAGES]["content"]
144-
assert "the model response" in span["data"][SPANDATA.AI_RESPONSES]["content"]
143+
assert "hello" in span["data"][SPANDATA.GEN_AI_REQUEST_MESSAGES]["content"]
144+
assert (
145+
"the model response"
146+
in span["data"][SPANDATA.GEN_AI_RESPONSE_TEXT]["content"]
147+
)
145148
else:
146-
assert SPANDATA.AI_INPUT_MESSAGES not in span["data"]
147-
assert SPANDATA.AI_RESPONSES not in span["data"]
149+
assert SPANDATA.GEN_AI_REQUEST_MESSAGES not in span["data"]
150+
assert SPANDATA.GEN_AI_RESPONSE_TEXT not in span["data"]
148151

149152
assert span["data"]["gen_ai.usage.output_tokens"] == 10
150153
assert span["data"]["gen_ai.usage.input_tokens"] == 20
@@ -179,14 +182,17 @@ async def test_nonstreaming_chat_completion_async(
179182
tx = events[0]
180183
assert tx["type"] == "transaction"
181184
span = tx["spans"][0]
182-
assert span["op"] == "ai.chat_completions.create.openai"
185+
assert span["op"] == "gen_ai.chat"
183186

184187
if send_default_pii and include_prompts:
185-
assert "hello" in span["data"][SPANDATA.AI_INPUT_MESSAGES]["content"]
186-
assert "the model response" in span["data"][SPANDATA.AI_RESPONSES]["content"]
188+
assert "hello" in span["data"][SPANDATA.GEN_AI_REQUEST_MESSAGES]["content"]
189+
assert (
190+
"the model response"
191+
in span["data"][SPANDATA.GEN_AI_RESPONSE_TEXT]["content"]
192+
)
187193
else:
188-
assert SPANDATA.AI_INPUT_MESSAGES not in span["data"]
189-
assert SPANDATA.AI_RESPONSES not in span["data"]
194+
assert SPANDATA.GEN_AI_REQUEST_MESSAGES not in span["data"]
195+
assert SPANDATA.GEN_AI_RESPONSE_TEXT not in span["data"]
190196

191197
assert span["data"]["gen_ai.usage.output_tokens"] == 10
192198
assert span["data"]["gen_ai.usage.input_tokens"] == 20
@@ -272,14 +278,14 @@ def test_streaming_chat_completion(
272278
tx = events[0]
273279
assert tx["type"] == "transaction"
274280
span = tx["spans"][0]
275-
assert span["op"] == "ai.chat_completions.create.openai"
281+
assert span["op"] == "gen_ai.chat"
276282

277283
if send_default_pii and include_prompts:
278-
assert "hello" in span["data"][SPANDATA.AI_INPUT_MESSAGES]["content"]
279-
assert "hello world" in span["data"][SPANDATA.AI_RESPONSES]
284+
assert "hello" in span["data"][SPANDATA.GEN_AI_REQUEST_MESSAGES]["content"]
285+
assert "hello world" in span["data"][SPANDATA.GEN_AI_RESPONSE_TEXT]
280286
else:
281-
assert SPANDATA.AI_INPUT_MESSAGES not in span["data"]
282-
assert SPANDATA.AI_RESPONSES not in span["data"]
287+
assert SPANDATA.GEN_AI_REQUEST_MESSAGES not in span["data"]
288+
assert SPANDATA.GEN_AI_RESPONSE_TEXT not in span["data"]
283289

284290
try:
285291
import tiktoken # type: ignore # noqa # pylint: disable=unused-import
@@ -368,14 +374,14 @@ async def test_streaming_chat_completion_async(
368374
tx = events[0]
369375
assert tx["type"] == "transaction"
370376
span = tx["spans"][0]
371-
assert span["op"] == "ai.chat_completions.create.openai"
377+
assert span["op"] == "gen_ai.chat"
372378

373379
if send_default_pii and include_prompts:
374-
assert "hello" in span["data"][SPANDATA.AI_INPUT_MESSAGES]["content"]
375-
assert "hello world" in span["data"][SPANDATA.AI_RESPONSES]
380+
assert "hello" in span["data"][SPANDATA.GEN_AI_REQUEST_MESSAGES]["content"]
381+
assert "hello world" in span["data"][SPANDATA.GEN_AI_RESPONSE_TEXT]
376382
else:
377-
assert SPANDATA.AI_INPUT_MESSAGES not in span["data"]
378-
assert SPANDATA.AI_RESPONSES not in span["data"]
383+
assert SPANDATA.GEN_AI_REQUEST_MESSAGES not in span["data"]
384+
assert SPANDATA.GEN_AI_RESPONSE_TEXT not in span["data"]
379385

380386
try:
381387
import tiktoken # type: ignore # noqa # pylint: disable=unused-import
@@ -459,11 +465,11 @@ def test_embeddings_create(
459465
tx = events[0]
460466
assert tx["type"] == "transaction"
461467
span = tx["spans"][0]
462-
assert span["op"] == "ai.embeddings.create.openai"
468+
assert span["op"] == "gen_ai.embeddings"
463469
if send_default_pii and include_prompts:
464-
assert "hello" in span["data"][SPANDATA.AI_INPUT_MESSAGES]
470+
assert "hello" in span["data"][SPANDATA.GEN_AI_REQUEST_MESSAGES]
465471
else:
466-
assert SPANDATA.AI_INPUT_MESSAGES not in span["data"]
472+
assert SPANDATA.GEN_AI_REQUEST_MESSAGES not in span["data"]
467473

468474
assert span["data"]["gen_ai.usage.input_tokens"] == 20
469475
assert span["data"]["gen_ai.usage.total_tokens"] == 30
@@ -507,11 +513,11 @@ async def test_embeddings_create_async(
507513
tx = events[0]
508514
assert tx["type"] == "transaction"
509515
span = tx["spans"][0]
510-
assert span["op"] == "ai.embeddings.create.openai"
516+
assert span["op"] == "gen_ai.embeddings"
511517
if send_default_pii and include_prompts:
512-
assert "hello" in span["data"][SPANDATA.AI_INPUT_MESSAGES]
518+
assert "hello" in span["data"][SPANDATA.GEN_AI_REQUEST_MESSAGES]
513519
else:
514-
assert SPANDATA.AI_INPUT_MESSAGES not in span["data"]
520+
assert SPANDATA.GEN_AI_REQUEST_MESSAGES not in span["data"]
515521

516522
assert span["data"]["gen_ai.usage.input_tokens"] == 20
517523
assert span["data"]["gen_ai.usage.total_tokens"] == 30

tests/test_ai_monitoring.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,44 @@ async def async_pipeline():
119119
assert ai_pipeline_span["tags"]["user"] == "czyber"
120120
assert ai_pipeline_span["data"]["some_data"] == "value"
121121
assert ai_run_span["description"] == "my async tool"
122+
123+
124+
def test_ai_track_with_explicit_op(sentry_init, capture_events):
125+
sentry_init(traces_sample_rate=1.0)
126+
events = capture_events()
127+
128+
@ai_track("my tool", op="custom.operation")
129+
def tool(**kwargs):
130+
pass
131+
132+
with sentry_sdk.start_transaction():
133+
tool()
134+
135+
transaction = events[0]
136+
assert transaction["type"] == "transaction"
137+
assert len(transaction["spans"]) == 1
138+
span = transaction["spans"][0]
139+
140+
assert span["description"] == "my tool"
141+
assert span["op"] == "custom.operation"
142+
143+
144+
@pytest.mark.asyncio
145+
async def test_ai_track_async_with_explicit_op(sentry_init, capture_events):
146+
sentry_init(traces_sample_rate=1.0)
147+
events = capture_events()
148+
149+
@ai_track("my async tool", op="custom.async.operation")
150+
async def async_tool(**kwargs):
151+
pass
152+
153+
with sentry_sdk.start_transaction():
154+
await async_tool()
155+
156+
transaction = events[0]
157+
assert transaction["type"] == "transaction"
158+
assert len(transaction["spans"]) == 1
159+
span = transaction["spans"][0]
160+
161+
assert span["description"] == "my async tool"
162+
assert span["op"] == "custom.async.operation"

0 commit comments

Comments
 (0)