Skip to content
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
953305e
Update attribute extraction to support dict as well as object.
tcdent Mar 25, 2025
ab8dad1
Adjust tests to match serialization format of `list[str]`. Patch JSON…
tcdent Mar 25, 2025
fd4b4f8
Instrumentor and wrappers for OpenAI responses.
tcdent Mar 25, 2025
4c6b12f
Collect base usage attributes, too.
tcdent Mar 25, 2025
0ebd74d
Merge branch 'main' into fix-openai-agents-counts
tcdent Mar 25, 2025
5589ec6
Move Response attribute parsing to openai module. Move common attribu…
tcdent Mar 25, 2025
f2dca86
Include tags in parent span. Helpers for accessing global config and …
tcdent Mar 25, 2025
e1e3506
Add tags to an example.
tcdent Mar 25, 2025
d7a9f6f
Remove duplicate library attributes.
tcdent Mar 25, 2025
f30f43a
Pass OpenAI responses objects through our new instrumentor.
tcdent Mar 25, 2025
d98a5b0
Merge branch 'fix-openai-agents-counts' into openai-responses
tcdent Mar 25, 2025
e942f94
Incorporate common attributes, too.
tcdent Mar 26, 2025
9b2c5f7
Add indexed PROMPT semconv to MessageAttributes. Provide reusable wra…
tcdent Mar 26, 2025
d58af58
Type checking.
tcdent Mar 26, 2025
9d2d493
Test coverage for instrumentation.common
tcdent Mar 26, 2025
12b559b
Type in method def should be string in case of missing import.
tcdent Mar 26, 2025
9d77845
Wrap third party module imports from openai in try except block
tcdent Mar 26, 2025
d5cdb57
OpenAI instrumentation tests. (Relocated to openai_core to avoid impo…
tcdent Mar 26, 2025
0df12c2
Merge branch 'main' into openai-responses
tcdent Mar 26, 2025
c430ad9
Merge branch 'openai-responses' into agents-voice
tcdent Mar 27, 2025
fe8932c
OpenAI Agents voice support
tcdent Mar 28, 2025
ef91208
Additional voice-specific fields.
tcdent Mar 31, 2025
2830999
update pyproject.toml and uv.lock to use correct dependency
dot-agi Apr 1, 2025
bebc809
Remove `upload_object` helper.
tcdent Apr 1, 2025
b6cb5dd
Remove tag helpers.
tcdent Apr 1, 2025
869a8af
Move agents example scripts.
tcdent Apr 1, 2025
6427682
Merge branch 'main' into agents-voice
dot-agi Apr 2, 2025
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
11 changes: 11 additions & 0 deletions agentops/client/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from agentops.client.api.base import BaseApiClient
from agentops.client.api.types import AuthTokenResponse
from agentops.client.api.versions.v3 import V3Client
from agentops.client.api.versions.v4 import V4Client

# Define a type variable for client classes
T = TypeVar("T", bound=BaseApiClient)
Expand Down Expand Up @@ -44,6 +45,16 @@ def v3(self) -> V3Client:
"""
return self._get_client("v3", V3Client)

@property
def v4(self) -> V4Client:
"""
Get the V4 API client.

Returns:
The V4 API client
"""
return self._get_client("v4", V4Client)

def _get_client(self, version: str, client_class: Type[T]) -> T:
"""
Get or create a version-specific client.
Expand Down
8 changes: 8 additions & 0 deletions agentops/client/api/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,18 @@
"""

from typing import TypedDict
from pydantic import BaseModel


class AuthTokenResponse(TypedDict):
"""Response from the auth/token endpoint"""

token: str
project_id: str


class UploadedObjectResponse(BaseModel):
Comment thread
dot-agi marked this conversation as resolved.
"""Response from the v4/objects/upload endpoint"""
url: str
size: int

3 changes: 2 additions & 1 deletion agentops/client/api/versions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
"""

from agentops.client.api.versions.v3 import V3Client
from agentops.client.api.versions.v4 import V4Client

__all__ = ["V3Client"]
__all__ = ["V3Client", "V4Client"]
71 changes: 71 additions & 0 deletions agentops/client/api/versions/v4.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"""
V4 API client for the AgentOps API.

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
from agentops.exceptions import ApiServerException
from agentops.client.api.types import UploadedObjectResponse


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.

Args:
token: The authentication token to set
"""
self.auth_token = token

def prepare_headers(self, custom_headers: Optional[Dict[str, str]] = None) -> Dict[str, str]:
Comment thread
tcdent marked this conversation as resolved.
"""
Prepare headers for API requests.

Args:
custom_headers: Additional headers to include
Returns:
Headers dictionary with standard headers and any custom headers
"""
headers = {

Check warning on line 35 in agentops/client/api/versions/v4.py

View check run for this annotation

Codecov / codecov/patch

agentops/client/api/versions/v4.py#L35

Added line #L35 was not covered by tests
"Authorization": f"Bearer {self.auth_token}",
}
if custom_headers:
headers.update(custom_headers)
return headers

Check warning on line 40 in agentops/client/api/versions/v4.py

View check run for this annotation

Codecov / codecov/patch

agentops/client/api/versions/v4.py#L38-L40

Added lines #L38 - L40 were not covered by tests

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")

Check warning on line 52 in agentops/client/api/versions/v4.py

View check run for this annotation

Codecov / codecov/patch

agentops/client/api/versions/v4.py#L51-L52

Added lines #L51 - L52 were not covered by tests

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

Check warning on line 54 in agentops/client/api/versions/v4.py

View check run for this annotation

Codecov / codecov/patch

agentops/client/api/versions/v4.py#L54

Added line #L54 was not covered by tests

if response.status_code != 200:
error_msg = f"Upload failed: {response.status_code}"
try:
error_data = response.json()
if "error" in error_data:
error_msg = error_data["error"]
except Exception:
pass
raise ApiServerException(error_msg)

Check warning on line 64 in agentops/client/api/versions/v4.py

View check run for this annotation

Codecov / codecov/patch

agentops/client/api/versions/v4.py#L56-L64

Added lines #L56 - L64 were not covered by tests

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

Check warning on line 70 in agentops/client/api/versions/v4.py

View check run for this annotation

Codecov / codecov/patch

agentops/client/api/versions/v4.py#L66-L70

Added lines #L66 - L70 were not covered by tests

3 changes: 3 additions & 0 deletions agentops/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ def init(self, **kwargs):
# Prefetch JWT token if enabled
# TODO: Move this validation somewhere else (and integrate with self.config.prefetch_jwt_token once we have a solution to that)
response = self.api.v3.fetch_auth_token(self.config.api_key)

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

# Initialize TracingCore with the current configuration and project_id
tracing_config = self.config.dict()
Expand Down
5 changes: 5 additions & 0 deletions agentops/helpers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
from .version import get_agentops_version, check_agentops_update
from .debug import debug_print_function_params
from .env import get_env_bool, get_env_int, get_env_list
from .config import get_config, get_tags_from_config
from .objects import upload_object

__all__ = [
"get_ISO_time",
Expand All @@ -45,4 +47,7 @@
"get_env_bool",
"get_env_int",
"get_env_list",
"get_config",
"get_tags_from_config",
"upload_object",
]
31 changes: 31 additions & 0 deletions agentops/helpers/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""Helper functions for accessing configuration values.

This module provides utility functions for accessing configuration values from
the global Config object in a safe way.
"""

from typing import List, Any

from agentops.config import Config


def get_config() -> Config:
"""Get the global configuration object from the Client singleton.

Returns:
The Config instance from the global Client
"""
from agentops import get_client
return get_client().config


def get_tags_from_config() -> List[str]:
"""Get tags from the global configuration.

Returns:
List of tags if they exist in the configuration, or empty list
"""
config = get_config()
if config.default_tags:
return list(config.default_tags)
return [] # Return empty list for empty tags set
Comment thread
tcdent marked this conversation as resolved.
Outdated
14 changes: 14 additions & 0 deletions agentops/helpers/objects.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from agentops.client.api.types import UploadedObjectResponse

Check warning on line 4 in agentops/helpers/objects.py

View check run for this annotation

Codecov / codecov/patch

agentops/helpers/objects.py#L4

Added line #L4 was not covered by tests


def upload_object(body: bytes) -> 'UploadedObjectResponse':
"""Upload an object to the agentops server."""
from agentops import get_client

Check warning on line 9 in agentops/helpers/objects.py

View check run for this annotation

Codecov / codecov/patch

agentops/helpers/objects.py#L9

Added line #L9 was not covered by tests

client = get_client()
return client.api.v4.upload_object(body)

Check warning on line 12 in agentops/helpers/objects.py

View check run for this annotation

Codecov / codecov/patch

agentops/helpers/objects.py#L11-L12

Added lines #L11 - L12 were not covered by tests
Comment thread
tcdent marked this conversation as resolved.
Outdated


4 changes: 2 additions & 2 deletions agentops/instrumentation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class InstrumentorLoader:
We use the `provider_import_name` to determine if the library is installed i
n the environment.

`modue_name` is the name of the module to import from.
`module_name` is the name of the module to import from.
`class_name` is the name of the class to instantiate from the module.
`provider_import_name` is the name of the package to check for availability.
"""
Expand Down Expand Up @@ -53,7 +53,7 @@ def get_instance(self) -> BaseInstrumentor:

available_instrumentors: list[InstrumentorLoader] = [
InstrumentorLoader(
module_name="opentelemetry.instrumentation.openai",
module_name="agentops.instrumentation.openai",
class_name="OpenAIInstrumentor",
provider_import_name="openai",
),
Expand Down
65 changes: 65 additions & 0 deletions agentops/instrumentation/common/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# AgentOps Instrumentation Common Module

The `agentops.instrumentation.common` module provides shared utilities for OpenTelemetry instrumentation across different LLM service providers.

## Core Components

### Attribute Handler Example

Attribute handlers extract data from method inputs and outputs:

```python
from typing import Optional, Any, Tuple, Dict
from agentops.instrumentation.common.attributes import AttributeMap
from agentops.semconv import SpanAttributes

def my_attribute_handler(args: Optional[Tuple] = None, kwargs: Optional[Dict] = None, return_value: Optional[Any] = None) -> AttributeMap:
attributes = {}

# Extract attributes from kwargs (method inputs)
if kwargs:
if "model" in kwargs:
attributes[SpanAttributes.MODEL_NAME] = kwargs["model"]
# ...

# Extract attributes from return value (method outputs)
if return_value:
if hasattr(return_value, "model"):
attributes[SpanAttributes.LLM_RESPONSE_MODEL] = return_value.model
# ...

return attributes
```

### `WrapConfig` Class

Config object defining how a method should be wrapped:

```python
from agentops.instrumentation.common.wrappers import WrapConfig
from opentelemetry.trace import SpanKind

config = WrapConfig(
trace_name="llm.completion", # Name that will appear in trace spans
package="openai.resources", # Path to the module containing the class
class_name="Completions", # Name of the class containing the method
method_name="create", # Name of the method to wrap
handler=my_attribute_handler, # Function that extracts attributes
span_kind=SpanKind.CLIENT # Type of span to create
)
```

### Wrapping/Unwrapping Methods

```python
from opentelemetry.trace import get_tracer
from agentops.instrumentation.common.wrappers import wrap, unwrap

# Create a tracer and wrap a method
tracer = get_tracer("openai", "0.0.0")
wrap(config, tracer)

# Later, unwrap the method
unwrap(config)
```

6 changes: 6 additions & 0 deletions agentops/instrumentation/common/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from .attributes import AttributeMap, _extract_attributes_from_mapping

__all__ = [
"AttributeMap",
"_extract_attributes_from_mapping",
]
Loading
Loading