Skip to content

Commit 594dce5

Browse files
fix(openai): Avoid consuming iterables passed to the Completions API (#5489)
Avoid consuming single-use iterators passed to the Completions API. All iterables that are not dictionaries or strings are transformed to lists in the internals of `openai` before they are passed to an API call.
1 parent d52e511 commit 594dce5

File tree

2 files changed

+77
-8
lines changed

2 files changed

+77
-8
lines changed

sentry_sdk/integrations/openai.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -357,13 +357,6 @@ def _set_completions_api_input_data(
357357
_commmon_set_input_data(span, kwargs)
358358
return
359359

360-
system_instructions = _get_system_instructions_completions(messages)
361-
if len(system_instructions) > 0:
362-
span.set_data(
363-
SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS,
364-
json.dumps(_transform_system_instructions(system_instructions)),
365-
)
366-
367360
if isinstance(messages, str):
368361
normalized_messages = normalize_message_roles([messages]) # type: ignore
369362
scope = sentry_sdk.get_current_scope()
@@ -376,13 +369,29 @@ def _set_completions_api_input_data(
376369
_commmon_set_input_data(span, kwargs)
377370
return
378371

372+
# dict special case following https://github.com/openai/openai-python/blob/3e0c05b84a2056870abf3bd6a5e7849020209cc3/src/openai/_utils/_transform.py#L194-L197
373+
if not isinstance(messages, Iterable) or isinstance(messages, dict):
374+
set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "chat")
375+
_commmon_set_input_data(span, kwargs)
376+
return
377+
378+
messages = list(messages)
379+
kwargs["messages"] = messages
380+
381+
system_instructions = _get_system_instructions_completions(messages)
382+
if len(system_instructions) > 0:
383+
span.set_data(
384+
SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS,
385+
json.dumps(_transform_system_instructions(system_instructions)),
386+
)
387+
379388
non_system_messages = [
380389
message
381390
for message in messages
382391
if not _is_system_instruction_completions(message)
383392
]
384393
if len(non_system_messages) > 0:
385-
normalized_messages = normalize_message_roles(non_system_messages) # type: ignore
394+
normalized_messages = normalize_message_roles(non_system_messages)
386395
scope = sentry_sdk.get_current_scope()
387396
messages_data = truncate_and_annotate_messages(normalized_messages, span, scope)
388397
if messages_data is not None:

tests/integrations/openai/test_openai.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,21 @@ def test_nonstreaming_chat_completion_no_prompts(
204204
],
205205
id="parts",
206206
),
207+
pytest.param(
208+
iter(
209+
[
210+
{
211+
"role": "system",
212+
"content": [
213+
{"type": "text", "text": "You are a helpful assistant."},
214+
{"type": "text", "text": "Be concise and clear."},
215+
],
216+
},
217+
{"role": "user", "content": "hello"},
218+
]
219+
),
220+
id="iterator",
221+
),
207222
],
208223
)
209224
def test_nonstreaming_chat_completion(sentry_init, capture_events, messages, request):
@@ -335,6 +350,21 @@ async def test_nonstreaming_chat_completion_async_no_prompts(
335350
],
336351
id="parts",
337352
),
353+
pytest.param(
354+
iter(
355+
[
356+
{
357+
"role": "system",
358+
"content": [
359+
{"type": "text", "text": "You are a helpful assistant."},
360+
{"type": "text", "text": "Be concise and clear."},
361+
],
362+
},
363+
{"role": "user", "content": "hello"},
364+
]
365+
),
366+
id="iterator",
367+
),
338368
],
339369
)
340370
async def test_nonstreaming_chat_completion_async(
@@ -521,6 +551,21 @@ def test_streaming_chat_completion_no_prompts(
521551
],
522552
id="parts",
523553
),
554+
pytest.param(
555+
iter(
556+
[
557+
{
558+
"role": "system",
559+
"content": [
560+
{"type": "text", "text": "You are a helpful assistant."},
561+
{"type": "text", "text": "Be concise and clear."},
562+
],
563+
},
564+
{"role": "user", "content": "hello"},
565+
]
566+
),
567+
id="iterator",
568+
),
524569
],
525570
)
526571
def test_streaming_chat_completion(sentry_init, capture_events, messages, request):
@@ -757,6 +802,21 @@ async def test_streaming_chat_completion_async_no_prompts(
757802
],
758803
id="parts",
759804
),
805+
pytest.param(
806+
iter(
807+
[
808+
{
809+
"role": "system",
810+
"content": [
811+
{"type": "text", "text": "You are a helpful assistant."},
812+
{"type": "text", "text": "Be concise and clear."},
813+
],
814+
},
815+
{"role": "user", "content": "hello"},
816+
]
817+
),
818+
id="iterator",
819+
),
760820
],
761821
)
762822
async def test_streaming_chat_completion_async(

0 commit comments

Comments
 (0)