langfuse/
langfuse.rs

1//! Example of using the Langfuse interceptor for LLM observability.
2//!
3//! This example demonstrates how to integrate Langfuse tracing with `OpenAI` API calls
4//! using the built-in interceptor system with task-local span storage.
5//!
6//! ## Setup
7//!
8//! Before running this example, set the following environment variables:
9//! - `OPENAI_API_KEY`: Your `OpenAI` API key
10//! - `LANGFUSE_PUBLIC_KEY`: Your Langfuse public key (starts with "pk-lf-")
11//! - `LANGFUSE_SECRET_KEY`: Your Langfuse secret key (starts with "sk-lf-")
12//! - `LANGFUSE_HOST` (optional): Langfuse API host (defaults to <https://cloud.langfuse.com>)
13//!
14//! ## Running the example
15//!
16//! ```bash
17//! cargo run --example langfuse
18//! ```
19
20use openai_ergonomic::{
21    Builder, ChatCompletionBuilder, Client, LangfuseConfig, LangfuseInterceptor,
22};
23use opentelemetry::{global, trace::TracerProvider};
24use opentelemetry_langfuse::ExporterBuilder;
25use opentelemetry_sdk::{
26    runtime::Tokio,
27    trace::{span_processor_with_async_runtime::BatchSpanProcessor, SdkTracerProvider},
28};
29
30#[tokio::main]
31async fn main() -> Result<(), Box<dyn std::error::Error>> {
32    // Initialize tracing for logging
33    tracing_subscriber::fmt()
34        .with_env_filter(
35            tracing_subscriber::EnvFilter::from_default_env()
36                .add_directive("openai_ergonomic=debug".parse()?),
37        )
38        .init();
39
40    // 1. Build Langfuse exporter from environment variables
41    let exporter = ExporterBuilder::from_env()?.build()?;
42
43    // 2. Create tracer provider with batch processor
44    let provider = SdkTracerProvider::builder()
45        .with_span_processor(BatchSpanProcessor::builder(exporter, Tokio).build())
46        .build();
47
48    // Set as global provider
49    global::set_tracer_provider(provider.clone());
50
51    // 3. Get tracer and create interceptor
52    let tracer = provider.tracer("openai-ergonomic");
53    let langfuse_interceptor =
54        std::sync::Arc::new(LangfuseInterceptor::new(tracer, LangfuseConfig::new()));
55
56    // 4. Create the OpenAI client and add the Langfuse interceptor
57    // Keep a reference to the interceptor so we can update context later
58    let client = Client::from_env()?
59        .with_interceptor(Box::new(langfuse_interceptor.clone()))
60        .build();
61
62    println!(" OpenAI client initialized with Langfuse observability");
63    println!(" Traces will be sent to Langfuse for monitoring\n");
64
65    // Example 1: Simple chat completion
66    println!("Example 1: Simple chat completion");
67    println!("---------------------------------");
68    let chat_builder = client
69        .chat_simple("What is the capital of France? Answer in one word.")
70        .build()?;
71    let response = client.execute_chat(chat_builder).await?;
72    println!("Response: {:?}\n", response.content());
73
74    // Example 2: Chat completion with builder pattern
75    println!("Example 2: Chat with builder pattern");
76    println!("-------------------------------------");
77    let chat_builder = client
78        .chat()
79        .system("You are a helpful assistant that speaks like a pirate.")
80        .user("Tell me about the ocean in 2 sentences.")
81        .temperature(0.7)
82        .max_tokens(100)
83        .build()?;
84    let response = client.execute_chat(chat_builder).await?;
85    println!("Response: {:?}\n", response.content());
86
87    // Example 3: Multiple messages in a conversation
88    println!("Example 3: Conversation");
89    println!("-----------------------");
90    let chat_builder = client
91        .chat()
92        .system("You are a math tutor.")
93        .user("What is 2 + 2?")
94        .assistant("2 + 2 equals 4.")
95        .user("And what about 3 + 3?")
96        .build()?;
97    let response = client.execute_chat(chat_builder).await?;
98    println!("Response: {:?}\n", response.content());
99
100    // Example 4: Error handling (intentionally trigger an error)
101    println!("Example 4: Error handling");
102    println!("-------------------------");
103    // Create a builder with a non-existent model
104    let chat_builder = ChatCompletionBuilder::new("non-existent-model")
105        .user("This should fail")
106        .build()?;
107    let result = client.execute_chat(chat_builder).await;
108
109    match result {
110        Ok(_) => println!("Unexpected success"),
111        Err(e) => println!("Expected error captured: {e}\n"),
112    }
113
114    // Example 5: Embeddings
115    println!("Example 5: Embeddings");
116    println!("--------------------");
117    let embeddings_builder = client.embeddings().text(
118        "text-embedding-ada-002",
119        "The quick brown fox jumps over the lazy dog",
120    );
121    let embeddings = client.embeddings().create(embeddings_builder).await?;
122    println!("Generated {} embedding(s)\n", embeddings.data.len());
123
124    // Example 6: Using custom metadata via interceptor context
125    println!("Example 6: Custom metadata via interceptor context");
126    println!("---------------------------------------------------");
127
128    // Set session and user IDs on the interceptor's context
129    langfuse_interceptor.set_session_id("demo-session-123");
130    langfuse_interceptor.set_user_id("demo-user-456");
131    langfuse_interceptor.add_tags(vec!["example".to_string(), "demo".to_string()]);
132
133    let chat_builder = client
134        .chat_simple("Say 'Hello from custom session!'")
135        .build()?;
136    let response = client.execute_chat(chat_builder).await?;
137    println!("Response with custom metadata: {:?}\n", response.content());
138
139    // Clear context for subsequent calls
140    langfuse_interceptor.clear_context();
141
142    println!(" All examples completed!");
143    println!(" Check your Langfuse dashboard to see the traces");
144    println!("   - Look for traces with operation name 'chat'");
145    println!("   - Each trace includes request/response details, token usage, and timing");
146    println!("   - Example 6 will have custom session_id, user_id, and tags");
147
148    // Shutdown the tracer provider to flush all spans
149    println!("\n⏳ Flushing spans to Langfuse...");
150    provider.shutdown()?;
151
152    Ok(())
153}