5a558eb09e
TypeScript SDK Compatibility V1.x E2E Tests / Select Node version matrix (push) Has been cancelled
TypeScript SDK Compatibility V1.x E2E Tests / TypeScript SDK Compatibility V1.x E2E Tests Node ${{matrix.node_version}} (push) Has been cancelled
TypeScript SDK E2E Tests / TypeScript SDK E2E Tests Node ${{matrix.node_version}} (push) Has been cancelled
Opik Optimizer - E2E Tests / build-opik (push) Has been cancelled
TypeScript SDK Compatibility V1.x E2E Tests / build-opik (push) Has been cancelled
Python SDK E2E Tests / Select Python version matrix (push) Has been cancelled
Python SDK E2E Tests / Python SDK E2E Tests ${{matrix.python_version}} (push) Has been cancelled
Python SDK E2E Tests / build-opik (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / Select Python version matrix (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / Python SDK Compatibility V1.x E2E Tests ${{matrix.python_version}} (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / build-opik (push) Has been cancelled
TypeScript SDK E2E Tests / Select Node version matrix (push) Has been cancelled
TypeScript SDK E2E Tests / build-opik (push) Has been cancelled
Opik Optimizer - E2E Tests / Opik Optimizer E2E Tests Python ${{matrix.python_version}} (push) Has been cancelled
Opik Optimizer - E2E Tests / Opik Optimizer Integration Smoke Tests (push) Has been cancelled
🐙 Code Quality / detect (push) Has been cancelled
🐙 Code Quality / lint (${{ matrix.leg.name }}) (push) Has been cancelled
🐙 Code Quality / summary (push) Has been cancelled
TypeScript SDK Library Integration Tests / Check Secrets (push) Has been cancelled
TypeScript SDK Library Integration Tests / opik-vercel (Vercel AI SDK / eve) (push) Has been cancelled
SDK Library Integration Tests Runner / Check Secrets (push) Has been cancelled
SDK Library Integration Tests Runner / Missed OpenAI API Key Warning (push) Has been cancelled
SDK Library Integration Tests Runner / Build (push) Has been cancelled
SDK Library Integration Tests Runner / openai_tests (push) Has been cancelled
SDK Library Integration Tests Runner / langchain_tests (push) Has been cancelled
SDK Library Integration Tests Runner / langchain_legacy_tests (push) Has been cancelled
SDK Library Integration Tests Runner / llama_index_tests (push) Has been cancelled
SDK Library Integration Tests Runner / anthropic_tests (push) Has been cancelled
SDK Library Integration Tests Runner / mistral_tests (push) Has been cancelled
SDK Library Integration Tests Runner / groq_tests (push) Has been cancelled
SDK Library Integration Tests Runner / aisuite_tests (push) Has been cancelled
SDK Library Integration Tests Runner / haystack_tests (push) Has been cancelled
SDK Library Integration Tests Runner / dspy_tests (push) Has been cancelled
SDK Library Integration Tests Runner / crewai_v0_tests (push) Has been cancelled
SDK Library Integration Tests Runner / crewai_v1_tests (push) Has been cancelled
SDK Library Integration Tests Runner / genai_tests (push) Has been cancelled
SDK Library Integration Tests Runner / adk_tests (push) Has been cancelled
SDK Library Integration Tests Runner / adk_legacy_1_3_0_tests (push) Has been cancelled
SDK Library Integration Tests Runner / evaluation_metrics_tests (push) Has been cancelled
SDK Library Integration Tests Runner / bedrock_tests (push) Has been cancelled
SDK Library Integration Tests Runner / litellm_tests (push) Has been cancelled
SDK Library Integration Tests Runner / harbor_tests (push) Has been cancelled
SDK Library Integration Tests Runner / Slack Notification (push) Has been cancelled
Lint Opik Helm Chart / render-equality (push) Has been cancelled
Opik Optimizer - Unit Tests / Opik Optimizer Unit Tests Python ${{matrix.python_version}} (push) Has been cancelled
Python BE E2E Tests / Python BE E2E (push) Has been cancelled
Python Backend Tests / run-python-backend-tests (push) Has been cancelled
Python SDK Unit Tests / Python SDK Unit Tests ${{matrix.python_version}} (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
SDK E2E Libraries Integration Tests / Check Secrets (push) Has been cancelled
SDK E2E Libraries Integration Tests / Missed OpenAI API Key Warning (push) Has been cancelled
SDK E2E Libraries Integration Tests / build-opik (push) Has been cancelled
SDK E2E Libraries Integration Tests / E2E Lib Integration Python ${{matrix.python_version}} (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-gemini) (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-langchain) (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-openai) (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-otel) (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-vercel) (push) Has been cancelled
TypeScript SDK Build & Publish / build-and-publish (push) Has been cancelled
TypeScript SDK Unit Tests / Test on Node ${{ matrix.node-version }} (push) Has been cancelled
Backend Tests / discover-tests (push) Has been cancelled
Backend Tests / ${{ matrix.name }} (push) Has been cancelled
Build and Publish SDK / build-and-publish (push) Has been cancelled
Build Opik Docker Images / set-version (push) Has been cancelled
Build Opik Docker Images / build-backend (push) Has been cancelled
Build Opik Docker Images / build-sandbox-executor-python (push) Has been cancelled
Build Opik Docker Images / build-python-backend (push) Has been cancelled
Build Opik Docker Images / build-frontend (push) Has been cancelled
Build Opik Docker Images / create-git-tag (push) Has been cancelled
ClickHouse Migration Cluster Check / validate-clickhouse-migrations (push) Has been cancelled
Docs - Publish / run (push) Has been cancelled
E2E Tests - Post Merge (v2) / 🧪 E2E v2 Tests (${{ github.event.inputs.tier || 't1' }}) (push) Has been cancelled
E2E Tests - Post Merge (v2) / 📢 Slack Notification (push) Has been cancelled
Frontend Unit Tests / Test on Node 20 (push) Has been cancelled
Guardrails E2E Tests / Select Python version matrix (push) Has been cancelled
Guardrails E2E Tests / Guardrails E2E Tests ${{matrix.python_version}} (push) Has been cancelled
Guardrails E2E Tests / 📢 Slack Notification (push) Has been cancelled
Guardrails Backend Unit Tests / Guardrails Backend Unit Tests (push) Has been cancelled
Guardrails Backend Unit Tests / 📢 Slack Notification (push) Has been cancelled
Lint Opik Helm Chart / lint-helm-chart (Helm v3.21.0) (push) Has been cancelled
Lint Opik Helm Chart / lint-helm-chart (Helm v4.2.0) (push) Has been cancelled
Lint Opik Helm Chart / unittest-helm-chart (push) Has been cancelled
596 lines
23 KiB
Markdown
596 lines
23 KiB
Markdown
# Opik Python SDK: Integrations Architecture
|
|
|
|
## Table of Contents
|
|
|
|
- [Overview](#overview)
|
|
- [Integration Patterns](#integration-patterns)
|
|
- [Method Patching Integrations](#method-patching-integrations)
|
|
- [Callback Integrations](#callback-integrations)
|
|
- [Hybrid Integrations](#hybrid-integrations)
|
|
- [Streaming Strategies](#streaming-strategies)
|
|
- [Token Usage and Cost Tracking](#token-usage-and-cost-tracking)
|
|
|
|
## Overview
|
|
|
|
The SDK provides automatic tracking for 12+ LLM frameworks through three architectural patterns. Integrations are designed to be lightweight, extensible, and framework-native.
|
|
|
|
### Integration Catalog
|
|
|
|
| Integration | Pattern | Location | Key Features |
|
|
|-------------|---------|----------|--------------|
|
|
| **OpenAI** | Method Patching | `integrations/openai/` | Multiple APIs, streaming, function calling |
|
|
| **Anthropic** | Method Patching | `integrations/anthropic/` | Messages API, delta accumulation |
|
|
| **Bedrock** | Method Patching | `integrations/bedrock/` | Multi-format aggregators, extensible |
|
|
| **Google GenAI** | Method Patching | `integrations/genai/` | Multi-modal support |
|
|
| **AISuite** | Method Patching | `integrations/aisuite/` | Unified interface |
|
|
| **LangChain** | Callback | `integrations/langchain/` | BaseTracer, provider extractors, external context support |
|
|
| **LlamaIndex** | Callback | `integrations/llama_index/` | Event parsing, dedicated client |
|
|
| **DSPy** | Callback | `integrations/dspy/` | Isolated context, graph visualization |
|
|
| **Haystack** | Callback | `integrations/haystack/` | Component-based |
|
|
| **ADK** | Hybrid | `integrations/adk/` | OpenTelemetry interception + callbacks |
|
|
| **CrewAI** | Hybrid | `integrations/crewai/` | Method wrapping + LiteLLM delegation |
|
|
|
|
## Integration Patterns
|
|
|
|
### Pattern Selection
|
|
|
|
```
|
|
Library Architecture Analysis:
|
|
|
|
Does library provide callbacks/hooks?
|
|
│
|
|
├─► Yes ─► Callbacks reliable and in-context?
|
|
│ │
|
|
│ ├─► Yes ─► Pure Callback
|
|
│ │ (LangChain, LlamaIndex, DSPy, Haystack)
|
|
│ │
|
|
│ └─► No ─► Hybrid (Callback + Patching)
|
|
│ (ADK, CrewAI)
|
|
│
|
|
└─► No ─► Method Patching
|
|
(OpenAI, Anthropic, Bedrock, GenAI, AISuite)
|
|
```
|
|
|
|
### Callback Reliability Issues
|
|
|
|
**Why callbacks alone may be insufficient**:
|
|
|
|
1. **Completion guarantee**: Some frameworks skip END callbacks on exceptions
|
|
2. **Context isolation**: Callbacks may execute in different thread/context than original call
|
|
3. **Timing**: Callbacks may fire with delays, complicating context management
|
|
|
|
**Solution**: Add patching/integration for OpenTelemetry interception (ADK) or external dependency tracking (CrewAI).
|
|
|
|
## Method Patching Integrations
|
|
|
|
### Architecture
|
|
|
|
Method patching wraps client methods to intercept calls:
|
|
|
|
```
|
|
track_library(client) → Wraps methods → client.method() intercepted
|
|
↓
|
|
BaseTrackDecorator
|
|
↓
|
|
_start_span_inputs_preprocessor
|
|
(extract input, create span)
|
|
↓
|
|
Call original method
|
|
↓
|
|
_streams_handler
|
|
(check if output is stream)
|
|
↓
|
|
┌────────┴────────┐
|
|
│ │
|
|
Stream? Not stream
|
|
│ │
|
|
Patch stream │
|
|
Defer finalization │
|
|
Return patched │
|
|
│ │
|
|
└────────┬────────┘
|
|
↓
|
|
_end_span_inputs_preprocessor
|
|
(extract output, usage, finalize span)
|
|
(called immediately for non-streaming,
|
|
or in finally block for streaming)
|
|
```
|
|
|
|
**All method patching integrations are idempotent**: Use `opik_tracked` marker to prevent double-wrapping.
|
|
|
|
### OpenAI Integration
|
|
|
|
**Files**:
|
|
- `opik_tracker.py` - Main entry point, wraps client methods
|
|
- `openai_chat_completions_decorator.py` - Chat completions decorator
|
|
- `openai_responses_decorator.py` - Responses API decorator
|
|
- `stream_patchers.py` - Stream iteration patching
|
|
- `chat_completion_chunks_aggregator.py` - Chunk aggregation
|
|
- `response_events_aggregator.py` - Response events aggregation
|
|
|
|
**Wrapped Methods**:
|
|
- `chat.completions.create()` - Standard chat API
|
|
- `beta.chat.completions.parse()` - Structured outputs
|
|
- `responses.create()` - Responses API
|
|
|
|
**Streaming Support**: Handles `openai.Stream`, `openai.AsyncStream`, and `ChatCompletionStreamManager`.
|
|
|
|
### Anthropic Integration
|
|
|
|
**Files**:
|
|
- `opik_tracker.py` - Main entry point
|
|
- `messages_create_decorator.py` - Messages decorator
|
|
- `stream_patchers.py` - Stream/context manager patching
|
|
|
|
**Wrapped Methods**:
|
|
- `messages.create()` - Both standard and streaming
|
|
- `messages.stream()` - Context manager pattern
|
|
|
|
**Key Implementation Detail**: **Delta Accumulation**
|
|
|
|
Anthropic streams delta events (not complete chunks) that must be accumulated. Event accumulator builds complete message by merging deltas progressively.
|
|
|
|
**Location**: `stream_patchers.py` - See accumulation logic
|
|
|
|
### Bedrock Integration
|
|
|
|
**Files**:
|
|
- `opik_tracker.py` - Main entry point
|
|
- `converse/converse_decorator.py` - Converse API
|
|
- `invoke_model/invoke_model_decorator.py` - Legacy InvokeModel API
|
|
- `invoke_model/chunks_aggregator/` - Extensible aggregator system
|
|
|
|
**Wrapped Methods**:
|
|
1. `client.converse()` - Unified Converse API
|
|
2. `client.invoke_model()` - Legacy API (multiple formats)
|
|
3. `client.invoke_agent()` - Agent invocations
|
|
|
|
**Key Implementation Detail**: **Extensible Multi-Format Aggregator**
|
|
|
|
**Problem**: Bedrock supports multiple model formats (Claude, Nova, Llama, Mistral) with different streaming structures.
|
|
|
|
**Solution**: Registry pattern with pluggable aggregators.
|
|
|
|
**Architecture** (`invoke_model/chunks_aggregator/`):
|
|
- `base.py` - `ChunkAggregator` protocol
|
|
- `format_detector.py` - Detection registry + aggregator registry
|
|
- `claude.py`, `nova.py`, `llama.py`, `mistral.py` - Format-specific aggregators
|
|
- `api.py` - Public interface: `detect_format()` + `aggregate_chunks_to_dataclass()`
|
|
|
|
**Extensibility**: Add new format by creating module + registering in `format_detector.py`. Zero changes to existing code.
|
|
|
|
**Benefits**: Open/Closed Principle, isolated testing, clear separation of concerns.
|
|
|
|
**Documentation**: See `EXTENDING.md` and `README.md` in `chunks_aggregator/` directory.
|
|
|
|
### Google GenAI Integration
|
|
|
|
**Files**:
|
|
- `opik_tracker.py` - Main entry point
|
|
- `generate_content_decorator.py` - Content generation decorator
|
|
- `stream_wrappers.py` - Stream handling
|
|
- `generations_aggregators.py` - Chunk aggregation
|
|
|
|
**Features**: Multi-modal support (text, images), streaming responses.
|
|
|
|
### AISuite Integration
|
|
|
|
**Files**:
|
|
- `opik_tracker.py` - Main entry point
|
|
- `aisuite_decorator.py` - Decorator implementation
|
|
|
|
**Pattern**: Similar to OpenAI (unified interface across providers).
|
|
|
|
## Callback Integrations
|
|
|
|
### Architecture
|
|
|
|
Callback integrations implement framework's callback interface:
|
|
|
|
```
|
|
Framework execution → Fires events → Callback methods
|
|
↓
|
|
on_start() - Create span/trace
|
|
on_end() - Update and send
|
|
on_error() - Capture error, finalize
|
|
```
|
|
|
|
### LangChain Integration
|
|
|
|
**Files**:
|
|
- `opik_tracer.py` - Implements `BaseTracer`
|
|
- `langgraph_tracer_injector.py` - Graph configuration injection for LangGraph
|
|
- `langgraph_async_context_bridge.py` - Context propagation for async LangGraph nodes
|
|
- `provider_usage_extractors/` - Provider-specific usage extraction
|
|
- `helpers.py` - Utility functions
|
|
- `base_llm_patcher.py` - Adds `base_url` to LLM dict (for provider ID)
|
|
|
|
**Pattern**: Pure callback (extends `langchain_core.tracers.BaseTracer`)
|
|
|
|
**Key Feature**: **Supports parent-child relations with external Opik spans/traces**
|
|
|
|
When used within `@track` decorated functions or existing Opik trace context:
|
|
- Detects existing trace in `context_storage`
|
|
- Creates LangChain spans as children of current Opik span
|
|
- Maintains proper hierarchy between Opik and LangChain operations
|
|
|
|
Example:
|
|
```python
|
|
@opik.track # Opik trace + span
|
|
def my_function():
|
|
chain.invoke(..., callbacks=[OpikTracer()]) # LangChain spans as children
|
|
```
|
|
|
|
**State Management**:
|
|
- `_span_data_map: Dict[UUID, SpanData]` - Maps LangChain run_id to Opik span
|
|
- `_created_traces_data_map: Dict[UUID, TraceData]` - Maps run_id to trace
|
|
- `_externally_created_traces_ids: Set[str]` - Tracks external traces
|
|
|
|
**Callback Methods** (implements full `BaseTracer` interface):
|
|
|
|
**Chain callbacks**:
|
|
- `_on_chain_start(run)` → Check for existing trace, create span as child if exists
|
|
- `_on_chain_end(run)` → Finalize span, send to backend
|
|
- `_on_chain_error(run)` → Capture error info, finalize span
|
|
|
|
**LLM callbacks**:
|
|
- `on_chat_model_start(...)` → Special handling for chat models
|
|
- `_on_chat_model_start(run)` → Internal processing
|
|
- `_on_llm_start(run)` → Create LLM span (type="llm"), extract provider
|
|
- `_on_llm_end(run)` → Extract usage via provider extractors, send span
|
|
- `_on_llm_error(run)` → Capture error, finalize span
|
|
|
|
**Tool callbacks**:
|
|
- `_on_tool_start(run)` → Create tool span (type="tool")
|
|
- `_on_tool_end(run)` → Finalize tool span
|
|
- `_on_tool_error(run)` → Capture error, finalize span
|
|
|
|
Error callbacks ensure spans finalized even when LangChain operations fail.
|
|
|
|
**Key Implementation Detail**: **Provider-Specific Usage Extractors**
|
|
|
|
**Location**: `provider_usage_extractors/`
|
|
|
|
**Challenge**: Each LangChain provider stores usage in different locations/formats within the `Run` object.
|
|
|
|
**Solution**: Registry pattern with provider-specific extractors.
|
|
|
|
Extractors:
|
|
- `OpenAIUsageExtractor` - Extracts from `run.outputs.llm_output.token_usage`
|
|
- `AnthropicUsageExtractor` - Handles Anthropic format
|
|
- `BedrockUsageExtractor` - Handles Bedrock format
|
|
- `GoogleUsageExtractor` - Handles Google format
|
|
- See `usage_extractor.py` for full registry
|
|
|
|
Each extractor knows where to find usage in that provider's Run structure.
|
|
|
|
**LangGraph Support**:
|
|
|
|
The integration provides enhanced support for LangGraph through:
|
|
|
|
1. **`track_langgraph()` Function**: High-level wrapper that injects `OpikTracer` into the graph's default configuration, eliminating the need to pass `config={"callbacks": [opik_tracer]}` on every invocation.
|
|
|
|
2. **Automatic Graph Visualization**: Extracts and stores Mermaid graph structure in trace metadata via `OpikTracer.set_graph()` method.
|
|
|
|
3. **Async Context Bridge**: `extract_current_langgraph_span_data()` helper for propagating trace context to `@track`-decorated functions in async LangGraph nodes.
|
|
|
|
**Usage Pattern**:
|
|
```python
|
|
from opik.integrations.langchain import OpikTracer, track_langgraph
|
|
from langgraph.graph import StateGraph, START, END
|
|
|
|
# Build and compile graph
|
|
builder = StateGraph(State)
|
|
builder.add_node("my_node", my_node_function)
|
|
builder.add_edge(START, "my_node")
|
|
builder.add_edge("my_node", END)
|
|
app = builder.compile()
|
|
|
|
# Track once
|
|
opik_tracer = OpikTracer(tags=["production"])
|
|
app = track_langgraph(app, opik_tracer)
|
|
|
|
# All invocations automatically tracked
|
|
result = app.invoke({"message": "Hello"})
|
|
```
|
|
|
|
**Implementation Details**:
|
|
- `langgraph_tracer_injector.py` - Injects `OpikTracer` into graph's default config
|
|
- `langgraph_async_context_bridge.py` - Extracts span data from LangGraph config for async context propagation
|
|
- `OpikTracer.set_graph()` - Stores graph visualization in `_trace_default_metadata["_opik_graph_definition"]`
|
|
|
|
### LlamaIndex Integration
|
|
|
|
**Files**:
|
|
- `callback.py` - Implements `BaseCallbackHandler`
|
|
- `event_parsing_utils.py` - Parses LlamaIndex event payloads
|
|
|
|
**Event Handling**:
|
|
- `on_event_start(event_type, payload, event_id, parent_id)` → Parse payload, create span
|
|
- `on_event_end(event_type, payload, event_id)` → Parse output/usage, send span
|
|
|
|
**Event Parser** (`event_parsing_utils.py`): Extracts data from payloads based on `event_type` (EMBEDDING, QUERY, LLM, etc.).
|
|
|
|
### DSPy Integration
|
|
|
|
**Files**:
|
|
- `callback.py` - Implements `dspy.utils.callback.BaseCallback`
|
|
- `graph.py` - Mermaid graph builder for DSPy programs
|
|
|
|
**Callbacks**:
|
|
- `on_module_start/end()` - DSPy module execution
|
|
- `on_lm_start/end()` - LM calls (extracts provider/model from "provider/model" format)
|
|
- `on_tool_start/end()` - Tool executions
|
|
|
|
**Key Implementation Detail**: **Global Context Storage with Safe Operations**
|
|
|
|
Uses global `OpikContextStorage` instance, enabling `opik.opik_context` API access to spans/traces created by DSPy callbacks. This allows users to:
|
|
- Access current span/trace data via `opik_context.get_current_span_data()` / `opik_context.get_current_trace_data()`
|
|
- Update spans/traces via `opik_context.update_current_span()` / `opik_context.update_current_trace()`
|
|
|
|
**Context Safety**: Uses `ensure_id` parameter for all context pop operations (`pop_span_data(ensure_id=...)`, `pop_trace_data(ensure_id=...)`) to prevent context corruption in concurrent scenarios or when DSPy callbacks coexist with `@track` decorated functions.
|
|
|
|
**Graph Visualization**: Builds Mermaid diagram of DSPy program structure (`graph.py`).
|
|
|
|
### Haystack Integration
|
|
|
|
**Files**:
|
|
- `opik_connector.py` - Component added to pipeline
|
|
- `opik_tracer.py` - Tracer for pipeline execution
|
|
- `converters.py` - Convert Haystack objects to Opik format
|
|
|
|
**Pattern**: Component-based (added to pipeline, observes without modifying data flow).
|
|
|
|
## Hybrid Integrations
|
|
|
|
### ADK Integration
|
|
|
|
**Files**:
|
|
- `opik_tracer.py` - Agent callbacks
|
|
- `patchers/adk_otel_tracer/opik_adk_otel_tracer.py` - OpenTelemetry tracer
|
|
- `recursive_callback_injector.py` - Recursive callback injection
|
|
- `graph/mermaid_graph_builder.py` - Agent graph visualization
|
|
- `patchers/patchers.py` - Global patches
|
|
|
|
**Why Hybrid**: ADK uses OpenTelemetry for internal tracing + provides agent callbacks.
|
|
|
|
**Dual Approach**:
|
|
|
|
1. **OpenTelemetry Patching** (`patchers/adk_otel_tracer/opik_adk_otel_tracer.py`):
|
|
- Intercepts `start_span()` calls from ADK
|
|
- Creates Opik spans instead
|
|
- Returns `INVALID_SPAN` (no-op for OpenTelemetry)
|
|
- Skips internal ADK spans via `_ADK_INTERNAL_SPAN_NAME_SKIP_LIST`
|
|
|
|
2. **Agent Callbacks** (`opik_tracer.py`):
|
|
- `before/after_agent_callback`
|
|
- `before/after_model_callback`
|
|
- `before/after_tool_callback`
|
|
- Recursively injected into agent tree (`recursive_callback_injector.py`)
|
|
|
|
**Key Implementation Details**:
|
|
|
|
1. **OpenTelemetry Interception**: Instead of dual tracing (OTel + Opik), intercepts OTel tracer to create only Opik spans. Single tracing backend, no OpenTelemetry overhead. Callbacks is used only to update spans and traces, but it's OTel tracer that is responsible
|
|
for creating them and working with context (it's done to benefit from reliability of OTel context manager)
|
|
|
|
2. **Graph Visualization** (`graph/mermaid_graph_builder.py`): Generates Mermaid diagram of agent structure including:
|
|
- Agent types (Sequential, Loop, Parallel, LLM)
|
|
- Tools and their connections
|
|
- Subagent relationships
|
|
- Stored in trace metadata `_opik_graph_definition`
|
|
|
|
### CrewAI Integration
|
|
|
|
**Files**:
|
|
- `opik_tracker.py` - Main tracking setup
|
|
- `crewai_decorator.py` - Decorator for CrewAI methods
|
|
- `flow_patchers.py` - Flow class patching
|
|
|
|
**Why Hybrid**: CrewAI methods wrapped + LiteLLM used for LLM tracking + direct provider client patching for v1.0.0+.
|
|
|
|
**Approach**:
|
|
1. **Method Wrapping**: Wrap `Crew.kickoff`, `Agent.execute_task`, `Task.execute_sync`
|
|
2. **LiteLLM Delegation**: Enable `litellm.track_litellm()` (CrewAI uses LiteLLM internally for v0.x)
|
|
3. **Flow Patching**: Patch `Flow.__init__` to auto-wrap dynamically registered methods (v1.0.0+ only)
|
|
4. **Provider Client Patching**: For v1.0.0+, directly patch OpenAI, Anthropic, Gemini, and Bedrock clients when `crew` argument is provided
|
|
|
|
**Key Implementation Details**:
|
|
|
|
1. **LiteLLM Delegation**: Reuses existing LiteLLM integration instead of duplicating LLM tracking logic.
|
|
|
|
2. **Flow Patching** (`flow_patchers.py`): Patches constructor to wrap methods registered via `@start`, `@listen` decorators. Gracefully handles missing `Flow` class (not available in CrewAI < v1.0.0).
|
|
|
|
3. **Graceful Degradation**: Handles missing provider libraries gracefully:
|
|
- If a provider library (e.g., `crewai.llms.providers.openai.completion`) is not installed, logs debug message and continues
|
|
- If tracking a specific provider client fails, logs warning and continues with other providers
|
|
- Ensures integration doesn't fail if some optional dependencies are missing
|
|
|
|
**Usage**:
|
|
```python
|
|
# For CrewAI v0.x (LiteLLM-based)
|
|
track_crewai(project_name="my-project")
|
|
|
|
# For CrewAI v1.0.0+ (direct provider clients)
|
|
crew = Crew(agents=[...], tasks=[...])
|
|
track_crewai(project_name="my-project", crew=crew) # crew argument enables LLM client tracking
|
|
```
|
|
|
|
## Streaming Strategies
|
|
|
|
### Streaming Challenges
|
|
|
|
1. **Deferred finalization**: Can't finalize span until stream consumed
|
|
2. **User-controlled consumption**: User determines when/if stream is fully consumed
|
|
3. **Chunk accumulation**: Need complete response for logging
|
|
4. **Error handling**: Exceptions during iteration
|
|
5. **Context cleanup**: Must finalize even if stream abandoned
|
|
|
|
### Strategy 1: Monkey-Patch Class Iterator
|
|
|
|
**Used by**: OpenAI (`openai.Stream`), Anthropic (`anthropic.Stream`)
|
|
|
|
**Files**: `stream_patchers.py` in each integration
|
|
|
|
**Approach**:
|
|
1. Save original `__iter__` from class
|
|
2. Create wrapper that accumulates chunks
|
|
3. Replace class method: `Stream.__iter__ = wrapper`
|
|
4. Mark instance: `stream.opik_tracked_instance = True`
|
|
5. Attach span/trace data to instance
|
|
6. Wrapper checks marker before processing
|
|
|
|
**Key Pattern - Context Pop Before Streaming**:
|
|
|
|
Before returning stream, pop span/trace from context:
|
|
```python
|
|
def _streams_handler(self, output, ...):
|
|
if is_stream(output):
|
|
# Pop BEFORE returning (stream consumed later)
|
|
span_to_end, trace_to_end = base_track_decorator.pop_end_candidates()
|
|
return patch_stream(output, span_to_end, trace_to_end, ...)
|
|
```
|
|
|
|
**Why**: Stream consumption happens after decorator returns. Popping prevents nested calls from seeing stale context.
|
|
|
|
**Key Pattern - Finalization Guarantee**:
|
|
|
|
All stream wrappers use `finally`:
|
|
```python
|
|
def wrapper(self):
|
|
try:
|
|
accumulated = []
|
|
for item in original(self):
|
|
accumulated.append(item)
|
|
yield item
|
|
finally:
|
|
# ALWAYS runs - even if stream not fully consumed
|
|
finalize_span(aggregator(accumulated), ...)
|
|
```
|
|
|
|
**Why**: User might break early or exception occurs. Span must finalize.
|
|
|
|
### Strategy 2: Context Manager Patching
|
|
|
|
**Used by**: Anthropic (`MessageStreamManager`)
|
|
|
|
**Approach**:
|
|
- Patch `__enter__` and `__exit__` of stream manager
|
|
- Accumulate during iteration (between enter/exit)
|
|
- Finalize in `__exit__`
|
|
|
|
**Files**: `stream_patchers.py`
|
|
|
|
Suitable for stream managers that use `with` statement pattern.
|
|
|
|
### Strategy 3: Generator Wrapper
|
|
|
|
**Used by**: Some Bedrock/GenAI cases
|
|
|
|
**Location**: `opik/decorator/generator_wrappers.py`
|
|
|
|
**Approach**: Wrap generator without modifying library classes. Returns custom proxy that finalizes in `__del__` or explicit close.
|
|
|
|
## Token Usage and Cost Tracking
|
|
|
|
### OpikUsage - Standardized Format
|
|
|
|
**Location**: `opik/llm_usage/opik_usage.py`
|
|
|
|
All providers map to standardized format:
|
|
```python
|
|
class OpikUsage(pydantic.BaseModel):
|
|
completion_tokens: Optional[int]
|
|
prompt_tokens: Optional[int]
|
|
total_tokens: Optional[int]
|
|
provider_usage: Optional[BaseOriginalProviderUsage] # Original preserved
|
|
```
|
|
|
|
### Usage Factory - Registry Pattern
|
|
|
|
**Location**: `opik/llm_usage/opik_usage_factory.py`
|
|
|
|
Registry with builder functions per provider:
|
|
```python
|
|
_PROVIDER_TO_OPIK_USAGE_BUILDERS: Dict[Provider, List[Callable]] = {
|
|
LLMProvider.OPENAI: [
|
|
OpikUsage.from_openai_completions_dict,
|
|
OpikUsage.from_openai_responses_dict, # Multiple formats supported
|
|
],
|
|
LLMProvider.ANTHROPIC: [OpikUsage.from_anthropic_dict],
|
|
LLMProvider.BEDROCK: [OpikUsage.from_bedrock_dict],
|
|
# ...
|
|
}
|
|
```
|
|
|
|
**Process**:
|
|
1. Integration extracts usage dict from response
|
|
2. Calls `build_opik_usage(provider, usage_dict)`
|
|
3. Factory tries each builder (supports multiple formats per provider)
|
|
4. Returns standardized `OpikUsage`
|
|
|
|
**Extensibility**: Add new provider by:
|
|
1. Create `MyProviderUsage` class
|
|
2. Add `from_myprovider_dict()` to `OpikUsage`
|
|
3. Register in factory
|
|
|
|
### Provider Enum
|
|
|
|
**Location**: `opik/types.py`
|
|
|
|
Supported providers for cost tracking:
|
|
- `OPENAI`, `ANTHROPIC`, `BEDROCK`
|
|
- `GOOGLE_VERTEXAI`, `GOOGLE_AI`
|
|
- `COHERE`, `GROQ`
|
|
- See `types.py` for complete list
|
|
|
|
### Cost Calculation
|
|
|
|
**SDK Responsibility**: Provide data
|
|
- `model`: Model name (e.g., "gpt-4")
|
|
- `provider`: Provider enum
|
|
- `usage`: Token counts (OpikUsage)
|
|
- `total_cost`: Optional override
|
|
|
|
**Backend Responsibility**: Calculate cost
|
|
- Pricing tables (model → price per token)
|
|
- Region-specific pricing (Bedrock)
|
|
- Token usage multiplication
|
|
|
|
**Note**: Integrations do **not** calculate cost - only provide data for backend.
|
|
|
|
## Summary
|
|
|
|
**Integration Patterns**:
|
|
- **Method Patching**: OpenAI, Anthropic, Bedrock, GenAI, AISuite
|
|
- **Callback**: LangChain, LlamaIndex, DSPy, Haystack
|
|
- **Hybrid**: ADK (callbacks + OTel), CrewAI (methods + LiteLLM)
|
|
|
|
**Streaming Strategies**:
|
|
- Class method patching (OpenAI, Anthropic Stream)
|
|
- Context manager patching (Anthropic MessageStreamManager)
|
|
- Generator wrapper (Bedrock, GenAI)
|
|
|
|
**Key Patterns**:
|
|
- **Idempotent tracking**: `opik_tracked` marker prevents double-wrapping
|
|
- **Context pop for streams**: Pop before returning stream (consumed later)
|
|
- **Finalization guarantee**: `finally` blocks ensure span completion
|
|
- **Registry patterns**: Pluggable providers/formats/extractors
|
|
- **Protocol-based**: Clear extension interfaces
|
|
|
|
**Notable Implementations**:
|
|
- **Bedrock**: Extensible aggregator system (add formats without modifying code)
|
|
- **ADK**: OpenTelemetry interception (single tracing backend)
|
|
- **LangChain**: External context support (composes with `@track`)
|
|
- **DSPy**: Global context with safe operations (enables `opik_context` API access)
|
|
- **CrewAI**: LiteLLM delegation (reuses existing integration)
|
|
|
|
For implementation details, see source code in:
|
|
- `opik/integrations/` - All integration implementations
|
|
- `opik/llm_usage/` - Usage tracking and conversion
|
|
- `opik/decorator/` - Base decorator and streaming utilities
|
|
|
|
For more information, see:
|
|
- [API and Data Flow](API_AND_DATA_FLOW.md) - Core SDK architecture
|
|
- [Evaluation](EVALUATION.md) - Evaluation framework
|
|
- [Testing](TESTING.md) - Testing integrations
|