opentelemetry-instrumentation-vertexai 0.45.0__tar.gz → 0.49.6__tar.gz
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.
- {opentelemetry_instrumentation_vertexai-0.45.0 → opentelemetry_instrumentation_vertexai-0.49.6}/PKG-INFO +7 -6
- {opentelemetry_instrumentation_vertexai-0.45.0 → opentelemetry_instrumentation_vertexai-0.49.6}/opentelemetry/instrumentation/vertexai/__init__.py +32 -11
- opentelemetry_instrumentation_vertexai-0.49.6/opentelemetry/instrumentation/vertexai/config.py +9 -0
- {opentelemetry_instrumentation_vertexai-0.45.0 → opentelemetry_instrumentation_vertexai-0.49.6}/opentelemetry/instrumentation/vertexai/event_emitter.py +13 -4
- opentelemetry_instrumentation_vertexai-0.49.6/opentelemetry/instrumentation/vertexai/span_utils.py +313 -0
- opentelemetry_instrumentation_vertexai-0.49.6/opentelemetry/instrumentation/vertexai/version.py +1 -0
- {opentelemetry_instrumentation_vertexai-0.45.0 → opentelemetry_instrumentation_vertexai-0.49.6}/pyproject.toml +6 -6
- opentelemetry_instrumentation_vertexai-0.45.0/opentelemetry/instrumentation/vertexai/config.py +0 -3
- opentelemetry_instrumentation_vertexai-0.45.0/opentelemetry/instrumentation/vertexai/span_utils.py +0 -89
- opentelemetry_instrumentation_vertexai-0.45.0/opentelemetry/instrumentation/vertexai/version.py +0 -1
- {opentelemetry_instrumentation_vertexai-0.45.0 → opentelemetry_instrumentation_vertexai-0.49.6}/README.md +0 -0
- {opentelemetry_instrumentation_vertexai-0.45.0 → opentelemetry_instrumentation_vertexai-0.49.6}/opentelemetry/instrumentation/vertexai/event_models.py +0 -0
- {opentelemetry_instrumentation_vertexai-0.45.0 → opentelemetry_instrumentation_vertexai-0.49.6}/opentelemetry/instrumentation/vertexai/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: opentelemetry-instrumentation-vertexai
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.49.6
|
|
4
4
|
Summary: OpenTelemetry Vertex AI instrumentation
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
Author: Gal Kleinman
|
|
@@ -13,11 +13,12 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
17
|
Provides-Extra: instruments
|
|
17
|
-
Requires-Dist: opentelemetry-api (>=1.
|
|
18
|
-
Requires-Dist: opentelemetry-instrumentation (>=0.
|
|
19
|
-
Requires-Dist: opentelemetry-semantic-conventions (>=0.
|
|
20
|
-
Requires-Dist: opentelemetry-semantic-conventions-ai (
|
|
18
|
+
Requires-Dist: opentelemetry-api (>=1.38.0,<2.0.0)
|
|
19
|
+
Requires-Dist: opentelemetry-instrumentation (>=0.59b0)
|
|
20
|
+
Requires-Dist: opentelemetry-semantic-conventions (>=0.59b0)
|
|
21
|
+
Requires-Dist: opentelemetry-semantic-conventions-ai (>=0.4.13,<0.5.0)
|
|
21
22
|
Project-URL: Repository, https://github.com/traceloop/openllmetry/tree/main/packages/opentelemetry-instrumentation-vertexai
|
|
22
23
|
Description-Content-Type: text/markdown
|
|
23
24
|
|
|
@@ -5,7 +5,7 @@ import types
|
|
|
5
5
|
from typing import Collection
|
|
6
6
|
|
|
7
7
|
from opentelemetry import context as context_api
|
|
8
|
-
from opentelemetry.
|
|
8
|
+
from opentelemetry._logs import get_logger
|
|
9
9
|
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
|
|
10
10
|
from opentelemetry.instrumentation.utils import _SUPPRESS_INSTRUMENTATION_KEY, unwrap
|
|
11
11
|
from opentelemetry.instrumentation.vertexai.config import Config
|
|
@@ -15,12 +15,16 @@ from opentelemetry.instrumentation.vertexai.event_emitter import (
|
|
|
15
15
|
)
|
|
16
16
|
from opentelemetry.instrumentation.vertexai.span_utils import (
|
|
17
17
|
set_input_attributes,
|
|
18
|
+
set_input_attributes_sync,
|
|
18
19
|
set_model_input_attributes,
|
|
19
20
|
set_model_response_attributes,
|
|
20
21
|
set_response_attributes,
|
|
21
22
|
)
|
|
22
23
|
from opentelemetry.instrumentation.vertexai.utils import dont_throw, should_emit_events
|
|
23
24
|
from opentelemetry.instrumentation.vertexai.version import __version__
|
|
25
|
+
from opentelemetry.semconv._incubating.attributes import (
|
|
26
|
+
gen_ai_attributes as GenAIAttributes,
|
|
27
|
+
)
|
|
24
28
|
from opentelemetry.semconv_ai import (
|
|
25
29
|
SUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION_KEY,
|
|
26
30
|
LLMRequestTypeValues,
|
|
@@ -178,12 +182,12 @@ async def _abuild_from_streaming_response(span, event_logger, response, llm_mode
|
|
|
178
182
|
|
|
179
183
|
|
|
180
184
|
@dont_throw
|
|
181
|
-
def _handle_request(span, event_logger, args, kwargs, llm_model):
|
|
185
|
+
async def _handle_request(span, event_logger, args, kwargs, llm_model):
|
|
182
186
|
set_model_input_attributes(span, kwargs, llm_model)
|
|
183
187
|
if should_emit_events():
|
|
184
188
|
emit_prompt_events(args, event_logger)
|
|
185
189
|
else:
|
|
186
|
-
set_input_attributes(span, args)
|
|
190
|
+
await set_input_attributes(span, args)
|
|
187
191
|
|
|
188
192
|
|
|
189
193
|
def _handle_response(span, event_logger, response, llm_model):
|
|
@@ -223,18 +227,23 @@ async def _awrap(tracer, event_logger, to_wrap, wrapped, instance, args, kwargs)
|
|
|
223
227
|
llm_model = instance._model_id
|
|
224
228
|
if hasattr(instance, "_model_name"):
|
|
225
229
|
llm_model = instance._model_name.replace("publishers/google/models/", "")
|
|
230
|
+
# For ChatSession, try to get model from the parent model object
|
|
231
|
+
if hasattr(instance, "_model") and hasattr(instance._model, "_model_name"):
|
|
232
|
+
llm_model = instance._model._model_name.replace("publishers/google/models/", "")
|
|
233
|
+
elif hasattr(instance, "_model") and hasattr(instance._model, "_model_id"):
|
|
234
|
+
llm_model = instance._model._model_id
|
|
226
235
|
|
|
227
236
|
name = to_wrap.get("span_name")
|
|
228
237
|
span = tracer.start_span(
|
|
229
238
|
name,
|
|
230
239
|
kind=SpanKind.CLIENT,
|
|
231
240
|
attributes={
|
|
232
|
-
|
|
241
|
+
GenAIAttributes.GEN_AI_SYSTEM: "Google",
|
|
233
242
|
SpanAttributes.LLM_REQUEST_TYPE: LLMRequestTypeValues.COMPLETION.value,
|
|
234
243
|
},
|
|
235
244
|
)
|
|
236
245
|
|
|
237
|
-
_handle_request(span, event_logger, args, kwargs, llm_model)
|
|
246
|
+
await _handle_request(span, event_logger, args, kwargs, llm_model)
|
|
238
247
|
|
|
239
248
|
response = await wrapped(*args, **kwargs)
|
|
240
249
|
|
|
@@ -267,18 +276,28 @@ def _wrap(tracer, event_logger, to_wrap, wrapped, instance, args, kwargs):
|
|
|
267
276
|
llm_model = instance._model_id
|
|
268
277
|
if hasattr(instance, "_model_name"):
|
|
269
278
|
llm_model = instance._model_name.replace("publishers/google/models/", "")
|
|
279
|
+
# For ChatSession, try to get model from the parent model object
|
|
280
|
+
if hasattr(instance, "_model") and hasattr(instance._model, "_model_name"):
|
|
281
|
+
llm_model = instance._model._model_name.replace("publishers/google/models/", "")
|
|
282
|
+
elif hasattr(instance, "_model") and hasattr(instance._model, "_model_id"):
|
|
283
|
+
llm_model = instance._model._model_id
|
|
270
284
|
|
|
271
285
|
name = to_wrap.get("span_name")
|
|
272
286
|
span = tracer.start_span(
|
|
273
287
|
name,
|
|
274
288
|
kind=SpanKind.CLIENT,
|
|
275
289
|
attributes={
|
|
276
|
-
|
|
290
|
+
GenAIAttributes.GEN_AI_SYSTEM: "Google",
|
|
277
291
|
SpanAttributes.LLM_REQUEST_TYPE: LLMRequestTypeValues.COMPLETION.value,
|
|
278
292
|
},
|
|
279
293
|
)
|
|
280
294
|
|
|
281
|
-
|
|
295
|
+
# Use sync version for non-async wrapper to avoid image processing for now
|
|
296
|
+
set_model_input_attributes(span, kwargs, llm_model)
|
|
297
|
+
if should_emit_events():
|
|
298
|
+
emit_prompt_events(args, event_logger)
|
|
299
|
+
else:
|
|
300
|
+
set_input_attributes_sync(span, args)
|
|
282
301
|
|
|
283
302
|
response = wrapped(*args, **kwargs)
|
|
284
303
|
|
|
@@ -301,10 +320,12 @@ def _wrap(tracer, event_logger, to_wrap, wrapped, instance, args, kwargs):
|
|
|
301
320
|
class VertexAIInstrumentor(BaseInstrumentor):
|
|
302
321
|
"""An instrumentor for VertextAI's client library."""
|
|
303
322
|
|
|
304
|
-
def __init__(self, exception_logger=None, use_legacy_attributes=True):
|
|
323
|
+
def __init__(self, exception_logger=None, use_legacy_attributes=True, upload_base64_image=None):
|
|
305
324
|
super().__init__()
|
|
306
325
|
Config.exception_logger = exception_logger
|
|
307
326
|
Config.use_legacy_attributes = use_legacy_attributes
|
|
327
|
+
if upload_base64_image:
|
|
328
|
+
Config.upload_base64_image = upload_base64_image
|
|
308
329
|
|
|
309
330
|
def instrumentation_dependencies(self) -> Collection[str]:
|
|
310
331
|
return _instruments
|
|
@@ -316,11 +337,11 @@ class VertexAIInstrumentor(BaseInstrumentor):
|
|
|
316
337
|
event_logger = None
|
|
317
338
|
|
|
318
339
|
if should_emit_events():
|
|
319
|
-
|
|
320
|
-
event_logger =
|
|
340
|
+
logger_provider = kwargs.get("logger_provider")
|
|
341
|
+
event_logger = get_logger(
|
|
321
342
|
__name__,
|
|
322
343
|
__version__,
|
|
323
|
-
|
|
344
|
+
logger_provider=logger_provider,
|
|
324
345
|
)
|
|
325
346
|
|
|
326
347
|
for wrapped_method in WRAPPED_METHODS:
|
|
@@ -2,7 +2,7 @@ from dataclasses import asdict
|
|
|
2
2
|
from enum import Enum
|
|
3
3
|
from typing import Union
|
|
4
4
|
|
|
5
|
-
from opentelemetry.
|
|
5
|
+
from opentelemetry._logs import LogRecord
|
|
6
6
|
from opentelemetry.instrumentation.vertexai.event_models import (
|
|
7
7
|
ChoiceEvent,
|
|
8
8
|
MessageEvent,
|
|
@@ -140,7 +140,12 @@ def _emit_message_event(event: MessageEvent, event_logger) -> None:
|
|
|
140
140
|
for tool_call in body["tool_calls"]:
|
|
141
141
|
tool_call["function"].pop("arguments", None)
|
|
142
142
|
|
|
143
|
-
|
|
143
|
+
log_record = LogRecord(
|
|
144
|
+
body=body,
|
|
145
|
+
attributes=EVENT_ATTRIBUTES,
|
|
146
|
+
event_name=name
|
|
147
|
+
)
|
|
148
|
+
event_logger.emit(log_record)
|
|
144
149
|
|
|
145
150
|
|
|
146
151
|
def _emit_choice_event(event: ChoiceEvent, event_logger) -> None:
|
|
@@ -159,6 +164,10 @@ def _emit_choice_event(event: ChoiceEvent, event_logger) -> None:
|
|
|
159
164
|
for tool_call in body["tool_calls"]:
|
|
160
165
|
tool_call["function"].pop("arguments", None)
|
|
161
166
|
|
|
162
|
-
|
|
163
|
-
|
|
167
|
+
log_record = LogRecord(
|
|
168
|
+
body=body,
|
|
169
|
+
attributes=EVENT_ATTRIBUTES,
|
|
170
|
+
event_name="gen_ai.choice"
|
|
171
|
+
|
|
164
172
|
)
|
|
173
|
+
event_logger.emit(log_record)
|
opentelemetry_instrumentation_vertexai-0.49.6/opentelemetry/instrumentation/vertexai/span_utils.py
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import json
|
|
3
|
+
import base64
|
|
4
|
+
import logging
|
|
5
|
+
import asyncio
|
|
6
|
+
import threading
|
|
7
|
+
from opentelemetry.instrumentation.vertexai.utils import dont_throw, should_send_prompts
|
|
8
|
+
from opentelemetry.instrumentation.vertexai.config import Config
|
|
9
|
+
from opentelemetry.semconv._incubating.attributes import (
|
|
10
|
+
gen_ai_attributes as GenAIAttributes,
|
|
11
|
+
)
|
|
12
|
+
from opentelemetry.semconv_ai import SpanAttributes
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _set_span_attribute(span, name, value):
|
|
19
|
+
if value is not None:
|
|
20
|
+
if value != "":
|
|
21
|
+
span.set_attribute(name, value)
|
|
22
|
+
return
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _is_base64_image_part(item):
|
|
26
|
+
"""Check if item is a VertexAI Part object containing image data"""
|
|
27
|
+
try:
|
|
28
|
+
# Check if it has the Part attributes we expect
|
|
29
|
+
if not hasattr(item, 'inline_data') or not hasattr(item, 'mime_type'):
|
|
30
|
+
return False
|
|
31
|
+
|
|
32
|
+
# Check if it's an image mime type and has inline data
|
|
33
|
+
if item.mime_type and 'image/' in item.mime_type and item.inline_data:
|
|
34
|
+
# Check if the inline_data has actual data
|
|
35
|
+
if hasattr(item.inline_data, 'data') and item.inline_data.data:
|
|
36
|
+
return True
|
|
37
|
+
|
|
38
|
+
return False
|
|
39
|
+
except Exception:
|
|
40
|
+
return False
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
async def _process_image_part(item, trace_id, span_id, content_index):
|
|
44
|
+
"""Process a VertexAI Part object containing image data"""
|
|
45
|
+
if not Config.upload_base64_image:
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
# Extract format from mime type (e.g., 'image/jpeg' -> 'jpeg')
|
|
50
|
+
image_format = item.mime_type.split('/')[1] if item.mime_type else 'unknown'
|
|
51
|
+
image_name = f"content_{content_index}.{image_format}"
|
|
52
|
+
|
|
53
|
+
# Convert binary data to base64 string for upload
|
|
54
|
+
binary_data = item.inline_data.data
|
|
55
|
+
base64_string = base64.b64encode(binary_data).decode('utf-8')
|
|
56
|
+
|
|
57
|
+
# Upload the base64 data - convert IDs to strings
|
|
58
|
+
url = await Config.upload_base64_image(str(trace_id), str(span_id), image_name, base64_string)
|
|
59
|
+
|
|
60
|
+
# Return OpenAI-compatible format for consistency across LLM providers
|
|
61
|
+
return {
|
|
62
|
+
"type": "image_url",
|
|
63
|
+
"image_url": {"url": url}
|
|
64
|
+
}
|
|
65
|
+
except Exception as e:
|
|
66
|
+
logger.warning(f"Failed to process image part: {e}")
|
|
67
|
+
# Return None to skip adding this image to the span
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def run_async(method):
|
|
72
|
+
"""Handle async method in sync context, following OpenAI's battle-tested approach"""
|
|
73
|
+
try:
|
|
74
|
+
loop = asyncio.get_running_loop()
|
|
75
|
+
except RuntimeError:
|
|
76
|
+
loop = None
|
|
77
|
+
|
|
78
|
+
if loop and loop.is_running():
|
|
79
|
+
thread = threading.Thread(target=lambda: asyncio.run(method))
|
|
80
|
+
thread.start()
|
|
81
|
+
thread.join()
|
|
82
|
+
else:
|
|
83
|
+
asyncio.run(method)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _process_image_part_sync(item, trace_id, span_id, content_index):
|
|
87
|
+
"""Synchronous version of image part processing using OpenAI's pattern"""
|
|
88
|
+
if not Config.upload_base64_image:
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
# Extract format from mime type (e.g., 'image/jpeg' -> 'jpeg')
|
|
93
|
+
image_format = item.mime_type.split('/')[1] if item.mime_type else 'unknown'
|
|
94
|
+
image_name = f"content_{content_index}.{image_format}"
|
|
95
|
+
|
|
96
|
+
# Convert binary data to base64 string for upload
|
|
97
|
+
binary_data = item.inline_data.data
|
|
98
|
+
base64_string = base64.b64encode(binary_data).decode('utf-8')
|
|
99
|
+
|
|
100
|
+
# Use OpenAI's run_async pattern to handle the async upload function
|
|
101
|
+
url = None
|
|
102
|
+
|
|
103
|
+
async def upload_task():
|
|
104
|
+
nonlocal url
|
|
105
|
+
url = await Config.upload_base64_image(str(trace_id), str(span_id), image_name, base64_string)
|
|
106
|
+
|
|
107
|
+
run_async(upload_task())
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
"type": "image_url",
|
|
111
|
+
"image_url": {"url": url}
|
|
112
|
+
}
|
|
113
|
+
except Exception as e:
|
|
114
|
+
logger.warning(f"Failed to process image part sync: {e}")
|
|
115
|
+
# Return None to skip adding this image to the span
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
async def _process_vertexai_argument(argument, span):
|
|
120
|
+
"""Process a single argument for VertexAI, handling different types"""
|
|
121
|
+
if isinstance(argument, str):
|
|
122
|
+
# Simple text argument in OpenAI format
|
|
123
|
+
return [{"type": "text", "text": argument}]
|
|
124
|
+
|
|
125
|
+
elif isinstance(argument, list):
|
|
126
|
+
# List of mixed content (text strings and Part objects) - deep copy and process
|
|
127
|
+
content_list = copy.deepcopy(argument)
|
|
128
|
+
processed_items = []
|
|
129
|
+
|
|
130
|
+
for item_index, content_item in enumerate(content_list):
|
|
131
|
+
processed_item = await _process_content_item_vertexai(content_item, span, item_index)
|
|
132
|
+
if processed_item is not None:
|
|
133
|
+
processed_items.append(processed_item)
|
|
134
|
+
|
|
135
|
+
return processed_items
|
|
136
|
+
|
|
137
|
+
else:
|
|
138
|
+
# Single Part object - convert to OpenAI format
|
|
139
|
+
processed_item = await _process_content_item_vertexai(argument, span, 0)
|
|
140
|
+
return [processed_item] if processed_item is not None else []
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
async def _process_content_item_vertexai(content_item, span, item_index):
|
|
144
|
+
"""Process a single content item for VertexAI"""
|
|
145
|
+
if isinstance(content_item, str):
|
|
146
|
+
# Convert text to OpenAI format
|
|
147
|
+
return {"type": "text", "text": content_item}
|
|
148
|
+
|
|
149
|
+
elif _is_base64_image_part(content_item):
|
|
150
|
+
# Process image part
|
|
151
|
+
return await _process_image_part(
|
|
152
|
+
content_item, span.context.trace_id, span.context.span_id, item_index
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
elif hasattr(content_item, 'text'):
|
|
156
|
+
# Text part to OpenAI format
|
|
157
|
+
return {"type": "text", "text": content_item.text}
|
|
158
|
+
|
|
159
|
+
else:
|
|
160
|
+
# Other types as text
|
|
161
|
+
return {"type": "text", "text": str(content_item)}
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _process_vertexai_argument_sync(argument, span):
|
|
165
|
+
"""Synchronous version of argument processing for VertexAI"""
|
|
166
|
+
if isinstance(argument, str):
|
|
167
|
+
# Simple text argument in OpenAI format
|
|
168
|
+
return [{"type": "text", "text": argument}]
|
|
169
|
+
|
|
170
|
+
elif isinstance(argument, list):
|
|
171
|
+
# List of mixed content (text strings and Part objects) - deep copy and process
|
|
172
|
+
content_list = copy.deepcopy(argument)
|
|
173
|
+
processed_items = []
|
|
174
|
+
|
|
175
|
+
for item_index, content_item in enumerate(content_list):
|
|
176
|
+
processed_item = _process_content_item_vertexai_sync(content_item, span, item_index)
|
|
177
|
+
if processed_item is not None:
|
|
178
|
+
processed_items.append(processed_item)
|
|
179
|
+
|
|
180
|
+
return processed_items
|
|
181
|
+
|
|
182
|
+
else:
|
|
183
|
+
# Single Part object - convert to OpenAI format
|
|
184
|
+
processed_item = _process_content_item_vertexai_sync(argument, span, 0)
|
|
185
|
+
return [processed_item] if processed_item is not None else []
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _process_content_item_vertexai_sync(content_item, span, item_index):
|
|
189
|
+
"""Synchronous version of content item processing for VertexAI"""
|
|
190
|
+
if isinstance(content_item, str):
|
|
191
|
+
# Convert text to OpenAI format
|
|
192
|
+
return {"type": "text", "text": content_item}
|
|
193
|
+
|
|
194
|
+
elif _is_base64_image_part(content_item):
|
|
195
|
+
# Process image part
|
|
196
|
+
return _process_image_part_sync(
|
|
197
|
+
content_item, span.context.trace_id, span.context.span_id, item_index
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
elif hasattr(content_item, 'text'):
|
|
201
|
+
# Text part to OpenAI format
|
|
202
|
+
return {"type": "text", "text": content_item.text}
|
|
203
|
+
|
|
204
|
+
else:
|
|
205
|
+
# Other types as text
|
|
206
|
+
return {"type": "text", "text": str(content_item)}
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@dont_throw
|
|
210
|
+
async def set_input_attributes(span, args):
|
|
211
|
+
"""Process input arguments, handling both text and image content"""
|
|
212
|
+
if not span.is_recording():
|
|
213
|
+
return
|
|
214
|
+
if should_send_prompts() and args is not None and len(args) > 0:
|
|
215
|
+
# Process each argument using extracted helper methods
|
|
216
|
+
for arg_index, argument in enumerate(args):
|
|
217
|
+
processed_content = await _process_vertexai_argument(argument, span)
|
|
218
|
+
|
|
219
|
+
if processed_content:
|
|
220
|
+
_set_span_attribute(
|
|
221
|
+
span,
|
|
222
|
+
f"{GenAIAttributes.GEN_AI_PROMPT}.{arg_index}.role",
|
|
223
|
+
"user"
|
|
224
|
+
)
|
|
225
|
+
_set_span_attribute(
|
|
226
|
+
span,
|
|
227
|
+
f"{GenAIAttributes.GEN_AI_PROMPT}.{arg_index}.content",
|
|
228
|
+
json.dumps(processed_content)
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
# Sync version with image processing support
|
|
233
|
+
@dont_throw
|
|
234
|
+
def set_input_attributes_sync(span, args):
|
|
235
|
+
"""Synchronous version with image processing support"""
|
|
236
|
+
if not span.is_recording():
|
|
237
|
+
return
|
|
238
|
+
if should_send_prompts() and args is not None and len(args) > 0:
|
|
239
|
+
# Process each argument using extracted helper methods
|
|
240
|
+
for arg_index, argument in enumerate(args):
|
|
241
|
+
processed_content = _process_vertexai_argument_sync(argument, span)
|
|
242
|
+
|
|
243
|
+
if processed_content:
|
|
244
|
+
_set_span_attribute(
|
|
245
|
+
span,
|
|
246
|
+
f"{GenAIAttributes.GEN_AI_PROMPT}.{arg_index}.role",
|
|
247
|
+
"user"
|
|
248
|
+
)
|
|
249
|
+
_set_span_attribute(
|
|
250
|
+
span,
|
|
251
|
+
f"{GenAIAttributes.GEN_AI_PROMPT}.{arg_index}.content",
|
|
252
|
+
json.dumps(processed_content)
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
@dont_throw
|
|
257
|
+
def set_model_input_attributes(span, kwargs, llm_model):
|
|
258
|
+
if not span.is_recording():
|
|
259
|
+
return
|
|
260
|
+
_set_span_attribute(span, GenAIAttributes.GEN_AI_REQUEST_MODEL, llm_model)
|
|
261
|
+
_set_span_attribute(
|
|
262
|
+
span, f"{GenAIAttributes.GEN_AI_PROMPT}.0.user", kwargs.get("prompt")
|
|
263
|
+
)
|
|
264
|
+
_set_span_attribute(
|
|
265
|
+
span, GenAIAttributes.GEN_AI_REQUEST_TEMPERATURE, kwargs.get("temperature")
|
|
266
|
+
)
|
|
267
|
+
_set_span_attribute(
|
|
268
|
+
span, GenAIAttributes.GEN_AI_REQUEST_MAX_TOKENS, kwargs.get("max_output_tokens")
|
|
269
|
+
)
|
|
270
|
+
_set_span_attribute(span, GenAIAttributes.GEN_AI_REQUEST_TOP_P, kwargs.get("top_p"))
|
|
271
|
+
_set_span_attribute(span, GenAIAttributes.GEN_AI_REQUEST_TOP_K, kwargs.get("top_k"))
|
|
272
|
+
_set_span_attribute(
|
|
273
|
+
span, SpanAttributes.LLM_PRESENCE_PENALTY, kwargs.get("presence_penalty")
|
|
274
|
+
)
|
|
275
|
+
_set_span_attribute(
|
|
276
|
+
span, SpanAttributes.LLM_FREQUENCY_PENALTY, kwargs.get("frequency_penalty")
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
@dont_throw
|
|
281
|
+
def set_response_attributes(span, llm_model, generation_text):
|
|
282
|
+
if not span.is_recording() or not should_send_prompts():
|
|
283
|
+
return
|
|
284
|
+
_set_span_attribute(span, f"{GenAIAttributes.GEN_AI_COMPLETION}.0.role", "assistant")
|
|
285
|
+
_set_span_attribute(
|
|
286
|
+
span,
|
|
287
|
+
f"{GenAIAttributes.GEN_AI_COMPLETION}.0.content",
|
|
288
|
+
generation_text,
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
@dont_throw
|
|
293
|
+
def set_model_response_attributes(span, llm_model, token_usage):
|
|
294
|
+
if not span.is_recording():
|
|
295
|
+
return
|
|
296
|
+
_set_span_attribute(span, GenAIAttributes.GEN_AI_RESPONSE_MODEL, llm_model)
|
|
297
|
+
|
|
298
|
+
if token_usage:
|
|
299
|
+
_set_span_attribute(
|
|
300
|
+
span,
|
|
301
|
+
SpanAttributes.LLM_USAGE_TOTAL_TOKENS,
|
|
302
|
+
token_usage.total_token_count,
|
|
303
|
+
)
|
|
304
|
+
_set_span_attribute(
|
|
305
|
+
span,
|
|
306
|
+
GenAIAttributes.GEN_AI_USAGE_OUTPUT_TOKENS,
|
|
307
|
+
token_usage.candidates_token_count,
|
|
308
|
+
)
|
|
309
|
+
_set_span_attribute(
|
|
310
|
+
span,
|
|
311
|
+
GenAIAttributes.GEN_AI_USAGE_INPUT_TOKENS,
|
|
312
|
+
token_usage.prompt_token_count,
|
|
313
|
+
)
|
opentelemetry_instrumentation_vertexai-0.49.6/opentelemetry/instrumentation/vertexai/version.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.49.6"
|
|
@@ -8,7 +8,7 @@ show_missing = true
|
|
|
8
8
|
|
|
9
9
|
[tool.poetry]
|
|
10
10
|
name = "opentelemetry-instrumentation-vertexai"
|
|
11
|
-
version = "0.
|
|
11
|
+
version = "0.49.6"
|
|
12
12
|
description = "OpenTelemetry Vertex AI instrumentation"
|
|
13
13
|
authors = [
|
|
14
14
|
"Gal Kleinman <gal@traceloop.com>",
|
|
@@ -25,10 +25,10 @@ include = "opentelemetry/instrumentation/vertexai"
|
|
|
25
25
|
|
|
26
26
|
[tool.poetry.dependencies]
|
|
27
27
|
python = ">=3.9,<4"
|
|
28
|
-
opentelemetry-api = "^1.
|
|
29
|
-
opentelemetry-instrumentation = ">=0.
|
|
30
|
-
opentelemetry-semantic-conventions = ">=0.
|
|
31
|
-
opentelemetry-semantic-conventions-ai = "0.4.
|
|
28
|
+
opentelemetry-api = "^1.38.0"
|
|
29
|
+
opentelemetry-instrumentation = ">=0.59b0"
|
|
30
|
+
opentelemetry-semantic-conventions = ">=0.59b0"
|
|
31
|
+
opentelemetry-semantic-conventions-ai = "^0.4.13"
|
|
32
32
|
|
|
33
33
|
[tool.poetry.group.dev.dependencies]
|
|
34
34
|
autopep8 = "^2.2.0"
|
|
@@ -41,7 +41,7 @@ pytest = "^8.2.2"
|
|
|
41
41
|
pytest-sugar = "1.0.0"
|
|
42
42
|
vcrpy = "^6.0.1"
|
|
43
43
|
pytest-recording = "^0.13.1"
|
|
44
|
-
opentelemetry-sdk = "^1.
|
|
44
|
+
opentelemetry-sdk = "^1.38.0"
|
|
45
45
|
google-cloud-aiplatform = "^1.54.0"
|
|
46
46
|
|
|
47
47
|
[build-system]
|
opentelemetry_instrumentation_vertexai-0.45.0/opentelemetry/instrumentation/vertexai/span_utils.py
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
from opentelemetry.instrumentation.vertexai.utils import dont_throw, should_send_prompts
|
|
2
|
-
from opentelemetry.semconv_ai import SpanAttributes
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
def _set_span_attribute(span, name, value):
|
|
6
|
-
if value is not None:
|
|
7
|
-
if value != "":
|
|
8
|
-
span.set_attribute(name, value)
|
|
9
|
-
return
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
@dont_throw
|
|
13
|
-
def set_input_attributes(span, args):
|
|
14
|
-
if not span.is_recording():
|
|
15
|
-
return
|
|
16
|
-
if should_send_prompts() and args is not None and len(args) > 0:
|
|
17
|
-
prompt = ""
|
|
18
|
-
for arg in args:
|
|
19
|
-
if isinstance(arg, str):
|
|
20
|
-
prompt = f"{prompt}{arg}\n"
|
|
21
|
-
elif isinstance(arg, list):
|
|
22
|
-
for subarg in arg:
|
|
23
|
-
prompt = f"{prompt}{subarg}\n"
|
|
24
|
-
|
|
25
|
-
_set_span_attribute(
|
|
26
|
-
span,
|
|
27
|
-
f"{SpanAttributes.LLM_PROMPTS}.0.user",
|
|
28
|
-
prompt,
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
@dont_throw
|
|
33
|
-
def set_model_input_attributes(span, kwargs, llm_model):
|
|
34
|
-
if not span.is_recording():
|
|
35
|
-
return
|
|
36
|
-
_set_span_attribute(span, SpanAttributes.LLM_REQUEST_MODEL, llm_model)
|
|
37
|
-
_set_span_attribute(
|
|
38
|
-
span, f"{SpanAttributes.LLM_PROMPTS}.0.user", kwargs.get("prompt")
|
|
39
|
-
)
|
|
40
|
-
_set_span_attribute(
|
|
41
|
-
span, SpanAttributes.LLM_REQUEST_TEMPERATURE, kwargs.get("temperature")
|
|
42
|
-
)
|
|
43
|
-
_set_span_attribute(
|
|
44
|
-
span, SpanAttributes.LLM_REQUEST_MAX_TOKENS, kwargs.get("max_output_tokens")
|
|
45
|
-
)
|
|
46
|
-
_set_span_attribute(span, SpanAttributes.LLM_REQUEST_TOP_P, kwargs.get("top_p"))
|
|
47
|
-
_set_span_attribute(span, SpanAttributes.LLM_TOP_K, kwargs.get("top_k"))
|
|
48
|
-
_set_span_attribute(
|
|
49
|
-
span, SpanAttributes.LLM_PRESENCE_PENALTY, kwargs.get("presence_penalty")
|
|
50
|
-
)
|
|
51
|
-
_set_span_attribute(
|
|
52
|
-
span, SpanAttributes.LLM_FREQUENCY_PENALTY, kwargs.get("frequency_penalty")
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
@dont_throw
|
|
57
|
-
def set_response_attributes(span, llm_model, generation_text):
|
|
58
|
-
if not span.is_recording() or not should_send_prompts():
|
|
59
|
-
return
|
|
60
|
-
_set_span_attribute(span, f"{SpanAttributes.LLM_COMPLETIONS}.0.role", "assistant")
|
|
61
|
-
_set_span_attribute(
|
|
62
|
-
span,
|
|
63
|
-
f"{SpanAttributes.LLM_COMPLETIONS}.0.content",
|
|
64
|
-
generation_text,
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
@dont_throw
|
|
69
|
-
def set_model_response_attributes(span, llm_model, token_usage):
|
|
70
|
-
if not span.is_recording():
|
|
71
|
-
return
|
|
72
|
-
_set_span_attribute(span, SpanAttributes.LLM_RESPONSE_MODEL, llm_model)
|
|
73
|
-
|
|
74
|
-
if token_usage:
|
|
75
|
-
_set_span_attribute(
|
|
76
|
-
span,
|
|
77
|
-
SpanAttributes.LLM_USAGE_TOTAL_TOKENS,
|
|
78
|
-
token_usage.total_token_count,
|
|
79
|
-
)
|
|
80
|
-
_set_span_attribute(
|
|
81
|
-
span,
|
|
82
|
-
SpanAttributes.LLM_USAGE_COMPLETION_TOKENS,
|
|
83
|
-
token_usage.candidates_token_count,
|
|
84
|
-
)
|
|
85
|
-
_set_span_attribute(
|
|
86
|
-
span,
|
|
87
|
-
SpanAttributes.LLM_USAGE_PROMPT_TOKENS,
|
|
88
|
-
token_usage.prompt_token_count,
|
|
89
|
-
)
|
opentelemetry_instrumentation_vertexai-0.45.0/opentelemetry/instrumentation/vertexai/version.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.45.0"
|
|
File without changes
|
|
File without changes
|