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.
- openlit/instrumentation/anthropic/__init__.py +20 -22
- openlit/instrumentation/anthropic/anthropic.py +39 -46
- openlit/instrumentation/anthropic/async_anthropic.py +40 -47
- openlit/instrumentation/anthropic/utils.py +144 -170
- {openlit-1.34.18.dist-info → openlit-1.34.19.dist-info}/METADATA +1 -1
- {openlit-1.34.18.dist-info → openlit-1.34.19.dist-info}/RECORD +8 -8
- {openlit-1.34.18.dist-info → openlit-1.34.19.dist-info}/LICENSE +0 -0
- {openlit-1.34.18.dist-info → openlit-1.34.19.dist-info}/WHEEL +0 -0
@@ -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 = (
|
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
|
-
|
23
|
-
environment = kwargs.get(
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
disable_metrics = kwargs.get(
|
30
|
-
|
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
|
-
|
35
|
-
|
36
|
-
messages(version, environment, application_name,
|
37
|
-
|
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
|
-
|
43
|
-
|
44
|
-
async_messages(version, environment, application_name,
|
45
|
-
|
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
|
-
|
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
|
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
|
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
|
98
|
+
Wraps the Anthropic Messages.create call.
|
110
99
|
"""
|
111
100
|
|
112
|
-
streaming = kwargs.get(
|
113
|
-
server_address, server_port = set_server_address_and_port(instance,
|
114
|
-
request_model = kwargs.get(
|
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
|
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
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
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
|
-
|
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
|
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
|
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
|
98
|
+
Wraps the Anthropic AsyncMessages.create call.
|
110
99
|
"""
|
111
100
|
|
112
|
-
streaming = kwargs.get(
|
113
|
-
server_address, server_port = set_server_address_and_port(instance,
|
114
|
-
request_model = kwargs.get(
|
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
|
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
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
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
|
-
|
16
|
-
|
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
|
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
|
-
|
57
|
+
scope._timestamps.append(end_time)
|
29
58
|
|
30
|
-
if len(
|
59
|
+
if len(scope._timestamps) == 1:
|
31
60
|
# Calculate time to first chunk
|
32
|
-
|
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(
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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(
|
45
|
-
if chunked.get(
|
46
|
-
|
47
|
-
elif chunked.get(
|
48
|
-
|
49
|
-
|
50
|
-
if chunked.get(
|
51
|
-
if chunked.get(
|
52
|
-
|
53
|
-
if chunked.get(
|
54
|
-
|
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(
|
58
|
-
|
59
|
-
|
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
|
-
|
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 =
|
72
|
-
request_model = scope._kwargs.get(
|
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
|
-
#
|
77
|
-
scope
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
scope._span.set_attribute(SemanticConvention.
|
84
|
-
scope._span.set_attribute(SemanticConvention.
|
85
|
-
scope._span.set_attribute(SemanticConvention.
|
86
|
-
scope._span.set_attribute(SemanticConvention.
|
87
|
-
scope._span.set_attribute(SemanticConvention.
|
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.
|
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
|
-
#
|
107
|
-
|
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:
|
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
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
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
|
-
|
197
|
-
|
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
|
171
|
+
Process streaming chat response and generate telemetry.
|
209
172
|
"""
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
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(
|
218
|
-
|
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,
|
222
|
-
span, capture_message_content=False, disable_metrics=False, version=
|
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
|
188
|
+
Process non-streaming chat response and generate telemetry.
|
225
189
|
"""
|
226
190
|
|
227
|
-
|
191
|
+
scope = type("GenericScope", (), {})()
|
228
192
|
response_dict = response_as_dict(response)
|
229
193
|
|
230
194
|
# pylint: disable = no-member
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
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.
|
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=
|
22
|
-
openlit/instrumentation/anthropic/anthropic.py,sha256=
|
23
|
-
openlit/instrumentation/anthropic/async_anthropic.py,sha256=
|
24
|
-
openlit/instrumentation/anthropic/utils.py,sha256=
|
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.
|
145
|
-
openlit-1.34.
|
146
|
-
openlit-1.34.
|
147
|
-
openlit-1.34.
|
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,,
|
File without changes
|
File without changes
|