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,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"
@@ -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
@@ -0,0 +1,11 @@
1
+ """Enum for LLM request types."""
2
+
3
+ from enum import Enum
4
+
5
+
6
+ class LLMRequestTypeValues(Enum):
7
+ COMPLETION = "completion"
8
+ CHAT = "chat"
9
+ RERANK = "rerank"
10
+ EMBEDDING = "embedding"
11
+ UNKNOWN = "unknown"
@@ -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"