openlit 1.34.18__py3-none-any.whl → 1.34.19__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.
@@ -8,7 +8,7 @@ from wrapt import wrap_function_wrapper
8
8
  from openlit.instrumentation.anthropic.anthropic import messages
9
9
  from openlit.instrumentation.anthropic.async_anthropic import async_messages
10
10
 
11
- _instruments = ('anthropic >= 0.21.0',)
11
+ _instruments = ("anthropic >= 0.21.0",)
12
12
 
13
13
  class AnthropicInstrumentor(BaseInstrumentor):
14
14
  """
@@ -19,32 +19,30 @@ class AnthropicInstrumentor(BaseInstrumentor):
19
19
  return _instruments
20
20
 
21
21
  def _instrument(self, **kwargs):
22
- application_name = kwargs.get('application_name', 'default')
23
- environment = kwargs.get('environment', 'default')
24
- tracer = kwargs.get('tracer')
25
- event_provider = kwargs.get('event_provider')
26
- metrics = kwargs.get('metrics_dict')
27
- pricing_info = kwargs.get('pricing_info', {})
28
- capture_message_content = kwargs.get('capture_message_content', False)
29
- disable_metrics = kwargs.get('disable_metrics')
30
- version = importlib.metadata.version('anthropic')
31
-
32
- #sync
22
+ version = importlib.metadata.version("anthropic")
23
+ environment = kwargs.get("environment", "default")
24
+ application_name = kwargs.get("application_name", "default")
25
+ tracer = kwargs.get("tracer")
26
+ pricing_info = kwargs.get("pricing_info", {})
27
+ capture_message_content = kwargs.get("capture_message_content", False)
28
+ metrics = kwargs.get("metrics_dict")
29
+ disable_metrics = kwargs.get("disable_metrics")
30
+
31
+ # sync
33
32
  wrap_function_wrapper(
34
- 'anthropic.resources.messages',
35
- 'Messages.create',
36
- messages(version, environment, application_name,
37
- tracer, event_provider, pricing_info, capture_message_content, metrics, disable_metrics),
33
+ "anthropic.resources.messages",
34
+ "Messages.create",
35
+ messages(version, environment, application_name, tracer, pricing_info,
36
+ capture_message_content, metrics, disable_metrics),
38
37
  )
39
38
 
40
- #async
39
+ # async
41
40
  wrap_function_wrapper(
42
- 'anthropic.resources.messages',
43
- 'AsyncMessages.create',
44
- async_messages(version, environment, application_name,
45
- tracer, event_provider, pricing_info, capture_message_content, metrics, disable_metrics),
41
+ "anthropic.resources.messages",
42
+ "AsyncMessages.create",
43
+ async_messages(version, environment, application_name, tracer, pricing_info,
44
+ capture_message_content, metrics, disable_metrics),
46
45
  )
47
46
 
48
47
  def _uninstrument(self, **kwargs):
49
- # Proper uninstrumentation logic to revert patched methods
50
48
  pass
@@ -2,7 +2,6 @@
2
2
  Module for monitoring Anthropic API calls.
3
3
  """
4
4
 
5
- import logging
6
5
  import time
7
6
  from opentelemetry.trace import SpanKind
8
7
  from openlit.__helpers import (
@@ -16,13 +15,9 @@ from openlit.instrumentation.anthropic.utils import (
16
15
  )
17
16
  from openlit.semcov import SemanticConvention
18
17
 
19
- # Initialize logger for logging potential issues and operations
20
- logger = logging.getLogger(__name__)
21
-
22
- def messages(version, environment, application_name, tracer, event_provider,
23
- pricing_info, capture_message_content, metrics, disable_metrics):
18
+ def messages(version, environment, application_name, tracer, pricing_info, capture_message_content, metrics, disable_metrics):
24
19
  """
25
- Generates a telemetry wrapper for GenAI function call
20
+ Generates a telemetry wrapper for Anthropic Messages.create calls.
26
21
  """
27
22
 
28
23
  class TracedSyncStream:
@@ -38,24 +33,21 @@ def messages(version, environment, application_name, tracer, event_provider,
38
33
  kwargs,
39
34
  server_address,
40
35
  server_port,
41
- **args,
42
36
  ):
43
37
  self.__wrapped__ = wrapped
44
38
  self._span = span
45
39
  self._span_name = span_name
46
- self._llmresponse = ''
47
- self._response_id = ''
48
- self._response_model = ''
49
- self._finish_reason = ''
50
- self._input_tokens = ''
51
- self._output_tokens = ''
52
- self._tool_arguments = ''
53
- self._tool_id = ''
54
- self._tool_name = ''
40
+ self._llmresponse = ""
41
+ self._response_id = ""
42
+ self._response_model = ""
43
+ self._finish_reason = ""
44
+ self._input_tokens = 0
45
+ self._output_tokens = 0
46
+ self._tool_arguments = ""
47
+ self._tool_id = ""
48
+ self._tool_name = ""
55
49
  self._tool_calls = None
56
- self._response_role = ''
57
-
58
- self._args = args
50
+ self._response_role = ""
59
51
  self._kwargs = kwargs
60
52
  self._start_time = time.time()
61
53
  self._end_time = None
@@ -86,34 +78,31 @@ def messages(version, environment, application_name, tracer, event_provider,
86
78
  return chunk
87
79
  except StopIteration:
88
80
  try:
89
- with tracer.start_as_current_span(self._span_name, kind= SpanKind.CLIENT) as self._span:
81
+ with self._span:
90
82
  process_streaming_chat_response(
91
83
  self,
92
84
  pricing_info=pricing_info,
93
85
  environment=environment,
94
86
  application_name=application_name,
95
87
  metrics=metrics,
96
- event_provider=event_provider,
97
88
  capture_message_content=capture_message_content,
98
89
  disable_metrics=disable_metrics,
99
90
  version=version
100
91
  )
101
-
102
92
  except Exception as e:
103
93
  handle_exception(self._span, e)
104
- logger.error("Error in trace creation: %s", e)
105
94
  raise
106
95
 
107
96
  def wrapper(wrapped, instance, args, kwargs):
108
97
  """
109
- Wraps the GenAI function call.
98
+ Wraps the Anthropic Messages.create call.
110
99
  """
111
100
 
112
- streaming = kwargs.get('stream', False)
113
- server_address, server_port = set_server_address_and_port(instance, 'api.anthropic.com', 443)
114
- request_model = kwargs.get('model', 'claude-3-5-sonnet-latest')
101
+ streaming = kwargs.get("stream", False)
102
+ server_address, server_port = set_server_address_and_port(instance, "api.anthropic.com", 443)
103
+ request_model = kwargs.get("model", "claude-3-5-sonnet-latest")
115
104
 
116
- span_name = f'{SemanticConvention.GEN_AI_OPERATION_TYPE_CHAT} {request_model}'
105
+ span_name = f"{SemanticConvention.GEN_AI_OPERATION_TYPE_CHAT} {request_model}"
117
106
 
118
107
  # pylint: disable=no-else-return
119
108
  if streaming:
@@ -126,23 +115,27 @@ def messages(version, environment, application_name, tracer, event_provider,
126
115
  with tracer.start_as_current_span(span_name, kind=SpanKind.CLIENT) as span:
127
116
  start_time = time.time()
128
117
  response = wrapped(*args, **kwargs)
129
- response = process_chat_response(
130
- response=response,
131
- request_model=request_model,
132
- pricing_info=pricing_info,
133
- server_port=server_port,
134
- server_address=server_address,
135
- environment=environment,
136
- application_name=application_name,
137
- metrics=metrics,
138
- event_provider=event_provider,
139
- start_time=start_time,
140
- span=span,
141
- capture_message_content=capture_message_content,
142
- disable_metrics=disable_metrics,
143
- version=version,
144
- **kwargs
145
- )
118
+
119
+ try:
120
+ response = process_chat_response(
121
+ response=response,
122
+ request_model=request_model,
123
+ pricing_info=pricing_info,
124
+ server_port=server_port,
125
+ server_address=server_address,
126
+ environment=environment,
127
+ application_name=application_name,
128
+ metrics=metrics,
129
+ start_time=start_time,
130
+ span=span,
131
+ capture_message_content=capture_message_content,
132
+ disable_metrics=disable_metrics,
133
+ version=version,
134
+ **kwargs
135
+ )
136
+
137
+ except Exception as e:
138
+ handle_exception(span, e)
146
139
 
147
140
  return response
148
141
 
@@ -2,7 +2,6 @@
2
2
  Module for monitoring Anthropic API calls.
3
3
  """
4
4
 
5
- import logging
6
5
  import time
7
6
  from opentelemetry.trace import SpanKind
8
7
  from openlit.__helpers import (
@@ -16,18 +15,14 @@ from openlit.instrumentation.anthropic.utils import (
16
15
  )
17
16
  from openlit.semcov import SemanticConvention
18
17
 
19
- # Initialize logger for logging potential issues and operations
20
- logger = logging.getLogger(__name__)
21
-
22
- def async_messages(version, environment, application_name, tracer, event_provider,
23
- pricing_info, capture_message_content, metrics, disable_metrics):
18
+ def async_messages(version, environment, application_name, tracer, pricing_info, capture_message_content, metrics, disable_metrics):
24
19
  """
25
- Generates a telemetry wrapper for GenAI function call
20
+ Generates a telemetry wrapper for Anthropic AsyncMessages.create calls.
26
21
  """
27
22
 
28
23
  class TracedAsyncStream:
29
24
  """
30
- Wrapper for streaming responses to collect telemetry.
25
+ Wrapper for async streaming responses to collect telemetry.
31
26
  """
32
27
 
33
28
  def __init__(
@@ -38,24 +33,21 @@ def async_messages(version, environment, application_name, tracer, event_provide
38
33
  kwargs,
39
34
  server_address,
40
35
  server_port,
41
- **args,
42
36
  ):
43
37
  self.__wrapped__ = wrapped
44
38
  self._span = span
45
39
  self._span_name = span_name
46
- self._llmresponse = ''
47
- self._response_id = ''
48
- self._response_model = ''
49
- self._finish_reason = ''
50
- self._input_tokens = ''
51
- self._output_tokens = ''
52
- self._tool_arguments = ''
53
- self._tool_id = ''
54
- self._tool_name = ''
40
+ self._llmresponse = ""
41
+ self._response_id = ""
42
+ self._response_model = ""
43
+ self._finish_reason = ""
44
+ self._input_tokens = 0
45
+ self._output_tokens = 0
46
+ self._tool_arguments = ""
47
+ self._tool_id = ""
48
+ self._tool_name = ""
55
49
  self._tool_calls = None
56
- self._response_role = ''
57
-
58
- self._args = args
50
+ self._response_role = ""
59
51
  self._kwargs = kwargs
60
52
  self._start_time = time.time()
61
53
  self._end_time = None
@@ -86,34 +78,31 @@ def async_messages(version, environment, application_name, tracer, event_provide
86
78
  return chunk
87
79
  except StopAsyncIteration:
88
80
  try:
89
- with tracer.start_as_current_span(self._span_name, kind= SpanKind.CLIENT) as self._span:
81
+ with self._span:
90
82
  process_streaming_chat_response(
91
83
  self,
92
84
  pricing_info=pricing_info,
93
85
  environment=environment,
94
86
  application_name=application_name,
95
87
  metrics=metrics,
96
- event_provider=event_provider,
97
88
  capture_message_content=capture_message_content,
98
89
  disable_metrics=disable_metrics,
99
90
  version=version
100
91
  )
101
-
102
92
  except Exception as e:
103
93
  handle_exception(self._span, e)
104
- logger.error("Error in trace creation: %s", e)
105
94
  raise
106
95
 
107
96
  async def wrapper(wrapped, instance, args, kwargs):
108
97
  """
109
- Wraps the GenAI function call.
98
+ Wraps the Anthropic AsyncMessages.create call.
110
99
  """
111
100
 
112
- streaming = kwargs.get('stream', False)
113
- server_address, server_port = set_server_address_and_port(instance, 'api.anthropic.com', 443)
114
- request_model = kwargs.get('model', 'claude-3-5-sonnet-latest')
101
+ streaming = kwargs.get("stream", False)
102
+ server_address, server_port = set_server_address_and_port(instance, "api.anthropic.com", 443)
103
+ request_model = kwargs.get("model", "claude-3-5-sonnet-latest")
115
104
 
116
- span_name = f'{SemanticConvention.GEN_AI_OPERATION_TYPE_CHAT} {request_model}'
105
+ span_name = f"{SemanticConvention.GEN_AI_OPERATION_TYPE_CHAT} {request_model}"
117
106
 
118
107
  # pylint: disable=no-else-return
119
108
  if streaming:
@@ -126,23 +115,27 @@ def async_messages(version, environment, application_name, tracer, event_provide
126
115
  with tracer.start_as_current_span(span_name, kind=SpanKind.CLIENT) as span:
127
116
  start_time = time.time()
128
117
  response = await wrapped(*args, **kwargs)
129
- response = process_chat_response(
130
- response=response,
131
- request_model=request_model,
132
- pricing_info=pricing_info,
133
- server_port=server_port,
134
- server_address=server_address,
135
- environment=environment,
136
- application_name=application_name,
137
- metrics=metrics,
138
- event_provider=event_provider,
139
- start_time=start_time,
140
- span=span,
141
- capture_message_content=capture_message_content,
142
- disable_metrics=disable_metrics,
143
- version=version,
144
- **kwargs
145
- )
118
+
119
+ try:
120
+ response = process_chat_response(
121
+ response=response,
122
+ request_model=request_model,
123
+ pricing_info=pricing_info,
124
+ server_port=server_port,
125
+ server_address=server_address,
126
+ environment=environment,
127
+ application_name=application_name,
128
+ metrics=metrics,
129
+ start_time=start_time,
130
+ span=span,
131
+ capture_message_content=capture_message_content,
132
+ disable_metrics=disable_metrics,
133
+ version=version,
134
+ **kwargs
135
+ )
136
+
137
+ except Exception as e:
138
+ handle_exception(span, e)
146
139
 
147
140
  return response
148
141
 
@@ -3,63 +3,92 @@ Anthropic OpenTelemetry instrumentation utility functions
3
3
  """
4
4
  import time
5
5
 
6
- from opentelemetry.sdk.resources import SERVICE_NAME, TELEMETRY_SDK_NAME, DEPLOYMENT_ENVIRONMENT
7
6
  from opentelemetry.trace import Status, StatusCode
8
7
 
9
8
  from openlit.__helpers import (
10
9
  calculate_ttft,
11
10
  response_as_dict,
12
11
  calculate_tbt,
13
- extract_and_format_input,
14
12
  get_chat_model_cost,
15
- create_metrics_attributes,
16
- otel_event,
17
- concatenate_all_contents
13
+ record_completion_metrics,
14
+ common_span_attributes,
18
15
  )
19
16
  from openlit.semcov import SemanticConvention
20
17
 
21
- def process_chunk(self, chunk):
18
+ def format_content(messages):
19
+ """
20
+ Format the messages into a string for span events.
21
+ """
22
+
23
+ if not messages:
24
+ return ""
25
+
26
+ formatted_messages = []
27
+ for message in messages:
28
+ if isinstance(message, dict):
29
+ role = message.get("role", "user")
30
+ content = message.get("content", "")
31
+ else:
32
+ # Handle Anthropic object format
33
+ role = getattr(message, "role", "user")
34
+ content = getattr(message, "content", "")
35
+
36
+ if isinstance(content, list):
37
+ # Handle structured content (e.g., text + images)
38
+ text_parts = []
39
+ for part in content:
40
+ if isinstance(part, dict) and part.get("type") == "text":
41
+ text_parts.append(part.get("text", ""))
42
+ content = " ".join(text_parts)
43
+ elif not isinstance(content, str):
44
+ content = str(content)
45
+
46
+ formatted_messages.append(f"{role}: {content}")
47
+
48
+ return "\n".join(formatted_messages)
49
+
50
+ def process_chunk(scope, chunk):
22
51
  """
23
52
  Process a chunk of response data and update state.
24
53
  """
25
54
 
26
55
  end_time = time.time()
27
56
  # Record the timestamp for the current chunk
28
- self._timestamps.append(end_time)
57
+ scope._timestamps.append(end_time)
29
58
 
30
- if len(self._timestamps) == 1:
59
+ if len(scope._timestamps) == 1:
31
60
  # Calculate time to first chunk
32
- self._ttft = calculate_ttft(self._timestamps, self._start_time)
61
+ scope._ttft = calculate_ttft(scope._timestamps, scope._start_time)
33
62
 
34
63
  chunked = response_as_dict(chunk)
35
64
 
36
65
  # Collect message IDs and input token from events
37
- if chunked.get('type') == 'message_start':
38
- self._response_id = chunked.get('message').get('id')
39
- self._input_tokens = chunked.get('message').get('usage').get('input_tokens')
40
- self._response_model = chunked.get('message').get('model')
41
- self._response_role = chunked.get('message').get('role')
66
+ if chunked.get("type") == "message_start":
67
+ scope._response_id = chunked.get("message").get("id")
68
+ scope._input_tokens = chunked.get("message").get("usage").get("input_tokens")
69
+ scope._response_model = chunked.get("message").get("model")
70
+ scope._response_role = chunked.get("message").get("role")
42
71
 
43
72
  # Collect message IDs and aggregated response from events
44
- if chunked.get('type') == 'content_block_delta':
45
- if chunked.get('delta').get('text'):
46
- self._llmresponse += chunked.get('delta').get('text')
47
- elif chunked.get('delta').get('partial_json'):
48
- self._tool_arguments += chunked.get('delta').get('partial_json')
49
-
50
- if chunked.get('type') == 'content_block_start':
51
- if chunked.get('content_block').get('id'):
52
- self._tool_id = chunked.get('content_block').get('id')
53
- if chunked.get('content_block').get('name'):
54
- self._tool_name = chunked.get('content_block').get('name')
73
+ if chunked.get("type") == "content_block_delta":
74
+ if chunked.get("delta").get("text"):
75
+ scope._llmresponse += chunked.get("delta").get("text")
76
+ elif chunked.get("delta").get("partial_json"):
77
+ scope._tool_arguments += chunked.get("delta").get("partial_json")
78
+
79
+ if chunked.get("type") == "content_block_start":
80
+ if chunked.get("content_block").get("id"):
81
+ scope._tool_id = chunked.get("content_block").get("id")
82
+ if chunked.get("content_block").get("name"):
83
+ scope._tool_name = chunked.get("content_block").get("name")
55
84
 
56
85
  # Collect output tokens and stop reason from events
57
- if chunked.get('type') == 'message_delta':
58
- self._output_tokens = chunked.get('usage').get('output_tokens')
59
- self._finish_reason = chunked.get('delta').get('stop_reason')
86
+ if chunked.get("type") == "message_delta":
87
+ scope._output_tokens = chunked.get("usage").get("output_tokens")
88
+ scope._finish_reason = chunked.get("delta").get("stop_reason")
60
89
 
61
90
  def common_chat_logic(scope, pricing_info, environment, application_name, metrics,
62
- event_provider, capture_message_content, disable_metrics, version, is_stream):
91
+ capture_message_content, disable_metrics, version, is_stream):
63
92
  """
64
93
  Process chat request and generate Telemetry
65
94
  """
@@ -68,48 +97,56 @@ def common_chat_logic(scope, pricing_info, environment, application_name, metric
68
97
  if len(scope._timestamps) > 1:
69
98
  scope._tbt = calculate_tbt(scope._timestamps)
70
99
 
71
- formatted_messages = extract_and_format_input(scope._kwargs.get('messages', ''))
72
- request_model = scope._kwargs.get('model', 'claude-3-opus-20240229')
100
+ formatted_messages = format_content(scope._kwargs.get("messages", []))
101
+ request_model = scope._kwargs.get("model", "claude-3-5-sonnet-latest")
73
102
 
74
103
  cost = get_chat_model_cost(request_model, pricing_info, scope._input_tokens, scope._output_tokens)
75
104
 
76
- # Set Span attributes (OTel Semconv)
77
- scope._span.set_attribute(TELEMETRY_SDK_NAME, 'openlit')
78
- scope._span.set_attribute(SemanticConvention.GEN_AI_OPERATION, SemanticConvention.GEN_AI_OPERATION_TYPE_CHAT)
79
- scope._span.set_attribute(SemanticConvention.GEN_AI_SYSTEM, SemanticConvention.GEN_AI_SYSTEM_ANTHROPIC)
80
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_MODEL, request_model)
81
- scope._span.set_attribute(SemanticConvention.SERVER_PORT, scope._server_port)
82
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_MAX_TOKENS, scope._kwargs.get('max_tokens', -1))
83
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_STOP_SEQUENCES, scope._kwargs.get('stop_sequences', []))
84
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_TEMPERATURE, scope._kwargs.get('temperature', 1.0))
85
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_TOP_K, scope._kwargs.get('top_k', 1.0))
86
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_TOP_P, scope._kwargs.get('top_p', 1.0))
87
- scope._span.set_attribute(SemanticConvention.GEN_AI_RESPONSE_FINISH_REASON, [scope._finish_reason])
105
+ # Common Span Attributes
106
+ common_span_attributes(scope,
107
+ SemanticConvention.GEN_AI_OPERATION_TYPE_CHAT, SemanticConvention.GEN_AI_SYSTEM_ANTHROPIC,
108
+ scope._server_address, scope._server_port, request_model, scope._response_model,
109
+ environment, application_name, is_stream, scope._tbt, scope._ttft, version)
110
+
111
+ # Span Attributes for Request parameters
112
+ scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_MAX_TOKENS, scope._kwargs.get("max_tokens", -1))
113
+ scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_STOP_SEQUENCES, scope._kwargs.get("stop_sequences", []))
114
+ scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_TEMPERATURE, scope._kwargs.get("temperature", 1.0))
115
+ scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_TOP_K, scope._kwargs.get("top_k", 1.0))
116
+ scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_TOP_P, scope._kwargs.get("top_p", 1.0))
117
+
118
+ # Span Attributes for Response parameters
88
119
  scope._span.set_attribute(SemanticConvention.GEN_AI_RESPONSE_ID, scope._response_id)
89
- scope._span.set_attribute(SemanticConvention.GEN_AI_RESPONSE_MODEL, scope._response_model)
120
+ scope._span.set_attribute(SemanticConvention.GEN_AI_RESPONSE_FINISH_REASON, [scope._finish_reason])
121
+ scope._span.set_attribute(SemanticConvention.GEN_AI_OUTPUT_TYPE, "text" if isinstance(scope._llmresponse, str) else "json")
122
+
123
+ # Span Attributes for Cost and Tokens
90
124
  scope._span.set_attribute(SemanticConvention.GEN_AI_USAGE_INPUT_TOKENS, scope._input_tokens)
91
125
  scope._span.set_attribute(SemanticConvention.GEN_AI_USAGE_OUTPUT_TOKENS, scope._output_tokens)
92
- scope._span.set_attribute(SemanticConvention.SERVER_ADDRESS, scope._server_address)
93
-
94
- scope._span.set_attribute(SemanticConvention.GEN_AI_OUTPUT_TYPE,
95
- 'text' if isinstance(scope._llmresponse, str) else 'json')
96
-
97
- scope._span.set_attribute(DEPLOYMENT_ENVIRONMENT, environment)
98
- scope._span.set_attribute(SERVICE_NAME, application_name)
99
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_IS_STREAM, is_stream)
100
126
  scope._span.set_attribute(SemanticConvention.GEN_AI_CLIENT_TOKEN_USAGE, scope._input_tokens + scope._output_tokens)
101
127
  scope._span.set_attribute(SemanticConvention.GEN_AI_USAGE_COST, cost)
102
- scope._span.set_attribute(SemanticConvention.GEN_AI_SERVER_TBT, scope._tbt)
103
- scope._span.set_attribute(SemanticConvention.GEN_AI_SERVER_TTFT, scope._ttft)
104
- scope._span.set_attribute(SemanticConvention.GEN_AI_SDK_VERSION, version)
105
128
 
106
- # To be removed one the change to log events (from span events) is complete
107
- prompt = concatenate_all_contents(formatted_messages)
129
+ # Handle tool calls if present
130
+ if scope._tool_calls:
131
+ # Optimized tool handling - extract name, id, and arguments
132
+ tool_name = scope._tool_calls.get("name", "")
133
+ tool_id = scope._tool_calls.get("id", "")
134
+ tool_args = scope._tool_calls.get("input", "")
135
+
136
+ scope._span.set_attribute(SemanticConvention.GEN_AI_TOOL_NAME, tool_name)
137
+ scope._span.set_attribute(SemanticConvention.GEN_AI_TOOL_CALL_ID, tool_id)
138
+ scope._span.set_attribute(SemanticConvention.GEN_AI_TOOL_ARGS, str(tool_args))
139
+
140
+ # Span Attributes for Content
108
141
  if capture_message_content:
142
+ scope._span.set_attribute(SemanticConvention.GEN_AI_CONTENT_PROMPT, formatted_messages)
143
+ scope._span.set_attribute(SemanticConvention.GEN_AI_CONTENT_COMPLETION, scope._llmresponse)
144
+
145
+ # To be removed once the change to span_attributes (from span events) is complete
109
146
  scope._span.add_event(
110
147
  name=SemanticConvention.GEN_AI_CONTENT_PROMPT_EVENT,
111
148
  attributes={
112
- SemanticConvention.GEN_AI_CONTENT_PROMPT: prompt,
149
+ SemanticConvention.GEN_AI_CONTENT_PROMPT: formatted_messages,
113
150
  },
114
151
  )
115
152
  scope._span.add_event(
@@ -119,133 +156,70 @@ def common_chat_logic(scope, pricing_info, environment, application_name, metric
119
156
  },
120
157
  )
121
158
 
122
- choice_event_body = {
123
- 'finish_reason': scope._finish_reason,
124
- 'index': 0,
125
- 'message': {
126
- **({'content': scope._llmresponse} if capture_message_content else {}),
127
- 'role': scope._response_role
128
- }
129
- }
130
-
131
- if scope._tool_calls:
132
- choice_event_body['message'].update({
133
- 'tool_calls': {
134
- 'function': {
135
- 'name': scope._tool_calls.get('name', ''),
136
- 'arguments': scope._tool_calls.get('input', '')
137
- },
138
- 'id': scope._tool_calls.get('id', ''),
139
- 'type': 'function'
140
- }
141
- })
142
-
143
- # Emit events
144
- for role in ['user', 'system', 'assistant', 'tool']:
145
- if formatted_messages.get(role, {}).get('content', ''):
146
- event = otel_event(
147
- name=getattr(SemanticConvention, f'GEN_AI_{role.upper()}_MESSAGE'),
148
- attributes={
149
- SemanticConvention.GEN_AI_SYSTEM: SemanticConvention.GEN_AI_SYSTEM_ANTHROPIC
150
- },
151
- body = {
152
- # pylint: disable=line-too-long
153
- **({'content': formatted_messages.get(role, {}).get('content', '')} if capture_message_content else {}),
154
- 'role': formatted_messages.get(role, {}).get('role', []),
155
- **({
156
- 'tool_calls': {
157
- 'function': {
158
- # pylint: disable=line-too-long
159
- 'name': (scope._tool_calls[0].get('function', {}).get('name', '') if scope._tool_calls else ''),
160
- 'arguments': (scope._tool_calls[0].get('function', {}).get('arguments', '') if scope._tool_calls else '')
161
- },
162
- 'id': (scope._tool_calls[0].get('id', '') if scope._tool_calls else ''),
163
- 'type': 'function'
164
- }
165
- } if role == 'assistant' else {}),
166
- **({
167
- 'id': (scope._tool_calls[0].get('id', '') if scope._tool_calls else '')
168
- } if role == 'tool' else {})
169
- }
170
- )
171
- event_provider.emit(event)
172
-
173
- choice_event = otel_event(
174
- name=SemanticConvention.GEN_AI_CHOICE,
175
- attributes={
176
- SemanticConvention.GEN_AI_SYSTEM: SemanticConvention.GEN_AI_SYSTEM_ANTHROPIC
177
- },
178
- body=choice_event_body
179
- )
180
- event_provider.emit(choice_event)
181
-
182
159
  scope._span.set_status(Status(StatusCode.OK))
183
160
 
161
+ # Record metrics
184
162
  if not disable_metrics:
185
- metrics_attributes = create_metrics_attributes(
186
- service_name=application_name,
187
- deployment_environment=environment,
188
- operation=SemanticConvention.GEN_AI_OPERATION_TYPE_CHAT,
189
- system=SemanticConvention.GEN_AI_SYSTEM_ANTHROPIC,
190
- request_model=request_model,
191
- server_address=scope._server_address,
192
- server_port=scope._server_port,
193
- response_model=scope._response_model,
194
- )
163
+ record_completion_metrics(metrics, SemanticConvention.GEN_AI_OPERATION_TYPE_CHAT, SemanticConvention.GEN_AI_SYSTEM_ANTHROPIC,
164
+ scope._server_address, scope._server_port, request_model, scope._response_model, environment,
165
+ application_name, scope._start_time, scope._end_time, scope._input_tokens, scope._output_tokens,
166
+ cost, scope._tbt, scope._ttft)
195
167
 
196
- metrics['genai_client_usage_tokens'].record(scope._input_tokens + scope._output_tokens, metrics_attributes)
197
- metrics['genai_client_operation_duration'].record(scope._end_time - scope._start_time, metrics_attributes)
198
- metrics['genai_server_tbt'].record(scope._tbt, metrics_attributes)
199
- metrics['genai_server_ttft'].record(scope._ttft, metrics_attributes)
200
- metrics['genai_requests'].add(1, metrics_attributes)
201
- metrics['genai_completion_tokens'].add(scope._output_tokens, metrics_attributes)
202
- metrics['genai_prompt_tokens'].add(scope._input_tokens, metrics_attributes)
203
- metrics['genai_cost'].record(cost, metrics_attributes)
204
-
205
- def process_streaming_chat_response(self, pricing_info, environment, application_name, metrics,
206
- event_provider, capture_message_content=False, disable_metrics=False, version=''):
168
+ def process_streaming_chat_response(scope, pricing_info, environment, application_name, metrics,
169
+ capture_message_content=False, disable_metrics=False, version=""):
207
170
  """
208
- Process chat request and generate Telemetry
171
+ Process streaming chat response and generate telemetry.
209
172
  """
210
- if self._tool_id != '':
211
- self._tool_calls = {
212
- 'id': self._tool_id,
213
- 'name': self._tool_name,
214
- 'input': self._tool_arguments
173
+
174
+ if scope._tool_id != "":
175
+ scope._tool_calls = {
176
+ "id": scope._tool_id,
177
+ "name": scope._tool_name,
178
+ "input": scope._tool_arguments
215
179
  }
216
180
 
217
- common_chat_logic(self, pricing_info, environment, application_name, metrics,
218
- event_provider, capture_message_content, disable_metrics, version, is_stream=True)
181
+ common_chat_logic(scope, pricing_info, environment, application_name, metrics,
182
+ capture_message_content, disable_metrics, version, is_stream=True)
219
183
 
220
184
  def process_chat_response(response, request_model, pricing_info, server_port, server_address,
221
- environment, application_name, metrics, event_provider, start_time,
222
- span, capture_message_content=False, disable_metrics=False, version='1.0.0', **kwargs):
185
+ environment, application_name, metrics, start_time,
186
+ span, capture_message_content=False, disable_metrics=False, version="1.0.0", **kwargs):
223
187
  """
224
- Process chat request and generate Telemetry
188
+ Process non-streaming chat response and generate telemetry.
225
189
  """
226
190
 
227
- self = type('GenericScope', (), {})()
191
+ scope = type("GenericScope", (), {})()
228
192
  response_dict = response_as_dict(response)
229
193
 
230
194
  # pylint: disable = no-member
231
- self._start_time = start_time
232
- self._end_time = time.time()
233
- self._span = span
234
- self._llmresponse = response_dict.get('content', {})[0].get('text', '')
235
- self._response_role = response_dict.get('message', {}).get('role', 'assistant')
236
- self._input_tokens = response_dict.get('usage').get('input_tokens')
237
- self._output_tokens = response_dict.get('usage').get('output_tokens')
238
- self._response_model = response_dict.get('model', '')
239
- self._finish_reason = response_dict.get('stop_reason', '')
240
- self._response_id = response_dict.get('id', '')
241
- self._timestamps = []
242
- self._ttft, self._tbt = self._end_time - self._start_time, 0
243
- self._server_address, self._server_port = server_address, server_port
244
- self._kwargs = kwargs
245
- #pylint: disable=line-too-long
246
- self._tool_calls = (lambda c: c[1] if len(c) > 1 and c[1].get('type') == 'tool_use' else None)(response_dict.get('content', []))
247
-
248
- common_chat_logic(self, pricing_info, environment, application_name, metrics,
249
- event_provider, capture_message_content, disable_metrics, version, is_stream=False)
195
+ scope._start_time = start_time
196
+ scope._end_time = time.time()
197
+ scope._span = span
198
+ scope._llmresponse = response_dict.get("content", [{}])[0].get("text", "")
199
+ scope._response_role = response_dict.get("role", "assistant")
200
+ scope._input_tokens = response_dict.get("usage").get("input_tokens")
201
+ scope._output_tokens = response_dict.get("usage").get("output_tokens")
202
+ scope._response_model = response_dict.get("model", "")
203
+ scope._finish_reason = response_dict.get("stop_reason", "")
204
+ scope._response_id = response_dict.get("id", "")
205
+ scope._timestamps = []
206
+ scope._ttft, scope._tbt = scope._end_time - scope._start_time, 0
207
+ scope._server_address, scope._server_port = server_address, server_port
208
+ scope._kwargs = kwargs
209
+
210
+ # Handle tool calls if present
211
+ content_blocks = response_dict.get("content", [])
212
+ scope._tool_calls = None
213
+ for block in content_blocks:
214
+ if block.get("type") == "tool_use":
215
+ scope._tool_calls = {
216
+ "id": block.get("id", ""),
217
+ "name": block.get("name", ""),
218
+ "input": block.get("input", "")
219
+ }
220
+ break
221
+
222
+ common_chat_logic(scope, pricing_info, environment, application_name, metrics,
223
+ capture_message_content, disable_metrics, version, is_stream=False)
250
224
 
251
225
  return response
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: openlit
3
- Version: 1.34.18
3
+ Version: 1.34.19
4
4
  Summary: OpenTelemetry-native Auto instrumentation library for monitoring LLM Applications and GPUs, facilitating the integration of observability into your GenAI-driven projects
5
5
  License: Apache-2.0
6
6
  Keywords: OpenTelemetry,otel,otlp,llm,tracing,openai,anthropic,claude,cohere,llm monitoring,observability,monitoring,gpt,Generative AI,chatGPT,gpu
@@ -18,10 +18,10 @@ openlit/instrumentation/ai21/__init__.py,sha256=tKX643fwxPWPJq1EXEZd0Xpd6B0jl_Vi
18
18
  openlit/instrumentation/ai21/ai21.py,sha256=zyQMfCLcOFG1tQWrZmGeMaVAmj8MtCUeXQtPHmlUAO0,6533
19
19
  openlit/instrumentation/ai21/async_ai21.py,sha256=q1Dhxru4tUJu0U1Px3PptNqrSGW0-VfRGcqkLKFR8vQ,6659
20
20
  openlit/instrumentation/ai21/utils.py,sha256=5zf69uw_TT8u-q-6R6rBeGm1bX0WpsbrAq-MTTZJ9Bk,14309
21
- openlit/instrumentation/anthropic/__init__.py,sha256=QEsiwdxcQDzzlVYR4_x7KTdf0-UJDJt8FjwNQMspnxM,1929
22
- openlit/instrumentation/anthropic/anthropic.py,sha256=NxJJjhsu9sSFIlBp322olGkPlLt9Bn5sndaugYA68dE,5149
23
- openlit/instrumentation/anthropic/async_anthropic.py,sha256=ivJGygKWVTS2hWWX12_g1tiq-5mpeHXETZsWoFZL3UE,5235
24
- openlit/instrumentation/anthropic/utils.py,sha256=g15QqkHdl2N5WsRCNvxWkMwOYpR_n-lrMcvlY71QbSs,11934
21
+ openlit/instrumentation/anthropic/__init__.py,sha256=mpb15zoUk1jymxUOyyk4IZSKlhsra-3MjO7RLbm1o-I,1753
22
+ openlit/instrumentation/anthropic/anthropic.py,sha256=e5by3_c8zr_6XsQX4R96Wzj840dDiQZe25BgM5melU0,4892
23
+ openlit/instrumentation/anthropic/async_anthropic.py,sha256=fZLqZO5gOMBn_KtTl-5MEDfoOFyQctpNxo6saVaCw8I,4994
24
+ openlit/instrumentation/anthropic/utils.py,sha256=W9paSh2npsCwsHyf-RyHeg0eRDcE509_f-pqb2IeO9c,9761
25
25
  openlit/instrumentation/assemblyai/__init__.py,sha256=-pW7c5Vxa493yETQABbebx4be_sTx5VwkvQrIHbhRbI,1404
26
26
  openlit/instrumentation/assemblyai/assemblyai.py,sha256=SJZ-O6k8adlRWJ2gMIP62vXobHJ3VI87PQOCFw9Ilng,2071
27
27
  openlit/instrumentation/assemblyai/utils.py,sha256=driBfwWBveWTqHyPRtl1R8oEG6m07-GXycyCnDfZ9PM,6089
@@ -141,7 +141,7 @@ openlit/otel/events.py,sha256=VrMjTpvnLtYRBHCiFwJojTQqqNpRCxoD4yJYeQrtPsk,3560
141
141
  openlit/otel/metrics.py,sha256=GM2PDloBGRhBTkHHkYaqmOwIAQkY124ZhW4sEqW1Fgk,7086
142
142
  openlit/otel/tracing.py,sha256=tjV2bEbEDPUB1Z46gE-UsJsb04sRdFrfbhIDkxViZc0,3103
143
143
  openlit/semcov/__init__.py,sha256=8oIh2VC667NDh8FA3M-ESusHmeus1sgDUD8binx_nAc,13519
144
- openlit-1.34.18.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
145
- openlit-1.34.18.dist-info/METADATA,sha256=H34xsI30squSeUxI--7xhbuKEcOc_fEl7ru4HsnOJZU,23470
146
- openlit-1.34.18.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
147
- openlit-1.34.18.dist-info/RECORD,,
144
+ openlit-1.34.19.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
145
+ openlit-1.34.19.dist-info/METADATA,sha256=dxAiXXKjEgxrLrnGShuRAbVebbg1GOmPaa5CODpVqCA,23470
146
+ openlit-1.34.19.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
147
+ openlit-1.34.19.dist-info/RECORD,,