openlit 1.33.21__py3-none-any.whl → 1.33.23__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openlit/instrumentation/google_ai_studio/__init__.py +21 -6
- openlit/instrumentation/google_ai_studio/async_google_ai_studio.py +123 -191
- openlit/instrumentation/google_ai_studio/google_ai_studio.py +123 -191
- openlit/instrumentation/google_ai_studio/utils.py +249 -0
- openlit/instrumentation/transformers/transformers.py +1 -2
- openlit/instrumentation/transformers/utils.py +3 -3
- openlit/otel/metrics.py +5 -0
- openlit/semcov/__init__.py +2 -0
- {openlit-1.33.21.dist-info → openlit-1.33.23.dist-info}/METADATA +1 -1
- {openlit-1.33.21.dist-info → openlit-1.33.23.dist-info}/RECORD +12 -11
- {openlit-1.33.21.dist-info → openlit-1.33.23.dist-info}/LICENSE +0 -0
- {openlit-1.33.21.dist-info → openlit-1.33.23.dist-info}/WHEEL +0 -0
@@ -7,11 +7,11 @@ from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
|
|
7
7
|
from wrapt import wrap_function_wrapper
|
8
8
|
|
9
9
|
from openlit.instrumentation.google_ai_studio.google_ai_studio import (
|
10
|
-
generate
|
10
|
+
generate, generate_stream
|
11
11
|
)
|
12
12
|
|
13
13
|
from openlit.instrumentation.google_ai_studio.async_google_ai_studio import (
|
14
|
-
async_generate
|
14
|
+
async_generate, async_generate_stream
|
15
15
|
)
|
16
16
|
|
17
17
|
_instruments = ("google-genai >= 1.3.0",)
|
@@ -39,16 +39,31 @@ class GoogleAIStudioInstrumentor(BaseInstrumentor):
|
|
39
39
|
"google.genai.models",
|
40
40
|
"Models.generate_content",
|
41
41
|
generate(version, environment, application_name,
|
42
|
-
|
42
|
+
tracer, pricing_info, capture_message_content, metrics, disable_metrics),
|
43
|
+
)
|
44
|
+
|
45
|
+
# sync stream generate
|
46
|
+
wrap_function_wrapper(
|
47
|
+
"google.genai.models",
|
48
|
+
"Models.generate_content_stream",
|
49
|
+
generate_stream(version, environment, application_name,
|
50
|
+
tracer, pricing_info, capture_message_content, metrics, disable_metrics),
|
43
51
|
)
|
44
52
|
|
45
53
|
# async generate
|
46
54
|
wrap_function_wrapper(
|
47
55
|
"google.genai.models",
|
48
56
|
"AsyncModels.generate_content",
|
49
|
-
async_generate(version, environment,
|
50
|
-
|
51
|
-
|
57
|
+
async_generate(version, environment, application_name,
|
58
|
+
tracer, pricing_info, capture_message_content, metrics, disable_metrics),
|
59
|
+
)
|
60
|
+
|
61
|
+
# async stream generate
|
62
|
+
wrap_function_wrapper(
|
63
|
+
"google.genai.models",
|
64
|
+
"AsyncModels.generate_content_stream",
|
65
|
+
async_generate_stream(version, environment, application_name,
|
66
|
+
tracer, pricing_info, capture_message_content, metrics, disable_metrics),
|
52
67
|
)
|
53
68
|
|
54
69
|
def _uninstrument(self, **kwargs):
|
@@ -4,53 +4,30 @@ Module for monitoring Google AI Studio API calls.
|
|
4
4
|
|
5
5
|
import logging
|
6
6
|
import time
|
7
|
-
from opentelemetry.trace import SpanKind
|
8
|
-
from opentelemetry.sdk.resources import SERVICE_NAME, TELEMETRY_SDK_NAME, DEPLOYMENT_ENVIRONMENT
|
7
|
+
from opentelemetry.trace import SpanKind
|
9
8
|
from openlit.__helpers import (
|
10
|
-
get_chat_model_cost,
|
11
9
|
handle_exception,
|
12
|
-
response_as_dict,
|
13
|
-
create_metrics_attributes,
|
14
10
|
set_server_address_and_port
|
15
11
|
)
|
12
|
+
from openlit.instrumentation.google_ai_studio.utils import (
|
13
|
+
process_chat_response,
|
14
|
+
process_chunk,
|
15
|
+
process_streaming_chat_response
|
16
|
+
)
|
16
17
|
from openlit.semcov import SemanticConvention
|
17
18
|
|
18
19
|
# Initialize logger for logging potential issues and operations
|
19
20
|
logger = logging.getLogger(__name__)
|
20
21
|
|
21
22
|
def async_generate(version, environment, application_name,
|
22
|
-
|
23
|
+
tracer, pricing_info, capture_message_content, metrics, disable_metrics):
|
23
24
|
"""
|
24
|
-
Generates a telemetry wrapper for
|
25
|
-
|
26
|
-
Args:
|
27
|
-
gen_ai_endpoint: Endpoint identifier for logging and tracing.
|
28
|
-
version: Version of the monitoring package.
|
29
|
-
environment: Deployment environment (e.g., production, staging).
|
30
|
-
application_name: Name of the application using the Google AI Studio API.
|
31
|
-
tracer: OpenTelemetry tracer for creating spans.
|
32
|
-
pricing_info: Information used for calculating the cost of Google AI Studio usage.
|
33
|
-
capture_message_content: Flag indicating whether to trace the actual content.
|
34
|
-
|
35
|
-
Returns:
|
36
|
-
A function that wraps the chat method to add telemetry.
|
25
|
+
Generates a telemetry wrapper for GenAI function call
|
37
26
|
"""
|
38
27
|
|
39
28
|
async def wrapper(wrapped, instance, args, kwargs):
|
40
29
|
"""
|
41
|
-
Wraps the
|
42
|
-
|
43
|
-
This collects metrics such as execution time, cost, and token usage, and handles errors
|
44
|
-
gracefully, adding details to the trace for observability.
|
45
|
-
|
46
|
-
Args:
|
47
|
-
wrapped: The original 'chat.completions' method to be wrapped.
|
48
|
-
instance: The instance of the class where the original method is defined.
|
49
|
-
args: Positional arguments for the 'chat.completions' method.
|
50
|
-
kwargs: Keyword arguments for the 'chat.completions' method.
|
51
|
-
|
52
|
-
Returns:
|
53
|
-
The response from the original 'chat.completions' method.
|
30
|
+
Wraps the GenAI function call.
|
54
31
|
"""
|
55
32
|
|
56
33
|
server_address, server_port = set_server_address_and_port(instance, "generativelanguage.googleapis.com", 443)
|
@@ -61,167 +38,122 @@ def async_generate(version, environment, application_name,
|
|
61
38
|
with tracer.start_as_current_span(span_name, kind=SpanKind.CLIENT) as span:
|
62
39
|
start_time = time.time()
|
63
40
|
response = await wrapped(*args, **kwargs)
|
64
|
-
end_time = time.time()
|
65
41
|
|
66
|
-
|
42
|
+
response = process_chat_response(
|
43
|
+
instance = instance,
|
44
|
+
response=response,
|
45
|
+
request_model=request_model,
|
46
|
+
pricing_info=pricing_info,
|
47
|
+
server_port=server_port,
|
48
|
+
server_address=server_address,
|
49
|
+
environment=environment,
|
50
|
+
application_name=application_name,
|
51
|
+
metrics=metrics,
|
52
|
+
start_time=start_time,
|
53
|
+
span=span,
|
54
|
+
args=args,
|
55
|
+
kwargs=kwargs,
|
56
|
+
capture_message_content=capture_message_content,
|
57
|
+
disable_metrics=disable_metrics,
|
58
|
+
version=version,
|
59
|
+
)
|
60
|
+
|
61
|
+
return response
|
62
|
+
|
63
|
+
return wrapper
|
64
|
+
|
65
|
+
def async_generate_stream(version, environment, application_name,
|
66
|
+
tracer, pricing_info, capture_message_content, metrics, disable_metrics):
|
67
|
+
"""
|
68
|
+
Generates a telemetry wrapper for GenAI function call
|
69
|
+
"""
|
70
|
+
|
71
|
+
class TracedAsyncStream:
|
72
|
+
"""
|
73
|
+
Wrapper for streaming responses to collect telemetry.
|
74
|
+
"""
|
67
75
|
|
76
|
+
def __init__(
|
77
|
+
self,
|
78
|
+
wrapped,
|
79
|
+
span,
|
80
|
+
span_name,
|
81
|
+
kwargs,
|
82
|
+
server_address,
|
83
|
+
server_port,
|
84
|
+
**args,
|
85
|
+
):
|
86
|
+
self.__wrapped__ = wrapped
|
87
|
+
self._span = span
|
88
|
+
self._span_name = span_name
|
89
|
+
self._llmresponse = ''
|
90
|
+
self._finish_reason = ''
|
91
|
+
self._output_tokens = ''
|
92
|
+
self._input_tokens = ''
|
93
|
+
self._response_model = ''
|
94
|
+
self._tools = None
|
95
|
+
|
96
|
+
self._args = args
|
97
|
+
self._kwargs = kwargs
|
98
|
+
self._start_time = time.time()
|
99
|
+
self._end_time = None
|
100
|
+
self._timestamps = []
|
101
|
+
self._ttft = 0
|
102
|
+
self._tbt = 0
|
103
|
+
self._server_address = server_address
|
104
|
+
self._server_port = server_port
|
105
|
+
|
106
|
+
async def __aenter__(self):
|
107
|
+
await self.__wrapped__.__aenter__()
|
108
|
+
return self
|
109
|
+
|
110
|
+
async def __aexit__(self, exc_type, exc_value, traceback):
|
111
|
+
await self.__wrapped__.__aexit__(exc_type, exc_value, traceback)
|
112
|
+
|
113
|
+
def __aiter__(self):
|
114
|
+
return self
|
115
|
+
|
116
|
+
async def __getattr__(self, name):
|
117
|
+
"""Delegate attribute access to the wrapped object."""
|
118
|
+
return getattr(await self.__wrapped__, name)
|
119
|
+
|
120
|
+
async def __anext__(self):
|
68
121
|
try:
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
# Calculate cost of the operation
|
107
|
-
cost = get_chat_model_cost(request_model,
|
108
|
-
pricing_info, input_tokens,
|
109
|
-
output_tokens)
|
110
|
-
|
111
|
-
# Set base span attribues (OTel Semconv)
|
112
|
-
span.set_attribute(TELEMETRY_SDK_NAME, "openlit")
|
113
|
-
span.set_attribute(SemanticConvention.GEN_AI_OPERATION,
|
114
|
-
SemanticConvention.GEN_AI_OPERATION_TYPE_CHAT)
|
115
|
-
span.set_attribute(SemanticConvention.GEN_AI_SYSTEM,
|
116
|
-
SemanticConvention.GEN_AI_SYSTEM_GEMINI)
|
117
|
-
span.set_attribute(SemanticConvention.GEN_AI_REQUEST_MODEL,
|
118
|
-
request_model)
|
119
|
-
span.set_attribute(SemanticConvention.SERVER_PORT,
|
120
|
-
server_port)
|
121
|
-
|
122
|
-
inference_config = kwargs.get('config', {})
|
123
|
-
|
124
|
-
# List of attributes and their config keys
|
125
|
-
attributes = [
|
126
|
-
(SemanticConvention.GEN_AI_REQUEST_FREQUENCY_PENALTY, 'frequency_penalty'),
|
127
|
-
(SemanticConvention.GEN_AI_REQUEST_MAX_TOKENS, 'max_tokens'),
|
128
|
-
(SemanticConvention.GEN_AI_REQUEST_PRESENCE_PENALTY, 'presence_penalty'),
|
129
|
-
(SemanticConvention.GEN_AI_REQUEST_STOP_SEQUENCES, 'stop_sequences'),
|
130
|
-
(SemanticConvention.GEN_AI_REQUEST_TEMPERATURE, 'temperature'),
|
131
|
-
(SemanticConvention.GEN_AI_REQUEST_TOP_P, 'top_p'),
|
132
|
-
(SemanticConvention.GEN_AI_REQUEST_TOP_K, 'top_k'),
|
133
|
-
]
|
134
|
-
|
135
|
-
# Set each attribute if the corresponding value exists and is not None
|
136
|
-
for attribute, key in attributes:
|
137
|
-
# Use getattr to get the attribute value from the object
|
138
|
-
value = getattr(inference_config, key, None)
|
139
|
-
if value is not None:
|
140
|
-
span.set_attribute(attribute, value)
|
141
|
-
|
142
|
-
span.set_attribute(SemanticConvention.GEN_AI_RESPONSE_MODEL,
|
143
|
-
response_dict.get('model_version'))
|
144
|
-
span.set_attribute(SemanticConvention.GEN_AI_USAGE_INPUT_TOKENS,
|
145
|
-
input_tokens)
|
146
|
-
span.set_attribute(SemanticConvention.GEN_AI_USAGE_OUTPUT_TOKENS,
|
147
|
-
output_tokens)
|
148
|
-
span.set_attribute(SemanticConvention.SERVER_ADDRESS,
|
149
|
-
server_address)
|
150
|
-
span.set_attribute(SemanticConvention.GEN_AI_RESPONSE_FINISH_REASON,
|
151
|
-
[str(response_dict.get('candidates')[0].get('finish_reason'))])
|
152
|
-
|
153
|
-
# Set base span attribues (Extras)
|
154
|
-
span.set_attribute(DEPLOYMENT_ENVIRONMENT,
|
155
|
-
environment)
|
156
|
-
span.set_attribute(SERVICE_NAME,
|
157
|
-
application_name)
|
158
|
-
span.set_attribute(SemanticConvention.GEN_AI_REQUEST_IS_STREAM,
|
159
|
-
False)
|
160
|
-
span.set_attribute(SemanticConvention.GEN_AI_USAGE_TOTAL_TOKENS,
|
161
|
-
input_tokens + output_tokens)
|
162
|
-
span.set_attribute(SemanticConvention.GEN_AI_USAGE_COST,
|
163
|
-
cost)
|
164
|
-
span.set_attribute(SemanticConvention.GEN_AI_SERVER_TTFT,
|
165
|
-
end_time - start_time)
|
166
|
-
span.set_attribute(SemanticConvention.GEN_AI_SDK_VERSION,
|
167
|
-
version)
|
168
|
-
if capture_message_content:
|
169
|
-
span.add_event(
|
170
|
-
name=SemanticConvention.GEN_AI_CONTENT_PROMPT_EVENT,
|
171
|
-
attributes={
|
172
|
-
SemanticConvention.GEN_AI_CONTENT_PROMPT: prompt,
|
173
|
-
},
|
174
|
-
)
|
175
|
-
span.add_event(
|
176
|
-
name=SemanticConvention.GEN_AI_CONTENT_COMPLETION_EVENT,
|
177
|
-
attributes={
|
178
|
-
SemanticConvention.GEN_AI_CONTENT_COMPLETION: response.text,
|
179
|
-
},
|
180
|
-
)
|
181
|
-
|
182
|
-
if isinstance(response_dict.get('text'), str):
|
183
|
-
span.set_attribute(SemanticConvention.GEN_AI_OUTPUT_TYPE,
|
184
|
-
"text")
|
185
|
-
elif response_dict.get('text') is not None:
|
186
|
-
span.set_attribute(SemanticConvention.GEN_AI_OUTPUT_TYPE,
|
187
|
-
"json")
|
188
|
-
|
189
|
-
span.set_status(Status(StatusCode.OK))
|
190
|
-
|
191
|
-
if disable_metrics is False:
|
192
|
-
attributes = create_metrics_attributes(
|
193
|
-
service_name=application_name,
|
194
|
-
deployment_environment=environment,
|
195
|
-
operation=SemanticConvention.GEN_AI_OPERATION_TYPE_CHAT,
|
196
|
-
system=SemanticConvention.GEN_AI_SYSTEM_GEMINI,
|
197
|
-
request_model=request_model,
|
198
|
-
server_address=server_address,
|
199
|
-
server_port=server_port,
|
200
|
-
response_model=response_dict.get('model_version'),
|
201
|
-
)
|
202
|
-
|
203
|
-
metrics["genai_client_usage_tokens"].record(
|
204
|
-
input_tokens + output_tokens, attributes
|
205
|
-
)
|
206
|
-
metrics["genai_client_operation_duration"].record(
|
207
|
-
end_time - start_time, attributes
|
208
|
-
)
|
209
|
-
metrics["genai_server_ttft"].record(
|
210
|
-
end_time - start_time, attributes
|
211
|
-
)
|
212
|
-
metrics["genai_requests"].add(1, attributes)
|
213
|
-
metrics["genai_completion_tokens"].add(output_tokens, attributes)
|
214
|
-
metrics["genai_prompt_tokens"].add(input_tokens, attributes)
|
215
|
-
metrics["genai_cost"].record(cost, attributes)
|
216
|
-
|
217
|
-
# Return original response
|
218
|
-
return response
|
219
|
-
|
220
|
-
except Exception as e:
|
221
|
-
handle_exception(span, e)
|
222
|
-
logger.error("Error in trace creation: %s", e)
|
223
|
-
|
224
|
-
# Return original response
|
225
|
-
return response
|
122
|
+
chunk = await self.__wrapped__.__anext__()
|
123
|
+
process_chunk(self, chunk)
|
124
|
+
return chunk
|
125
|
+
except StopAsyncIteration:
|
126
|
+
try:
|
127
|
+
with tracer.start_as_current_span(self._span_name, kind= SpanKind.CLIENT) as self._span:
|
128
|
+
process_streaming_chat_response(
|
129
|
+
self,
|
130
|
+
pricing_info=pricing_info,
|
131
|
+
environment=environment,
|
132
|
+
application_name=application_name,
|
133
|
+
metrics=metrics,
|
134
|
+
capture_message_content=capture_message_content,
|
135
|
+
disable_metrics=disable_metrics,
|
136
|
+
version=version
|
137
|
+
)
|
138
|
+
|
139
|
+
except Exception as e:
|
140
|
+
handle_exception(self._span, e)
|
141
|
+
logger.error("Error in trace creation: %s", e)
|
142
|
+
raise
|
143
|
+
|
144
|
+
async def wrapper(wrapped, instance, args, kwargs):
|
145
|
+
"""
|
146
|
+
Wraps the GenAI function call.
|
147
|
+
"""
|
148
|
+
|
149
|
+
server_address, server_port = set_server_address_and_port(instance, "generativelanguage.googleapis.com", 443)
|
150
|
+
request_model = kwargs.get("model", "gemini-2.0-flash")
|
151
|
+
|
152
|
+
span_name = f"{SemanticConvention.GEN_AI_OPERATION_TYPE_CHAT} {request_model}"
|
153
|
+
|
154
|
+
awaited_wrapped = await wrapped(*args, **kwargs)
|
155
|
+
span = tracer.start_as_current_span(span_name, kind=SpanKind.CLIENT)
|
156
|
+
|
157
|
+
return TracedAsyncStream(awaited_wrapped, span, span_name, kwargs, server_address, server_port)
|
226
158
|
|
227
159
|
return wrapper
|
@@ -4,53 +4,30 @@ Module for monitoring Google AI Studio API calls.
|
|
4
4
|
|
5
5
|
import logging
|
6
6
|
import time
|
7
|
-
from opentelemetry.trace import SpanKind
|
8
|
-
from opentelemetry.sdk.resources import SERVICE_NAME, TELEMETRY_SDK_NAME, DEPLOYMENT_ENVIRONMENT
|
7
|
+
from opentelemetry.trace import SpanKind
|
9
8
|
from openlit.__helpers import (
|
10
|
-
get_chat_model_cost,
|
11
9
|
handle_exception,
|
12
|
-
response_as_dict,
|
13
|
-
create_metrics_attributes,
|
14
10
|
set_server_address_and_port
|
15
11
|
)
|
12
|
+
from openlit.instrumentation.google_ai_studio.utils import (
|
13
|
+
process_chat_response,
|
14
|
+
process_chunk,
|
15
|
+
process_streaming_chat_response
|
16
|
+
)
|
16
17
|
from openlit.semcov import SemanticConvention
|
17
18
|
|
18
19
|
# Initialize logger for logging potential issues and operations
|
19
20
|
logger = logging.getLogger(__name__)
|
20
21
|
|
21
22
|
def generate(version, environment, application_name,
|
22
|
-
|
23
|
+
tracer, pricing_info, capture_message_content, metrics, disable_metrics):
|
23
24
|
"""
|
24
|
-
Generates a telemetry wrapper for
|
25
|
-
|
26
|
-
Args:
|
27
|
-
gen_ai_endpoint: Endpoint identifier for logging and tracing.
|
28
|
-
version: Version of the monitoring package.
|
29
|
-
environment: Deployment environment (e.g., production, staging).
|
30
|
-
application_name: Name of the application using the Google AI Studio API.
|
31
|
-
tracer: OpenTelemetry tracer for creating spans.
|
32
|
-
pricing_info: Information used for calculating the cost of Google AI Studio usage.
|
33
|
-
capture_message_content: Flag indicating whether to trace the actual content.
|
34
|
-
|
35
|
-
Returns:
|
36
|
-
A function that wraps the chat method to add telemetry.
|
25
|
+
Generates a telemetry wrapper for GenAI function call
|
37
26
|
"""
|
38
27
|
|
39
28
|
def wrapper(wrapped, instance, args, kwargs):
|
40
29
|
"""
|
41
|
-
Wraps the
|
42
|
-
|
43
|
-
This collects metrics such as execution time, cost, and token usage, and handles errors
|
44
|
-
gracefully, adding details to the trace for observability.
|
45
|
-
|
46
|
-
Args:
|
47
|
-
wrapped: The original 'chat.completions' method to be wrapped.
|
48
|
-
instance: The instance of the class where the original method is defined.
|
49
|
-
args: Positional arguments for the 'chat.completions' method.
|
50
|
-
kwargs: Keyword arguments for the 'chat.completions' method.
|
51
|
-
|
52
|
-
Returns:
|
53
|
-
The response from the original 'chat.completions' method.
|
30
|
+
Wraps the GenAI function call.
|
54
31
|
"""
|
55
32
|
|
56
33
|
server_address, server_port = set_server_address_and_port(instance, "generativelanguage.googleapis.com", 443)
|
@@ -61,167 +38,122 @@ def generate(version, environment, application_name,
|
|
61
38
|
with tracer.start_as_current_span(span_name, kind=SpanKind.CLIENT) as span:
|
62
39
|
start_time = time.time()
|
63
40
|
response = wrapped(*args, **kwargs)
|
64
|
-
end_time = time.time()
|
65
41
|
|
66
|
-
|
42
|
+
response = process_chat_response(
|
43
|
+
instance = instance,
|
44
|
+
response=response,
|
45
|
+
request_model=request_model,
|
46
|
+
pricing_info=pricing_info,
|
47
|
+
server_port=server_port,
|
48
|
+
server_address=server_address,
|
49
|
+
environment=environment,
|
50
|
+
application_name=application_name,
|
51
|
+
metrics=metrics,
|
52
|
+
start_time=start_time,
|
53
|
+
span=span,
|
54
|
+
args=args,
|
55
|
+
kwargs=kwargs,
|
56
|
+
capture_message_content=capture_message_content,
|
57
|
+
disable_metrics=disable_metrics,
|
58
|
+
version=version,
|
59
|
+
)
|
60
|
+
|
61
|
+
return response
|
62
|
+
|
63
|
+
return wrapper
|
64
|
+
|
65
|
+
def generate_stream(version, environment, application_name,
|
66
|
+
tracer, pricing_info, capture_message_content, metrics, disable_metrics):
|
67
|
+
"""
|
68
|
+
Generates a telemetry wrapper for GenAI function call
|
69
|
+
"""
|
70
|
+
|
71
|
+
class TracedSyncStream:
|
72
|
+
"""
|
73
|
+
Wrapper for streaming responses to collect telemetry.
|
74
|
+
"""
|
67
75
|
|
76
|
+
def __init__(
|
77
|
+
self,
|
78
|
+
wrapped,
|
79
|
+
span,
|
80
|
+
span_name,
|
81
|
+
kwargs,
|
82
|
+
server_address,
|
83
|
+
server_port,
|
84
|
+
**args,
|
85
|
+
):
|
86
|
+
self.__wrapped__ = wrapped
|
87
|
+
self._span = span
|
88
|
+
self._span_name = span_name
|
89
|
+
self._llmresponse = ''
|
90
|
+
self._finish_reason = ''
|
91
|
+
self._output_tokens = ''
|
92
|
+
self._input_tokens = ''
|
93
|
+
self._response_model = ''
|
94
|
+
self._tools = None
|
95
|
+
|
96
|
+
self._args = args
|
97
|
+
self._kwargs = kwargs
|
98
|
+
self._start_time = time.time()
|
99
|
+
self._end_time = None
|
100
|
+
self._timestamps = []
|
101
|
+
self._ttft = 0
|
102
|
+
self._tbt = 0
|
103
|
+
self._server_address = server_address
|
104
|
+
self._server_port = server_port
|
105
|
+
|
106
|
+
def __enter__(self):
|
107
|
+
self.__wrapped__.__enter__()
|
108
|
+
return self
|
109
|
+
|
110
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
111
|
+
self.__wrapped__.__exit__(exc_type, exc_value, traceback)
|
112
|
+
|
113
|
+
def __iter__(self):
|
114
|
+
return self
|
115
|
+
|
116
|
+
def __getattr__(self, name):
|
117
|
+
"""Delegate attribute access to the wrapped object."""
|
118
|
+
return getattr(self.__wrapped__, name)
|
119
|
+
|
120
|
+
def __next__(self):
|
68
121
|
try:
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
# Calculate cost of the operation
|
107
|
-
cost = get_chat_model_cost(request_model,
|
108
|
-
pricing_info, input_tokens,
|
109
|
-
output_tokens)
|
110
|
-
|
111
|
-
# Set base span attribues (OTel Semconv)
|
112
|
-
span.set_attribute(TELEMETRY_SDK_NAME, "openlit")
|
113
|
-
span.set_attribute(SemanticConvention.GEN_AI_OPERATION,
|
114
|
-
SemanticConvention.GEN_AI_OPERATION_TYPE_CHAT)
|
115
|
-
span.set_attribute(SemanticConvention.GEN_AI_SYSTEM,
|
116
|
-
SemanticConvention.GEN_AI_SYSTEM_GEMINI)
|
117
|
-
span.set_attribute(SemanticConvention.GEN_AI_REQUEST_MODEL,
|
118
|
-
request_model)
|
119
|
-
span.set_attribute(SemanticConvention.SERVER_PORT,
|
120
|
-
server_port)
|
121
|
-
|
122
|
-
inference_config = kwargs.get('config', {})
|
123
|
-
|
124
|
-
# List of attributes and their config keys
|
125
|
-
attributes = [
|
126
|
-
(SemanticConvention.GEN_AI_REQUEST_FREQUENCY_PENALTY, 'frequency_penalty'),
|
127
|
-
(SemanticConvention.GEN_AI_REQUEST_MAX_TOKENS, 'max_tokens'),
|
128
|
-
(SemanticConvention.GEN_AI_REQUEST_PRESENCE_PENALTY, 'presence_penalty'),
|
129
|
-
(SemanticConvention.GEN_AI_REQUEST_STOP_SEQUENCES, 'stop_sequences'),
|
130
|
-
(SemanticConvention.GEN_AI_REQUEST_TEMPERATURE, 'temperature'),
|
131
|
-
(SemanticConvention.GEN_AI_REQUEST_TOP_P, 'top_p'),
|
132
|
-
(SemanticConvention.GEN_AI_REQUEST_TOP_K, 'top_k'),
|
133
|
-
]
|
134
|
-
|
135
|
-
# Set each attribute if the corresponding value exists and is not None
|
136
|
-
for attribute, key in attributes:
|
137
|
-
# Use getattr to get the attribute value from the object
|
138
|
-
value = getattr(inference_config, key, None)
|
139
|
-
if value is not None:
|
140
|
-
span.set_attribute(attribute, value)
|
141
|
-
|
142
|
-
span.set_attribute(SemanticConvention.GEN_AI_RESPONSE_MODEL,
|
143
|
-
response_dict.get('model_version'))
|
144
|
-
span.set_attribute(SemanticConvention.GEN_AI_USAGE_INPUT_TOKENS,
|
145
|
-
input_tokens)
|
146
|
-
span.set_attribute(SemanticConvention.GEN_AI_USAGE_OUTPUT_TOKENS,
|
147
|
-
output_tokens)
|
148
|
-
span.set_attribute(SemanticConvention.SERVER_ADDRESS,
|
149
|
-
server_address)
|
150
|
-
span.set_attribute(SemanticConvention.GEN_AI_RESPONSE_FINISH_REASON,
|
151
|
-
[str(response_dict.get('candidates')[0].get('finish_reason'))])
|
152
|
-
|
153
|
-
# Set base span attribues (Extras)
|
154
|
-
span.set_attribute(DEPLOYMENT_ENVIRONMENT,
|
155
|
-
environment)
|
156
|
-
span.set_attribute(SERVICE_NAME,
|
157
|
-
application_name)
|
158
|
-
span.set_attribute(SemanticConvention.GEN_AI_REQUEST_IS_STREAM,
|
159
|
-
False)
|
160
|
-
span.set_attribute(SemanticConvention.GEN_AI_USAGE_TOTAL_TOKENS,
|
161
|
-
input_tokens + output_tokens)
|
162
|
-
span.set_attribute(SemanticConvention.GEN_AI_USAGE_COST,
|
163
|
-
cost)
|
164
|
-
span.set_attribute(SemanticConvention.GEN_AI_SERVER_TTFT,
|
165
|
-
end_time - start_time)
|
166
|
-
span.set_attribute(SemanticConvention.GEN_AI_SDK_VERSION,
|
167
|
-
version)
|
168
|
-
if capture_message_content:
|
169
|
-
span.add_event(
|
170
|
-
name=SemanticConvention.GEN_AI_CONTENT_PROMPT_EVENT,
|
171
|
-
attributes={
|
172
|
-
SemanticConvention.GEN_AI_CONTENT_PROMPT: prompt,
|
173
|
-
},
|
174
|
-
)
|
175
|
-
span.add_event(
|
176
|
-
name=SemanticConvention.GEN_AI_CONTENT_COMPLETION_EVENT,
|
177
|
-
attributes={
|
178
|
-
SemanticConvention.GEN_AI_CONTENT_COMPLETION: response.text,
|
179
|
-
},
|
180
|
-
)
|
181
|
-
|
182
|
-
if isinstance(response_dict.get('text'), str):
|
183
|
-
span.set_attribute(SemanticConvention.GEN_AI_OUTPUT_TYPE,
|
184
|
-
"text")
|
185
|
-
elif response_dict.get('text') is not None:
|
186
|
-
span.set_attribute(SemanticConvention.GEN_AI_OUTPUT_TYPE,
|
187
|
-
"json")
|
188
|
-
|
189
|
-
span.set_status(Status(StatusCode.OK))
|
190
|
-
|
191
|
-
if disable_metrics is False:
|
192
|
-
attributes = create_metrics_attributes(
|
193
|
-
service_name=application_name,
|
194
|
-
deployment_environment=environment,
|
195
|
-
operation=SemanticConvention.GEN_AI_OPERATION_TYPE_CHAT,
|
196
|
-
system=SemanticConvention.GEN_AI_SYSTEM_GEMINI,
|
197
|
-
request_model=request_model,
|
198
|
-
server_address=server_address,
|
199
|
-
server_port=server_port,
|
200
|
-
response_model=response_dict.get('model_version'),
|
201
|
-
)
|
202
|
-
|
203
|
-
metrics["genai_client_usage_tokens"].record(
|
204
|
-
input_tokens + output_tokens, attributes
|
205
|
-
)
|
206
|
-
metrics["genai_client_operation_duration"].record(
|
207
|
-
end_time - start_time, attributes
|
208
|
-
)
|
209
|
-
metrics["genai_server_ttft"].record(
|
210
|
-
end_time - start_time, attributes
|
211
|
-
)
|
212
|
-
metrics["genai_requests"].add(1, attributes)
|
213
|
-
metrics["genai_completion_tokens"].add(output_tokens, attributes)
|
214
|
-
metrics["genai_prompt_tokens"].add(input_tokens, attributes)
|
215
|
-
metrics["genai_cost"].record(cost, attributes)
|
216
|
-
|
217
|
-
# Return original response
|
218
|
-
return response
|
219
|
-
|
220
|
-
except Exception as e:
|
221
|
-
handle_exception(span, e)
|
222
|
-
logger.error("Error in trace creation: %s", e)
|
223
|
-
|
224
|
-
# Return original response
|
225
|
-
return response
|
122
|
+
chunk = self.__wrapped__.__next__()
|
123
|
+
process_chunk(self, chunk)
|
124
|
+
return chunk
|
125
|
+
except StopIteration:
|
126
|
+
try:
|
127
|
+
with tracer.start_as_current_span(self._span_name, kind= SpanKind.CLIENT) as self._span:
|
128
|
+
process_streaming_chat_response(
|
129
|
+
self,
|
130
|
+
pricing_info=pricing_info,
|
131
|
+
environment=environment,
|
132
|
+
application_name=application_name,
|
133
|
+
metrics=metrics,
|
134
|
+
capture_message_content=capture_message_content,
|
135
|
+
disable_metrics=disable_metrics,
|
136
|
+
version=version
|
137
|
+
)
|
138
|
+
|
139
|
+
except Exception as e:
|
140
|
+
handle_exception(self._span, e)
|
141
|
+
logger.error("Error in trace creation: %s", e)
|
142
|
+
raise
|
143
|
+
|
144
|
+
def wrapper(wrapped, instance, args, kwargs):
|
145
|
+
"""
|
146
|
+
Wraps the GenAI function call.
|
147
|
+
"""
|
148
|
+
|
149
|
+
server_address, server_port = set_server_address_and_port(instance, "generativelanguage.googleapis.com", 443)
|
150
|
+
request_model = kwargs.get("model", "gemini-2.0-flash")
|
151
|
+
|
152
|
+
span_name = f"{SemanticConvention.GEN_AI_OPERATION_TYPE_CHAT} {request_model}"
|
153
|
+
|
154
|
+
awaited_wrapped = wrapped(*args, **kwargs)
|
155
|
+
span = tracer.start_as_current_span(span_name, kind=SpanKind.CLIENT)
|
156
|
+
|
157
|
+
return TracedSyncStream(awaited_wrapped, span, span_name, kwargs, server_address, server_port)
|
226
158
|
|
227
159
|
return wrapper
|
@@ -0,0 +1,249 @@
|
|
1
|
+
"""
|
2
|
+
Google AI Studio OpenTelemetry instrumentation utility functions
|
3
|
+
"""
|
4
|
+
import time
|
5
|
+
|
6
|
+
from opentelemetry.sdk.resources import SERVICE_NAME, TELEMETRY_SDK_NAME, DEPLOYMENT_ENVIRONMENT
|
7
|
+
from opentelemetry.trace import Status, StatusCode
|
8
|
+
|
9
|
+
from openlit.__helpers import (
|
10
|
+
calculate_ttft,
|
11
|
+
response_as_dict,
|
12
|
+
calculate_tbt,
|
13
|
+
get_chat_model_cost,
|
14
|
+
create_metrics_attributes,
|
15
|
+
)
|
16
|
+
from openlit.semcov import SemanticConvention
|
17
|
+
|
18
|
+
def format_content(messages):
|
19
|
+
"""
|
20
|
+
Process a list of messages to extract content, categorize them by role,
|
21
|
+
and concatenate all 'content' fields into a single string with role: content format.
|
22
|
+
"""
|
23
|
+
|
24
|
+
formatted_messages = []
|
25
|
+
prompt = ""
|
26
|
+
|
27
|
+
if isinstance(messages, list):
|
28
|
+
try:
|
29
|
+
for content in messages:
|
30
|
+
role = content.role if content.role is not None else "user"
|
31
|
+
parts = content.parts
|
32
|
+
content_str = []
|
33
|
+
|
34
|
+
for part in parts:
|
35
|
+
# Collect relevant fields and handle each type of data that Part could contain
|
36
|
+
if part.text:
|
37
|
+
content_str.append(f"text: {part.text}")
|
38
|
+
if part.video_metadata:
|
39
|
+
content_str.append(f"video_metadata: {part.video_metadata}")
|
40
|
+
if part.thought:
|
41
|
+
content_str.append(f"thought: {part.thought}")
|
42
|
+
if part.code_execution_result:
|
43
|
+
content_str.append(f"code_execution_result: {part.code_execution_result}")
|
44
|
+
if part.executable_code:
|
45
|
+
content_str.append(f"executable_code: {part.executable_code}")
|
46
|
+
if part.file_data:
|
47
|
+
content_str.append(f"file_data: {part.file_data}")
|
48
|
+
if part.function_call:
|
49
|
+
content_str.append(f"function_call: {part.function_call}")
|
50
|
+
if part.function_response:
|
51
|
+
content_str.append(f"function_response: {part.function_response}")
|
52
|
+
if part.inline_data:
|
53
|
+
content_str.append(f"inline_data: {part.inline_data}")
|
54
|
+
|
55
|
+
formatted_messages.append(f"{role}: {', '.join(content_str)}")
|
56
|
+
|
57
|
+
prompt = "\n".join(formatted_messages)
|
58
|
+
|
59
|
+
except:
|
60
|
+
prompt = str(messages)
|
61
|
+
|
62
|
+
else:
|
63
|
+
prompt = messages
|
64
|
+
|
65
|
+
return prompt
|
66
|
+
|
67
|
+
def process_chunk(self, chunk):
|
68
|
+
"""
|
69
|
+
Process a chunk of response data and update state.
|
70
|
+
"""
|
71
|
+
|
72
|
+
end_time = time.time()
|
73
|
+
# Record the timestamp for the current chunk
|
74
|
+
self._timestamps.append(end_time)
|
75
|
+
|
76
|
+
if len(self._timestamps) == 1:
|
77
|
+
# Calculate time to first chunk
|
78
|
+
self._ttft = calculate_ttft(self._timestamps, self._start_time)
|
79
|
+
|
80
|
+
chunked = response_as_dict(chunk)
|
81
|
+
|
82
|
+
|
83
|
+
self._response_id = str(chunked.get('response_id'))
|
84
|
+
self._input_tokens = chunked.get('usage_metadata').get('prompt_token_count')
|
85
|
+
self._response_model = chunked.get('model_version')
|
86
|
+
|
87
|
+
if chunk.text:
|
88
|
+
self._llmresponse += str(chunk.text)
|
89
|
+
|
90
|
+
self._output_tokens = chunked.get('usage_metadata').get('candidates_token_count')
|
91
|
+
self._reasoning_tokens = chunked.get('usage_metadata').get('thoughts_token_count') or 0
|
92
|
+
self._finish_reason = str(chunked.get('candidates')[0].get('finish_reason'))
|
93
|
+
|
94
|
+
try:
|
95
|
+
self._tools = chunked.get('candidates', [])[0].get('content', {}).get('parts', [])[0].get('function_call', '')
|
96
|
+
except:
|
97
|
+
self._tools = None
|
98
|
+
|
99
|
+
def common_chat_logic(scope, pricing_info, environment, application_name, metrics,
|
100
|
+
capture_message_content, disable_metrics, version, is_stream):
|
101
|
+
"""
|
102
|
+
Process chat request and generate Telemetry
|
103
|
+
"""
|
104
|
+
|
105
|
+
scope._end_time = time.time()
|
106
|
+
if len(scope._timestamps) > 1:
|
107
|
+
scope._tbt = calculate_tbt(scope._timestamps)
|
108
|
+
|
109
|
+
prompt = format_content(scope._kwargs.get('contents', ''))
|
110
|
+
request_model = scope._kwargs.get("model", "gemini-2.0-flash")
|
111
|
+
|
112
|
+
cost = get_chat_model_cost(request_model, pricing_info, scope._input_tokens, scope._output_tokens)
|
113
|
+
|
114
|
+
# Set Span attributes (OTel Semconv)
|
115
|
+
scope._span.set_attribute(TELEMETRY_SDK_NAME, 'openlit')
|
116
|
+
scope._span.set_attribute(SemanticConvention.GEN_AI_OPERATION, SemanticConvention.GEN_AI_OPERATION_TYPE_CHAT)
|
117
|
+
scope._span.set_attribute(SemanticConvention.GEN_AI_SYSTEM, SemanticConvention.GEN_AI_SYSTEM_GEMINI)
|
118
|
+
scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_MODEL, request_model)
|
119
|
+
scope._span.set_attribute(SemanticConvention.SERVER_PORT, scope._server_port)
|
120
|
+
|
121
|
+
inference_config = scope._kwargs.get('config', {})
|
122
|
+
|
123
|
+
attributes = [
|
124
|
+
(SemanticConvention.GEN_AI_REQUEST_FREQUENCY_PENALTY, 'frequency_penalty'),
|
125
|
+
(SemanticConvention.GEN_AI_REQUEST_MAX_TOKENS, 'max_tokens'),
|
126
|
+
(SemanticConvention.GEN_AI_REQUEST_PRESENCE_PENALTY, 'presence_penalty'),
|
127
|
+
(SemanticConvention.GEN_AI_REQUEST_STOP_SEQUENCES, 'stop_sequences'),
|
128
|
+
(SemanticConvention.GEN_AI_REQUEST_TEMPERATURE, 'temperature'),
|
129
|
+
(SemanticConvention.GEN_AI_REQUEST_TOP_P, 'top_p'),
|
130
|
+
(SemanticConvention.GEN_AI_REQUEST_TOP_K, 'top_k'),
|
131
|
+
]
|
132
|
+
|
133
|
+
# Set each attribute if the corresponding value exists and is not None
|
134
|
+
for attribute, key in attributes:
|
135
|
+
# Use getattr to get the attribute value from the object
|
136
|
+
value = getattr(inference_config, key, None)
|
137
|
+
if value is not None:
|
138
|
+
scope._span.set_attribute(attribute, value)
|
139
|
+
|
140
|
+
scope._span.set_attribute(SemanticConvention.GEN_AI_RESPONSE_FINISH_REASON, [scope._finish_reason])
|
141
|
+
scope._span.set_attribute(SemanticConvention.GEN_AI_RESPONSE_MODEL, scope._response_model)
|
142
|
+
scope._span.set_attribute(SemanticConvention.GEN_AI_USAGE_INPUT_TOKENS, scope._input_tokens)
|
143
|
+
scope._span.set_attribute(SemanticConvention.GEN_AI_USAGE_OUTPUT_TOKENS, scope._output_tokens)
|
144
|
+
scope._span.set_attribute(SemanticConvention.GEN_AI_USAGE_REASONING_TOKENS, scope._reasoning_tokens)
|
145
|
+
scope._span.set_attribute(SemanticConvention.SERVER_ADDRESS, scope._server_address)
|
146
|
+
|
147
|
+
scope._span.set_attribute(SemanticConvention.GEN_AI_OUTPUT_TYPE,
|
148
|
+
'text' if isinstance(scope._llmresponse, str) else 'json')
|
149
|
+
|
150
|
+
scope._span.set_attribute(DEPLOYMENT_ENVIRONMENT, environment)
|
151
|
+
scope._span.set_attribute(SERVICE_NAME, application_name)
|
152
|
+
scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_IS_STREAM, is_stream)
|
153
|
+
scope._span.set_attribute(SemanticConvention.GEN_AI_USAGE_COST, cost)
|
154
|
+
scope._span.set_attribute(SemanticConvention.GEN_AI_SERVER_TBT, scope._tbt)
|
155
|
+
scope._span.set_attribute(SemanticConvention.GEN_AI_SERVER_TTFT, scope._ttft)
|
156
|
+
scope._span.set_attribute(SemanticConvention.GEN_AI_SDK_VERSION, version)
|
157
|
+
scope._span.set_attribute(SemanticConvention.GEN_AI_CLIENT_TOKEN_USAGE,
|
158
|
+
scope._input_tokens + scope._output_tokens + scope._reasoning_tokens)
|
159
|
+
|
160
|
+
if scope._tools:
|
161
|
+
scope._span.set_attribute(SemanticConvention.GEN_AI_TOOL_NAME, scope._tools.get('name',''))
|
162
|
+
scope._span.set_attribute(SemanticConvention.GEN_AI_TOOL_CALL_ID, str(scope._tools.get('id','')))
|
163
|
+
scope._span.set_attribute(SemanticConvention.GEN_AI_TOOL_ARGS, str(scope._tools.get('args','')))
|
164
|
+
|
165
|
+
# To be removed one the change to span_attributes (from span events) is complete
|
166
|
+
if capture_message_content:
|
167
|
+
scope._span.set_attribute(SemanticConvention.GEN_AI_CONTENT_PROMPT, prompt)
|
168
|
+
scope._span.set_attribute(SemanticConvention.GEN_AI_CONTENT_COMPLETION, scope._llmresponse)
|
169
|
+
scope._span.add_event(
|
170
|
+
name=SemanticConvention.GEN_AI_CONTENT_PROMPT_EVENT,
|
171
|
+
attributes={
|
172
|
+
SemanticConvention.GEN_AI_CONTENT_PROMPT: prompt,
|
173
|
+
},
|
174
|
+
)
|
175
|
+
scope._span.add_event(
|
176
|
+
name=SemanticConvention.GEN_AI_CONTENT_COMPLETION_EVENT,
|
177
|
+
attributes={
|
178
|
+
SemanticConvention.GEN_AI_CONTENT_COMPLETION: scope._llmresponse,
|
179
|
+
},
|
180
|
+
)
|
181
|
+
|
182
|
+
scope._span.set_status(Status(StatusCode.OK))
|
183
|
+
|
184
|
+
if not disable_metrics:
|
185
|
+
metrics_attributes = create_metrics_attributes(
|
186
|
+
service_name=application_name,
|
187
|
+
deployment_environment=environment,
|
188
|
+
operation=SemanticConvention.GEN_AI_OPERATION_TYPE_CHAT,
|
189
|
+
system=SemanticConvention.GEN_AI_SYSTEM_GEMINI,
|
190
|
+
request_model=request_model,
|
191
|
+
server_address=scope._server_address,
|
192
|
+
server_port=scope._server_port,
|
193
|
+
response_model=scope._response_model,
|
194
|
+
)
|
195
|
+
|
196
|
+
metrics['genai_client_operation_duration'].record(scope._end_time - scope._start_time, metrics_attributes)
|
197
|
+
metrics['genai_server_tbt'].record(scope._tbt, metrics_attributes)
|
198
|
+
metrics['genai_server_ttft'].record(scope._ttft, metrics_attributes)
|
199
|
+
metrics['genai_requests'].add(1, metrics_attributes)
|
200
|
+
metrics['genai_completion_tokens'].add(scope._output_tokens, metrics_attributes)
|
201
|
+
metrics['genai_prompt_tokens'].add(scope._input_tokens, metrics_attributes)
|
202
|
+
metrics['genai_reasoning_tokens'].add(scope._reasoning_tokens, metrics_attributes)
|
203
|
+
metrics['genai_cost'].record(cost, metrics_attributes)
|
204
|
+
metrics['genai_client_usage_tokens'].record(
|
205
|
+
scope._input_tokens + scope._output_tokens + scope._reasoning_tokens, metrics_attributes)
|
206
|
+
|
207
|
+
|
208
|
+
def process_streaming_chat_response(self, pricing_info, environment, application_name, metrics,
|
209
|
+
capture_message_content=False, disable_metrics=False, version=''):
|
210
|
+
"""
|
211
|
+
Process chat request and generate Telemetry
|
212
|
+
"""
|
213
|
+
|
214
|
+
common_chat_logic(self, pricing_info, environment, application_name, metrics,
|
215
|
+
capture_message_content, disable_metrics, version, is_stream=True)
|
216
|
+
|
217
|
+
def process_chat_response(instance, response, request_model, pricing_info, server_port, server_address,
|
218
|
+
environment, application_name, metrics, start_time,
|
219
|
+
span, args, kwargs, capture_message_content=False, disable_metrics=False, version="1.0.0"):
|
220
|
+
"""
|
221
|
+
Process chat request and generate Telemetry
|
222
|
+
"""
|
223
|
+
|
224
|
+
self = type('GenericScope', (), {})()
|
225
|
+
response_dict = response_as_dict(response)
|
226
|
+
|
227
|
+
self._start_time = start_time
|
228
|
+
self._end_time = time.time()
|
229
|
+
self._span = span
|
230
|
+
self._llmresponse = str(response.text)
|
231
|
+
self._input_tokens = response_dict.get('usage_metadata').get('prompt_token_count')
|
232
|
+
self._output_tokens = response_dict.get('usage_metadata').get('candidates_token_count')
|
233
|
+
self._reasoning_tokens = response_dict.get('usage_metadata').get('thoughts_token_count') or 0
|
234
|
+
self._response_model = response_dict.get('model_version')
|
235
|
+
self._timestamps = []
|
236
|
+
self._ttft, self._tbt = self._end_time - self._start_time, 0
|
237
|
+
self._server_address, self._server_port = server_address, server_port
|
238
|
+
self._kwargs = kwargs
|
239
|
+
self._finish_reason = str(response_dict.get('candidates')[0].get('finish_reason'))
|
240
|
+
|
241
|
+
try:
|
242
|
+
self._tools = response_dict.get('candidates', [])[0].get('content', {}).get('parts', [])[0].get('function_call', '')
|
243
|
+
except:
|
244
|
+
self._tools = None
|
245
|
+
|
246
|
+
common_chat_logic(self, pricing_info, environment, application_name, metrics,
|
247
|
+
capture_message_content, disable_metrics, version, is_stream=False)
|
248
|
+
|
249
|
+
return response
|
@@ -8,7 +8,6 @@ from opentelemetry.trace import SpanKind
|
|
8
8
|
from openlit.__helpers import (
|
9
9
|
set_server_address_and_port
|
10
10
|
)
|
11
|
-
|
12
11
|
from openlit.instrumentation.transformers.utils import (
|
13
12
|
process_chat_response,
|
14
13
|
)
|
@@ -18,7 +17,7 @@ from openlit.semcov import SemanticConvention
|
|
18
17
|
logger = logging.getLogger(__name__)
|
19
18
|
|
20
19
|
def pipeline_wrapper(version, environment, application_name,
|
21
|
-
|
20
|
+
tracer, pricing_info, capture_message_content, metrics, disable_metrics):
|
22
21
|
"""
|
23
22
|
Generates a telemetry wrapper for GenAI function call
|
24
23
|
"""
|
@@ -72,7 +72,7 @@ def common_chat_logic(scope, pricing_info, environment, application_name, metric
|
|
72
72
|
# To be removed one the change to span_attributes (from span events) is complete
|
73
73
|
if capture_message_content:
|
74
74
|
scope._span.set_attribute(SemanticConvention.GEN_AI_CONTENT_PROMPT, scope._prompt)
|
75
|
-
scope._span.set_attribute(SemanticConvention.GEN_AI_CONTENT_COMPLETION, scope._llmresponse
|
75
|
+
scope._span.set_attribute(SemanticConvention.GEN_AI_CONTENT_COMPLETION, scope._llmresponse)
|
76
76
|
|
77
77
|
scope._span.add_event(
|
78
78
|
name=SemanticConvention.GEN_AI_CONTENT_PROMPT_EVENT,
|
@@ -111,8 +111,8 @@ def common_chat_logic(scope, pricing_info, environment, application_name, metric
|
|
111
111
|
metrics["genai_cost"].record(cost, metrics_attributes)
|
112
112
|
|
113
113
|
def process_chat_response(instance, response, request_model, pricing_info, server_port, server_address,
|
114
|
-
|
115
|
-
|
114
|
+
environment, application_name, metrics, start_time,
|
115
|
+
span, args, kwargs, capture_message_content=False, disable_metrics=False, version="1.0.0"):
|
116
116
|
"""
|
117
117
|
Process chat request and generate Telemetry
|
118
118
|
"""
|
openlit/otel/metrics.py
CHANGED
@@ -202,6 +202,11 @@ def setup_meter(application_name, environment, meter, otlp_endpoint, otlp_header
|
|
202
202
|
description="Number of completion tokens processed.",
|
203
203
|
unit="1",
|
204
204
|
),
|
205
|
+
"genai_reasoning_tokens": meter.create_counter(
|
206
|
+
name=SemanticConvention.GEN_AI_USAGE_REASONING_TOKENS,
|
207
|
+
description="Number of reasoning thought tokens processed.",
|
208
|
+
unit="1",
|
209
|
+
),
|
205
210
|
"genai_cost": meter.create_histogram(
|
206
211
|
name=SemanticConvention.GEN_AI_USAGE_COST,
|
207
212
|
description="The distribution of GenAI request costs.",
|
openlit/semcov/__init__.py
CHANGED
@@ -58,8 +58,10 @@ class SemanticConvention:
|
|
58
58
|
GEN_AI_RESPONSE_MODEL = "gen_ai.response.model"
|
59
59
|
GEN_AI_USAGE_INPUT_TOKENS = "gen_ai.usage.input_tokens"
|
60
60
|
GEN_AI_USAGE_OUTPUT_TOKENS = "gen_ai.usage.output_tokens"
|
61
|
+
GEN_AI_USAGE_REASONING_TOKENS = "gen_ai.usage.reasoning_tokens"
|
61
62
|
GEN_AI_TOOL_CALL_ID = "gen_ai.tool.call.id"
|
62
63
|
GEN_AI_TOOL_NAME = "gen_ai.tool.name"
|
64
|
+
GEN_AI_TOOL_ARGS = "gen_ai.tool.args"
|
63
65
|
|
64
66
|
# GenAI Operation Types (OTel Semconv)
|
65
67
|
GEN_AI_OPERATION_TYPE_CHAT = "chat"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: openlit
|
3
|
-
Version: 1.33.
|
3
|
+
Version: 1.33.23
|
4
4
|
Summary: OpenTelemetry-native Auto instrumentation library for monitoring LLM Applications and GPUs, facilitating the integration of observability into your GenAI-driven projects
|
5
5
|
License: Apache-2.0
|
6
6
|
Keywords: OpenTelemetry,otel,otlp,llm,tracing,openai,anthropic,claude,cohere,llm monitoring,observability,monitoring,gpt,Generative AI,chatGPT,gpu
|
@@ -56,9 +56,10 @@ openlit/instrumentation/embedchain/__init__.py,sha256=x2_qvJTwWog_mH6IY987Bp9mWx
|
|
56
56
|
openlit/instrumentation/embedchain/embedchain.py,sha256=f4hyOr1Xr0RC4PNHRu46aV-jmEh-lIeKN8XLjgY7aWM,7929
|
57
57
|
openlit/instrumentation/firecrawl/__init__.py,sha256=kyVsAiDBC2djifqT2w1cPRAotiEyEabNvnBeSQxi9N8,1876
|
58
58
|
openlit/instrumentation/firecrawl/firecrawl.py,sha256=4X38UrLYeGm3uez-edYA6qEc0nKC3p77yfKgKBBud0A,3826
|
59
|
-
openlit/instrumentation/google_ai_studio/__init__.py,sha256=
|
60
|
-
openlit/instrumentation/google_ai_studio/async_google_ai_studio.py,sha256=
|
61
|
-
openlit/instrumentation/google_ai_studio/google_ai_studio.py,sha256=
|
59
|
+
openlit/instrumentation/google_ai_studio/__init__.py,sha256=d4aDvCSfDtT2geRbwG5yinu62uPTHaj4PtalimuvG-k,2685
|
60
|
+
openlit/instrumentation/google_ai_studio/async_google_ai_studio.py,sha256=kv8X1c3klmFEPTyxj9kJag2D670_YZxxkBhsIrW7JH4,5501
|
61
|
+
openlit/instrumentation/google_ai_studio/google_ai_studio.py,sha256=MJQjHF1o6DuXHr1xE2-hp_cgejV65-LpXIGL6bacE4Q,5403
|
62
|
+
openlit/instrumentation/google_ai_studio/utils.py,sha256=s9-5qnIWfXsq2xjYbWToI0RBAUo5lT0yLPOILc6eY1k,11256
|
62
63
|
openlit/instrumentation/gpt4all/__init__.py,sha256=cO8mi3hhPDXcNwb9AwQ3-wQ_ydnOeBRwb0cptlQmAM4,1805
|
63
64
|
openlit/instrumentation/gpt4all/gpt4all.py,sha256=EYp0njZ1kF56rTAjYZVtufA5W4xTWGzSIntjJ4MEfl4,24185
|
64
65
|
openlit/instrumentation/gpu/__init__.py,sha256=QQCFVEbRfdeTjmdFe-UeEiy19vEEWSIBpj2B1wYGhUs,11036
|
@@ -115,18 +116,18 @@ openlit/instrumentation/together/__init__.py,sha256=MLLL2t8FyrytpfMueqcwekiqTKn-
|
|
115
116
|
openlit/instrumentation/together/async_together.py,sha256=ToSeYqE0mCgSsCNSO0pqoyS7WU6YarHxa3I7ZrzH-d8,30634
|
116
117
|
openlit/instrumentation/together/together.py,sha256=7Da9fjHaZk_ObXMnSZA79-RktgwHRVYevsZAA-OpcXY,30530
|
117
118
|
openlit/instrumentation/transformers/__init__.py,sha256=9Ubss5nlumcypxprxff8Fv3sst7II27SsvCzqkBX9Kg,1457
|
118
|
-
openlit/instrumentation/transformers/transformers.py,sha256=
|
119
|
-
openlit/instrumentation/transformers/utils.py,sha256=
|
119
|
+
openlit/instrumentation/transformers/transformers.py,sha256=y--t7PXhUfPC81w-aEE7qowMah3os9gnKBQ5bN4QLGc,1980
|
120
|
+
openlit/instrumentation/transformers/utils.py,sha256=3f-ewpUpduaBrTVIFJKaabACjz-6Vf8K7NEU0EzQ4Nk,8042
|
120
121
|
openlit/instrumentation/vertexai/__init__.py,sha256=mT28WCBvQfRCkAWGL6bd0EjEPHvMjaNcz6T3jsLZh8k,3745
|
121
122
|
openlit/instrumentation/vertexai/async_vertexai.py,sha256=-kpg-eiL76O5_XopUPghCYwJHf0Nrxi00_Z5tCwq6zM,23086
|
122
123
|
openlit/instrumentation/vertexai/vertexai.py,sha256=5NB090aWlm9DnlccNNLRO6A97P_RN-JnHb5JS01tYyw,23000
|
123
124
|
openlit/instrumentation/vllm/__init__.py,sha256=8Su4DEpxdT2wr4Qr17heakzoGSbuq6ey1MmSVR_vbOA,1508
|
124
125
|
openlit/instrumentation/vllm/vllm.py,sha256=FxDIR4WH1VySivi0wop4E1DBo2HXyCr8nZ9x1c7x4eM,7778
|
125
126
|
openlit/otel/events.py,sha256=VrMjTpvnLtYRBHCiFwJojTQqqNpRCxoD4yJYeQrtPsk,3560
|
126
|
-
openlit/otel/metrics.py,sha256=
|
127
|
+
openlit/otel/metrics.py,sha256=GM2PDloBGRhBTkHHkYaqmOwIAQkY124ZhW4sEqW1Fgk,7086
|
127
128
|
openlit/otel/tracing.py,sha256=tjV2bEbEDPUB1Z46gE-UsJsb04sRdFrfbhIDkxViZc0,3103
|
128
|
-
openlit/semcov/__init__.py,sha256=
|
129
|
-
openlit-1.33.
|
130
|
-
openlit-1.33.
|
131
|
-
openlit-1.33.
|
132
|
-
openlit-1.33.
|
129
|
+
openlit/semcov/__init__.py,sha256=_AYtEgo44psrG93XqA2gtxDXZNg3kcLyxMJ-xiV3c9U,13415
|
130
|
+
openlit-1.33.23.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
131
|
+
openlit-1.33.23.dist-info/METADATA,sha256=fTK6ToBIYL4z0bteu_gkeiKtWFMkGw90UKM-lVHrw2w,23470
|
132
|
+
openlit-1.33.23.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
133
|
+
openlit-1.33.23.dist-info/RECORD,,
|
File without changes
|
File without changes
|