opentelemetry-instrumentation-groq 0.38.6__py3-none-any.whl → 0.38.8__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.

Potentially problematic release.


This version of opentelemetry-instrumentation-groq might be problematic. Click here for more details.

@@ -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 GEN_AI_RESPONSE_ID
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(span, SpanAttributes.LLM_IS_STREAMING, kwargs.get("stream") or False)
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, usage.get("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
- # TODO: implement streaming
319
- pass
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
- # TODO: implement streaming
396
- pass
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))
@@ -1 +1 @@
1
- __version__ = "0.38.6"
1
+ __version__ = "0.38.8"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: opentelemetry-instrumentation-groq
3
- Version: 0.38.6
3
+ Version: 0.38.8
4
4
  Summary: OpenTelemetry Groq instrumentation
5
5
  License: Apache-2.0
6
6
  Author: Gal Kleinman
@@ -0,0 +1,8 @@
1
+ opentelemetry/instrumentation/groq/__init__.py,sha256=zRi0qEDmT-iBS2zGKUecx53flOP4criJWS9mtBsEYRc,20313
2
+ opentelemetry/instrumentation/groq/config.py,sha256=eN2YxQdWlAF-qWPwZZr0xFM-8tx9zUjmiparuB64jcU,170
3
+ opentelemetry/instrumentation/groq/utils.py,sha256=1ESL4NCp8Mjww8cGEzQO_AEqGiSK4JSiMFYUhwBnuao,2151
4
+ opentelemetry/instrumentation/groq/version.py,sha256=iWPMFCHqu7GKqG8CoHB9sVbeBazpddbqk566MeK8WnE,23
5
+ opentelemetry_instrumentation_groq-0.38.8.dist-info/METADATA,sha256=tFRh8E5qAfhDrbuWFk1HWOs5fDJnYRINHbT90eMq2Cs,2117
6
+ opentelemetry_instrumentation_groq-0.38.8.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
7
+ opentelemetry_instrumentation_groq-0.38.8.dist-info/entry_points.txt,sha256=uezQe06CpIK8xTZZSK0lF29nOKkz_w6VR4sQnb4IAFQ,87
8
+ opentelemetry_instrumentation_groq-0.38.8.dist-info/RECORD,,
@@ -1,8 +0,0 @@
1
- opentelemetry/instrumentation/groq/__init__.py,sha256=RgXnJKztm990BbUtJvOcliYqU-c975JF1CRv9wSalcc,16048
2
- opentelemetry/instrumentation/groq/config.py,sha256=eN2YxQdWlAF-qWPwZZr0xFM-8tx9zUjmiparuB64jcU,170
3
- opentelemetry/instrumentation/groq/utils.py,sha256=1ESL4NCp8Mjww8cGEzQO_AEqGiSK4JSiMFYUhwBnuao,2151
4
- opentelemetry/instrumentation/groq/version.py,sha256=uJq0GABwRg0CsFCDLTKi9uMtcv-BL9qIEL5QqdBeXXE,23
5
- opentelemetry_instrumentation_groq-0.38.6.dist-info/METADATA,sha256=Yiphzu4bN1WwFwrnOc8vPDne4aiS3yKXe9mcuny6NKk,2117
6
- opentelemetry_instrumentation_groq-0.38.6.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
7
- opentelemetry_instrumentation_groq-0.38.6.dist-info/entry_points.txt,sha256=uezQe06CpIK8xTZZSK0lF29nOKkz_w6VR4sQnb4IAFQ,87
8
- opentelemetry_instrumentation_groq-0.38.6.dist-info/RECORD,,