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,486 @@
|
|
1
|
+
import inspect
|
2
|
+
import functools
|
3
|
+
import asyncio
|
4
|
+
from typing import Any, Dict, Callable, Optional, Union
|
5
|
+
|
6
|
+
|
7
|
+
import wrapt # type: ignore
|
8
|
+
|
9
|
+
from agentops.logging import logger
|
10
|
+
from agentops.sdk.core import TraceContext, tracer
|
11
|
+
from agentops.semconv.span_kinds import SpanKind
|
12
|
+
from agentops.semconv import SpanAttributes, CoreAttributes
|
13
|
+
|
14
|
+
from agentops.sdk.decorators.utility import (
|
15
|
+
_create_as_current_span,
|
16
|
+
_process_async_generator,
|
17
|
+
_process_sync_generator,
|
18
|
+
_record_entity_input,
|
19
|
+
_record_entity_output,
|
20
|
+
_extract_request_data,
|
21
|
+
_extract_response_data,
|
22
|
+
)
|
23
|
+
|
24
|
+
|
25
|
+
def create_entity_decorator(entity_kind: str) -> Callable[..., Any]:
|
26
|
+
"""
|
27
|
+
Factory that creates decorators for instrumenting functions and classes.
|
28
|
+
Handles different entity kinds (e.g., SESSION, TASK, HTTP) and function types (sync, async, generator).
|
29
|
+
"""
|
30
|
+
|
31
|
+
def decorator(
|
32
|
+
wrapped: Optional[Callable[..., Any]] = None,
|
33
|
+
*,
|
34
|
+
name: Optional[str] = None,
|
35
|
+
version: Optional[Any] = None,
|
36
|
+
tags: Optional[Union[list, dict]] = None,
|
37
|
+
cost=None,
|
38
|
+
spec=None,
|
39
|
+
capture_request: bool = True,
|
40
|
+
capture_response: bool = True,
|
41
|
+
) -> Callable[..., Any]:
|
42
|
+
if wrapped is None:
|
43
|
+
return functools.partial(
|
44
|
+
decorator,
|
45
|
+
name=name,
|
46
|
+
version=version,
|
47
|
+
tags=tags,
|
48
|
+
cost=cost,
|
49
|
+
spec=spec,
|
50
|
+
capture_request=capture_request,
|
51
|
+
capture_response=capture_response,
|
52
|
+
)
|
53
|
+
|
54
|
+
if inspect.isclass(wrapped):
|
55
|
+
# Class decoration wraps __init__ and aenter/aexit for context management.
|
56
|
+
# For SpanKind.SESSION, this creates a span for __init__ or async context, not instance lifetime.
|
57
|
+
class WrappedClass(wrapped):
|
58
|
+
def __init__(self, *args: Any, **kwargs: Any):
|
59
|
+
op_name = name or wrapped.__name__
|
60
|
+
self._agentops_span_context_manager = _create_as_current_span(op_name, entity_kind, version)
|
61
|
+
self._agentops_active_span = self._agentops_span_context_manager.__enter__()
|
62
|
+
try:
|
63
|
+
_record_entity_input(self._agentops_active_span, args, kwargs)
|
64
|
+
except Exception as e:
|
65
|
+
logger.warning(f"Failed to record entity input for class {op_name}: {e}")
|
66
|
+
super().__init__(*args, **kwargs)
|
67
|
+
|
68
|
+
def __del__(self):
|
69
|
+
"""Ensure span is properly ended when object is destroyed."""
|
70
|
+
if hasattr(self, "_agentops_span_context_manager") and self._agentops_span_context_manager:
|
71
|
+
try:
|
72
|
+
self._agentops_span_context_manager.__exit__(None, None, None)
|
73
|
+
except Exception:
|
74
|
+
pass
|
75
|
+
|
76
|
+
async def __aenter__(self) -> "WrappedClass":
|
77
|
+
if hasattr(self, "_agentops_active_span") and self._agentops_active_span is not None:
|
78
|
+
return self
|
79
|
+
op_name = name or wrapped.__name__
|
80
|
+
self._agentops_span_context_manager = _create_as_current_span(op_name, entity_kind, version)
|
81
|
+
self._agentops_active_span = self._agentops_span_context_manager.__enter__()
|
82
|
+
return self
|
83
|
+
|
84
|
+
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
85
|
+
if hasattr(self, "_agentops_active_span") and hasattr(self, "_agentops_span_context_manager"):
|
86
|
+
try:
|
87
|
+
_record_entity_output(self._agentops_active_span, self)
|
88
|
+
except Exception as e:
|
89
|
+
logger.warning(f"Failed to record entity output for class instance: {e}")
|
90
|
+
self._agentops_span_context_manager.__exit__(exc_type, exc_val, exc_tb)
|
91
|
+
self._agentops_span_context_manager = None
|
92
|
+
self._agentops_active_span = None
|
93
|
+
|
94
|
+
WrappedClass.__name__ = wrapped.__name__
|
95
|
+
WrappedClass.__qualname__ = wrapped.__qualname__
|
96
|
+
WrappedClass.__module__ = wrapped.__module__
|
97
|
+
WrappedClass.__doc__ = wrapped.__doc__
|
98
|
+
return WrappedClass
|
99
|
+
|
100
|
+
@wrapt.decorator
|
101
|
+
def wrapper(
|
102
|
+
wrapped_func: Callable[..., Any], instance: Optional[Any], args: tuple, kwargs: Dict[str, Any]
|
103
|
+
) -> Any:
|
104
|
+
if not tracer.initialized:
|
105
|
+
return wrapped_func(*args, **kwargs)
|
106
|
+
|
107
|
+
operation_name = name or wrapped_func.__name__
|
108
|
+
is_async = asyncio.iscoroutinefunction(wrapped_func)
|
109
|
+
is_generator = inspect.isgeneratorfunction(wrapped_func)
|
110
|
+
is_async_generator = inspect.isasyncgenfunction(wrapped_func)
|
111
|
+
|
112
|
+
# Special handling for HTTP entity kind
|
113
|
+
if entity_kind == SpanKind.HTTP:
|
114
|
+
if is_generator or is_async_generator:
|
115
|
+
logger.warning(
|
116
|
+
f"@track_endpoint on generator '{operation_name}' is not supported. Use @trace instead."
|
117
|
+
)
|
118
|
+
return wrapped_func(*args, **kwargs)
|
119
|
+
|
120
|
+
if is_async:
|
121
|
+
|
122
|
+
async def _wrapped_http_async() -> Any:
|
123
|
+
trace_context: Optional[TraceContext] = None
|
124
|
+
try:
|
125
|
+
# Create main session span
|
126
|
+
trace_context = tracer.start_trace(trace_name=operation_name, tags=tags)
|
127
|
+
if not trace_context:
|
128
|
+
logger.error(
|
129
|
+
f"Failed to start trace for @track_endpoint '{operation_name}'. Executing without trace."
|
130
|
+
)
|
131
|
+
return await wrapped_func(*args, **kwargs)
|
132
|
+
|
133
|
+
# Create HTTP request span
|
134
|
+
if capture_request:
|
135
|
+
with _create_as_current_span(
|
136
|
+
f"{operation_name}.request",
|
137
|
+
SpanKind.HTTP,
|
138
|
+
version=version,
|
139
|
+
attributes={SpanAttributes.HTTP_METHOD: "REQUEST"}
|
140
|
+
if SpanAttributes.HTTP_METHOD
|
141
|
+
else None,
|
142
|
+
) as request_span:
|
143
|
+
try:
|
144
|
+
request_data = _extract_request_data()
|
145
|
+
if request_data:
|
146
|
+
# Set HTTP attributes
|
147
|
+
if hasattr(SpanAttributes, "HTTP_METHOD") and request_data.get("method"):
|
148
|
+
request_span.set_attribute(
|
149
|
+
SpanAttributes.HTTP_METHOD, request_data["method"]
|
150
|
+
)
|
151
|
+
if hasattr(SpanAttributes, "HTTP_URL") and request_data.get("url"):
|
152
|
+
request_span.set_attribute(SpanAttributes.HTTP_URL, request_data["url"])
|
153
|
+
|
154
|
+
# Record the full request data
|
155
|
+
_record_entity_input(request_span, (request_data,), {})
|
156
|
+
except Exception as e:
|
157
|
+
logger.warning(f"Failed to record HTTP request for '{operation_name}': {e}")
|
158
|
+
|
159
|
+
# Execute the main function
|
160
|
+
result = await wrapped_func(*args, **kwargs)
|
161
|
+
|
162
|
+
# Create HTTP response span
|
163
|
+
if capture_response:
|
164
|
+
with _create_as_current_span(
|
165
|
+
f"{operation_name}.response",
|
166
|
+
SpanKind.HTTP,
|
167
|
+
version=version,
|
168
|
+
attributes={SpanAttributes.HTTP_METHOD: "RESPONSE"}
|
169
|
+
if SpanAttributes.HTTP_METHOD
|
170
|
+
else None,
|
171
|
+
) as response_span:
|
172
|
+
try:
|
173
|
+
response_data = _extract_response_data(result)
|
174
|
+
if response_data:
|
175
|
+
# Set HTTP attributes
|
176
|
+
if hasattr(SpanAttributes, "HTTP_STATUS_CODE") and response_data.get(
|
177
|
+
"status_code"
|
178
|
+
):
|
179
|
+
response_span.set_attribute(
|
180
|
+
SpanAttributes.HTTP_STATUS_CODE, response_data["status_code"]
|
181
|
+
)
|
182
|
+
|
183
|
+
# Record the full response data
|
184
|
+
_record_entity_output(response_span, response_data)
|
185
|
+
except Exception as e:
|
186
|
+
logger.warning(f"Failed to record HTTP response for '{operation_name}': {e}")
|
187
|
+
|
188
|
+
tracer.end_trace(trace_context, "Success")
|
189
|
+
return result
|
190
|
+
except Exception:
|
191
|
+
if trace_context:
|
192
|
+
tracer.end_trace(trace_context, "Indeterminate")
|
193
|
+
raise
|
194
|
+
finally:
|
195
|
+
if trace_context and trace_context.span.is_recording():
|
196
|
+
logger.warning(
|
197
|
+
f"Trace for @track_endpoint '{operation_name}' not explicitly ended. Ending as 'Unknown'."
|
198
|
+
)
|
199
|
+
tracer.end_trace(trace_context, "Unknown")
|
200
|
+
|
201
|
+
return _wrapped_http_async()
|
202
|
+
else: # Sync function for HTTP
|
203
|
+
trace_context: Optional[TraceContext] = None
|
204
|
+
try:
|
205
|
+
# Create main session span
|
206
|
+
trace_context = tracer.start_trace(trace_name=operation_name, tags=tags)
|
207
|
+
if not trace_context:
|
208
|
+
logger.error(
|
209
|
+
f"Failed to start trace for @track_endpoint '{operation_name}'. Executing without trace."
|
210
|
+
)
|
211
|
+
return wrapped_func(*args, **kwargs)
|
212
|
+
|
213
|
+
# Create HTTP request span
|
214
|
+
if capture_request:
|
215
|
+
with _create_as_current_span(
|
216
|
+
f"{operation_name}.request",
|
217
|
+
SpanKind.HTTP,
|
218
|
+
version=version,
|
219
|
+
attributes={SpanAttributes.HTTP_METHOD: "REQUEST"}
|
220
|
+
if SpanAttributes.HTTP_METHOD
|
221
|
+
else None,
|
222
|
+
) as request_span:
|
223
|
+
try:
|
224
|
+
request_data = _extract_request_data()
|
225
|
+
if request_data:
|
226
|
+
# Set HTTP attributes
|
227
|
+
if hasattr(SpanAttributes, "HTTP_METHOD") and request_data.get("method"):
|
228
|
+
request_span.set_attribute(
|
229
|
+
SpanAttributes.HTTP_METHOD, request_data["method"]
|
230
|
+
)
|
231
|
+
if hasattr(SpanAttributes, "HTTP_URL") and request_data.get("url"):
|
232
|
+
request_span.set_attribute(SpanAttributes.HTTP_URL, request_data["url"])
|
233
|
+
|
234
|
+
# Record the full request data
|
235
|
+
_record_entity_input(request_span, (request_data,), {})
|
236
|
+
except Exception as e:
|
237
|
+
logger.warning(f"Failed to record HTTP request for '{operation_name}': {e}")
|
238
|
+
|
239
|
+
# Execute the main function
|
240
|
+
result = wrapped_func(*args, **kwargs)
|
241
|
+
|
242
|
+
# Create HTTP response span
|
243
|
+
if capture_response:
|
244
|
+
with _create_as_current_span(
|
245
|
+
f"{operation_name}.response",
|
246
|
+
SpanKind.HTTP,
|
247
|
+
version=version,
|
248
|
+
attributes={SpanAttributes.HTTP_METHOD: "RESPONSE"}
|
249
|
+
if SpanAttributes.HTTP_METHOD
|
250
|
+
else None,
|
251
|
+
) as response_span:
|
252
|
+
try:
|
253
|
+
response_data = _extract_response_data(result)
|
254
|
+
if response_data:
|
255
|
+
# Set HTTP attributes
|
256
|
+
if hasattr(SpanAttributes, "HTTP_STATUS_CODE") and response_data.get(
|
257
|
+
"status_code"
|
258
|
+
):
|
259
|
+
response_span.set_attribute(
|
260
|
+
SpanAttributes.HTTP_STATUS_CODE, response_data["status_code"]
|
261
|
+
)
|
262
|
+
|
263
|
+
# Record the full response data
|
264
|
+
_record_entity_output(response_span, response_data)
|
265
|
+
except Exception as e:
|
266
|
+
logger.warning(f"Failed to record HTTP response for '{operation_name}': {e}")
|
267
|
+
|
268
|
+
tracer.end_trace(trace_context, "Success")
|
269
|
+
return result
|
270
|
+
except Exception:
|
271
|
+
if trace_context:
|
272
|
+
tracer.end_trace(trace_context, "Indeterminate")
|
273
|
+
raise
|
274
|
+
finally:
|
275
|
+
if trace_context and trace_context.span.is_recording():
|
276
|
+
logger.warning(
|
277
|
+
f"Trace for @track_endpoint '{operation_name}' not explicitly ended. Ending as 'Unknown'."
|
278
|
+
)
|
279
|
+
tracer.end_trace(trace_context, "Unknown")
|
280
|
+
|
281
|
+
elif entity_kind == SpanKind.SESSION:
|
282
|
+
if is_generator or is_async_generator:
|
283
|
+
logger.warning(
|
284
|
+
f"@agentops.trace on generator '{operation_name}' creates a single span, not a full trace."
|
285
|
+
)
|
286
|
+
# Fallthrough to existing generator logic which creates a single span.
|
287
|
+
|
288
|
+
# !! was previously not implemented, checking with @dwij if this was intentional or if my implementation should go in
|
289
|
+
if is_generator:
|
290
|
+
span, _, token = tracer.make_span(
|
291
|
+
operation_name,
|
292
|
+
entity_kind,
|
293
|
+
version=version,
|
294
|
+
attributes={CoreAttributes.TAGS: tags} if tags else None,
|
295
|
+
)
|
296
|
+
try:
|
297
|
+
_record_entity_input(span, args, kwargs, entity_kind=entity_kind)
|
298
|
+
except Exception as e:
|
299
|
+
logger.warning(f"Input recording failed for '{operation_name}': {e}")
|
300
|
+
result = wrapped_func(*args, **kwargs)
|
301
|
+
return _process_sync_generator(span, result)
|
302
|
+
elif is_async_generator:
|
303
|
+
span, _, token = tracer.make_span(
|
304
|
+
operation_name,
|
305
|
+
entity_kind,
|
306
|
+
version=version,
|
307
|
+
attributes={CoreAttributes.TAGS: tags} if tags else None,
|
308
|
+
)
|
309
|
+
try:
|
310
|
+
_record_entity_input(span, args, kwargs, entity_kind=entity_kind)
|
311
|
+
except Exception as e:
|
312
|
+
logger.warning(f"Input recording failed for '{operation_name}': {e}")
|
313
|
+
result = wrapped_func(*args, **kwargs)
|
314
|
+
return _process_async_generator(span, token, result)
|
315
|
+
elif is_async:
|
316
|
+
|
317
|
+
async def _wrapped_session_async() -> Any:
|
318
|
+
trace_context: Optional[TraceContext] = None
|
319
|
+
try:
|
320
|
+
trace_context = tracer.start_trace(trace_name=operation_name, tags=tags)
|
321
|
+
if not trace_context:
|
322
|
+
logger.error(
|
323
|
+
f"Failed to start trace for @trace '{operation_name}'. Executing without trace."
|
324
|
+
)
|
325
|
+
return await wrapped_func(*args, **kwargs)
|
326
|
+
try:
|
327
|
+
_record_entity_input(trace_context.span, args, kwargs)
|
328
|
+
except Exception as e:
|
329
|
+
logger.warning(f"Input recording failed for @trace '{operation_name}': {e}")
|
330
|
+
result = await wrapped_func(*args, **kwargs)
|
331
|
+
try:
|
332
|
+
_record_entity_output(trace_context.span, result)
|
333
|
+
except Exception as e:
|
334
|
+
logger.warning(f"Output recording failed for @trace '{operation_name}': {e}")
|
335
|
+
tracer.end_trace(trace_context, "Success")
|
336
|
+
return result
|
337
|
+
except Exception:
|
338
|
+
if trace_context:
|
339
|
+
tracer.end_trace(trace_context, "Indeterminate")
|
340
|
+
raise
|
341
|
+
finally:
|
342
|
+
if trace_context and trace_context.span.is_recording():
|
343
|
+
logger.warning(
|
344
|
+
f"Trace for @trace '{operation_name}' not explicitly ended. Ending as 'Unknown'."
|
345
|
+
)
|
346
|
+
tracer.end_trace(trace_context, "Unknown")
|
347
|
+
|
348
|
+
return _wrapped_session_async()
|
349
|
+
else: # Sync function for SpanKind.SESSION
|
350
|
+
trace_context: Optional[TraceContext] = None
|
351
|
+
try:
|
352
|
+
trace_context = tracer.start_trace(trace_name=operation_name, tags=tags)
|
353
|
+
if not trace_context:
|
354
|
+
logger.error(
|
355
|
+
f"Failed to start trace for @trace '{operation_name}'. Executing without trace."
|
356
|
+
)
|
357
|
+
return wrapped_func(*args, **kwargs)
|
358
|
+
try:
|
359
|
+
_record_entity_input(trace_context.span, args, kwargs)
|
360
|
+
except Exception as e:
|
361
|
+
logger.warning(f"Input recording failed for @trace '{operation_name}': {e}")
|
362
|
+
result = wrapped_func(*args, **kwargs)
|
363
|
+
try:
|
364
|
+
_record_entity_output(trace_context.span, result)
|
365
|
+
except Exception as e:
|
366
|
+
logger.warning(f"Output recording failed for @trace '{operation_name}': {e}")
|
367
|
+
tracer.end_trace(trace_context, "Success")
|
368
|
+
return result
|
369
|
+
except Exception:
|
370
|
+
if trace_context:
|
371
|
+
tracer.end_trace(trace_context, "Indeterminate")
|
372
|
+
raise
|
373
|
+
finally:
|
374
|
+
if trace_context and trace_context.span.is_recording():
|
375
|
+
logger.warning(
|
376
|
+
f"Trace for @trace '{operation_name}' not explicitly ended. Ending as 'Unknown'."
|
377
|
+
)
|
378
|
+
tracer.end_trace(trace_context, "Unknown")
|
379
|
+
|
380
|
+
# Logic for non-SESSION kinds or generators under @trace (as per fallthrough)
|
381
|
+
elif is_generator:
|
382
|
+
span, _, token = tracer.make_span(
|
383
|
+
operation_name,
|
384
|
+
entity_kind,
|
385
|
+
version=version,
|
386
|
+
attributes={CoreAttributes.TAGS: tags} if tags else None,
|
387
|
+
)
|
388
|
+
try:
|
389
|
+
_record_entity_input(span, args, kwargs, entity_kind=entity_kind)
|
390
|
+
# Set cost attribute if tool
|
391
|
+
if entity_kind == "tool" and cost is not None:
|
392
|
+
span.set_attribute(SpanAttributes.LLM_USAGE_TOOL_COST, cost)
|
393
|
+
# Set spec attribute if guardrail
|
394
|
+
if entity_kind == "guardrail" and (spec == "input" or spec == "output"):
|
395
|
+
span.set_attribute(SpanAttributes.AGENTOPS_DECORATOR_SPEC.format(entity_kind=entity_kind), spec)
|
396
|
+
except Exception as e:
|
397
|
+
logger.warning(f"Input recording failed for '{operation_name}': {e}")
|
398
|
+
result = wrapped_func(*args, **kwargs)
|
399
|
+
return _process_sync_generator(span, result)
|
400
|
+
elif is_async_generator:
|
401
|
+
span, _, token = tracer.make_span(
|
402
|
+
operation_name,
|
403
|
+
entity_kind,
|
404
|
+
version=version,
|
405
|
+
attributes={CoreAttributes.TAGS: tags} if tags else None,
|
406
|
+
)
|
407
|
+
try:
|
408
|
+
_record_entity_input(span, args, kwargs, entity_kind=entity_kind)
|
409
|
+
# Set cost attribute if tool
|
410
|
+
if entity_kind == "tool" and cost is not None:
|
411
|
+
span.set_attribute(SpanAttributes.LLM_USAGE_TOOL_COST, cost)
|
412
|
+
# Set spec attribute if guardrail
|
413
|
+
if entity_kind == "guardrail" and (spec == "input" or spec == "output"):
|
414
|
+
span.set_attribute(SpanAttributes.AGENTOPS_DECORATOR_SPEC.format(entity_kind=entity_kind), spec)
|
415
|
+
except Exception as e:
|
416
|
+
logger.warning(f"Input recording failed for '{operation_name}': {e}")
|
417
|
+
result = wrapped_func(*args, **kwargs)
|
418
|
+
return _process_async_generator(span, token, result)
|
419
|
+
elif is_async:
|
420
|
+
|
421
|
+
async def _wrapped_async() -> Any:
|
422
|
+
with _create_as_current_span(
|
423
|
+
operation_name,
|
424
|
+
entity_kind,
|
425
|
+
version=version,
|
426
|
+
attributes={CoreAttributes.TAGS: tags} if tags else None,
|
427
|
+
) as span:
|
428
|
+
try:
|
429
|
+
_record_entity_input(span, args, kwargs, entity_kind=entity_kind)
|
430
|
+
# Set cost attribute if tool
|
431
|
+
if entity_kind == "tool" and cost is not None:
|
432
|
+
span.set_attribute(SpanAttributes.LLM_USAGE_TOOL_COST, cost)
|
433
|
+
# Set spec attribute if guardrail
|
434
|
+
if entity_kind == "guardrail" and (spec == "input" or spec == "output"):
|
435
|
+
span.set_attribute(
|
436
|
+
SpanAttributes.AGENTOPS_DECORATOR_SPEC.format(entity_kind=entity_kind), spec
|
437
|
+
)
|
438
|
+
except Exception as e:
|
439
|
+
logger.warning(f"Input recording failed for '{operation_name}': {e}")
|
440
|
+
try:
|
441
|
+
result = await wrapped_func(*args, **kwargs)
|
442
|
+
try:
|
443
|
+
_record_entity_output(span, result, entity_kind=entity_kind)
|
444
|
+
except Exception as e:
|
445
|
+
logger.warning(f"Output recording failed for '{operation_name}': {e}")
|
446
|
+
return result
|
447
|
+
except Exception as e:
|
448
|
+
logger.error(f"Error in async function execution: {e}")
|
449
|
+
span.record_exception(e)
|
450
|
+
raise
|
451
|
+
|
452
|
+
return _wrapped_async()
|
453
|
+
else: # Sync function for non-SESSION kinds
|
454
|
+
with _create_as_current_span(
|
455
|
+
operation_name,
|
456
|
+
entity_kind,
|
457
|
+
version=version,
|
458
|
+
attributes={CoreAttributes.TAGS: tags} if tags else None,
|
459
|
+
) as span:
|
460
|
+
try:
|
461
|
+
_record_entity_input(span, args, kwargs, entity_kind=entity_kind)
|
462
|
+
# Set cost attribute if tool
|
463
|
+
if entity_kind == "tool" and cost is not None:
|
464
|
+
span.set_attribute(SpanAttributes.LLM_USAGE_TOOL_COST, cost)
|
465
|
+
# Set spec attribute if guardrail
|
466
|
+
if entity_kind == "guardrail" and (spec == "input" or spec == "output"):
|
467
|
+
span.set_attribute(
|
468
|
+
SpanAttributes.AGENTOPS_DECORATOR_SPEC.format(entity_kind=entity_kind), spec
|
469
|
+
)
|
470
|
+
except Exception as e:
|
471
|
+
logger.warning(f"Input recording failed for '{operation_name}': {e}")
|
472
|
+
try:
|
473
|
+
result = wrapped_func(*args, **kwargs)
|
474
|
+
try:
|
475
|
+
_record_entity_output(span, result, entity_kind=entity_kind)
|
476
|
+
except Exception as e:
|
477
|
+
logger.warning(f"Output recording failed for '{operation_name}': {e}")
|
478
|
+
return result
|
479
|
+
except Exception as e:
|
480
|
+
logger.error(f"Error in sync function execution: {e}")
|
481
|
+
span.record_exception(e)
|
482
|
+
raise
|
483
|
+
|
484
|
+
return wrapper(wrapped)
|
485
|
+
|
486
|
+
return decorator
|