@fallom/trace 0.1.0 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,28 +6,27 @@ Model A/B testing and tracing for LLM applications. Zero latency, production-rea
6
6
 
7
7
  ```bash
8
8
  npm install @fallom/trace
9
-
10
- # With auto-instrumentation for your LLM provider:
11
- npm install @fallom/trace @traceloop/node-server-sdk
12
9
  ```
13
10
 
14
11
  ## Quick Start
15
12
 
16
13
  ```typescript
17
- import fallom from '@fallom/trace';
18
- import OpenAI from 'openai';
14
+ import fallom from "@fallom/trace";
15
+ import OpenAI from "openai";
16
+
17
+ // Initialize Fallom
18
+ await fallom.init({ apiKey: "your-api-key" });
19
19
 
20
- // Initialize FIRST - before importing your LLM libraries
21
- fallom.init({ apiKey: 'your-api-key' });
20
+ // Wrap your LLM client for automatic tracing
21
+ const openai = fallom.trace.wrapOpenAI(new OpenAI());
22
22
 
23
- // Set default session context for tracing
24
- fallom.trace.setSession('my-agent', sessionId);
23
+ // Set session context
24
+ fallom.trace.setSession("my-agent", sessionId);
25
25
 
26
26
  // All LLM calls are now automatically traced!
27
- const openai = new OpenAI();
28
27
  const response = await openai.chat.completions.create({
29
- model: 'gpt-4o',
30
- messages: [{ role: 'user', content: 'Hello!' }],
28
+ model: "gpt-4o",
29
+ messages: [{ role: "user", content: "Hello!" }],
31
30
  });
32
31
  ```
33
32
 
@@ -36,120 +35,112 @@ const response = await openai.chat.completions.create({
36
35
  Run A/B tests on models with zero latency. Same session always gets same model (sticky assignment).
37
36
 
38
37
  ```typescript
39
- import { models } from '@fallom/trace';
38
+ import { models } from "@fallom/trace";
40
39
 
41
40
  // Get assigned model for this session
42
- const model = await models.get('summarizer-config', sessionId);
41
+ const model = await models.get("summarizer-config", sessionId);
43
42
  // Returns: "gpt-4o" or "claude-3-5-sonnet" based on your config weights
44
43
 
45
- const agent = new Agent({ model });
46
- await agent.run(message);
47
- ```
48
-
49
- ### Version Pinning
50
-
51
- Pin to a specific config version, or use latest (default):
52
-
53
- ```typescript
54
- // Use latest version (default)
55
- const model = await models.get('my-config', sessionId);
56
-
57
- // Pin to specific version
58
- const model = await models.get('my-config', sessionId, { version: 2 });
44
+ const response = await openai.chat.completions.create({ model, ... });
59
45
  ```
60
46
 
61
47
  ### Fallback for Resilience
62
48
 
63
- Always provide a fallback so your app works even if Fallom is down:
64
-
65
49
  ```typescript
66
- const model = await models.get('my-config', sessionId, {
67
- fallback: 'gpt-4o-mini', // Used if config not found or Fallom unreachable
50
+ const model = await models.get("my-config", sessionId, {
51
+ fallback: "gpt-4o-mini", // Used if config not found or Fallom unreachable
68
52
  });
69
53
  ```
70
54
 
71
- **Resilience guarantees:**
72
- - Short timeouts (1-2 seconds max)
73
- - Background config sync (never blocks your requests)
74
- - Graceful degradation (returns fallback on any error)
75
- - Your app is never impacted by Fallom being down
76
-
77
55
  ## Tracing
78
56
 
79
- Auto-capture all LLM calls with OpenTelemetry instrumentation.
80
-
81
- > ⚠️ **Important:** Auto-tracing only works with supported LLM SDKs (OpenAI, Anthropic, etc.) - not raw HTTP requests. If you're using an OpenAI-compatible API like OpenRouter, LiteLLM, or a self-hosted model, use the OpenAI SDK with a custom `baseURL`:
82
- >
83
- > ```typescript
84
- > import OpenAI from 'openai';
85
- >
86
- > // OpenRouter, LiteLLM, vLLM, etc.
87
- > const client = new OpenAI({
88
- > baseURL: 'https://openrouter.ai/api/v1', // or your provider's URL
89
- > apiKey: 'your-provider-key',
90
- > });
91
- >
92
- > // Now this call will be auto-traced!
93
- > const response = await client.chat.completions.create({
94
- > model: 'gpt-4o',
95
- > messages: [...],
96
- > });
97
- > ```
98
-
99
- ### Automatic Tracing
57
+ Wrap your LLM client once, all calls are automatically traced.
58
+
59
+ ### OpenAI (+ OpenRouter, Azure, LiteLLM, etc.)
100
60
 
101
61
  ```typescript
102
- import fallom from '@fallom/trace';
62
+ import OpenAI from "openai";
63
+ import fallom from "@fallom/trace";
103
64
 
104
- // Initialize before making LLM calls
105
- fallom.init();
65
+ await fallom.init({ apiKey: "your-api-key" });
106
66
 
107
- // Set session context
108
- fallom.trace.setSession('my-agent', sessionId);
67
+ // Works with any OpenAI-compatible API
68
+ const openai = fallom.trace.wrapOpenAI(
69
+ new OpenAI({
70
+ baseURL: "https://openrouter.ai/api/v1", // or Azure, LiteLLM, etc.
71
+ apiKey: "your-provider-key",
72
+ })
73
+ );
109
74
 
110
- // All LLM calls automatically traced with:
111
- // - Model, tokens, latency
112
- // - Prompts and completions
113
- // - Your config_key and session_id
75
+ fallom.trace.setSession("my-config", sessionId);
76
+
77
+ // Automatically traced!
114
78
  const response = await openai.chat.completions.create({
115
- model: 'gpt-4o',
116
- messages: [...],
79
+ model: "gpt-4o",
80
+ messages: [{ role: "user", content: "Hello!" }],
117
81
  });
118
82
  ```
119
83
 
120
- ### Async Context Propagation
121
-
122
- For proper session context across async boundaries, use `runWithSession`:
84
+ ### Anthropic (Claude)
123
85
 
124
86
  ```typescript
125
- import { trace } from '@fallom/trace';
87
+ import Anthropic from "@anthropic-ai/sdk";
88
+ import fallom from "@fallom/trace";
89
+
90
+ await fallom.init({ apiKey: "your-api-key" });
91
+
92
+ const anthropic = fallom.trace.wrapAnthropic(new Anthropic());
126
93
 
127
- await trace.runWithSession('my-agent', sessionId, async () => {
128
- // All LLM calls in here have session context
129
- await agent.run(message);
130
- await anotherAsyncOperation();
94
+ fallom.trace.setSession("my-config", sessionId);
95
+
96
+ // Automatically traced!
97
+ const response = await anthropic.messages.create({
98
+ model: "claude-3-5-sonnet-20241022",
99
+ messages: [{ role: "user", content: "Hello!" }],
131
100
  });
132
101
  ```
133
102
 
134
- ### Custom Metrics
103
+ ### Google AI (Gemini)
104
+
105
+ ```typescript
106
+ import { GoogleGenerativeAI } from "@google/generative-ai";
107
+ import fallom from "@fallom/trace";
108
+
109
+ await fallom.init({ apiKey: "your-api-key" });
110
+
111
+ const genAI = new GoogleGenerativeAI(apiKey);
112
+ const model = fallom.trace.wrapGoogleAI(
113
+ genAI.getGenerativeModel({ model: "gemini-pro" })
114
+ );
115
+
116
+ fallom.trace.setSession("my-config", sessionId);
117
+
118
+ // Automatically traced!
119
+ const response = await model.generateContent("Hello!");
120
+ ```
121
+
122
+ ## What Gets Traced
135
123
 
136
- Record business metrics that OTEL can't capture automatically:
124
+ For each LLM call, Fallom automatically captures:
125
+ - ✅ Model name
126
+ - ✅ Duration (latency)
127
+ - ✅ Token counts (prompt, completion, total)
128
+ - ✅ Input/output content (can be disabled)
129
+ - ✅ Errors
130
+ - ✅ Config key + session ID (for A/B analysis)
131
+
132
+ ## Custom Metrics
133
+
134
+ Record business metrics for your A/B tests:
137
135
 
138
136
  ```typescript
139
- import { trace } from '@fallom/trace';
137
+ import { trace } from "@fallom/trace";
140
138
 
141
- // Record custom metrics for this session
142
139
  trace.span({
143
140
  outlier_score: 0.8,
144
141
  user_satisfaction: 4,
145
142
  conversion: true,
146
143
  });
147
-
148
- // Or explicitly specify session (for batch jobs)
149
- trace.span(
150
- { outlier_score: 0.8 },
151
- { configKey: 'my-agent', sessionId: 'user123-convo456' }
152
- );
153
144
  ```
154
145
 
155
146
  ## Configuration
@@ -158,118 +149,54 @@ trace.span(
158
149
 
159
150
  ```bash
160
151
  FALLOM_API_KEY=your-api-key
161
- FALLOM_BASE_URL=https://spans.fallom.com # or http://localhost:8001 for local dev
152
+ FALLOM_BASE_URL=https://spans.fallom.com
162
153
  FALLOM_CAPTURE_CONTENT=true # set to "false" for privacy mode
163
154
  ```
164
155
 
165
- ### Initialization Options
166
-
167
- ```typescript
168
- fallom.init({
169
- apiKey: 'your-api-key', // Or use FALLOM_API_KEY env var
170
- baseUrl: 'https://spans.fallom.com', // Or use FALLOM_BASE_URL env var
171
- captureContent: true, // Set false for privacy mode
172
- });
173
- ```
174
-
175
156
  ### Privacy Mode
176
157
 
177
- For companies with strict data policies, disable prompt/completion capture:
158
+ Disable prompt/completion capture:
178
159
 
179
160
  ```typescript
180
- // Via parameter
181
161
  fallom.init({ captureContent: false });
182
-
183
- // Or via environment variable
184
- // FALLOM_CAPTURE_CONTENT=false
185
162
  ```
186
163
 
187
- In privacy mode, Fallom still tracks:
188
- - ✅ Model used
189
- - ✅ Token counts
190
- - ✅ Latency
191
- - ✅ Session/config context
192
- - ❌ Prompt content (not captured)
193
- - ❌ Completion content (not captured)
194
-
195
164
  ## API Reference
196
165
 
197
166
  ### `fallom.init(options?)`
198
167
 
199
- Initialize the SDK. Call this before making LLM calls for auto-instrumentation.
168
+ Initialize the SDK.
200
169
 
201
- | Option | Type | Default | Description |
202
- |--------|------|---------|-------------|
203
- | `apiKey` | `string` | `FALLOM_API_KEY` env | Your Fallom API key |
204
- | `baseUrl` | `string` | `https://spans.fallom.com` | API base URL |
205
- | `captureContent` | `boolean` | `true` | Capture prompt/completion text |
170
+ ### `fallom.trace.wrapOpenAI(client)`
206
171
 
207
- ### `fallom.models.get(configKey, sessionId, options?)`
208
-
209
- Get model assignment for a session.
172
+ Wrap OpenAI client for automatic tracing. Works with any OpenAI-compatible API.
210
173
 
211
- | Parameter | Type | Description |
212
- |-----------|------|-------------|
213
- | `configKey` | `string` | Your config name from the dashboard |
214
- | `sessionId` | `string` | Unique session/conversation ID (sticky assignment) |
215
- | `options.version` | `number` | Pin to specific version (default: latest) |
216
- | `options.fallback` | `string` | Model to return if anything fails |
217
- | `options.debug` | `boolean` | Enable debug logging |
174
+ ### `fallom.trace.wrapAnthropic(client)`
218
175
 
219
- Returns: `Promise<string>` - The assigned model name
176
+ Wrap Anthropic client for automatic tracing.
220
177
 
221
- ### `fallom.trace.setSession(configKey, sessionId)`
178
+ ### `fallom.trace.wrapGoogleAI(model)`
222
179
 
223
- Set trace context. All subsequent LLM calls will be tagged with this session.
180
+ Wrap Google AI model for automatic tracing.
224
181
 
225
- ### `fallom.trace.runWithSession(configKey, sessionId, fn)`
182
+ ### `fallom.trace.setSession(configKey, sessionId)`
226
183
 
227
- Run a function with session context that propagates across async boundaries.
184
+ Set session context for tracing.
228
185
 
229
- ### `fallom.trace.clearSession()`
186
+ ### `fallom.models.get(configKey, sessionId, options?)`
230
187
 
231
- Clear trace context.
188
+ Get model assignment for A/B testing. Returns `Promise<string>`.
232
189
 
233
- ### `fallom.trace.span(data, options?)`
190
+ ### `fallom.trace.span(data)`
234
191
 
235
192
  Record custom business metrics.
236
193
 
237
- | Parameter | Type | Description |
238
- |-----------|------|-------------|
239
- | `data` | `Record<string, unknown>` | Metrics to record |
240
- | `options.configKey` | `string` | Optional if `setSession()` was called |
241
- | `options.sessionId` | `string` | Optional if `setSession()` was called |
242
-
243
- ### `fallom.trace.shutdown()`
244
-
245
- Gracefully shutdown the tracing SDK. Call this on process exit.
246
-
247
- ## Supported LLM Providers
248
-
249
- Auto-instrumentation available for:
250
- - OpenAI (+ OpenAI-compatible APIs: OpenRouter, LiteLLM, vLLM, Ollama, etc.)
251
- - Anthropic
252
- - Cohere
253
- - AWS Bedrock
254
- - Google Generative AI
255
- - Azure OpenAI
256
- - LangChain
257
- - And more via Traceloop
258
-
259
- Install `@traceloop/node-server-sdk` for comprehensive LLM instrumentation.
260
-
261
- **Note:** You must use the official SDK for your provider. Raw HTTP requests (e.g., `fetch()`) will not be traced. For OpenAI-compatible APIs, use the OpenAI SDK with a custom `baseURL`.
262
-
263
- ## Examples
264
-
265
- See the `../examples/` folder for complete examples:
266
- - `random-fact/` - Simple A/B testing with Hono server
267
-
268
194
  ## Requirements
269
195
 
270
196
  - Node.js >= 18.0.0
271
197
 
198
+ Works with ESM and CommonJS. Works with tsx, ts-node, Bun, and compiled JavaScript.
199
+
272
200
  ## License
273
201
 
274
202
  MIT
275
-
package/dist/index.d.mts CHANGED
@@ -36,7 +36,8 @@ declare function init$2(options?: {
36
36
  apiKey?: string;
37
37
  baseUrl?: string;
38
38
  captureContent?: boolean;
39
- }): void;
39
+ debug?: boolean;
40
+ }): Promise<void>;
40
41
  /**
41
42
  * Set the current session context.
42
43
  *
@@ -111,6 +112,74 @@ declare function span(data: Record<string, unknown>, options?: {
111
112
  * Shutdown the tracing SDK gracefully.
112
113
  */
113
114
  declare function shutdown(): Promise<void>;
115
+ /**
116
+ * Wrap an OpenAI client to automatically trace all chat completions.
117
+ * Works with OpenAI, OpenRouter, Azure OpenAI, LiteLLM, and any OpenAI-compatible API.
118
+ *
119
+ * @param client - The OpenAI client instance
120
+ * @returns The same client with tracing enabled
121
+ *
122
+ * @example
123
+ * ```typescript
124
+ * import OpenAI from "openai";
125
+ * import { trace } from "@fallom/trace";
126
+ *
127
+ * const openai = trace.wrapOpenAI(new OpenAI());
128
+ *
129
+ * trace.setSession("my-config", sessionId);
130
+ * const response = await openai.chat.completions.create({...}); // Automatically traced!
131
+ * ```
132
+ */
133
+ declare function wrapOpenAI<T extends {
134
+ chat: {
135
+ completions: {
136
+ create: (...args: any[]) => Promise<any>;
137
+ };
138
+ };
139
+ }>(client: T): T;
140
+ /**
141
+ * Wrap an Anthropic client to automatically trace all message creations.
142
+ *
143
+ * @param client - The Anthropic client instance
144
+ * @returns The same client with tracing enabled
145
+ *
146
+ * @example
147
+ * ```typescript
148
+ * import Anthropic from "@anthropic-ai/sdk";
149
+ * import { trace } from "@fallom/trace";
150
+ *
151
+ * const anthropic = trace.wrapAnthropic(new Anthropic());
152
+ *
153
+ * trace.setSession("my-config", sessionId);
154
+ * const response = await anthropic.messages.create({...}); // Automatically traced!
155
+ * ```
156
+ */
157
+ declare function wrapAnthropic<T extends {
158
+ messages: {
159
+ create: (...args: any[]) => Promise<any>;
160
+ };
161
+ }>(client: T): T;
162
+ /**
163
+ * Wrap a Google Generative AI client to automatically trace all content generations.
164
+ *
165
+ * @param client - The GoogleGenerativeAI client instance
166
+ * @returns The same client with tracing enabled
167
+ *
168
+ * @example
169
+ * ```typescript
170
+ * import { GoogleGenerativeAI } from "@google/generative-ai";
171
+ * import { trace } from "@fallom/trace";
172
+ *
173
+ * const genAI = new GoogleGenerativeAI(apiKey);
174
+ * const model = trace.wrapGoogleAI(genAI.getGenerativeModel({ model: "gemini-pro" }));
175
+ *
176
+ * trace.setSession("my-config", sessionId);
177
+ * const response = await model.generateContent("Hello!"); // Automatically traced!
178
+ * ```
179
+ */
180
+ declare function wrapGoogleAI<T extends {
181
+ generateContent: (...args: any[]) => Promise<any>;
182
+ }>(model: T): T;
114
183
 
115
184
  declare const trace_clearSession: typeof clearSession;
116
185
  declare const trace_getSession: typeof getSession;
@@ -118,8 +187,11 @@ declare const trace_runWithSession: typeof runWithSession;
118
187
  declare const trace_setSession: typeof setSession;
119
188
  declare const trace_shutdown: typeof shutdown;
120
189
  declare const trace_span: typeof span;
190
+ declare const trace_wrapAnthropic: typeof wrapAnthropic;
191
+ declare const trace_wrapGoogleAI: typeof wrapGoogleAI;
192
+ declare const trace_wrapOpenAI: typeof wrapOpenAI;
121
193
  declare namespace trace {
122
- export { trace_clearSession as clearSession, trace_getSession as getSession, init$2 as init, trace_runWithSession as runWithSession, trace_setSession as setSession, trace_shutdown as shutdown, trace_span as span };
194
+ export { trace_clearSession as clearSession, trace_getSession as getSession, init$2 as init, trace_runWithSession as runWithSession, trace_setSession as setSession, trace_shutdown as shutdown, trace_span as span, trace_wrapAnthropic as wrapAnthropic, trace_wrapGoogleAI as wrapGoogleAI, trace_wrapOpenAI as wrapOpenAI };
123
195
  }
124
196
 
125
197
  /**
@@ -182,6 +254,7 @@ interface InitOptions {
182
254
  apiKey?: string;
183
255
  baseUrl?: string;
184
256
  captureContent?: boolean;
257
+ debug?: boolean;
185
258
  }
186
259
  /**
187
260
  * Initialize both trace and models at once.
@@ -205,7 +278,7 @@ interface InitOptions {
205
278
  * fallom.init({ captureContent: false });
206
279
  * ```
207
280
  */
208
- declare function init(options?: InitOptions): void;
281
+ declare function init(options?: InitOptions): Promise<void>;
209
282
 
210
283
  /**
211
284
  * Fallom - Model A/B testing and tracing for LLM applications.
package/dist/index.d.ts CHANGED
@@ -36,7 +36,8 @@ declare function init$2(options?: {
36
36
  apiKey?: string;
37
37
  baseUrl?: string;
38
38
  captureContent?: boolean;
39
- }): void;
39
+ debug?: boolean;
40
+ }): Promise<void>;
40
41
  /**
41
42
  * Set the current session context.
42
43
  *
@@ -111,6 +112,74 @@ declare function span(data: Record<string, unknown>, options?: {
111
112
  * Shutdown the tracing SDK gracefully.
112
113
  */
113
114
  declare function shutdown(): Promise<void>;
115
+ /**
116
+ * Wrap an OpenAI client to automatically trace all chat completions.
117
+ * Works with OpenAI, OpenRouter, Azure OpenAI, LiteLLM, and any OpenAI-compatible API.
118
+ *
119
+ * @param client - The OpenAI client instance
120
+ * @returns The same client with tracing enabled
121
+ *
122
+ * @example
123
+ * ```typescript
124
+ * import OpenAI from "openai";
125
+ * import { trace } from "@fallom/trace";
126
+ *
127
+ * const openai = trace.wrapOpenAI(new OpenAI());
128
+ *
129
+ * trace.setSession("my-config", sessionId);
130
+ * const response = await openai.chat.completions.create({...}); // Automatically traced!
131
+ * ```
132
+ */
133
+ declare function wrapOpenAI<T extends {
134
+ chat: {
135
+ completions: {
136
+ create: (...args: any[]) => Promise<any>;
137
+ };
138
+ };
139
+ }>(client: T): T;
140
+ /**
141
+ * Wrap an Anthropic client to automatically trace all message creations.
142
+ *
143
+ * @param client - The Anthropic client instance
144
+ * @returns The same client with tracing enabled
145
+ *
146
+ * @example
147
+ * ```typescript
148
+ * import Anthropic from "@anthropic-ai/sdk";
149
+ * import { trace } from "@fallom/trace";
150
+ *
151
+ * const anthropic = trace.wrapAnthropic(new Anthropic());
152
+ *
153
+ * trace.setSession("my-config", sessionId);
154
+ * const response = await anthropic.messages.create({...}); // Automatically traced!
155
+ * ```
156
+ */
157
+ declare function wrapAnthropic<T extends {
158
+ messages: {
159
+ create: (...args: any[]) => Promise<any>;
160
+ };
161
+ }>(client: T): T;
162
+ /**
163
+ * Wrap a Google Generative AI client to automatically trace all content generations.
164
+ *
165
+ * @param client - The GoogleGenerativeAI client instance
166
+ * @returns The same client with tracing enabled
167
+ *
168
+ * @example
169
+ * ```typescript
170
+ * import { GoogleGenerativeAI } from "@google/generative-ai";
171
+ * import { trace } from "@fallom/trace";
172
+ *
173
+ * const genAI = new GoogleGenerativeAI(apiKey);
174
+ * const model = trace.wrapGoogleAI(genAI.getGenerativeModel({ model: "gemini-pro" }));
175
+ *
176
+ * trace.setSession("my-config", sessionId);
177
+ * const response = await model.generateContent("Hello!"); // Automatically traced!
178
+ * ```
179
+ */
180
+ declare function wrapGoogleAI<T extends {
181
+ generateContent: (...args: any[]) => Promise<any>;
182
+ }>(model: T): T;
114
183
 
115
184
  declare const trace_clearSession: typeof clearSession;
116
185
  declare const trace_getSession: typeof getSession;
@@ -118,8 +187,11 @@ declare const trace_runWithSession: typeof runWithSession;
118
187
  declare const trace_setSession: typeof setSession;
119
188
  declare const trace_shutdown: typeof shutdown;
120
189
  declare const trace_span: typeof span;
190
+ declare const trace_wrapAnthropic: typeof wrapAnthropic;
191
+ declare const trace_wrapGoogleAI: typeof wrapGoogleAI;
192
+ declare const trace_wrapOpenAI: typeof wrapOpenAI;
121
193
  declare namespace trace {
122
- export { trace_clearSession as clearSession, trace_getSession as getSession, init$2 as init, trace_runWithSession as runWithSession, trace_setSession as setSession, trace_shutdown as shutdown, trace_span as span };
194
+ export { trace_clearSession as clearSession, trace_getSession as getSession, init$2 as init, trace_runWithSession as runWithSession, trace_setSession as setSession, trace_shutdown as shutdown, trace_span as span, trace_wrapAnthropic as wrapAnthropic, trace_wrapGoogleAI as wrapGoogleAI, trace_wrapOpenAI as wrapOpenAI };
123
195
  }
124
196
 
125
197
  /**
@@ -182,6 +254,7 @@ interface InitOptions {
182
254
  apiKey?: string;
183
255
  baseUrl?: string;
184
256
  captureContent?: boolean;
257
+ debug?: boolean;
185
258
  }
186
259
  /**
187
260
  * Initialize both trace and models at once.
@@ -205,7 +278,7 @@ interface InitOptions {
205
278
  * fallom.init({ captureContent: false });
206
279
  * ```
207
280
  */
208
- declare function init(options?: InitOptions): void;
281
+ declare function init(options?: InitOptions): Promise<void>;
209
282
 
210
283
  /**
211
284
  * Fallom - Model A/B testing and tracing for LLM applications.