Skip to content

Conversation

@delner
Copy link
Collaborator

@delner delner commented Jan 9, 2026

Anthropic Integration Migration to New API

Summary

This PR migrates the Anthropic gem integration to the new contrib integration API, aligning it with the patterns established by the OpenAI and RubyLLM integrations. The migration introduces a unified Braintrust.instrument!() API while maintaining backward compatibility with the existing wrap() method.

Key Changes

New Unified API

Class-level instrumentation (all clients):

Braintrust.instrument!(:anthropic)
client = Anthropic::Client.new
# All Anthropic clients are now instrumented

Instance-level instrumentation (specific client only):

client_traced = Anthropic::Client.new
client_untraced = Anthropic::Client.new

Braintrust.instrument!(:anthropic, target: client_traced)
# Only client_traced is instrumented

Deprecated API

The old wrap() API still works but emits a deprecation warning:

# DEPRECATED - use Braintrust.instrument! instead
Braintrust::Trace::Anthropic.wrap(client)

Architectural Overview

File Structure

lib/braintrust/contrib/anthropic/
├── integration.rb          # Integration registration & metadata
├── patcher.rb              # Class/instance patching logic
├── deprecated.rb           # Backward compatibility shim
└── instrumentation/
    ├── common.rb           # Shared utilities (token parsing)
    └── messages.rb         # Messages & MessageStream instrumentation

Components

Component Responsibility
Integration Registers with contrib framework, defines gem name, minimum version, and require paths
Patcher Determines what to patch (class vs instance), applies instrumentation modules
Instrumentation::Messages Wraps create() and stream() methods on Anthropic::Resources::Messages
Instrumentation::MessageStream Wraps each(), text(), and close() on stream objects for deferred tracing

Streaming Architecture

Streaming uses deferred span creation - the span is created when the stream is consumed, not when stream() is called:

stream()          → Stores context on stream object (tracer, params, metadata)
                    No span created yet

each()/text()     → Creates span, yields events, finalizes span with accumulated output
                    Span includes full response and metrics

close()           → If stream closed without consumption, creates minimal span

This ensures accurate time_to_first_token metrics and complete output capture.

Usage Examples

Basic Usage (Class-Level)

require "braintrust"
require "anthropic"

Braintrust.init
Braintrust.instrument!(:anthropic)

client = Anthropic::Client.new

message = client.messages.create(
  model: "claude-3-haiku-20240307",
  max_tokens: 100,
  system: "You are a helpful assistant.",
  messages: [{role: "user", content: "Hello!"}]
)

Instance-Level Instrumentation

require "braintrust"
require "anthropic"

Braintrust.init

# Create two clients
client_traced = Anthropic::Client.new
client_untraced = Anthropic::Client.new

# Only instrument one
Braintrust.instrument!(:anthropic, target: client_traced)

# This call is traced
client_traced.messages.create(...)

# This call is NOT traced
client_untraced.messages.create(...)

Streaming

Braintrust.init
Braintrust.instrument!(:anthropic)

client = Anthropic::Client.new

# Pattern 1: Iterator-based
stream = client.messages.stream(
  model: "claude-3-haiku-20240307",
  max_tokens: 100,
  messages: [{role: "user", content: "Count to 5"}]
)

stream.each do |event|
  if event.type == :content_block_delta
    print event.delta.text
  end
end

# Pattern 2: Text-only
stream = client.messages.stream(...)
stream.text.each { |chunk| print chunk }

What's Traced

Each messages.create or consumed messages.stream creates a span with:

Attribute Description
braintrust.input_json Input messages (including system prompt)
braintrust.output_json Response content
braintrust.metadata Provider, model, temperature, stop_reason, etc.
braintrust.metrics Token counts (prompt_tokens, completion_tokens, tokens, time_to_first_token)

Migration Guide

Before (Deprecated) After
require "braintrust/trace/contrib/anthropic" require "braintrust"
Braintrust::Trace::Anthropic.wrap(client) Braintrust.instrument!(:anthropic, target: client)
N/A Braintrust.instrument!(:anthropic) (class-level)

The old API continues to work but logs a deprecation warning.

@delner delner requested review from clutchski and realark January 9, 2026 16:10
@delner delner self-assigned this Jan 9, 2026
@delner delner added the enhancement New feature or request label Jan 9, 2026
@delner delner marked this pull request as ready for review January 9, 2026 17:06
@delner delner force-pushed the auto_instrument/anthropic branch from 0d93adf to 4221a54 Compare January 9, 2026 22:30
@delner delner merged commit d43fcef into feature/auto_instrument Jan 9, 2026
@delner delner deleted the auto_instrument/anthropic branch January 9, 2026 22:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants