Skip to content

Commit 7fc7d02

Browse files
committed
test(openai-agents): cover message and tool capture modes
1 parent 57b2772 commit 7fc7d02

File tree

1 file changed

+168
-6
lines changed
  • instrumentation-genai/opentelemetry-instrumentation-openai-agents/tests

1 file changed

+168
-6
lines changed

instrumentation-genai/opentelemetry-instrumentation-openai-agents/tests/test_tracer.py

Lines changed: 168 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import json
34
import sys
45
from pathlib import Path
56

@@ -22,10 +23,16 @@
2223
OpenAIAgentsInstrumentor,
2324
)
2425
from opentelemetry.sdk.trace import TracerProvider # noqa: E402
25-
from opentelemetry.sdk.trace.export import ( # noqa: E402
26-
InMemorySpanExporter,
27-
SimpleSpanProcessor,
28-
)
26+
from opentelemetry.sdk.trace.export import SimpleSpanProcessor # noqa: E402
27+
28+
try: # pragma: no cover - compatibility for older SDK versions
29+
from opentelemetry.sdk.trace.export import ( # type: ignore[attr-defined] # noqa: E402
30+
InMemorySpanExporter,
31+
)
32+
except ImportError: # pragma: no cover - fallback for newer SDK layout
33+
from opentelemetry.sdk.trace.export.in_memory_span_exporter import ( # noqa: E402
34+
InMemorySpanExporter,
35+
)
2936
from opentelemetry.semconv._incubating.attributes import ( # noqa: E402
3037
gen_ai_attributes as GenAI,
3138
)
@@ -34,15 +41,28 @@
3441
)
3542
from opentelemetry.trace import SpanKind # noqa: E402
3643

44+
GEN_AI_INPUT_MESSAGES = getattr(
45+
GenAI, "GEN_AI_INPUT_MESSAGES", "gen_ai.input.messages"
46+
)
47+
GEN_AI_OUTPUT_MESSAGES = getattr(
48+
GenAI, "GEN_AI_OUTPUT_MESSAGES", "gen_ai.output.messages"
49+
)
50+
GEN_AI_TOOL_CALL_ARGUMENTS = getattr(
51+
GenAI, "GEN_AI_TOOL_CALL_ARGUMENTS", "gen_ai.tool.call.arguments"
52+
)
53+
GEN_AI_TOOL_CALL_RESULT = getattr(
54+
GenAI, "GEN_AI_TOOL_CALL_RESULT", "gen_ai.tool.call.result"
55+
)
3756

38-
def _instrument_with_provider():
57+
58+
def _instrument_with_provider(**instrument_kwargs):
3959
set_trace_processors([])
4060
provider = TracerProvider()
4161
exporter = InMemorySpanExporter()
4262
provider.add_span_processor(SimpleSpanProcessor(exporter))
4363

4464
instrumentor = OpenAIAgentsInstrumentor()
45-
instrumentor.instrument(tracer_provider=provider)
65+
instrumentor.instrument(tracer_provider=provider, **instrument_kwargs)
4666

4767
return instrumentor, exporter
4868

@@ -108,3 +128,145 @@ def test_function_span_records_tool_attributes():
108128
finally:
109129
instrumentor.uninstrument()
110130
exporter.clear()
131+
132+
133+
def test_generation_span_captures_messages_by_default():
134+
instrumentor, exporter = _instrument_with_provider()
135+
136+
try:
137+
with trace("workflow"):
138+
with generation_span(
139+
input=[{"role": "user", "content": "hi"}],
140+
output=[{"role": "assistant", "content": "hello"}],
141+
model="gpt-4o-mini",
142+
):
143+
pass
144+
145+
spans = exporter.get_finished_spans()
146+
client_span = next(
147+
span for span in spans if span.kind is SpanKind.CLIENT
148+
)
149+
150+
prompt = json.loads(client_span.attributes[GEN_AI_INPUT_MESSAGES])
151+
completion = json.loads(client_span.attributes[GEN_AI_OUTPUT_MESSAGES])
152+
153+
assert prompt == [
154+
{
155+
"role": "user",
156+
"parts": [{"type": "text", "content": "hi"}],
157+
}
158+
]
159+
assert completion == [
160+
{
161+
"role": "assistant",
162+
"parts": [{"type": "text", "content": "hello"}],
163+
}
164+
]
165+
166+
event_names = {event.name for event in client_span.events}
167+
assert "gen_ai.input" in event_names
168+
assert "gen_ai.output" in event_names
169+
170+
input_event = next(
171+
event
172+
for event in client_span.events
173+
if event.name == "gen_ai.input"
174+
)
175+
output_event = next(
176+
event
177+
for event in client_span.events
178+
if event.name == "gen_ai.output"
179+
)
180+
181+
assert (
182+
json.loads(input_event.attributes[GEN_AI_INPUT_MESSAGES]) == prompt
183+
)
184+
assert (
185+
json.loads(output_event.attributes[GEN_AI_OUTPUT_MESSAGES])
186+
== completion
187+
)
188+
finally:
189+
instrumentor.uninstrument()
190+
exporter.clear()
191+
192+
193+
def test_capture_mode_can_be_disabled():
194+
instrumentor, exporter = _instrument_with_provider(
195+
capture_message_content="no_content"
196+
)
197+
198+
try:
199+
with trace("workflow"):
200+
with generation_span(
201+
input=[{"role": "user", "content": "hi"}],
202+
output=[{"role": "assistant", "content": "hello"}],
203+
model="gpt-4o-mini",
204+
):
205+
pass
206+
207+
spans = exporter.get_finished_spans()
208+
client_span = next(
209+
span for span in spans if span.kind is SpanKind.CLIENT
210+
)
211+
212+
assert GEN_AI_INPUT_MESSAGES not in client_span.attributes
213+
assert GEN_AI_OUTPUT_MESSAGES not in client_span.attributes
214+
for event in client_span.events:
215+
assert GEN_AI_INPUT_MESSAGES not in event.attributes
216+
assert GEN_AI_OUTPUT_MESSAGES not in event.attributes
217+
finally:
218+
instrumentor.uninstrument()
219+
exporter.clear()
220+
221+
222+
def test_function_span_captures_tool_payload():
223+
instrumentor, exporter = _instrument_with_provider()
224+
225+
try:
226+
with trace("workflow"):
227+
with function_span(
228+
name="fetch_weather",
229+
input={"city": "Paris"},
230+
output={"forecast": "sunny"},
231+
):
232+
pass
233+
234+
spans = exporter.get_finished_spans()
235+
tool_span = next(
236+
span for span in spans if span.kind is SpanKind.INTERNAL
237+
)
238+
239+
arguments = json.loads(
240+
tool_span.attributes[GEN_AI_TOOL_CALL_ARGUMENTS]
241+
)
242+
result = json.loads(tool_span.attributes[GEN_AI_TOOL_CALL_RESULT])
243+
244+
assert arguments == {"city": "Paris"}
245+
assert result == {"forecast": "sunny"}
246+
247+
event_names = {event.name for event in tool_span.events}
248+
assert "gen_ai.tool.arguments" in event_names
249+
assert "gen_ai.tool.result" in event_names
250+
251+
args_event = next(
252+
event
253+
for event in tool_span.events
254+
if event.name == "gen_ai.tool.arguments"
255+
)
256+
result_event = next(
257+
event
258+
for event in tool_span.events
259+
if event.name == "gen_ai.tool.result"
260+
)
261+
262+
assert (
263+
json.loads(args_event.attributes[GEN_AI_TOOL_CALL_ARGUMENTS])
264+
== arguments
265+
)
266+
assert (
267+
json.loads(result_event.attributes[GEN_AI_TOOL_CALL_RESULT])
268+
== result
269+
)
270+
finally:
271+
instrumentor.uninstrument()
272+
exporter.clear()

0 commit comments

Comments
 (0)