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,216 @@
|
|
1
|
+
import types
|
2
|
+
from contextlib import contextmanager
|
3
|
+
from typing import Any, Dict, Generator, Optional
|
4
|
+
|
5
|
+
from opentelemetry import context as context_api
|
6
|
+
from opentelemetry import trace
|
7
|
+
from opentelemetry.context import attach, set_value
|
8
|
+
from opentelemetry.trace import Span
|
9
|
+
|
10
|
+
from agentops.helpers.serialization import safe_serialize
|
11
|
+
from agentops.logging import logger
|
12
|
+
from agentops.sdk.core import tracer
|
13
|
+
from agentops.semconv.span_attributes import SpanAttributes
|
14
|
+
|
15
|
+
"""
|
16
|
+
!! NOTE !!
|
17
|
+
References to SpanKind, span_kind, etc. are NOT destined towards `span.kind`,
|
18
|
+
but instead used as an `agentops.semconv.span_attributes.AGENTOPS_SPAN_KIND`
|
19
|
+
"""
|
20
|
+
|
21
|
+
|
22
|
+
def set_workflow_name(workflow_name: str) -> None:
|
23
|
+
attach(set_value("workflow_name", workflow_name))
|
24
|
+
|
25
|
+
|
26
|
+
def set_entity_path(entity_path: str) -> None:
|
27
|
+
attach(set_value("entity_path", entity_path))
|
28
|
+
|
29
|
+
|
30
|
+
# Helper functions for content management
|
31
|
+
|
32
|
+
|
33
|
+
def _check_content_size(content_json: str) -> bool:
|
34
|
+
"""Verify that a JSON string is within acceptable size limits (1MB)"""
|
35
|
+
return len(content_json) < 1_000_000
|
36
|
+
|
37
|
+
|
38
|
+
def _process_sync_generator(span: trace.Span, generator: types.GeneratorType):
|
39
|
+
"""Process a synchronous generator and manage its span lifecycle"""
|
40
|
+
# Ensure span context is attached to the generator context
|
41
|
+
context_api.attach(trace.set_span_in_context(span))
|
42
|
+
|
43
|
+
# Yield from the generator while maintaining span context
|
44
|
+
yield from generator
|
45
|
+
|
46
|
+
# End the span when generator is exhausted
|
47
|
+
span.end()
|
48
|
+
# No detach because of OpenTelemetry issue #2606
|
49
|
+
# Context will be detached during garbage collection
|
50
|
+
|
51
|
+
|
52
|
+
async def _process_async_generator(span: trace.Span, context_token: Any, generator: types.AsyncGeneratorType):
|
53
|
+
"""Process an asynchronous generator and manage its span lifecycle"""
|
54
|
+
try:
|
55
|
+
async for item in generator:
|
56
|
+
yield item
|
57
|
+
finally:
|
58
|
+
# Always ensure span is ended and context detached
|
59
|
+
span.end()
|
60
|
+
context_api.detach(context_token)
|
61
|
+
|
62
|
+
|
63
|
+
def _get_current_span_info():
|
64
|
+
"""Helper to get information about the current span for debugging"""
|
65
|
+
current_span = trace.get_current_span()
|
66
|
+
if hasattr(current_span, "get_span_context"):
|
67
|
+
ctx = current_span.get_span_context()
|
68
|
+
return {
|
69
|
+
"span_id": f"{ctx.span_id:x}" if hasattr(ctx, "span_id") else "None",
|
70
|
+
"trace_id": f"{ctx.trace_id:x}" if hasattr(ctx, "trace_id") else "None",
|
71
|
+
"name": getattr(current_span, "name", "Unknown"),
|
72
|
+
"is_recording": getattr(current_span, "is_recording", False),
|
73
|
+
}
|
74
|
+
return {"name": "No current span"}
|
75
|
+
|
76
|
+
|
77
|
+
@contextmanager
|
78
|
+
def _create_as_current_span(
|
79
|
+
operation_name: str, span_kind: str, version: Optional[int] = None, attributes: Optional[Dict[str, Any]] = None
|
80
|
+
) -> Generator[Span, None, None]:
|
81
|
+
"""
|
82
|
+
Create and yield an instrumentation span as the current span using proper context management.
|
83
|
+
|
84
|
+
This function creates a span that will automatically be nested properly
|
85
|
+
within any parent span based on the current execution context, using OpenTelemetry's
|
86
|
+
context management to properly handle span lifecycle.
|
87
|
+
|
88
|
+
Args:
|
89
|
+
operation_name: Name of the operation being traced
|
90
|
+
span_kind: Type of operation (from SpanKind)
|
91
|
+
version: Optional version identifier for the operation
|
92
|
+
attributes: Optional dictionary of attributes to set on the span
|
93
|
+
|
94
|
+
Yields:
|
95
|
+
A span with proper context that will be automatically closed when exiting the context
|
96
|
+
"""
|
97
|
+
# Log before we do anything
|
98
|
+
before_span = _get_current_span_info()
|
99
|
+
logger.debug(f"[DEBUG] BEFORE {operation_name}.{span_kind} - Current context: {before_span}")
|
100
|
+
|
101
|
+
# Create span with proper naming convention
|
102
|
+
span_name = f"{operation_name}.{span_kind}"
|
103
|
+
|
104
|
+
# Get tracer
|
105
|
+
otel_tracer = tracer.get_tracer()
|
106
|
+
|
107
|
+
# Prepare attributes
|
108
|
+
if attributes is None:
|
109
|
+
attributes = {}
|
110
|
+
|
111
|
+
# Add span kind to attributes
|
112
|
+
attributes[SpanAttributes.AGENTOPS_SPAN_KIND] = span_kind
|
113
|
+
|
114
|
+
# Add standard attributes
|
115
|
+
attributes[SpanAttributes.OPERATION_NAME] = operation_name
|
116
|
+
if version is not None:
|
117
|
+
attributes[SpanAttributes.OPERATION_VERSION] = version
|
118
|
+
|
119
|
+
# Get current context explicitly to debug it
|
120
|
+
current_context = context_api.get_current()
|
121
|
+
|
122
|
+
# Use OpenTelemetry's context manager to properly handle span lifecycle
|
123
|
+
with otel_tracer.start_as_current_span(span_name, attributes=attributes, context=current_context) as span:
|
124
|
+
# Log after span creation
|
125
|
+
if hasattr(span, "get_span_context"):
|
126
|
+
span_ctx = span.get_span_context()
|
127
|
+
logger.debug(
|
128
|
+
f"[DEBUG] CREATED {span_name} - span_id: {span_ctx.span_id:x}, parent: {before_span.get('span_id', 'None')}"
|
129
|
+
)
|
130
|
+
|
131
|
+
yield span
|
132
|
+
|
133
|
+
# Log after we're done
|
134
|
+
after_span = _get_current_span_info()
|
135
|
+
logger.debug(f"[DEBUG] AFTER {operation_name}.{span_kind} - Returned to context: {after_span}")
|
136
|
+
|
137
|
+
|
138
|
+
def _record_entity_input(span: trace.Span, args: tuple, kwargs: Dict[str, Any], entity_kind: str = "entity") -> None:
|
139
|
+
"""Record operation input parameters to span if content tracing is enabled"""
|
140
|
+
try:
|
141
|
+
input_data = {"args": args, "kwargs": kwargs}
|
142
|
+
json_data = safe_serialize(input_data)
|
143
|
+
|
144
|
+
if _check_content_size(json_data):
|
145
|
+
span.set_attribute(SpanAttributes.AGENTOPS_DECORATOR_INPUT.format(entity_kind=entity_kind), json_data)
|
146
|
+
else:
|
147
|
+
logger.debug("Operation input exceeds size limit, not recording")
|
148
|
+
except Exception as err:
|
149
|
+
logger.warning(f"Failed to serialize operation input: {err}")
|
150
|
+
|
151
|
+
|
152
|
+
def _record_entity_output(span: trace.Span, result: Any, entity_kind: str = "entity") -> None:
|
153
|
+
"""Record operation output value to span if content tracing is enabled"""
|
154
|
+
try:
|
155
|
+
json_data = safe_serialize(result)
|
156
|
+
|
157
|
+
if _check_content_size(json_data):
|
158
|
+
span.set_attribute(SpanAttributes.AGENTOPS_DECORATOR_OUTPUT.format(entity_kind=entity_kind), json_data)
|
159
|
+
else:
|
160
|
+
logger.debug("Operation output exceeds size limit, not recording")
|
161
|
+
except Exception as err:
|
162
|
+
logger.warning(f"Failed to serialize operation output: {err}")
|
163
|
+
|
164
|
+
|
165
|
+
# Helper functions for HTTP request/response data extraction
|
166
|
+
|
167
|
+
|
168
|
+
def _extract_request_data():
|
169
|
+
"""Extract HTTP request data from the current web framework context."""
|
170
|
+
request_data = {}
|
171
|
+
|
172
|
+
try:
|
173
|
+
# Try to import Flask and get current request
|
174
|
+
from flask import request
|
175
|
+
|
176
|
+
request_data = {
|
177
|
+
"method": request.method,
|
178
|
+
"url": request.url,
|
179
|
+
"headers": dict(request.headers),
|
180
|
+
"args": dict(request.args),
|
181
|
+
"form": dict(request.form) if request.form else None,
|
182
|
+
"json": request.get_json(silent=True),
|
183
|
+
"data": request.get_data(as_text=True) if request.content_length else None,
|
184
|
+
}
|
185
|
+
except ImportError:
|
186
|
+
logger.debug("Flask not available for request data extraction")
|
187
|
+
except Exception as e:
|
188
|
+
logger.warning(f"Failed to extract request data: {e}")
|
189
|
+
|
190
|
+
return request_data
|
191
|
+
|
192
|
+
|
193
|
+
def _extract_response_data(response):
|
194
|
+
"""Extract HTTP response data from response object."""
|
195
|
+
response_data = {}
|
196
|
+
|
197
|
+
try:
|
198
|
+
# Handle Flask response objects
|
199
|
+
from flask import Response
|
200
|
+
|
201
|
+
if isinstance(response, Response):
|
202
|
+
response_data = {
|
203
|
+
"status_code": response.status_code,
|
204
|
+
"headers": dict(response.headers),
|
205
|
+
"data": response.get_data(as_text=True) if response.content_length else None,
|
206
|
+
}
|
207
|
+
else:
|
208
|
+
# Handle cases where response is just data (will be converted to Response by Flask)
|
209
|
+
response_data = {
|
210
|
+
"status_code": 200, # Default status for successful responses
|
211
|
+
"data": str(response) if response is not None else None,
|
212
|
+
}
|
213
|
+
except Exception as e:
|
214
|
+
logger.warning(f"Failed to extract response data: {e}")
|
215
|
+
|
216
|
+
return response_data
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# Define a separate class for the authenticated OTLP exporter
|
2
|
+
# This is imported conditionally to avoid dependency issues
|
3
|
+
from typing import Dict, Optional, Sequence
|
4
|
+
|
5
|
+
import requests
|
6
|
+
from opentelemetry.exporter.otlp.proto.http import Compression
|
7
|
+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
|
8
|
+
from opentelemetry.sdk.trace import ReadableSpan
|
9
|
+
from opentelemetry.sdk.trace.export import SpanExportResult
|
10
|
+
|
11
|
+
from agentops.exceptions import AgentOpsApiJwtExpiredException, ApiServerException
|
12
|
+
from agentops.logging import logger
|
13
|
+
|
14
|
+
|
15
|
+
class AuthenticatedOTLPExporter(OTLPSpanExporter):
|
16
|
+
"""
|
17
|
+
OTLP exporter with JWT authentication support.
|
18
|
+
|
19
|
+
This exporter automatically handles JWT authentication and token refresh
|
20
|
+
for telemetry data sent to the AgentOps API using a dedicated HTTP session
|
21
|
+
with authentication retry logic built in.
|
22
|
+
"""
|
23
|
+
|
24
|
+
def __init__(
|
25
|
+
self,
|
26
|
+
endpoint: str,
|
27
|
+
jwt: str,
|
28
|
+
headers: Optional[Dict[str, str]] = None,
|
29
|
+
timeout: Optional[int] = None,
|
30
|
+
compression: Optional[Compression] = None,
|
31
|
+
**kwargs,
|
32
|
+
):
|
33
|
+
# TODO: Implement re-authentication
|
34
|
+
# FIXME: endpoint here is not "endpoint" from config
|
35
|
+
# self._session = HttpClient.get_authenticated_session(endpoint, api_key)
|
36
|
+
|
37
|
+
# Initialize the parent class
|
38
|
+
super().__init__(
|
39
|
+
endpoint=endpoint,
|
40
|
+
headers={
|
41
|
+
"Authorization": f"Bearer {jwt}",
|
42
|
+
}, # Base headers
|
43
|
+
timeout=timeout,
|
44
|
+
compression=compression,
|
45
|
+
# session=self._session, # Use our authenticated session
|
46
|
+
)
|
47
|
+
|
48
|
+
def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult:
|
49
|
+
"""
|
50
|
+
Export spans with automatic authentication handling
|
51
|
+
|
52
|
+
The authentication and retry logic is now handled by the underlying
|
53
|
+
HTTP session adapter, so we just need to call the parent export method.
|
54
|
+
|
55
|
+
Args:
|
56
|
+
spans: The list of spans to export
|
57
|
+
|
58
|
+
Returns:
|
59
|
+
The result of the export
|
60
|
+
"""
|
61
|
+
try:
|
62
|
+
return super().export(spans)
|
63
|
+
except AgentOpsApiJwtExpiredException as e:
|
64
|
+
# Authentication token expired or invalid
|
65
|
+
logger.warning(f"Authentication error during span export: {e}")
|
66
|
+
return SpanExportResult.FAILURE
|
67
|
+
except ApiServerException as e:
|
68
|
+
# Server-side error
|
69
|
+
logger.error(f"API server error during span export: {e}")
|
70
|
+
return SpanExportResult.FAILURE
|
71
|
+
except requests.RequestException as e:
|
72
|
+
# Network or HTTP error
|
73
|
+
logger.error(f"Network error during span export: {e}")
|
74
|
+
return SpanExportResult.FAILURE
|
75
|
+
except Exception as e:
|
76
|
+
# Any other error
|
77
|
+
logger.error(f"Unexpected error during span export: {e}")
|
78
|
+
return SpanExportResult.FAILURE
|
79
|
+
|
80
|
+
def clear(self):
|
81
|
+
"""
|
82
|
+
Clear any stored spans.
|
83
|
+
|
84
|
+
This method is added for compatibility with test fixtures.
|
85
|
+
The OTLP exporter doesn't store spans, so this is a no-op.
|
86
|
+
"""
|
87
|
+
pass
|
@@ -0,0 +1,71 @@
|
|
1
|
+
"""
|
2
|
+
Span processors for AgentOps SDK.
|
3
|
+
|
4
|
+
This module contains processors for OpenTelemetry spans.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from typing import Optional
|
8
|
+
|
9
|
+
from opentelemetry.context import Context
|
10
|
+
from opentelemetry.sdk.trace import ReadableSpan, Span, SpanProcessor
|
11
|
+
|
12
|
+
from agentops.logging import logger, upload_logfile
|
13
|
+
|
14
|
+
|
15
|
+
class InternalSpanProcessor(SpanProcessor):
|
16
|
+
"""
|
17
|
+
A span processor that prints information about spans.
|
18
|
+
|
19
|
+
This processor is particularly useful for debugging and monitoring
|
20
|
+
as it prints information about spans as they are created and ended.
|
21
|
+
For session spans, it prints a URL to the AgentOps dashboard.
|
22
|
+
|
23
|
+
Note about span kinds:
|
24
|
+
- OpenTelemetry spans have a native 'kind' property (INTERNAL, CLIENT, CONSUMER, etc.)
|
25
|
+
- AgentOps also uses a semantic convention attribute AGENTOPS_SPAN_KIND for domain-specific kinds
|
26
|
+
- This processor tries to use the native kind first, then falls back to the attribute
|
27
|
+
"""
|
28
|
+
|
29
|
+
_root_span_id: Optional[int] = None
|
30
|
+
|
31
|
+
def on_start(self, span: Span, parent_context: Optional[Context] = None) -> None:
|
32
|
+
"""
|
33
|
+
Called when a span is started.
|
34
|
+
|
35
|
+
Args:
|
36
|
+
span: The span that was started.
|
37
|
+
parent_context: The parent context, if any.
|
38
|
+
"""
|
39
|
+
# Skip if span is not sampled
|
40
|
+
if not span.context or not span.context.trace_flags.sampled:
|
41
|
+
return
|
42
|
+
|
43
|
+
if not self._root_span_id:
|
44
|
+
self._root_span_id = span.context.span_id
|
45
|
+
logger.debug(f"[agentops.InternalSpanProcessor] Found root span: {span.name}")
|
46
|
+
|
47
|
+
def on_end(self, span: ReadableSpan) -> None:
|
48
|
+
"""
|
49
|
+
Called when a span is ended.
|
50
|
+
|
51
|
+
Args:
|
52
|
+
span: The span that was ended.
|
53
|
+
"""
|
54
|
+
# Skip if span is not sampled
|
55
|
+
if not span.context or not span.context.trace_flags.sampled:
|
56
|
+
return
|
57
|
+
|
58
|
+
if self._root_span_id and (span.context.span_id is self._root_span_id):
|
59
|
+
logger.debug(f"[agentops.InternalSpanProcessor] Ending root span: {span.name}")
|
60
|
+
try:
|
61
|
+
upload_logfile(span.context.trace_id)
|
62
|
+
except Exception as e:
|
63
|
+
logger.error(f"[agentops.InternalSpanProcessor] Error uploading logfile: {e}")
|
64
|
+
|
65
|
+
def shutdown(self) -> None:
|
66
|
+
"""Shutdown the processor."""
|
67
|
+
self._root_span_id = None
|
68
|
+
|
69
|
+
def force_flush(self, timeout_millis: int = 30000) -> bool:
|
70
|
+
"""Force flush the processor."""
|
71
|
+
return True
|
agentops/sdk/types.py
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
from typing import Annotated, Optional, TypedDict
|
2
|
+
|
3
|
+
from opentelemetry.sdk.trace import SpanProcessor
|
4
|
+
from opentelemetry.sdk.trace.export import SpanExporter
|
5
|
+
|
6
|
+
ISOTimeStamp = Annotated[str, "ISO 8601 formatted timestamp string (e.g. '2023-04-15T12:30:45.123456+00:00')"]
|
7
|
+
|
8
|
+
|
9
|
+
class TracingConfig(TypedDict, total=False):
|
10
|
+
"""Configuration for the tracing core."""
|
11
|
+
|
12
|
+
service_name: Optional[str]
|
13
|
+
exporter: Optional[SpanExporter]
|
14
|
+
processor: Optional[SpanProcessor]
|
15
|
+
exporter_endpoint: Optional[str]
|
16
|
+
metrics_endpoint: Optional[str]
|
17
|
+
api_key: Optional[str] # API key for authentication with AgentOps services
|
18
|
+
project_id: Optional[str] # Project ID to include in resource attributes
|
19
|
+
max_queue_size: int # Required with a default value
|
20
|
+
max_wait_time: int # Required with a default value
|
21
|
+
export_flush_interval: int # Time interval between automatic exports
|
@@ -0,0 +1,36 @@
|
|
1
|
+
"""AgentOps semantic conventions for spans."""
|
2
|
+
|
3
|
+
from agentops.semconv.span_kinds import SpanKind
|
4
|
+
from agentops.semconv.core import CoreAttributes
|
5
|
+
from agentops.semconv.agent import AgentAttributes
|
6
|
+
from agentops.semconv.tool import ToolAttributes
|
7
|
+
from agentops.semconv.status import ToolStatus
|
8
|
+
from agentops.semconv.workflow import WorkflowAttributes
|
9
|
+
from agentops.semconv.instrumentation import InstrumentationAttributes
|
10
|
+
from agentops.semconv.enum import LLMRequestTypeValues
|
11
|
+
from agentops.semconv.span_attributes import SpanAttributes
|
12
|
+
from agentops.semconv.meters import Meters
|
13
|
+
from agentops.semconv.span_kinds import AgentOpsSpanKindValues
|
14
|
+
from agentops.semconv.resource import ResourceAttributes
|
15
|
+
from agentops.semconv.message import MessageAttributes
|
16
|
+
from agentops.semconv.langchain import LangChainAttributes, LangChainAttributeValues
|
17
|
+
|
18
|
+
SUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION_KEY = "suppress_language_model_instrumentation"
|
19
|
+
__all__ = [
|
20
|
+
"SUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION_KEY",
|
21
|
+
"SpanKind",
|
22
|
+
"CoreAttributes",
|
23
|
+
"AgentAttributes",
|
24
|
+
"ToolAttributes",
|
25
|
+
"ToolStatus",
|
26
|
+
"WorkflowAttributes",
|
27
|
+
"InstrumentationAttributes",
|
28
|
+
"LLMRequestTypeValues",
|
29
|
+
"SpanAttributes",
|
30
|
+
"Meters",
|
31
|
+
"AgentOpsSpanKindValues",
|
32
|
+
"ResourceAttributes",
|
33
|
+
"MessageAttributes",
|
34
|
+
"LangChainAttributes",
|
35
|
+
"LangChainAttributeValues",
|
36
|
+
]
|
@@ -0,0 +1,29 @@
|
|
1
|
+
"""Attributes specific to agent spans."""
|
2
|
+
|
3
|
+
|
4
|
+
class AgentAttributes:
|
5
|
+
"""Attributes specific to agent spans."""
|
6
|
+
|
7
|
+
# Identity
|
8
|
+
AGENT_ID = "agent.id" # Unique identifier for the agent
|
9
|
+
AGENT_NAME = "agent.name" # Name of the agent
|
10
|
+
AGENT_ROLE = "agent.role" # Role of the agent
|
11
|
+
|
12
|
+
# Capabilities
|
13
|
+
AGENT_TOOLS = "agent.tools" # Tools available to the agent
|
14
|
+
AGENT_MODELS = "agent.models" # Models available to the agent
|
15
|
+
|
16
|
+
TOOLS = "tools"
|
17
|
+
HANDOFFS = "handoffs"
|
18
|
+
|
19
|
+
# NOTE: This attribute deviates from the OpenTelemetry GenAI semantic conventions.
|
20
|
+
# According to OpenTelemetry GenAI conventions, this should be named "gen_ai.agent.source"
|
21
|
+
# or follow a similar pattern under the "gen_ai" namespace.
|
22
|
+
FROM_AGENT = "from_agent"
|
23
|
+
|
24
|
+
# NOTE: This attribute deviates from the OpenTelemetry GenAI semantic conventions.
|
25
|
+
# According to OpenTelemetry GenAI conventions, this should be named "gen_ai.agent.destination"
|
26
|
+
# or follow a similar pattern under the "gen_ai" namespace.
|
27
|
+
TO_AGENT = "to_agent"
|
28
|
+
|
29
|
+
AGENT_REASONING = "agent.reasoning"
|
agentops/semconv/core.py
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
"""Core attributes applicable to all spans."""
|
2
|
+
|
3
|
+
|
4
|
+
class CoreAttributes:
|
5
|
+
"""Core attributes applicable to all spans."""
|
6
|
+
|
7
|
+
# Error attributes
|
8
|
+
ERROR_TYPE = "error.type" # Type of error if status is error
|
9
|
+
ERROR_MESSAGE = "error.message" # Error message if status is error
|
10
|
+
|
11
|
+
TAGS = "agentops.tags" # Tags passed to agentops.init
|
12
|
+
|
13
|
+
# Trace context attributes
|
14
|
+
TRACE_ID = "trace.id" # Trace ID
|
15
|
+
SPAN_ID = "span.id" # Span ID
|
16
|
+
PARENT_ID = "parent.id" # Parent ID
|
17
|
+
GROUP_ID = "group.id" # Group ID
|
18
|
+
|
19
|
+
# Note: WORKFLOW_NAME is defined in WorkflowAttributes to avoid duplication
|
agentops/semconv/enum.py
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
"""Attributes specific to instrumentation."""
|
2
|
+
|
3
|
+
|
4
|
+
class InstrumentationAttributes:
|
5
|
+
"""Instrumentation specific attributes."""
|
6
|
+
|
7
|
+
NAME = "instrumentation.name" # Name of the instrumentation
|
8
|
+
VERSION = "instrumentation.version" # Version of the instrumentation
|
9
|
+
|
10
|
+
LIBRARY_NAME = "library.name" # Name of the library
|
11
|
+
LIBRARY_VERSION = "library.version" # Version of the library
|
12
|
+
|
13
|
+
INSTRUMENTATION_TYPE = "instrumentation.type" # Type of instrumentation
|
@@ -0,0 +1,63 @@
|
|
1
|
+
"""Semantic conventions for LangChain instrumentation."""
|
2
|
+
|
3
|
+
|
4
|
+
class LangChainAttributeValues:
|
5
|
+
"""Standard values for LangChain attributes."""
|
6
|
+
|
7
|
+
CHAIN_KIND_SEQUENTIAL = "sequential"
|
8
|
+
CHAIN_KIND_LLM = "llm"
|
9
|
+
CHAIN_KIND_ROUTER = "router"
|
10
|
+
|
11
|
+
# Chat message roles
|
12
|
+
ROLE_SYSTEM = "system"
|
13
|
+
ROLE_USER = "user"
|
14
|
+
ROLE_ASSISTANT = "assistant"
|
15
|
+
ROLE_FUNCTION = "function"
|
16
|
+
ROLE_TOOL = "tool"
|
17
|
+
|
18
|
+
|
19
|
+
class LangChainAttributes:
|
20
|
+
"""
|
21
|
+
Attributes for LangChain instrumentation.
|
22
|
+
|
23
|
+
Note: LLM-specific attributes are derived from SpanAttributes to maintain
|
24
|
+
consistency across instrumentations.
|
25
|
+
"""
|
26
|
+
|
27
|
+
# Session attributes
|
28
|
+
SESSION_TAGS = "langchain.session.tags"
|
29
|
+
|
30
|
+
LLM_NAME = "langchain.llm.name"
|
31
|
+
LLM_MODEL = "langchain.llm.model"
|
32
|
+
|
33
|
+
# Chain attributes - specific to LangChain
|
34
|
+
CHAIN_NAME = "langchain.chain.name"
|
35
|
+
CHAIN_TYPE = "langchain.chain.type"
|
36
|
+
CHAIN_ERROR = "langchain.chain.error"
|
37
|
+
CHAIN_KIND = "langchain.chain.kind"
|
38
|
+
CHAIN_VERBOSE = "langchain.chain.verbose"
|
39
|
+
|
40
|
+
# Agent attributes - specific to LangChain agents
|
41
|
+
AGENT_ACTION_LOG = "langchain.agent.action.log"
|
42
|
+
AGENT_ACTION_INPUT = "langchain.agent.action.input"
|
43
|
+
AGENT_ACTION_TOOL = "langchain.agent.action.tool"
|
44
|
+
AGENT_FINISH_RETURN_VALUES = "langchain.agent.finish.return_values"
|
45
|
+
AGENT_FINISH_LOG = "langchain.agent.finish.log"
|
46
|
+
|
47
|
+
# Tool attributes - specific to LangChain tools
|
48
|
+
TOOL_NAME = "langchain.tool.name"
|
49
|
+
TOOL_INPUT = "langchain.tool.input"
|
50
|
+
TOOL_OUTPUT = "langchain.tool.output"
|
51
|
+
TOOL_DESCRIPTION = "langchain.tool.description"
|
52
|
+
TOOL_ERROR = "langchain.tool.error"
|
53
|
+
TOOL_ARGS_SCHEMA = "langchain.tool.args_schema"
|
54
|
+
TOOL_RETURN_DIRECT = "langchain.tool.return_direct"
|
55
|
+
|
56
|
+
# Chat attributes - specific to LangChain chat models
|
57
|
+
CHAT_MESSAGE_ROLES = "langchain.chat_message.roles"
|
58
|
+
CHAT_MODEL_TYPE = "langchain.chat_model.type"
|
59
|
+
|
60
|
+
# Text callback attributes
|
61
|
+
TEXT_CONTENT = "langchain.text.content"
|
62
|
+
|
63
|
+
LLM_ERROR = "langchain.llm.error"
|
@@ -0,0 +1,61 @@
|
|
1
|
+
"""Semantic conventions for message-related attributes in AI systems."""
|
2
|
+
|
3
|
+
|
4
|
+
class MessageAttributes:
|
5
|
+
"""Semantic conventions for message-related attributes in AI systems."""
|
6
|
+
|
7
|
+
PROMPT_ROLE = "gen_ai.prompt.{i}.role" # Role of the prompt message
|
8
|
+
PROMPT_CONTENT = "gen_ai.prompt.{i}.content" # Content of the prompt message
|
9
|
+
PROMPT_TYPE = "gen_ai.prompt.{i}.type" # Type of the prompt message
|
10
|
+
PROMPT_SPEAKER = "gen_ai.prompt.{i}.speaker" # Speaker/agent name for the prompt message
|
11
|
+
|
12
|
+
# Indexed function calls (with {i} for interpolation)
|
13
|
+
TOOL_CALL_ID = "gen_ai.request.tools.{i}.id" # Unique identifier for the function call at index {i}
|
14
|
+
TOOL_CALL_TYPE = "gen_ai.request.tools.{i}.type" # Type of the function call at index {i}
|
15
|
+
TOOL_CALL_NAME = "gen_ai.request.tools.{i}.name" # Name of the function call at index {i}
|
16
|
+
TOOL_CALL_DESCRIPTION = "gen_ai.request.tools.{i}.description" # Description of the function call at index {i}
|
17
|
+
TOOL_CALL_ARGUMENTS = "gen_ai.request.tools.{i}.arguments" # Arguments for function call at index {i}
|
18
|
+
|
19
|
+
# Indexed completions (with {i} for interpolation)
|
20
|
+
COMPLETION_ID = "gen_ai.completion.{i}.id" # Unique identifier for the completion
|
21
|
+
COMPLETION_TYPE = "gen_ai.completion.{i}.type" # Type of the completion at index {i}
|
22
|
+
COMPLETION_ROLE = "gen_ai.completion.{i}.role" # Role of the completion message at index {i}
|
23
|
+
COMPLETION_CONTENT = "gen_ai.completion.{i}.content" # Content of the completion message at index {i}
|
24
|
+
COMPLETION_FINISH_REASON = "gen_ai.completion.{i}.finish_reason" # Finish reason for completion at index {i}
|
25
|
+
COMPLETION_SPEAKER = "gen_ai.completion.{i}.speaker" # Speaker/agent name for the completion message
|
26
|
+
|
27
|
+
# Indexed tool calls (with {i}/{j} for nested interpolation)
|
28
|
+
COMPLETION_TOOL_CALL_ID = "gen_ai.completion.{i}.tool_calls.{j}.id" # ID of tool call {j} in completion {i}
|
29
|
+
COMPLETION_TOOL_CALL_TYPE = "gen_ai.completion.{i}.tool_calls.{j}.type" # Type of tool call {j} in completion {i}
|
30
|
+
COMPLETION_TOOL_CALL_STATUS = (
|
31
|
+
"gen_ai.completion.{i}.tool_calls.{j}.status" # Status of tool call {j} in completion {i}
|
32
|
+
)
|
33
|
+
COMPLETION_TOOL_CALL_NAME = (
|
34
|
+
"gen_ai.completion.{i}.tool_calls.{j}.name" # Name of the tool called in tool call {j} in completion {i}
|
35
|
+
)
|
36
|
+
COMPLETION_TOOL_CALL_DESCRIPTION = (
|
37
|
+
"gen_ai.completion.{i}.tool_calls.{j}.description" # Description of the tool call {j} in completion {i}
|
38
|
+
)
|
39
|
+
COMPLETION_TOOL_CALL_STATUS = (
|
40
|
+
"gen_ai.completion.{i}.tool_calls.{j}.status" # Status of the tool call {j} in completion {i}
|
41
|
+
)
|
42
|
+
COMPLETION_TOOL_CALL_ARGUMENTS = (
|
43
|
+
"gen_ai.completion.{i}.tool_calls.{j}.arguments" # Arguments for tool call {j} in completion {i}
|
44
|
+
)
|
45
|
+
|
46
|
+
# Indexed annotations of the internal tools (with {i}/{j} for nested interpolation)
|
47
|
+
COMPLETION_ANNOTATION_START_INDEX = (
|
48
|
+
"gen_ai.completion.{i}.annotations.{j}.start_index" # Start index of the URL annotation {j} in completion {i}
|
49
|
+
)
|
50
|
+
COMPLETION_ANNOTATION_END_INDEX = (
|
51
|
+
"gen_ai.completion.{i}.annotations.{j}.end_index" # End index of the URL annotation {j} in completion {i}
|
52
|
+
)
|
53
|
+
COMPLETION_ANNOTATION_TITLE = (
|
54
|
+
"gen_ai.completion.{i}.annotations.{j}.title" # Title of the URL annotation {j} in completion {i}
|
55
|
+
)
|
56
|
+
COMPLETION_ANNOTATION_TYPE = (
|
57
|
+
"gen_ai.completion.{i}.annotations.{j}.type" # Type of the URL annotation {j} in completion {i}
|
58
|
+
)
|
59
|
+
COMPLETION_ANNOTATION_URL = (
|
60
|
+
"gen_ai.completion.{i}.annotations.{j}.url" # URL link of the URL annotation {j} in completion {i}
|
61
|
+
)
|
@@ -0,0 +1,24 @@
|
|
1
|
+
"""Metrics for OpenTelemetry semantic conventions."""
|
2
|
+
|
3
|
+
|
4
|
+
class Meters:
|
5
|
+
# Gen AI metrics (OpenTelemetry standard)
|
6
|
+
LLM_GENERATION_CHOICES = "gen_ai.client.generation.choices"
|
7
|
+
LLM_TOKEN_USAGE = "gen_ai.client.token.usage"
|
8
|
+
LLM_OPERATION_DURATION = "gen_ai.client.operation.duration"
|
9
|
+
|
10
|
+
# OpenAI specific metrics
|
11
|
+
LLM_COMPLETIONS_EXCEPTIONS = "gen_ai.openai.chat_completions.exceptions"
|
12
|
+
LLM_STREAMING_TIME_TO_FIRST_TOKEN = "gen_ai.openai.chat_completions.streaming_time_to_first_token"
|
13
|
+
LLM_STREAMING_TIME_TO_GENERATE = "gen_ai.openai.chat_completions.streaming_time_to_generate"
|
14
|
+
LLM_EMBEDDINGS_EXCEPTIONS = "gen_ai.openai.embeddings.exceptions"
|
15
|
+
LLM_EMBEDDINGS_VECTOR_SIZE = "gen_ai.openai.embeddings.vector_size"
|
16
|
+
LLM_IMAGE_GENERATIONS_EXCEPTIONS = "gen_ai.openai.image_generations.exceptions"
|
17
|
+
|
18
|
+
# Anthropic specific metrics
|
19
|
+
LLM_ANTHROPIC_COMPLETION_EXCEPTIONS = "gen_ai.anthropic.completion.exceptions"
|
20
|
+
|
21
|
+
# Agent metrics
|
22
|
+
AGENT_RUNS = "gen_ai.agent.runs"
|
23
|
+
AGENT_TURNS = "gen_ai.agent.turns"
|
24
|
+
AGENT_EXECUTION_TIME = "gen_ai.agent.execution_time"
|