@getfoil/foil-js 0.4.0
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 +1029 -0
- package/package.json +54 -0
- package/src/foil.js +2214 -0
- package/src/otel.js +610 -0
package/README.md
ADDED
|
@@ -0,0 +1,1029 @@
|
|
|
1
|
+
# Foil JavaScript SDK
|
|
2
|
+
|
|
3
|
+
JavaScript/Node.js SDK for monitoring and logging AI agent invocations with Foil. Features distributed tracing, semantic search, custom evaluations, multimodal content support (images, documents), signals/feedback, and framework integrations.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @foil/foil-js
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
const { createFoilTracer, SpanKind, content, ContentBlock, MediaCategory } = require('@foil/foil-js');
|
|
15
|
+
|
|
16
|
+
// Create a tracer for your agent
|
|
17
|
+
const tracer = createFoilTracer({
|
|
18
|
+
apiKey: 'your-api-key',
|
|
19
|
+
agentName: 'my-agent',
|
|
20
|
+
baseUrl: 'https://api.foil.dev/api', // or your self-hosted URL
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Trace an agent execution
|
|
24
|
+
const result = await tracer.trace(async (ctx) => {
|
|
25
|
+
// Create an LLM span
|
|
26
|
+
const llmSpan = await ctx.startSpan(SpanKind.LLM, 'gpt-4', {
|
|
27
|
+
input: 'What is the weather like?',
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// ... call your LLM ...
|
|
31
|
+
|
|
32
|
+
await llmSpan.end({
|
|
33
|
+
output: 'The weather is sunny today!',
|
|
34
|
+
tokens: { prompt: 10, completion: 8, total: 18 },
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return 'Agent completed';
|
|
38
|
+
}, {
|
|
39
|
+
name: 'weather-query',
|
|
40
|
+
input: 'User asked about weather',
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Table of Contents
|
|
45
|
+
|
|
46
|
+
- [Distributed Tracing](#distributed-tracing)
|
|
47
|
+
- [Semantic Search](#semantic-search)
|
|
48
|
+
- [Custom Evaluations](#custom-evaluations)
|
|
49
|
+
- [Multimodal Content](#multimodal-content)
|
|
50
|
+
- [Signals & Feedback](#signals--feedback)
|
|
51
|
+
- [Framework Integrations](#framework-integrations)
|
|
52
|
+
- [OpenTelemetry Integration](#opentelemetry-integration)
|
|
53
|
+
- [Experiments (A/B Testing)](#experiments-ab-testing)
|
|
54
|
+
- [Low-Level API](#low-level-api)
|
|
55
|
+
- [Debug Mode](#debug-mode)
|
|
56
|
+
- [API Reference](#api-reference)
|
|
57
|
+
- [Examples](#examples)
|
|
58
|
+
|
|
59
|
+
## Distributed Tracing
|
|
60
|
+
|
|
61
|
+
### Basic Tracing
|
|
62
|
+
|
|
63
|
+
The tracer captures the full lifecycle of your AI agent: prompts → LLM calls → tool executions → responses.
|
|
64
|
+
|
|
65
|
+
```javascript
|
|
66
|
+
const tracer = createFoilTracer({
|
|
67
|
+
apiKey: 'your-api-key',
|
|
68
|
+
agentName: 'customer-support-agent',
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
await tracer.trace(async (ctx) => {
|
|
72
|
+
// LLM span for the main model call
|
|
73
|
+
const llm = await ctx.startSpan(SpanKind.LLM, 'gpt-4-turbo', {
|
|
74
|
+
input: messages,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const response = await openai.chat.completions.create({
|
|
78
|
+
model: 'gpt-4-turbo',
|
|
79
|
+
messages,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
await llm.end({
|
|
83
|
+
output: response.choices[0].message.content,
|
|
84
|
+
tokens: {
|
|
85
|
+
prompt: response.usage.prompt_tokens,
|
|
86
|
+
completion: response.usage.completion_tokens,
|
|
87
|
+
total: response.usage.total_tokens,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
return response.choices[0].message.content;
|
|
92
|
+
}, {
|
|
93
|
+
name: 'support-query',
|
|
94
|
+
input: userMessage,
|
|
95
|
+
sessionId: conversationId, // Group traces by conversation
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Span Kinds
|
|
100
|
+
|
|
101
|
+
```javascript
|
|
102
|
+
const { SpanKind } = require('@foil/foil-js');
|
|
103
|
+
|
|
104
|
+
SpanKind.AGENT // Root agent span (automatic for trace())
|
|
105
|
+
SpanKind.LLM // Language model calls
|
|
106
|
+
SpanKind.TOOL // Tool/function executions
|
|
107
|
+
SpanKind.CHAIN // Chain of operations
|
|
108
|
+
SpanKind.RETRIEVER // RAG retrieval operations
|
|
109
|
+
SpanKind.EMBEDDING // Embedding model calls
|
|
110
|
+
SpanKind.CUSTOM // Custom operation types
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Nested Spans
|
|
114
|
+
|
|
115
|
+
Spans automatically nest based on the call stack:
|
|
116
|
+
|
|
117
|
+
```javascript
|
|
118
|
+
await tracer.trace(async (ctx) => {
|
|
119
|
+
// LLM decides to use a tool
|
|
120
|
+
const llmSpan = await ctx.startSpan(SpanKind.LLM, 'gpt-4', {
|
|
121
|
+
input: 'Find customer order #12345',
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Tool execution (automatically becomes child of LLM span)
|
|
125
|
+
const result = await ctx.tool('lookup_order', async () => {
|
|
126
|
+
return await database.findOrder('12345');
|
|
127
|
+
}, {
|
|
128
|
+
input: { orderId: '12345' },
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
await llmSpan.end({
|
|
132
|
+
output: `Order found: ${JSON.stringify(result)}`,
|
|
133
|
+
});
|
|
134
|
+
}, { name: 'order-lookup' });
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Convenience Methods
|
|
138
|
+
|
|
139
|
+
```javascript
|
|
140
|
+
// Wrap tool execution
|
|
141
|
+
await ctx.tool('search_database', async () => {
|
|
142
|
+
return await db.search(query);
|
|
143
|
+
}, { input: { query } });
|
|
144
|
+
|
|
145
|
+
// Wrap retriever operation
|
|
146
|
+
await ctx.retriever('vector_store', async () => {
|
|
147
|
+
return await vectorStore.similaritySearch(query, 5);
|
|
148
|
+
}, { input: query });
|
|
149
|
+
|
|
150
|
+
// Wrap embedding operation
|
|
151
|
+
await ctx.embedding('text-embedding-3-small', async () => {
|
|
152
|
+
return await openai.embeddings.create({ input: text, model: 'text-embedding-3-small' });
|
|
153
|
+
}, { input: text });
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Semantic Search
|
|
157
|
+
|
|
158
|
+
Search through your traces using natural language queries. Foil automatically embeds your trace content and enables AI-powered semantic search.
|
|
159
|
+
|
|
160
|
+
### Basic Search
|
|
161
|
+
|
|
162
|
+
```javascript
|
|
163
|
+
const Foil = require('@foil/foil-js');
|
|
164
|
+
|
|
165
|
+
const foil = new Foil({
|
|
166
|
+
apiKey: 'your-api-key',
|
|
167
|
+
baseUrl: 'https://api.foil.dev/api',
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Search for conversations about a topic
|
|
171
|
+
const results = await foil.semanticSearch('conversations about refund requests');
|
|
172
|
+
|
|
173
|
+
console.log(`Found ${results.results.length} matching traces`);
|
|
174
|
+
results.results.forEach((result) => {
|
|
175
|
+
console.log(`Trace: ${result.traceId}, Similarity: ${(result.similarity * 100).toFixed(1)}%`);
|
|
176
|
+
});
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Search with Filters
|
|
180
|
+
|
|
181
|
+
```javascript
|
|
182
|
+
const results = await foil.semanticSearch('user asking about pricing', {
|
|
183
|
+
agentId: 'customer-support-agent', // Filter by specific agent
|
|
184
|
+
agentName: 'support-bot', // Or filter by agent name
|
|
185
|
+
from: '2024-01-01', // Start date (ISO format)
|
|
186
|
+
to: '2024-12-31', // End date (ISO format)
|
|
187
|
+
limit: 10, // Max results (default: 20)
|
|
188
|
+
offset: 0, // Pagination offset
|
|
189
|
+
threshold: 0.4, // Min similarity score 0-1 (default: 0.3)
|
|
190
|
+
});
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Find Similar Traces
|
|
194
|
+
|
|
195
|
+
Find traces that are semantically similar to a specific trace:
|
|
196
|
+
|
|
197
|
+
```javascript
|
|
198
|
+
// Find traces similar to a known trace
|
|
199
|
+
const similar = await foil.findSimilarTraces('trace-abc-123', {
|
|
200
|
+
limit: 5,
|
|
201
|
+
threshold: 0.4,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
console.log(`Found ${similar.results.length} similar traces`);
|
|
205
|
+
similar.results.forEach((result) => {
|
|
206
|
+
console.log(`${result.traceId}: ${(result.similarity * 100).toFixed(1)}% similar`);
|
|
207
|
+
});
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Check Semantic Search Status
|
|
211
|
+
|
|
212
|
+
```javascript
|
|
213
|
+
// Get overall embedding status
|
|
214
|
+
const status = await foil.getSemanticSearchStatus();
|
|
215
|
+
console.log(`Embedded: ${status.embeddedSpans} / ${status.totalSpans} spans`);
|
|
216
|
+
console.log(`Coverage: ${status.coveragePercent}%`);
|
|
217
|
+
console.log(`Ready: ${status.ready}`);
|
|
218
|
+
|
|
219
|
+
// Get status for specific agent
|
|
220
|
+
const agentStatus = await foil.getSemanticSearchStatus('my-agent-id');
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Custom Evaluations
|
|
224
|
+
|
|
225
|
+
Define custom evaluation criteria to analyze your agent's responses. Evaluations run automatically on new traces and can be boolean, score-based, or categorical.
|
|
226
|
+
|
|
227
|
+
### Get Evaluation Templates
|
|
228
|
+
|
|
229
|
+
```javascript
|
|
230
|
+
// List available pre-built evaluation templates
|
|
231
|
+
const templates = await foil.getEvaluationTemplates();
|
|
232
|
+
|
|
233
|
+
templates.templates.forEach((t) => {
|
|
234
|
+
console.log(`${t.name}: ${t.description}`);
|
|
235
|
+
});
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Create a Boolean Evaluation
|
|
239
|
+
|
|
240
|
+
```javascript
|
|
241
|
+
const evaluation = await foil.createEvaluation('agent-123', {
|
|
242
|
+
name: 'professional_tone',
|
|
243
|
+
description: 'Checks if the response maintains a professional tone',
|
|
244
|
+
prompt: `Evaluate the assistant's response for professional tone.
|
|
245
|
+
|
|
246
|
+
Consider:
|
|
247
|
+
- Is the language respectful and courteous?
|
|
248
|
+
- Does it avoid slang or casual expressions?
|
|
249
|
+
- Is it helpful without being condescending?
|
|
250
|
+
|
|
251
|
+
Return true if professional, false otherwise.`,
|
|
252
|
+
evaluationType: 'boolean',
|
|
253
|
+
enabled: true,
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
console.log('Created evaluation:', evaluation.evaluation.name);
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Create a Score Evaluation (1-10)
|
|
260
|
+
|
|
261
|
+
```javascript
|
|
262
|
+
const evaluation = await foil.createEvaluation('agent-123', {
|
|
263
|
+
name: 'helpfulness_score',
|
|
264
|
+
description: 'Rates response helpfulness on a scale of 1-10',
|
|
265
|
+
prompt: `Rate the helpfulness of the response on a scale of 1-10.
|
|
266
|
+
|
|
267
|
+
Scoring:
|
|
268
|
+
- 1-3: Unhelpful
|
|
269
|
+
- 4-5: Somewhat helpful
|
|
270
|
+
- 6-7: Helpful
|
|
271
|
+
- 8-9: Very helpful
|
|
272
|
+
- 10: Exceptional
|
|
273
|
+
|
|
274
|
+
Return a single number from 1 to 10.`,
|
|
275
|
+
evaluationType: 'score',
|
|
276
|
+
scoreMin: 1,
|
|
277
|
+
scoreMax: 10,
|
|
278
|
+
enabled: true,
|
|
279
|
+
});
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Create a Category Evaluation
|
|
283
|
+
|
|
284
|
+
```javascript
|
|
285
|
+
const evaluation = await foil.createEvaluation('agent-123', {
|
|
286
|
+
name: 'response_intent',
|
|
287
|
+
description: 'Categorizes the type of response',
|
|
288
|
+
prompt: `Classify the response into one category:
|
|
289
|
+
|
|
290
|
+
- informational: Provides facts or explanations
|
|
291
|
+
- action: Guides through a process
|
|
292
|
+
- clarification: Asks for more information
|
|
293
|
+
- escalation: Suggests human assistance
|
|
294
|
+
- closure: Wraps up the conversation
|
|
295
|
+
|
|
296
|
+
Return only the category name.`,
|
|
297
|
+
evaluationType: 'category',
|
|
298
|
+
categories: ['informational', 'action', 'clarification', 'escalation', 'closure'],
|
|
299
|
+
enabled: true,
|
|
300
|
+
});
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Test an Evaluation
|
|
304
|
+
|
|
305
|
+
Validate your evaluation prompt before enabling:
|
|
306
|
+
|
|
307
|
+
```javascript
|
|
308
|
+
const result = await foil.testEvaluation('agent-123', evaluationId, {
|
|
309
|
+
input: 'How do I reset my password?',
|
|
310
|
+
output: 'I\'d be happy to help! Go to Settings > Security > Reset Password.',
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
console.log('Result:', result.result); // true/false, score, or category
|
|
314
|
+
console.log('Reasoning:', result.reasoning); // Explanation
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Add Few-Shot Examples
|
|
318
|
+
|
|
319
|
+
Improve evaluation accuracy with examples:
|
|
320
|
+
|
|
321
|
+
```javascript
|
|
322
|
+
// Add a positive example
|
|
323
|
+
await foil.addEvaluationExample('agent-123', evaluationId, {
|
|
324
|
+
input: 'Why is my order late?',
|
|
325
|
+
output: 'I apologize for the delay. Let me check the status for you.',
|
|
326
|
+
expectedResult: true,
|
|
327
|
+
reasoning: 'Professional and helpful response',
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// Add a negative example
|
|
331
|
+
await foil.addEvaluationExample('agent-123', evaluationId, {
|
|
332
|
+
input: 'Why is my order late?',
|
|
333
|
+
output: 'idk check tracking yourself',
|
|
334
|
+
expectedResult: false,
|
|
335
|
+
reasoning: 'Casual and unhelpful',
|
|
336
|
+
});
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### Manage Evaluations
|
|
340
|
+
|
|
341
|
+
```javascript
|
|
342
|
+
// List all evaluations for an agent
|
|
343
|
+
const evaluations = await foil.getAgentEvaluations('agent-123');
|
|
344
|
+
|
|
345
|
+
// Get specific evaluation details
|
|
346
|
+
const evaluation = await foil.getEvaluation('agent-123', evaluationId);
|
|
347
|
+
|
|
348
|
+
// Update an evaluation
|
|
349
|
+
await foil.updateEvaluation('agent-123', evaluationId, {
|
|
350
|
+
description: 'Updated description',
|
|
351
|
+
enabled: false, // Disable temporarily
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
// Get evaluation analytics
|
|
355
|
+
const analytics = await foil.getEvaluationAnalytics('agent-123', evaluationId);
|
|
356
|
+
console.log('Total evaluations:', analytics.totalEvaluations);
|
|
357
|
+
console.log('Distribution:', analytics.distribution);
|
|
358
|
+
|
|
359
|
+
// Clone a template
|
|
360
|
+
const cloned = await foil.cloneEvaluationTemplate('agent-123', templateId);
|
|
361
|
+
|
|
362
|
+
// Delete an evaluation
|
|
363
|
+
await foil.deleteEvaluation('agent-123', evaluationId);
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
## Multimodal Content
|
|
367
|
+
|
|
368
|
+
Foil supports multimodal input/output including images, documents, and other media types.
|
|
369
|
+
|
|
370
|
+
### Media Categories
|
|
371
|
+
|
|
372
|
+
```javascript
|
|
373
|
+
const { MediaCategory } = require('@foil/foil-js');
|
|
374
|
+
|
|
375
|
+
MediaCategory.IMAGE // Images (png, jpg, gif, webp, etc.)
|
|
376
|
+
MediaCategory.DOCUMENT // Documents (pdf, doc, docx, etc.)
|
|
377
|
+
MediaCategory.SPREADSHEET // Spreadsheets (xlsx, csv, etc.)
|
|
378
|
+
MediaCategory.CODE // Code files
|
|
379
|
+
MediaCategory.AUDIO // Audio files
|
|
380
|
+
MediaCategory.VIDEO // Video files
|
|
381
|
+
MediaCategory.ARCHIVE // Archives (zip, tar, etc.)
|
|
382
|
+
MediaCategory.NOTEBOOK // Jupyter notebooks
|
|
383
|
+
MediaCategory.OTHER // Other file types
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### Uploading Media
|
|
387
|
+
|
|
388
|
+
```javascript
|
|
389
|
+
const Foil = require('@foil/foil-js');
|
|
390
|
+
|
|
391
|
+
const foil = new Foil({
|
|
392
|
+
apiKey: 'your-api-key',
|
|
393
|
+
baseUrl: 'https://api.foil.dev/api',
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
// Upload from file path
|
|
397
|
+
const result = await foil.uploadMedia('/path/to/image.png');
|
|
398
|
+
console.log(result.mediaId); // 'media_abc123'
|
|
399
|
+
console.log(result.category); // 'image'
|
|
400
|
+
console.log(result.filename); // 'image.png'
|
|
401
|
+
console.log(result.mimeType); // 'image/png'
|
|
402
|
+
|
|
403
|
+
// Upload from Buffer
|
|
404
|
+
const buffer = await fs.promises.readFile('/path/to/document.pdf');
|
|
405
|
+
const result = await foil.uploadMedia(buffer, {
|
|
406
|
+
filename: 'document.pdf',
|
|
407
|
+
mimeType: 'application/pdf',
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
// Upload with trace/span association
|
|
411
|
+
const result = await foil.uploadMedia('/path/to/image.png', {
|
|
412
|
+
traceId: 'trace_123',
|
|
413
|
+
spanId: 'span_456',
|
|
414
|
+
direction: 'input', // or 'output'
|
|
415
|
+
});
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### Content Blocks
|
|
419
|
+
|
|
420
|
+
Use content blocks to create multimodal input/output:
|
|
421
|
+
|
|
422
|
+
```javascript
|
|
423
|
+
const { content, ContentBlock, MediaCategory } = require('@foil/foil-js');
|
|
424
|
+
|
|
425
|
+
// Create multimodal content array
|
|
426
|
+
const multimodalInput = content(
|
|
427
|
+
'Please analyze this image:',
|
|
428
|
+
ContentBlock.media(uploadResult.mediaId, {
|
|
429
|
+
category: MediaCategory.IMAGE,
|
|
430
|
+
filename: 'photo.jpg',
|
|
431
|
+
mimeType: 'image/jpeg',
|
|
432
|
+
}),
|
|
433
|
+
'Focus on the composition and colors.'
|
|
434
|
+
);
|
|
435
|
+
|
|
436
|
+
// Use in a span
|
|
437
|
+
const span = await ctx.startSpan(SpanKind.LLM, 'gpt-4-vision-preview', {
|
|
438
|
+
input: multimodalInput,
|
|
439
|
+
});
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
### Image Analysis Example
|
|
443
|
+
|
|
444
|
+
```javascript
|
|
445
|
+
const Foil = require('@foil/foil-js');
|
|
446
|
+
const { createFoilTracer, SpanKind, content, ContentBlock, MediaCategory } = require('@foil/foil-js');
|
|
447
|
+
|
|
448
|
+
const foil = new Foil({ apiKey: API_KEY, baseUrl: BASE_URL });
|
|
449
|
+
const tracer = createFoilTracer({ apiKey: API_KEY, agentName: 'vision-agent', baseUrl: BASE_URL });
|
|
450
|
+
|
|
451
|
+
// Upload the image
|
|
452
|
+
const uploadResult = await foil.uploadMedia('/path/to/image.jpg');
|
|
453
|
+
|
|
454
|
+
// Create a trace with multimodal content
|
|
455
|
+
await tracer.trace(async (ctx) => {
|
|
456
|
+
const span = await ctx.startSpan(SpanKind.LLM, 'gpt-4-vision-preview', {
|
|
457
|
+
input: content(
|
|
458
|
+
'What objects do you see in this image?',
|
|
459
|
+
ContentBlock.media(uploadResult.mediaId, {
|
|
460
|
+
category: MediaCategory.IMAGE,
|
|
461
|
+
filename: uploadResult.filename,
|
|
462
|
+
mimeType: uploadResult.mimeType,
|
|
463
|
+
})
|
|
464
|
+
),
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
// Call vision model...
|
|
468
|
+
const response = await callVisionModel(uploadResult.mediaId);
|
|
469
|
+
|
|
470
|
+
await span.end({
|
|
471
|
+
output: response.description,
|
|
472
|
+
tokens: { prompt: 1200, completion: 150, total: 1350 },
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
return response;
|
|
476
|
+
}, {
|
|
477
|
+
name: 'image-analysis',
|
|
478
|
+
input: content(
|
|
479
|
+
'User uploaded an image for analysis',
|
|
480
|
+
ContentBlock.media(uploadResult.mediaId, {
|
|
481
|
+
category: MediaCategory.IMAGE,
|
|
482
|
+
filename: uploadResult.filename,
|
|
483
|
+
})
|
|
484
|
+
),
|
|
485
|
+
});
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
### Media Retrieval
|
|
489
|
+
|
|
490
|
+
```javascript
|
|
491
|
+
// Get media information
|
|
492
|
+
const mediaInfo = await foil.getMedia(mediaId);
|
|
493
|
+
|
|
494
|
+
// Get presigned URL for download
|
|
495
|
+
const urlInfo = await foil.getMediaUrl(mediaId, 'original');
|
|
496
|
+
console.log(urlInfo.url); // Presigned S3 URL
|
|
497
|
+
|
|
498
|
+
// Get extracted content URL (for documents)
|
|
499
|
+
const extractedUrl = await foil.getMediaUrl(mediaId, 'extracted');
|
|
500
|
+
|
|
501
|
+
// Batch get multiple media
|
|
502
|
+
const batchInfo = await foil.batchMediaInfo([mediaId1, mediaId2, mediaId3]);
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
## Signals & Feedback
|
|
506
|
+
|
|
507
|
+
Record user feedback and custom signals for your traces:
|
|
508
|
+
|
|
509
|
+
```javascript
|
|
510
|
+
await tracer.trace(async (ctx) => {
|
|
511
|
+
// ... agent logic ...
|
|
512
|
+
|
|
513
|
+
// Record user feedback (thumbs up/down)
|
|
514
|
+
await ctx.recordFeedback(true); // thumbs up
|
|
515
|
+
|
|
516
|
+
// Record a rating
|
|
517
|
+
await ctx.recordRating(5); // 5-star rating
|
|
518
|
+
|
|
519
|
+
// Record custom signal
|
|
520
|
+
await ctx.recordSignal('response_quality', 0.95, {
|
|
521
|
+
signalType: 'quality',
|
|
522
|
+
source: 'llm', // or 'user', 'system'
|
|
523
|
+
metadata: { evaluator: 'gpt-4' },
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
return result;
|
|
527
|
+
}, { name: 'support-query' });
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
### Direct Signal Recording
|
|
531
|
+
|
|
532
|
+
```javascript
|
|
533
|
+
const foil = new Foil({ apiKey: API_KEY });
|
|
534
|
+
|
|
535
|
+
// Record a signal for a specific trace
|
|
536
|
+
await foil.recordSignal({
|
|
537
|
+
traceId: 'trace_123',
|
|
538
|
+
signalName: 'user_satisfaction',
|
|
539
|
+
value: 4,
|
|
540
|
+
signalType: 'feedback',
|
|
541
|
+
source: 'user',
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
// Batch record signals
|
|
545
|
+
await foil.recordSignalBatch([
|
|
546
|
+
{ traceId: 'trace_123', signalName: 'thumbs', value: true, source: 'user' },
|
|
547
|
+
{ traceId: 'trace_123', signalName: 'rating', value: 5, source: 'user' },
|
|
548
|
+
]);
|
|
549
|
+
|
|
550
|
+
// Get signals for a trace
|
|
551
|
+
const signals = await foil.getTraceSignals('trace_123');
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
## Framework Integrations
|
|
555
|
+
|
|
556
|
+
### OpenAI Integration
|
|
557
|
+
|
|
558
|
+
Automatic tracing for OpenAI calls:
|
|
559
|
+
|
|
560
|
+
```javascript
|
|
561
|
+
const OpenAI = require('openai');
|
|
562
|
+
const { createFoilTracer } = require('@foil/foil-js');
|
|
563
|
+
|
|
564
|
+
const tracer = createFoilTracer({ apiKey: API_KEY, agentName: 'my-agent' });
|
|
565
|
+
|
|
566
|
+
await tracer.trace(async (ctx) => {
|
|
567
|
+
const openai = new OpenAI();
|
|
568
|
+
|
|
569
|
+
// Wrap OpenAI client for automatic tracing
|
|
570
|
+
tracer.wrapOpenAI(openai, { context: ctx });
|
|
571
|
+
|
|
572
|
+
// All calls are now automatically traced
|
|
573
|
+
const response = await openai.chat.completions.create({
|
|
574
|
+
model: 'gpt-4',
|
|
575
|
+
messages: [{ role: 'user', content: 'Hello!' }],
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
return response.choices[0].message.content;
|
|
579
|
+
}, { name: 'chat' });
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
### LangChain Integration
|
|
583
|
+
|
|
584
|
+
```javascript
|
|
585
|
+
const { createFoilTracer, createLangChainCallback } = require('@foil/foil-js');
|
|
586
|
+
|
|
587
|
+
const tracer = createFoilTracer({ apiKey: API_KEY, agentName: 'langchain-agent' });
|
|
588
|
+
const callbacks = [createLangChainCallback(tracer)];
|
|
589
|
+
|
|
590
|
+
// Use with LangChain
|
|
591
|
+
const chain = new LLMChain({ llm, prompt });
|
|
592
|
+
const result = await chain.call({ input: 'Hello' }, { callbacks });
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
### Vercel AI SDK Integration
|
|
596
|
+
|
|
597
|
+
```javascript
|
|
598
|
+
const { createFoilTracer, createVercelAICallbacks } = require('@foil/foil-js');
|
|
599
|
+
|
|
600
|
+
const tracer = createFoilTracer({ apiKey: API_KEY, agentName: 'vercel-agent' });
|
|
601
|
+
const callbacks = createVercelAICallbacks(tracer);
|
|
602
|
+
|
|
603
|
+
// Use with Vercel AI SDK
|
|
604
|
+
const result = await streamText({
|
|
605
|
+
model: openai('gpt-4'),
|
|
606
|
+
messages,
|
|
607
|
+
...callbacks,
|
|
608
|
+
});
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
## OpenTelemetry Integration
|
|
612
|
+
|
|
613
|
+
Foil supports OpenTelemetry (OTEL) for automatic instrumentation of LLM calls. This enables zero-code tracing when combined with instrumentations like [OpenLLMetry](https://github.com/traceloop/openllmetry-js).
|
|
614
|
+
|
|
615
|
+
### Quick Setup with Foil.init()
|
|
616
|
+
|
|
617
|
+
The easiest way to set up OpenTelemetry with Foil:
|
|
618
|
+
|
|
619
|
+
```javascript
|
|
620
|
+
const { Foil } = require('@foil/foil-js/otel');
|
|
621
|
+
|
|
622
|
+
// Initialize Foil with OpenTelemetry
|
|
623
|
+
Foil.init({
|
|
624
|
+
apiKey: process.env.FOIL_API_KEY,
|
|
625
|
+
agentName: 'my-ai-agent', // Optional, groups traces under this agent in Foil
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
// Now all OpenTelemetry traces will be exported to Foil
|
|
629
|
+
// Add instrumentations like OpenLLMetry for automatic LLM tracing
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
### Using with OpenLLMetry
|
|
633
|
+
|
|
634
|
+
[OpenLLMetry](https://github.com/traceloop/openllmetry-js) provides automatic instrumentation for popular LLM libraries:
|
|
635
|
+
|
|
636
|
+
```bash
|
|
637
|
+
npm install @traceloop/node-server-sdk @foil/foil-js @opentelemetry/sdk-trace-node
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
```javascript
|
|
641
|
+
const { Foil } = require('@foil/foil-js/otel');
|
|
642
|
+
const Traceloop = require('@traceloop/node-server-sdk');
|
|
643
|
+
|
|
644
|
+
// Initialize Foil first
|
|
645
|
+
const { provider } = Foil.init({
|
|
646
|
+
apiKey: process.env.FOIL_API_KEY,
|
|
647
|
+
agentName: 'my-openai-agent',
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
// Initialize OpenLLMetry with the Foil provider
|
|
651
|
+
Traceloop.initialize({
|
|
652
|
+
disableBatch: true, // Send traces immediately
|
|
653
|
+
tracerProvider: provider,
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
// Now OpenAI, Anthropic, LangChain, etc. calls are automatically traced
|
|
657
|
+
const OpenAI = require('openai');
|
|
658
|
+
const openai = new OpenAI();
|
|
659
|
+
|
|
660
|
+
const response = await openai.chat.completions.create({
|
|
661
|
+
model: 'gpt-4',
|
|
662
|
+
messages: [{ role: 'user', content: 'Hello!' }],
|
|
663
|
+
});
|
|
664
|
+
// ↑ This call is automatically traced to Foil!
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
### Manual Setup with FoilSpanProcessor
|
|
668
|
+
|
|
669
|
+
For more control over the OpenTelemetry configuration:
|
|
670
|
+
|
|
671
|
+
```javascript
|
|
672
|
+
const { FoilSpanProcessor } = require('@foil/foil-js/otel');
|
|
673
|
+
const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');
|
|
674
|
+
const { Resource } = require('@opentelemetry/resources');
|
|
675
|
+
|
|
676
|
+
// Create a custom provider
|
|
677
|
+
const provider = new NodeTracerProvider({
|
|
678
|
+
resource: new Resource({
|
|
679
|
+
'service.name': 'my-custom-agent',
|
|
680
|
+
'deployment.environment': 'production',
|
|
681
|
+
}),
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
// Add the Foil processor
|
|
685
|
+
provider.addSpanProcessor(new FoilSpanProcessor({
|
|
686
|
+
apiKey: process.env.FOIL_API_KEY,
|
|
687
|
+
maxBatchSize: 100, // Max spans per batch (default: 100)
|
|
688
|
+
scheduledDelayMs: 5000, // Batch export interval (default: 5000ms)
|
|
689
|
+
exportTimeoutMs: 30000, // Request timeout (default: 30000ms)
|
|
690
|
+
debug: true, // Enable debug logging
|
|
691
|
+
}));
|
|
692
|
+
|
|
693
|
+
// Register globally
|
|
694
|
+
provider.register();
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
### Configuration Options
|
|
698
|
+
|
|
699
|
+
#### Foil.init() Options
|
|
700
|
+
|
|
701
|
+
| Option | Type | Required | Description |
|
|
702
|
+
|--------|------|----------|-------------|
|
|
703
|
+
| `apiKey` | string | Yes | Your Foil API key |
|
|
704
|
+
| `agentName` | string | No | Agent name for grouping traces in Foil |
|
|
705
|
+
| `endpoint` | string | No | Custom OTLP endpoint URL |
|
|
706
|
+
| `debug` | boolean | No | Enable debug logging |
|
|
707
|
+
|
|
708
|
+
#### FoilSpanProcessor Options
|
|
709
|
+
|
|
710
|
+
| Option | Type | Required | Description |
|
|
711
|
+
|--------|------|----------|-------------|
|
|
712
|
+
| `apiKey` | string | Yes | Your Foil API key |
|
|
713
|
+
| `endpoint` | string | No | Custom OTLP endpoint (default: `https://api.foil.dev/api/otlp/v1/traces`) |
|
|
714
|
+
| `maxBatchSize` | number | No | Maximum spans per batch (default: 100) |
|
|
715
|
+
| `scheduledDelayMs` | number | No | Batch export interval in ms (default: 5000) |
|
|
716
|
+
| `exportTimeoutMs` | number | No | Export request timeout in ms (default: 30000) |
|
|
717
|
+
| `debug` | boolean | No | Enable debug logging |
|
|
718
|
+
|
|
719
|
+
### Environment Variables
|
|
720
|
+
|
|
721
|
+
| Variable | Description |
|
|
722
|
+
|----------|-------------|
|
|
723
|
+
| `FOIL_API_KEY` | Foil API key (alternative to passing in options) |
|
|
724
|
+
| `FOIL_OTLP_ENDPOINT` | Custom OTLP endpoint URL |
|
|
725
|
+
| `FOIL_DEBUG` | Set to `'true'` to enable debug logging |
|
|
726
|
+
| `OTEL_SERVICE_NAME` | Default service name for traces |
|
|
727
|
+
|
|
728
|
+
### Supported Instrumentations
|
|
729
|
+
|
|
730
|
+
When using OpenLLMetry or similar, the following are automatically instrumented:
|
|
731
|
+
|
|
732
|
+
- **OpenAI** - Chat completions, embeddings, assistants
|
|
733
|
+
- **Anthropic** - Claude messages
|
|
734
|
+
- **Azure OpenAI** - All Azure OpenAI endpoints
|
|
735
|
+
- **Cohere** - Chat, generate, embed
|
|
736
|
+
- **Google Generative AI** - Gemini models
|
|
737
|
+
- **Bedrock** - AWS Bedrock runtime
|
|
738
|
+
- **LangChain** - Chains, agents, retrievers
|
|
739
|
+
- **LlamaIndex** - Queries, retrievers
|
|
740
|
+
|
|
741
|
+
### Shutdown and Cleanup
|
|
742
|
+
|
|
743
|
+
Always shut down gracefully to flush pending spans:
|
|
744
|
+
|
|
745
|
+
```javascript
|
|
746
|
+
// Graceful shutdown
|
|
747
|
+
process.on('SIGTERM', async () => {
|
|
748
|
+
await Foil.shutdown();
|
|
749
|
+
process.exit(0);
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
// Or flush manually
|
|
753
|
+
await Foil.flush();
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
### Combining with Manual Tracing
|
|
757
|
+
|
|
758
|
+
You can use OpenTelemetry auto-instrumentation alongside manual Foil tracing:
|
|
759
|
+
|
|
760
|
+
```javascript
|
|
761
|
+
const { Foil } = require('@foil/foil-js/otel');
|
|
762
|
+
const { createFoilTracer, SpanKind } = require('@foil/foil-js');
|
|
763
|
+
|
|
764
|
+
// Set up OTEL for auto-instrumentation
|
|
765
|
+
Foil.init({ apiKey: API_KEY });
|
|
766
|
+
|
|
767
|
+
// Create a manual tracer for custom spans
|
|
768
|
+
const tracer = createFoilTracer({
|
|
769
|
+
apiKey: API_KEY,
|
|
770
|
+
agentName: 'hybrid-agent',
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
await tracer.trace(async (ctx) => {
|
|
774
|
+
// Manual custom span
|
|
775
|
+
const span = await ctx.startSpan(SpanKind.CUSTOM, 'process-result', {
|
|
776
|
+
input: { action: 'validate' },
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
// Auto-instrumented OpenAI call (via OpenLLMetry)
|
|
780
|
+
const openai = new OpenAI();
|
|
781
|
+
const response = await openai.chat.completions.create({
|
|
782
|
+
model: 'gpt-4',
|
|
783
|
+
messages: [{ role: 'user', content: 'Hello!' }],
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
await span.end({ output: { validated: true } });
|
|
787
|
+
|
|
788
|
+
return response;
|
|
789
|
+
}, { name: 'hybrid-trace' });
|
|
790
|
+
```
|
|
791
|
+
|
|
792
|
+
## Experiments (A/B Testing)
|
|
793
|
+
|
|
794
|
+
Get variant assignments for experiments:
|
|
795
|
+
|
|
796
|
+
```javascript
|
|
797
|
+
const foil = new Foil({ apiKey: API_KEY });
|
|
798
|
+
|
|
799
|
+
// Get assigned variant for a user
|
|
800
|
+
const assignment = await foil.getExperimentVariant('experiment_123', userId);
|
|
801
|
+
|
|
802
|
+
if (assignment.inExperiment) {
|
|
803
|
+
console.log(assignment.variantName); // 'control' or 'treatment'
|
|
804
|
+
console.log(assignment.config); // { model: 'gpt-4', temperature: 0.7 }
|
|
805
|
+
}
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
## Low-Level API
|
|
809
|
+
|
|
810
|
+
For more control, use the Foil client directly:
|
|
811
|
+
|
|
812
|
+
```javascript
|
|
813
|
+
const Foil = require('@foil/foil-js');
|
|
814
|
+
|
|
815
|
+
const foil = new Foil({
|
|
816
|
+
apiKey: 'your-api-key',
|
|
817
|
+
baseUrl: 'https://api.foil.dev/api',
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
// Manual span management
|
|
821
|
+
const traceId = foil.createTraceId();
|
|
822
|
+
const spanId = foil.createSpanId();
|
|
823
|
+
|
|
824
|
+
await foil.startSpan({
|
|
825
|
+
spanId,
|
|
826
|
+
traceId,
|
|
827
|
+
name: 'gpt-4',
|
|
828
|
+
agentName: 'my-agent',
|
|
829
|
+
spanKind: 'llm',
|
|
830
|
+
input: messages,
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
// ... do work ...
|
|
834
|
+
|
|
835
|
+
await foil.endSpan({
|
|
836
|
+
spanId,
|
|
837
|
+
traceId,
|
|
838
|
+
agentName: 'my-agent',
|
|
839
|
+
output: response,
|
|
840
|
+
tokens: { prompt: 100, completion: 50, total: 150 },
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
// Get trace data
|
|
844
|
+
const trace = await foil.getTrace(traceId);
|
|
845
|
+
|
|
846
|
+
// List traces
|
|
847
|
+
const traces = await foil.listTraces({
|
|
848
|
+
agentName: 'my-agent',
|
|
849
|
+
limit: 10,
|
|
850
|
+
from: '2024-01-01T00:00:00Z',
|
|
851
|
+
});
|
|
852
|
+
```
|
|
853
|
+
|
|
854
|
+
## Debug Mode
|
|
855
|
+
|
|
856
|
+
Enable debug logging to see trace activity:
|
|
857
|
+
|
|
858
|
+
```javascript
|
|
859
|
+
// Via environment variable
|
|
860
|
+
process.env.FOIL_DEBUG = 'true';
|
|
861
|
+
|
|
862
|
+
// Or via tracer options
|
|
863
|
+
const tracer = createFoilTracer({
|
|
864
|
+
apiKey: API_KEY,
|
|
865
|
+
agentName: 'my-agent',
|
|
866
|
+
debug: true,
|
|
867
|
+
});
|
|
868
|
+
```
|
|
869
|
+
|
|
870
|
+
Output:
|
|
871
|
+
```
|
|
872
|
+
[Foil] ━━━ TRACE START: weather-query ━━━ {"traceId":"abc12345"}
|
|
873
|
+
[Foil] ▶ START [llm] gpt-4 {"spanId":"def67890"}
|
|
874
|
+
[Foil] ■ END [llm] gpt-4 {"spanId":"def67890","duration":"1234ms","tokens":18}
|
|
875
|
+
[Foil] ━━━ TRACE END: weather-query ━━━ {"traceId":"abc12345","duration":"1250ms"}
|
|
876
|
+
```
|
|
877
|
+
|
|
878
|
+
## API Reference
|
|
879
|
+
|
|
880
|
+
### Tracer Methods
|
|
881
|
+
|
|
882
|
+
#### `createFoilTracer(options)`
|
|
883
|
+
|
|
884
|
+
Create a tracer instance.
|
|
885
|
+
|
|
886
|
+
| Option | Type | Required | Description |
|
|
887
|
+
|--------|------|----------|-------------|
|
|
888
|
+
| `apiKey` | string | Yes | Your Foil API key |
|
|
889
|
+
| `agentName` | string | Yes | Unique name for your agent |
|
|
890
|
+
| `baseUrl` | string | No | API base URL (default: api.getfoil.ai) |
|
|
891
|
+
| `debug` | boolean | No | Enable debug logging |
|
|
892
|
+
|
|
893
|
+
#### `tracer.trace(fn, options)`
|
|
894
|
+
|
|
895
|
+
Execute a function within a traced context.
|
|
896
|
+
|
|
897
|
+
| Option | Type | Description |
|
|
898
|
+
|--------|------|-------------|
|
|
899
|
+
| `name` | string | Name for the root span |
|
|
900
|
+
| `traceId` | string | Custom trace ID (auto-generated if not provided) |
|
|
901
|
+
| `sessionId` | string | Session ID for conversation tracking |
|
|
902
|
+
| `input` | any | Input to record (supports multimodal content) |
|
|
903
|
+
| `properties` | object | Custom properties |
|
|
904
|
+
|
|
905
|
+
#### `ctx.startSpan(spanKind, name, attributes)`
|
|
906
|
+
|
|
907
|
+
Start a new span within a trace context.
|
|
908
|
+
|
|
909
|
+
| Attribute | Type | Description |
|
|
910
|
+
|-----------|------|-------------|
|
|
911
|
+
| `input` | any | Input to the operation (supports multimodal) |
|
|
912
|
+
| `properties` | object | Custom properties |
|
|
913
|
+
| `parentSpanId` | string | Explicit parent span ID |
|
|
914
|
+
|
|
915
|
+
#### `span.end(result)`
|
|
916
|
+
|
|
917
|
+
End a span with results.
|
|
918
|
+
|
|
919
|
+
| Field | Type | Description |
|
|
920
|
+
|-------|------|-------------|
|
|
921
|
+
| `output` | any | Output from the operation |
|
|
922
|
+
| `tokens` | object | Token usage `{ prompt, completion, total }` |
|
|
923
|
+
| `error` | object | Error info `{ type, message }` |
|
|
924
|
+
| `ttft` | number | Time to first token (ms) |
|
|
925
|
+
|
|
926
|
+
### Foil Client Methods
|
|
927
|
+
|
|
928
|
+
#### Semantic Search
|
|
929
|
+
|
|
930
|
+
| Method | Description |
|
|
931
|
+
|--------|-------------|
|
|
932
|
+
| `semanticSearch(query, options)` | Search spans using natural language |
|
|
933
|
+
| `findSimilarTraces(traceId, options)` | Find traces similar to a given trace |
|
|
934
|
+
| `getSemanticSearchStatus(agentId?)` | Get embedding statistics |
|
|
935
|
+
|
|
936
|
+
#### Custom Evaluations
|
|
937
|
+
|
|
938
|
+
| Method | Description |
|
|
939
|
+
|--------|-------------|
|
|
940
|
+
| `getEvaluationTemplates()` | List available evaluation templates |
|
|
941
|
+
| `getAgentEvaluations(agentId)` | List evaluations for an agent |
|
|
942
|
+
| `getEvaluation(agentId, evaluationId)` | Get evaluation details |
|
|
943
|
+
| `createEvaluation(agentId, data)` | Create a custom evaluation |
|
|
944
|
+
| `updateEvaluation(agentId, evaluationId, data)` | Update an evaluation |
|
|
945
|
+
| `deleteEvaluation(agentId, evaluationId)` | Delete an evaluation |
|
|
946
|
+
| `testEvaluation(agentId, evaluationId, data)` | Test with sample data |
|
|
947
|
+
| `cloneEvaluationTemplate(agentId, templateId)` | Clone a template |
|
|
948
|
+
| `addEvaluationExample(agentId, evaluationId, data)` | Add few-shot example |
|
|
949
|
+
| `removeEvaluationExample(agentId, evaluationId, exampleId)` | Remove example |
|
|
950
|
+
| `getEvaluationAnalytics(agentId, evaluationId)` | Get evaluation metrics |
|
|
951
|
+
|
|
952
|
+
#### Media
|
|
953
|
+
|
|
954
|
+
| Method | Description |
|
|
955
|
+
|--------|-------------|
|
|
956
|
+
| `uploadMedia(file, options)` | Upload media for multimodal content |
|
|
957
|
+
| `getMedia(mediaId, options)` | Get media information |
|
|
958
|
+
| `getMediaUrl(mediaId, content)` | Get presigned download URL |
|
|
959
|
+
| `batchMediaInfo(mediaIds)` | Get info for multiple media |
|
|
960
|
+
|
|
961
|
+
#### Signals
|
|
962
|
+
|
|
963
|
+
| Method | Description |
|
|
964
|
+
|--------|-------------|
|
|
965
|
+
| `recordSignal(data)` | Record a signal |
|
|
966
|
+
| `recordSignalBatch(signals)` | Record multiple signals |
|
|
967
|
+
| `getTraceSignals(traceId)` | Get signals for a trace |
|
|
968
|
+
|
|
969
|
+
#### Traces
|
|
970
|
+
|
|
971
|
+
| Method | Description |
|
|
972
|
+
|--------|-------------|
|
|
973
|
+
| `startSpan(data)` | Start a span |
|
|
974
|
+
| `endSpan(data)` | End a span |
|
|
975
|
+
| `getTrace(traceId)` | Get trace with all spans |
|
|
976
|
+
| `listTraces(options)` | List traces with filters |
|
|
977
|
+
|
|
978
|
+
#### Experiments
|
|
979
|
+
|
|
980
|
+
| Method | Description |
|
|
981
|
+
|--------|-------------|
|
|
982
|
+
| `getExperimentVariant(experimentId, identifier)` | Get variant assignment |
|
|
983
|
+
|
|
984
|
+
### Content Helpers
|
|
985
|
+
|
|
986
|
+
#### `content(...parts)` / `ContentBlock`
|
|
987
|
+
|
|
988
|
+
Create multimodal content arrays.
|
|
989
|
+
|
|
990
|
+
```javascript
|
|
991
|
+
// Using content() helper
|
|
992
|
+
const input = content(
|
|
993
|
+
'Analyze this:',
|
|
994
|
+
ContentBlock.media(mediaId, { category: MediaCategory.IMAGE }),
|
|
995
|
+
'What do you see?'
|
|
996
|
+
);
|
|
997
|
+
|
|
998
|
+
// Manual content block creation
|
|
999
|
+
const blocks = [
|
|
1000
|
+
ContentBlock.text('Hello'),
|
|
1001
|
+
ContentBlock.media(mediaId, { category: MediaCategory.IMAGE, filename: 'photo.jpg' }),
|
|
1002
|
+
];
|
|
1003
|
+
```
|
|
1004
|
+
|
|
1005
|
+
## Examples
|
|
1006
|
+
|
|
1007
|
+
See the `examples/` directory for complete working examples:
|
|
1008
|
+
|
|
1009
|
+
- `tracer-test.js` - Basic tracing setup
|
|
1010
|
+
- `openai-agent-test.js` - OpenAI integration
|
|
1011
|
+
- `multimodal-test.js` - Multimodal content handling
|
|
1012
|
+
- `semantic-search-example.js` - Semantic search usage
|
|
1013
|
+
- `custom-evaluations-example.js` - Custom evaluations
|
|
1014
|
+
- `otel-integration-example.js` - OpenTelemetry integration with multiple approaches
|
|
1015
|
+
|
|
1016
|
+
Run examples:
|
|
1017
|
+
|
|
1018
|
+
```bash
|
|
1019
|
+
npm run example:tracer
|
|
1020
|
+
npm run example:openai
|
|
1021
|
+
npm run example:multimodal
|
|
1022
|
+
npm run example:semantic-search
|
|
1023
|
+
npm run example:evaluations
|
|
1024
|
+
npm run example:otel
|
|
1025
|
+
```
|
|
1026
|
+
|
|
1027
|
+
## License
|
|
1028
|
+
|
|
1029
|
+
MIT
|