@@ -35,6 +35,8 @@ async def process_message(self, message: FlowMessage):
3535
3636from typing import Any
3737
38+ from opentelemetry import trace
39+
3840from qtype .interpreter .types import (
3941 ErrorEvent ,
4042 ReasoningStreamDeltaEvent ,
@@ -54,6 +56,24 @@ async def process_message(self, message: FlowMessage):
5456from qtype .semantic .model import Step
5557
5658
59+ def get_current_telemetry_metadata () -> dict [str , Any ]:
60+ """
61+ Get current OpenTelemetry span context as metadata dict.
62+
63+ Returns:
64+ Dictionary with span_id and trace_id if span is recording,
65+ empty dict otherwise
66+ """
67+ span = trace .get_current_span ()
68+ if span and span .is_recording ():
69+ ctx = span .get_span_context ()
70+ return {
71+ "span_id" : format (ctx .span_id , "016x" ),
72+ "trace_id" : format (ctx .trace_id , "032x" ),
73+ }
74+ return {}
75+
76+
5777class TextStreamContext :
5878 """
5979 Async context manager for text streaming.
@@ -83,7 +103,11 @@ async def __aenter__(self) -> TextStreamContext:
83103 """Emit TextStreamStartEvent when entering context."""
84104 if self .on_stream_event :
85105 await self .on_stream_event (
86- TextStreamStartEvent (step = self .step , stream_id = self .stream_id )
106+ TextStreamStartEvent (
107+ step = self .step ,
108+ stream_id = self .stream_id ,
109+ metadata = get_current_telemetry_metadata (),
110+ )
87111 )
88112 return self
89113
@@ -96,7 +120,11 @@ async def __aexit__(
96120 """Emit TextStreamEndEvent when exiting context."""
97121 if self .on_stream_event :
98122 await self .on_stream_event (
99- TextStreamEndEvent (step = self .step , stream_id = self .stream_id )
123+ TextStreamEndEvent (
124+ step = self .step ,
125+ stream_id = self .stream_id ,
126+ metadata = get_current_telemetry_metadata (),
127+ )
100128 )
101129 return False
102130
@@ -113,6 +141,7 @@ async def delta(self, text: str) -> None:
113141 step = self .step ,
114142 stream_id = self .stream_id ,
115143 delta = text ,
144+ metadata = get_current_telemetry_metadata (),
116145 )
117146 )
118147
@@ -158,7 +187,9 @@ async def __aexit__(
158187 if self ._started and self .on_stream_event :
159188 await self .on_stream_event (
160189 ReasoningStreamEndEvent (
161- step = self .step , stream_id = self .stream_id
190+ step = self .step ,
191+ stream_id = self .stream_id ,
192+ metadata = get_current_telemetry_metadata (),
162193 )
163194 )
164195 return False
@@ -173,11 +204,15 @@ async def delta(self, text: str) -> None:
173204 text: The incremental reasoning content to append to the stream
174205 """
175206 if self .on_stream_event :
207+ metadata = get_current_telemetry_metadata ()
208+
176209 # Emit start event on first delta
177210 if not self ._started :
178211 await self .on_stream_event (
179212 ReasoningStreamStartEvent (
180- step = self .step , stream_id = self .stream_id
213+ step = self .step ,
214+ stream_id = self .stream_id ,
215+ metadata = metadata ,
181216 )
182217 )
183218 self ._started = True
@@ -187,6 +222,7 @@ async def delta(self, text: str) -> None:
187222 step = self .step ,
188223 stream_id = self .stream_id ,
189224 delta = text ,
225+ metadata = metadata ,
190226 )
191227 )
192228
@@ -218,7 +254,11 @@ def __init__(
218254 async def __aenter__ (self ) -> StepBoundaryContext :
219255 """Emit StepStartEvent when entering context."""
220256 if self .on_stream_event :
221- await self .on_stream_event (StepStartEvent (step = self .step ))
257+ await self .on_stream_event (
258+ StepStartEvent (
259+ step = self .step , metadata = get_current_telemetry_metadata ()
260+ )
261+ )
222262 return self
223263
224264 async def __aexit__ (
@@ -229,7 +269,11 @@ async def __aexit__(
229269 ) -> bool :
230270 """Emit StepEndEvent when exiting context."""
231271 if self .on_stream_event :
232- await self .on_stream_event (StepEndEvent (step = self .step ))
272+ await self .on_stream_event (
273+ StepEndEvent (
274+ step = self .step , metadata = get_current_telemetry_metadata ()
275+ )
276+ )
233277 return False
234278
235279
@@ -277,6 +321,7 @@ async def __aenter__(self) -> ToolExecutionContext:
277321 tool_call_id = self .tool_call_id ,
278322 tool_name = self .tool_name ,
279323 tool_input = self .tool_input ,
324+ metadata = get_current_telemetry_metadata (),
280325 )
281326 )
282327 return self
@@ -299,6 +344,7 @@ async def __aexit__(
299344 step = self .step ,
300345 tool_call_id = self .tool_call_id ,
301346 error_message = str (exc_val ),
347+ metadata = get_current_telemetry_metadata (),
302348 )
303349 )
304350 self ._completed = True
@@ -319,6 +365,7 @@ async def complete(self, output: Any) -> None:
319365 step = self .step ,
320366 tool_call_id = self .tool_call_id ,
321367 tool_output = output ,
368+ metadata = get_current_telemetry_metadata (),
322369 )
323370 )
324371 self ._completed = True
@@ -338,6 +385,7 @@ async def error(self, error_message: str) -> None:
338385 step = self .step ,
339386 tool_call_id = self .tool_call_id ,
340387 error_message = error_message ,
388+ metadata = get_current_telemetry_metadata (),
341389 )
342390 )
343391 self ._completed = True
@@ -387,6 +435,18 @@ def __init__(
387435 self .step = step
388436 self .on_stream_event = on_stream_event
389437
438+ def _get_span_context (self ) -> tuple [str | None , str | None ]:
439+ """
440+ Get current OpenTelemetry span context.
441+
442+ Returns:
443+ Tuple of (span_id, trace_id) as hex strings, or (None, None)
444+
445+ Deprecated: Use get_current_telemetry_metadata() instead.
446+ """
447+ metadata = get_current_telemetry_metadata ()
448+ return metadata .get ("span_id" ), metadata .get ("trace_id" )
449+
390450 def text_stream (self , stream_id : str ) -> TextStreamContext :
391451 """
392452 Create a context manager for text streaming.
@@ -459,7 +519,11 @@ async def status(self, message: str) -> None:
459519 """
460520 if self .on_stream_event :
461521 await self .on_stream_event (
462- StatusEvent (step = self .step , message = message )
522+ StatusEvent (
523+ step = self .step ,
524+ message = message ,
525+ metadata = get_current_telemetry_metadata (),
526+ )
463527 )
464528
465529 async def error (self , error_message : str ) -> None :
@@ -471,5 +535,9 @@ async def error(self, error_message: str) -> None:
471535 """
472536 if self .on_stream_event :
473537 await self .on_stream_event (
474- ErrorEvent (step = self .step , error_message = error_message )
538+ ErrorEvent (
539+ step = self .step ,
540+ error_message = error_message ,
541+ metadata = get_current_telemetry_metadata (),
542+ )
475543 )
0 commit comments