openlit 1.34.29__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 +111 -24
  53. openlit/instrumentation/crewai/async_crewai.py +114 -0
  54. openlit/instrumentation/crewai/crewai.py +104 -131
  55. openlit/instrumentation/crewai/utils.py +615 -0
  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 +312 -101
  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 +660 -186
  121. openlit/instrumentation/openai_agents/__init__.py +6 -2
  122. openlit/instrumentation/openai_agents/processor.py +409 -537
  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 +101 -7
  161. {openlit-1.34.29.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.29.dist-info/RECORD +0 -166
  167. {openlit-1.34.29.dist-info → openlit-1.34.31.dist-info}/LICENSE +0 -0
  168. {openlit-1.34.29.dist-info → openlit-1.34.31.dist-info}/WHEEL +0 -0
@@ -1,6 +1,7 @@
1
1
  """
2
2
  LiteLLM OpenTelemetry instrumentation utility functions
3
3
  """
4
+
4
5
  import time
5
6
 
6
7
  from opentelemetry.trace import Status, StatusCode
@@ -17,6 +18,7 @@ from openlit.__helpers import (
17
18
  )
18
19
  from openlit.semcov import SemanticConvention
19
20
 
21
+
20
22
  def format_content(messages):
21
23
  """
22
24
  Process a list of messages to extract content.
@@ -24,20 +26,22 @@ def format_content(messages):
24
26
 
25
27
  formatted_messages = []
26
28
  for message in messages:
27
- role = message['role']
28
- content = message['content']
29
+ role = message["role"]
30
+ content = message["content"]
29
31
 
30
32
  if isinstance(content, list):
31
33
  content_str = ", ".join(
32
- f'{item["type"]}: {item["text"] if "text" in item else item["image_url"]}'
33
- if "type" in item else f'text: {item["text"]}'
34
+ f"{item['type']}: {item['text'] if 'text' in item else item['image_url']}"
35
+ if "type" in item
36
+ else f"text: {item['text']}"
34
37
  for item in content
35
38
  )
36
- formatted_messages.append(f'{role}: {content_str}')
39
+ formatted_messages.append(f"{role}: {content_str}")
37
40
  else:
38
- formatted_messages.append(f'{role}: {content}')
41
+ formatted_messages.append(f"{role}: {content}")
42
+
43
+ return "\n".join(formatted_messages)
39
44
 
40
- return '\n'.join(formatted_messages)
41
45
 
42
46
  def process_chunk(scope, chunk):
43
47
  """
@@ -55,45 +59,63 @@ def process_chunk(scope, chunk):
55
59
  chunked = response_as_dict(chunk)
56
60
 
57
61
  # Collect message IDs and aggregated response from events
58
- if (len(chunked.get('choices', [])) > 0 and ('delta' in chunked.get('choices')[0] and
59
- 'content' in chunked.get('choices')[0].get('delta', {}))):
60
-
61
- content = chunked.get('choices')[0].get('delta').get('content')
62
+ if len(chunked.get("choices", [])) > 0 and (
63
+ "delta" in chunked.get("choices")[0]
64
+ and "content" in chunked.get("choices")[0].get("delta", {})
65
+ ):
66
+ content = chunked.get("choices")[0].get("delta").get("content")
62
67
  if content:
63
68
  scope._llmresponse += content
64
69
 
65
70
  # Handle tool calls in streaming - optimized
66
- delta_tools = chunked.get('choices', [{}])[0].get('delta', {}).get('tool_calls')
71
+ delta_tools = chunked.get("choices", [{}])[0].get("delta", {}).get("tool_calls")
67
72
  if delta_tools:
68
73
  scope._tools = scope._tools or []
69
74
 
70
75
  for tool in delta_tools:
71
- idx = tool.get('index', 0)
76
+ idx = tool.get("index", 0)
72
77
 
73
78
  # Extend list if needed
74
79
  scope._tools.extend([{}] * (idx + 1 - len(scope._tools)))
75
80
 
76
- if tool.get('id'): # New tool (id exists)
77
- func = tool.get('function', {})
81
+ if tool.get("id"): # New tool (id exists)
82
+ func = tool.get("function", {})
78
83
  scope._tools[idx] = {
79
- 'id': tool['id'],
80
- 'function': {'name': func.get('name', ''), 'arguments': func.get('arguments', '')},
81
- 'type': tool.get('type', 'function')
84
+ "id": tool["id"],
85
+ "function": {
86
+ "name": func.get("name", ""),
87
+ "arguments": func.get("arguments", ""),
88
+ },
89
+ "type": tool.get("type", "function"),
82
90
  }
83
- elif scope._tools[idx] and 'function' in tool: # Append args (id is None)
84
- scope._tools[idx]['function']['arguments'] += tool['function'].get('arguments', '')
85
-
86
- if chunked.get('usage'):
87
- scope._input_tokens = chunked.get('usage').get('prompt_tokens', 0)
88
- scope._output_tokens = chunked.get('usage').get('completion_tokens', 0)
89
- scope._response_id = chunked.get('id')
90
- scope._response_model = chunked.get('model')
91
- scope._finish_reason = chunked.get('choices', [{}])[0].get('finish_reason')
92
- scope._response_service_tier = str(chunked.get('system_fingerprint', ''))
91
+ elif (
92
+ scope._tools[idx] and "function" in tool
93
+ ): # Append args (id is None)
94
+ scope._tools[idx]["function"]["arguments"] += tool["function"].get(
95
+ "arguments", ""
96
+ )
97
+
98
+ if chunked.get("usage"):
99
+ scope._input_tokens = chunked.get("usage").get("prompt_tokens", 0)
100
+ scope._output_tokens = chunked.get("usage").get("completion_tokens", 0)
101
+ scope._response_id = chunked.get("id")
102
+ scope._response_model = chunked.get("model")
103
+ scope._finish_reason = chunked.get("choices", [{}])[0].get("finish_reason")
104
+ scope._response_service_tier = str(chunked.get("system_fingerprint", ""))
93
105
  scope._end_time = time.time()
94
106
 
95
- def common_chat_logic(scope, pricing_info, environment, application_name, metrics,
96
- capture_message_content, disable_metrics, version, is_stream):
107
+
108
+ def common_chat_logic(
109
+ scope,
110
+ pricing_info,
111
+ environment,
112
+ application_name,
113
+ metrics,
114
+ capture_message_content,
115
+ disable_metrics,
116
+ version,
117
+ is_stream,
118
+ ):
97
119
  """
98
120
  Process chat request and generate Telemetry
99
121
  """
@@ -101,60 +123,135 @@ def common_chat_logic(scope, pricing_info, environment, application_name, metric
101
123
  if len(scope._timestamps) > 1:
102
124
  scope._tbt = calculate_tbt(scope._timestamps)
103
125
 
104
- prompt = format_content(scope._kwargs.get('messages', []))
105
- request_model = scope._kwargs.get('model', 'openai/gpt-4o')
126
+ prompt = format_content(scope._kwargs.get("messages", []))
127
+ request_model = scope._kwargs.get("model", "openai/gpt-4o")
106
128
 
107
- cost = get_chat_model_cost(request_model, pricing_info, scope._input_tokens, scope._output_tokens)
129
+ cost = get_chat_model_cost(
130
+ request_model, pricing_info, scope._input_tokens, scope._output_tokens
131
+ )
108
132
 
109
133
  # Common Span Attributes
110
- common_span_attributes(scope,
111
- SemanticConvention.GEN_AI_OPERATION_TYPE_CHAT, SemanticConvention.GEN_AI_SYSTEM_LITELLM,
112
- scope._server_address, scope._server_port, request_model, scope._response_model,
113
- environment, application_name, is_stream, scope._tbt, scope._ttft, version)
134
+ common_span_attributes(
135
+ scope,
136
+ SemanticConvention.GEN_AI_OPERATION_TYPE_CHAT,
137
+ SemanticConvention.GEN_AI_SYSTEM_LITELLM,
138
+ scope._server_address,
139
+ scope._server_port,
140
+ request_model,
141
+ scope._response_model,
142
+ environment,
143
+ application_name,
144
+ is_stream,
145
+ scope._tbt,
146
+ scope._ttft,
147
+ version,
148
+ )
149
+
150
+ # Helper function to handle None values with proper defaults
151
+ def safe_get(value, default):
152
+ return default if value is None else value
114
153
 
115
154
  # Span Attributes for Request parameters
116
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_SEED, scope._kwargs.get('seed', ''))
117
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_FREQUENCY_PENALTY, scope._kwargs.get('frequency_penalty', 0.0))
118
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_MAX_TOKENS, scope._kwargs.get('max_tokens', -1))
119
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_PRESENCE_PENALTY, scope._kwargs.get('presence_penalty', 0.0))
120
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_STOP_SEQUENCES, scope._kwargs.get('stop', []))
121
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_TEMPERATURE, scope._kwargs.get('temperature', 1.0))
122
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_TOP_P, scope._kwargs.get('top_p', 1.0))
123
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_USER, scope._kwargs.get('user', ''))
124
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_SERVICE_TIER, scope._kwargs.get('service_tier', 'auto'))
155
+ scope._span.set_attribute(
156
+ SemanticConvention.GEN_AI_REQUEST_SEED, safe_get(scope._kwargs.get("seed"), "")
157
+ )
158
+ scope._span.set_attribute(
159
+ SemanticConvention.GEN_AI_REQUEST_FREQUENCY_PENALTY,
160
+ safe_get(scope._kwargs.get("frequency_penalty"), 0.0),
161
+ )
162
+ scope._span.set_attribute(
163
+ SemanticConvention.GEN_AI_REQUEST_MAX_TOKENS,
164
+ safe_get(scope._kwargs.get("max_tokens"), -1),
165
+ )
166
+ scope._span.set_attribute(
167
+ SemanticConvention.GEN_AI_REQUEST_PRESENCE_PENALTY,
168
+ safe_get(scope._kwargs.get("presence_penalty"), 0.0),
169
+ )
170
+ scope._span.set_attribute(
171
+ SemanticConvention.GEN_AI_REQUEST_STOP_SEQUENCES, scope._kwargs.get("stop", [])
172
+ )
173
+ scope._span.set_attribute(
174
+ SemanticConvention.GEN_AI_REQUEST_TEMPERATURE,
175
+ safe_get(scope._kwargs.get("temperature"), 1.0),
176
+ )
177
+ scope._span.set_attribute(
178
+ SemanticConvention.GEN_AI_REQUEST_TOP_P,
179
+ safe_get(scope._kwargs.get("top_p"), 1.0),
180
+ )
181
+ scope._span.set_attribute(
182
+ SemanticConvention.GEN_AI_REQUEST_USER, safe_get(scope._kwargs.get("user"), "")
183
+ )
184
+ scope._span.set_attribute(
185
+ SemanticConvention.GEN_AI_REQUEST_SERVICE_TIER,
186
+ safe_get(scope._kwargs.get("service_tier"), "auto"),
187
+ )
125
188
 
126
189
  # Span Attributes for Response parameters
127
190
  scope._span.set_attribute(SemanticConvention.GEN_AI_RESPONSE_ID, scope._response_id)
128
- scope._span.set_attribute(SemanticConvention.GEN_AI_RESPONSE_FINISH_REASON, [scope._finish_reason])
129
- scope._span.set_attribute(SemanticConvention.GEN_AI_RESPONSE_SERVICE_TIER, scope._response_service_tier)
130
- scope._span.set_attribute(SemanticConvention.GEN_AI_RESPONSE_SYSTEM_FINGERPRINT, scope._response_service_tier)
131
- scope._span.set_attribute(SemanticConvention.GEN_AI_OUTPUT_TYPE, "text" if isinstance(scope._llmresponse, str) else "json")
191
+ scope._span.set_attribute(
192
+ SemanticConvention.GEN_AI_RESPONSE_FINISH_REASON, [scope._finish_reason]
193
+ )
194
+ scope._span.set_attribute(
195
+ SemanticConvention.GEN_AI_RESPONSE_SERVICE_TIER, scope._response_service_tier
196
+ )
197
+ scope._span.set_attribute(
198
+ SemanticConvention.GEN_AI_RESPONSE_SYSTEM_FINGERPRINT,
199
+ scope._response_service_tier,
200
+ )
201
+ scope._span.set_attribute(
202
+ SemanticConvention.GEN_AI_OUTPUT_TYPE,
203
+ "text" if isinstance(scope._llmresponse, str) else "json",
204
+ )
132
205
 
133
206
  # Span Attributes for Cost and Tokens
134
- scope._span.set_attribute(SemanticConvention.GEN_AI_USAGE_INPUT_TOKENS, scope._input_tokens)
135
- scope._span.set_attribute(SemanticConvention.GEN_AI_USAGE_OUTPUT_TOKENS, scope._output_tokens)
136
- scope._span.set_attribute(SemanticConvention.GEN_AI_CLIENT_TOKEN_USAGE, scope._input_tokens + scope._output_tokens)
207
+ scope._span.set_attribute(
208
+ SemanticConvention.GEN_AI_USAGE_INPUT_TOKENS, scope._input_tokens
209
+ )
210
+ scope._span.set_attribute(
211
+ SemanticConvention.GEN_AI_USAGE_OUTPUT_TOKENS, scope._output_tokens
212
+ )
213
+ scope._span.set_attribute(
214
+ SemanticConvention.GEN_AI_CLIENT_TOKEN_USAGE,
215
+ scope._input_tokens + scope._output_tokens,
216
+ )
137
217
  scope._span.set_attribute(SemanticConvention.GEN_AI_USAGE_COST, cost)
138
218
 
139
219
  # Span Attributes for Tools - optimized
140
220
  if scope._tools:
141
221
  tools = scope._tools if isinstance(scope._tools, list) else [scope._tools]
142
222
 
143
- names, ids, args = zip(*[
144
- (t.get("function", {}).get("name", ""),
145
- str(t.get("id", "")),
146
- str(t.get("function", {}).get("arguments", "")))
147
- for t in tools if isinstance(t, dict) and t
148
- ]) if tools else ([], [], [])
223
+ names, ids, args = (
224
+ zip(
225
+ *[
226
+ (
227
+ t.get("function", {}).get("name", ""),
228
+ str(t.get("id", "")),
229
+ str(t.get("function", {}).get("arguments", "")),
230
+ )
231
+ for t in tools
232
+ if isinstance(t, dict) and t
233
+ ]
234
+ )
235
+ if tools
236
+ else ([], [], [])
237
+ )
149
238
 
150
- scope._span.set_attribute(SemanticConvention.GEN_AI_TOOL_NAME, ", ".join(filter(None, names)))
151
- scope._span.set_attribute(SemanticConvention.GEN_AI_TOOL_CALL_ID, ", ".join(filter(None, ids)))
152
- scope._span.set_attribute(SemanticConvention.GEN_AI_TOOL_ARGS, ", ".join(filter(None, args)))
239
+ scope._span.set_attribute(
240
+ SemanticConvention.GEN_AI_TOOL_NAME, ", ".join(filter(None, names))
241
+ )
242
+ scope._span.set_attribute(
243
+ SemanticConvention.GEN_AI_TOOL_CALL_ID, ", ".join(filter(None, ids))
244
+ )
245
+ scope._span.set_attribute(
246
+ SemanticConvention.GEN_AI_TOOL_ARGS, ", ".join(filter(None, args))
247
+ )
153
248
 
154
249
  # Span Attributes for Content
155
250
  if capture_message_content:
156
251
  scope._span.set_attribute(SemanticConvention.GEN_AI_CONTENT_PROMPT, prompt)
157
- scope._span.set_attribute(SemanticConvention.GEN_AI_CONTENT_COMPLETION, scope._llmresponse)
252
+ scope._span.set_attribute(
253
+ SemanticConvention.GEN_AI_CONTENT_COMPLETION, scope._llmresponse
254
+ )
158
255
 
159
256
  # To be removed once the change to span_attributes (from span events) is complete
160
257
  scope._span.add_event(
@@ -174,23 +271,69 @@ def common_chat_logic(scope, pricing_info, environment, application_name, metric
174
271
 
175
272
  # Metrics
176
273
  if not disable_metrics:
177
- record_completion_metrics(metrics, SemanticConvention.GEN_AI_OPERATION_TYPE_CHAT, SemanticConvention.GEN_AI_SYSTEM_LITELLM,
178
- scope._server_address, scope._server_port, request_model, scope._response_model, environment,
179
- application_name, scope._start_time, scope._end_time, scope._input_tokens, scope._output_tokens,
180
- cost, scope._tbt, scope._ttft)
274
+ record_completion_metrics(
275
+ metrics,
276
+ SemanticConvention.GEN_AI_OPERATION_TYPE_CHAT,
277
+ SemanticConvention.GEN_AI_SYSTEM_LITELLM,
278
+ scope._server_address,
279
+ scope._server_port,
280
+ request_model,
281
+ scope._response_model,
282
+ environment,
283
+ application_name,
284
+ scope._start_time,
285
+ scope._end_time,
286
+ scope._input_tokens,
287
+ scope._output_tokens,
288
+ cost,
289
+ scope._tbt,
290
+ scope._ttft,
291
+ )
292
+
181
293
 
182
- def process_streaming_chat_response(scope, pricing_info, environment, application_name, metrics,
183
- capture_message_content=False, disable_metrics=False, version=""):
294
+ def process_streaming_chat_response(
295
+ scope,
296
+ pricing_info,
297
+ environment,
298
+ application_name,
299
+ metrics,
300
+ capture_message_content=False,
301
+ disable_metrics=False,
302
+ version="",
303
+ ):
184
304
  """
185
305
  Process streaming chat request and generate Telemetry
186
306
  """
187
307
 
188
- common_chat_logic(scope, pricing_info, environment, application_name, metrics,
189
- capture_message_content, disable_metrics, version, is_stream=True)
308
+ common_chat_logic(
309
+ scope,
310
+ pricing_info,
311
+ environment,
312
+ application_name,
313
+ metrics,
314
+ capture_message_content,
315
+ disable_metrics,
316
+ version,
317
+ is_stream=True,
318
+ )
319
+
190
320
 
191
- def process_chat_response(response, request_model, pricing_info, server_port, server_address,
192
- environment, application_name, metrics, start_time, span, capture_message_content=False,
193
- disable_metrics=False, version="1.0.0", **kwargs):
321
+ def process_chat_response(
322
+ response,
323
+ request_model,
324
+ pricing_info,
325
+ server_port,
326
+ server_address,
327
+ environment,
328
+ application_name,
329
+ metrics,
330
+ start_time,
331
+ span,
332
+ capture_message_content=False,
333
+ disable_metrics=False,
334
+ version="1.0.0",
335
+ **kwargs,
336
+ ):
194
337
  """
195
338
  Process chat request and generate Telemetry
196
339
  """
@@ -206,12 +349,14 @@ def process_chat_response(response, request_model, pricing_info, server_port, se
206
349
  (choice.get("message", {}).get("content") or "")
207
350
  for choice in response_dict.get("choices", [])
208
351
  )
209
- scope._input_tokens = response_dict.get('usage', {}).get('prompt_tokens', 0)
210
- scope._output_tokens = response_dict.get('usage', {}).get('completion_tokens', 0)
211
- scope._response_id = response_dict.get('id')
212
- scope._response_model = response_dict.get('model')
213
- scope._finish_reason = str(response_dict.get('choices', [])[0].get('finish_reason', ''))
214
- scope._response_service_tier = str(response_dict.get('system_fingerprint', ''))
352
+ scope._input_tokens = response_dict.get("usage", {}).get("prompt_tokens", 0)
353
+ scope._output_tokens = response_dict.get("usage", {}).get("completion_tokens", 0)
354
+ scope._response_id = response_dict.get("id")
355
+ scope._response_model = response_dict.get("model")
356
+ scope._finish_reason = str(
357
+ response_dict.get("choices", [])[0].get("finish_reason", "")
358
+ )
359
+ scope._response_service_tier = str(response_dict.get("system_fingerprint", ""))
215
360
  scope._timestamps = []
216
361
  scope._ttft, scope._tbt = scope._end_time - scope._start_time, 0
217
362
  scope._server_address, scope._server_port = server_address, server_port
@@ -219,18 +364,43 @@ def process_chat_response(response, request_model, pricing_info, server_port, se
219
364
 
220
365
  # Handle tool calls
221
366
  if scope._kwargs.get("tools"):
222
- scope._tools = response_dict.get("choices", [{}])[0].get("message", {}).get("tool_calls")
367
+ scope._tools = (
368
+ response_dict.get("choices", [{}])[0].get("message", {}).get("tool_calls")
369
+ )
223
370
  else:
224
371
  scope._tools = None
225
372
 
226
- common_chat_logic(scope, pricing_info, environment, application_name, metrics,
227
- capture_message_content, disable_metrics, version, is_stream=False)
373
+ common_chat_logic(
374
+ scope,
375
+ pricing_info,
376
+ environment,
377
+ application_name,
378
+ metrics,
379
+ capture_message_content,
380
+ disable_metrics,
381
+ version,
382
+ is_stream=False,
383
+ )
228
384
 
229
385
  return response
230
386
 
231
- def process_embedding_response(response, request_model, pricing_info, server_port, server_address,
232
- environment, application_name, metrics, start_time, span, capture_message_content=False,
233
- disable_metrics=False, version="1.0.0", **kwargs):
387
+
388
+ def process_embedding_response(
389
+ response,
390
+ request_model,
391
+ pricing_info,
392
+ server_port,
393
+ server_address,
394
+ environment,
395
+ application_name,
396
+ metrics,
397
+ start_time,
398
+ span,
399
+ capture_message_content=False,
400
+ disable_metrics=False,
401
+ version="1.0.0",
402
+ **kwargs,
403
+ ):
234
404
  """
235
405
  Process embedding request and generate Telemetry
236
406
  """
@@ -242,8 +412,8 @@ def process_embedding_response(response, request_model, pricing_info, server_por
242
412
  scope._start_time = start_time
243
413
  scope._end_time = time.time()
244
414
  scope._span = span
245
- scope._input_tokens = response_dict.get('usage', {}).get('prompt_tokens', 0)
246
- scope._response_model = response_dict.get('model')
415
+ scope._input_tokens = response_dict.get("usage", {}).get("prompt_tokens", 0)
416
+ scope._response_model = response_dict.get("model")
247
417
  scope._server_address, scope._server_port = server_address, server_port
248
418
  scope._kwargs = kwargs
249
419
 
@@ -251,29 +421,58 @@ def process_embedding_response(response, request_model, pricing_info, server_por
251
421
  cost = get_embed_model_cost(request_model, pricing_info, scope._input_tokens)
252
422
 
253
423
  # Common Span Attributes
254
- common_span_attributes(scope,
255
- SemanticConvention.GEN_AI_OPERATION_TYPE_EMBEDDING, SemanticConvention.GEN_AI_SYSTEM_LITELLM,
256
- scope._server_address, scope._server_port, request_model, scope._response_model,
257
- environment, application_name, False, 0, scope._end_time - scope._start_time, version)
424
+ common_span_attributes(
425
+ scope,
426
+ SemanticConvention.GEN_AI_OPERATION_TYPE_EMBEDDING,
427
+ SemanticConvention.GEN_AI_SYSTEM_LITELLM,
428
+ scope._server_address,
429
+ scope._server_port,
430
+ request_model,
431
+ scope._response_model,
432
+ environment,
433
+ application_name,
434
+ False,
435
+ 0,
436
+ scope._end_time - scope._start_time,
437
+ version,
438
+ )
439
+
440
+ # Helper function to handle None values with proper defaults
441
+ def safe_get(value, default):
442
+ return default if value is None else value
258
443
 
259
444
  # Span Attributes for Request parameters
260
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_ENCODING_FORMATS, [scope._kwargs.get('encoding_format', 'float')])
261
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_USER, scope._kwargs.get('user', ''))
445
+ scope._span.set_attribute(
446
+ SemanticConvention.GEN_AI_REQUEST_ENCODING_FORMATS,
447
+ [scope._kwargs.get("encoding_format", "float")],
448
+ )
449
+ scope._span.set_attribute(
450
+ SemanticConvention.GEN_AI_REQUEST_USER, safe_get(scope._kwargs.get("user"), "")
451
+ )
262
452
 
263
453
  # Span Attributes for Cost and Tokens
264
- scope._span.set_attribute(SemanticConvention.GEN_AI_USAGE_INPUT_TOKENS, scope._input_tokens)
265
- scope._span.set_attribute(SemanticConvention.GEN_AI_CLIENT_TOKEN_USAGE, scope._input_tokens)
454
+ scope._span.set_attribute(
455
+ SemanticConvention.GEN_AI_USAGE_INPUT_TOKENS, scope._input_tokens
456
+ )
457
+ scope._span.set_attribute(
458
+ SemanticConvention.GEN_AI_CLIENT_TOKEN_USAGE, scope._input_tokens
459
+ )
266
460
  scope._span.set_attribute(SemanticConvention.GEN_AI_USAGE_COST, cost)
267
461
 
268
462
  # Span Attributes for Content
269
463
  if capture_message_content:
270
- scope._span.set_attribute(SemanticConvention.GEN_AI_CONTENT_PROMPT, str(scope._kwargs.get('input', '')))
464
+ scope._span.set_attribute(
465
+ SemanticConvention.GEN_AI_CONTENT_PROMPT,
466
+ str(scope._kwargs.get("input", "")),
467
+ )
271
468
 
272
469
  # To be removed once the change to span_attributes (from span events) is complete
273
470
  scope._span.add_event(
274
471
  name=SemanticConvention.GEN_AI_CONTENT_PROMPT_EVENT,
275
472
  attributes={
276
- SemanticConvention.GEN_AI_CONTENT_PROMPT: str(scope._kwargs.get('input', '')),
473
+ SemanticConvention.GEN_AI_CONTENT_PROMPT: str(
474
+ scope._kwargs.get("input", "")
475
+ ),
277
476
  },
278
477
  )
279
478
 
@@ -281,8 +480,20 @@ def process_embedding_response(response, request_model, pricing_info, server_por
281
480
 
282
481
  # Metrics
283
482
  if not disable_metrics:
284
- record_embedding_metrics(metrics, SemanticConvention.GEN_AI_OPERATION_TYPE_EMBEDDING, SemanticConvention.GEN_AI_SYSTEM_LITELLM,
285
- scope._server_address, scope._server_port, request_model, scope._response_model, environment,
286
- application_name, scope._start_time, scope._end_time, scope._input_tokens, cost)
483
+ record_embedding_metrics(
484
+ metrics,
485
+ SemanticConvention.GEN_AI_OPERATION_TYPE_EMBEDDING,
486
+ SemanticConvention.GEN_AI_SYSTEM_LITELLM,
487
+ scope._server_address,
488
+ scope._server_port,
489
+ request_model,
490
+ scope._response_model,
491
+ environment,
492
+ application_name,
493
+ scope._start_time,
494
+ scope._end_time,
495
+ scope._input_tokens,
496
+ cost,
497
+ )
287
498
 
288
499
  return response