Skip to content
Open
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
13 changes: 13 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ license = "Apache-2.0"
keywords = ["braintrust", "logging", "tracing", "observability"]
categories = ["api-bindings", "asynchronous", "development-tools"]

[features]
default = ["auto-instrumentation"]
auto-instrumentation = ["dep:tracing-subscriber"]

[dependencies]
anyhow = "1"
base64 = "0.22"
Expand All @@ -30,13 +34,22 @@ thiserror = "1"
regex = "1"
tokio = { version = "1", features = ["rt-multi-thread", "macros", "time", "sync"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["registry"], optional = true }
uuid = { version = "1", features = ["v4", "serde"] }
url = "2"

[dev-dependencies]
bytes = "1"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
wiremock = "0.5.22"
opentelemetry = "0.27"
opentelemetry_sdk = { version = "0.27", features = ["testing"] }
tracing-opentelemetry = "0.28"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

[[example]]
name = "auto_instrumentation"
required-features = ["auto-instrumentation"]

[package.metadata.docs.rs]
all-features = true
Expand Down
105 changes: 105 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,113 @@ async fn main() -> anyhow::Result<()> {
}
```

## Auto-Instrumentation

**New in 0.1.0-alpha.2**: Automatically log AI SDK calls with zero manual code!

The SDK now includes `BraintrustTracingLayer`, a tracing subscriber that automatically captures OpenTelemetry GenAI semantic convention events from instrumented AI SDKs.

### Quick Start

```rust
use braintrust_sdk_rust::{BraintrustClient, BraintrustTracingLayer};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Initialize Braintrust
let braintrust = BraintrustClient::builder()
.api_url("https://api.braintrust.dev")
.build()
.await?;

let span = braintrust
.span_builder().await?
.project_name("my-project")
.build();

// Install BraintrustTracingLayer - this is all you need!
tracing_subscriber::registry()
.with(BraintrustTracingLayer::new(span.clone()))
.with(tracing_subscriber::fmt::layer())
.init();

// Now all instrumented AI SDK calls are automatically logged!
// No manual SpanLog::builder() calls needed.

Ok(())
}
```

### Supported SDKs

Auto-instrumentation works with any SDK that emits OpenTelemetry GenAI semantic convention events via the `tracing` crate:

- **async-openai** (with `instrumentation` feature)
- **rust-genai / genai** (with `instrumentation` feature) - covers 11+ providers:
- OpenAI, Anthropic, Gemini, Groq, Cohere, DeepSeek, Ollama, Fireworks, Together, Xai, BigModel, Aliyun, Mimo, Nebius

### Benefits

- ✅ **Zero manual logging code** - just install the layer once
- ✅ **Consistent format** across all providers
- ✅ **Standard conventions** - follows OpenTelemetry GenAI semantic conventions
- ✅ **Works with streaming** - automatically captures streaming responses
- ✅ **Low overhead** - async logging doesn't block your application

### How It Works

1. AI SDKs emit structured `tracing` events following [OpenTelemetry GenAI semantic conventions](https://opentelemetry.io/docs/specs/semconv/gen-ai/)
2. `BraintrustTracingLayer` subscribes to these events
3. Events are automatically converted to Braintrust `SpanLog` entries
4. Logs are sent to Braintrust in the background

### Example with async-openai

```toml
[dependencies]
braintrust-sdk-rust = "0.1.0-alpha.2"
async-openai = { version = "0.33", features = ["instrumentation", "chat-completion"] }
tracing-subscriber = "0.3"
```

```rust
use async_openai::{types::CreateChatCompletionRequestArgs, Client};
use braintrust_sdk_rust::{BraintrustClient, BraintrustTracingLayer};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Setup (one-time)
let braintrust = BraintrustClient::builder()
.api_url("https://api.braintrust.dev")
.build().await?;
let span = braintrust.span_builder().await?.project_name("my-project").build();

tracing_subscriber::registry()
.with(BraintrustTracingLayer::new(span.clone()))
.init();

// Use async-openai as normal - automatic logging!
let client = Client::new();
let request = CreateChatCompletionRequestArgs::default()
.model("gpt-4")
.messages(vec![/* ... */])
.build()?;

let response = client.chat().create(request).await?;
// ↑ This call was automatically logged to Braintrust!

span.flush().await?;
Ok(())
}
```

See the [auto-instrumentation example](examples/auto_instrumentation.rs) for more details.

## Features

- **Auto-instrumentation**: Automatically capture AI SDK calls via OpenTelemetry GenAI conventions
- **Span-based logging**: Create spans to track LLM calls and other operations
- **Usage metrics extraction**: Built-in extractors for OpenAI and Anthropic usage metrics
- **Async-first**: Built on Tokio for high-performance async operations
Expand Down
Loading