opentelemetry-instrumentation-groq 0.38.6__tar.gz → 0.38.8__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.
Potentially problematic release.
This version of opentelemetry-instrumentation-groq might be problematic. Click here for more details.
- {opentelemetry_instrumentation_groq-0.38.6 → opentelemetry_instrumentation_groq-0.38.8}/PKG-INFO +1 -1
- {opentelemetry_instrumentation_groq-0.38.6 → opentelemetry_instrumentation_groq-0.38.8}/opentelemetry/instrumentation/groq/__init__.py +139 -17
- opentelemetry_instrumentation_groq-0.38.8/opentelemetry/instrumentation/groq/version.py +1 -0
- {opentelemetry_instrumentation_groq-0.38.6 → opentelemetry_instrumentation_groq-0.38.8}/pyproject.toml +2 -2
- opentelemetry_instrumentation_groq-0.38.6/opentelemetry/instrumentation/groq/version.py +0 -1
- {opentelemetry_instrumentation_groq-0.38.6 → opentelemetry_instrumentation_groq-0.38.8}/README.md +0 -0
- {opentelemetry_instrumentation_groq-0.38.6 → opentelemetry_instrumentation_groq-0.38.8}/opentelemetry/instrumentation/groq/config.py +0 -0
- {opentelemetry_instrumentation_groq-0.38.6 → opentelemetry_instrumentation_groq-0.38.8}/opentelemetry/instrumentation/groq/utils.py +0 -0
|
@@ -21,7 +21,9 @@ from opentelemetry.instrumentation.groq.version import __version__
|
|
|
21
21
|
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
|
|
22
22
|
from opentelemetry.instrumentation.utils import _SUPPRESS_INSTRUMENTATION_KEY, unwrap
|
|
23
23
|
from opentelemetry.metrics import Counter, Histogram, Meter, get_meter
|
|
24
|
-
from opentelemetry.semconv._incubating.attributes.gen_ai_attributes import
|
|
24
|
+
from opentelemetry.semconv._incubating.attributes.gen_ai_attributes import (
|
|
25
|
+
GEN_AI_RESPONSE_ID,
|
|
26
|
+
)
|
|
25
27
|
from opentelemetry.semconv_ai import (
|
|
26
28
|
SUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION_KEY,
|
|
27
29
|
LLMRequestTypeValues,
|
|
@@ -97,7 +99,9 @@ def _set_input_attributes(span, kwargs):
|
|
|
97
99
|
set_span_attribute(
|
|
98
100
|
span, SpanAttributes.LLM_PRESENCE_PENALTY, kwargs.get("presence_penalty")
|
|
99
101
|
)
|
|
100
|
-
set_span_attribute(
|
|
102
|
+
set_span_attribute(
|
|
103
|
+
span, SpanAttributes.LLM_IS_STREAMING, kwargs.get("stream") or False
|
|
104
|
+
)
|
|
101
105
|
|
|
102
106
|
if should_send_prompts():
|
|
103
107
|
if kwargs.get("prompt") is not None:
|
|
@@ -124,9 +128,7 @@ def _set_completions(span, choices):
|
|
|
124
128
|
for choice in choices:
|
|
125
129
|
index = choice.get("index")
|
|
126
130
|
prefix = f"{SpanAttributes.LLM_COMPLETIONS}.{index}"
|
|
127
|
-
set_span_attribute(
|
|
128
|
-
span, f"{prefix}.finish_reason", choice.get("finish_reason")
|
|
129
|
-
)
|
|
131
|
+
set_span_attribute(span, f"{prefix}.finish_reason", choice.get("finish_reason"))
|
|
130
132
|
|
|
131
133
|
if choice.get("content_filter_results"):
|
|
132
134
|
set_span_attribute(
|
|
@@ -181,26 +183,38 @@ def _set_completions(span, choices):
|
|
|
181
183
|
|
|
182
184
|
|
|
183
185
|
@dont_throw
|
|
184
|
-
def _set_response_attributes(span, response):
|
|
186
|
+
def _set_response_attributes(span, response, token_histogram):
|
|
185
187
|
response = model_as_dict(response)
|
|
186
|
-
|
|
187
188
|
set_span_attribute(span, SpanAttributes.LLM_RESPONSE_MODEL, response.get("model"))
|
|
188
189
|
set_span_attribute(span, GEN_AI_RESPONSE_ID, response.get("id"))
|
|
189
190
|
|
|
190
|
-
usage = response.get("usage")
|
|
191
|
+
usage = response.get("usage") or {}
|
|
192
|
+
prompt_tokens = usage.get("prompt_tokens")
|
|
193
|
+
completion_tokens = usage.get("completion_tokens")
|
|
191
194
|
if usage:
|
|
192
195
|
set_span_attribute(
|
|
193
196
|
span, SpanAttributes.LLM_USAGE_TOTAL_TOKENS, usage.get("total_tokens")
|
|
194
197
|
)
|
|
195
198
|
set_span_attribute(
|
|
196
199
|
span,
|
|
197
|
-
SpanAttributes.LLM_USAGE_COMPLETION_TOKENS,
|
|
198
|
-
usage.get("completion_tokens"),
|
|
200
|
+
SpanAttributes.LLM_USAGE_COMPLETION_TOKENS, completion_tokens
|
|
199
201
|
)
|
|
200
202
|
set_span_attribute(
|
|
201
|
-
span, SpanAttributes.LLM_USAGE_PROMPT_TOKENS,
|
|
203
|
+
span, SpanAttributes.LLM_USAGE_PROMPT_TOKENS, prompt_tokens
|
|
202
204
|
)
|
|
203
205
|
|
|
206
|
+
if isinstance(prompt_tokens, int) and prompt_tokens >= 0 and token_histogram is not None:
|
|
207
|
+
token_histogram.record(prompt_tokens, attributes={
|
|
208
|
+
SpanAttributes.LLM_TOKEN_TYPE: "input",
|
|
209
|
+
SpanAttributes.LLM_RESPONSE_MODEL: response.get("model")
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
if isinstance(completion_tokens, int) and completion_tokens >= 0 and token_histogram is not None:
|
|
213
|
+
token_histogram.record(completion_tokens, attributes={
|
|
214
|
+
SpanAttributes.LLM_TOKEN_TYPE: "output",
|
|
215
|
+
SpanAttributes.LLM_RESPONSE_MODEL: response.get("model")
|
|
216
|
+
})
|
|
217
|
+
|
|
204
218
|
choices = response.get("choices")
|
|
205
219
|
if should_send_prompts() and choices:
|
|
206
220
|
_set_completions(span, choices)
|
|
@@ -268,6 +282,96 @@ def _create_metrics(meter: Meter):
|
|
|
268
282
|
return token_histogram, choice_counter, duration_histogram
|
|
269
283
|
|
|
270
284
|
|
|
285
|
+
def _process_streaming_chunk(chunk):
|
|
286
|
+
"""Extract content, finish_reason and usage from a streaming chunk."""
|
|
287
|
+
if not chunk.choices:
|
|
288
|
+
return None, None, None
|
|
289
|
+
|
|
290
|
+
delta = chunk.choices[0].delta
|
|
291
|
+
content = delta.content if hasattr(delta, "content") else None
|
|
292
|
+
finish_reason = chunk.choices[0].finish_reason
|
|
293
|
+
|
|
294
|
+
# Extract usage from x_groq if present in the final chunk
|
|
295
|
+
usage = None
|
|
296
|
+
if hasattr(chunk, "x_groq") and chunk.x_groq and chunk.x_groq.usage:
|
|
297
|
+
usage = chunk.x_groq.usage
|
|
298
|
+
|
|
299
|
+
return content, finish_reason, usage
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _set_streaming_response_attributes(
|
|
303
|
+
span, accumulated_content, finish_reason=None, usage=None
|
|
304
|
+
):
|
|
305
|
+
"""Set span attributes for accumulated streaming response."""
|
|
306
|
+
if not span.is_recording():
|
|
307
|
+
return
|
|
308
|
+
|
|
309
|
+
prefix = f"{SpanAttributes.LLM_COMPLETIONS}.0"
|
|
310
|
+
set_span_attribute(span, f"{prefix}.role", "assistant")
|
|
311
|
+
set_span_attribute(span, f"{prefix}.content", accumulated_content)
|
|
312
|
+
if finish_reason:
|
|
313
|
+
set_span_attribute(span, f"{prefix}.finish_reason", finish_reason)
|
|
314
|
+
|
|
315
|
+
if usage:
|
|
316
|
+
set_span_attribute(
|
|
317
|
+
span, SpanAttributes.LLM_USAGE_COMPLETION_TOKENS, usage.completion_tokens
|
|
318
|
+
)
|
|
319
|
+
set_span_attribute(
|
|
320
|
+
span, SpanAttributes.LLM_USAGE_PROMPT_TOKENS, usage.prompt_tokens
|
|
321
|
+
)
|
|
322
|
+
set_span_attribute(
|
|
323
|
+
span, SpanAttributes.LLM_USAGE_TOTAL_TOKENS, usage.total_tokens
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def _create_stream_processor(response, span):
|
|
328
|
+
"""Create a generator that processes a stream while collecting telemetry."""
|
|
329
|
+
accumulated_content = ""
|
|
330
|
+
finish_reason = None
|
|
331
|
+
usage = None
|
|
332
|
+
|
|
333
|
+
for chunk in response:
|
|
334
|
+
content, chunk_finish_reason, chunk_usage = _process_streaming_chunk(chunk)
|
|
335
|
+
if content:
|
|
336
|
+
accumulated_content += content
|
|
337
|
+
if chunk_finish_reason:
|
|
338
|
+
finish_reason = chunk_finish_reason
|
|
339
|
+
if chunk_usage:
|
|
340
|
+
usage = chunk_usage
|
|
341
|
+
yield chunk
|
|
342
|
+
|
|
343
|
+
if span.is_recording():
|
|
344
|
+
_set_streaming_response_attributes(
|
|
345
|
+
span, accumulated_content, finish_reason, usage
|
|
346
|
+
)
|
|
347
|
+
span.set_status(Status(StatusCode.OK))
|
|
348
|
+
span.end()
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
async def _create_async_stream_processor(response, span):
|
|
352
|
+
"""Create an async generator that processes a stream while collecting telemetry."""
|
|
353
|
+
accumulated_content = ""
|
|
354
|
+
finish_reason = None
|
|
355
|
+
usage = None
|
|
356
|
+
|
|
357
|
+
async for chunk in response:
|
|
358
|
+
content, chunk_finish_reason, chunk_usage = _process_streaming_chunk(chunk)
|
|
359
|
+
if content:
|
|
360
|
+
accumulated_content += content
|
|
361
|
+
if chunk_finish_reason:
|
|
362
|
+
finish_reason = chunk_finish_reason
|
|
363
|
+
if chunk_usage:
|
|
364
|
+
usage = chunk_usage
|
|
365
|
+
yield chunk
|
|
366
|
+
|
|
367
|
+
if span.is_recording():
|
|
368
|
+
_set_streaming_response_attributes(
|
|
369
|
+
span, accumulated_content, finish_reason, usage
|
|
370
|
+
)
|
|
371
|
+
span.set_status(Status(StatusCode.OK))
|
|
372
|
+
span.end()
|
|
373
|
+
|
|
374
|
+
|
|
271
375
|
@_with_chat_telemetry_wrapper
|
|
272
376
|
def _wrap(
|
|
273
377
|
tracer: Tracer,
|
|
@@ -315,8 +419,16 @@ def _wrap(
|
|
|
315
419
|
end_time = time.time()
|
|
316
420
|
|
|
317
421
|
if is_streaming_response(response):
|
|
318
|
-
|
|
319
|
-
|
|
422
|
+
try:
|
|
423
|
+
return _create_stream_processor(response, span)
|
|
424
|
+
except Exception as ex:
|
|
425
|
+
logger.warning(
|
|
426
|
+
"Failed to process streaming response for groq span, error: %s",
|
|
427
|
+
str(ex),
|
|
428
|
+
)
|
|
429
|
+
span.set_status(Status(StatusCode.ERROR))
|
|
430
|
+
span.end()
|
|
431
|
+
raise
|
|
320
432
|
elif response:
|
|
321
433
|
try:
|
|
322
434
|
metric_attributes = shared_metrics_attributes(response)
|
|
@@ -329,7 +441,7 @@ def _wrap(
|
|
|
329
441
|
)
|
|
330
442
|
|
|
331
443
|
if span.is_recording():
|
|
332
|
-
_set_response_attributes(span, response)
|
|
444
|
+
_set_response_attributes(span, response, token_histogram)
|
|
333
445
|
|
|
334
446
|
except Exception as ex: # pylint: disable=broad-except
|
|
335
447
|
logger.warning(
|
|
@@ -391,9 +503,19 @@ async def _awrap(
|
|
|
391
503
|
|
|
392
504
|
raise e
|
|
393
505
|
|
|
506
|
+
end_time = time.time()
|
|
507
|
+
|
|
394
508
|
if is_streaming_response(response):
|
|
395
|
-
|
|
396
|
-
|
|
509
|
+
try:
|
|
510
|
+
return await _create_async_stream_processor(response, span)
|
|
511
|
+
except Exception as ex:
|
|
512
|
+
logger.warning(
|
|
513
|
+
"Failed to process streaming response for groq span, error: %s",
|
|
514
|
+
str(ex),
|
|
515
|
+
)
|
|
516
|
+
span.set_status(Status(StatusCode.ERROR))
|
|
517
|
+
span.end()
|
|
518
|
+
raise
|
|
397
519
|
elif response:
|
|
398
520
|
metric_attributes = shared_metrics_attributes(response)
|
|
399
521
|
|
|
@@ -405,7 +527,7 @@ async def _awrap(
|
|
|
405
527
|
)
|
|
406
528
|
|
|
407
529
|
if span.is_recording():
|
|
408
|
-
_set_response_attributes(span, response)
|
|
530
|
+
_set_response_attributes(span, response, token_histogram)
|
|
409
531
|
|
|
410
532
|
if span.is_recording():
|
|
411
533
|
span.set_status(Status(StatusCode.OK))
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.38.8"
|
|
@@ -8,7 +8,7 @@ show_missing = true
|
|
|
8
8
|
|
|
9
9
|
[tool.poetry]
|
|
10
10
|
name = "opentelemetry-instrumentation-groq"
|
|
11
|
-
version = "0.38.
|
|
11
|
+
version = "0.38.8"
|
|
12
12
|
description = "OpenTelemetry Groq instrumentation"
|
|
13
13
|
authors = ["Gal Kleinman <gal@traceloop.com>", "Nir Gazit <nir@traceloop.com>"]
|
|
14
14
|
repository = "https://github.com/traceloop/openllmetry/tree/main/packages/opentelemetry-instrumentation-groq"
|
|
@@ -32,7 +32,7 @@ pytest = "^8.2.2"
|
|
|
32
32
|
pytest-sugar = "1.0.0"
|
|
33
33
|
|
|
34
34
|
[tool.poetry.group.test.dependencies]
|
|
35
|
-
groq = ">=0.
|
|
35
|
+
groq = ">=0.18.0"
|
|
36
36
|
pytest = "^8.2.2"
|
|
37
37
|
pytest-sugar = "1.0.0"
|
|
38
38
|
vcrpy = "^6.0.1"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.38.6"
|
{opentelemetry_instrumentation_groq-0.38.6 → opentelemetry_instrumentation_groq-0.38.8}/README.md
RENAMED
|
File without changes
|
|
File without changes
|