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.
Files changed (94) hide show
  1. agentops/__init__.py +488 -0
  2. agentops/client/__init__.py +5 -0
  3. agentops/client/api/__init__.py +71 -0
  4. agentops/client/api/base.py +162 -0
  5. agentops/client/api/types.py +21 -0
  6. agentops/client/api/versions/__init__.py +10 -0
  7. agentops/client/api/versions/v3.py +65 -0
  8. agentops/client/api/versions/v4.py +104 -0
  9. agentops/client/client.py +211 -0
  10. agentops/client/http/__init__.py +0 -0
  11. agentops/client/http/http_adapter.py +116 -0
  12. agentops/client/http/http_client.py +215 -0
  13. agentops/config.py +268 -0
  14. agentops/enums.py +36 -0
  15. agentops/exceptions.py +38 -0
  16. agentops/helpers/__init__.py +44 -0
  17. agentops/helpers/dashboard.py +54 -0
  18. agentops/helpers/deprecation.py +50 -0
  19. agentops/helpers/env.py +52 -0
  20. agentops/helpers/serialization.py +137 -0
  21. agentops/helpers/system.py +178 -0
  22. agentops/helpers/time.py +11 -0
  23. agentops/helpers/version.py +36 -0
  24. agentops/instrumentation/__init__.py +598 -0
  25. agentops/instrumentation/common/__init__.py +82 -0
  26. agentops/instrumentation/common/attributes.py +278 -0
  27. agentops/instrumentation/common/instrumentor.py +147 -0
  28. agentops/instrumentation/common/metrics.py +100 -0
  29. agentops/instrumentation/common/objects.py +26 -0
  30. agentops/instrumentation/common/span_management.py +176 -0
  31. agentops/instrumentation/common/streaming.py +218 -0
  32. agentops/instrumentation/common/token_counting.py +177 -0
  33. agentops/instrumentation/common/version.py +71 -0
  34. agentops/instrumentation/common/wrappers.py +235 -0
  35. agentops/legacy/__init__.py +277 -0
  36. agentops/legacy/event.py +156 -0
  37. agentops/logging/__init__.py +4 -0
  38. agentops/logging/config.py +86 -0
  39. agentops/logging/formatters.py +34 -0
  40. agentops/logging/instrument_logging.py +91 -0
  41. agentops/sdk/__init__.py +27 -0
  42. agentops/sdk/attributes.py +151 -0
  43. agentops/sdk/core.py +607 -0
  44. agentops/sdk/decorators/__init__.py +51 -0
  45. agentops/sdk/decorators/factory.py +486 -0
  46. agentops/sdk/decorators/utility.py +216 -0
  47. agentops/sdk/exporters.py +87 -0
  48. agentops/sdk/processors.py +71 -0
  49. agentops/sdk/types.py +21 -0
  50. agentops/semconv/__init__.py +36 -0
  51. agentops/semconv/agent.py +29 -0
  52. agentops/semconv/core.py +19 -0
  53. agentops/semconv/enum.py +11 -0
  54. agentops/semconv/instrumentation.py +13 -0
  55. agentops/semconv/langchain.py +63 -0
  56. agentops/semconv/message.py +61 -0
  57. agentops/semconv/meters.py +24 -0
  58. agentops/semconv/resource.py +52 -0
  59. agentops/semconv/span_attributes.py +118 -0
  60. agentops/semconv/span_kinds.py +50 -0
  61. agentops/semconv/status.py +11 -0
  62. agentops/semconv/tool.py +15 -0
  63. agentops/semconv/workflow.py +69 -0
  64. agentops/validation.py +357 -0
  65. mseep_agentops-0.4.18.dist-info/METADATA +49 -0
  66. mseep_agentops-0.4.18.dist-info/RECORD +94 -0
  67. mseep_agentops-0.4.18.dist-info/WHEEL +5 -0
  68. mseep_agentops-0.4.18.dist-info/licenses/LICENSE +21 -0
  69. mseep_agentops-0.4.18.dist-info/top_level.txt +2 -0
  70. tests/__init__.py +0 -0
  71. tests/conftest.py +10 -0
  72. tests/unit/__init__.py +0 -0
  73. tests/unit/client/__init__.py +1 -0
  74. tests/unit/client/test_http_adapter.py +221 -0
  75. tests/unit/client/test_http_client.py +206 -0
  76. tests/unit/conftest.py +54 -0
  77. tests/unit/sdk/__init__.py +1 -0
  78. tests/unit/sdk/instrumentation_tester.py +207 -0
  79. tests/unit/sdk/test_attributes.py +392 -0
  80. tests/unit/sdk/test_concurrent_instrumentation.py +468 -0
  81. tests/unit/sdk/test_decorators.py +763 -0
  82. tests/unit/sdk/test_exporters.py +241 -0
  83. tests/unit/sdk/test_factory.py +1188 -0
  84. tests/unit/sdk/test_internal_span_processor.py +397 -0
  85. tests/unit/sdk/test_resource_attributes.py +35 -0
  86. tests/unit/test_config.py +82 -0
  87. tests/unit/test_context_manager.py +777 -0
  88. tests/unit/test_events.py +27 -0
  89. tests/unit/test_host_env.py +54 -0
  90. tests/unit/test_init_py.py +501 -0
  91. tests/unit/test_serialization.py +433 -0
  92. tests/unit/test_session.py +676 -0
  93. tests/unit/test_user_agent.py +34 -0
  94. 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
+ ]
@@ -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
@@ -0,0 +1,4 @@
1
+ from agentops.logging.config import configure_logging, logger
2
+ from agentops.logging.instrument_logging import setup_print_logger, upload_logfile
3
+
4
+ __all__ = ["logger", "configure_logging", "setup_print_logger", "upload_logfile"]