openlit 1.33.18__tar.gz → 1.33.19__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.
Files changed (132) hide show
  1. {openlit-1.33.18 → openlit-1.33.19}/PKG-INFO +1 -1
  2. {openlit-1.33.18 → openlit-1.33.19}/pyproject.toml +1 -1
  3. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/__helpers.py +4 -34
  4. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/azure_ai_inference/async_azure_ai_inference.py +9 -9
  5. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/azure_ai_inference/azure_ai_inference.py +9 -9
  6. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/azure_ai_inference/utils.py +2 -2
  7. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/bedrock/__init__.py +2 -1
  8. openlit-1.33.19/src/openlit/instrumentation/bedrock/bedrock.py +77 -0
  9. openlit-1.33.19/src/openlit/instrumentation/bedrock/utils.py +252 -0
  10. openlit-1.33.18/src/openlit/instrumentation/bedrock/bedrock.py +0 -259
  11. {openlit-1.33.18 → openlit-1.33.19}/LICENSE +0 -0
  12. {openlit-1.33.18 → openlit-1.33.19}/README.md +0 -0
  13. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/__init__.py +0 -0
  14. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/evals/__init__.py +0 -0
  15. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/evals/all.py +0 -0
  16. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/evals/bias_detection.py +0 -0
  17. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/evals/hallucination.py +0 -0
  18. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/evals/toxicity.py +0 -0
  19. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/evals/utils.py +0 -0
  20. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/guard/__init__.py +0 -0
  21. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/guard/all.py +0 -0
  22. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/guard/prompt_injection.py +0 -0
  23. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/guard/restrict_topic.py +0 -0
  24. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/guard/sensitive_topic.py +0 -0
  25. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/guard/utils.py +0 -0
  26. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/ag2/__init__.py +0 -0
  27. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/ag2/ag2.py +0 -0
  28. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/ai21/__init__.py +0 -0
  29. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/ai21/ai21.py +0 -0
  30. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/ai21/async_ai21.py +0 -0
  31. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/ai21/utils.py +0 -0
  32. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/anthropic/__init__.py +0 -0
  33. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/anthropic/anthropic.py +0 -0
  34. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/anthropic/async_anthropic.py +0 -0
  35. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/anthropic/utils.py +0 -0
  36. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/assemblyai/__init__.py +0 -0
  37. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/assemblyai/assemblyai.py +0 -0
  38. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/astra/__init__.py +0 -0
  39. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/astra/astra.py +0 -0
  40. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/astra/async_astra.py +0 -0
  41. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/astra/utils.py +0 -0
  42. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/azure_ai_inference/__init__.py +0 -0
  43. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/chroma/__init__.py +0 -0
  44. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/chroma/chroma.py +0 -0
  45. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/cohere/__init__.py +0 -0
  46. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/cohere/async_cohere.py +0 -0
  47. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/cohere/cohere.py +0 -0
  48. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/controlflow/__init__.py +0 -0
  49. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/controlflow/controlflow.py +0 -0
  50. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/crawl4ai/__init__.py +0 -0
  51. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/crawl4ai/async_crawl4ai.py +0 -0
  52. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/crawl4ai/crawl4ai.py +0 -0
  53. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/crewai/__init__.py +0 -0
  54. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/crewai/crewai.py +0 -0
  55. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/dynamiq/__init__.py +0 -0
  56. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/dynamiq/dynamiq.py +0 -0
  57. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/elevenlabs/__init__.py +0 -0
  58. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/elevenlabs/async_elevenlabs.py +0 -0
  59. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/elevenlabs/elevenlabs.py +0 -0
  60. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/embedchain/__init__.py +0 -0
  61. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/embedchain/embedchain.py +0 -0
  62. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/firecrawl/__init__.py +0 -0
  63. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/firecrawl/firecrawl.py +0 -0
  64. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/google_ai_studio/__init__.py +0 -0
  65. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/google_ai_studio/async_google_ai_studio.py +0 -0
  66. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/google_ai_studio/google_ai_studio.py +0 -0
  67. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/gpt4all/__init__.py +0 -0
  68. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/gpt4all/gpt4all.py +0 -0
  69. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/gpu/__init__.py +0 -0
  70. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/groq/__init__.py +0 -0
  71. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/groq/async_groq.py +0 -0
  72. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/groq/groq.py +0 -0
  73. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/haystack/__init__.py +0 -0
  74. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/haystack/haystack.py +0 -0
  75. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/julep/__init__.py +0 -0
  76. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/julep/async_julep.py +0 -0
  77. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/julep/julep.py +0 -0
  78. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/langchain/__init__.py +0 -0
  79. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/langchain/async_langchain.py +0 -0
  80. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/langchain/langchain.py +0 -0
  81. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/letta/__init__.py +0 -0
  82. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/letta/letta.py +0 -0
  83. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/litellm/__init__.py +0 -0
  84. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/litellm/async_litellm.py +0 -0
  85. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/litellm/litellm.py +0 -0
  86. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/llamaindex/__init__.py +0 -0
  87. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/llamaindex/llamaindex.py +0 -0
  88. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/mem0/__init__.py +0 -0
  89. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/mem0/mem0.py +0 -0
  90. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/milvus/__init__.py +0 -0
  91. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/milvus/milvus.py +0 -0
  92. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/mistral/__init__.py +0 -0
  93. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/mistral/async_mistral.py +0 -0
  94. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/mistral/mistral.py +0 -0
  95. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/multion/__init__.py +0 -0
  96. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/multion/async_multion.py +0 -0
  97. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/multion/multion.py +0 -0
  98. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/ollama/__init__.py +0 -0
  99. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/ollama/async_ollama.py +0 -0
  100. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/ollama/ollama.py +0 -0
  101. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/ollama/utils.py +0 -0
  102. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/openai/__init__.py +0 -0
  103. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/openai/async_openai.py +0 -0
  104. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/openai/openai.py +0 -0
  105. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/openai_agents/__init__.py +0 -0
  106. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/openai_agents/openai_agents.py +0 -0
  107. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/phidata/__init__.py +0 -0
  108. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/phidata/phidata.py +0 -0
  109. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/pinecone/__init__.py +0 -0
  110. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/pinecone/pinecone.py +0 -0
  111. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/premai/__init__.py +0 -0
  112. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/premai/premai.py +0 -0
  113. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/qdrant/__init__.py +0 -0
  114. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/qdrant/async_qdrant.py +0 -0
  115. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/qdrant/qdrant.py +0 -0
  116. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/reka/__init__.py +0 -0
  117. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/reka/async_reka.py +0 -0
  118. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/reka/reka.py +0 -0
  119. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/together/__init__.py +0 -0
  120. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/together/async_together.py +0 -0
  121. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/together/together.py +0 -0
  122. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/transformers/__init__.py +0 -0
  123. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/transformers/transformers.py +0 -0
  124. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/vertexai/__init__.py +0 -0
  125. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/vertexai/async_vertexai.py +0 -0
  126. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/vertexai/vertexai.py +0 -0
  127. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/vllm/__init__.py +0 -0
  128. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/instrumentation/vllm/vllm.py +0 -0
  129. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/otel/events.py +0 -0
  130. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/otel/metrics.py +0 -0
  131. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/otel/tracing.py +0 -0
  132. {openlit-1.33.18 → openlit-1.33.19}/src/openlit/semcov/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: openlit
3
- Version: 1.33.18
3
+ Version: 1.33.19
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
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "openlit"
3
- version = "1.33.18"
3
+ version = "1.33.19"
4
4
  description = "OpenTelemetry-native Auto instrumentation library for monitoring LLM Applications and GPUs, facilitating the integration of observability into your GenAI-driven projects"
5
5
  authors = ["OpenLIT"]
6
6
  license = "Apache-2.0"
@@ -237,27 +237,21 @@ def extract_and_format_input(messages):
237
237
  them into fixed roles like 'user', 'assistant', 'system', 'tool'.
238
238
  """
239
239
 
240
- fixed_roles = ['user', 'assistant', 'system', 'tool'] # Ensure these are your fixed keys
241
- # Initialize the dictionary with fixed keys and empty structures
240
+ fixed_roles = ['user', 'assistant', 'system', 'tool', 'developer']
242
241
  formatted_messages = {role_key: {'role': '', 'content': ''} for role_key in fixed_roles}
243
242
 
244
243
  for message in messages:
245
- # Normalize the message structure
246
244
  message = response_as_dict(message)
247
245
 
248
- # Extract role and content
249
246
  role = message.get('role')
250
247
  if role not in fixed_roles:
251
- continue # Skip any role not in our predefined roles
248
+ continue
252
249
 
253
250
  content = message.get('content', '')
254
251
 
255
- # Prepare content as a string
252
+ # Prepare content as a string, handling both list and str
256
253
  if isinstance(content, list):
257
- content_str = ", ".join(
258
- f'{item.get("type", "text")}: {extract_text_from_item(item)}'
259
- for item in content
260
- )
254
+ content_str = ", ".join(str(item) for item in content)
261
255
  else:
262
256
  content_str = content
263
257
 
@@ -272,30 +266,6 @@ def extract_and_format_input(messages):
272
266
 
273
267
  return formatted_messages
274
268
 
275
- def extract_text_from_item(item):
276
- """
277
- Extract text from inpit message
278
- """
279
-
280
- #pylint: disable=no-else-return
281
- if item.get('type') == 'text':
282
- return item.get('text', '')
283
- elif item.get('type') == 'image':
284
- # Handle image content specifically checking for 'url' or 'base64'
285
- source = item.get('source', {})
286
- if isinstance(source, dict):
287
- if source.get('type') == 'base64':
288
- # Return the actual base64 data if present
289
- return source.get('data', '[Missing base64 data]')
290
- elif source.get('type') == 'url':
291
- return source.get('url', '[Missing URL]')
292
- elif item.get('type') == 'image_url':
293
- # New format: Handle the 'image_url' type
294
- image_url = item.get('image_url', {})
295
- if isinstance(image_url, dict):
296
- return image_url.get('url', '[Missing image URL]')
297
- return ''
298
-
299
269
  # To be removed one the change to log events (from span events) is complete
300
270
  def concatenate_all_contents(formatted_messages):
301
271
  """
@@ -43,10 +43,10 @@ def async_complete(version, environment, application_name,
43
43
  self.__wrapped__ = wrapped
44
44
  self._span = span
45
45
  self._span_name = span_name
46
- self._llmresponse = ""
47
- self._response_id = ""
48
- self._response_model = ""
49
- self._finish_reason = ""
46
+ self._llmresponse = ''
47
+ self._response_id = ''
48
+ self._response_model = ''
49
+ self._finish_reason = ''
50
50
  self._input_tokens = 0
51
51
  self._output_tokens = 0
52
52
 
@@ -96,7 +96,7 @@ def async_complete(version, environment, application_name,
96
96
 
97
97
  except Exception as e:
98
98
  handle_exception(self._span, e)
99
- logger.error("Error in trace creation: %s", e)
99
+ logger.error('Error in trace creation: %s', e)
100
100
  raise
101
101
 
102
102
  async def wrapper(wrapped, instance, args, kwargs):
@@ -104,11 +104,11 @@ def async_complete(version, environment, application_name,
104
104
  Wraps the GenAI function call.
105
105
  """
106
106
 
107
- streaming = kwargs.get("stream", False)
108
- server_address, server_port = set_server_address_and_port(instance, "models.github.ai", 443)
109
- request_model = kwargs.get("model", "gpt-4o")
107
+ streaming = kwargs.get('stream', False)
108
+ server_address, server_port = set_server_address_and_port(instance, 'models.github.ai', 443)
109
+ request_model = kwargs.get('model', 'gpt-4o')
110
110
 
111
- span_name = f"{SemanticConvetion.GEN_AI_OPERATION_TYPE_CHAT} {request_model}"
111
+ span_name = f'{SemanticConvetion.GEN_AI_OPERATION_TYPE_CHAT} {request_model}'
112
112
 
113
113
  # pylint: disable=no-else-return
114
114
  if streaming:
@@ -43,10 +43,10 @@ def complete(version, environment, application_name,
43
43
  self.__wrapped__ = wrapped
44
44
  self._span = span
45
45
  self._span_name = span_name
46
- self._llmresponse = ""
47
- self._response_id = ""
48
- self._response_model = ""
49
- self._finish_reason = ""
46
+ self._llmresponse = ''
47
+ self._response_id = ''
48
+ self._response_model = ''
49
+ self._finish_reason = ''
50
50
  self._input_tokens = 0
51
51
  self._output_tokens = 0
52
52
 
@@ -96,7 +96,7 @@ def complete(version, environment, application_name,
96
96
 
97
97
  except Exception as e:
98
98
  handle_exception(self._span, e)
99
- logger.error("Error in trace creation: %s", e)
99
+ logger.error('Error in trace creation: %s', e)
100
100
  raise
101
101
 
102
102
  def wrapper(wrapped, instance, args, kwargs):
@@ -104,11 +104,11 @@ def complete(version, environment, application_name,
104
104
  Wraps the GenAI function call.
105
105
  """
106
106
 
107
- streaming = kwargs.get("stream", False)
108
- server_address, server_port = set_server_address_and_port(instance, "models.github.ai", 443)
109
- request_model = kwargs.get("model", "gpt-4o")
107
+ streaming = kwargs.get('stream', False)
108
+ server_address, server_port = set_server_address_and_port(instance, 'models.github.ai', 443)
109
+ request_model = kwargs.get('model', 'gpt-4o')
110
110
 
111
- span_name = f"{SemanticConvetion.GEN_AI_OPERATION_TYPE_CHAT} {request_model}"
111
+ span_name = f'{SemanticConvetion.GEN_AI_OPERATION_TYPE_CHAT} {request_model}'
112
112
 
113
113
  # pylint: disable=no-else-return
114
114
  if streaming:
@@ -76,9 +76,9 @@ def common_chat_logic(scope, pricing_info, environment, application_name, metric
76
76
  scope._span.set_attribute(SemanticConvetion.GEN_AI_REQUEST_TOP_K, scope._kwargs.get('top_k', 1.0))
77
77
  scope._span.set_attribute(SemanticConvetion.GEN_AI_REQUEST_TOP_P, scope._kwargs.get('top_p', 1.0))
78
78
  scope._span.set_attribute(SemanticConvetion.GEN_AI_REQUEST_FREQUENCY_PENALTY,
79
- scope._kwargs.get("frequency_penalty", 0.0))
79
+ scope._kwargs.get('frequency_penalty', 0.0))
80
80
  scope._span.set_attribute(SemanticConvetion.GEN_AI_REQUEST_PRESENCE_PENALTY,
81
- scope._kwargs.get("presence_penalty", 0.0))
81
+ scope._kwargs.get('presence_penalty', 0.0))
82
82
  scope._span.set_attribute(SemanticConvetion.GEN_AI_RESPONSE_FINISH_REASON, [scope._finish_reason])
83
83
  scope._span.set_attribute(SemanticConvetion.GEN_AI_RESPONSE_ID, scope._response_id)
84
84
  scope._span.set_attribute(SemanticConvetion.GEN_AI_RESPONSE_MODEL, scope._response_model)
@@ -22,6 +22,7 @@ class BedrockInstrumentor(BaseInstrumentor):
22
22
  application_name = kwargs.get("application_name", "default_application")
23
23
  environment = kwargs.get("environment", "default_environment")
24
24
  tracer = kwargs.get("tracer")
25
+ event_provider = kwargs.get('event_provider')
25
26
  metrics = kwargs.get("metrics_dict")
26
27
  pricing_info = kwargs.get("pricing_info", {})
27
28
  capture_message_content = kwargs.get("capture_message_content", False)
@@ -33,7 +34,7 @@ class BedrockInstrumentor(BaseInstrumentor):
33
34
  "botocore.client",
34
35
  "ClientCreator.create_client",
35
36
  converse(version, environment, application_name,
36
- tracer, pricing_info, capture_message_content, metrics, disable_metrics),
37
+ tracer, event_provider, pricing_info, capture_message_content, metrics, disable_metrics),
37
38
  )
38
39
 
39
40
  def _uninstrument(self, **kwargs):
@@ -0,0 +1,77 @@
1
+ """
2
+ Module for monitoring Amazon Bedrock API calls.
3
+ """
4
+
5
+ import logging
6
+ import time
7
+ from opentelemetry.trace import SpanKind
8
+ from openlit.__helpers import (
9
+ set_server_address_and_port
10
+ )
11
+ from openlit.instrumentation.bedrock.utils import (
12
+ process_chat_response,
13
+ )
14
+ from openlit.semcov import SemanticConvetion
15
+
16
+ # Initialize logger for logging potential issues and operations
17
+ logger = logging.getLogger(__name__)
18
+
19
+ def converse(version, environment, application_name, tracer, event_provider,
20
+ pricing_info, capture_message_content, metrics, disable_metrics):
21
+ """
22
+ Generates a telemetry wrapper for GenAI function call
23
+ """
24
+
25
+ def wrapper(wrapped, instance, args, kwargs):
26
+ """
27
+ Wraps the GenAI function call.
28
+ """
29
+
30
+ def converse_wrapper(original_method, *method_args, **method_kwargs):
31
+
32
+ """
33
+ Wraps the GenAI function call.
34
+ """
35
+
36
+ server_address, server_port = set_server_address_and_port(instance, 'aws.amazon.com', 443)
37
+ request_model = method_kwargs.get('modelId', 'amazon.titan-text-express-v1')
38
+
39
+ span_name = f'{SemanticConvetion.GEN_AI_OPERATION_TYPE_CHAT} {request_model}'
40
+
41
+ with tracer.start_as_current_span(span_name, kind=SpanKind.CLIENT) as span:
42
+ start_time = time.time()
43
+ response = original_method(*method_args, **method_kwargs)
44
+ llm_config = method_kwargs.get('inferenceConfig', {})
45
+ response = process_chat_response(
46
+ response=response,
47
+ request_model=request_model,
48
+ pricing_info=pricing_info,
49
+ server_port=server_port,
50
+ server_address=server_address,
51
+ environment=environment,
52
+ application_name=application_name,
53
+ metrics=metrics,
54
+ event_provider=event_provider,
55
+ start_time=start_time,
56
+ span=span,
57
+ capture_message_content=capture_message_content,
58
+ disable_metrics=disable_metrics,
59
+ version=version,
60
+ llm_config=llm_config,
61
+ **method_kwargs
62
+ )
63
+
64
+ return response
65
+
66
+ # Get the original client instance from the wrapper
67
+ client = wrapped(*args, **kwargs)
68
+
69
+ # Replace the original method with the instrumented one
70
+ if kwargs.get('service_name') == 'bedrock-runtime':
71
+ original_invoke_model = client.converse
72
+ client.converse = lambda *args, **kwargs: converse_wrapper(original_invoke_model,
73
+ *args, **kwargs)
74
+
75
+ return client
76
+
77
+ return wrapper
@@ -0,0 +1,252 @@
1
+ """
2
+ AWS Bedrock 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
+ extract_and_format_input,
14
+ get_chat_model_cost,
15
+ create_metrics_attributes,
16
+ otel_event,
17
+ concatenate_all_contents
18
+ )
19
+ from openlit.semcov import SemanticConvetion
20
+
21
+ def process_chunk(self, chunk):
22
+ """
23
+ Process a chunk of response data and update state.
24
+ """
25
+
26
+ end_time = time.time()
27
+ # Record the timestamp for the current chunk
28
+ self._timestamps.append(end_time)
29
+
30
+ if len(self._timestamps) == 1:
31
+ # Calculate time to first chunk
32
+ self._ttft = calculate_ttft(self._timestamps, self._start_time)
33
+
34
+ chunked = response_as_dict(chunk)
35
+
36
+ # Collect message IDs and input token from events
37
+ if chunked.get('type') == 'message_start':
38
+ self._response_id = chunked.get('message').get('id')
39
+ self._input_tokens = chunked.get('message').get('usage').get('input_tokens')
40
+ self._response_model = chunked.get('message').get('model')
41
+ self._response_role = chunked.get('message').get('role')
42
+
43
+ # Collect message IDs and aggregated response from events
44
+ if chunked.get('type') == 'content_block_delta':
45
+ if chunked.get('delta').get('text'):
46
+ self._llmresponse += chunked.get('delta').get('text')
47
+ elif chunked.get('delta').get('partial_json'):
48
+ self._tool_arguments += chunked.get('delta').get('partial_json')
49
+
50
+ if chunked.get('type') == 'content_block_start':
51
+ if chunked.get('content_block').get('id'):
52
+ self._tool_id = chunked.get('content_block').get('id')
53
+ if chunked.get('content_block').get('name'):
54
+ self._tool_name = chunked.get('content_block').get('name')
55
+
56
+ # Collect output tokens and stop reason from events
57
+ if chunked.get('type') == 'message_delta':
58
+ self._output_tokens = chunked.get('usage').get('output_tokens')
59
+ self._finish_reason = chunked.get('delta').get('stop_reason')
60
+
61
+ def common_chat_logic(scope, pricing_info, environment, application_name, metrics,
62
+ event_provider, capture_message_content, disable_metrics, version, llm_config, is_stream):
63
+ """
64
+ Process chat request and generate Telemetry
65
+ """
66
+
67
+ scope._end_time = time.time()
68
+ if len(scope._timestamps) > 1:
69
+ scope._tbt = calculate_tbt(scope._timestamps)
70
+
71
+ formatted_messages = extract_and_format_input(scope._kwargs.get('messages', ''))
72
+ print(formatted_messages)
73
+ request_model = scope._kwargs.get('model', 'claude-3-opus-20240229')
74
+
75
+ cost = get_chat_model_cost(request_model, pricing_info, scope._input_tokens, scope._output_tokens)
76
+
77
+ # Set Span attributes (OTel Semconv)
78
+ scope._span.set_attribute(TELEMETRY_SDK_NAME, 'openlit')
79
+ scope._span.set_attribute(SemanticConvetion.GEN_AI_OPERATION, SemanticConvetion.GEN_AI_OPERATION_TYPE_CHAT)
80
+ scope._span.set_attribute(SemanticConvetion.GEN_AI_SYSTEM, SemanticConvetion.GEN_AI_SYSTEM_AWS_BEDROCK)
81
+ scope._span.set_attribute(SemanticConvetion.GEN_AI_REQUEST_MODEL, request_model)
82
+ scope._span.set_attribute(SemanticConvetion.SERVER_PORT, scope._server_port)
83
+
84
+ # List of attributes and their config keys
85
+ attributes = [
86
+ (SemanticConvetion.GEN_AI_REQUEST_FREQUENCY_PENALTY, 'frequencyPenalty'),
87
+ (SemanticConvetion.GEN_AI_REQUEST_MAX_TOKENS, 'maxTokens'),
88
+ (SemanticConvetion.GEN_AI_REQUEST_PRESENCE_PENALTY, 'presencePenalty'),
89
+ (SemanticConvetion.GEN_AI_REQUEST_STOP_SEQUENCES, 'stopSequences'),
90
+ (SemanticConvetion.GEN_AI_REQUEST_TEMPERATURE, 'temperature'),
91
+ (SemanticConvetion.GEN_AI_REQUEST_TOP_P, 'topP'),
92
+ (SemanticConvetion.GEN_AI_REQUEST_TOP_K, 'topK'),
93
+ ]
94
+
95
+ # Set each attribute if the corresponding value exists and is not None
96
+ for attribute, key in attributes:
97
+ value = llm_config.get(key)
98
+ if value is not None:
99
+ scope._span.set_attribute(attribute, value)
100
+
101
+ scope._span.set_attribute(SemanticConvetion.GEN_AI_RESPONSE_FINISH_REASON, [scope._finish_reason])
102
+ scope._span.set_attribute(SemanticConvetion.GEN_AI_RESPONSE_ID, scope._response_id)
103
+ scope._span.set_attribute(SemanticConvetion.GEN_AI_RESPONSE_MODEL, scope._response_model)
104
+ scope._span.set_attribute(SemanticConvetion.GEN_AI_USAGE_INPUT_TOKENS, scope._input_tokens)
105
+ scope._span.set_attribute(SemanticConvetion.GEN_AI_USAGE_OUTPUT_TOKENS, scope._output_tokens)
106
+ scope._span.set_attribute(SemanticConvetion.SERVER_ADDRESS, scope._server_address)
107
+
108
+ scope._span.set_attribute(SemanticConvetion.GEN_AI_OUTPUT_TYPE,
109
+ 'text' if isinstance(scope._llmresponse, str) else 'json')
110
+
111
+ scope._span.set_attribute(DEPLOYMENT_ENVIRONMENT, environment)
112
+ scope._span.set_attribute(SERVICE_NAME, application_name)
113
+ scope._span.set_attribute(SemanticConvetion.GEN_AI_REQUEST_IS_STREAM, is_stream)
114
+ scope._span.set_attribute(SemanticConvetion.GEN_AI_CLIENT_TOKEN_USAGE, scope._input_tokens + scope._output_tokens)
115
+ scope._span.set_attribute(SemanticConvetion.GEN_AI_USAGE_COST, cost)
116
+ scope._span.set_attribute(SemanticConvetion.GEN_AI_SERVER_TBT, scope._tbt)
117
+ scope._span.set_attribute(SemanticConvetion.GEN_AI_SERVER_TTFT, scope._ttft)
118
+ scope._span.set_attribute(SemanticConvetion.GEN_AI_SDK_VERSION, version)
119
+
120
+ # To be removed one the change to log events (from span events) is complete
121
+ prompt = concatenate_all_contents(formatted_messages)
122
+ if capture_message_content:
123
+ scope._span.add_event(
124
+ name=SemanticConvetion.GEN_AI_CONTENT_PROMPT_EVENT,
125
+ attributes={
126
+ SemanticConvetion.GEN_AI_CONTENT_PROMPT: prompt,
127
+ },
128
+ )
129
+ scope._span.add_event(
130
+ name=SemanticConvetion.GEN_AI_CONTENT_COMPLETION_EVENT,
131
+ attributes={
132
+ SemanticConvetion.GEN_AI_CONTENT_COMPLETION: scope._llmresponse,
133
+ },
134
+ )
135
+
136
+ choice_event_body = {
137
+ 'finish_reason': scope._finish_reason,
138
+ 'index': 0,
139
+ 'message': {
140
+ **({'content': scope._llmresponse} if capture_message_content else {}),
141
+ 'role': scope._response_role
142
+ }
143
+ }
144
+
145
+ # Emit events
146
+ for role in ['user', 'system', 'assistant', 'tool']:
147
+ if formatted_messages.get(role, {}).get('content', ''):
148
+ event = otel_event(
149
+ name=getattr(SemanticConvetion, f'GEN_AI_{role.upper()}_MESSAGE'),
150
+ attributes={
151
+ SemanticConvetion.GEN_AI_SYSTEM: SemanticConvetion.GEN_AI_SYSTEM_AWS_BEDROCK
152
+ },
153
+ body = {
154
+ # pylint: disable=line-too-long
155
+ **({'content': formatted_messages.get(role, {}).get('content', '')} if capture_message_content else {}),
156
+ 'role': formatted_messages.get(role, {}).get('role', []),
157
+ **({
158
+ 'tool_calls': {
159
+ 'function': {
160
+ # pylint: disable=line-too-long
161
+ 'name': (scope._tool_calls[0].get('function', {}).get('name', '') if scope._tool_calls else ''),
162
+ 'arguments': (scope._tool_calls[0].get('function', {}).get('arguments', '') if scope._tool_calls else '')
163
+ },
164
+ 'id': (scope._tool_calls[0].get('id', '') if scope._tool_calls else ''),
165
+ 'type': 'function'
166
+ }
167
+ } if role == 'assistant' else {}),
168
+ **({
169
+ 'id': (scope._tool_calls[0].get('id', '') if scope._tool_calls else '')
170
+ } if role == 'tool' else {})
171
+ }
172
+ )
173
+ event_provider.emit(event)
174
+
175
+ choice_event = otel_event(
176
+ name=SemanticConvetion.GEN_AI_CHOICE,
177
+ attributes={
178
+ SemanticConvetion.GEN_AI_SYSTEM: SemanticConvetion.GEN_AI_SYSTEM_AWS_BEDROCK
179
+ },
180
+ body=choice_event_body
181
+ )
182
+ event_provider.emit(choice_event)
183
+
184
+ scope._span.set_status(Status(StatusCode.OK))
185
+
186
+ if not disable_metrics:
187
+ metrics_attributes = create_metrics_attributes(
188
+ service_name=application_name,
189
+ deployment_environment=environment,
190
+ operation=SemanticConvetion.GEN_AI_OPERATION_TYPE_CHAT,
191
+ system=SemanticConvetion.GEN_AI_SYSTEM_AWS_BEDROCK,
192
+ request_model=request_model,
193
+ server_address=scope._server_address,
194
+ server_port=scope._server_port,
195
+ response_model=scope._response_model,
196
+ )
197
+
198
+ metrics['genai_client_usage_tokens'].record(scope._input_tokens + scope._output_tokens, metrics_attributes)
199
+ metrics['genai_client_operation_duration'].record(scope._end_time - scope._start_time, metrics_attributes)
200
+ metrics['genai_server_tbt'].record(scope._tbt, metrics_attributes)
201
+ metrics['genai_server_ttft'].record(scope._ttft, metrics_attributes)
202
+ metrics['genai_requests'].add(1, metrics_attributes)
203
+ metrics['genai_completion_tokens'].add(scope._output_tokens, metrics_attributes)
204
+ metrics['genai_prompt_tokens'].add(scope._input_tokens, metrics_attributes)
205
+ metrics['genai_cost'].record(cost, metrics_attributes)
206
+
207
+ def process_streaming_chat_response(self, pricing_info, environment, application_name, metrics,
208
+ event_provider, capture_message_content=False, disable_metrics=False, version='', llm_config=''):
209
+
210
+ """
211
+ Process chat request and generate Telemetry
212
+ """
213
+ if self._tool_id != '':
214
+ self._tool_calls = {
215
+ 'id': self._tool_id,
216
+ 'name': self._tool_name,
217
+ 'input': self._tool_arguments
218
+ }
219
+
220
+ common_chat_logic(self, pricing_info, environment, application_name, metrics,
221
+ event_provider, capture_message_content, disable_metrics, version, llm_config, is_stream=True)
222
+
223
+ def process_chat_response(response, request_model, pricing_info, server_port, server_address, environment,
224
+ application_name, metrics, event_provider, start_time, span, capture_message_content=False,
225
+ disable_metrics=False, version='1.0.0', llm_config='', **kwargs):
226
+
227
+ """
228
+ Process chat request and generate Telemetry
229
+ """
230
+
231
+ self = type('GenericScope', (), {})()
232
+ response_dict = response_as_dict(response)
233
+
234
+ # pylint: disable = no-member
235
+ self._start_time = start_time
236
+ self._end_time = time.time()
237
+ self._span = span
238
+ self._llmresponse = response_dict.get('output').get('message').get('content')[0].get('text')
239
+ self._response_role = 'assistant'
240
+ self._input_tokens = response_dict.get('usage').get('inputTokens')
241
+ self._output_tokens = response_dict.get('usage').get('outputTokens')
242
+ self._response_model = request_model
243
+ self._finish_reason = response_dict.get('stopReason', '')
244
+ self._response_id = response_dict.get('ResponseMetadata').get('RequestId')
245
+ self._timestamps = []
246
+ self._ttft, self._tbt = self._end_time - self._start_time, 0
247
+ self._server_address, self._server_port = server_address, server_port
248
+ self._kwargs = kwargs
249
+ common_chat_logic(self, pricing_info, environment, application_name, metrics,
250
+ event_provider, capture_message_content, disable_metrics, version, llm_config, is_stream=False)
251
+
252
+ return response