openlit 1.16.1__tar.gz → 1.17.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. {openlit-1.16.1 → openlit-1.17.0}/PKG-INFO +1 -1
  2. {openlit-1.16.1 → openlit-1.17.0}/pyproject.toml +1 -1
  3. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/anthropic/anthropic.py +28 -10
  4. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/anthropic/async_anthropic.py +27 -10
  5. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/bedrock/__init__.py +3 -3
  6. openlit-1.17.0/src/openlit/instrumentation/bedrock/bedrock.py +206 -0
  7. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/cohere/cohere.py +33 -12
  8. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/elevenlabs/async_elevenlabs.py +6 -2
  9. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/elevenlabs/elevenlabs.py +6 -2
  10. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/gpt4all/gpt4all.py +30 -10
  11. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/groq/async_groq.py +31 -11
  12. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/groq/groq.py +31 -11
  13. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/mistral/async_mistral.py +33 -12
  14. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/mistral/mistral.py +33 -12
  15. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/ollama/async_ollama.py +57 -20
  16. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/ollama/ollama.py +57 -20
  17. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/openai/async_azure_openai.py +94 -35
  18. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/openai/async_openai.py +68 -27
  19. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/openai/azure_openai.py +89 -31
  20. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/openai/openai.py +68 -29
  21. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/transformers/transformers.py +20 -16
  22. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/vertexai/async_vertexai.py +104 -35
  23. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/vertexai/vertexai.py +104 -35
  24. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/semcov/__init__.py +3 -1
  25. openlit-1.16.1/src/openlit/instrumentation/bedrock/bedrock.py +0 -436
  26. {openlit-1.16.1 → openlit-1.17.0}/LICENSE +0 -0
  27. {openlit-1.16.1 → openlit-1.17.0}/README.md +0 -0
  28. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/__helpers.py +0 -0
  29. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/__init__.py +0 -0
  30. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/anthropic/__init__.py +0 -0
  31. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/chroma/__init__.py +0 -0
  32. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/chroma/chroma.py +0 -0
  33. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/cohere/__init__.py +0 -0
  34. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/elevenlabs/__init__.py +0 -0
  35. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/embedchain/__init__.py +0 -0
  36. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/embedchain/embedchain.py +0 -0
  37. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/gpt4all/__init__.py +0 -0
  38. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/gpu/__init__.py +0 -0
  39. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/groq/__init__.py +0 -0
  40. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/haystack/__init__.py +0 -0
  41. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/haystack/haystack.py +0 -0
  42. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/langchain/__init__.py +0 -0
  43. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/langchain/langchain.py +0 -0
  44. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/llamaindex/__init__.py +0 -0
  45. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/llamaindex/llamaindex.py +0 -0
  46. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/milvus/__init__.py +0 -0
  47. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/milvus/milvus.py +0 -0
  48. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/mistral/__init__.py +0 -0
  49. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/ollama/__init__.py +0 -0
  50. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/openai/__init__.py +0 -0
  51. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/pinecone/__init__.py +0 -0
  52. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/pinecone/pinecone.py +0 -0
  53. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/qdrant/__init__.py +0 -0
  54. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/qdrant/qdrant.py +0 -0
  55. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/transformers/__init__.py +0 -0
  56. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/instrumentation/vertexai/__init__.py +0 -0
  57. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/otel/metrics.py +0 -0
  58. {openlit-1.16.1 → openlit-1.17.0}/src/openlit/otel/tracing.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: openlit
3
- Version: 1.16.1
3
+ Version: 1.17.0
4
4
  Summary: OpenTelemetry-native Auto instrumentation library for monitoring LLM Applications, facilitating the integration of observability into your GenAI-driven projects
5
5
  Home-page: https://github.com/openlit/openlit/tree/main/openlit/python
6
6
  Keywords: OpenTelemetry,otel,otlp,llm,tracing,openai,anthropic,claude,cohere,llm monitoring,observability,monitoring,gpt,Generative AI,chatGPT
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "openlit"
3
- version = "1.16.1"
3
+ version = "1.17.0"
4
4
  description = "OpenTelemetry-native Auto instrumentation library for monitoring LLM Applications, facilitating the integration of observability into your GenAI-driven projects"
5
5
  authors = ["OpenLIT"]
6
6
  repository = "https://github.com/openlit/openlit/tree/main/openlit/python"
@@ -130,7 +130,7 @@ def messages(gen_ai_endpoint, version, environment, application_name, tracer,
130
130
  span.set_attribute(SemanticConvetion.GEN_AI_REQUEST_TOP_K,
131
131
  kwargs.get("top_k", ""))
132
132
  span.set_attribute(SemanticConvetion.GEN_AI_RESPONSE_FINISH_REASON,
133
- finish_reason)
133
+ [finish_reason])
134
134
  span.set_attribute(SemanticConvetion.GEN_AI_USAGE_PROMPT_TOKENS,
135
135
  prompt_tokens)
136
136
  span.set_attribute(SemanticConvetion.GEN_AI_USAGE_COMPLETION_TOKENS,
@@ -140,10 +140,18 @@ def messages(gen_ai_endpoint, version, environment, application_name, tracer,
140
140
  span.set_attribute(SemanticConvetion.GEN_AI_USAGE_COST,
141
141
  cost)
142
142
  if trace_content:
143
- span.set_attribute(SemanticConvetion.GEN_AI_CONTENT_PROMPT,
144
- prompt)
145
- span.set_attribute(SemanticConvetion.GEN_AI_CONTENT_COMPLETION,
146
- llmresponse)
143
+ span.add_event(
144
+ name=SemanticConvetion.GEN_AI_CONTENT_PROMPT_EVENT,
145
+ attributes={
146
+ SemanticConvetion.GEN_AI_CONTENT_PROMPT: prompt,
147
+ },
148
+ )
149
+ span.add_event(
150
+ name=SemanticConvetion.GEN_AI_CONTENT_COMPLETION_EVENT,
151
+ attributes={
152
+ SemanticConvetion.GEN_AI_CONTENT_COMPLETION: llmresponse,
153
+ },
154
+ )
147
155
 
148
156
  span.set_status(Status(StatusCode.OK))
149
157
 
@@ -234,7 +242,7 @@ def messages(gen_ai_endpoint, version, environment, application_name, tracer,
234
242
  span.set_attribute(SemanticConvetion.GEN_AI_REQUEST_TOP_K,
235
243
  kwargs.get("top_k", ""))
236
244
  span.set_attribute(SemanticConvetion.GEN_AI_RESPONSE_FINISH_REASON,
237
- response.stop_reason)
245
+ [response.stop_reason])
238
246
  span.set_attribute(SemanticConvetion.GEN_AI_USAGE_PROMPT_TOKENS,
239
247
  response.usage.input_tokens)
240
248
  span.set_attribute(SemanticConvetion.GEN_AI_USAGE_COMPLETION_TOKENS,
@@ -244,11 +252,21 @@ def messages(gen_ai_endpoint, version, environment, application_name, tracer,
244
252
  response.usage.output_tokens)
245
253
  span.set_attribute(SemanticConvetion.GEN_AI_USAGE_COST,
246
254
  cost)
255
+
247
256
  if trace_content:
248
- span.set_attribute(SemanticConvetion.GEN_AI_CONTENT_PROMPT,
249
- prompt)
250
- # pylint: disable=line-too-long
251
- span.set_attribute(SemanticConvetion.GEN_AI_CONTENT_COMPLETION, response.content[0].text if response.content else "")
257
+ span.add_event(
258
+ name=SemanticConvetion.GEN_AI_CONTENT_PROMPT_EVENT,
259
+ attributes={
260
+ SemanticConvetion.GEN_AI_CONTENT_PROMPT: prompt,
261
+ },
262
+ )
263
+ span.add_event(
264
+ name=SemanticConvetion.GEN_AI_CONTENT_COMPLETION_EVENT,
265
+ attributes={
266
+ # pylint: disable=line-too-long
267
+ SemanticConvetion.GEN_AI_CONTENT_COMPLETION: response.content[0].text if response.content else "",
268
+ },
269
+ )
252
270
 
253
271
  span.set_status(Status(StatusCode.OK))
254
272
 
@@ -130,7 +130,7 @@ def async_messages(gen_ai_endpoint, version, environment, application_name,
130
130
  span.set_attribute(SemanticConvetion.GEN_AI_REQUEST_TOP_K,
131
131
  kwargs.get("top_k", ""))
132
132
  span.set_attribute(SemanticConvetion.GEN_AI_RESPONSE_FINISH_REASON,
133
- finish_reason)
133
+ [finish_reason])
134
134
  span.set_attribute(SemanticConvetion.GEN_AI_USAGE_PROMPT_TOKENS,
135
135
  prompt_tokens)
136
136
  span.set_attribute(SemanticConvetion.GEN_AI_USAGE_COMPLETION_TOKENS,
@@ -140,10 +140,18 @@ def async_messages(gen_ai_endpoint, version, environment, application_name,
140
140
  span.set_attribute(SemanticConvetion.GEN_AI_USAGE_COST,
141
141
  cost)
142
142
  if trace_content:
143
- span.set_attribute(SemanticConvetion.GEN_AI_CONTENT_PROMPT,
144
- prompt)
145
- span.set_attribute(SemanticConvetion.GEN_AI_CONTENT_COMPLETION,
146
- llmresponse)
143
+ span.add_event(
144
+ name=SemanticConvetion.GEN_AI_CONTENT_PROMPT_EVENT,
145
+ attributes={
146
+ SemanticConvetion.GEN_AI_CONTENT_PROMPT: prompt,
147
+ },
148
+ )
149
+ span.add_event(
150
+ name=SemanticConvetion.GEN_AI_CONTENT_COMPLETION_EVENT,
151
+ attributes={
152
+ SemanticConvetion.GEN_AI_CONTENT_COMPLETION: llmresponse,
153
+ },
154
+ )
147
155
 
148
156
  span.set_status(Status(StatusCode.OK))
149
157
 
@@ -234,7 +242,7 @@ def async_messages(gen_ai_endpoint, version, environment, application_name,
234
242
  span.set_attribute(SemanticConvetion.GEN_AI_REQUEST_TOP_K,
235
243
  kwargs.get("top_k", ""))
236
244
  span.set_attribute(SemanticConvetion.GEN_AI_RESPONSE_FINISH_REASON,
237
- response.stop_reason)
245
+ [response.stop_reason])
238
246
  span.set_attribute(SemanticConvetion.GEN_AI_USAGE_PROMPT_TOKENS,
239
247
  response.usage.input_tokens)
240
248
  span.set_attribute(SemanticConvetion.GEN_AI_USAGE_COMPLETION_TOKENS,
@@ -245,10 +253,19 @@ def async_messages(gen_ai_endpoint, version, environment, application_name,
245
253
  span.set_attribute(SemanticConvetion.GEN_AI_USAGE_COST,
246
254
  cost)
247
255
  if trace_content:
248
- span.set_attribute(SemanticConvetion.GEN_AI_CONTENT_PROMPT,
249
- prompt)
250
- # pylint: disable=line-too-long
251
- span.set_attribute(SemanticConvetion.GEN_AI_CONTENT_COMPLETION, response.content[0].text if response.content else "")
256
+ span.add_event(
257
+ name=SemanticConvetion.GEN_AI_CONTENT_PROMPT_EVENT,
258
+ attributes={
259
+ SemanticConvetion.GEN_AI_CONTENT_PROMPT: prompt,
260
+ },
261
+ )
262
+ span.add_event(
263
+ name=SemanticConvetion.GEN_AI_CONTENT_COMPLETION_EVENT,
264
+ attributes={
265
+ # pylint: disable=line-too-long
266
+ SemanticConvetion.GEN_AI_CONTENT_COMPLETION: response.content[0].text if response.content else "",
267
+ },
268
+ )
252
269
 
253
270
  span.set_status(Status(StatusCode.OK))
254
271
 
@@ -6,9 +6,9 @@ import importlib.metadata
6
6
  from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
7
7
  from wrapt import wrap_function_wrapper
8
8
 
9
- from openlit.instrumentation.bedrock.bedrock import chat
9
+ from openlit.instrumentation.bedrock.bedrock import converse
10
10
 
11
- _instruments = ("boto3 >= 1.34.93",)
11
+ _instruments = ("boto3 >= 1.34.138",)
12
12
 
13
13
  class BedrockInstrumentor(BaseInstrumentor):
14
14
  """
@@ -32,7 +32,7 @@ class BedrockInstrumentor(BaseInstrumentor):
32
32
  wrap_function_wrapper(
33
33
  "botocore.client",
34
34
  "ClientCreator.create_client",
35
- chat("bedrock.invoke_model", version, environment, application_name,
35
+ converse("bedrock.converse", version, environment, application_name,
36
36
  tracer, pricing_info, trace_content, metrics, disable_metrics),
37
37
  )
38
38
 
@@ -0,0 +1,206 @@
1
+ # pylint: disable=duplicate-code, broad-exception-caught, too-many-statements, unused-argument, protected-access, too-many-branches
2
+ """
3
+ Module for monitoring Amazon Bedrock API calls.
4
+ """
5
+
6
+ import logging
7
+ from botocore.response import StreamingBody
8
+ from botocore.exceptions import ReadTimeoutError, ResponseStreamingError
9
+ from urllib3.exceptions import ProtocolError as URLLib3ProtocolError
10
+ from urllib3.exceptions import ReadTimeoutError as URLLib3ReadTimeoutError
11
+ from opentelemetry.trace import SpanKind, Status, StatusCode
12
+ from opentelemetry.sdk.resources import TELEMETRY_SDK_NAME
13
+ from openlit.__helpers import get_chat_model_cost
14
+ from openlit.__helpers import handle_exception
15
+ from openlit.semcov import SemanticConvetion
16
+
17
+ # Initialize logger for logging potential issues and operations
18
+ logger = logging.getLogger(__name__)
19
+
20
+ class CustomStreamWrapper(StreamingBody):
21
+ """Handle streaming responses with the ability to read multiple times."""
22
+
23
+ def __init__(self, stream_source, length):
24
+ super().__init__(stream_source, length)
25
+ self._stream_data = None
26
+ self._read_position = 0
27
+
28
+ def read(self, amt=None):
29
+ if self._stream_data is None:
30
+ try:
31
+ self._stream_data = self._raw_stream.read()
32
+ except URLLib3ReadTimeoutError as error:
33
+ raise ReadTimeoutError(endpoint_url=error.url, error=error) from error
34
+ except URLLib3ProtocolError as error:
35
+ raise ResponseStreamingError(error=error) from error
36
+
37
+ self._amount_read += len(self._stream_data)
38
+ if amt is None or (not self._stream_data and amt > 0):
39
+ self._verify_content_length()
40
+
41
+ if amt is None:
42
+ data_chunk = self._stream_data[self._read_position:]
43
+ else:
44
+ data_start = self._read_position
45
+ self._read_position += amt
46
+ data_chunk = self._stream_data[data_start:self._read_position]
47
+
48
+ return data_chunk
49
+
50
+
51
+ def converse(gen_ai_endpoint, version, environment, application_name, tracer,
52
+ pricing_info, trace_content, metrics, disable_metrics):
53
+ """
54
+ Generates a telemetry wrapper for messages to collect metrics.
55
+
56
+ Args:
57
+ gen_ai_endpoint: Endpoint identifier for logging and tracing.
58
+ version: The monitoring package version.
59
+ environment: Deployment environment (e.g. production, staging).
60
+ application_name: Name of the application using the Bedrock API.
61
+ tracer: OpenTelemetry tracer for creating spans.
62
+ pricing_info: Information for calculating Bedrock usage cost.
63
+ trace_content: Whether to trace the actual content.
64
+ metrics: Metrics collector.
65
+ disable_metrics: Flag to toggle metrics collection.
66
+ Returns:
67
+ A function that wraps the chat method to add telemetry.
68
+ """
69
+
70
+ def wrapper(wrapped, instance, args, kwargs):
71
+ """
72
+ Wraps an API call to add telemetry.
73
+
74
+ Args:
75
+ wrapped: Original method.
76
+ instance: Instance of the class.
77
+ args: Positional arguments of the 'messages' method.
78
+ kwargs: Keyword arguments of the 'messages' method.
79
+ Returns:
80
+ Response from the original method.
81
+ """
82
+
83
+ def converse_wrapper(original_method, *method_args, **method_kwargs):
84
+ """
85
+ Adds instrumentation to the invoke model call.
86
+
87
+ Args:
88
+ original_method: The original invoke model method.
89
+ *method_args: Positional arguments for the method.
90
+ **method_kwargs: Keyword arguments for the method.
91
+ Returns:
92
+ The modified response with telemetry.
93
+ """
94
+ with tracer.start_as_current_span(gen_ai_endpoint, kind=SpanKind.CLIENT) as span:
95
+ response = original_method(*method_args, **method_kwargs)
96
+
97
+ try:
98
+ message_prompt = method_kwargs.get("messages", "")
99
+ formatted_messages = []
100
+ for message in message_prompt:
101
+ role = message["role"]
102
+ content = message["content"]
103
+
104
+ if isinstance(content, list):
105
+ content_str = ", ".join(
106
+ # pylint: disable=line-too-long
107
+ f'{item["type"]}: {item["text"] if "text" in item else item["image_url"]}'
108
+ if "type" in item else f'text: {item["text"]}'
109
+ for item in content
110
+ )
111
+ formatted_messages.append(f"{role}: {content_str}")
112
+ else:
113
+ formatted_messages.append(f"{role}: {content}")
114
+ prompt = "\n".join(formatted_messages)
115
+
116
+ model = method_kwargs.get("modelId", "amazon.titan-text-express-v1")
117
+ input_tokens = response["usage"]["inputTokens"]
118
+ output_tokens = response["usage"]["outputTokens"]
119
+
120
+ span.set_attribute(TELEMETRY_SDK_NAME, "openlit")
121
+ span.set_attribute(SemanticConvetion.GEN_AI_SYSTEM,
122
+ SemanticConvetion.GEN_AI_SYSTEM_BEDROCK)
123
+ span.set_attribute(SemanticConvetion.GEN_AI_ENDPOINT,
124
+ gen_ai_endpoint)
125
+ span.set_attribute(SemanticConvetion.GEN_AI_ENVIRONMENT,
126
+ environment)
127
+ span.set_attribute(SemanticConvetion.GEN_AI_APPLICATION_NAME,
128
+ application_name)
129
+ span.set_attribute(SemanticConvetion.GEN_AI_REQUEST_MODEL,
130
+ model)
131
+ span.set_attribute(SemanticConvetion.GEN_AI_USAGE_PROMPT_TOKENS,
132
+ input_tokens)
133
+ span.set_attribute(SemanticConvetion.GEN_AI_USAGE_COMPLETION_TOKENS,
134
+ output_tokens)
135
+ span.set_attribute(SemanticConvetion.GEN_AI_USAGE_TOTAL_TOKENS,
136
+ input_tokens + output_tokens)
137
+
138
+ # Calculate cost of the operation
139
+ cost = get_chat_model_cost(model,
140
+ pricing_info, input_tokens,
141
+ output_tokens)
142
+ span.set_attribute(SemanticConvetion.GEN_AI_USAGE_COST,
143
+ cost)
144
+
145
+ if trace_content:
146
+ span.add_event(
147
+ name=SemanticConvetion.GEN_AI_CONTENT_PROMPT_EVENT,
148
+ attributes={
149
+ SemanticConvetion.GEN_AI_CONTENT_PROMPT: prompt,
150
+ },
151
+ )
152
+ span.add_event(
153
+ name=SemanticConvetion.GEN_AI_CONTENT_COMPLETION_EVENT,
154
+ attributes={
155
+ # pylint: disable=line-too-long
156
+ SemanticConvetion.GEN_AI_CONTENT_COMPLETION: response["output"]["message"]["content"][0]["text"],
157
+ },
158
+ )
159
+
160
+ span.set_status(Status(StatusCode.OK))
161
+
162
+ if disable_metrics is False:
163
+ attributes = {
164
+ TELEMETRY_SDK_NAME:
165
+ "openlit",
166
+ SemanticConvetion.GEN_AI_APPLICATION_NAME:
167
+ application_name,
168
+ SemanticConvetion.GEN_AI_SYSTEM:
169
+ SemanticConvetion.GEN_AI_SYSTEM_BEDROCK,
170
+ SemanticConvetion.GEN_AI_ENVIRONMENT:
171
+ environment,
172
+ SemanticConvetion.GEN_AI_TYPE:
173
+ SemanticConvetion.GEN_AI_TYPE_CHAT,
174
+ SemanticConvetion.GEN_AI_REQUEST_MODEL:
175
+ model
176
+ }
177
+
178
+ metrics["genai_requests"].add(1, attributes)
179
+ metrics["genai_total_tokens"].add(
180
+ input_tokens + output_tokens, attributes
181
+ )
182
+ metrics["genai_completion_tokens"].add(output_tokens, attributes)
183
+ metrics["genai_prompt_tokens"].add(input_tokens, attributes)
184
+ metrics["genai_cost"].record(cost, attributes)
185
+
186
+ return response
187
+
188
+ except Exception as e:
189
+ handle_exception(span, e)
190
+ logger.error("Error in trace creation: %s", e)
191
+
192
+ # Return original response
193
+ return response
194
+
195
+ # Get the original client instance from the wrapper
196
+ client = wrapped(*args, **kwargs)
197
+
198
+ # Replace the original method with the instrumented one
199
+ if kwargs.get("service_name") == "bedrock-runtime":
200
+ original_invoke_model = client.converse
201
+ client.converse = lambda *args, **kwargs: converse_wrapper(original_invoke_model,
202
+ *args, **kwargs)
203
+
204
+ return client
205
+
206
+ return wrapper
@@ -89,8 +89,12 @@ def embed(gen_ai_endpoint, version, environment, application_name, tracer,
89
89
  span.set_attribute(SemanticConvetion.GEN_AI_USAGE_COST,
90
90
  cost)
91
91
  if trace_content:
92
- span.set_attribute(SemanticConvetion.GEN_AI_CONTENT_PROMPT,
93
- prompt)
92
+ span.add_event(
93
+ name=SemanticConvetion.GEN_AI_CONTENT_PROMPT_EVENT,
94
+ attributes={
95
+ SemanticConvetion.GEN_AI_CONTENT_PROMPT: prompt,
96
+ },
97
+ )
94
98
 
95
99
  span.set_status(Status(StatusCode.OK))
96
100
 
@@ -205,7 +209,7 @@ def chat(gen_ai_endpoint, version, environment, application_name, tracer,
205
209
  span.set_attribute(SemanticConvetion.GEN_AI_RESPONSE_ID,
206
210
  response.generation_id)
207
211
  span.set_attribute(SemanticConvetion.GEN_AI_RESPONSE_FINISH_REASON,
208
- response.finish_reason)
212
+ [response.finish_reason])
209
213
  span.set_attribute(SemanticConvetion.GEN_AI_USAGE_PROMPT_TOKENS,
210
214
  response.meta.billed_units.input_tokens)
211
215
  span.set_attribute(SemanticConvetion.GEN_AI_USAGE_COMPLETION_TOKENS,
@@ -215,11 +219,20 @@ def chat(gen_ai_endpoint, version, environment, application_name, tracer,
215
219
  response.meta.billed_units.output_tokens)
216
220
  span.set_attribute(SemanticConvetion.GEN_AI_USAGE_COST,
217
221
  cost)
222
+
218
223
  if trace_content:
219
- span.set_attribute(SemanticConvetion.GEN_AI_CONTENT_PROMPT,
220
- kwargs.get("message", ""))
221
- span.set_attribute(SemanticConvetion.GEN_AI_CONTENT_COMPLETION,
222
- response.text)
224
+ span.add_event(
225
+ name=SemanticConvetion.GEN_AI_CONTENT_PROMPT_EVENT,
226
+ attributes={
227
+ SemanticConvetion.GEN_AI_CONTENT_PROMPT: kwargs.get("message", ""),
228
+ },
229
+ )
230
+ span.add_event(
231
+ name=SemanticConvetion.GEN_AI_CONTENT_COMPLETION_EVENT,
232
+ attributes={
233
+ SemanticConvetion.GEN_AI_CONTENT_COMPLETION: response.text,
234
+ },
235
+ )
223
236
 
224
237
  span.set_status(Status(StatusCode.OK))
225
238
 
@@ -348,7 +361,7 @@ def chat_stream(gen_ai_endpoint, version, environment, application_name,
348
361
  span.set_attribute(SemanticConvetion.GEN_AI_RESPONSE_ID,
349
362
  response_id)
350
363
  span.set_attribute(SemanticConvetion.GEN_AI_RESPONSE_FINISH_REASON,
351
- finish_reason)
364
+ [finish_reason])
352
365
  span.set_attribute(SemanticConvetion.GEN_AI_USAGE_PROMPT_TOKENS,
353
366
  prompt_tokens)
354
367
  span.set_attribute(SemanticConvetion.GEN_AI_USAGE_COMPLETION_TOKENS,
@@ -358,10 +371,18 @@ def chat_stream(gen_ai_endpoint, version, environment, application_name,
358
371
  span.set_attribute(SemanticConvetion.GEN_AI_USAGE_COST,
359
372
  cost)
360
373
  if trace_content:
361
- span.set_attribute(SemanticConvetion.GEN_AI_CONTENT_PROMPT,
362
- kwargs.get("message", ""))
363
- span.set_attribute(SemanticConvetion.GEN_AI_CONTENT_COMPLETION,
364
- llmresponse)
374
+ span.add_event(
375
+ name=SemanticConvetion.GEN_AI_CONTENT_PROMPT_EVENT,
376
+ attributes={
377
+ SemanticConvetion.GEN_AI_CONTENT_PROMPT: kwargs.get("message", ""),
378
+ },
379
+ )
380
+ span.add_event(
381
+ name=SemanticConvetion.GEN_AI_CONTENT_COMPLETION_EVENT,
382
+ attributes={
383
+ SemanticConvetion.GEN_AI_CONTENT_COMPLETION: llmresponse,
384
+ },
385
+ )
365
386
 
366
387
  span.set_status(Status(StatusCode.OK))
367
388
 
@@ -80,8 +80,12 @@ def async_generate(gen_ai_endpoint, version, environment, application_name,
80
80
  span.set_attribute(SemanticConvetion.GEN_AI_USAGE_COST,
81
81
  cost)
82
82
  if trace_content:
83
- span.set_attribute(SemanticConvetion.GEN_AI_CONTENT_PROMPT,
84
- str(kwargs.get("text", "")))
83
+ span.add_event(
84
+ name=SemanticConvetion.GEN_AI_CONTENT_PROMPT_EVENT,
85
+ attributes={
86
+ SemanticConvetion.GEN_AI_CONTENT_PROMPT: str(kwargs.get("text", "")),
87
+ },
88
+ )
85
89
 
86
90
  span.set_status(Status(StatusCode.OK))
87
91
 
@@ -86,8 +86,12 @@ def generate(gen_ai_endpoint, version, environment, application_name,
86
86
  span.set_attribute(SemanticConvetion.GEN_AI_USAGE_COST,
87
87
  cost)
88
88
  if trace_content:
89
- span.set_attribute(SemanticConvetion.GEN_AI_CONTENT_PROMPT,
90
- str(kwargs.get("text", "")))
89
+ span.add_event(
90
+ name=SemanticConvetion.GEN_AI_CONTENT_PROMPT_EVENT,
91
+ attributes={
92
+ SemanticConvetion.GEN_AI_CONTENT_PROMPT: str(kwargs.get("text", "")),
93
+ },
94
+ )
91
95
 
92
96
  span.set_status(Status(StatusCode.OK))
93
97
 
@@ -107,10 +107,18 @@ def generate(gen_ai_endpoint, version, environment, application_name,
107
107
  span.set_attribute(SemanticConvetion.GEN_AI_REQUEST_IS_STREAM,
108
108
  True)
109
109
  if trace_content:
110
- span.set_attribute(SemanticConvetion.GEN_AI_CONTENT_PROMPT,
111
- prompt)
112
- span.set_attribute(SemanticConvetion.GEN_AI_CONTENT_COMPLETION,
113
- llmresponse)
110
+ span.add_event(
111
+ name=SemanticConvetion.GEN_AI_CONTENT_PROMPT_EVENT,
112
+ attributes={
113
+ SemanticConvetion.GEN_AI_CONTENT_PROMPT: prompt,
114
+ },
115
+ )
116
+ span.add_event(
117
+ name=SemanticConvetion.GEN_AI_CONTENT_COMPLETION_EVENT,
118
+ attributes={
119
+ SemanticConvetion.GEN_AI_CONTENT_COMPLETION: llmresponse,
120
+ },
121
+ )
114
122
 
115
123
  span.set_attribute(SemanticConvetion.GEN_AI_USAGE_PROMPT_TOKENS,
116
124
  prompt_tokens)
@@ -195,10 +203,18 @@ def generate(gen_ai_endpoint, version, environment, application_name,
195
203
  span.set_attribute(SemanticConvetion.GEN_AI_REQUEST_IS_STREAM,
196
204
  False)
197
205
  if trace_content:
198
- span.set_attribute(SemanticConvetion.GEN_AI_CONTENT_PROMPT,
199
- prompt)
200
- span.set_attribute(SemanticConvetion.GEN_AI_CONTENT_COMPLETION,
201
- response)
206
+ span.add_event(
207
+ name=SemanticConvetion.GEN_AI_CONTENT_PROMPT_EVENT,
208
+ attributes={
209
+ SemanticConvetion.GEN_AI_CONTENT_PROMPT: prompt,
210
+ },
211
+ )
212
+ span.add_event(
213
+ name=SemanticConvetion.GEN_AI_CONTENT_COMPLETION_EVENT,
214
+ attributes={
215
+ SemanticConvetion.GEN_AI_CONTENT_COMPLETION: response,
216
+ },
217
+ )
202
218
 
203
219
  span.set_attribute(SemanticConvetion.GEN_AI_USAGE_PROMPT_TOKENS,
204
220
  prompt_tokens)
@@ -313,8 +329,12 @@ def embed(gen_ai_endpoint, version, environment, application_name,
313
329
  span.set_attribute(SemanticConvetion.GEN_AI_USAGE_COST,
314
330
  cost)
315
331
  if trace_content:
316
- span.set_attribute(SemanticConvetion.GEN_AI_CONTENT_PROMPT,
317
- prompt)
332
+ span.add_event(
333
+ name=SemanticConvetion.GEN_AI_CONTENT_PROMPT_EVENT,
334
+ attributes={
335
+ SemanticConvetion.GEN_AI_CONTENT_PROMPT: prompt,
336
+ },
337
+ )
318
338
 
319
339
  span.set_status(Status(StatusCode.OK))
320
340
 
@@ -141,10 +141,18 @@ def async_chat(gen_ai_endpoint, version, environment, application_name,
141
141
  span.set_attribute(SemanticConvetion.GEN_AI_USAGE_COST,
142
142
  cost)
143
143
  if trace_content:
144
- span.set_attribute(SemanticConvetion.GEN_AI_CONTENT_PROMPT,
145
- prompt)
146
- span.set_attribute(SemanticConvetion.GEN_AI_CONTENT_COMPLETION,
147
- llmresponse)
144
+ span.add_event(
145
+ name=SemanticConvetion.GEN_AI_CONTENT_PROMPT_EVENT,
146
+ attributes={
147
+ SemanticConvetion.GEN_AI_CONTENT_PROMPT: prompt,
148
+ },
149
+ )
150
+ span.add_event(
151
+ name=SemanticConvetion.GEN_AI_CONTENT_COMPLETION_EVENT,
152
+ attributes={
153
+ SemanticConvetion.GEN_AI_CONTENT_COMPLETION: llmresponse,
154
+ },
155
+ )
148
156
 
149
157
  span.set_status(Status(StatusCode.OK))
150
158
 
@@ -237,8 +245,12 @@ def async_chat(gen_ai_endpoint, version, environment, application_name,
237
245
  span.set_attribute(SemanticConvetion.GEN_AI_REQUEST_IS_STREAM,
238
246
  False)
239
247
  if trace_content:
240
- span.set_attribute(SemanticConvetion.GEN_AI_CONTENT_PROMPT,
241
- prompt)
248
+ span.add_event(
249
+ name=SemanticConvetion.GEN_AI_CONTENT_PROMPT_EVENT,
250
+ attributes={
251
+ SemanticConvetion.GEN_AI_CONTENT_PROMPT: prompt,
252
+ },
253
+ )
242
254
 
243
255
  # Set span attributes when tools is not passed to the function call
244
256
  if "tools" not in kwargs:
@@ -254,23 +266,31 @@ def async_chat(gen_ai_endpoint, version, environment, application_name,
254
266
  span.set_attribute(SemanticConvetion.GEN_AI_USAGE_TOTAL_TOKENS,
255
267
  response.usage.total_tokens)
256
268
  span.set_attribute(SemanticConvetion.GEN_AI_RESPONSE_FINISH_REASON,
257
- response.choices[0].finish_reason)
269
+ [response.choices[0].finish_reason])
258
270
  span.set_attribute(SemanticConvetion.GEN_AI_USAGE_COST,
259
271
  cost)
260
272
 
261
273
  # Set span attributes for when n = 1 (default)
262
274
  if "n" not in kwargs or kwargs["n"] == 1:
263
275
  if trace_content:
264
- span.set_attribute(SemanticConvetion.GEN_AI_CONTENT_COMPLETION,
265
- response.choices[0].message.content)
276
+ span.add_event(
277
+ name=SemanticConvetion.GEN_AI_CONTENT_COMPLETION_EVENT,
278
+ attributes={
279
+ SemanticConvetion.GEN_AI_CONTENT_COMPLETION: response.choices[0].message.content,
280
+ },
281
+ )
266
282
 
267
283
  # Set span attributes for when n > 0
268
284
  else:
269
285
  i = 0
270
286
  while i < kwargs["n"] and trace_content is True:
271
287
  attribute_name = f"gen_ai.completion.{i}"
272
- span.set_attribute(attribute_name,
273
- response.choices[i].message.content)
288
+ span.add_event(
289
+ name=attribute_name,
290
+ attributes={
291
+ SemanticConvetion.GEN_AI_CONTENT_COMPLETION: response.choices[i].message.content,
292
+ },
293
+ )
274
294
  i += 1
275
295
 
276
296
  # Return original response