Skip to content

Commit 13a0834

Browse files
nagkumar91Copilot
andcommitted
fix(openai-agents): capture system instructions from Response object (open-telemetry#4188)
When using the Responses API, instructions are passed separately and stored on response.instructions rather than as role="system" input messages. This adds a fallback to read response.instructions for gen_ai.system_instructions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 5560324 commit 13a0834

3 files changed

Lines changed: 73 additions & 0 deletions

File tree

instrumentation-genai/opentelemetry-instrumentation-openai-agents-v2/src/opentelemetry/instrumentation/openai_agents/span_processor.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,6 +1064,13 @@ def _build_content_payload(self, span: Span[Any]) -> ContentPayload:
10641064
sys_instr = self._collect_system_instructions(span_input)
10651065
if sys_instr:
10661066
payload.system_instructions = sys_instr
1067+
if capture_system and not payload.system_instructions:
1068+
response = getattr(span_data, "response", None)
1069+
instructions = getattr(response, "instructions", None)
1070+
if isinstance(instructions, str) and instructions:
1071+
payload.system_instructions = [
1072+
{"type": "text", "content": instructions}
1073+
]
10671074
if capture_messages:
10681075
normalized_out = self._normalize_output_messages_to_role_parts(
10691076
span_data

instrumentation-genai/opentelemetry-instrumentation-openai-agents-v2/tests/stubs/agents/tracing/__init__.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"GenerationSpanData",
3030
"FunctionSpanData",
3131
"ResponseSpanData",
32+
"MCPListToolsSpanData",
3233
]
3334

3435

@@ -74,12 +75,26 @@ def type(self) -> str:
7475
@dataclass
7576
class ResponseSpanData:
7677
response: Any = None
78+
input: Sequence[Mapping[str, Any]] | None = None
7779

7880
@property
7981
def type(self) -> str:
8082
return SPAN_TYPE_RESPONSE
8183

8284

85+
SPAN_TYPE_MCP_LIST_TOOLS = "mcp_tools"
86+
87+
88+
@dataclass
89+
class MCPListToolsSpanData:
90+
server: str | None = None
91+
result: list[str] | None = None
92+
93+
@property
94+
def type(self) -> str:
95+
return "mcp_tools"
96+
97+
8398
class _ProcessorFanout(TracingProcessor):
8499
def __init__(self) -> None:
85100
self._processors: list[TracingProcessor] = []

instrumentation-genai/opentelemetry-instrumentation-openai-agents-v2/tests/test_z_span_processor_unit.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,57 @@ def test_span_lifecycle_and_shutdown(processor_setup):
508508
)
509509

510510

511+
def test_response_span_instructions_fallback(processor_setup):
512+
"""Instructions on Response object populate gen_ai.system_instructions."""
513+
processor, exporter = processor_setup
514+
515+
class _Usage:
516+
def __init__(self):
517+
self.input_tokens = None
518+
self.prompt_tokens = 5
519+
self.output_tokens = None
520+
self.completion_tokens = 3
521+
self.total_tokens = 8
522+
523+
class _Response:
524+
def __init__(self):
525+
self.id = "resp-instr"
526+
self.model = "gpt-4o"
527+
self.usage = _Usage()
528+
self.output = [{"finish_reason": "stop"}]
529+
self.instructions = "You are a helpful assistant."
530+
531+
trace = FakeTrace(
532+
name="instr-trace",
533+
trace_id="trace-instr",
534+
started_at="2024-01-01T00:00:00Z",
535+
ended_at="2024-01-01T00:00:02Z",
536+
)
537+
processor.on_trace_start(trace)
538+
539+
response_data = ResponseSpanData(response=_Response())
540+
# No system messages in input – instructions only on response object
541+
response_data.input = [{"role": "user", "content": "Hello"}]
542+
span = FakeSpan(
543+
trace_id="trace-instr",
544+
span_id="span-instr",
545+
span_data=response_data,
546+
started_at="2024-01-01T00:00:00Z",
547+
ended_at="2024-01-01T00:00:01Z",
548+
)
549+
processor.on_span_start(span)
550+
processor.on_span_end(span)
551+
processor.on_trace_end(trace)
552+
553+
finished = exporter.get_finished_spans()
554+
response_span = next(s for s in finished if s.name.startswith("chat"))
555+
sys_instr = json.loads(
556+
response_span.attributes[sp.GEN_AI_SYSTEM_INSTRUCTIONS]
557+
)
558+
assert len(sys_instr) == 1
559+
assert sys_instr[0]["content"] == "You are a helpful assistant."
560+
561+
511562
def test_chat_span_renamed_with_model(processor_setup):
512563
processor, exporter = processor_setup
513564

0 commit comments

Comments
 (0)