Skip to content

Commit a6b4f2e

Browse files
feat(openai): Set system instruction attribute for Responses API (#5376)
Set the system instruction attribute on `gen_ai.chat` spans generated when using the Responses API with the `OpenAIIntegration`. Extract instructions from string input, content strings and parts-style content lists.
1 parent 6611957 commit a6b4f2e

File tree

2 files changed

+772
-25
lines changed

2 files changed

+772
-25
lines changed

sentry_sdk/integrations/openai.py

Lines changed: 89 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,24 @@ def _get_system_instructions_completions(
215215
]
216216

217217

218+
def _is_system_instruction_responses(message: "ResponseInputItemParam") -> bool:
219+
if not isinstance(message, dict) or not message.get("role") == "system":
220+
return False
221+
222+
return "type" not in message or message["type"] == "message"
223+
224+
225+
def _get_system_instructions_responses(
226+
messages: "Union[str, ResponseInputParam]",
227+
) -> "list[ResponseInputItemParam]":
228+
if not isinstance(messages, list):
229+
return []
230+
231+
return [
232+
message for message in messages if _is_system_instruction_responses(message)
233+
]
234+
235+
218236
def _transform_system_instructions(
219237
system_instructions: "list[ChatCompletionSystemMessageParam]",
220238
) -> "list[TextPart]":
@@ -289,17 +307,80 @@ def _set_responses_api_input_data(
289307
kwargs: "dict[str, Any]",
290308
integration: "OpenAIIntegration",
291309
) -> None:
292-
messages: "Optional[Union[ResponseInputParam, list[str]]]" = _get_input_messages(
293-
kwargs
294-
)
310+
explicit_instructions: "Union[Optional[str], Omit]" = kwargs.get("instructions")
311+
messages: "Optional[Union[str, ResponseInputParam]]" = kwargs.get("input")
312+
313+
if not should_send_default_pii() or not integration.include_prompts:
314+
set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "responses")
315+
_commmon_set_input_data(span, kwargs)
316+
return
295317

296318
if (
297-
messages is not None
298-
and len(messages) > 0
299-
and should_send_default_pii()
300-
and integration.include_prompts
319+
messages is None
320+
and explicit_instructions is not None
321+
and _is_given(explicit_instructions)
301322
):
302-
normalized_messages = normalize_message_roles(messages) # type: ignore
323+
set_data_normalized(
324+
span,
325+
SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS,
326+
[
327+
{
328+
"type": "text",
329+
"content": explicit_instructions,
330+
}
331+
],
332+
unpack=False,
333+
)
334+
335+
set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "responses")
336+
_commmon_set_input_data(span, kwargs)
337+
return
338+
339+
if messages is None:
340+
set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "responses")
341+
_commmon_set_input_data(span, kwargs)
342+
return
343+
344+
instructions_text_parts: "list[TextPart]" = []
345+
if explicit_instructions is not None and _is_given(explicit_instructions):
346+
instructions_text_parts.append(
347+
{
348+
"type": "text",
349+
"content": explicit_instructions,
350+
}
351+
)
352+
353+
system_instructions = _get_system_instructions_responses(messages)
354+
# Deliberate use of function accepting completions API type because
355+
# of shared structure FOR THIS PURPOSE ONLY.
356+
instructions_text_parts += _transform_system_instructions(system_instructions)
357+
358+
if len(instructions_text_parts) > 0:
359+
set_data_normalized(
360+
span,
361+
SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS,
362+
instructions_text_parts,
363+
unpack=False,
364+
)
365+
366+
if isinstance(messages, str):
367+
normalized_messages = normalize_message_roles([messages]) # type: ignore
368+
scope = sentry_sdk.get_current_scope()
369+
messages_data = truncate_and_annotate_messages(normalized_messages, span, scope)
370+
if messages_data is not None:
371+
set_data_normalized(
372+
span, SPANDATA.GEN_AI_REQUEST_MESSAGES, messages_data, unpack=False
373+
)
374+
375+
set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "responses")
376+
_commmon_set_input_data(span, kwargs)
377+
return
378+
379+
non_system_messages = [
380+
message for message in messages if not _is_system_instruction_responses(message)
381+
]
382+
if len(non_system_messages) > 0:
383+
normalized_messages = normalize_message_roles(non_system_messages)
303384
scope = sentry_sdk.get_current_scope()
304385
messages_data = truncate_and_annotate_messages(normalized_messages, span, scope)
305386
if messages_data is not None:

0 commit comments

Comments
 (0)