3131 follow the GenAI semantic conventions.
3232"""
3333
34- import json
3534from contextlib import contextmanager
36- from dataclasses import asdict , dataclass , field
37- from typing import Any , Dict , List , Optional
35+ from dataclasses import dataclass , field
36+ from typing import Dict , List , Optional
3837from uuid import UUID
3938
4039from opentelemetry import trace
4140from opentelemetry .semconv ._incubating .attributes import (
4241 gen_ai_attributes as GenAI ,
4342)
44- from opentelemetry .semconv .attributes import (
45- error_attributes as ErrorAttributes ,
46- )
4743from opentelemetry .trace import (
4844 Span ,
4945 SpanKind ,
5046 Tracer ,
5147 set_span_in_context ,
5248 use_span ,
5349)
54- from opentelemetry .trace .status import Status , StatusCode
55- from opentelemetry .util .genai .utils import (
56- ContentCapturingMode ,
57- get_content_capturing_mode ,
58- is_experimental_mode ,
59- )
60- from opentelemetry .util .types import AttributeValue
6150
62- from .types import Error , InputMessage , LLMInvocation , OutputMessage
51+ from .span_utils import (
52+ _apply_error_attributes ,
53+ _apply_finish_attributes ,
54+ )
55+ from .types import Error , LLMInvocation
6356
6457
6558@dataclass
@@ -68,103 +61,6 @@ class _SpanState:
6861 children : List [UUID ] = field (default_factory = list )
6962
7063
71- def _apply_common_span_attributes (
72- span : Span , invocation : LLMInvocation
73- ) -> None :
74- """Apply attributes shared by finish() and error() and compute metrics.
75-
76- Returns (genai_attributes) for use with metrics.
77- """
78- request_model = invocation .attributes .get ("request_model" )
79- provider = invocation .attributes .get ("provider" )
80-
81- span .set_attribute (
82- GenAI .GEN_AI_OPERATION_NAME , GenAI .GenAiOperationNameValues .CHAT .value
83- )
84- if request_model :
85- span .set_attribute (GenAI .GEN_AI_REQUEST_MODEL , request_model )
86- if provider is not None :
87- # TODO: clean provider name to match GenAiProviderNameValues?
88- span .set_attribute (GenAI .GEN_AI_PROVIDER_NAME , provider )
89-
90- finish_reasons : List [str ] = []
91- for gen in invocation .chat_generations :
92- finish_reasons .append (gen .finish_reason )
93- if finish_reasons :
94- span .set_attribute (
95- GenAI .GEN_AI_RESPONSE_FINISH_REASONS , finish_reasons
96- )
97-
98- response_model = invocation .attributes .get ("response_model_name" )
99- response_id = invocation .attributes .get ("response_id" )
100- prompt_tokens = invocation .attributes .get ("input_tokens" )
101- completion_tokens = invocation .attributes .get ("output_tokens" )
102- _set_response_and_usage_attributes (
103- span ,
104- response_model ,
105- response_id ,
106- prompt_tokens ,
107- completion_tokens ,
108- )
109-
110-
111- def _set_response_and_usage_attributes (
112- span : Span ,
113- response_model : Optional [str ],
114- response_id : Optional [str ],
115- prompt_tokens : Optional [AttributeValue ],
116- completion_tokens : Optional [AttributeValue ],
117- ) -> None :
118- if response_model is not None :
119- span .set_attribute (GenAI .GEN_AI_RESPONSE_MODEL , response_model )
120- if response_id is not None :
121- span .set_attribute (GenAI .GEN_AI_RESPONSE_ID , response_id )
122- if isinstance (prompt_tokens , (int , float )):
123- span .set_attribute (GenAI .GEN_AI_USAGE_INPUT_TOKENS , prompt_tokens )
124- if isinstance (completion_tokens , (int , float )):
125- span .set_attribute (GenAI .GEN_AI_USAGE_OUTPUT_TOKENS , completion_tokens )
126-
127-
128- def _maybe_set_span_messages (
129- span : Span ,
130- input_messages : List [InputMessage ],
131- output_messages : List [OutputMessage ],
132- ) -> None :
133- if not is_experimental_mode () or get_content_capturing_mode () not in (
134- ContentCapturingMode .SPAN_ONLY ,
135- ContentCapturingMode .SPAN_AND_EVENT ,
136- ):
137- return
138- message_parts : List [Dict [str , Any ]] = [
139- asdict (message ) for message in input_messages
140- ]
141- if message_parts :
142- span .set_attribute ("gen_ai.input.messages" , json .dumps (message_parts ))
143-
144- generation_parts : List [Dict [str , Any ]] = [
145- asdict (generation ) for generation in output_messages
146- ]
147- if generation_parts :
148- span .set_attribute (
149- "gen_ai.output.messages" , json .dumps (generation_parts )
150- )
151-
152-
153- def _apply_finish_attributes (span : Span , invocation : LLMInvocation ) -> None :
154- """Apply attributes/messages common to finish() paths."""
155- _apply_common_span_attributes (span , invocation )
156- _maybe_set_span_messages (
157- span , invocation .messages , invocation .chat_generations
158- )
159-
160-
161- def _apply_error_attributes (span : Span , error : Error ) -> None :
162- """Apply status and error attributes common to error() paths."""
163- span .set_status (Status (StatusCode .ERROR , error .message ))
164- if span .is_recording ():
165- span .set_attribute (ErrorAttributes .ERROR_TYPE , error .type .__qualname__ )
166-
167-
16864class BaseTelemetryGenerator :
16965 """
17066 Abstract base for emitters mapping GenAI types -> OpenTelemetry.
0 commit comments