Skip to content

Commit f64bf69

Browse files
committed
merge test files
1 parent a8bb486 commit f64bf69

File tree

3 files changed

+154
-154
lines changed

3 files changed

+154
-154
lines changed

util/opentelemetry-util-genai/src/opentelemetry/util/genai/generators.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,9 @@ def _end_span(self, run_id: UUID):
375375
child_state = self.spans.get(child_id)
376376
if child_state:
377377
child_state.span.end()
378+
# TODO: Clear our cache for child spans
378379
state.span.end()
380+
# TODO: Clear our cache for parent span
379381

380382
def start(self, invocation: LLMInvocation):
381383
if (

util/opentelemetry-util-genai/tests/test_utils.py

Lines changed: 152 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,37 @@
1515
import os
1616
import unittest
1717
from unittest.mock import patch
18+
from uuid import uuid4
1819

20+
from opentelemetry import trace
1921
from opentelemetry.instrumentation._semconv import (
2022
OTEL_SEMCONV_STABILITY_OPT_IN,
2123
_OpenTelemetrySemanticConventionStability,
2224
)
25+
from opentelemetry.sdk._logs import LoggerProvider
26+
from opentelemetry.sdk._logs.export import (
27+
InMemoryLogExporter,
28+
SimpleLogRecordProcessor,
29+
)
30+
from opentelemetry.sdk.trace import TracerProvider
31+
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
32+
from opentelemetry.sdk.trace.export.in_memory_span_exporter import (
33+
InMemorySpanExporter,
34+
)
2335
from opentelemetry.util.genai.environment_variables import (
2436
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT,
2537
)
26-
from opentelemetry.util.genai.types import ContentCapturingMode
38+
from opentelemetry.util.genai.handler import (
39+
TelemetryHandler,
40+
llm_start,
41+
llm_stop,
42+
)
43+
from opentelemetry.util.genai.types import (
44+
ContentCapturingMode,
45+
InputMessage,
46+
OutputMessage,
47+
Text,
48+
)
2749
from opentelemetry.util.genai.utils import get_content_capturing_mode
2850

2951

@@ -81,3 +103,132 @@ def test_get_content_capturing_mode_raises_exception_on_invalid_envvar(
81103
)
82104
self.assertEqual(len(cm.output), 1)
83105
self.assertIn("INVALID_VALUE is not a valid option for ", cm.output[0])
106+
107+
108+
class TestTelemetryHandler(unittest.TestCase):
109+
def setUp(self):
110+
# Set up in-memory span exporter to capture spans
111+
self.span_exporter = InMemorySpanExporter()
112+
tracer_provider = TracerProvider()
113+
tracer_provider.add_span_processor(
114+
SimpleSpanProcessor(self.span_exporter)
115+
)
116+
# Set the tracer provider
117+
trace.set_tracer_provider(tracer_provider)
118+
119+
def tearDown(self):
120+
# Cleanup
121+
self.span_exporter.clear()
122+
# Reset to default tracer provider
123+
trace.set_tracer_provider(trace.NoOpTracerProvider())
124+
125+
def test_llm_start_and_stop_creates_span(self):
126+
run_id = uuid4()
127+
message = InputMessage(
128+
role="Human", parts=[Text(content="hello world")]
129+
)
130+
chat_generation = OutputMessage(
131+
role="AI", parts=[Text(content="hello back")], finish_reason="stop"
132+
)
133+
134+
# Start and stop LLM invocation
135+
llm_start(
136+
[message], run_id=run_id, custom_attr="value", system="test-system"
137+
)
138+
invocation = llm_stop(
139+
run_id, chat_generations=[chat_generation], extra="info"
140+
)
141+
142+
# Get the spans that were created
143+
spans = self.span_exporter.get_finished_spans()
144+
145+
# Verify span was created
146+
assert len(spans) == 1
147+
span = spans[0]
148+
149+
# Verify span properties
150+
assert span.name == "test-system.chat"
151+
assert span.kind == trace.SpanKind.CLIENT
152+
153+
# Verify span attributes
154+
assert span.attributes is not None
155+
span_attrs = span.attributes
156+
assert span_attrs.get("gen_ai.operation.name") == "chat"
157+
assert span_attrs.get("gen_ai.system") == "test-system"
158+
# Add more attribute checks as needed
159+
160+
# Verify span timing
161+
assert span.start_time is not None
162+
assert span.end_time is not None
163+
assert span.end_time > span.start_time
164+
165+
# Verify invocation data
166+
assert invocation.run_id == run_id
167+
assert invocation.attributes.get("custom_attr") == "value"
168+
assert invocation.attributes.get("extra") == "info"
169+
170+
def test_structured_logs_emitted(self):
171+
# Configure in-memory log exporter and provider
172+
log_exporter = InMemoryLogExporter()
173+
logger_provider = LoggerProvider()
174+
logger_provider.add_log_record_processor(
175+
SimpleLogRecordProcessor(log_exporter)
176+
)
177+
178+
# Build a dedicated TelemetryHandler using our logger provider
179+
handler = TelemetryHandler(
180+
emitter_type_full=True,
181+
logger_provider=logger_provider,
182+
)
183+
184+
run_id = uuid4()
185+
message = InputMessage(
186+
role="user", parts=[Text(content="hello world")]
187+
)
188+
generation = OutputMessage(
189+
role="assistant",
190+
parts=[Text(content="hello back")],
191+
finish_reason="stop",
192+
)
193+
194+
# Start and stop via the handler (emits logs at start and finish)
195+
handler.start_llm(
196+
[message], run_id=run_id, system="test-system", framework="pytest"
197+
)
198+
handler.stop_llm(run_id, chat_generations=[generation])
199+
200+
# Collect logs
201+
logs = log_exporter.get_finished_logs()
202+
# Expect one input-detail log and one choice log
203+
assert len(logs) == 2
204+
records = [ld.log_record for ld in logs]
205+
206+
# Assert the first record contains structured details for the input message
207+
# Use event_name which is explicitly set by the generator
208+
records_by_event = {rec.event_name: rec for rec in records}
209+
210+
input_rec = records_by_event[
211+
"gen_ai.client.inference.operation.details"
212+
]
213+
assert input_rec.attributes is not None
214+
input_attrs = input_rec.attributes
215+
assert input_attrs.get("gen_ai.provider.name") == "test-system"
216+
assert input_attrs.get("gen_ai.framework") == "pytest"
217+
assert input_rec.body == {
218+
"role": "user",
219+
"parts": [{"content": "hello world", "type": "text"}],
220+
}
221+
222+
choice_rec = records_by_event["gen_ai.choice"]
223+
assert choice_rec.attributes is not None
224+
choice_attrs = choice_rec.attributes
225+
assert choice_attrs.get("gen_ai.provider.name") == "test-system"
226+
assert choice_attrs.get("gen_ai.framework") == "pytest"
227+
assert choice_rec.body == {
228+
"index": 0,
229+
"finish_reason": "stop",
230+
"message": {
231+
"type": "assistant",
232+
"content": "hello back",
233+
},
234+
}

util/opentelemetry-util-genai/tests/test_utils_old.py

Lines changed: 0 additions & 153 deletions
This file was deleted.

0 commit comments

Comments
 (0)