Files
comet-ml--opik/sdks/python/design/INTEGRATIONS.md
T
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 13:25:44 +08:00

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