Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 27 additions & 11 deletions agentops/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
# For backwards compatibility
from agentops.legacy import (
start_session,
end_session,
track_agent,
track_tool,
end_all_sessions,
Session,
ToolEvent,
ErrorEvent,
ActionEvent,
LLMEvent,
) # type: ignore

from typing import List, Optional, Union
from agentops.client import Client

Expand All @@ -16,18 +30,18 @@ def get_client() -> Client:
def record(event):
"""
Legacy function to record an event. This is kept for backward compatibility.

In the current version, this simply sets the end_timestamp on the event.

Args:
event: The event to record
"""
from agentops.helpers.time import get_ISO_time

# TODO: Manual timestamp assignment is a temporary fix; should use proper event lifecycle
if event and hasattr(event, 'end_timestamp'):
if event and hasattr(event, "end_timestamp"):
event.end_timestamp = get_ISO_time()

return event


Expand Down Expand Up @@ -77,7 +91,7 @@ def init(
**kwargs: Additional configuration parameters to be passed to the client.
"""
global _client

# Merge tags and default_tags if both are provided
merged_tags = None
if tags and default_tags:
Expand Down Expand Up @@ -128,7 +142,7 @@ def configure(**kwargs):
- exporter_endpoint: Endpoint for the exporter
"""
global _client

# List of valid parameters that can be passed to configure
valid_params = {
"api_key",
Expand Down Expand Up @@ -158,10 +172,6 @@ def configure(**kwargs):
_client.configure(**kwargs)


# For backwards compatibility

from agentops.legacy import * # type: ignore

__all__ = [
"init",
"configure",
Expand All @@ -171,4 +181,10 @@ def configure(**kwargs):
"end_session",
"track_agent",
"track_tool",
"end_all_sessions",
"Session",
"ToolEvent",
"ErrorEvent",
"ActionEvent",
"LLMEvent",
]
2 changes: 1 addition & 1 deletion agentops/client/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
This module provides the client for the AgentOps API.
"""

from typing import Dict, Optional, Type, TypeVar, cast
from typing import Dict, Type, TypeVar, cast

from agentops.client.api.base import BaseApiClient
from agentops.client.api.types import AuthTokenResponse
Expand Down
1 change: 0 additions & 1 deletion agentops/client/api/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

import requests

from agentops.client.api.types import AuthTokenResponse
from agentops.client.http.http_client import HttpClient


Expand Down
2 changes: 1 addition & 1 deletion agentops/client/api/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ class AuthTokenResponse(TypedDict):

class UploadedObjectResponse(BaseModel):
"""Response from the v4/objects/upload endpoint"""

url: str
size: int

5 changes: 1 addition & 4 deletions agentops/client/api/versions/v3.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,12 @@
This module provides the client for the V3 version of the AgentOps API.
"""

from typing import Any, Dict, List, Optional

import requests

from agentops.client.api.base import BaseApiClient
from agentops.client.api.types import AuthTokenResponse
from agentops.exceptions import ApiServerException
from agentops.logging import logger


class V3Client(BaseApiClient):
"""Client for the AgentOps V3 API"""

Expand Down
24 changes: 12 additions & 12 deletions agentops/client/api/versions/v4.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

This module provides the client for the V4 version of the AgentOps API.
"""

from typing import Optional, Union, Dict

from agentops.client.api.base import BaseApiClient
Expand All @@ -12,8 +13,9 @@

class V4Client(BaseApiClient):
"""Client for the AgentOps V4 API"""

auth_token: str

def set_auth_token(self, token: str):
"""
Set the authentication token for API requests.
Expand All @@ -22,7 +24,7 @@ def set_auth_token(self, token: str):
token: The authentication token to set
"""
self.auth_token = token

def prepare_headers(self, custom_headers: Optional[Dict[str, str]] = None) -> Dict[str, str]:
"""
Prepare headers for API requests.
Expand All @@ -42,17 +44,17 @@ def prepare_headers(self, custom_headers: Optional[Dict[str, str]] = None) -> Di
def upload_object(self, body: Union[str, bytes]) -> UploadedObjectResponse:
"""
Upload an object to the API and return the response.

Args:
body: The object to upload, either as a string or bytes.
Returns:
UploadedObjectResponse: The response from the API after upload.
"""
if isinstance(body, bytes):
body = body.decode("utf-8")

response = self.post("/v4/objects/upload/", body, self.prepare_headers())

if response.status_code != 200:
error_msg = f"Upload failed: {response.status_code}"
try:
Expand All @@ -62,28 +64,27 @@ def upload_object(self, body: Union[str, bytes]) -> UploadedObjectResponse:
except Exception:
pass
raise ApiServerException(error_msg)

try:
response_data = response.json()
return UploadedObjectResponse(**response_data)
except Exception as e:
raise ApiServerException(f"Failed to process upload response: {str(e)}")


def upload_logfile(self, body: Union[str, bytes], trace_id: int) -> UploadedObjectResponse:
"""
Upload an log file to the API and return the response.

Args:
body: The log file to upload, either as a string or bytes.
Returns:
UploadedObjectResponse: The response from the API after upload.
"""
if isinstance(body, bytes):
body = body.decode("utf-8")

response = self.post("/v4/logs/upload/", body, {**self.prepare_headers(), "Trace-Id": str(trace_id)})

if response.status_code != 200:
error_msg = f"Upload failed: {response.status_code}"
try:
Expand All @@ -93,10 +94,9 @@ def upload_logfile(self, body: Union[str, bytes], trace_id: int) -> UploadedObje
except Exception:
pass
raise ApiServerException(error_msg)

try:
response_data = response.json()
return UploadedObjectResponse(**response_data)
except Exception as e:
raise ApiServerException(f"Failed to process upload response: {str(e)}")

14 changes: 8 additions & 6 deletions agentops/client/client.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
from typing import List, Optional, Union
import atexit

from agentops.client.api import ApiClient
from agentops.config import Config
from agentops.exceptions import AgentOpsClientNotInitializedException, NoApiKeyException, NoSessionException
from agentops.exceptions import NoApiKeyException
from agentops.instrumentation import instrument_all
from agentops.logging import logger
from agentops.logging.config import configure_logging, intercept_opentelemetry_logging
Expand All @@ -15,23 +14,26 @@
# Single atexit handler registered flag
_atexit_registered = False


def _end_active_session():
"""Global handler to end the active session during shutdown"""
global _active_session
if _active_session is not None:
logger.debug("Auto-ending active session during shutdown")
try:
from agentops.legacy import end_session

end_session(_active_session)
except Exception as e:
logger.warning(f"Error ending active session during shutdown: {e}")
# Final fallback: try to end the span directly
try:
if hasattr(_active_session, 'span') and hasattr(_active_session.span, 'end'):
if hasattr(_active_session, "span") and hasattr(_active_session.span, "end"):
_active_session.span.end()
except:
pass


class Client:
"""Singleton client for AgentOps service"""

Expand Down Expand Up @@ -70,7 +72,7 @@ def init(self, **kwargs):
response = self.api.v3.fetch_auth_token(self.config.api_key)
if response is None:
return

# Save the bearer for use with the v4 API
self.api.v4.set_auth_token(response["token"])

Expand Down Expand Up @@ -102,11 +104,11 @@ def init(self, **kwargs):
session = start_session(tags=list(self.config.default_tags))
else:
session = start_session()

# Register this session globally
global _active_session
_active_session = session

return session

def configure(self, **kwargs):
Expand Down
5 changes: 1 addition & 4 deletions agentops/client/http/http_adapter.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
from typing import Callable, Dict, Optional, Union
from typing import Optional

from requests.adapters import HTTPAdapter
from urllib3.util import Retry

# from agentops.client.auth_manager import AuthManager
from agentops.exceptions import AgentOpsApiJwtExpiredException, ApiServerException
from agentops.logging import logger
from agentops.client.api.types import AuthTokenResponse


class BaseHTTPAdapter(HTTPAdapter):
Expand Down
4 changes: 1 addition & 3 deletions agentops/client/http/http_client.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
from typing import Callable, Dict, Optional
from typing import Dict, Optional

import requests

from agentops.client.http.http_adapter import BaseHTTPAdapter
from agentops.exceptions import AgentOpsApiJwtExpiredException, ApiServerException
from agentops.logging import logger
from agentops.semconv import ResourceAttributes


class HttpClient:
Expand Down
12 changes: 5 additions & 7 deletions agentops/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
import logging
import os
import sys
from dataclasses import asdict, dataclass, field
from typing import Any, List, Optional, Set, TypedDict, Union
from dataclasses import dataclass, field
from typing import List, Optional, Set, TypedDict, Union
from uuid import UUID

from opentelemetry.sdk.trace import SpanProcessor
Expand All @@ -13,8 +13,6 @@
from agentops.helpers.env import get_env_bool, get_env_int, get_env_list
from agentops.helpers.serialization import AgentOpsJSONEncoder

from .logging.config import logger


class ConfigDict(TypedDict):
api_key: Optional[str]
Expand Down Expand Up @@ -55,7 +53,7 @@ class Config:
default_factory=lambda: get_env_int("AGENTOPS_MAX_WAIT_TIME", 5000),
metadata={"description": "Maximum time in milliseconds to wait for API responses"},
)

export_flush_interval: int = field(
default_factory=lambda: get_env_int("AGENTOPS_EXPORT_FLUSH_INTERVAL", 1000),
metadata={"description": "Time interval in milliseconds between automatic exports of telemetry data"},
Expand Down Expand Up @@ -158,13 +156,13 @@ def configure(

if endpoint is not None:
self.endpoint = endpoint

if app_url is not None:
self.app_url = app_url

if max_wait_time is not None:
self.max_wait_time = max_wait_time

if export_flush_interval is not None:
self.export_flush_interval = export_flush_interval

Expand Down
3 changes: 0 additions & 3 deletions agentops/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
from agentops.logging import logger


class MultiSessionException(Exception):
def __init__(self, message):
super().__init__(message)
Expand Down
11 changes: 6 additions & 5 deletions agentops/helpers/dashboard.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Helpers for interacting with the AgentOps dashboard.
Helpers for interacting with the AgentOps dashboard.
"""

from typing import Union
from termcolor import colored
from opentelemetry.sdk.trace import Span, ReadableSpan
Expand All @@ -18,16 +19,17 @@ def get_trace_url(span: Union[Span, ReadableSpan]) -> str:
The session URL.
"""
trace_id: Union[int, str] = span.context.trace_id

# Convert trace_id to hex string if it's not already
# We don't add dashes to this to format it as a UUID since the dashboard doesn't either
if isinstance(trace_id, int):
trace_id = format(trace_id, "032x")

# Get the app_url from the config - import here to avoid circular imports
from agentops import get_client

app_url = get_client().config.app_url

return f"{app_url}/sessions?trace_id={trace_id}"


Expand All @@ -40,4 +42,3 @@ def log_trace_url(span: Union[Span, ReadableSpan]) -> None:
"""
session_url = get_trace_url(span)
logger.info(colored(f"\x1b[34mSession Replay: {session_url}\x1b[0m", "blue"))

Loading
Loading