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.
- openlit/__helpers.py +235 -86
- openlit/__init__.py +16 -13
- openlit/_instrumentors.py +2 -1
- openlit/evals/all.py +50 -21
- openlit/evals/bias_detection.py +47 -20
- openlit/evals/hallucination.py +53 -22
- openlit/evals/toxicity.py +50 -21
- openlit/evals/utils.py +54 -30
- openlit/guard/all.py +61 -19
- openlit/guard/prompt_injection.py +34 -14
- openlit/guard/restrict_topic.py +46 -15
- openlit/guard/sensitive_topic.py +34 -14
- openlit/guard/utils.py +58 -22
- openlit/instrumentation/ag2/__init__.py +24 -8
- openlit/instrumentation/ag2/ag2.py +34 -13
- openlit/instrumentation/ag2/async_ag2.py +34 -13
- openlit/instrumentation/ag2/utils.py +133 -30
- openlit/instrumentation/ai21/__init__.py +43 -14
- openlit/instrumentation/ai21/ai21.py +47 -21
- openlit/instrumentation/ai21/async_ai21.py +47 -21
- openlit/instrumentation/ai21/utils.py +299 -78
- openlit/instrumentation/anthropic/__init__.py +21 -4
- openlit/instrumentation/anthropic/anthropic.py +28 -17
- openlit/instrumentation/anthropic/async_anthropic.py +28 -17
- openlit/instrumentation/anthropic/utils.py +145 -35
- openlit/instrumentation/assemblyai/__init__.py +11 -2
- openlit/instrumentation/assemblyai/assemblyai.py +15 -4
- openlit/instrumentation/assemblyai/utils.py +120 -25
- openlit/instrumentation/astra/__init__.py +43 -10
- openlit/instrumentation/astra/astra.py +28 -5
- openlit/instrumentation/astra/async_astra.py +28 -5
- openlit/instrumentation/astra/utils.py +151 -55
- openlit/instrumentation/azure_ai_inference/__init__.py +43 -10
- openlit/instrumentation/azure_ai_inference/async_azure_ai_inference.py +53 -21
- openlit/instrumentation/azure_ai_inference/azure_ai_inference.py +53 -21
- openlit/instrumentation/azure_ai_inference/utils.py +307 -83
- openlit/instrumentation/bedrock/__init__.py +21 -4
- openlit/instrumentation/bedrock/bedrock.py +63 -25
- openlit/instrumentation/bedrock/utils.py +139 -30
- openlit/instrumentation/chroma/__init__.py +89 -16
- openlit/instrumentation/chroma/chroma.py +28 -6
- openlit/instrumentation/chroma/utils.py +167 -51
- openlit/instrumentation/cohere/__init__.py +63 -18
- openlit/instrumentation/cohere/async_cohere.py +63 -24
- openlit/instrumentation/cohere/cohere.py +63 -24
- openlit/instrumentation/cohere/utils.py +286 -73
- openlit/instrumentation/controlflow/__init__.py +35 -9
- openlit/instrumentation/controlflow/controlflow.py +66 -33
- openlit/instrumentation/crawl4ai/__init__.py +25 -10
- openlit/instrumentation/crawl4ai/async_crawl4ai.py +78 -31
- openlit/instrumentation/crawl4ai/crawl4ai.py +78 -31
- openlit/instrumentation/crewai/__init__.py +40 -15
- openlit/instrumentation/crewai/async_crewai.py +32 -7
- openlit/instrumentation/crewai/crewai.py +32 -7
- openlit/instrumentation/crewai/utils.py +159 -56
- openlit/instrumentation/dynamiq/__init__.py +46 -12
- openlit/instrumentation/dynamiq/dynamiq.py +74 -33
- openlit/instrumentation/elevenlabs/__init__.py +23 -4
- openlit/instrumentation/elevenlabs/async_elevenlabs.py +16 -4
- openlit/instrumentation/elevenlabs/elevenlabs.py +16 -4
- openlit/instrumentation/elevenlabs/utils.py +128 -25
- openlit/instrumentation/embedchain/__init__.py +11 -2
- openlit/instrumentation/embedchain/embedchain.py +68 -35
- openlit/instrumentation/firecrawl/__init__.py +24 -7
- openlit/instrumentation/firecrawl/firecrawl.py +46 -20
- openlit/instrumentation/google_ai_studio/__init__.py +45 -10
- openlit/instrumentation/google_ai_studio/async_google_ai_studio.py +67 -44
- openlit/instrumentation/google_ai_studio/google_ai_studio.py +67 -44
- openlit/instrumentation/google_ai_studio/utils.py +180 -67
- openlit/instrumentation/gpt4all/__init__.py +22 -7
- openlit/instrumentation/gpt4all/gpt4all.py +67 -29
- openlit/instrumentation/gpt4all/utils.py +285 -61
- openlit/instrumentation/gpu/__init__.py +128 -47
- openlit/instrumentation/groq/__init__.py +21 -4
- openlit/instrumentation/groq/async_groq.py +33 -21
- openlit/instrumentation/groq/groq.py +33 -21
- openlit/instrumentation/groq/utils.py +192 -55
- openlit/instrumentation/haystack/__init__.py +70 -24
- openlit/instrumentation/haystack/async_haystack.py +28 -6
- openlit/instrumentation/haystack/haystack.py +28 -6
- openlit/instrumentation/haystack/utils.py +196 -74
- openlit/instrumentation/julep/__init__.py +69 -19
- openlit/instrumentation/julep/async_julep.py +53 -27
- openlit/instrumentation/julep/julep.py +53 -28
- openlit/instrumentation/langchain/__init__.py +74 -63
- openlit/instrumentation/langchain/callback_handler.py +1100 -0
- openlit/instrumentation/langchain_community/__init__.py +13 -2
- openlit/instrumentation/langchain_community/async_langchain_community.py +23 -5
- openlit/instrumentation/langchain_community/langchain_community.py +23 -5
- openlit/instrumentation/langchain_community/utils.py +35 -9
- openlit/instrumentation/letta/__init__.py +68 -15
- openlit/instrumentation/letta/letta.py +99 -54
- openlit/instrumentation/litellm/__init__.py +43 -14
- openlit/instrumentation/litellm/async_litellm.py +51 -26
- openlit/instrumentation/litellm/litellm.py +51 -26
- openlit/instrumentation/litellm/utils.py +304 -102
- openlit/instrumentation/llamaindex/__init__.py +267 -90
- openlit/instrumentation/llamaindex/async_llamaindex.py +28 -6
- openlit/instrumentation/llamaindex/llamaindex.py +28 -6
- openlit/instrumentation/llamaindex/utils.py +204 -91
- openlit/instrumentation/mem0/__init__.py +11 -2
- openlit/instrumentation/mem0/mem0.py +50 -29
- openlit/instrumentation/milvus/__init__.py +10 -2
- openlit/instrumentation/milvus/milvus.py +31 -6
- openlit/instrumentation/milvus/utils.py +166 -67
- openlit/instrumentation/mistral/__init__.py +63 -18
- openlit/instrumentation/mistral/async_mistral.py +63 -24
- openlit/instrumentation/mistral/mistral.py +63 -24
- openlit/instrumentation/mistral/utils.py +277 -69
- openlit/instrumentation/multion/__init__.py +69 -19
- openlit/instrumentation/multion/async_multion.py +57 -26
- openlit/instrumentation/multion/multion.py +57 -26
- openlit/instrumentation/ollama/__init__.py +39 -18
- openlit/instrumentation/ollama/async_ollama.py +57 -26
- openlit/instrumentation/ollama/ollama.py +57 -26
- openlit/instrumentation/ollama/utils.py +226 -50
- openlit/instrumentation/openai/__init__.py +156 -32
- openlit/instrumentation/openai/async_openai.py +147 -67
- openlit/instrumentation/openai/openai.py +150 -67
- openlit/instrumentation/openai/utils.py +657 -185
- openlit/instrumentation/openai_agents/__init__.py +5 -1
- openlit/instrumentation/openai_agents/processor.py +110 -90
- openlit/instrumentation/phidata/__init__.py +13 -5
- openlit/instrumentation/phidata/phidata.py +67 -32
- openlit/instrumentation/pinecone/__init__.py +48 -9
- openlit/instrumentation/pinecone/async_pinecone.py +27 -5
- openlit/instrumentation/pinecone/pinecone.py +27 -5
- openlit/instrumentation/pinecone/utils.py +153 -47
- openlit/instrumentation/premai/__init__.py +22 -7
- openlit/instrumentation/premai/premai.py +51 -26
- openlit/instrumentation/premai/utils.py +246 -59
- openlit/instrumentation/pydantic_ai/__init__.py +49 -22
- openlit/instrumentation/pydantic_ai/pydantic_ai.py +69 -16
- openlit/instrumentation/pydantic_ai/utils.py +89 -24
- openlit/instrumentation/qdrant/__init__.py +19 -4
- openlit/instrumentation/qdrant/async_qdrant.py +33 -7
- openlit/instrumentation/qdrant/qdrant.py +33 -7
- openlit/instrumentation/qdrant/utils.py +228 -93
- openlit/instrumentation/reka/__init__.py +23 -10
- openlit/instrumentation/reka/async_reka.py +17 -11
- openlit/instrumentation/reka/reka.py +17 -11
- openlit/instrumentation/reka/utils.py +138 -36
- openlit/instrumentation/together/__init__.py +44 -12
- openlit/instrumentation/together/async_together.py +50 -27
- openlit/instrumentation/together/together.py +50 -27
- openlit/instrumentation/together/utils.py +301 -71
- openlit/instrumentation/transformers/__init__.py +2 -1
- openlit/instrumentation/transformers/transformers.py +13 -3
- openlit/instrumentation/transformers/utils.py +139 -36
- openlit/instrumentation/vertexai/__init__.py +81 -16
- openlit/instrumentation/vertexai/async_vertexai.py +33 -15
- openlit/instrumentation/vertexai/utils.py +123 -27
- openlit/instrumentation/vertexai/vertexai.py +33 -15
- openlit/instrumentation/vllm/__init__.py +12 -5
- openlit/instrumentation/vllm/utils.py +121 -31
- openlit/instrumentation/vllm/vllm.py +16 -10
- openlit/otel/events.py +35 -10
- openlit/otel/metrics.py +32 -24
- openlit/otel/tracing.py +24 -9
- openlit/semcov/__init__.py +72 -6
- {openlit-1.34.30.dist-info → openlit-1.34.31.dist-info}/METADATA +2 -1
- openlit-1.34.31.dist-info/RECORD +166 -0
- openlit/instrumentation/langchain/async_langchain.py +0 -102
- openlit/instrumentation/langchain/langchain.py +0 -102
- openlit/instrumentation/langchain/utils.py +0 -252
- openlit-1.34.30.dist-info/RECORD +0 -168
- {openlit-1.34.30.dist-info → openlit-1.34.31.dist-info}/LICENSE +0 -0
- {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
|
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,
|
34
|
+
if hasattr(response, "model_dump"):
|
29
35
|
return response.model_dump()
|
30
|
-
elif hasattr(response,
|
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[
|
62
|
-
(
|
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[
|
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[
|
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[
|
109
|
+
cost = (len(prompt) / 1000) * pricing_info["audio"][model]
|
97
110
|
else:
|
98
|
-
cost = duration * pricing_info[
|
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=
|
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(
|
131
|
+
logger.error("Pricing information file not found: %s", pricing_json)
|
118
132
|
except json.JSONDecodeError:
|
119
|
-
logger.error(
|
133
|
+
logger.error("Error decoding JSON from file: %s", pricing_json)
|
120
134
|
except Exception as file_err:
|
121
|
-
logger.error(
|
135
|
+
logger.error(
|
136
|
+
"Unexpected error occurred while reading file: %s", file_err
|
137
|
+
)
|
122
138
|
return {}
|
123
139
|
else:
|
124
|
-
pricing_url =
|
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(
|
149
|
+
logger.error("HTTP error occured while fetching pricing info: %s", http_err)
|
132
150
|
except Exception as err:
|
133
|
-
logger.error(
|
151
|
+
logger.error("Unexpected error occurred while fetching pricing info: %s", err)
|
134
152
|
return {}
|
135
153
|
|
136
|
-
|
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 = [
|
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:
|
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
|
-
|
188
|
-
|
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,
|
196
|
-
base_url = getattr(base_client,
|
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,
|
201
|
-
base_url = getattr(config,
|
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,
|
206
|
-
base_url = getattr(config,
|
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,
|
211
|
-
base_url = getattr(config,
|
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((
|
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,
|
226
|
-
port_attr = getattr(base_url,
|
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 = [
|
252
|
-
formatted_messages = {
|
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[
|
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(
|
292
|
+
role = message.get("role")
|
263
293
|
if role not in fixed_roles:
|
264
294
|
continue
|
265
295
|
|
266
|
-
content = message.get(
|
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][
|
276
|
-
formatted_messages[role][
|
305
|
+
if not formatted_messages[role]["role"]:
|
306
|
+
formatted_messages[role]["role"] = role
|
277
307
|
|
278
|
-
if formatted_messages[role][
|
279
|
-
formatted_messages[role][
|
308
|
+
if formatted_messages[role]["content"]:
|
309
|
+
formatted_messages[role]["content"] += " " + content_str
|
280
310
|
else:
|
281
|
-
formatted_messages[role][
|
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
|
291
|
-
message_data[
|
321
|
+
return " ".join(
|
322
|
+
message_data["content"]
|
292
323
|
for message_data in formatted_messages.values()
|
293
|
-
if message_data[
|
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[
|
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 =
|
310
|
-
formatted_messages[
|
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(
|
315
|
-
|
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] = {
|
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(
|
328
|
-
image_url = item.get(
|
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][
|
337
|
-
formatted_messages[role][
|
370
|
+
if formatted_messages[role]["content"]:
|
371
|
+
formatted_messages[role]["content"] += " " + content_str
|
338
372
|
else:
|
339
|
-
formatted_messages[role][
|
373
|
+
formatted_messages[role]["content"] = content_str
|
340
374
|
|
341
375
|
# Concatenate role and content for all messages
|
342
|
-
return
|
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[
|
379
|
+
if message_data["content"]
|
346
380
|
)
|
347
381
|
|
348
|
-
|
349
|
-
|
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
|
-
|
369
|
-
|
370
|
-
|
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(
|
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
|
-
|
395
|
-
|
396
|
-
|
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
|
-
|
418
|
-
|
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
|
-
|
438
|
-
|
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
|
-
|
458
|
-
|
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(
|
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
|
-
|
473
|
-
|
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(
|
483
|
-
|
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(
|
489
|
-
|
616
|
+
scope._span.set_attribute(
|
617
|
+
SemanticConvention.GEN_AI_CLIENT_OPERATION_DURATION,
|
618
|
+
scope._end_time - scope._start_time,
|
619
|
+
)
|
490
620
|
|
491
|
-
|
492
|
-
|
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
|
-
|
511
|
-
|
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
|
"""
|