62
62
from contextlib import contextmanager
63
63
from typing import Any , Iterator , Optional
64
64
65
- from opentelemetry .util .genai .generators import SpanGenerator
65
+ from opentelemetry import context as otel_context
66
+ from opentelemetry import trace
67
+ from opentelemetry .semconv ._incubating .attributes import (
68
+ gen_ai_attributes as GenAI ,
69
+ )
70
+ from opentelemetry .semconv .schemas import Schemas
71
+ from opentelemetry .trace import (
72
+ SpanKind ,
73
+ Tracer ,
74
+ get_tracer ,
75
+ set_span_in_context ,
76
+ )
77
+ from opentelemetry .util .genai .span_utils import (
78
+ _apply_error_attributes ,
79
+ _apply_finish_attributes ,
80
+ )
66
81
from opentelemetry .util .genai .types import Error , LLMInvocation
82
+ from opentelemetry .util .genai .version import __version__
67
83
68
84
69
85
class TelemetryHandler :
@@ -73,28 +89,57 @@ class TelemetryHandler:
73
89
"""
74
90
75
91
def __init__ (self , ** kwargs : Any ):
76
- self ._generator = SpanGenerator (** kwargs )
92
+ tracer_provider = kwargs .get ("tracer_provider" )
93
+ tracer = get_tracer (
94
+ __name__ ,
95
+ __version__ ,
96
+ tracer_provider ,
97
+ schema_url = Schemas .V1_36_0 .value ,
98
+ )
99
+ self ._tracer : Tracer = tracer or trace .get_tracer (__name__ )
77
100
78
101
def start_llm (
79
102
self ,
80
103
invocation : LLMInvocation ,
81
104
) -> LLMInvocation :
82
105
"""Start an LLM invocation and create a pending span entry."""
83
- self ._generator .start (invocation )
106
+ # Create a span and attach it as current; keep the token to detach later
107
+ span = self ._tracer .start_span (
108
+ name = f"{ GenAI .GenAiOperationNameValues .CHAT .value } { invocation .request_model } " ,
109
+ kind = SpanKind .CLIENT ,
110
+ )
111
+ invocation .span = span
112
+ invocation .context_token = otel_context .attach (
113
+ set_span_in_context (span )
114
+ )
84
115
return invocation
85
116
86
- def stop_llm (self , invocation : LLMInvocation ) -> LLMInvocation :
117
+ def stop_llm (self , invocation : LLMInvocation ) -> LLMInvocation : # pylint: disable=no-self-use
87
118
"""Finalize an LLM invocation successfully and end its span."""
88
119
invocation .end_time = time .time ()
89
- self ._generator .finish (invocation )
120
+ if invocation .context_token is None or invocation .span is None :
121
+ # TODO: Provide feedback that this invocation was not started
122
+ return invocation
123
+
124
+ _apply_finish_attributes (invocation .span , invocation )
125
+ # Detach context and end span
126
+ otel_context .detach (invocation .context_token )
127
+ invocation .span .end ()
90
128
return invocation
91
129
92
- def fail_llm (
130
+ def fail_llm ( # pylint: disable=no-self-use
93
131
self , invocation : LLMInvocation , error : Error
94
132
) -> LLMInvocation :
95
133
"""Fail an LLM invocation and end its span with error status."""
96
134
invocation .end_time = time .time ()
97
- self ._generator .error (error , invocation )
135
+ if invocation .context_token is None or invocation .span is None :
136
+ # TODO: Provide feedback that this invocation was not started
137
+ return invocation
138
+
139
+ _apply_error_attributes (invocation .span , error )
140
+ # Detach context and end span
141
+ otel_context .detach (invocation .context_token )
142
+ invocation .span .end ()
98
143
return invocation
99
144
100
145
@contextmanager
0 commit comments