Skip to content
Closed
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
159 changes: 147 additions & 12 deletions agentops/instrumentation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,164 @@ This package provides OpenTelemetry instrumentation for various LLM providers an

## Available Instrumentors

- OpenAI (`v0.27.0+` and `v1.0.0+`)
- **OpenAI** (`v0.27.0+` and `v1.0.0+`)
- **Anthropic** (`v0.7.0+`)
- **Google GenAI** (`v0.1.0+`)
- **IBM WatsonX AI** (`v0.1.0+`)
- **CrewAI** (`v0.56.0+`)
- **AG2/AutoGen** (`v0.3.2+`)
- **Google ADK** (`v0.1.0+`)
- **Agno** (`v0.0.1+`)
- **Mem0** (`v0.1.0+`)
- **smolagents** (`v0.1.0+`)

## Common Module Usage

## Usage
The `agentops.instrumentation.common` module provides shared utilities for creating instrumentations:

### OpenAI Instrumentation
### Base Instrumentor

Use `CommonInstrumentor` for creating new instrumentations:

```python
from agentops.instrumentation.common import CommonInstrumentor, InstrumentorConfig, WrapConfig

class MyInstrumentor(CommonInstrumentor):
def __init__(self):
config = InstrumentorConfig(
library_name="my-library",
library_version="1.0.0",
wrapped_methods=[
WrapConfig(
trace_name="my.method",
package="my_library.module",
class_name="MyClass",
method_name="my_method",
handler=my_attribute_handler
)
],
dependencies=["my-library >= 1.0.0"]
)
super().__init__(config)
```

### Attribute Handlers

Create attribute handlers to extract data from method calls:

```python
from agentops.instrumentation.common import AttributeMap

def my_attribute_handler(args=None, kwargs=None, return_value=None) -> AttributeMap:
attributes = {}

if kwargs and "model" in kwargs:
attributes["llm.request.model"] = kwargs["model"]

if return_value and hasattr(return_value, "usage"):
attributes["llm.usage.total_tokens"] = return_value.usage.total_tokens

return attributes
```

### Span Management

Use the span management utilities for consistent span creation:

```python
from agentops.instrumentation.common import create_span, SpanAttributeManager

# Create an attribute manager
attr_manager = SpanAttributeManager(service_name="my-service")

# Use the create_span context manager
with create_span(
tracer,
"my.operation",
attributes={"my.attribute": "value"},
attribute_manager=attr_manager
) as span:
# Your operation code here
pass
```

### Token Counting

Use the token counting utilities for consistent token usage extraction:

```python
from opentelemetry.instrumentation.openai import OpenAIInstrumentor
from agentops.instrumentation.common import TokenUsageExtractor, set_token_usage_attributes

# Extract token usage from a response
usage = TokenUsageExtractor.extract_from_response(response)

# Set token usage attributes on a span
set_token_usage_attributes(span, response)
```

### Streaming Support

from agentops.telemetry import get_tracer_provider()
Use streaming utilities for handling streaming responses:

# Initialize and instrument
instrumentor = OpenAIInstrumentor(
enrich_assistant=True, # Include assistant messages in spans
enrich_token_usage=True, # Include token usage in spans
enable_trace_context_propagation=True, # Enable trace context propagation
```python
from agentops.instrumentation.common import create_stream_wrapper_factory, StreamingResponseHandler

# Create a stream wrapper factory
wrapper = create_stream_wrapper_factory(
tracer,
"my.stream",
extract_chunk_content=StreamingResponseHandler.extract_generic_chunk_content,
initial_attributes={"stream.type": "text"}
)
instrumentor.instrument(tracer_provider=tracer_provider) # <-- Uses the global AgentOps TracerProvider

# Apply to streaming methods
wrap_function_wrapper("my_module", "stream_method", wrapper)
```

### Metrics

Use standard metrics for consistency across instrumentations:

```python
from agentops.instrumentation.common import StandardMetrics, MetricsRecorder

# Create standard metrics
metrics = StandardMetrics.create_standard_metrics(meter)

# Use the metrics recorder
recorder = MetricsRecorder(metrics)
recorder.record_token_usage(prompt_tokens=100, completion_tokens=50)
recorder.record_duration(1.5)
```

## Creating a New Instrumentor

1. Create a new directory under `agentops/instrumentation/` for your provider
2. Create an `__init__.py` file with version information
3. Create an `instrumentor.py` file extending `CommonInstrumentor`
4. Create attribute handlers in an `attributes/` subdirectory
5. Add your instrumentor to the main `__init__.py` configuration

Example structure:
```
agentops/instrumentation/
├── my_provider/
│ ├── __init__.py
│ ├── instrumentor.py
│ └── attributes/
│ ├── __init__.py
│ └── handlers.py
```

> To add custom instrumentation, please do so in the `third_party/opentelemetry` directory.
## Best Practices

1. **Use Common Utilities**: Leverage the common module for consistency
2. **Follow Semantic Conventions**: Use attributes from `agentops.semconv`
3. **Handle Errors Gracefully**: Wrap operations in try-except blocks
4. **Support Async**: Provide both sync and async method wrapping
5. **Document Attributes**: Comment on what attributes are captured
6. **Test Thoroughly**: Write unit tests for your instrumentor

## Examples

See the `examples/` directory for usage examples of each instrumentor.
54 changes: 35 additions & 19 deletions agentops/instrumentation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
from dataclasses import dataclass
import importlib
import sys
from importlib.metadata import version
from packaging.version import Version, parse
import builtins

Expand All @@ -34,6 +33,7 @@

from agentops.logging import logger
from agentops.sdk.core import tracer
from agentops.instrumentation.common import get_library_version


# Define the structure for instrumentor configurations
Expand All @@ -47,28 +47,28 @@ class InstrumentorConfig(TypedDict):
# Configuration for supported LLM providers
PROVIDERS: dict[str, InstrumentorConfig] = {
"openai": {
"module_name": "agentops.instrumentation.openai",
"class_name": "OpenAIInstrumentor",
"module_name": "agentops.instrumentation.providers.openai",
"class_name": "OpenaiInstrumentor",
"min_version": "1.0.0",
},
"anthropic": {
"module_name": "agentops.instrumentation.anthropic",
"module_name": "agentops.instrumentation.providers.anthropic",
"class_name": "AnthropicInstrumentor",
"min_version": "0.32.0",
},
"ibm_watsonx_ai": {
"module_name": "agentops.instrumentation.ibm_watsonx_ai",
"class_name": "IBMWatsonXInstrumentor",
"module_name": "agentops.instrumentation.providers.ibm_watsonx_ai",
"class_name": "WatsonxInstrumentor",
"min_version": "0.1.0",
},
"google.genai": {
"module_name": "agentops.instrumentation.google_genai",
"class_name": "GoogleGenAIInstrumentor",
"module_name": "agentops.instrumentation.providers.google_genai",
"class_name": "GoogleGenaiInstrumentor",
"min_version": "0.1.0",
"package_name": "google-genai", # Actual pip package name
},
"mem0": {
"module_name": "agentops.instrumentation.mem0",
"module_name": "agentops.instrumentation.providers.mem0",
"class_name": "Mem0Instrumentor",
"min_version": "0.1.0",
"package_name": "mem0ai",
Expand All @@ -78,7 +78,7 @@ class InstrumentorConfig(TypedDict):
# Configuration for utility instrumentors
UTILITY_INSTRUMENTORS: dict[str, InstrumentorConfig] = {
"concurrent.futures": {
"module_name": "agentops.instrumentation.concurrent_futures",
"module_name": "agentops.instrumentation.utilities.concurrent_futures",
"class_name": "ConcurrentFuturesInstrumentor",
"min_version": "3.7.0", # Python 3.7+ (concurrent.futures is stdlib)
"package_name": "python", # Special case for stdlib modules
Expand All @@ -88,21 +88,35 @@ class InstrumentorConfig(TypedDict):
# Configuration for supported agentic libraries
AGENTIC_LIBRARIES: dict[str, InstrumentorConfig] = {
"crewai": {
"module_name": "agentops.instrumentation.crewai",
"class_name": "CrewAIInstrumentor",
"module_name": "agentops.instrumentation.agentic.crewai",
"class_name": "CrewaiInstrumentor",
"min_version": "0.56.0",
},
"autogen": {"module_name": "agentops.instrumentation.ag2", "class_name": "AG2Instrumentor", "min_version": "0.3.2"},
"autogen": {
"module_name": "agentops.instrumentation.agentic.ag2",
"class_name": "AG2Instrumentor",
"min_version": "0.3.2",
},
"agents": {
"module_name": "agentops.instrumentation.openai_agents",
"module_name": "agentops.instrumentation.agentic.openai_agents",
"class_name": "OpenAIAgentsInstrumentor",
"min_version": "0.0.1",
},
"google.adk": {
"module_name": "agentops.instrumentation.google_adk",
"class_name": "GoogleADKInstrumentor",
"module_name": "agentops.instrumentation.agentic.google_adk",
"class_name": "GooogleAdkInstrumentor",
"min_version": "0.1.0",
},
"agno": {
"module_name": "agentops.instrumentation.agentic.agno",
"class_name": "AgnoInstrumentor",
"min_version": "0.1.0",
},
"smolagents": {
"module_name": "agentops.instrumentation.agentic.smolagents",
"class_name": "SmolagentsInstrumentor",
"min_version": "1.0.0",
},
}

# Combine all target packages for monitoring
Expand Down Expand Up @@ -456,9 +470,11 @@ def should_activate(self) -> bool:
provider_name = self.package_name
else:
provider_name = self.module_name.split(".")[-1]
module_version = version(provider_name)
return module_version is not None and Version(module_version) >= parse(self.min_version)
except ImportError:

# Use common version utility
module_version = get_library_version(provider_name)
return module_version != "unknown" and Version(module_version) >= parse(self.min_version)
except Exception:
return False

def get_instance(self) -> BaseInstrumentor:
Expand Down
28 changes: 0 additions & 28 deletions agentops/instrumentation/ag2/__init__.py

This file was deleted.

Loading
Loading