mseep-agentops 0.4.18__py3-none-any.whl → 0.4.23__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 (153) hide show
  1. agentops/__init__.py +0 -0
  2. agentops/client/api/base.py +28 -30
  3. agentops/client/api/versions/v3.py +29 -25
  4. agentops/client/api/versions/v4.py +87 -46
  5. agentops/client/client.py +98 -29
  6. agentops/client/http/README.md +87 -0
  7. agentops/client/http/http_client.py +126 -172
  8. agentops/config.py +8 -2
  9. agentops/instrumentation/OpenTelemetry.md +133 -0
  10. agentops/instrumentation/README.md +167 -0
  11. agentops/instrumentation/__init__.py +13 -1
  12. agentops/instrumentation/agentic/ag2/__init__.py +18 -0
  13. agentops/instrumentation/agentic/ag2/instrumentor.py +922 -0
  14. agentops/instrumentation/agentic/agno/__init__.py +19 -0
  15. agentops/instrumentation/agentic/agno/attributes/__init__.py +20 -0
  16. agentops/instrumentation/agentic/agno/attributes/agent.py +250 -0
  17. agentops/instrumentation/agentic/agno/attributes/metrics.py +214 -0
  18. agentops/instrumentation/agentic/agno/attributes/storage.py +158 -0
  19. agentops/instrumentation/agentic/agno/attributes/team.py +195 -0
  20. agentops/instrumentation/agentic/agno/attributes/tool.py +210 -0
  21. agentops/instrumentation/agentic/agno/attributes/workflow.py +254 -0
  22. agentops/instrumentation/agentic/agno/instrumentor.py +1313 -0
  23. agentops/instrumentation/agentic/crewai/LICENSE +201 -0
  24. agentops/instrumentation/agentic/crewai/NOTICE.md +10 -0
  25. agentops/instrumentation/agentic/crewai/__init__.py +6 -0
  26. agentops/instrumentation/agentic/crewai/crewai_span_attributes.py +335 -0
  27. agentops/instrumentation/agentic/crewai/instrumentation.py +535 -0
  28. agentops/instrumentation/agentic/crewai/version.py +1 -0
  29. agentops/instrumentation/agentic/google_adk/__init__.py +19 -0
  30. agentops/instrumentation/agentic/google_adk/instrumentor.py +68 -0
  31. agentops/instrumentation/agentic/google_adk/patch.py +767 -0
  32. agentops/instrumentation/agentic/haystack/__init__.py +1 -0
  33. agentops/instrumentation/agentic/haystack/instrumentor.py +186 -0
  34. agentops/instrumentation/agentic/langgraph/__init__.py +3 -0
  35. agentops/instrumentation/agentic/langgraph/attributes.py +54 -0
  36. agentops/instrumentation/agentic/langgraph/instrumentation.py +598 -0
  37. agentops/instrumentation/agentic/langgraph/version.py +1 -0
  38. agentops/instrumentation/agentic/openai_agents/README.md +156 -0
  39. agentops/instrumentation/agentic/openai_agents/SPANS.md +145 -0
  40. agentops/instrumentation/agentic/openai_agents/TRACING_API.md +144 -0
  41. agentops/instrumentation/agentic/openai_agents/__init__.py +30 -0
  42. agentops/instrumentation/agentic/openai_agents/attributes/common.py +549 -0
  43. agentops/instrumentation/agentic/openai_agents/attributes/completion.py +172 -0
  44. agentops/instrumentation/agentic/openai_agents/attributes/model.py +58 -0
  45. agentops/instrumentation/agentic/openai_agents/attributes/tokens.py +275 -0
  46. agentops/instrumentation/agentic/openai_agents/exporter.py +469 -0
  47. agentops/instrumentation/agentic/openai_agents/instrumentor.py +107 -0
  48. agentops/instrumentation/agentic/openai_agents/processor.py +58 -0
  49. agentops/instrumentation/agentic/smolagents/README.md +88 -0
  50. agentops/instrumentation/agentic/smolagents/__init__.py +12 -0
  51. agentops/instrumentation/agentic/smolagents/attributes/agent.py +354 -0
  52. agentops/instrumentation/agentic/smolagents/attributes/model.py +205 -0
  53. agentops/instrumentation/agentic/smolagents/instrumentor.py +286 -0
  54. agentops/instrumentation/agentic/smolagents/stream_wrapper.py +258 -0
  55. agentops/instrumentation/agentic/xpander/__init__.py +15 -0
  56. agentops/instrumentation/agentic/xpander/context.py +112 -0
  57. agentops/instrumentation/agentic/xpander/instrumentor.py +877 -0
  58. agentops/instrumentation/agentic/xpander/trace_probe.py +86 -0
  59. agentops/instrumentation/agentic/xpander/version.py +3 -0
  60. agentops/instrumentation/common/README.md +65 -0
  61. agentops/instrumentation/common/attributes.py +1 -2
  62. agentops/instrumentation/providers/anthropic/__init__.py +24 -0
  63. agentops/instrumentation/providers/anthropic/attributes/__init__.py +23 -0
  64. agentops/instrumentation/providers/anthropic/attributes/common.py +64 -0
  65. agentops/instrumentation/providers/anthropic/attributes/message.py +541 -0
  66. agentops/instrumentation/providers/anthropic/attributes/tools.py +231 -0
  67. agentops/instrumentation/providers/anthropic/event_handler_wrapper.py +90 -0
  68. agentops/instrumentation/providers/anthropic/instrumentor.py +146 -0
  69. agentops/instrumentation/providers/anthropic/stream_wrapper.py +436 -0
  70. agentops/instrumentation/providers/google_genai/README.md +33 -0
  71. agentops/instrumentation/providers/google_genai/__init__.py +24 -0
  72. agentops/instrumentation/providers/google_genai/attributes/__init__.py +25 -0
  73. agentops/instrumentation/providers/google_genai/attributes/chat.py +125 -0
  74. agentops/instrumentation/providers/google_genai/attributes/common.py +88 -0
  75. agentops/instrumentation/providers/google_genai/attributes/model.py +284 -0
  76. agentops/instrumentation/providers/google_genai/instrumentor.py +170 -0
  77. agentops/instrumentation/providers/google_genai/stream_wrapper.py +238 -0
  78. agentops/instrumentation/providers/ibm_watsonx_ai/__init__.py +28 -0
  79. agentops/instrumentation/providers/ibm_watsonx_ai/attributes/__init__.py +27 -0
  80. agentops/instrumentation/providers/ibm_watsonx_ai/attributes/attributes.py +277 -0
  81. agentops/instrumentation/providers/ibm_watsonx_ai/attributes/common.py +104 -0
  82. agentops/instrumentation/providers/ibm_watsonx_ai/instrumentor.py +162 -0
  83. agentops/instrumentation/providers/ibm_watsonx_ai/stream_wrapper.py +302 -0
  84. agentops/instrumentation/providers/mem0/__init__.py +45 -0
  85. agentops/instrumentation/providers/mem0/common.py +377 -0
  86. agentops/instrumentation/providers/mem0/instrumentor.py +270 -0
  87. agentops/instrumentation/providers/mem0/memory.py +430 -0
  88. agentops/instrumentation/providers/openai/__init__.py +21 -0
  89. agentops/instrumentation/providers/openai/attributes/__init__.py +7 -0
  90. agentops/instrumentation/providers/openai/attributes/common.py +55 -0
  91. agentops/instrumentation/providers/openai/attributes/response.py +607 -0
  92. agentops/instrumentation/providers/openai/config.py +36 -0
  93. agentops/instrumentation/providers/openai/instrumentor.py +312 -0
  94. agentops/instrumentation/providers/openai/stream_wrapper.py +941 -0
  95. agentops/instrumentation/providers/openai/utils.py +44 -0
  96. agentops/instrumentation/providers/openai/v0.py +176 -0
  97. agentops/instrumentation/providers/openai/v0_wrappers.py +483 -0
  98. agentops/instrumentation/providers/openai/wrappers/__init__.py +30 -0
  99. agentops/instrumentation/providers/openai/wrappers/assistant.py +277 -0
  100. agentops/instrumentation/providers/openai/wrappers/chat.py +259 -0
  101. agentops/instrumentation/providers/openai/wrappers/completion.py +109 -0
  102. agentops/instrumentation/providers/openai/wrappers/embeddings.py +94 -0
  103. agentops/instrumentation/providers/openai/wrappers/image_gen.py +75 -0
  104. agentops/instrumentation/providers/openai/wrappers/responses.py +191 -0
  105. agentops/instrumentation/providers/openai/wrappers/shared.py +81 -0
  106. agentops/instrumentation/utilities/concurrent_futures/__init__.py +10 -0
  107. agentops/instrumentation/utilities/concurrent_futures/instrumentation.py +206 -0
  108. agentops/integration/callbacks/dspy/__init__.py +11 -0
  109. agentops/integration/callbacks/dspy/callback.py +471 -0
  110. agentops/integration/callbacks/langchain/README.md +59 -0
  111. agentops/integration/callbacks/langchain/__init__.py +15 -0
  112. agentops/integration/callbacks/langchain/callback.py +791 -0
  113. agentops/integration/callbacks/langchain/utils.py +54 -0
  114. agentops/legacy/crewai.md +121 -0
  115. agentops/logging/instrument_logging.py +4 -0
  116. agentops/sdk/README.md +220 -0
  117. agentops/sdk/core.py +75 -32
  118. agentops/sdk/descriptors/classproperty.py +28 -0
  119. agentops/sdk/exporters.py +152 -33
  120. agentops/semconv/README.md +125 -0
  121. agentops/semconv/span_kinds.py +0 -2
  122. agentops/validation.py +102 -63
  123. {mseep_agentops-0.4.18.dist-info → mseep_agentops-0.4.23.dist-info}/METADATA +30 -40
  124. mseep_agentops-0.4.23.dist-info/RECORD +178 -0
  125. {mseep_agentops-0.4.18.dist-info → mseep_agentops-0.4.23.dist-info}/WHEEL +1 -2
  126. mseep_agentops-0.4.18.dist-info/RECORD +0 -94
  127. mseep_agentops-0.4.18.dist-info/top_level.txt +0 -2
  128. tests/conftest.py +0 -10
  129. tests/unit/client/__init__.py +0 -1
  130. tests/unit/client/test_http_adapter.py +0 -221
  131. tests/unit/client/test_http_client.py +0 -206
  132. tests/unit/conftest.py +0 -54
  133. tests/unit/sdk/__init__.py +0 -1
  134. tests/unit/sdk/instrumentation_tester.py +0 -207
  135. tests/unit/sdk/test_attributes.py +0 -392
  136. tests/unit/sdk/test_concurrent_instrumentation.py +0 -468
  137. tests/unit/sdk/test_decorators.py +0 -763
  138. tests/unit/sdk/test_exporters.py +0 -241
  139. tests/unit/sdk/test_factory.py +0 -1188
  140. tests/unit/sdk/test_internal_span_processor.py +0 -397
  141. tests/unit/sdk/test_resource_attributes.py +0 -35
  142. tests/unit/test_config.py +0 -82
  143. tests/unit/test_context_manager.py +0 -777
  144. tests/unit/test_events.py +0 -27
  145. tests/unit/test_host_env.py +0 -54
  146. tests/unit/test_init_py.py +0 -501
  147. tests/unit/test_serialization.py +0 -433
  148. tests/unit/test_session.py +0 -676
  149. tests/unit/test_user_agent.py +0 -34
  150. tests/unit/test_validation.py +0 -405
  151. {tests → agentops/instrumentation/agentic/openai_agents/attributes}/__init__.py +0 -0
  152. /tests/unit/__init__.py → /agentops/instrumentation/providers/openai/attributes/tools.py +0 -0
  153. {mseep_agentops-0.4.18.dist-info → mseep_agentops-0.4.23.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,54 @@
1
+ """
2
+ Utility functions for LangChain integration.
3
+ """
4
+
5
+ from typing import Any, Dict, Optional
6
+
7
+ from agentops.logging import logger
8
+
9
+
10
+ def get_model_info(serialized: Optional[Dict[str, Any]]) -> Dict[str, str]:
11
+ """
12
+ Extract model information from serialized LangChain data.
13
+
14
+ This function attempts to extract model name information
15
+ from the serialized data of a LangChain model.
16
+
17
+ Args:
18
+ serialized: Serialized data from LangChain
19
+
20
+ Returns:
21
+ Dictionary with model_name key
22
+ """
23
+ if serialized is None:
24
+ return {"model_name": "unknown"}
25
+
26
+ model_info = {"model_name": "unknown"}
27
+
28
+ try:
29
+ if isinstance(serialized.get("id"), list) and len(serialized["id"]) > 0:
30
+ id_list = serialized["id"]
31
+ if len(id_list) > 0:
32
+ model_info["model_name"] = id_list[-1]
33
+
34
+ if isinstance(serialized.get("model_name"), str):
35
+ model_info["model_name"] = serialized["model_name"]
36
+
37
+ elif serialized.get("id") and isinstance(serialized.get("id"), str):
38
+ model_id = serialized.get("id", "")
39
+ if "/" in model_id:
40
+ _, model_name = model_id.split("/", 1)
41
+ model_info["model_name"] = model_name
42
+ else:
43
+ model_info["model_name"] = model_id
44
+
45
+ if serialized.get("kwargs") and isinstance(serialized["kwargs"], dict):
46
+ if serialized["kwargs"].get("model_name"):
47
+ model_info["model_name"] = serialized["kwargs"]["model_name"]
48
+ elif serialized["kwargs"].get("model"):
49
+ model_info["model_name"] = serialized["kwargs"]["model"]
50
+
51
+ except Exception as e:
52
+ logger.warning(f"Error extracting model info: {e}")
53
+
54
+ return model_info
@@ -0,0 +1,121 @@
1
+ # CrewAI Integration Reference
2
+
3
+ ## Overview
4
+ This document provides information about CrewAI's integration with AgentOps and how our legacy compatibility layer supports different versions of CrewAI.
5
+
6
+ ## CrewAI Integration Points
7
+
8
+ CrewAI has two distinct integration patterns with AgentOps:
9
+
10
+ ### 1. Direct Integration (CrewAI < 0.105.0)
11
+ In CrewAI versions 0.98.0 through 0.102.0, integration is done directly in the core code:
12
+
13
+ - In `crew.py` (_finish_execution method):
14
+ ```python
15
+ if agentops:
16
+ agentops.end_session(
17
+ end_state="Success",
18
+ end_state_reason="Finished Execution",
19
+ is_auto_end=True,
20
+ )
21
+ ```
22
+
23
+ - In `tools/tool_usage.py`:
24
+ ```python
25
+ # Tool event creation
26
+ tool_event = agentops.ToolEvent(name=calling.tool_name) if agentops else None
27
+
28
+ # Error recording
29
+ if agentops:
30
+ agentops.record(agentops.ErrorEvent(exception=e, trigger_event=tool_event))
31
+
32
+ # Tool usage recording
33
+ if agentops:
34
+ agentops.record(tool_event)
35
+ ```
36
+
37
+ ### 2. Event-Based Integration (CrewAI >= 0.105.0)
38
+ In CrewAI versions 0.105.0 and above, integration uses an event-based system:
39
+
40
+ ```python
41
+ # In utilities/events/third_party/agentops_listener.py
42
+ class AgentOpsListener(BaseEventListener):
43
+ # Called when a crew kickoff starts
44
+ @crewai_event_bus.on(CrewKickoffStartedEvent)
45
+ def on_crew_kickoff_started(source, event):
46
+ self.session = agentops.init()
47
+ for agent in source.agents:
48
+ if self.session:
49
+ self.session.create_agent(
50
+ name=agent.role,
51
+ agent_id=str(agent.id),
52
+ )
53
+
54
+ # Called when a crew kickoff completes
55
+ @crewai_event_bus.on(CrewKickoffCompletedEvent)
56
+ def on_crew_kickoff_completed(source, event):
57
+ if self.session:
58
+ self.session.end_session(
59
+ end_state="Success",
60
+ end_state_reason="Finished Execution",
61
+ )
62
+
63
+ # Tool usage and other events are also tracked
64
+ # ...
65
+ ```
66
+
67
+ ## Required AgentOps Legacy API
68
+
69
+ To maintain compatibility with all CrewAI versions, our legacy API must support:
70
+
71
+ ### Function Signatures
72
+
73
+ | Function | Parameters | Used By |
74
+ |----------|------------|---------|
75
+ | `agentops.init()` | - | All versions, returns a Session object |
76
+ | `agentops.end_session()` | Various (see below) | All versions |
77
+ | `agentops.record()` | Event object | CrewAI < 0.105.0 |
78
+ | `agentops.ToolEvent()` | `name` | CrewAI < 0.105.0 |
79
+ | `agentops.ErrorEvent()` | `exception`, `trigger_event` | CrewAI < 0.105.0 |
80
+ | `agentops.ActionEvent()` | `action_type` | Used in tests |
81
+
82
+ ### Supported `end_session()` Calls
83
+
84
+ The `end_session()` function must handle:
85
+
86
+ 1. A simple string status:
87
+ ```python
88
+ agentops.end_session("Success")
89
+ ```
90
+
91
+ 2. Named arguments from CrewAI < 0.105.0:
92
+ ```python
93
+ agentops.end_session(
94
+ end_state="Success",
95
+ end_state_reason="Finished Execution",
96
+ is_auto_end=True
97
+ )
98
+ ```
99
+
100
+ 3. Session object method calls from CrewAI >= 0.105.0:
101
+ ```python
102
+ session.end_session(
103
+ end_state="Success",
104
+ end_state_reason="Finished Execution"
105
+ )
106
+ ```
107
+
108
+ ### Session Class Methods
109
+
110
+ The Session class must support:
111
+
112
+ 1. `create_agent(name, agent_id)` - Used in CrewAI >= 0.105.0
113
+ 2. `record(event)` - Used in CrewAI >= 0.105.0
114
+ 3. `end_session(**kwargs)` - Used in CrewAI >= 0.105.0
115
+
116
+ ## Implementation Guidelines
117
+
118
+ - All legacy interfaces should accept their parameters without errors but don't need to implement actual functionality.
119
+ - New code should use OpenTelemetry instrumentation instead of these legacy interfaces.
120
+ - This compatibility layer will be maintained until CrewAI migrates to using OpenTelemetry directly.
121
+ - Tests ensure backward compatibility with both integration patterns.
@@ -9,6 +9,8 @@ _original_print = builtins.print
9
9
  # Global buffer to store logs
10
10
  _log_buffer = StringIO()
11
11
 
12
+ print_logger = None
13
+
12
14
 
13
15
  def setup_print_logger() -> None:
14
16
  """
@@ -29,6 +31,8 @@ def setup_print_logger() -> None:
29
31
  # Ensure the new logger doesn't propagate to root
30
32
  buffer_logger.propagate = False
31
33
 
34
+ global print_logger
35
+
32
36
  def print_logger(*args: Any, **kwargs: Any) -> None:
33
37
  """
34
38
  Custom print function that logs to buffer and console.
agentops/sdk/README.md ADDED
@@ -0,0 +1,220 @@
1
+ # AgentOps v0.4 Architecture
2
+
3
+ ## Transition from Events to Spans
4
+
5
+ In AgentOps v0.4, we've transitioned from the "Event" concept to using "Spans" for all event tracking. This proposal outlines a new architecture that supports this transition and enables custom implementations through decorators.
6
+
7
+ ## Core Concepts
8
+
9
+ 1. **Session**: The master trace that serves as the root for all spans. No spans can exist without a session at the top.
10
+ 2. **Spans**: Represent different types of operations (Agent, Tool, etc.) and are organized hierarchically.
11
+ 3. **Decorators**: Allow users to easily mark their custom components with AgentOps-specific span types.
12
+ 4. **TracingConfig**: A dedicated configuration structure for the tracing core, separate from the main application configuration.
13
+
14
+ ## Architecture Diagram
15
+
16
+ ```mermaid
17
+ flowchart TD
18
+ %% Core Tracing Components
19
+ subgraph "Core Tracing Infrastructure"
20
+ TracingCore[Tracing Core]
21
+ TracingConfig[Tracing Config]
22
+ SpanFactory[Span Factory]
23
+ SpanProcessor[Span Processor]
24
+ SpanExporter[Span Exporter]
25
+
26
+ TracingConfig --> TracingCore
27
+ TracingCore --> SpanFactory
28
+ TracingCore --> SpanProcessor
29
+ SpanProcessor --> SpanExporter
30
+ end
31
+
32
+ %% Span Base Classes
33
+ subgraph "Span Base Classes"
34
+ TracedObject[TracedObject]
35
+ end
36
+
37
+ %% Span Types
38
+ subgraph "Span Types"
39
+ SessionSpan[SessionSpan]
40
+ AgentSpan[AgentSpan]
41
+ ToolSpan[ToolSpan]
42
+ LLMSpan[LLMSpan]
43
+ CustomSpan[CustomSpan]
44
+
45
+ TracedObject --> SessionSpan
46
+ TracedObject --> AgentSpan
47
+ TracedObject --> ToolSpan
48
+ TracedObject --> LLMSpan
49
+ TracedObject --> CustomSpan
50
+ end
51
+
52
+ %% Decorators
53
+ subgraph "Decorators"
54
+ SessionDecorator[session]
55
+ AgentDecorator[agent]
56
+ ToolDecorator[tool]
57
+ LLMDecorator[llm]
58
+
59
+ AgentDecorator --> AgentSpan
60
+ ToolDecorator --> ToolSpan
61
+ SessionDecorator --> SessionSpan
62
+ LLMDecorator --> LLMSpan
63
+ end
64
+
65
+ %% User-Facing Classes
66
+ subgraph "User-Facing Classes"
67
+ Session[Session]
68
+ Agent[Agent]
69
+ Tool[Tool]
70
+
71
+ SessionSpan --> Session
72
+ AgentSpan --> Agent
73
+ ToolSpan --> Tool
74
+ end
75
+
76
+ %% Relationships
77
+ SpanFactory --> TracedObject
78
+ Session -.->|"Master Trace"| Agent
79
+ Session -.->|"Master Trace"| Tool
80
+
81
+ %% Context Management
82
+ subgraph "Context Management"
83
+ SpanContext[Span Context]
84
+ Registry[Registry]
85
+
86
+ SpanContext <--> Registry
87
+ end
88
+
89
+ TracingCore <--> SpanContext
90
+
91
+ class TracingCore,SpanFactory,SpanProcessor,SpanExporter core
92
+ class TracedObject base
93
+ class SessionSpan,AgentSpan,ToolSpan,LLMSpan,CustomSpan span
94
+ class SessionDecorator,AgentDecorator,ToolDecorator,LLMDecorator decorator
95
+ class Session,Agent,Tool user
96
+ class SpanContext,Registry context
97
+ ```
98
+
99
+ ## Component Descriptions
100
+
101
+ ### Core Tracing Infrastructure
102
+
103
+ - **Tracing Core**: Central component that manages the creation, processing, and export of spans.
104
+ - **Tracing Config**: Configuration specific to the tracing infrastructure, separate from the main application configuration.
105
+ - **Span Factory**: Creates spans of different types based on context and decorator information.
106
+ - **Span Processor**: Processes spans (adds attributes, manages context, etc.) before they are exported.
107
+ - **Span Exporter**: Exports spans to the configured destination (e.g., AgentOps backend).
108
+
109
+ ### Span Base Classes
110
+
111
+ - **TracedObject**: Base class that provides core tracing functionality (trace ID, span ID, etc.) and common span operations (start, end, attributes).
112
+
113
+ ### Span Types
114
+
115
+ - **SessionSpan**: Represents a session (master trace).
116
+ - **AgentSpan**: Represents an agent operation.
117
+ - **ToolSpan**: Represents a tool operation.
118
+ - **LLMSpan**: Represents an LLM operation.
119
+ - **CustomSpan**: Allows for custom span types.
120
+
121
+ ### Decorators
122
+
123
+ - **@session**: Creates a new session span.
124
+ - **@agent**: Creates a new agent span.
125
+ - **@tool**: Creates a new tool span.
126
+ - **@llm**: Creates a new LLM span.
127
+
128
+ ### User-Facing Classes
129
+
130
+ - **Session**: User-facing session class that wraps SessionSpan.
131
+ - **Agent**: User-facing agent class that wraps AgentSpan.
132
+ - **Tool**: User-facing tool class that wraps ToolSpan.
133
+
134
+ ### Context Management
135
+
136
+ - **Span Context**: Manages the current span context (parent-child relationships).
137
+ - **Registry**: Keeps track of active spans and their relationships.
138
+
139
+ ## Implementation Considerations
140
+
141
+ 1. **Decorator Implementation**:
142
+ ```python
143
+ def agent(cls=None, **kwargs):
144
+ def decorator(cls):
145
+ # Wrap methods with span creation/management
146
+ original_init = cls.__init__
147
+
148
+ def __init__(self, *args, **init_kwargs):
149
+ # Get current session from context
150
+ session = get_current_session()
151
+ if not session:
152
+ raise ValueError("No active session found. Create a session first.")
153
+
154
+ # Create agent span as child of session
155
+ self._span = create_span("agent", parent=session.span, **kwargs)
156
+
157
+ # Call original init
158
+ original_init(self, *args, **init_kwargs)
159
+
160
+ cls.__init__ = __init__
161
+ return cls
162
+
163
+ if cls is None:
164
+ return decorator
165
+ return decorator(cls)
166
+ ```
167
+
168
+ 2. **Session as Master Trace**:
169
+ - All spans must have a session as their root ancestor.
170
+ - Session creation should be explicit and precede any agent or tool operations.
171
+
172
+ 3. **Context Propagation**:
173
+ - Span context should be propagated automatically through the call stack.
174
+ - Context should be accessible globally but thread-safe.
175
+
176
+ ## Example Usage
177
+
178
+ ```python
179
+ from agentops import Session, agent, tool, tracer
180
+ from agentops.sdk import TracingConfig
181
+
182
+ # Initialize the global tracer with a dedicated configuration
183
+ tracer.initialize(
184
+ service_name="my-service",
185
+ exporter_endpoint="https://my-exporter-endpoint.com",
186
+ max_queue_size=1000,
187
+ max_wait_time=10000
188
+ )
189
+
190
+ # Create a session (master trace)
191
+ with Session() as session:
192
+ # Create an agent
193
+ @agent
194
+ class MyAgent:
195
+ def __init__(self, name):
196
+ self.name = name
197
+
198
+ def run(self):
199
+ # Agent operations are automatically traced
200
+ result = self.use_tool()
201
+ return result
202
+
203
+ @tool
204
+ def use_tool(self):
205
+ # Tool operations are automatically traced
206
+ return "Tool result"
207
+
208
+ # Use the agent
209
+ agent = MyAgent("Agent1")
210
+ result = agent.run()
211
+ ```
212
+
213
+ ## Benefits
214
+
215
+ 1. **Simplified API**: Users can easily mark their components with decorators.
216
+ 2. **Hierarchical Tracing**: All operations are organized hierarchically with the session as the root.
217
+ 3. **Automatic Context Propagation**: Context is propagated automatically through the call stack.
218
+ 4. **Extensibility**: Custom span types can be added easily.
219
+ 5. **Separation of Concerns**: Tracing configuration is separate from the main application configuration.
220
+
agentops/sdk/core.py CHANGED
@@ -2,11 +2,10 @@ from __future__ import annotations
2
2
 
3
3
  import atexit
4
4
  import threading
5
- from typing import Optional, Any, Dict, Union
5
+ from typing import Optional, Any, Dict, Union, Callable
6
6
 
7
7
  from opentelemetry import metrics, trace
8
8
  from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter
9
- from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
10
9
  from opentelemetry.sdk.metrics import MeterProvider
11
10
  from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
12
11
  from opentelemetry.sdk.resources import Resource
@@ -18,6 +17,7 @@ from agentops.exceptions import AgentOpsClientNotInitializedException
18
17
  from agentops.logging import logger, setup_print_logger
19
18
  from agentops.sdk.processors import InternalSpanProcessor
20
19
  from agentops.sdk.types import TracingConfig
20
+ from agentops.sdk.exporters import AuthenticatedOTLPExporter
21
21
  from agentops.sdk.attributes import (
22
22
  get_global_resource_attributes,
23
23
  get_trace_attributes,
@@ -83,7 +83,7 @@ def setup_telemetry(
83
83
  max_queue_size: int = 512,
84
84
  max_wait_time: int = 5000,
85
85
  export_flush_interval: int = 1000,
86
- jwt: Optional[str] = None,
86
+ jwt_provider: Optional[Callable[[], Optional[str]]] = None,
87
87
  ) -> tuple[TracerProvider, MeterProvider]:
88
88
  """
89
89
  Setup the telemetry system.
@@ -96,7 +96,7 @@ def setup_telemetry(
96
96
  max_queue_size: Maximum number of spans to queue before forcing a flush
97
97
  max_wait_time: Maximum time in milliseconds to wait before flushing
98
98
  export_flush_interval: Time interval in milliseconds between automatic exports of telemetry data
99
- jwt: JWT token for authentication
99
+ jwt_provider: Function that returns the current JWT token
100
100
 
101
101
  Returns:
102
102
  Tuple of (TracerProvider, MeterProvider)
@@ -113,8 +113,8 @@ def setup_telemetry(
113
113
  # Set as global provider
114
114
  trace.set_tracer_provider(provider)
115
115
 
116
- # Create exporter with authentication
117
- exporter = OTLPSpanExporter(endpoint=exporter_endpoint, headers={"Authorization": f"Bearer {jwt}"} if jwt else {})
116
+ # Create exporter with dynamic JWT support
117
+ exporter = AuthenticatedOTLPExporter(endpoint=exporter_endpoint, jwt_provider=jwt_provider)
118
118
 
119
119
  # Regular processor for normal spans and immediate export
120
120
  processor = BatchSpanProcessor(
@@ -126,10 +126,13 @@ def setup_telemetry(
126
126
  internal_processor = InternalSpanProcessor() # Catches spans for AgentOps on-terminal printing
127
127
  provider.add_span_processor(internal_processor)
128
128
 
129
- # Setup metrics
130
- metric_exporter = OTLPMetricExporter(
131
- endpoint=metrics_endpoint, headers={"Authorization": f"Bearer {jwt}"} if jwt else {}
132
- )
129
+ # Setup metrics with JWT provider
130
+ def get_metrics_headers():
131
+ token = jwt_provider() if jwt_provider else None
132
+ return {"Authorization": f"Bearer {token}"} if token else {}
133
+
134
+ metric_exporter = OTLPMetricExporter(endpoint=metrics_endpoint, headers=get_metrics_headers())
135
+
133
136
  metric_reader = PeriodicExportingMetricReader(metric_exporter)
134
137
  meter_provider = MeterProvider(resource=resource, metric_readers=[metric_reader])
135
138
  metrics.set_meter_provider(meter_provider)
@@ -162,16 +165,17 @@ class TracingCore:
162
165
  self._span_processors: list = []
163
166
  self._active_traces: dict = {}
164
167
  self._traces_lock = threading.Lock()
168
+ self._jwt_provider: Optional[Callable[[], Optional[str]]] = None
165
169
 
166
170
  # Register shutdown handler
167
171
  atexit.register(self.shutdown)
168
172
 
169
- def initialize(self, jwt: Optional[str] = None, **kwargs: Any) -> None:
173
+ def initialize(self, jwt_provider: Optional[Callable[[], Optional[str]]] = None, **kwargs: Any) -> None:
170
174
  """
171
175
  Initialize the tracing core with the given configuration.
172
176
 
173
177
  Args:
174
- jwt: JWT token for authentication
178
+ jwt_provider: Function that returns the current JWT token
175
179
  **kwargs: Configuration parameters for tracing
176
180
  service_name: Name of the service
177
181
  exporter: Custom span exporter
@@ -185,6 +189,9 @@ class TracingCore:
185
189
  if self._initialized:
186
190
  return
187
191
 
192
+ # Store JWT provider for potential updates
193
+ self._jwt_provider = jwt_provider
194
+
188
195
  # Set default values for required fields
189
196
  kwargs.setdefault("service_name", "agentops")
190
197
  kwargs.setdefault("exporter_endpoint", "https://otlp.agentops.ai/v1/traces")
@@ -216,7 +223,7 @@ class TracingCore:
216
223
  max_queue_size=config["max_queue_size"],
217
224
  max_wait_time=config["max_wait_time"],
218
225
  export_flush_interval=config["export_flush_interval"],
219
- jwt=jwt,
226
+ jwt_provider=jwt_provider,
220
227
  )
221
228
 
222
229
  self.provider = provider
@@ -225,6 +232,29 @@ class TracingCore:
225
232
  self._initialized = True
226
233
  logger.debug("Tracing core initialized")
227
234
 
235
+ def update_config(self, config_updates: Dict[str, Any]) -> None:
236
+ """
237
+ Update the tracing configuration.
238
+
239
+ Args:
240
+ config_updates: Dictionary of configuration updates
241
+ """
242
+ if not self._initialized:
243
+ logger.warning("Cannot update config: tracer not initialized")
244
+ return
245
+
246
+ if self._config:
247
+ # Update the stored config
248
+ self._config.update(config_updates)
249
+
250
+ # Update resource attributes if project_id changed
251
+ if "project_id" in config_updates:
252
+ new_project_id = config_updates["project_id"]
253
+ if new_project_id and new_project_id != "temporary":
254
+ logger.debug(f"Updating tracer project_id to: {new_project_id}")
255
+ # Note: OpenTelemetry doesn't easily support updating resource attributes
256
+ # after initialization, but we can log the change for debugging
257
+
228
258
  @property
229
259
  def initialized(self) -> bool:
230
260
  """Check if the tracing core is initialized."""
@@ -239,29 +269,39 @@ class TracingCore:
239
269
  return self._config
240
270
 
241
271
  def shutdown(self) -> None:
242
- """Shutdown the tracing core."""
243
-
244
- if not self._initialized or not self.provider:
272
+ """Shutdown the tracing core and clean up resources."""
273
+ if not self._initialized:
245
274
  return
246
275
 
247
- logger.debug("Attempting to flush span processors during shutdown...")
248
- self._flush_span_processors()
249
-
250
- # Shutdown provider
251
276
  try:
252
- self.provider.shutdown()
253
- except Exception as e:
254
- logger.warning(f"Error shutting down provider: {e}")
277
+ # End all active traces
278
+ with self._traces_lock:
279
+ active_traces = list(self._active_traces.values())
280
+ logger.debug(f"Shutting down tracer with {len(active_traces)} active traces")
255
281
 
256
- # Shutdown meter_provider
257
- if hasattr(self, "_meter_provider") and self._meter_provider:
258
- try:
282
+ for trace_context in active_traces:
283
+ try:
284
+ self._end_single_trace(trace_context, "Shutdown")
285
+ except Exception as e:
286
+ logger.error(f"Error ending trace during shutdown: {e}")
287
+
288
+ # Force flush all processors
289
+ self._flush_span_processors()
290
+
291
+ # Shutdown providers
292
+ if self.provider:
293
+ self.provider.shutdown()
294
+
295
+ if self._meter_provider:
259
296
  self._meter_provider.shutdown()
260
- except Exception as e:
261
- logger.warning(f"Error shutting down meter provider: {e}")
262
297
 
263
- self._initialized = False
264
- logger.debug("Tracing core shut down")
298
+ logger.debug("Tracing core shutdown complete")
299
+
300
+ except Exception as e:
301
+ logger.error(f"Error during tracing core shutdown: {e}")
302
+
303
+ finally:
304
+ self._initialized = False
265
305
 
266
306
  def _flush_span_processors(self) -> None:
267
307
  """Helper to force flush all span processors."""
@@ -291,12 +331,15 @@ class TracingCore:
291
331
  return trace.get_tracer(name)
292
332
 
293
333
  @classmethod
294
- def initialize_from_config(cls, config_obj: Any, **kwargs: Any) -> None:
334
+ def initialize_from_config(
335
+ cls, config_obj: Any, jwt_provider: Optional[Callable[[], Optional[str]]] = None, **kwargs: Any
336
+ ) -> None:
295
337
  """
296
338
  Initialize the tracing core from a configuration object.
297
339
 
298
340
  Args:
299
341
  config: Configuration object (dict or object with dict method)
342
+ jwt_provider: Function that returns the current JWT token
300
343
  **kwargs: Additional keyword arguments to pass to initialize
301
344
  """
302
345
  # Use the global tracer instance instead of getting singleton
@@ -330,7 +373,7 @@ class TracingCore:
330
373
  tracing_kwargs.update(kwargs)
331
374
 
332
375
  # Initialize with the extracted configuration
333
- instance.initialize(**tracing_kwargs)
376
+ instance.initialize(jwt_provider=jwt_provider, **tracing_kwargs)
334
377
 
335
378
  # Span types are registered in the constructor
336
379
  # No need to register them here anymore
@@ -0,0 +1,28 @@
1
+ class ClassPropertyDescriptor(object):
2
+ def __init__(self, fget, fset=None):
3
+ self.fget = fget
4
+ self.fset = fset
5
+
6
+ def __get__(self, obj, klass=None):
7
+ if klass is None:
8
+ klass = type(obj)
9
+ return self.fget.__get__(obj, klass)()
10
+
11
+ def __set__(self, obj, value):
12
+ if not self.fset:
13
+ raise AttributeError("can't set attribute")
14
+ type_ = type(obj)
15
+ return self.fset.__get__(obj, type_)(value)
16
+
17
+ def setter(self, func):
18
+ if not isinstance(func, (classmethod, staticmethod)):
19
+ func = classmethod(func)
20
+ self.fset = func
21
+ return self
22
+
23
+
24
+ def classproperty(func):
25
+ if not isinstance(func, (classmethod, staticmethod)):
26
+ func = classmethod(func)
27
+
28
+ return ClassPropertyDescriptor(func)