mseep-agentops 0.4.18__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.
- agentops/__init__.py +488 -0
- agentops/client/__init__.py +5 -0
- agentops/client/api/__init__.py +71 -0
- agentops/client/api/base.py +162 -0
- agentops/client/api/types.py +21 -0
- agentops/client/api/versions/__init__.py +10 -0
- agentops/client/api/versions/v3.py +65 -0
- agentops/client/api/versions/v4.py +104 -0
- agentops/client/client.py +211 -0
- agentops/client/http/__init__.py +0 -0
- agentops/client/http/http_adapter.py +116 -0
- agentops/client/http/http_client.py +215 -0
- agentops/config.py +268 -0
- agentops/enums.py +36 -0
- agentops/exceptions.py +38 -0
- agentops/helpers/__init__.py +44 -0
- agentops/helpers/dashboard.py +54 -0
- agentops/helpers/deprecation.py +50 -0
- agentops/helpers/env.py +52 -0
- agentops/helpers/serialization.py +137 -0
- agentops/helpers/system.py +178 -0
- agentops/helpers/time.py +11 -0
- agentops/helpers/version.py +36 -0
- agentops/instrumentation/__init__.py +598 -0
- agentops/instrumentation/common/__init__.py +82 -0
- agentops/instrumentation/common/attributes.py +278 -0
- agentops/instrumentation/common/instrumentor.py +147 -0
- agentops/instrumentation/common/metrics.py +100 -0
- agentops/instrumentation/common/objects.py +26 -0
- agentops/instrumentation/common/span_management.py +176 -0
- agentops/instrumentation/common/streaming.py +218 -0
- agentops/instrumentation/common/token_counting.py +177 -0
- agentops/instrumentation/common/version.py +71 -0
- agentops/instrumentation/common/wrappers.py +235 -0
- agentops/legacy/__init__.py +277 -0
- agentops/legacy/event.py +156 -0
- agentops/logging/__init__.py +4 -0
- agentops/logging/config.py +86 -0
- agentops/logging/formatters.py +34 -0
- agentops/logging/instrument_logging.py +91 -0
- agentops/sdk/__init__.py +27 -0
- agentops/sdk/attributes.py +151 -0
- agentops/sdk/core.py +607 -0
- agentops/sdk/decorators/__init__.py +51 -0
- agentops/sdk/decorators/factory.py +486 -0
- agentops/sdk/decorators/utility.py +216 -0
- agentops/sdk/exporters.py +87 -0
- agentops/sdk/processors.py +71 -0
- agentops/sdk/types.py +21 -0
- agentops/semconv/__init__.py +36 -0
- agentops/semconv/agent.py +29 -0
- agentops/semconv/core.py +19 -0
- agentops/semconv/enum.py +11 -0
- agentops/semconv/instrumentation.py +13 -0
- agentops/semconv/langchain.py +63 -0
- agentops/semconv/message.py +61 -0
- agentops/semconv/meters.py +24 -0
- agentops/semconv/resource.py +52 -0
- agentops/semconv/span_attributes.py +118 -0
- agentops/semconv/span_kinds.py +50 -0
- agentops/semconv/status.py +11 -0
- agentops/semconv/tool.py +15 -0
- agentops/semconv/workflow.py +69 -0
- agentops/validation.py +357 -0
- mseep_agentops-0.4.18.dist-info/METADATA +49 -0
- mseep_agentops-0.4.18.dist-info/RECORD +94 -0
- mseep_agentops-0.4.18.dist-info/WHEEL +5 -0
- mseep_agentops-0.4.18.dist-info/licenses/LICENSE +21 -0
- mseep_agentops-0.4.18.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
- tests/conftest.py +10 -0
- tests/unit/__init__.py +0 -0
- tests/unit/client/__init__.py +1 -0
- tests/unit/client/test_http_adapter.py +221 -0
- tests/unit/client/test_http_client.py +206 -0
- tests/unit/conftest.py +54 -0
- tests/unit/sdk/__init__.py +1 -0
- tests/unit/sdk/instrumentation_tester.py +207 -0
- tests/unit/sdk/test_attributes.py +392 -0
- tests/unit/sdk/test_concurrent_instrumentation.py +468 -0
- tests/unit/sdk/test_decorators.py +763 -0
- tests/unit/sdk/test_exporters.py +241 -0
- tests/unit/sdk/test_factory.py +1188 -0
- tests/unit/sdk/test_internal_span_processor.py +397 -0
- tests/unit/sdk/test_resource_attributes.py +35 -0
- tests/unit/test_config.py +82 -0
- tests/unit/test_context_manager.py +777 -0
- tests/unit/test_events.py +27 -0
- tests/unit/test_host_env.py +54 -0
- tests/unit/test_init_py.py +501 -0
- tests/unit/test_serialization.py +433 -0
- tests/unit/test_session.py +676 -0
- tests/unit/test_user_agent.py +34 -0
- tests/unit/test_validation.py +405 -0
@@ -0,0 +1,235 @@
|
|
1
|
+
"""Common wrapper utilities for OpenTelemetry instrumentation.
|
2
|
+
|
3
|
+
This module provides common utilities for creating and managing wrappers
|
4
|
+
around functions and methods for OpenTelemetry instrumentation. It includes
|
5
|
+
a configuration class for wrapping methods, helper functions for updating
|
6
|
+
spans with attributes, and functions for creating and applying wrappers.
|
7
|
+
"""
|
8
|
+
|
9
|
+
from typing import Any, Optional, Tuple, Dict, Callable
|
10
|
+
from dataclasses import dataclass
|
11
|
+
import logging
|
12
|
+
from wrapt import wrap_function_wrapper # type: ignore
|
13
|
+
from opentelemetry.instrumentation.utils import unwrap as _unwrap
|
14
|
+
from opentelemetry.trace import Tracer
|
15
|
+
from opentelemetry.trace import Span, SpanKind, Status, StatusCode
|
16
|
+
from opentelemetry import context as context_api
|
17
|
+
from opentelemetry.instrumentation.utils import _SUPPRESS_INSTRUMENTATION_KEY
|
18
|
+
|
19
|
+
from agentops.instrumentation.common.attributes import AttributeMap
|
20
|
+
|
21
|
+
logger = logging.getLogger(__name__)
|
22
|
+
|
23
|
+
AttributeHandler = Callable[[Optional[Tuple], Optional[Dict], Optional[Any]], AttributeMap]
|
24
|
+
|
25
|
+
|
26
|
+
@dataclass
|
27
|
+
class WrapConfig:
|
28
|
+
"""Configuration for wrapping a method with OpenTelemetry instrumentation.
|
29
|
+
|
30
|
+
This class defines how a method should be wrapped for instrumentation,
|
31
|
+
including what package, class, and method to wrap, what span attributes
|
32
|
+
to set, and how to name the resulting trace spans.
|
33
|
+
|
34
|
+
Attributes:
|
35
|
+
trace_name: The name to use for the trace span
|
36
|
+
package: The package containing the target class
|
37
|
+
class_name: The name of the class containing the method
|
38
|
+
method_name: The name of the method to wrap
|
39
|
+
handler: A function that extracts attributes from args, kwargs, or return value
|
40
|
+
is_async: Whether the method is asynchronous (default: False)
|
41
|
+
We explicitly specify async methods since `asyncio.iscoroutinefunction`
|
42
|
+
is not reliable in this context.
|
43
|
+
span_kind: The kind of span to create (default: CLIENT)
|
44
|
+
"""
|
45
|
+
|
46
|
+
trace_name: str
|
47
|
+
package: str
|
48
|
+
class_name: str
|
49
|
+
method_name: str
|
50
|
+
handler: AttributeHandler
|
51
|
+
is_async: bool = False
|
52
|
+
span_kind: SpanKind = SpanKind.CLIENT
|
53
|
+
|
54
|
+
def __repr__(self):
|
55
|
+
return f"{self.package}.{self.class_name}.{self.method_name}"
|
56
|
+
|
57
|
+
|
58
|
+
def _update_span(span: Span, attributes: AttributeMap) -> None:
|
59
|
+
"""Update a span with the provided attributes.
|
60
|
+
|
61
|
+
Args:
|
62
|
+
span: The OpenTelemetry span to update
|
63
|
+
attributes: A dictionary of attributes to set on the span
|
64
|
+
"""
|
65
|
+
for key, value in attributes.items():
|
66
|
+
span.set_attribute(key, value)
|
67
|
+
|
68
|
+
|
69
|
+
def _finish_span_success(span: Span) -> None:
|
70
|
+
"""Mark a span as successful by setting its status to OK.
|
71
|
+
|
72
|
+
Args:
|
73
|
+
span: The OpenTelemetry span to update
|
74
|
+
"""
|
75
|
+
span.set_status(Status(StatusCode.OK))
|
76
|
+
|
77
|
+
|
78
|
+
def _finish_span_error(span: Span, exception: Exception) -> None:
|
79
|
+
"""Mark a span as failed by recording the exception and setting error status.
|
80
|
+
|
81
|
+
Args:
|
82
|
+
span: The OpenTelemetry span to update
|
83
|
+
exception: The exception that caused the error
|
84
|
+
"""
|
85
|
+
span.record_exception(exception)
|
86
|
+
span.set_status(Status(StatusCode.ERROR, str(exception)))
|
87
|
+
|
88
|
+
|
89
|
+
def _create_wrapper(wrap_config: WrapConfig, tracer: Tracer) -> Callable:
|
90
|
+
"""Create a wrapper function for the specified configuration.
|
91
|
+
|
92
|
+
This function creates a wrapper that:
|
93
|
+
1. Creates a new span for the wrapped method
|
94
|
+
2. Sets attributes on the span based on input arguments
|
95
|
+
3. Calls the wrapped method
|
96
|
+
4. Sets attributes on the span based on the return value
|
97
|
+
5. Handles exceptions by recording them on the span
|
98
|
+
|
99
|
+
Args:
|
100
|
+
wrap_config: Configuration for the wrapper
|
101
|
+
tracer: The OpenTelemetry tracer to use for creating spans
|
102
|
+
|
103
|
+
Returns:
|
104
|
+
A wrapper function compatible with wrapt.wrap_function_wrapper
|
105
|
+
"""
|
106
|
+
handler = wrap_config.handler
|
107
|
+
|
108
|
+
async def awrapper(wrapped, instance, args, kwargs):
|
109
|
+
# Skip instrumentation if it's suppressed in the current context
|
110
|
+
# TODO I don't understand what this actually does
|
111
|
+
if context_api.get_value(_SUPPRESS_INSTRUMENTATION_KEY):
|
112
|
+
return wrapped(*args, **kwargs)
|
113
|
+
|
114
|
+
return_value = None
|
115
|
+
|
116
|
+
with tracer.start_as_current_span(
|
117
|
+
wrap_config.trace_name,
|
118
|
+
kind=wrap_config.span_kind,
|
119
|
+
) as span:
|
120
|
+
try:
|
121
|
+
# Add the input attributes to the span before execution
|
122
|
+
attributes = handler(args=args, kwargs=kwargs)
|
123
|
+
_update_span(span, attributes)
|
124
|
+
|
125
|
+
return_value = await wrapped(*args, **kwargs)
|
126
|
+
|
127
|
+
# Add the output attributes to the span after execution
|
128
|
+
attributes = handler(return_value=return_value)
|
129
|
+
_update_span(span, attributes)
|
130
|
+
_finish_span_success(span)
|
131
|
+
except Exception as e:
|
132
|
+
# Add everything we have in the case of an error
|
133
|
+
attributes = handler(args=args, kwargs=kwargs, return_value=return_value)
|
134
|
+
_update_span(span, attributes)
|
135
|
+
_finish_span_error(span, e)
|
136
|
+
raise
|
137
|
+
|
138
|
+
return return_value
|
139
|
+
|
140
|
+
def wrapper(wrapped, instance, args, kwargs):
|
141
|
+
# Skip instrumentation if it's suppressed in the current context
|
142
|
+
# TODO I don't understand what this actually does
|
143
|
+
if context_api.get_value(_SUPPRESS_INSTRUMENTATION_KEY):
|
144
|
+
return wrapped(*args, **kwargs)
|
145
|
+
|
146
|
+
return_value = None
|
147
|
+
|
148
|
+
with tracer.start_as_current_span(
|
149
|
+
wrap_config.trace_name,
|
150
|
+
kind=wrap_config.span_kind,
|
151
|
+
) as span:
|
152
|
+
try:
|
153
|
+
# Add the input attributes to the span before execution
|
154
|
+
attributes = handler(args=args, kwargs=kwargs)
|
155
|
+
_update_span(span, attributes)
|
156
|
+
|
157
|
+
return_value = wrapped(*args, **kwargs)
|
158
|
+
|
159
|
+
# Add the output attributes to the span after execution
|
160
|
+
attributes = handler(return_value=return_value)
|
161
|
+
_update_span(span, attributes)
|
162
|
+
_finish_span_success(span)
|
163
|
+
except Exception as e:
|
164
|
+
# Add everything we have in the case of an error
|
165
|
+
attributes = handler(args=args, kwargs=kwargs, return_value=return_value)
|
166
|
+
_update_span(span, attributes)
|
167
|
+
_finish_span_error(span, e)
|
168
|
+
raise
|
169
|
+
|
170
|
+
return return_value
|
171
|
+
|
172
|
+
if wrap_config.is_async:
|
173
|
+
return awrapper
|
174
|
+
else:
|
175
|
+
return wrapper
|
176
|
+
|
177
|
+
|
178
|
+
def wrap(wrap_config: WrapConfig, tracer: Tracer) -> Callable:
|
179
|
+
"""Wrap a method with OpenTelemetry instrumentation.
|
180
|
+
|
181
|
+
This function applies the wrapper created by _create_wrapper
|
182
|
+
to the method specified in the wrap_config.
|
183
|
+
|
184
|
+
Args:
|
185
|
+
wrap_config: Configuration specifying what to wrap and how
|
186
|
+
tracer: The OpenTelemetry tracer to use for creating spans
|
187
|
+
|
188
|
+
Returns:
|
189
|
+
The result of wrap_function_wrapper (typically None)
|
190
|
+
"""
|
191
|
+
return wrap_function_wrapper(
|
192
|
+
wrap_config.package,
|
193
|
+
f"{wrap_config.class_name}.{wrap_config.method_name}",
|
194
|
+
_create_wrapper(wrap_config, tracer),
|
195
|
+
)
|
196
|
+
|
197
|
+
|
198
|
+
def unwrap(wrap_config: WrapConfig):
|
199
|
+
"""Remove instrumentation wrapper from a method.
|
200
|
+
|
201
|
+
This function removes the wrapper applied by wrap().
|
202
|
+
|
203
|
+
Args:
|
204
|
+
wrap_config: Configuration specifying what to unwrap
|
205
|
+
|
206
|
+
Returns:
|
207
|
+
The result of the unwrap operation (typically None)
|
208
|
+
"""
|
209
|
+
return _unwrap(
|
210
|
+
f"{wrap_config.package}.{wrap_config.class_name}",
|
211
|
+
wrap_config.method_name,
|
212
|
+
)
|
213
|
+
|
214
|
+
|
215
|
+
def _with_tracer_wrapper(func):
|
216
|
+
"""Wrap a function with a tracer.
|
217
|
+
|
218
|
+
This decorator creates a higher-order function that takes a tracer as its first argument
|
219
|
+
and returns a function suitable for use with wrapt's wrap_function_wrapper. It's used
|
220
|
+
to consistently apply OpenTelemetry tracing to SDK functions.
|
221
|
+
|
222
|
+
Args:
|
223
|
+
func: The instrumentation function to wrap
|
224
|
+
|
225
|
+
Returns:
|
226
|
+
A decorator function that takes a tracer and returns a wrapt-compatible wrapper
|
227
|
+
"""
|
228
|
+
|
229
|
+
def _with_tracer(tracer):
|
230
|
+
def wrapper(wrapped, instance, args, kwargs):
|
231
|
+
return func(tracer, wrapped, instance, args, kwargs)
|
232
|
+
|
233
|
+
return wrapper
|
234
|
+
|
235
|
+
return _with_tracer
|
@@ -0,0 +1,277 @@
|
|
1
|
+
"""
|
2
|
+
Compatibility layer for deprecated functions and classes.
|
3
|
+
|
4
|
+
CrewAI contains direct integrations with AgentOps across multiple versions.
|
5
|
+
These integrations use different patterns:
|
6
|
+
- CrewAI < 0.105.0: Direct calls to agentops.end_session() with kwargs
|
7
|
+
- CrewAI >= 0.105.0: Event-based integration using Session objects
|
8
|
+
|
9
|
+
This module maintains backward compatibility with all these API patterns.
|
10
|
+
"""
|
11
|
+
|
12
|
+
from typing import Optional, Any, Dict, List, Union
|
13
|
+
|
14
|
+
from agentops.logging import logger
|
15
|
+
from agentops.sdk.core import TraceContext, tracer
|
16
|
+
from agentops.helpers.deprecation import deprecated
|
17
|
+
|
18
|
+
_current_session: Optional["Session"] = None
|
19
|
+
_current_trace_context: Optional[TraceContext] = None
|
20
|
+
|
21
|
+
|
22
|
+
class Session:
|
23
|
+
"""
|
24
|
+
This class provides compatibility with CrewAI >= 0.105.0, which uses an event-based
|
25
|
+
integration pattern where it calls methods directly on the Session object:
|
26
|
+
|
27
|
+
- create_agent(): Called when a CrewAI agent is created
|
28
|
+
- record(): Called when a CrewAI tool is used
|
29
|
+
- end_session(): Called when a CrewAI run completes
|
30
|
+
"""
|
31
|
+
|
32
|
+
def __init__(self, trace_context: Optional[TraceContext]):
|
33
|
+
self.trace_context = trace_context
|
34
|
+
|
35
|
+
@property
|
36
|
+
def span(self) -> Optional[Any]:
|
37
|
+
return self.trace_context.span if self.trace_context else None
|
38
|
+
|
39
|
+
@property
|
40
|
+
def token(self) -> Optional[Any]:
|
41
|
+
return self.trace_context.token if self.trace_context else None
|
42
|
+
|
43
|
+
def __del__(self):
|
44
|
+
if self.trace_context and self.trace_context.span and self.trace_context.span.is_recording():
|
45
|
+
if not self.trace_context.is_init_trace:
|
46
|
+
logger.warning(
|
47
|
+
f"Legacy Session (trace ID: {self.trace_context.span.get_span_context().span_id}) \
|
48
|
+
was garbage collected but its trace might still be recording. Ensure legacy sessions are ended with end_session()."
|
49
|
+
)
|
50
|
+
|
51
|
+
def create_agent(self, name: Optional[str] = None, agent_id: Optional[str] = None, **kwargs: Any):
|
52
|
+
"""Method for CrewAI >= 0.105.0 compatibility. Currently a no-op."""
|
53
|
+
pass
|
54
|
+
|
55
|
+
def record(self, event: Any = None):
|
56
|
+
"""Method for CrewAI >= 0.105.0 compatibility. Currently a no-op."""
|
57
|
+
pass
|
58
|
+
|
59
|
+
def end_session(self, **kwargs: Any):
|
60
|
+
"""Ends the session for CrewAI >= 0.105.0 compatibility. Calls the global legacy end_session."""
|
61
|
+
end_session(session_or_status=self, **kwargs)
|
62
|
+
|
63
|
+
|
64
|
+
@deprecated("Use agentops.start_trace() instead.")
|
65
|
+
def start_session(
|
66
|
+
tags: Union[Dict[str, Any], List[str], None] = None,
|
67
|
+
) -> Session:
|
68
|
+
"""
|
69
|
+
@deprecated Use agentops.start_trace() instead.
|
70
|
+
Starts a legacy AgentOps session. Calls tracer.start_trace internally.
|
71
|
+
"""
|
72
|
+
global _current_session, _current_trace_context
|
73
|
+
|
74
|
+
if not tracer.initialized:
|
75
|
+
from agentops import Client
|
76
|
+
|
77
|
+
try:
|
78
|
+
Client().init(auto_start_session=False)
|
79
|
+
if not tracer.initialized:
|
80
|
+
logger.warning("AgentOps client init failed during legacy start_session. Creating dummy session.")
|
81
|
+
dummy_session = Session(None)
|
82
|
+
_current_session = dummy_session
|
83
|
+
_current_trace_context = None
|
84
|
+
return dummy_session
|
85
|
+
except Exception as e:
|
86
|
+
logger.warning(f"AgentOps client init failed: {str(e)}. Creating dummy session.")
|
87
|
+
dummy_session = Session(None)
|
88
|
+
_current_session = dummy_session
|
89
|
+
_current_trace_context = None
|
90
|
+
return dummy_session
|
91
|
+
|
92
|
+
trace_context = tracer.start_trace(trace_name="session", tags=tags)
|
93
|
+
if trace_context is None:
|
94
|
+
logger.error("Failed to start trace via global tracer. Returning dummy session.")
|
95
|
+
dummy_session = Session(None)
|
96
|
+
_current_session = dummy_session
|
97
|
+
_current_trace_context = None
|
98
|
+
return dummy_session
|
99
|
+
|
100
|
+
session_obj = Session(trace_context)
|
101
|
+
_current_session = session_obj
|
102
|
+
_current_trace_context = trace_context
|
103
|
+
|
104
|
+
try:
|
105
|
+
import agentops.client.client
|
106
|
+
|
107
|
+
agentops.client.client._active_session = session_obj # type: ignore
|
108
|
+
if hasattr(agentops.client.client, "_active_trace_context"):
|
109
|
+
agentops.client.client._active_trace_context = trace_context # type: ignore
|
110
|
+
except (ImportError, AttributeError):
|
111
|
+
pass
|
112
|
+
return session_obj
|
113
|
+
|
114
|
+
|
115
|
+
def _set_span_attributes(span: Any, attributes: Dict[str, Any]) -> None:
|
116
|
+
"""Helper to set attributes on a span for legacy purposes."""
|
117
|
+
if span is None or not attributes:
|
118
|
+
return
|
119
|
+
for key, value in attributes.items():
|
120
|
+
if key.lower() == "end_state" and "end_state" in attributes:
|
121
|
+
pass
|
122
|
+
else:
|
123
|
+
span.set_attribute(f"agentops.legacy.{key}", str(value))
|
124
|
+
|
125
|
+
|
126
|
+
@deprecated("Use agentops.end_trace() instead.")
|
127
|
+
def end_session(session_or_status: Any = None, **kwargs: Any) -> None:
|
128
|
+
"""
|
129
|
+
@deprecated Use agentops.end_trace() instead.
|
130
|
+
Ends a legacy AgentOps session. Calls tracer.end_trace internally.
|
131
|
+
Supports multiple calling patterns for backward compatibility.
|
132
|
+
"""
|
133
|
+
global _current_session, _current_trace_context
|
134
|
+
|
135
|
+
if not tracer.initialized:
|
136
|
+
logger.debug("Ignoring end_session: global tracer not initialized.")
|
137
|
+
return
|
138
|
+
|
139
|
+
target_trace_context: Optional[TraceContext] = None
|
140
|
+
end_state_from_args = "Success"
|
141
|
+
extra_attributes = kwargs.copy()
|
142
|
+
|
143
|
+
if isinstance(session_or_status, Session):
|
144
|
+
target_trace_context = session_or_status.trace_context
|
145
|
+
if "end_state" in extra_attributes:
|
146
|
+
end_state_from_args = str(extra_attributes.pop("end_state"))
|
147
|
+
elif isinstance(session_or_status, str):
|
148
|
+
end_state_from_args = session_or_status
|
149
|
+
target_trace_context = _current_trace_context
|
150
|
+
if "end_state" in extra_attributes:
|
151
|
+
end_state_from_args = str(extra_attributes.pop("end_state"))
|
152
|
+
elif session_or_status is None and kwargs:
|
153
|
+
target_trace_context = _current_trace_context
|
154
|
+
if "end_state" in extra_attributes:
|
155
|
+
end_state_from_args = str(extra_attributes.pop("end_state"))
|
156
|
+
else:
|
157
|
+
target_trace_context = _current_trace_context
|
158
|
+
if "end_state" in extra_attributes:
|
159
|
+
end_state_from_args = str(extra_attributes.pop("end_state"))
|
160
|
+
|
161
|
+
if not target_trace_context:
|
162
|
+
logger.warning("end_session called but no active trace context found.")
|
163
|
+
return
|
164
|
+
|
165
|
+
if target_trace_context.span and extra_attributes:
|
166
|
+
_set_span_attributes(target_trace_context.span, extra_attributes)
|
167
|
+
|
168
|
+
tracer.end_trace(target_trace_context, end_state=end_state_from_args)
|
169
|
+
|
170
|
+
if target_trace_context is _current_trace_context:
|
171
|
+
_current_session = None
|
172
|
+
_current_trace_context = None
|
173
|
+
|
174
|
+
try:
|
175
|
+
import agentops.client.client
|
176
|
+
|
177
|
+
if (
|
178
|
+
hasattr(agentops.client.client, "_active_trace_context")
|
179
|
+
and agentops.client.client._active_trace_context is target_trace_context
|
180
|
+
): # type: ignore
|
181
|
+
agentops.client.client._active_trace_context = None # type: ignore
|
182
|
+
agentops.client.client._active_session = None # type: ignore
|
183
|
+
elif (
|
184
|
+
hasattr(agentops.client.client, "_init_trace_context")
|
185
|
+
and agentops.client.client._init_trace_context is target_trace_context
|
186
|
+
): # type: ignore
|
187
|
+
logger.debug("Legacy end_session called on client's auto-init trace. This is unusual.")
|
188
|
+
except (ImportError, AttributeError):
|
189
|
+
pass
|
190
|
+
|
191
|
+
|
192
|
+
@deprecated("Use agentops.end_trace() instead.")
|
193
|
+
def end_all_sessions() -> None:
|
194
|
+
"""@deprecated Ends all active sessions/traces."""
|
195
|
+
if not tracer.initialized:
|
196
|
+
logger.debug("Ignoring end_all_sessions: global tracer not initialized.")
|
197
|
+
return
|
198
|
+
|
199
|
+
# Use the new end_trace functionality to end all active traces
|
200
|
+
tracer.end_trace(trace_context=None, end_state="Success")
|
201
|
+
|
202
|
+
# Clear legacy global state
|
203
|
+
global _current_session, _current_trace_context
|
204
|
+
_current_session = None
|
205
|
+
_current_trace_context = None
|
206
|
+
|
207
|
+
|
208
|
+
@deprecated("Automatically tracked in v4.")
|
209
|
+
def ToolEvent(*args: Any, **kwargs: Any) -> None:
|
210
|
+
"""@deprecated Automatically tracked in v4."""
|
211
|
+
return None
|
212
|
+
|
213
|
+
|
214
|
+
@deprecated("Automatically tracked in v4.")
|
215
|
+
def ErrorEvent(*args: Any, **kwargs: Any) -> Any:
|
216
|
+
"""@deprecated Automatically tracked in v4. Returns minimal object for test compatibility."""
|
217
|
+
from agentops.helpers.time import get_ISO_time
|
218
|
+
|
219
|
+
class LegacyErrorEvent:
|
220
|
+
def __init__(self):
|
221
|
+
self.init_timestamp = get_ISO_time()
|
222
|
+
self.end_timestamp = None
|
223
|
+
|
224
|
+
return LegacyErrorEvent()
|
225
|
+
|
226
|
+
|
227
|
+
@deprecated("Automatically tracked in v4.")
|
228
|
+
def ActionEvent(*args: Any, **kwargs: Any) -> Any:
|
229
|
+
"""@deprecated Automatically tracked in v4. Returns minimal object for test compatibility."""
|
230
|
+
from agentops.helpers.time import get_ISO_time
|
231
|
+
|
232
|
+
class LegacyActionEvent:
|
233
|
+
def __init__(self):
|
234
|
+
self.init_timestamp = get_ISO_time()
|
235
|
+
self.end_timestamp = None
|
236
|
+
|
237
|
+
return LegacyActionEvent()
|
238
|
+
|
239
|
+
|
240
|
+
@deprecated("Automatically tracked in v4.")
|
241
|
+
def LLMEvent(*args: Any, **kwargs: Any) -> None:
|
242
|
+
"""@deprecated Automatically tracked in v4."""
|
243
|
+
return None
|
244
|
+
|
245
|
+
|
246
|
+
@deprecated("Use @agent decorator instead.")
|
247
|
+
def track_agent(*args: Any, **kwargs: Any) -> Any:
|
248
|
+
"""@deprecated No-op decorator."""
|
249
|
+
|
250
|
+
def noop(f: Any) -> Any:
|
251
|
+
return f
|
252
|
+
|
253
|
+
return noop
|
254
|
+
|
255
|
+
|
256
|
+
@deprecated("Use @tool decorator instead.")
|
257
|
+
def track_tool(*args: Any, **kwargs: Any) -> Any:
|
258
|
+
"""@deprecated No-op decorator."""
|
259
|
+
|
260
|
+
def noop(f: Any) -> Any:
|
261
|
+
return f
|
262
|
+
|
263
|
+
return noop
|
264
|
+
|
265
|
+
|
266
|
+
__all__ = [
|
267
|
+
"start_session",
|
268
|
+
"end_session",
|
269
|
+
"ToolEvent",
|
270
|
+
"ErrorEvent",
|
271
|
+
"ActionEvent",
|
272
|
+
"track_agent",
|
273
|
+
"track_tool",
|
274
|
+
"end_all_sessions",
|
275
|
+
"Session",
|
276
|
+
"LLMEvent",
|
277
|
+
]
|
agentops/legacy/event.py
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
"""
|
2
|
+
AgentOps events.
|
3
|
+
|
4
|
+
Data Class:
|
5
|
+
Event: Represents discrete events to be recorded.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import traceback
|
9
|
+
from dataclasses import dataclass, field
|
10
|
+
from enum import Enum
|
11
|
+
from typing import Any, Dict, List, Optional, Sequence, Union
|
12
|
+
from uuid import UUID, uuid4
|
13
|
+
|
14
|
+
from agentops.helpers import get_ISO_time
|
15
|
+
|
16
|
+
|
17
|
+
class EventType(Enum):
|
18
|
+
LLM = "llms"
|
19
|
+
ACTION = "actions"
|
20
|
+
API = "apis"
|
21
|
+
TOOL = "tools"
|
22
|
+
ERROR = "errors"
|
23
|
+
|
24
|
+
|
25
|
+
@dataclass
|
26
|
+
class Event:
|
27
|
+
"""
|
28
|
+
Abstract base class for events that will be recorded. Should not be instantiated directly.
|
29
|
+
|
30
|
+
event_type(str): The type of event. Defined in events.EventType. Some values are 'llm', 'action', 'api', 'tool', 'error'.
|
31
|
+
params(dict, optional): The parameters of the function containing the triggered event, e.g. {'x': 1} in example below
|
32
|
+
returns(str, optional): The return value of the function containing the triggered event, e.g. 2 in example below
|
33
|
+
init_timestamp(str): A timestamp indicating when the event began. Defaults to the time when this Event was instantiated.
|
34
|
+
end_timestamp(str): A timestamp indicating when the event ended. Defaults to the time when this Event was instantiated.
|
35
|
+
agent_id(UUID, optional): The unique identifier of the agent that triggered the event.
|
36
|
+
id(UUID): A unique identifier for the event. Defaults to a new UUID.
|
37
|
+
session_id(UUID, optional): The unique identifier of the session that the event belongs to.
|
38
|
+
|
39
|
+
foo(x=1) {
|
40
|
+
...
|
41
|
+
// params equals {'x': 1}
|
42
|
+
record(ActionEvent(params=**kwargs, ...))
|
43
|
+
...
|
44
|
+
// returns equals 2
|
45
|
+
return x+1
|
46
|
+
}
|
47
|
+
"""
|
48
|
+
|
49
|
+
event_type: str
|
50
|
+
params: Optional[dict] = None
|
51
|
+
returns: Optional[Union[str, List[str]]] = None
|
52
|
+
init_timestamp: str = field(default_factory=get_ISO_time)
|
53
|
+
end_timestamp: Optional[str] = None
|
54
|
+
agent_id: Optional[UUID] = None
|
55
|
+
id: UUID = field(default_factory=uuid4)
|
56
|
+
session_id: Optional[UUID] = None
|
57
|
+
|
58
|
+
|
59
|
+
@dataclass
|
60
|
+
class ActionEvent(Event):
|
61
|
+
"""
|
62
|
+
For generic events
|
63
|
+
|
64
|
+
action_type(str, optional): High level name describing the action
|
65
|
+
logs(str, optional): For detailed information/logging related to the action
|
66
|
+
screenshot(str, optional): url to snapshot if agent interacts with UI
|
67
|
+
"""
|
68
|
+
|
69
|
+
event_type: str = EventType.ACTION.value
|
70
|
+
# TODO: Should not be optional, but non-default argument 'agent_id' follows default argument error
|
71
|
+
action_type: Optional[str] = None
|
72
|
+
logs: Optional[Union[str, Sequence[Any]]] = None
|
73
|
+
screenshot: Optional[str] = None
|
74
|
+
|
75
|
+
|
76
|
+
@dataclass
|
77
|
+
class LLMEvent(Event):
|
78
|
+
"""
|
79
|
+
For recording calls to LLMs. AgentOps auto-instruments calls to the most popular LLMs e.g. GPT, Claude, Gemini, etc.
|
80
|
+
|
81
|
+
thread_id(UUID, optional): The unique identifier of the contextual thread that a message pertains to.
|
82
|
+
prompt(str, list, optional): The message or messages that were used to prompt the LLM. Preferably in ChatML format which is more fully supported by AgentOps.
|
83
|
+
prompt_tokens(int, optional): The number of tokens in the prompt message.
|
84
|
+
completion(str, object, optional): The message or messages returned by the LLM. Preferably in ChatML format which is more fully supported by AgentOps.
|
85
|
+
completion_tokens(int, optional): The number of tokens in the completion message.
|
86
|
+
model(str, optional): LLM model e.g. "gpt-4", "gpt-3.5-turbo".
|
87
|
+
|
88
|
+
"""
|
89
|
+
|
90
|
+
event_type: str = EventType.LLM.value
|
91
|
+
thread_id: Optional[UUID] = None
|
92
|
+
prompt: Optional[Union[str, List]] = None
|
93
|
+
prompt_tokens: Optional[int] = None
|
94
|
+
completion: Union[str, object] = None
|
95
|
+
completion_tokens: Optional[int] = None
|
96
|
+
cost: Optional[float] = None
|
97
|
+
model: Optional[str] = None
|
98
|
+
|
99
|
+
|
100
|
+
@dataclass
|
101
|
+
class ToolEvent(Event):
|
102
|
+
"""
|
103
|
+
For recording calls to tools e.g. searchWeb, fetchFromDB
|
104
|
+
|
105
|
+
name(str, optional): A name describing the tool or the actual function name if applicable e.g. searchWeb, fetchFromDB.
|
106
|
+
logs(str, dict, optional): For detailed information/logging related to the tool.
|
107
|
+
|
108
|
+
"""
|
109
|
+
|
110
|
+
event_type: str = EventType.TOOL.value
|
111
|
+
name: Optional[str] = None
|
112
|
+
logs: Optional[Union[str, dict]] = None
|
113
|
+
|
114
|
+
|
115
|
+
# Does not inherit from Event because error will (optionally) be linked to an ActionEvent, LLMEvent, etc that will have the details
|
116
|
+
|
117
|
+
|
118
|
+
@dataclass
|
119
|
+
class ErrorEvent(Event):
|
120
|
+
"""
|
121
|
+
For recording any errors e.g. ones related to agent execution
|
122
|
+
|
123
|
+
trigger_event(Event, optional): The event object that triggered the error if applicable.
|
124
|
+
exception(BaseException, optional): The thrown exception. We will automatically parse the error_type and details from this.
|
125
|
+
error_type(str, optional): The type of error e.g. "ValueError".
|
126
|
+
code(str, optional): A code that can be used to identify the error e.g. 501.
|
127
|
+
details(str, optional): Detailed information about the error.
|
128
|
+
logs(str, optional): For detailed information/logging related to the error.
|
129
|
+
"""
|
130
|
+
|
131
|
+
# Inherit common Event fields
|
132
|
+
event_type: str = field(default=EventType.ERROR.value)
|
133
|
+
|
134
|
+
# Error-specific fields
|
135
|
+
trigger_event: Optional[Event] = None
|
136
|
+
exception: Optional[BaseException] = None
|
137
|
+
error_type: Optional[str] = None
|
138
|
+
code: Optional[str] = None
|
139
|
+
details: Optional[Union[str, Dict[str, str]]] = None
|
140
|
+
logs: Optional[str] = field(default_factory=traceback.format_exc)
|
141
|
+
|
142
|
+
def __post_init__(self):
|
143
|
+
"""Process exception if provided"""
|
144
|
+
if self.exception:
|
145
|
+
self.error_type = self.error_type or type(self.exception).__name__
|
146
|
+
self.details = self.details or str(self.exception)
|
147
|
+
self.exception = None # removes exception from serialization
|
148
|
+
|
149
|
+
# Ensure end timestamp is set
|
150
|
+
if not self.end_timestamp:
|
151
|
+
self.end_timestamp = get_ISO_time()
|
152
|
+
|
153
|
+
@property
|
154
|
+
def timestamp(self) -> str:
|
155
|
+
"""Maintain backward compatibility with old code expecting timestamp"""
|
156
|
+
return self.init_timestamp
|