2929 handler = get_telemetry_handler()
3030
3131 # Create an invocation object with your request data
32- # The span and span_scope attributes are set by the TelemetryHandler, and
32+ # The span and context_token attributes are set by the TelemetryHandler, and
3333 # managed by the TelemetryHandler during the lifecycle of the span.
3434
3535 # Use the context manager to manage the lifecycle of an LLM invocation.
6363from contextlib import contextmanager
6464from typing import Iterator , Optional
6565
66+ from opentelemetry import context as otel_context
6667from opentelemetry .semconv ._incubating .attributes import (
6768 gen_ai_attributes as GenAI ,
6869)
7172 SpanKind ,
7273 TracerProvider ,
7374 get_tracer ,
74- use_span ,
75+ set_span_in_context ,
7576)
7677from opentelemetry .util .genai .span_utils import (
7778 _apply_error_attributes ,
7879 _apply_finish_attributes ,
7980)
80- from opentelemetry .util .genai .types import (
81- Error ,
82- LLMInvocation ,
83- )
81+ from opentelemetry .util .genai .types import Error , LLMInvocation
8482from opentelemetry .util .genai .version import __version__
8583
8684
@@ -103,56 +101,41 @@ def start_llm(
103101 invocation : LLMInvocation ,
104102 ) -> LLMInvocation :
105103 """Start an LLM invocation and create a pending span entry."""
106- # Create a span and activate it using the OpenTelemetry helper scope
104+ # Create a span and attach it as current; keep the token to detach later
107105 span = self ._tracer .start_span (
108106 name = f"{ GenAI .GenAiOperationNameValues .CHAT .value } { invocation .request_model } " ,
109107 kind = SpanKind .CLIENT ,
110108 )
111109 invocation .span = span
112- scope = use_span (
113- span ,
114- end_on_exit = False ,
115- record_exception = False ,
116- set_status_on_exception = False ,
110+ invocation .context_token = otel_context .attach (
111+ set_span_in_context (span )
117112 )
118- scope .__enter__ ()
119- invocation .span_scope = scope
120113 return invocation
121114
122115 def stop_llm (self , invocation : LLMInvocation ) -> LLMInvocation : # pylint: disable=no-self-use
123116 """Finalize an LLM invocation successfully and end its span."""
124- if invocation .span_scope is None or invocation .span is None :
117+ if invocation .context_token is None or invocation .span is None :
125118 # TODO: Provide feedback that this invocation was not started
126119 return invocation
127120
128- scope = invocation .span_scope
129- span = invocation .span
130- try :
131- _apply_finish_attributes (span , invocation )
132- finally :
133- scope .__exit__ (None , None , None )
134- span .end ()
135- invocation .span_scope = None
136- invocation .span = None
121+ _apply_finish_attributes (invocation .span , invocation )
122+ # Detach context and end span
123+ otel_context .detach (invocation .context_token )
124+ invocation .span .end ()
137125 return invocation
138126
139127 def fail_llm ( # pylint: disable=no-self-use
140128 self , invocation : LLMInvocation , error : Error
141129 ) -> LLMInvocation :
142130 """Fail an LLM invocation and end its span with error status."""
143- if invocation .span_scope is None or invocation .span is None :
131+ if invocation .context_token is None or invocation .span is None :
144132 # TODO: Provide feedback that this invocation was not started
145133 return invocation
146134
147- scope = invocation .span_scope
148- span = invocation .span
149- try :
150- _apply_error_attributes (span , error )
151- finally :
152- scope .__exit__ (None , None , None )
153- span .end ()
154- invocation .span_scope = None
155- invocation .span = None
135+ _apply_error_attributes (invocation .span , error )
136+ # Detach context and end span
137+ otel_context .detach (invocation .context_token )
138+ invocation .span .end ()
156139 return invocation
157140
158141 @contextmanager
@@ -161,22 +144,21 @@ def llm(
161144 ) -> Iterator [LLMInvocation ]:
162145 """Context manager for LLM invocations.
163146
164- Only set data attributes on the invocation object, do not modify the span or
165- context. Starts the span on entry. On normal exit, finalizes the invocation and
166- ends the span. If an exception occurs inside the context, marks the span as
167- error, ends it, and re-raises the original exception.
147+ Only set data attributes on the invocation object, do not modify the span or context.
148+
149+ Starts the span on entry. On normal exit, finalizes the invocation and ends the span.
150+ If an exception occurs inside the context, marks the span as error, ends it, and
151+ re-raises the original exception.
168152 """
169153 if invocation is None :
170- invocation = LLMInvocation (request_model = "" )
171-
154+ invocation = LLMInvocation (
155+ request_model = "" ,
156+ )
172157 self .start_llm (invocation )
173158 try :
174159 yield invocation
175160 except Exception as exc :
176- self .fail_llm (
177- invocation ,
178- Error (message = str (exc ), type = type (exc )),
179- )
161+ self .fail_llm (invocation , Error (message = str (exc ), type = type (exc )))
180162 raise
181163 self .stop_llm (invocation )
182164
0 commit comments