Skip to content

Commit 2676c56

Browse files
authored
Merge branch 'main' into smolagents-instrumentation
2 parents 835a1e3 + aa3f549 commit 2676c56

31 files changed

Lines changed: 3187 additions & 505 deletions

agentops/__init__.py

Lines changed: 58 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,29 @@
1414

1515
from typing import List, Optional, Union, Dict, Any
1616
from agentops.client import Client
17-
from agentops.sdk.core import TracingCore, TraceContext
18-
from agentops.sdk.decorators import trace, session, agent, task, workflow, operation
17+
from agentops.sdk.core import TraceContext, tracer
18+
from agentops.sdk.decorators import trace, session, agent, task, workflow, operation, tool
19+
from agentops.enums import TraceState, SUCCESS, ERROR, UNSET
20+
from opentelemetry.trace.status import StatusCode
1921

2022
from agentops.logging.config import logger
23+
import threading
2124

22-
# Client global instance; one per process runtime
23-
_client = Client()
25+
# Thread-safe client management
26+
_client_lock = threading.Lock()
27+
_client = None
2428

2529

2630
def get_client() -> Client:
27-
"""Get the singleton client instance"""
31+
"""Get the singleton client instance in a thread-safe manner"""
2832
global _client
2933

34+
# Double-checked locking pattern for thread safety
35+
if _client is None:
36+
with _client_lock:
37+
if _client is None:
38+
_client = Client()
39+
3040
return _client
3141

3242

@@ -106,24 +116,31 @@ def init(
106116
elif default_tags:
107117
merged_tags = default_tags
108118

109-
return _client.init(
110-
api_key=api_key,
111-
endpoint=endpoint,
112-
app_url=app_url,
113-
max_wait_time=max_wait_time,
114-
max_queue_size=max_queue_size,
115-
default_tags=merged_tags,
116-
trace_name=trace_name,
117-
instrument_llm_calls=instrument_llm_calls,
118-
auto_start_session=auto_start_session,
119-
auto_init=auto_init,
120-
skip_auto_end_session=skip_auto_end_session,
121-
env_data_opt_out=env_data_opt_out,
122-
log_level=log_level,
123-
fail_safe=fail_safe,
124-
exporter_endpoint=exporter_endpoint,
119+
# Prepare initialization arguments
120+
init_kwargs = {
121+
"api_key": api_key,
122+
"endpoint": endpoint,
123+
"app_url": app_url,
124+
"max_wait_time": max_wait_time,
125+
"max_queue_size": max_queue_size,
126+
"default_tags": merged_tags,
127+
"trace_name": trace_name,
128+
"instrument_llm_calls": instrument_llm_calls,
129+
"auto_start_session": auto_start_session,
130+
"auto_init": auto_init,
131+
"skip_auto_end_session": skip_auto_end_session,
132+
"env_data_opt_out": env_data_opt_out,
133+
"log_level": log_level,
134+
"fail_safe": fail_safe,
135+
"exporter_endpoint": exporter_endpoint,
125136
**kwargs,
126-
)
137+
}
138+
139+
# Get the current client instance (creates new one if needed)
140+
client = get_client()
141+
142+
# Initialize the client directly
143+
return client.init(**init_kwargs)
127144

128145

129146
def configure(**kwargs):
@@ -173,7 +190,8 @@ def configure(**kwargs):
173190
if invalid_params:
174191
logger.warning(f"Invalid configuration parameters: {invalid_params}")
175192

176-
_client.configure(**kwargs)
193+
client = get_client()
194+
client.configure(**kwargs)
177195

178196

179197
def start_trace(
@@ -190,25 +208,26 @@ def start_trace(
190208
Returns:
191209
A TraceContext object containing the span and context token, or None if SDK not initialized.
192210
"""
193-
tracing_core = TracingCore.get_instance()
194-
if not tracing_core.initialized:
211+
if not tracer.initialized:
195212
# Optionally, attempt to initialize the client if not already, or log a more severe warning.
196213
# For now, align with legacy start_session that would try to init.
197214
# However, explicit init is preferred before starting traces.
198215
logger.warning("AgentOps SDK not initialized. Attempting to initialize with defaults before starting trace.")
199216
try:
200217
init() # Attempt to initialize with environment variables / defaults
201-
if not tracing_core.initialized:
218+
if not tracer.initialized:
202219
logger.error("SDK initialization failed. Cannot start trace.")
203220
return None
204221
except Exception as e:
205222
logger.error(f"SDK auto-initialization failed during start_trace: {e}. Cannot start trace.")
206223
return None
207224

208-
return tracing_core.start_trace(trace_name=trace_name, tags=tags)
225+
return tracer.start_trace(trace_name=trace_name, tags=tags)
209226

210227

211-
def end_trace(trace_context: Optional[TraceContext] = None, end_state: str = "Success") -> None:
228+
def end_trace(
229+
trace_context: Optional[TraceContext] = None, end_state: Union[TraceState, StatusCode, str] = TraceState.SUCCESS
230+
) -> None:
212231
"""
213232
Ends a trace (its root span) and finalizes it.
214233
If no trace_context is provided, ends all active session spans.
@@ -217,11 +236,10 @@ def end_trace(trace_context: Optional[TraceContext] = None, end_state: str = "Su
217236
trace_context: The TraceContext object returned by start_trace. If None, ends all active traces.
218237
end_state: The final state of the trace (e.g., "Success", "Failure", "Error").
219238
"""
220-
tracing_core = TracingCore.get_instance()
221-
if not tracing_core.initialized:
239+
if not tracer.initialized:
222240
logger.warning("AgentOps SDK not initialized. Cannot end trace.")
223241
return
224-
tracing_core.end_trace(trace_context=trace_context, end_state=end_state)
242+
tracer.end_trace(trace_context=trace_context, end_state=end_state)
225243

226244

227245
__all__ = [
@@ -247,4 +265,13 @@ def end_trace(trace_context: Optional[TraceContext] = None, end_state: str = "Su
247265
"task",
248266
"workflow",
249267
"operation",
268+
"tracer",
269+
"tool",
270+
# Trace state enums
271+
"TraceState",
272+
"SUCCESS",
273+
"ERROR",
274+
"UNSET",
275+
# OpenTelemetry status codes (for advanced users)
276+
"StatusCode",
250277
]

agentops/client/client.py

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from agentops.instrumentation import instrument_all
88
from agentops.logging import logger
99
from agentops.logging.config import configure_logging, intercept_opentelemetry_logging
10-
from agentops.sdk.core import TracingCore, TraceContext
10+
from agentops.sdk.core import TraceContext, tracer
1111
from agentops.legacy import Session
1212

1313
# Global variables to hold the client's auto-started trace and its legacy session wrapper
@@ -24,10 +24,9 @@ def _end_init_trace_atexit():
2424
if _client_init_trace_context is not None:
2525
logger.debug("Auto-ending client's init trace during shutdown.")
2626
try:
27-
# Use TracingCore to end the trace directly
28-
tracing_core = TracingCore.get_instance()
29-
if tracing_core.initialized and _client_init_trace_context.span.is_recording():
30-
tracing_core.end_trace(_client_init_trace_context, end_state="Shutdown")
27+
# Use global tracer to end the trace directly
28+
if tracer.initialized and _client_init_trace_context.span.is_recording():
29+
tracer.end_trace(_client_init_trace_context, end_state="Shutdown")
3130
except Exception as e:
3231
logger.warning(f"Error ending client's init trace during shutdown: {e}")
3332
finally:
@@ -83,7 +82,7 @@ def init(self, **kwargs: Any) -> None: # Return type updated to None
8382
self._initialized = False
8483
if self._init_trace_context and self._init_trace_context.span.is_recording():
8584
logger.warning("Ending previously auto-started trace due to re-initialization.")
86-
TracingCore.get_instance().end_trace(self._init_trace_context, "Reinitialized")
85+
tracer.end_trace(self._init_trace_context, "Reinitialized")
8786
self._init_trace_context = None
8887
self._legacy_session_for_init_trace = None
8988

@@ -105,7 +104,7 @@ def init(self, **kwargs: Any) -> None: # Return type updated to None
105104
try:
106105
response = self.api.v3.fetch_auth_token(self.config.api_key)
107106
if response is None:
108-
# If auth fails, we cannot proceed with TracingCore initialization that depends on project_id
107+
# If auth fails, we cannot proceed with tracer initialization that depends on project_id
109108
logger.error("Failed to fetch auth token. AgentOps SDK will not be initialized.")
110109
return None # Explicitly return None if auth fails
111110
except Exception as e:
@@ -118,8 +117,7 @@ def init(self, **kwargs: Any) -> None: # Return type updated to None
118117
tracing_config = self.config.dict()
119118
tracing_config["project_id"] = response["project_id"]
120119

121-
tracing_core = TracingCore.get_instance()
122-
tracing_core.initialize_from_config(tracing_config, jwt=response["token"])
120+
tracer.initialize_from_config(tracing_config, jwt=response["token"])
123121

124122
if self.config.instrument_llm_calls:
125123
instrument_all()
@@ -136,7 +134,7 @@ def init(self, **kwargs: Any) -> None: # Return type updated to None
136134
if self._init_trace_context is None or not self._init_trace_context.span.is_recording():
137135
logger.debug("Auto-starting init trace.")
138136
trace_name = self.config.trace_name or "default"
139-
self._init_trace_context = tracing_core.start_trace(
137+
self._init_trace_context = tracer.start_trace(
140138
trace_name=trace_name,
141139
tags=list(self.config.default_tags) if self.config.default_tags else None,
142140
is_init_trace=True,
@@ -163,9 +161,9 @@ def init(self, **kwargs: Any) -> None: # Return type updated to None
163161

164162
else:
165163
logger.error("Failed to start the auto-init trace.")
166-
# Even if auto-start fails, core services up to TracingCore might be initialized.
167-
# Set self.initialized to True if TracingCore is up, but return None.
168-
self._initialized = tracing_core.initialized
164+
# Even if auto-start fails, core services up to the tracer might be initialized.
165+
# Set self.initialized to True if tracer is up, but return None.
166+
self._initialized = tracer.initialized
169167
return None # Failed to start trace
170168

171169
self._initialized = True # Successfully initialized and auto-trace started (if configured)

agentops/enums.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""
2+
AgentOps enums for user-friendly API.
3+
4+
This module provides simple enums that users can import from agentops
5+
without needing to know about OpenTelemetry internals.
6+
"""
7+
8+
from enum import Enum
9+
from opentelemetry.trace.status import StatusCode
10+
11+
12+
class TraceState(Enum):
13+
"""
14+
Enum for trace end states.
15+
16+
This provides a user-friendly interface that maps to OpenTelemetry StatusCode internally.
17+
Users can simply use agentops.TraceState.SUCCESS instead of importing OpenTelemetry.
18+
"""
19+
20+
SUCCESS = StatusCode.OK
21+
ERROR = StatusCode.ERROR
22+
UNSET = StatusCode.UNSET
23+
24+
def __str__(self) -> str:
25+
"""Return the name for string representation."""
26+
return self.name
27+
28+
def to_status_code(self) -> StatusCode:
29+
"""Convert to OpenTelemetry StatusCode."""
30+
return self.value
31+
32+
33+
# For backward compatibility, also provide these as module-level constants
34+
SUCCESS = TraceState.SUCCESS
35+
ERROR = TraceState.ERROR
36+
UNSET = TraceState.UNSET

agentops/helpers/system.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,46 @@
1010
from agentops.helpers.version import get_agentops_version
1111

1212

13+
def get_imported_libraries():
14+
"""
15+
Get the top-level imported libraries in the current script.
16+
17+
Returns:
18+
list: List of imported libraries
19+
"""
20+
user_libs = []
21+
22+
builtin_modules = {
23+
"builtins",
24+
"sys",
25+
"os",
26+
"_thread",
27+
"abc",
28+
"io",
29+
"re",
30+
"types",
31+
"collections",
32+
"enum",
33+
"math",
34+
"datetime",
35+
"time",
36+
"warnings",
37+
}
38+
39+
try:
40+
main_module = sys.modules.get("__main__")
41+
if main_module and hasattr(main_module, "__dict__"):
42+
for name, obj in main_module.__dict__.items():
43+
if isinstance(obj, type(sys)) and hasattr(obj, "__name__"):
44+
mod_name = obj.__name__.split(".")[0]
45+
if mod_name and not mod_name.startswith("_") and mod_name not in builtin_modules:
46+
user_libs.append(mod_name)
47+
except Exception as e:
48+
logger.debug(f"Error getting imports: {e}")
49+
50+
return user_libs
51+
52+
1353
def get_sdk_details():
1454
try:
1555
return {

0 commit comments

Comments
 (0)