openlit 1.34.30__py3-none-any.whl → 1.34.31__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.
Files changed (168) hide show
  1. openlit/__helpers.py +235 -86
  2. openlit/__init__.py +16 -13
  3. openlit/_instrumentors.py +2 -1
  4. openlit/evals/all.py +50 -21
  5. openlit/evals/bias_detection.py +47 -20
  6. openlit/evals/hallucination.py +53 -22
  7. openlit/evals/toxicity.py +50 -21
  8. openlit/evals/utils.py +54 -30
  9. openlit/guard/all.py +61 -19
  10. openlit/guard/prompt_injection.py +34 -14
  11. openlit/guard/restrict_topic.py +46 -15
  12. openlit/guard/sensitive_topic.py +34 -14
  13. openlit/guard/utils.py +58 -22
  14. openlit/instrumentation/ag2/__init__.py +24 -8
  15. openlit/instrumentation/ag2/ag2.py +34 -13
  16. openlit/instrumentation/ag2/async_ag2.py +34 -13
  17. openlit/instrumentation/ag2/utils.py +133 -30
  18. openlit/instrumentation/ai21/__init__.py +43 -14
  19. openlit/instrumentation/ai21/ai21.py +47 -21
  20. openlit/instrumentation/ai21/async_ai21.py +47 -21
  21. openlit/instrumentation/ai21/utils.py +299 -78
  22. openlit/instrumentation/anthropic/__init__.py +21 -4
  23. openlit/instrumentation/anthropic/anthropic.py +28 -17
  24. openlit/instrumentation/anthropic/async_anthropic.py +28 -17
  25. openlit/instrumentation/anthropic/utils.py +145 -35
  26. openlit/instrumentation/assemblyai/__init__.py +11 -2
  27. openlit/instrumentation/assemblyai/assemblyai.py +15 -4
  28. openlit/instrumentation/assemblyai/utils.py +120 -25
  29. openlit/instrumentation/astra/__init__.py +43 -10
  30. openlit/instrumentation/astra/astra.py +28 -5
  31. openlit/instrumentation/astra/async_astra.py +28 -5
  32. openlit/instrumentation/astra/utils.py +151 -55
  33. openlit/instrumentation/azure_ai_inference/__init__.py +43 -10
  34. openlit/instrumentation/azure_ai_inference/async_azure_ai_inference.py +53 -21
  35. openlit/instrumentation/azure_ai_inference/azure_ai_inference.py +53 -21
  36. openlit/instrumentation/azure_ai_inference/utils.py +307 -83
  37. openlit/instrumentation/bedrock/__init__.py +21 -4
  38. openlit/instrumentation/bedrock/bedrock.py +63 -25
  39. openlit/instrumentation/bedrock/utils.py +139 -30
  40. openlit/instrumentation/chroma/__init__.py +89 -16
  41. openlit/instrumentation/chroma/chroma.py +28 -6
  42. openlit/instrumentation/chroma/utils.py +167 -51
  43. openlit/instrumentation/cohere/__init__.py +63 -18
  44. openlit/instrumentation/cohere/async_cohere.py +63 -24
  45. openlit/instrumentation/cohere/cohere.py +63 -24
  46. openlit/instrumentation/cohere/utils.py +286 -73
  47. openlit/instrumentation/controlflow/__init__.py +35 -9
  48. openlit/instrumentation/controlflow/controlflow.py +66 -33
  49. openlit/instrumentation/crawl4ai/__init__.py +25 -10
  50. openlit/instrumentation/crawl4ai/async_crawl4ai.py +78 -31
  51. openlit/instrumentation/crawl4ai/crawl4ai.py +78 -31
  52. openlit/instrumentation/crewai/__init__.py +40 -15
  53. openlit/instrumentation/crewai/async_crewai.py +32 -7
  54. openlit/instrumentation/crewai/crewai.py +32 -7
  55. openlit/instrumentation/crewai/utils.py +159 -56
  56. openlit/instrumentation/dynamiq/__init__.py +46 -12
  57. openlit/instrumentation/dynamiq/dynamiq.py +74 -33
  58. openlit/instrumentation/elevenlabs/__init__.py +23 -4
  59. openlit/instrumentation/elevenlabs/async_elevenlabs.py +16 -4
  60. openlit/instrumentation/elevenlabs/elevenlabs.py +16 -4
  61. openlit/instrumentation/elevenlabs/utils.py +128 -25
  62. openlit/instrumentation/embedchain/__init__.py +11 -2
  63. openlit/instrumentation/embedchain/embedchain.py +68 -35
  64. openlit/instrumentation/firecrawl/__init__.py +24 -7
  65. openlit/instrumentation/firecrawl/firecrawl.py +46 -20
  66. openlit/instrumentation/google_ai_studio/__init__.py +45 -10
  67. openlit/instrumentation/google_ai_studio/async_google_ai_studio.py +67 -44
  68. openlit/instrumentation/google_ai_studio/google_ai_studio.py +67 -44
  69. openlit/instrumentation/google_ai_studio/utils.py +180 -67
  70. openlit/instrumentation/gpt4all/__init__.py +22 -7
  71. openlit/instrumentation/gpt4all/gpt4all.py +67 -29
  72. openlit/instrumentation/gpt4all/utils.py +285 -61
  73. openlit/instrumentation/gpu/__init__.py +128 -47
  74. openlit/instrumentation/groq/__init__.py +21 -4
  75. openlit/instrumentation/groq/async_groq.py +33 -21
  76. openlit/instrumentation/groq/groq.py +33 -21
  77. openlit/instrumentation/groq/utils.py +192 -55
  78. openlit/instrumentation/haystack/__init__.py +70 -24
  79. openlit/instrumentation/haystack/async_haystack.py +28 -6
  80. openlit/instrumentation/haystack/haystack.py +28 -6
  81. openlit/instrumentation/haystack/utils.py +196 -74
  82. openlit/instrumentation/julep/__init__.py +69 -19
  83. openlit/instrumentation/julep/async_julep.py +53 -27
  84. openlit/instrumentation/julep/julep.py +53 -28
  85. openlit/instrumentation/langchain/__init__.py +74 -63
  86. openlit/instrumentation/langchain/callback_handler.py +1100 -0
  87. openlit/instrumentation/langchain_community/__init__.py +13 -2
  88. openlit/instrumentation/langchain_community/async_langchain_community.py +23 -5
  89. openlit/instrumentation/langchain_community/langchain_community.py +23 -5
  90. openlit/instrumentation/langchain_community/utils.py +35 -9
  91. openlit/instrumentation/letta/__init__.py +68 -15
  92. openlit/instrumentation/letta/letta.py +99 -54
  93. openlit/instrumentation/litellm/__init__.py +43 -14
  94. openlit/instrumentation/litellm/async_litellm.py +51 -26
  95. openlit/instrumentation/litellm/litellm.py +51 -26
  96. openlit/instrumentation/litellm/utils.py +304 -102
  97. openlit/instrumentation/llamaindex/__init__.py +267 -90
  98. openlit/instrumentation/llamaindex/async_llamaindex.py +28 -6
  99. openlit/instrumentation/llamaindex/llamaindex.py +28 -6
  100. openlit/instrumentation/llamaindex/utils.py +204 -91
  101. openlit/instrumentation/mem0/__init__.py +11 -2
  102. openlit/instrumentation/mem0/mem0.py +50 -29
  103. openlit/instrumentation/milvus/__init__.py +10 -2
  104. openlit/instrumentation/milvus/milvus.py +31 -6
  105. openlit/instrumentation/milvus/utils.py +166 -67
  106. openlit/instrumentation/mistral/__init__.py +63 -18
  107. openlit/instrumentation/mistral/async_mistral.py +63 -24
  108. openlit/instrumentation/mistral/mistral.py +63 -24
  109. openlit/instrumentation/mistral/utils.py +277 -69
  110. openlit/instrumentation/multion/__init__.py +69 -19
  111. openlit/instrumentation/multion/async_multion.py +57 -26
  112. openlit/instrumentation/multion/multion.py +57 -26
  113. openlit/instrumentation/ollama/__init__.py +39 -18
  114. openlit/instrumentation/ollama/async_ollama.py +57 -26
  115. openlit/instrumentation/ollama/ollama.py +57 -26
  116. openlit/instrumentation/ollama/utils.py +226 -50
  117. openlit/instrumentation/openai/__init__.py +156 -32
  118. openlit/instrumentation/openai/async_openai.py +147 -67
  119. openlit/instrumentation/openai/openai.py +150 -67
  120. openlit/instrumentation/openai/utils.py +657 -185
  121. openlit/instrumentation/openai_agents/__init__.py +5 -1
  122. openlit/instrumentation/openai_agents/processor.py +110 -90
  123. openlit/instrumentation/phidata/__init__.py +13 -5
  124. openlit/instrumentation/phidata/phidata.py +67 -32
  125. openlit/instrumentation/pinecone/__init__.py +48 -9
  126. openlit/instrumentation/pinecone/async_pinecone.py +27 -5
  127. openlit/instrumentation/pinecone/pinecone.py +27 -5
  128. openlit/instrumentation/pinecone/utils.py +153 -47
  129. openlit/instrumentation/premai/__init__.py +22 -7
  130. openlit/instrumentation/premai/premai.py +51 -26
  131. openlit/instrumentation/premai/utils.py +246 -59
  132. openlit/instrumentation/pydantic_ai/__init__.py +49 -22
  133. openlit/instrumentation/pydantic_ai/pydantic_ai.py +69 -16
  134. openlit/instrumentation/pydantic_ai/utils.py +89 -24
  135. openlit/instrumentation/qdrant/__init__.py +19 -4
  136. openlit/instrumentation/qdrant/async_qdrant.py +33 -7
  137. openlit/instrumentation/qdrant/qdrant.py +33 -7
  138. openlit/instrumentation/qdrant/utils.py +228 -93
  139. openlit/instrumentation/reka/__init__.py +23 -10
  140. openlit/instrumentation/reka/async_reka.py +17 -11
  141. openlit/instrumentation/reka/reka.py +17 -11
  142. openlit/instrumentation/reka/utils.py +138 -36
  143. openlit/instrumentation/together/__init__.py +44 -12
  144. openlit/instrumentation/together/async_together.py +50 -27
  145. openlit/instrumentation/together/together.py +50 -27
  146. openlit/instrumentation/together/utils.py +301 -71
  147. openlit/instrumentation/transformers/__init__.py +2 -1
  148. openlit/instrumentation/transformers/transformers.py +13 -3
  149. openlit/instrumentation/transformers/utils.py +139 -36
  150. openlit/instrumentation/vertexai/__init__.py +81 -16
  151. openlit/instrumentation/vertexai/async_vertexai.py +33 -15
  152. openlit/instrumentation/vertexai/utils.py +123 -27
  153. openlit/instrumentation/vertexai/vertexai.py +33 -15
  154. openlit/instrumentation/vllm/__init__.py +12 -5
  155. openlit/instrumentation/vllm/utils.py +121 -31
  156. openlit/instrumentation/vllm/vllm.py +16 -10
  157. openlit/otel/events.py +35 -10
  158. openlit/otel/metrics.py +32 -24
  159. openlit/otel/tracing.py +24 -9
  160. openlit/semcov/__init__.py +72 -6
  161. {openlit-1.34.30.dist-info → openlit-1.34.31.dist-info}/METADATA +2 -1
  162. openlit-1.34.31.dist-info/RECORD +166 -0
  163. openlit/instrumentation/langchain/async_langchain.py +0 -102
  164. openlit/instrumentation/langchain/langchain.py +0 -102
  165. openlit/instrumentation/langchain/utils.py +0 -252
  166. openlit-1.34.30.dist-info/RECORD +0 -168
  167. {openlit-1.34.30.dist-info → openlit-1.34.31.dist-info}/LICENSE +0 -0
  168. {openlit-1.34.30.dist-info → openlit-1.34.31.dist-info}/WHEEL +0 -0
openlit/__helpers.py CHANGED
@@ -2,6 +2,7 @@
2
2
  """
3
3
  This module has functions to calculate model costs based on tokens and to fetch pricing information.
4
4
  """
5
+
5
6
  import os
6
7
  import json
7
8
  import logging
@@ -9,7 +10,11 @@ from urllib.parse import urlparse
9
10
  from typing import Any, Dict, List, Tuple
10
11
  import math
11
12
  import requests
12
- from opentelemetry.sdk.resources import SERVICE_NAME, TELEMETRY_SDK_NAME, DEPLOYMENT_ENVIRONMENT
13
+ from opentelemetry.sdk.resources import (
14
+ SERVICE_NAME,
15
+ TELEMETRY_SDK_NAME,
16
+ DEPLOYMENT_ENVIRONMENT,
17
+ )
13
18
  from opentelemetry.trace import Status, StatusCode
14
19
  from opentelemetry._events import Event
15
20
  from openlit.semcov import SemanticConvention
@@ -17,6 +22,7 @@ from openlit.semcov import SemanticConvention
17
22
  # Set up logging
18
23
  logger = logging.getLogger(__name__)
19
24
 
25
+
20
26
  def response_as_dict(response):
21
27
  """
22
28
  Return parsed response as a dict
@@ -25,13 +31,14 @@ def response_as_dict(response):
25
31
  # pylint: disable=no-else-return
26
32
  if isinstance(response, dict):
27
33
  return response
28
- if hasattr(response, 'model_dump'):
34
+ if hasattr(response, "model_dump"):
29
35
  return response.model_dump()
30
- elif hasattr(response, 'parse'):
36
+ elif hasattr(response, "parse"):
31
37
  return response_as_dict(response.parse())
32
38
  else:
33
39
  return response
34
40
 
41
+
35
42
  def get_env_variable(name, arg_value, error_message):
36
43
  """
37
44
  Retrieve an environment variable if the argument is not provided
@@ -45,6 +52,7 @@ def get_env_variable(name, arg_value, error_message):
45
52
  raise RuntimeError(error_message)
46
53
  return value
47
54
 
55
+
48
56
  def general_tokens(text):
49
57
  """
50
58
  Calculate the number of tokens a given text would take up.
@@ -52,40 +60,45 @@ def general_tokens(text):
52
60
 
53
61
  return math.ceil(len(text) / 2)
54
62
 
63
+
55
64
  def get_chat_model_cost(model, pricing_info, prompt_tokens, completion_tokens):
56
65
  """
57
66
  Retrieve the cost of processing for a given model based on prompt and tokens.
58
67
  """
59
68
 
60
69
  try:
61
- cost = ((prompt_tokens / 1000) * pricing_info['chat'][model]['promptPrice']) + \
62
- ((completion_tokens / 1000) * pricing_info['chat'][model]['completionPrice'])
70
+ cost = ((prompt_tokens / 1000) * pricing_info["chat"][model]["promptPrice"]) + (
71
+ (completion_tokens / 1000) * pricing_info["chat"][model]["completionPrice"]
72
+ )
63
73
  except:
64
74
  cost = 0
65
75
  return cost
66
76
 
77
+
67
78
  def get_embed_model_cost(model, pricing_info, prompt_tokens):
68
79
  """
69
80
  Retrieve the cost of processing for a given model based on prompt tokens.
70
81
  """
71
82
 
72
83
  try:
73
- cost = (prompt_tokens / 1000) * pricing_info['embeddings'][model]
84
+ cost = (prompt_tokens / 1000) * pricing_info["embeddings"][model]
74
85
  except:
75
86
  cost = 0
76
87
  return cost
77
88
 
89
+
78
90
  def get_image_model_cost(model, pricing_info, size, quality):
79
91
  """
80
92
  Retrieve the cost of processing for a given model based on image size and quailty.
81
93
  """
82
94
 
83
95
  try:
84
- cost = pricing_info['images'][model][quality][size]
96
+ cost = pricing_info["images"][model][quality][size]
85
97
  except:
86
98
  cost = 0
87
99
  return cost
88
100
 
101
+
89
102
  def get_audio_model_cost(model, pricing_info, prompt, duration=None):
90
103
  """
91
104
  Retrieve the cost of processing for a given model based on prompt.
@@ -93,52 +106,59 @@ def get_audio_model_cost(model, pricing_info, prompt, duration=None):
93
106
 
94
107
  try:
95
108
  if prompt:
96
- cost = (len(prompt) / 1000) * pricing_info['audio'][model]
109
+ cost = (len(prompt) / 1000) * pricing_info["audio"][model]
97
110
  else:
98
- cost = duration * pricing_info['audio'][model]
111
+ cost = duration * pricing_info["audio"][model]
99
112
  except:
100
113
  cost = 0
101
114
  return cost
102
115
 
116
+
103
117
  def fetch_pricing_info(pricing_json=None):
104
118
  """
105
119
  Fetches pricing information from a specified URL or File Path.
106
120
  """
107
121
 
108
122
  if pricing_json:
109
- is_url = urlparse(pricing_json).scheme != ''
123
+ is_url = urlparse(pricing_json).scheme != ""
110
124
  if is_url:
111
125
  pricing_url = pricing_json
112
126
  else:
113
127
  try:
114
- with open(pricing_json, mode='r', encoding='utf-8') as f:
128
+ with open(pricing_json, mode="r", encoding="utf-8") as f:
115
129
  return json.load(f)
116
130
  except FileNotFoundError:
117
- logger.error('Pricing information file not found: %s', pricing_json)
131
+ logger.error("Pricing information file not found: %s", pricing_json)
118
132
  except json.JSONDecodeError:
119
- logger.error('Error decoding JSON from file: %s', pricing_json)
133
+ logger.error("Error decoding JSON from file: %s", pricing_json)
120
134
  except Exception as file_err:
121
- logger.error('Unexpected error occurred while reading file: %s', file_err)
135
+ logger.error(
136
+ "Unexpected error occurred while reading file: %s", file_err
137
+ )
122
138
  return {}
123
139
  else:
124
- pricing_url = 'https://raw.githubusercontent.com/openlit/openlit/main/assets/pricing.json'
140
+ pricing_url = (
141
+ "https://raw.githubusercontent.com/openlit/openlit/main/assets/pricing.json"
142
+ )
125
143
  try:
126
144
  # Set a timeout of 10 seconds for both the connection and the read
127
145
  response = requests.get(pricing_url, timeout=20)
128
146
  response.raise_for_status()
129
147
  return response.json()
130
148
  except requests.HTTPError as http_err:
131
- logger.error('HTTP error occured while fetching pricing info: %s', http_err)
149
+ logger.error("HTTP error occured while fetching pricing info: %s", http_err)
132
150
  except Exception as err:
133
- logger.error('Unexpected error occurred while fetching pricing info: %s', err)
151
+ logger.error("Unexpected error occurred while fetching pricing info: %s", err)
134
152
  return {}
135
153
 
136
- def handle_exception(span,e):
154
+
155
+ def handle_exception(span, e):
137
156
  """Handles Exception when LLM Function fails or trace creation fails."""
138
157
 
139
158
  span.record_exception(e)
140
159
  span.set_status(Status(StatusCode.ERROR))
141
160
 
161
+
142
162
  def calculate_ttft(timestamps: List[float], start_time: float) -> float:
143
163
  """
144
164
  Calculate the time to the first tokens.
@@ -148,16 +168,20 @@ def calculate_ttft(timestamps: List[float], start_time: float) -> float:
148
168
  return timestamps[0] - start_time
149
169
  return 0.0
150
170
 
171
+
151
172
  def calculate_tbt(timestamps: List[float]) -> float:
152
173
  """
153
174
  Calculate the average time between tokens.
154
175
  """
155
176
 
156
177
  if len(timestamps) > 1:
157
- time_diffs = [timestamps[i] - timestamps[i - 1] for i in range(1, len(timestamps))]
178
+ time_diffs = [
179
+ timestamps[i] - timestamps[i - 1] for i in range(1, len(timestamps))
180
+ ]
158
181
  return sum(time_diffs) / len(time_diffs)
159
182
  return 0.0
160
183
 
184
+
161
185
  def create_metrics_attributes(
162
186
  service_name: str,
163
187
  deployment_environment: str,
@@ -173,7 +197,7 @@ def create_metrics_attributes(
173
197
  """
174
198
 
175
199
  return {
176
- TELEMETRY_SDK_NAME: 'openlit',
200
+ TELEMETRY_SDK_NAME: "openlit",
177
201
  SERVICE_NAME: service_name,
178
202
  DEPLOYMENT_ENVIRONMENT: deployment_environment,
179
203
  SemanticConvention.GEN_AI_OPERATION: operation,
@@ -181,39 +205,41 @@ def create_metrics_attributes(
181
205
  SemanticConvention.GEN_AI_REQUEST_MODEL: request_model,
182
206
  SemanticConvention.SERVER_ADDRESS: server_address,
183
207
  SemanticConvention.SERVER_PORT: server_port,
184
- SemanticConvention.GEN_AI_RESPONSE_MODEL: response_model
208
+ SemanticConvention.GEN_AI_RESPONSE_MODEL: response_model,
185
209
  }
186
210
 
187
- def set_server_address_and_port(client_instance: Any,
188
- default_server_address: str, default_server_port: int) -> Tuple[str, int]:
211
+
212
+ def set_server_address_and_port(
213
+ client_instance: Any, default_server_address: str, default_server_port: int
214
+ ) -> Tuple[str, int]:
189
215
  """
190
216
  Determines and returns the server address and port based on the provided client's `base_url`,
191
217
  using defaults if none found or values are None.
192
218
  """
193
219
 
194
220
  # Try getting base_url from multiple potential attributes
195
- base_client = getattr(client_instance, '_client', None)
196
- base_url = getattr(base_client, 'base_url', None)
221
+ base_client = getattr(client_instance, "_client", None)
222
+ base_url = getattr(base_client, "base_url", None)
197
223
 
198
224
  if not base_url:
199
225
  # Attempt to get endpoint from instance._config.endpoint if base_url is not set
200
- config = getattr(client_instance, '_config', None)
201
- base_url = getattr(config, 'endpoint', None)
226
+ config = getattr(client_instance, "_config", None)
227
+ base_url = getattr(config, "endpoint", None)
202
228
 
203
229
  if not base_url:
204
230
  # Attempt to get server_url from instance.sdk_configuration.server_url
205
- config = getattr(client_instance, 'sdk_configuration', None)
206
- base_url = getattr(config, 'server_url', None)
231
+ config = getattr(client_instance, "sdk_configuration", None)
232
+ base_url = getattr(config, "server_url", None)
207
233
 
208
234
  if not base_url:
209
235
  # Attempt to get host from instance.config.host (used by Pinecone and other vector DBs)
210
- config = getattr(client_instance, 'config', None)
211
- base_url = getattr(config, 'host', None)
236
+ config = getattr(client_instance, "config", None)
237
+ base_url = getattr(config, "host", None)
212
238
 
213
239
  if base_url:
214
240
  if isinstance(base_url, str):
215
241
  # Check if it's a full URL or just a hostname
216
- if base_url.startswith(('http://', 'https://')):
242
+ if base_url.startswith(("http://", "https://")):
217
243
  url = urlparse(base_url)
218
244
  server_address = url.hostname or default_server_address
219
245
  server_port = url.port if url.port is not None else default_server_port
@@ -222,8 +248,8 @@ def set_server_address_and_port(client_instance: Any,
222
248
  server_address = base_url
223
249
  server_port = default_server_port
224
250
  else: # base_url might not be a str; handle as an object.
225
- server_address = getattr(base_url, 'host', None) or default_server_address
226
- port_attr = getattr(base_url, 'port', None)
251
+ server_address = getattr(base_url, "host", None) or default_server_address
252
+ port_attr = getattr(base_url, "port", None)
227
253
  server_port = port_attr if port_attr is not None else default_server_port
228
254
  else: # no base_url or endpoint provided; use defaults.
229
255
  server_address = default_server_address
@@ -231,6 +257,7 @@ def set_server_address_and_port(client_instance: Any,
231
257
 
232
258
  return server_address, server_port
233
259
 
260
+
234
261
  def otel_event(name, attributes, body):
235
262
  """
236
263
  Returns an OpenTelemetry Event object
@@ -242,28 +269,31 @@ def otel_event(name, attributes, body):
242
269
  body=body,
243
270
  )
244
271
 
272
+
245
273
  def extract_and_format_input(messages):
246
274
  """
247
275
  Process a list of messages to extract content and categorize
248
276
  them into fixed roles like 'user', 'assistant', 'system', 'tool'.
249
277
  """
250
278
 
251
- fixed_roles = ['user', 'assistant', 'system', 'tool', 'developer']
252
- formatted_messages = {role_key: {'role': '', 'content': ''} for role_key in fixed_roles}
279
+ fixed_roles = ["user", "assistant", "system", "tool", "developer"]
280
+ formatted_messages = {
281
+ role_key: {"role": "", "content": ""} for role_key in fixed_roles
282
+ }
253
283
 
254
284
  # Check if input is a simple string
255
285
  if isinstance(messages, str):
256
- formatted_messages['user'] = {'role': 'user', 'content': messages}
286
+ formatted_messages["user"] = {"role": "user", "content": messages}
257
287
  return formatted_messages
258
288
 
259
289
  for message in messages:
260
290
  message = response_as_dict(message)
261
291
 
262
- role = message.get('role')
292
+ role = message.get("role")
263
293
  if role not in fixed_roles:
264
294
  continue
265
295
 
266
- content = message.get('content', '')
296
+ content = message.get("content", "")
267
297
 
268
298
  # Prepare content as a string, handling both list and str
269
299
  if isinstance(content, list):
@@ -272,27 +302,29 @@ def extract_and_format_input(messages):
272
302
  content_str = content
273
303
 
274
304
  # Set the role in the formatted message and concatenate content
275
- if not formatted_messages[role]['role']:
276
- formatted_messages[role]['role'] = role
305
+ if not formatted_messages[role]["role"]:
306
+ formatted_messages[role]["role"] = role
277
307
 
278
- if formatted_messages[role]['content']:
279
- formatted_messages[role]['content'] += ' ' + content_str
308
+ if formatted_messages[role]["content"]:
309
+ formatted_messages[role]["content"] += " " + content_str
280
310
  else:
281
- formatted_messages[role]['content'] = content_str
311
+ formatted_messages[role]["content"] = content_str
282
312
 
283
313
  return formatted_messages
284
314
 
315
+
285
316
  # To be removed one the change to log events (from span events) is complete
286
317
  def concatenate_all_contents(formatted_messages):
287
318
  """
288
319
  Concatenate all 'content' fields into a single strin
289
320
  """
290
- return ' '.join(
291
- message_data['content']
321
+ return " ".join(
322
+ message_data["content"]
292
323
  for message_data in formatted_messages.values()
293
- if message_data['content']
324
+ if message_data["content"]
294
325
  )
295
326
 
327
+
296
328
  def format_and_concatenate(messages):
297
329
  """
298
330
  Process a list of messages to extract content, categorize them by role,
@@ -303,20 +335,22 @@ def format_and_concatenate(messages):
303
335
 
304
336
  # Check if input is a simple string
305
337
  if isinstance(messages, str):
306
- formatted_messages['user'] = {'role': 'user', 'content': messages}
338
+ formatted_messages["user"] = {"role": "user", "content": messages}
307
339
  elif isinstance(messages, list) and all(isinstance(m, str) for m in messages):
308
340
  # If it's a list of strings, each string is 'user' input
309
- user_content = ' '.join(messages)
310
- formatted_messages['user'] = {'role': 'user', 'content': user_content}
341
+ user_content = " ".join(messages)
342
+ formatted_messages["user"] = {"role": "user", "content": user_content}
311
343
  else:
312
344
  for message in messages:
313
345
  message = response_as_dict(message)
314
- role = message.get('role', 'unknown') # Default to 'unknown' if no role is specified
315
- content = message.get('content', '')
346
+ role = message.get(
347
+ "role", "unknown"
348
+ ) # Default to 'unknown' if no role is specified
349
+ content = message.get("content", "")
316
350
 
317
351
  # Initialize role in formatted messages if not present
318
352
  if role not in formatted_messages:
319
- formatted_messages[role] = {'role': role, 'content': ''}
353
+ formatted_messages[role] = {"role": role, "content": ""}
320
354
 
321
355
  # Handle list of dictionaries in content
322
356
  if isinstance(content, list):
@@ -324,8 +358,8 @@ def format_and_concatenate(messages):
324
358
  for item in content:
325
359
  if isinstance(item, dict):
326
360
  # Collect text or other attributes as needed
327
- text = item.get('text', '')
328
- image_url = item.get('image_url', '')
361
+ text = item.get("text", "")
362
+ image_url = item.get("image_url", "")
329
363
  content_str.append(text)
330
364
  content_str.append(image_url)
331
365
  content_str = ", ".join(filter(None, content_str))
@@ -333,20 +367,34 @@ def format_and_concatenate(messages):
333
367
  content_str = content
334
368
 
335
369
  # Concatenate content
336
- if formatted_messages[role]['content']:
337
- formatted_messages[role]['content'] += ' ' + content_str
370
+ if formatted_messages[role]["content"]:
371
+ formatted_messages[role]["content"] += " " + content_str
338
372
  else:
339
- formatted_messages[role]['content'] = content_str
373
+ formatted_messages[role]["content"] = content_str
340
374
 
341
375
  # Concatenate role and content for all messages
342
- return ' '.join(
376
+ return " ".join(
343
377
  f"{message_data['role']}: {message_data['content']}"
344
378
  for message_data in formatted_messages.values()
345
- if message_data['content']
379
+ if message_data["content"]
346
380
  )
347
381
 
348
- def common_span_attributes(scope, gen_ai_operation, gen_ai_system, server_address, server_port,
349
- request_model, response_model, environment, application_name, is_stream, tbt, ttft, version):
382
+
383
+ def common_span_attributes(
384
+ scope,
385
+ gen_ai_operation,
386
+ gen_ai_system,
387
+ server_address,
388
+ server_port,
389
+ request_model,
390
+ response_model,
391
+ environment,
392
+ application_name,
393
+ is_stream,
394
+ tbt,
395
+ ttft,
396
+ version,
397
+ ):
350
398
  """
351
399
  Set common span attributes for both chat and RAG operations.
352
400
  """
@@ -365,9 +413,25 @@ def common_span_attributes(scope, gen_ai_operation, gen_ai_system, server_addres
365
413
  scope._span.set_attribute(SemanticConvention.GEN_AI_SERVER_TTFT, ttft)
366
414
  scope._span.set_attribute(SemanticConvention.GEN_AI_SDK_VERSION, version)
367
415
 
368
- def record_completion_metrics(metrics, gen_ai_operation, gen_ai_system, server_address, server_port,
369
- request_model, response_model, environment, application_name, start_time, end_time, cost,
370
- input_tokens, output_tokens, tbt, ttft):
416
+
417
+ def record_completion_metrics(
418
+ metrics,
419
+ gen_ai_operation,
420
+ gen_ai_system,
421
+ server_address,
422
+ server_port,
423
+ request_model,
424
+ response_model,
425
+ environment,
426
+ application_name,
427
+ start_time,
428
+ end_time,
429
+ cost,
430
+ input_tokens,
431
+ output_tokens,
432
+ tbt,
433
+ ttft,
434
+ ):
371
435
  """
372
436
  Record completion metrics for the operation.
373
437
  """
@@ -382,7 +446,9 @@ def record_completion_metrics(metrics, gen_ai_operation, gen_ai_system, server_a
382
446
  service_name=application_name,
383
447
  deployment_environment=environment,
384
448
  )
385
- metrics["genai_client_usage_tokens"].record(input_tokens + output_tokens, attributes)
449
+ metrics["genai_client_usage_tokens"].record(
450
+ input_tokens + output_tokens, attributes
451
+ )
386
452
  metrics["genai_client_operation_duration"].record(end_time - start_time, attributes)
387
453
  metrics["genai_server_tbt"].record(tbt, attributes)
388
454
  metrics["genai_server_ttft"].record(ttft, attributes)
@@ -391,9 +457,22 @@ def record_completion_metrics(metrics, gen_ai_operation, gen_ai_system, server_a
391
457
  metrics["genai_prompt_tokens"].add(input_tokens, attributes)
392
458
  metrics["genai_cost"].record(cost, attributes)
393
459
 
394
- def record_embedding_metrics(metrics, gen_ai_operation, gen_ai_system, server_address, server_port,
395
- request_model, response_model, environment, application_name, start_time, end_time,
396
- input_tokens, cost):
460
+
461
+ def record_embedding_metrics(
462
+ metrics,
463
+ gen_ai_operation,
464
+ gen_ai_system,
465
+ server_address,
466
+ server_port,
467
+ request_model,
468
+ response_model,
469
+ environment,
470
+ application_name,
471
+ start_time,
472
+ end_time,
473
+ input_tokens,
474
+ cost,
475
+ ):
397
476
  """
398
477
  Record embedding-specific metrics for the operation.
399
478
  """
@@ -414,8 +493,21 @@ def record_embedding_metrics(metrics, gen_ai_operation, gen_ai_system, server_ad
414
493
  metrics["genai_prompt_tokens"].add(input_tokens, attributes)
415
494
  metrics["genai_cost"].record(cost, attributes)
416
495
 
417
- def record_audio_metrics(metrics, gen_ai_operation, gen_ai_system, server_address, server_port,
418
- request_model, response_model, environment, application_name, start_time, end_time, cost):
496
+
497
+ def record_audio_metrics(
498
+ metrics,
499
+ gen_ai_operation,
500
+ gen_ai_system,
501
+ server_address,
502
+ server_port,
503
+ request_model,
504
+ response_model,
505
+ environment,
506
+ application_name,
507
+ start_time,
508
+ end_time,
509
+ cost,
510
+ ):
419
511
  """
420
512
  Record audio-specific metrics for the operation.
421
513
  """
@@ -434,8 +526,21 @@ def record_audio_metrics(metrics, gen_ai_operation, gen_ai_system, server_addres
434
526
  metrics["genai_requests"].add(1, attributes)
435
527
  metrics["genai_cost"].record(cost, attributes)
436
528
 
437
- def record_image_metrics(metrics, gen_ai_operation, gen_ai_system, server_address, server_port,
438
- request_model, response_model, environment, application_name, start_time, end_time, cost):
529
+
530
+ def record_image_metrics(
531
+ metrics,
532
+ gen_ai_operation,
533
+ gen_ai_system,
534
+ server_address,
535
+ server_port,
536
+ request_model,
537
+ response_model,
538
+ environment,
539
+ application_name,
540
+ start_time,
541
+ end_time,
542
+ cost,
543
+ ):
439
544
  """
440
545
  Record image-specific metrics for the operation.
441
546
  """
@@ -454,14 +559,25 @@ def record_image_metrics(metrics, gen_ai_operation, gen_ai_system, server_addres
454
559
  metrics["genai_requests"].add(1, attributes)
455
560
  metrics["genai_cost"].record(cost, attributes)
456
561
 
457
- def common_db_span_attributes(scope, db_system, server_address, server_port,
458
- environment, application_name, version):
562
+
563
+ def common_db_span_attributes(
564
+ scope,
565
+ db_system,
566
+ server_address,
567
+ server_port,
568
+ environment,
569
+ application_name,
570
+ version,
571
+ ):
459
572
  """
460
573
  Set common span attributes for database operations.
461
574
  """
462
575
 
463
576
  scope._span.set_attribute(TELEMETRY_SDK_NAME, "openlit")
464
- scope._span.set_attribute(SemanticConvention.GEN_AI_OPERATION, SemanticConvention.GEN_AI_OPERATION_TYPE_VECTORDB)
577
+ scope._span.set_attribute(
578
+ SemanticConvention.GEN_AI_OPERATION,
579
+ SemanticConvention.GEN_AI_OPERATION_TYPE_VECTORDB,
580
+ )
465
581
  scope._span.set_attribute(SemanticConvention.DB_SYSTEM_NAME, db_system)
466
582
  scope._span.set_attribute(SemanticConvention.SERVER_ADDRESS, server_address)
467
583
  scope._span.set_attribute(SemanticConvention.SERVER_PORT, server_port)
@@ -469,8 +585,18 @@ def common_db_span_attributes(scope, db_system, server_address, server_port,
469
585
  scope._span.set_attribute(SERVICE_NAME, application_name)
470
586
  scope._span.set_attribute(SemanticConvention.DB_SDK_VERSION, version)
471
587
 
472
- def common_framework_span_attributes(scope, framework_system, server_address, server_port,
473
- environment, application_name, version, endpoint, instance=None):
588
+
589
+ def common_framework_span_attributes(
590
+ scope,
591
+ framework_system,
592
+ server_address,
593
+ server_port,
594
+ environment,
595
+ application_name,
596
+ version,
597
+ endpoint,
598
+ instance=None,
599
+ ):
474
600
  """
475
601
  Set common span attributes for GenAI framework operations.
476
602
  """
@@ -479,17 +605,31 @@ def common_framework_span_attributes(scope, framework_system, server_address, se
479
605
  scope._span.set_attribute(SemanticConvention.GEN_AI_SDK_VERSION, version)
480
606
  scope._span.set_attribute(SemanticConvention.GEN_AI_SYSTEM, framework_system)
481
607
  scope._span.set_attribute(SemanticConvention.GEN_AI_OPERATION, endpoint)
482
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_MODEL,
483
- getattr(instance, "model_name", "unknown") if instance else "unknown")
608
+ scope._span.set_attribute(
609
+ SemanticConvention.GEN_AI_REQUEST_MODEL,
610
+ getattr(instance, "model_name", "unknown") if instance else "unknown",
611
+ )
484
612
  scope._span.set_attribute(SemanticConvention.SERVER_ADDRESS, server_address)
485
613
  scope._span.set_attribute(SemanticConvention.SERVER_PORT, server_port)
486
614
  scope._span.set_attribute(DEPLOYMENT_ENVIRONMENT, environment)
487
615
  scope._span.set_attribute(SERVICE_NAME, application_name)
488
- scope._span.set_attribute(SemanticConvention.GEN_AI_CLIENT_OPERATION_DURATION,
489
- scope._end_time - scope._start_time)
616
+ scope._span.set_attribute(
617
+ SemanticConvention.GEN_AI_CLIENT_OPERATION_DURATION,
618
+ scope._end_time - scope._start_time,
619
+ )
490
620
 
491
- def record_framework_metrics(metrics, gen_ai_operation, gen_ai_system, server_address, server_port,
492
- environment, application_name, start_time, end_time):
621
+
622
+ def record_framework_metrics(
623
+ metrics,
624
+ gen_ai_operation,
625
+ gen_ai_system,
626
+ server_address,
627
+ server_port,
628
+ environment,
629
+ application_name,
630
+ start_time,
631
+ end_time,
632
+ ):
493
633
  """
494
634
  Record basic framework metrics for the operation (only gen_ai_requests counter).
495
635
  """
@@ -507,8 +647,17 @@ def record_framework_metrics(metrics, gen_ai_operation, gen_ai_system, server_ad
507
647
  metrics["genai_requests"].add(1, attributes)
508
648
  metrics["genai_client_operation_duration"].record(end_time - start_time, attributes)
509
649
 
510
- def record_db_metrics(metrics, db_system, server_address, server_port,
511
- environment, application_name, start_time, end_time):
650
+
651
+ def record_db_metrics(
652
+ metrics,
653
+ db_system,
654
+ server_address,
655
+ server_port,
656
+ environment,
657
+ application_name,
658
+ start_time,
659
+ end_time,
660
+ ):
512
661
  """
513
662
  Record database-specific metrics for the operation.
514
663
  """