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
agentops/__init__.py
ADDED
@@ -0,0 +1,488 @@
|
|
1
|
+
# For backwards compatibility
|
2
|
+
from agentops.legacy import (
|
3
|
+
start_session,
|
4
|
+
end_session,
|
5
|
+
track_agent,
|
6
|
+
track_tool,
|
7
|
+
end_all_sessions,
|
8
|
+
Session,
|
9
|
+
ToolEvent,
|
10
|
+
ErrorEvent,
|
11
|
+
ActionEvent,
|
12
|
+
LLMEvent,
|
13
|
+
) # type: ignore
|
14
|
+
|
15
|
+
# Import all required modules at the top
|
16
|
+
from opentelemetry.trace import get_current_span
|
17
|
+
from agentops.semconv import (
|
18
|
+
AgentAttributes,
|
19
|
+
ToolAttributes,
|
20
|
+
WorkflowAttributes,
|
21
|
+
CoreAttributes,
|
22
|
+
SpanKind,
|
23
|
+
SpanAttributes,
|
24
|
+
)
|
25
|
+
import json
|
26
|
+
from typing import List, Optional, Union, Dict, Any
|
27
|
+
from agentops.client import Client
|
28
|
+
from agentops.sdk.core import TraceContext, tracer
|
29
|
+
from agentops.sdk.decorators import trace, session, agent, task, workflow, operation, tool, guardrail, track_endpoint
|
30
|
+
from agentops.enums import TraceState, SUCCESS, ERROR, UNSET
|
31
|
+
from opentelemetry.trace.status import StatusCode
|
32
|
+
|
33
|
+
from agentops.logging.config import logger
|
34
|
+
from agentops.helpers.deprecation import deprecated, warn_deprecated_param
|
35
|
+
import threading
|
36
|
+
|
37
|
+
# Import validation functions
|
38
|
+
from agentops.validation import validate_trace_spans, print_validation_summary, ValidationError
|
39
|
+
|
40
|
+
# Thread-safe client management
|
41
|
+
_client_lock = threading.Lock()
|
42
|
+
_client = None
|
43
|
+
|
44
|
+
|
45
|
+
def get_client() -> Client:
|
46
|
+
"""Get the singleton client instance in a thread-safe manner"""
|
47
|
+
global _client
|
48
|
+
|
49
|
+
# Double-checked locking pattern for thread safety
|
50
|
+
if _client is None:
|
51
|
+
with _client_lock:
|
52
|
+
if _client is None:
|
53
|
+
_client = Client()
|
54
|
+
|
55
|
+
return _client
|
56
|
+
|
57
|
+
|
58
|
+
@deprecated("Automatically tracked in v4.")
|
59
|
+
def record(event):
|
60
|
+
"""
|
61
|
+
Legacy function to record an event. This is kept for backward compatibility.
|
62
|
+
|
63
|
+
In the current version, this simply sets the end_timestamp on the event.
|
64
|
+
|
65
|
+
Args:
|
66
|
+
event: The event to record
|
67
|
+
"""
|
68
|
+
from agentops.helpers.time import get_ISO_time
|
69
|
+
|
70
|
+
# TODO: Manual timestamp assignment is a temporary fix; should use proper event lifecycle
|
71
|
+
if event and hasattr(event, "end_timestamp"):
|
72
|
+
event.end_timestamp = get_ISO_time()
|
73
|
+
|
74
|
+
return event
|
75
|
+
|
76
|
+
|
77
|
+
def init(
|
78
|
+
api_key: Optional[str] = None,
|
79
|
+
endpoint: Optional[str] = None,
|
80
|
+
app_url: Optional[str] = None,
|
81
|
+
max_wait_time: Optional[int] = None,
|
82
|
+
max_queue_size: Optional[int] = None,
|
83
|
+
tags: Optional[List[str]] = None,
|
84
|
+
default_tags: Optional[List[str]] = None,
|
85
|
+
trace_name: Optional[str] = None,
|
86
|
+
instrument_llm_calls: Optional[bool] = None,
|
87
|
+
auto_start_session: Optional[bool] = None,
|
88
|
+
auto_init: Optional[bool] = None,
|
89
|
+
skip_auto_end_session: Optional[bool] = None,
|
90
|
+
env_data_opt_out: Optional[bool] = None,
|
91
|
+
log_level: Optional[Union[str, int]] = None,
|
92
|
+
fail_safe: Optional[bool] = None,
|
93
|
+
log_session_replay_url: Optional[bool] = None,
|
94
|
+
exporter_endpoint: Optional[str] = None,
|
95
|
+
**kwargs,
|
96
|
+
):
|
97
|
+
"""
|
98
|
+
Initializes the AgentOps SDK.
|
99
|
+
|
100
|
+
Args:
|
101
|
+
api_key (str, optional): API Key for AgentOps services. If none is provided, key will
|
102
|
+
be read from the AGENTOPS_API_KEY environment variable.
|
103
|
+
endpoint (str, optional): The endpoint for the AgentOps service. If none is provided, key will
|
104
|
+
be read from the AGENTOPS_API_ENDPOINT environment variable. Defaults to 'https://api.agentops.ai'.
|
105
|
+
app_url (str, optional): The dashboard URL for the AgentOps app. If none is provided, key will
|
106
|
+
be read from the AGENTOPS_APP_URL environment variable. Defaults to 'https://app.agentops.ai'.
|
107
|
+
max_wait_time (int, optional): The maximum time to wait in milliseconds before flushing the queue.
|
108
|
+
Defaults to 5,000 (5 seconds)
|
109
|
+
max_queue_size (int, optional): The maximum size of the event queue. Defaults to 512.
|
110
|
+
tags (List[str], optional): [Deprecated] Use `default_tags` instead.
|
111
|
+
default_tags (List[str], optional): Default tags for the sessions that can be used for grouping or sorting later (e.g. ["GPT-4"]).
|
112
|
+
trace_name (str, optional): Name for the default trace/session. If none is provided, defaults to "default".
|
113
|
+
instrument_llm_calls (bool): Whether to instrument LLM calls and emit LLMEvents.
|
114
|
+
auto_start_session (bool): Whether to start a session automatically when the client is created.
|
115
|
+
auto_init (bool): Whether to automatically initialize the client on import. Defaults to True.
|
116
|
+
skip_auto_end_session (optional, bool): Don't automatically end session based on your framework's decision-making
|
117
|
+
(i.e. Crew determining when tasks are complete and ending the session)
|
118
|
+
env_data_opt_out (bool): Whether to opt out of collecting environment data.
|
119
|
+
log_level (str, int): The log level to use for the client. Defaults to 'CRITICAL'.
|
120
|
+
fail_safe (bool): Whether to suppress errors and continue execution when possible.
|
121
|
+
log_session_replay_url (bool): Whether to log session replay URLs to the console. Defaults to True.
|
122
|
+
exporter_endpoint (str, optional): Endpoint for the exporter. If none is provided, key will
|
123
|
+
be read from the AGENTOPS_EXPORTER_ENDPOINT environment variable.
|
124
|
+
**kwargs: Additional configuration parameters to be passed to the client.
|
125
|
+
"""
|
126
|
+
global _client
|
127
|
+
|
128
|
+
# Check for deprecated parameters and emit warnings
|
129
|
+
if tags is not None:
|
130
|
+
warn_deprecated_param("tags", "default_tags")
|
131
|
+
|
132
|
+
# Merge tags and default_tags if both are provided
|
133
|
+
merged_tags = None
|
134
|
+
if tags and default_tags:
|
135
|
+
merged_tags = list(set(tags + default_tags))
|
136
|
+
elif tags:
|
137
|
+
merged_tags = tags
|
138
|
+
elif default_tags:
|
139
|
+
merged_tags = default_tags
|
140
|
+
|
141
|
+
# Check if in a Jupyter Notebook (manual start/end_trace())
|
142
|
+
try:
|
143
|
+
get_ipython().__class__.__name__ == "ZMQInteractiveShell" # type: ignore
|
144
|
+
auto_start_session = False
|
145
|
+
except NameError:
|
146
|
+
pass
|
147
|
+
|
148
|
+
# Prepare initialization arguments
|
149
|
+
init_kwargs = {
|
150
|
+
"api_key": api_key,
|
151
|
+
"endpoint": endpoint,
|
152
|
+
"app_url": app_url,
|
153
|
+
"max_wait_time": max_wait_time,
|
154
|
+
"max_queue_size": max_queue_size,
|
155
|
+
"default_tags": merged_tags,
|
156
|
+
"trace_name": trace_name,
|
157
|
+
"instrument_llm_calls": instrument_llm_calls,
|
158
|
+
"auto_start_session": auto_start_session,
|
159
|
+
"auto_init": auto_init,
|
160
|
+
"skip_auto_end_session": skip_auto_end_session,
|
161
|
+
"env_data_opt_out": env_data_opt_out,
|
162
|
+
"log_level": log_level,
|
163
|
+
"fail_safe": fail_safe,
|
164
|
+
"log_session_replay_url": log_session_replay_url,
|
165
|
+
"exporter_endpoint": exporter_endpoint,
|
166
|
+
**kwargs,
|
167
|
+
}
|
168
|
+
|
169
|
+
# Get the current client instance (creates new one if needed)
|
170
|
+
client = get_client()
|
171
|
+
|
172
|
+
# Initialize the client directly
|
173
|
+
return client.init(**init_kwargs)
|
174
|
+
|
175
|
+
|
176
|
+
def configure(**kwargs):
|
177
|
+
"""Update client configuration
|
178
|
+
|
179
|
+
Args:
|
180
|
+
**kwargs: Configuration parameters. Supported parameters include:
|
181
|
+
- api_key: API Key for AgentOps services
|
182
|
+
- endpoint: The endpoint for the AgentOps service
|
183
|
+
- app_url: The dashboard URL for the AgentOps app
|
184
|
+
- max_wait_time: Maximum time to wait in milliseconds before flushing the queue
|
185
|
+
- max_queue_size: Maximum size of the event queue
|
186
|
+
- default_tags: Default tags for the sessions
|
187
|
+
- instrument_llm_calls: Whether to instrument LLM calls
|
188
|
+
- auto_start_session: Whether to start a session automatically
|
189
|
+
- skip_auto_end_session: Don't automatically end session
|
190
|
+
- env_data_opt_out: Whether to opt out of collecting environment data
|
191
|
+
- log_level: The log level to use for the client
|
192
|
+
- fail_safe: Whether to suppress errors and continue execution
|
193
|
+
- exporter: Custom span exporter for OpenTelemetry trace data
|
194
|
+
- processor: Custom span processor for OpenTelemetry trace data
|
195
|
+
- exporter_endpoint: Endpoint for the exporter
|
196
|
+
"""
|
197
|
+
global _client
|
198
|
+
|
199
|
+
# List of valid parameters that can be passed to configure
|
200
|
+
valid_params = {
|
201
|
+
"api_key",
|
202
|
+
"endpoint",
|
203
|
+
"app_url",
|
204
|
+
"max_wait_time",
|
205
|
+
"max_queue_size",
|
206
|
+
"default_tags",
|
207
|
+
"instrument_llm_calls",
|
208
|
+
"auto_start_session",
|
209
|
+
"skip_auto_end_session",
|
210
|
+
"env_data_opt_out",
|
211
|
+
"log_level",
|
212
|
+
"fail_safe",
|
213
|
+
"exporter",
|
214
|
+
"processor",
|
215
|
+
"exporter_endpoint",
|
216
|
+
}
|
217
|
+
|
218
|
+
# Check for invalid parameters
|
219
|
+
invalid_params = set(kwargs.keys()) - valid_params
|
220
|
+
if invalid_params:
|
221
|
+
logger.warning(f"Invalid configuration parameters: {invalid_params}")
|
222
|
+
|
223
|
+
client = get_client()
|
224
|
+
client.configure(**kwargs)
|
225
|
+
|
226
|
+
|
227
|
+
def start_trace(
|
228
|
+
trace_name: str = "session", tags: Optional[Union[Dict[str, Any], List[str]]] = None
|
229
|
+
) -> Optional[TraceContext]:
|
230
|
+
"""
|
231
|
+
Starts a new trace (root span) and returns its context.
|
232
|
+
This allows for multiple concurrent, user-managed traces.
|
233
|
+
|
234
|
+
Args:
|
235
|
+
trace_name: Name for the trace (e.g., "session", "my_custom_task").
|
236
|
+
tags: Optional tags to attach to the trace span (list of strings or dict).
|
237
|
+
|
238
|
+
Returns:
|
239
|
+
A TraceContext object containing the span and context token, or None if SDK not initialized.
|
240
|
+
"""
|
241
|
+
if not tracer.initialized:
|
242
|
+
# Optionally, attempt to initialize the client if not already, or log a more severe warning.
|
243
|
+
# For now, align with legacy start_session that would try to init.
|
244
|
+
# However, explicit init is preferred before starting traces.
|
245
|
+
logger.warning("AgentOps SDK not initialized. Attempting to initialize with defaults before starting trace.")
|
246
|
+
try:
|
247
|
+
init() # Attempt to initialize with environment variables / defaults
|
248
|
+
if not tracer.initialized:
|
249
|
+
logger.error("SDK initialization failed. Cannot start trace.")
|
250
|
+
return None
|
251
|
+
except Exception as e:
|
252
|
+
logger.error(f"SDK auto-initialization failed during start_trace: {e}. Cannot start trace.")
|
253
|
+
return None
|
254
|
+
|
255
|
+
return tracer.start_trace(trace_name=trace_name, tags=tags)
|
256
|
+
|
257
|
+
|
258
|
+
def end_trace(
|
259
|
+
trace_context: Optional[TraceContext] = None, end_state: Union[TraceState, StatusCode, str] = TraceState.SUCCESS
|
260
|
+
) -> None:
|
261
|
+
"""
|
262
|
+
Ends a trace (its root span) and finalizes it.
|
263
|
+
If no trace_context is provided, ends all active session spans.
|
264
|
+
|
265
|
+
Args:
|
266
|
+
trace_context: The TraceContext object returned by start_trace. If None, ends all active traces.
|
267
|
+
end_state: The final state of the trace (e.g., "Success", "Indeterminate", "Error").
|
268
|
+
"""
|
269
|
+
if not tracer.initialized:
|
270
|
+
logger.warning("AgentOps SDK not initialized. Cannot end trace.")
|
271
|
+
return
|
272
|
+
tracer.end_trace(trace_context=trace_context, end_state=end_state)
|
273
|
+
|
274
|
+
|
275
|
+
def update_trace_metadata(metadata: Dict[str, Any], prefix: str = "trace.metadata") -> bool:
|
276
|
+
"""
|
277
|
+
Update metadata on the current running trace.
|
278
|
+
|
279
|
+
Args:
|
280
|
+
metadata: Dictionary of key-value pairs to set as trace metadata.
|
281
|
+
Values must be strings, numbers, booleans, or lists of these types.
|
282
|
+
Lists are converted to JSON string representation.
|
283
|
+
Keys can be either custom keys or semantic convention aliases.
|
284
|
+
prefix: Prefix for metadata attributes (default: "trace.metadata").
|
285
|
+
Ignored for semantic convention attributes.
|
286
|
+
|
287
|
+
Returns:
|
288
|
+
bool: True if metadata was successfully updated, False otherwise.
|
289
|
+
|
290
|
+
"""
|
291
|
+
if not tracer.initialized:
|
292
|
+
logger.warning("AgentOps SDK not initialized. Cannot update trace metadata.")
|
293
|
+
return False
|
294
|
+
|
295
|
+
# Build semantic convention mappings dynamically
|
296
|
+
def build_semconv_mappings():
|
297
|
+
"""Build mappings from user-friendly keys to semantic convention attributes."""
|
298
|
+
mappings = {}
|
299
|
+
|
300
|
+
# Helper function to extract attribute name from semantic convention
|
301
|
+
def extract_key_from_attr(attr_value: str) -> str:
|
302
|
+
parts = attr_value.split(".")
|
303
|
+
if len(parts) >= 2:
|
304
|
+
# Handle special cases
|
305
|
+
if parts[0] == "error":
|
306
|
+
# error.type -> error_type
|
307
|
+
return "_".join(parts)
|
308
|
+
else:
|
309
|
+
# Default: entity.attribute -> entity_attribute
|
310
|
+
return "_".join(parts)
|
311
|
+
return attr_value
|
312
|
+
|
313
|
+
# Process each semantic convention class
|
314
|
+
for cls in [AgentAttributes, ToolAttributes, WorkflowAttributes, CoreAttributes, SpanAttributes]:
|
315
|
+
for attr_name, attr_value in cls.__dict__.items():
|
316
|
+
if not attr_name.startswith("_") and isinstance(attr_value, str):
|
317
|
+
# Skip gen_ai attributes
|
318
|
+
if attr_value.startswith("gen_ai."):
|
319
|
+
continue
|
320
|
+
|
321
|
+
# Generate user-friendly key
|
322
|
+
user_key = extract_key_from_attr(attr_value)
|
323
|
+
mappings[user_key] = attr_value
|
324
|
+
|
325
|
+
# Add some additional convenience mappings
|
326
|
+
if attr_value == CoreAttributes.TAGS:
|
327
|
+
mappings["tags"] = attr_value
|
328
|
+
|
329
|
+
return mappings
|
330
|
+
|
331
|
+
# Build mappings if using semantic conventions
|
332
|
+
SEMCONV_MAPPINGS = build_semconv_mappings()
|
333
|
+
|
334
|
+
# Collect all valid semantic convention attributes
|
335
|
+
VALID_SEMCONV_ATTRS = set()
|
336
|
+
for cls in [AgentAttributes, ToolAttributes, WorkflowAttributes, CoreAttributes, SpanAttributes]:
|
337
|
+
for key, value in cls.__dict__.items():
|
338
|
+
if not key.startswith("_") and isinstance(value, str):
|
339
|
+
# Include all attributes except gen_ai ones
|
340
|
+
if not value.startswith("gen_ai."):
|
341
|
+
VALID_SEMCONV_ATTRS.add(value)
|
342
|
+
|
343
|
+
# Find the current trace span
|
344
|
+
span = None
|
345
|
+
|
346
|
+
# Get the current span from OpenTelemetry context
|
347
|
+
current_span = get_current_span()
|
348
|
+
|
349
|
+
# Check if the current span is valid and recording
|
350
|
+
if current_span and hasattr(current_span, "is_recording") and current_span.is_recording():
|
351
|
+
# Check if this is a trace/session span or a child span
|
352
|
+
span_name = getattr(current_span, "name", "")
|
353
|
+
|
354
|
+
# If it's a session/trace span, use it directly
|
355
|
+
if span_name.endswith(f".{SpanKind.SESSION}"):
|
356
|
+
span = current_span
|
357
|
+
else:
|
358
|
+
# It's a child span, try to find the root trace span
|
359
|
+
# Get all active traces
|
360
|
+
active_traces = tracer.get_active_traces()
|
361
|
+
if active_traces:
|
362
|
+
# Find the trace that contains the current span
|
363
|
+
current_trace_id = current_span.get_span_context().trace_id
|
364
|
+
|
365
|
+
for trace_id_str, trace_ctx in active_traces.items():
|
366
|
+
try:
|
367
|
+
# Convert hex string back to int for comparison
|
368
|
+
trace_id = int(trace_id_str, 16)
|
369
|
+
if trace_id == current_trace_id:
|
370
|
+
span = trace_ctx.span
|
371
|
+
break
|
372
|
+
except (ValueError, AttributeError):
|
373
|
+
continue
|
374
|
+
|
375
|
+
# If we couldn't find the parent trace, use the current span
|
376
|
+
if not span:
|
377
|
+
span = current_span
|
378
|
+
else:
|
379
|
+
# No active traces, use the current span
|
380
|
+
span = current_span
|
381
|
+
|
382
|
+
# If no current span or it's not recording, check active traces
|
383
|
+
if not span:
|
384
|
+
active_traces = tracer.get_active_traces()
|
385
|
+
if active_traces:
|
386
|
+
# Get the most recently created trace (last in the dict)
|
387
|
+
trace_context = list(active_traces.values())[-1]
|
388
|
+
span = trace_context.span
|
389
|
+
logger.debug("Using most recent active trace for metadata update")
|
390
|
+
else:
|
391
|
+
logger.warning("No active trace found. Cannot update metadata.")
|
392
|
+
return False
|
393
|
+
|
394
|
+
# Ensure the span is recording before updating
|
395
|
+
if not span or (hasattr(span, "is_recording") and not span.is_recording()):
|
396
|
+
logger.warning("Span is not recording. Cannot update metadata.")
|
397
|
+
return False
|
398
|
+
|
399
|
+
# Update the span attributes with the metadata
|
400
|
+
try:
|
401
|
+
updated_count = 0
|
402
|
+
for key, value in metadata.items():
|
403
|
+
# Validate the value type
|
404
|
+
if value is None:
|
405
|
+
continue
|
406
|
+
|
407
|
+
# Convert lists to JSON string representation for OpenTelemetry compatibility
|
408
|
+
if isinstance(value, list):
|
409
|
+
# Ensure all list items are valid types
|
410
|
+
if all(isinstance(item, (str, int, float, bool)) for item in value):
|
411
|
+
value = json.dumps(value)
|
412
|
+
else:
|
413
|
+
logger.warning(f"Skipping metadata key '{key}': list contains invalid types")
|
414
|
+
continue
|
415
|
+
elif not isinstance(value, (str, int, float, bool)):
|
416
|
+
logger.warning(f"Skipping metadata key '{key}': value type {type(value)} not supported")
|
417
|
+
continue
|
418
|
+
|
419
|
+
# Determine the attribute key
|
420
|
+
attribute_key = key
|
421
|
+
|
422
|
+
# Check if key is already a valid semantic convention attribute
|
423
|
+
if key in VALID_SEMCONV_ATTRS:
|
424
|
+
# Key is already a valid semantic convention, use as-is
|
425
|
+
attribute_key = key
|
426
|
+
elif key in SEMCONV_MAPPINGS:
|
427
|
+
# It's a user-friendly key, map it to semantic convention
|
428
|
+
attribute_key = SEMCONV_MAPPINGS[key]
|
429
|
+
logger.debug(f"Mapped '{key}' to semantic convention '{attribute_key}'")
|
430
|
+
else:
|
431
|
+
# Not a semantic convention, use with prefix
|
432
|
+
attribute_key = f"{prefix}.{key}"
|
433
|
+
|
434
|
+
# Set the attribute
|
435
|
+
span.set_attribute(attribute_key, value)
|
436
|
+
updated_count += 1
|
437
|
+
|
438
|
+
if updated_count > 0:
|
439
|
+
logger.debug(f"Successfully updated {updated_count} metadata attributes on trace")
|
440
|
+
return True
|
441
|
+
else:
|
442
|
+
logger.warning("No valid metadata attributes were updated")
|
443
|
+
return False
|
444
|
+
|
445
|
+
except Exception as e:
|
446
|
+
logger.error(f"Error updating trace metadata: {e}")
|
447
|
+
return False
|
448
|
+
|
449
|
+
|
450
|
+
__all__ = [
|
451
|
+
# Legacy exports
|
452
|
+
"start_session",
|
453
|
+
"end_session",
|
454
|
+
"track_agent",
|
455
|
+
"track_tool",
|
456
|
+
"end_all_sessions",
|
457
|
+
"Session",
|
458
|
+
"ToolEvent",
|
459
|
+
"ErrorEvent",
|
460
|
+
"ActionEvent",
|
461
|
+
"LLMEvent",
|
462
|
+
# Modern exports
|
463
|
+
"init",
|
464
|
+
"start_trace",
|
465
|
+
"end_trace",
|
466
|
+
"update_trace_metadata",
|
467
|
+
"Client",
|
468
|
+
"get_client",
|
469
|
+
# Decorators
|
470
|
+
"trace",
|
471
|
+
"session",
|
472
|
+
"agent",
|
473
|
+
"task",
|
474
|
+
"workflow",
|
475
|
+
"operation",
|
476
|
+
"tool",
|
477
|
+
"guardrail",
|
478
|
+
"track_endpoint",
|
479
|
+
# Enums
|
480
|
+
"TraceState",
|
481
|
+
"SUCCESS",
|
482
|
+
"ERROR",
|
483
|
+
"UNSET",
|
484
|
+
# Validation
|
485
|
+
"validate_trace_spans",
|
486
|
+
"print_validation_summary",
|
487
|
+
"ValidationError",
|
488
|
+
]
|
@@ -0,0 +1,71 @@
|
|
1
|
+
"""
|
2
|
+
API client for the AgentOps API.
|
3
|
+
|
4
|
+
This module provides the client for the AgentOps API.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from typing import Dict, Type, TypeVar, cast
|
8
|
+
|
9
|
+
from agentops.client.api.base import BaseApiClient
|
10
|
+
from agentops.client.api.types import AuthTokenResponse
|
11
|
+
from agentops.client.api.versions.v3 import V3Client
|
12
|
+
from agentops.client.api.versions.v4 import V4Client
|
13
|
+
|
14
|
+
# Define a type variable for client classes
|
15
|
+
T = TypeVar("T", bound=BaseApiClient)
|
16
|
+
|
17
|
+
__all__ = ["ApiClient", "BaseApiClient", "AuthTokenResponse"]
|
18
|
+
|
19
|
+
|
20
|
+
class ApiClient:
|
21
|
+
"""
|
22
|
+
Master API client that contains all version-specific clients.
|
23
|
+
|
24
|
+
This client provides a unified interface for accessing different API versions.
|
25
|
+
It lazily initializes version-specific clients when they are first accessed.
|
26
|
+
"""
|
27
|
+
|
28
|
+
def __init__(self, endpoint: str = "https://api.agentops.ai"):
|
29
|
+
"""
|
30
|
+
Initialize the master API client.
|
31
|
+
|
32
|
+
Args:
|
33
|
+
endpoint: The base URL for the API
|
34
|
+
"""
|
35
|
+
self.endpoint = endpoint
|
36
|
+
self._clients: Dict[str, BaseApiClient] = {}
|
37
|
+
|
38
|
+
@property
|
39
|
+
def v3(self) -> V3Client:
|
40
|
+
"""
|
41
|
+
Get the V3 API client.
|
42
|
+
|
43
|
+
Returns:
|
44
|
+
The V3 API client
|
45
|
+
"""
|
46
|
+
return self._get_client("v3", V3Client)
|
47
|
+
|
48
|
+
@property
|
49
|
+
def v4(self) -> V4Client:
|
50
|
+
"""
|
51
|
+
Get the V4 API client.
|
52
|
+
|
53
|
+
Returns:
|
54
|
+
The V4 API client
|
55
|
+
"""
|
56
|
+
return self._get_client("v4", V4Client)
|
57
|
+
|
58
|
+
def _get_client(self, version: str, client_class: Type[T]) -> T:
|
59
|
+
"""
|
60
|
+
Get or create a version-specific client.
|
61
|
+
|
62
|
+
Args:
|
63
|
+
version: The API version
|
64
|
+
client_class: The client class to instantiate
|
65
|
+
|
66
|
+
Returns:
|
67
|
+
The version-specific client
|
68
|
+
"""
|
69
|
+
if version not in self._clients:
|
70
|
+
self._clients[version] = client_class(self.endpoint)
|
71
|
+
return cast(T, self._clients[version])
|