Skip to content

Commit 8245e12

Browse files
committed
PR feedback fixes
1 parent 1b29839 commit 8245e12

File tree

4 files changed

+92
-54
lines changed

4 files changed

+92
-54
lines changed

sentry_sdk/consts.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,8 @@ class OP:
219219
MIDDLEWARE_STARLITE = "middleware.starlite"
220220
MIDDLEWARE_STARLITE_RECEIVE = "middleware.starlite.receive"
221221
MIDDLEWARE_STARLITE_SEND = "middleware.starlite.send"
222+
OPENAI_CHAT_COMPLETIONS_CREATE = "openai.chat_completions.create"
223+
OPENAI_EMBEDDINGS_CREATE = "openai.embeddings.create"
222224
QUEUE_SUBMIT_ARQ = "queue.submit.arq"
223225
QUEUE_TASK_ARQ = "queue.task.arq"
224226
QUEUE_SUBMIT_CELERY = "queue.submit.celery"

sentry_sdk/integrations/openai.py

Lines changed: 49 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
from __future__ import absolute_import
2-
1+
from sentry_sdk import consts
32
from sentry_sdk._types import TYPE_CHECKING
43

54
if TYPE_CHECKING:
65
from typing import Any, Iterable, List, Optional, Callable, Iterator
76
from sentry_sdk.tracing import Span
87

8+
import sentry_sdk
99
from sentry_sdk._functools import wraps
1010
from sentry_sdk.hub import Hub
1111
from sentry_sdk.integrations import DidNotEnable, Integration
12-
from sentry_sdk.utils import logger, capture_internal_exceptions
12+
from sentry_sdk.utils import logger, capture_internal_exceptions, event_from_exception
1313

1414
try:
1515
from openai.resources.chat.completions import Completions
@@ -42,9 +42,9 @@ def count_tokens(s):
4242
return 0
4343

4444

45-
COMPLETION_TOKENS = "completion_tоkens"
46-
PROMPT_TOKENS = "prompt_tоkens"
47-
TOTAL_TOKENS = "total_tоkens"
45+
COMPLETION_TOKENS_USED = "ai.completion_tоkens.used"
46+
PROMPT_TOKENS_USED = "ai.prompt_tоkens.used"
47+
TOTAL_TOKENS_USED = "ai.total_tоkens.used"
4848

4949

5050
class OpenAIIntegration(Integration):
@@ -54,7 +54,19 @@ class OpenAIIntegration(Integration):
5454
def setup_once():
5555
# type: () -> None
5656
Completions.create = _wrap_chat_completion_create(Completions.create)
57-
Embeddings.create = _wrap_enbeddings_create(Embeddings.create)
57+
Embeddings.create = _wrap_embeddings_create(Embeddings.create)
58+
59+
60+
def _capture_exception(hub, exc):
61+
# type: (Hub, Any) -> None
62+
63+
if hub.client is not None:
64+
event, hint = event_from_exception(
65+
exc,
66+
client_options=hub.client.options,
67+
mechanism={"type": "openai", "handled": False},
68+
)
69+
hub.capture_event(event, hint=hint)
5870

5971

6072
def _calculate_chat_completion_usage(
@@ -98,23 +110,18 @@ def _calculate_chat_completion_usage(
98110
total_tokens = prompt_tokens + completion_tokens
99111

100112
if completion_tokens != 0:
101-
span.set_data(COMPLETION_TOKENS, completion_tokens)
113+
span.set_data(COMPLETION_TOKENS_USED, completion_tokens)
102114
if prompt_tokens != 0:
103-
span.set_data(PROMPT_TOKENS, prompt_tokens)
115+
span.set_data(PROMPT_TOKENS_USED, prompt_tokens)
104116
if total_tokens != 0:
105-
span.set_data(TOTAL_TOKENS, total_tokens)
117+
span.set_data(TOTAL_TOKENS_USED, total_tokens)
106118

107119

108120
def _wrap_chat_completion_create(f):
109121
# type: (Callable[..., Any]) -> Callable[..., Any]
110122
@wraps(f)
111123
def new_chat_completion(*args, **kwargs):
112124
# type: (*Any, **Any) -> Any
113-
hub = Hub.current
114-
integration = hub.get_integration(OpenAIIntegration)
115-
if integration is None:
116-
return f(*args, **kwargs)
117-
118125
if "messages" not in kwargs:
119126
# invalid call (in all versions of openai), let it return error
120127
return f(*args, **kwargs)
@@ -130,13 +137,21 @@ def new_chat_completion(*args, **kwargs):
130137
model = kwargs.get("model")
131138
streaming = kwargs.get("stream")
132139

133-
span = hub.start_span(op="openai", description="Chat Completion")
140+
span = sentry_sdk.start_span(
141+
op=consts.OP.OPENAI_CHAT_COMPLETIONS_CREATE, description="Chat Completion"
142+
)
134143
span.__enter__()
135-
res = f(*args, **kwargs)
144+
try:
145+
res = f(*args, **kwargs)
146+
except Exception as e:
147+
_capture_exception(Hub.current, e)
148+
span.__exit__(None, None, None)
149+
raise e from None
150+
136151
with capture_internal_exceptions():
137152
span.set_data("messages", messages)
138-
span.set_tag("model", model)
139-
span.set_tag("streaming", streaming)
153+
span.set_data("model", model)
154+
span.set_data("streaming", streaming)
140155

141156
if hasattr(res, "choices"):
142157
span.set_data("response", res.choices[0].message)
@@ -175,32 +190,34 @@ def new_iterator():
175190

176191
res._iterator = new_iterator()
177192
else:
178-
span.set_tag("unknown_response", True)
193+
span.set_data("unknown_response", True)
179194
span.__exit__(None, None, None)
180195
return res
181196

182197
return new_chat_completion
183198

184199

185-
def _wrap_enbeddings_create(f):
200+
def _wrap_embeddings_create(f):
186201
# type: (Callable[..., Any]) -> Callable[..., Any]
187202

188203
@wraps(f)
189204
def new_embeddings_create(*args, **kwargs):
190205
# type: (*Any, **Any) -> Any
191-
hub = Hub.current
192-
integration = hub.get_integration(OpenAIIntegration)
193-
if integration is None:
194-
return f(*args, **kwargs)
195-
196-
with hub.start_span(op="openai", description="Embeddings Creation") as span:
206+
with sentry_sdk.start_span(
207+
op=consts.OP.OPENAI_EMBEDDINGS_CREATE,
208+
description="OpenAI Embedding Creation",
209+
) as span:
197210
if "input" in kwargs and isinstance(kwargs["input"], str):
198211
span.set_data("input", kwargs["input"])
199212
if "model" in kwargs:
200-
span.set_tag("model", kwargs["model"])
213+
span.set_data("model", kwargs["model"])
201214
if "dimensions" in kwargs:
202-
span.set_tag("dimensions", kwargs["dimensions"])
203-
response = f(*args, **kwargs)
215+
span.set_data("dimensions", kwargs["dimensions"])
216+
try:
217+
response = f(*args, **kwargs)
218+
except Exception as e:
219+
_capture_exception(Hub.current, e)
220+
raise e from None
204221

205222
prompt_tokens = 0
206223
total_tokens = 0
@@ -220,8 +237,8 @@ def new_embeddings_create(*args, **kwargs):
220237
if total_tokens == 0:
221238
total_tokens = prompt_tokens
222239

223-
span.set_data(PROMPT_TOKENS, prompt_tokens)
224-
span.set_data(TOTAL_TOKENS, total_tokens)
240+
span.set_data(PROMPT_TOKENS_USED, prompt_tokens)
241+
span.set_data(TOTAL_TOKENS_USED, total_tokens)
225242

226243
return response
227244

tests/integrations/openai/test_openai.py

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
1-
from openai import OpenAI, Stream
1+
import pytest
2+
from openai import OpenAI, Stream, OpenAIError
23
from openai.types import CompletionUsage, CreateEmbeddingResponse, Embedding
34
from openai.types.chat import ChatCompletion, ChatCompletionMessage, ChatCompletionChunk
45
from openai.types.chat.chat_completion import Choice
56
from openai.types.chat.chat_completion_chunk import ChoiceDelta, Choice as DeltaChoice
67
from openai.types.create_embedding_response import Usage as EmbeddingTokenUsage
78

89
from sentry_sdk import start_transaction
9-
from sentry_sdk.integrations.openai import OpenAIIntegration
10+
from sentry_sdk.integrations.openai import (
11+
OpenAIIntegration,
12+
COMPLETION_TOKENS_USED,
13+
PROMPT_TOKENS_USED,
14+
TOTAL_TOKENS_USED,
15+
)
1016

11-
try:
12-
from unittest import mock # python 3.3 and above
13-
except ImportError:
14-
import mock # python < 3.3
15-
16-
COMPLETION_TOKENS = "completion_tоkens"
17-
PROMPT_TOKENS = "prompt_tоkens"
18-
TOTAL_TOKENS = "total_tоkens"
17+
from unittest import mock # python 3.3 and above
1918

2019

2120
def test_nonstreaming_chat_completion(sentry_init, capture_events):
@@ -56,11 +55,11 @@ def test_nonstreaming_chat_completion(sentry_init, capture_events):
5655
tx = events[0]
5756
assert tx["type"] == "transaction"
5857
span = tx["spans"][0]
59-
assert span["op"] == "openai"
58+
assert span["op"] == "openai.chat_completions.create"
6059

61-
assert span["data"][COMPLETION_TOKENS] == 10
62-
assert span["data"][PROMPT_TOKENS] == 20
63-
assert span["data"][TOTAL_TOKENS] == 30
60+
assert span["data"][COMPLETION_TOKENS_USED] == 10
61+
assert span["data"][PROMPT_TOKENS_USED] == 20
62+
assert span["data"][TOTAL_TOKENS_USED] == 30
6463

6564

6665
# noinspection PyTypeChecker
@@ -118,10 +117,27 @@ def test_streaming_chat_completion(sentry_init, capture_events):
118117
tx = events[0]
119118
assert tx["type"] == "transaction"
120119
span = tx["spans"][0]
121-
assert span["op"] == "openai"
122-
assert span["data"][COMPLETION_TOKENS] == 2
123-
assert span["data"][PROMPT_TOKENS] == 1
124-
assert span["data"][TOTAL_TOKENS] == 3
120+
assert span["op"] == "openai.chat_completions.create"
121+
assert span["data"][COMPLETION_TOKENS_USED] == 2
122+
assert span["data"][PROMPT_TOKENS_USED] == 1
123+
assert span["data"][TOTAL_TOKENS_USED] == 3
124+
125+
126+
def test_bad_chat_completion(sentry_init, capture_events):
127+
sentry_init(integrations=[OpenAIIntegration()], traces_sample_rate=1.0)
128+
events = capture_events()
129+
130+
client = OpenAI(api_key="z")
131+
client.chat.completions._post = mock.Mock(
132+
side_effect=OpenAIError("API rate limit reached")
133+
)
134+
with pytest.raises(OpenAIError):
135+
client.chat.completions.create(
136+
model="some-model", messages=[{"role": "system", "content": "hello"}]
137+
)
138+
139+
(event,) = events
140+
assert event["level"] == "error"
125141

126142

127143
def test_embeddings_create(sentry_init, capture_events):
@@ -151,7 +167,7 @@ def test_embeddings_create(sentry_init, capture_events):
151167
tx = events[0]
152168
assert tx["type"] == "transaction"
153169
span = tx["spans"][0]
154-
assert span["op"] == "openai"
170+
assert span["op"] == "openai.embeddings.create"
155171

156-
assert span["data"][PROMPT_TOKENS] == 20
157-
assert span["data"][TOTAL_TOKENS] == 30
172+
assert span["data"][PROMPT_TOKENS_USED] == 20
173+
assert span["data"][TOTAL_TOKENS_USED] == 30

tox.ini

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ envlist =
149149
# OpenAI
150150
{py3.9,py3.11,py3.12}-openai-v1
151151
{py3.9,py3.11,py3.12}-openai-latest
152+
{py3.9,py3.11,py3.12}-openai-without-tiktoken
152153

153154
# OpenTelemetry (OTel)
154155
{py3.7,py3.9,py3.11,py3.12}-opentelemetry
@@ -444,9 +445,11 @@ deps =
444445
loguru-latest: loguru
445446

446447
# OpenAI
447-
openai: tiktoken
448448
openai-v1: openai~=1.0.0
449+
openai-v1: tiktoken~=0.6.0
449450
openai-latest: openai
451+
openai-latest: tiktoken~=0.6.0
452+
openai-without-tiktoken: openai
450453

451454
# OpenTelemetry (OTel)
452455
opentelemetry: opentelemetry-distro

0 commit comments

Comments
 (0)