@bratsos/workflow-engine 0.0.11
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 +986 -0
- package/package.json +112 -0
- package/skills/workflow-engine/SKILL.md +539 -0
- package/skills/workflow-engine/references/01-stage-definitions.md +535 -0
- package/skills/workflow-engine/references/02-workflow-builder.md +421 -0
- package/skills/workflow-engine/references/03-runtime-setup.md +497 -0
- package/skills/workflow-engine/references/04-ai-integration.md +587 -0
- package/skills/workflow-engine/references/05-persistence-setup.md +614 -0
- package/skills/workflow-engine/references/06-async-batch-stages.md +514 -0
- package/skills/workflow-engine/references/07-testing-patterns.md +546 -0
- package/skills/workflow-engine/references/08-common-patterns.md +503 -0
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
# AI Integration
|
|
2
|
+
|
|
3
|
+
Complete guide for using AIHelper for text generation, structured output, embeddings, and batch operations.
|
|
4
|
+
|
|
5
|
+
## Topic Convention for Cost Tracking
|
|
6
|
+
|
|
7
|
+
**Topics are hierarchical strings that enable cost aggregation at different levels.**
|
|
8
|
+
|
|
9
|
+
### The Convention
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
workflow.{workflowRunId}.stage.{stageId}.{optional-suffix}
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
| Topic Pattern | Use Case | Example |
|
|
16
|
+
|---------------|----------|---------|
|
|
17
|
+
| `workflow.{runId}` | Root topic for a workflow run | `workflow.abc123` |
|
|
18
|
+
| `workflow.{runId}.stage.{stageId}` | Specific stage | `workflow.abc123.stage.extraction` |
|
|
19
|
+
| `workflow.{runId}.stage.{stageId}.tool.{name}` | Tool call within a stage | `workflow.abc123.stage.extraction.tool.search` |
|
|
20
|
+
| `task.{taskId}` | Standalone task (not a workflow) | `task.process-document-456` |
|
|
21
|
+
|
|
22
|
+
### How Cost Aggregation Works
|
|
23
|
+
|
|
24
|
+
The `AICallLogger.getStats(topicPrefix)` method uses **prefix matching** to aggregate costs:
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
// All AI calls are logged with their topic
|
|
28
|
+
ai.generateText("gemini-2.5-flash", prompt); // Logged with topic "workflow.abc123.stage.extraction"
|
|
29
|
+
|
|
30
|
+
// Later, aggregate by prefix:
|
|
31
|
+
const workflowStats = await aiLogger.getStats("workflow.abc123");
|
|
32
|
+
// Returns: all costs for that workflow run (all stages)
|
|
33
|
+
|
|
34
|
+
const stageStats = await aiLogger.getStats("workflow.abc123.stage.extraction");
|
|
35
|
+
// Returns: costs for just that stage
|
|
36
|
+
|
|
37
|
+
const allStats = await aiLogger.getStats("workflow");
|
|
38
|
+
// Returns: costs for ALL workflows
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Automatic Workflow Cost Tracking
|
|
42
|
+
|
|
43
|
+
When a workflow completes, the executor automatically calculates total cost:
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
// Inside WorkflowExecutor (automatic)
|
|
47
|
+
const stats = await aiLogger.getStats(`workflow.${workflowRunId}`);
|
|
48
|
+
await persistence.updateRun(workflowRunId, {
|
|
49
|
+
totalCost: stats.totalCost,
|
|
50
|
+
totalTokens: stats.totalInputTokens + stats.totalOutputTokens,
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Result:** `WorkflowRun.totalCost` and `WorkflowRun.totalTokens` are populated automatically.
|
|
55
|
+
|
|
56
|
+
### Using Topics in Stages
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
const extractStage = defineStage({
|
|
60
|
+
id: "extraction",
|
|
61
|
+
// ...
|
|
62
|
+
async execute(ctx) {
|
|
63
|
+
// Create AI helper with proper topic convention
|
|
64
|
+
const ai = runtime.createAIHelper(
|
|
65
|
+
`workflow.${ctx.workflowRunId}.stage.${ctx.stageId}`
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
// All AI calls are now tracked under this topic
|
|
69
|
+
const { text } = await ai.generateText("gemini-2.5-flash", prompt);
|
|
70
|
+
|
|
71
|
+
return { output: { extracted: text } };
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Querying Costs
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
// Get costs for a specific workflow run
|
|
80
|
+
const runStats = await aiLogger.getStats(`workflow.${workflowRunId}`);
|
|
81
|
+
console.log(`Workflow cost: $${runStats.totalCost.toFixed(4)}`);
|
|
82
|
+
console.log(`Total tokens: ${runStats.totalInputTokens + runStats.totalOutputTokens}`);
|
|
83
|
+
console.log(`Calls by model:`, runStats.perModel);
|
|
84
|
+
|
|
85
|
+
// Get costs for a specific stage
|
|
86
|
+
const stageStats = await aiLogger.getStats(`workflow.${workflowRunId}.stage.extraction`);
|
|
87
|
+
|
|
88
|
+
// Get costs across all workflows (for reporting)
|
|
89
|
+
const allStats = await aiLogger.getStats("workflow");
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Stats Response Type
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
interface AIHelperStats {
|
|
96
|
+
totalCalls: number;
|
|
97
|
+
totalInputTokens: number;
|
|
98
|
+
totalOutputTokens: number;
|
|
99
|
+
totalCost: number;
|
|
100
|
+
perModel: {
|
|
101
|
+
[modelKey: string]: {
|
|
102
|
+
calls: number;
|
|
103
|
+
inputTokens: number;
|
|
104
|
+
outputTokens: number;
|
|
105
|
+
cost: number;
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Creating an AIHelper
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
import { createAIHelper } from "@bratsos/workflow-engine";
|
|
115
|
+
import { createPrismaAICallLogger } from "@bratsos/workflow-engine/persistence/prisma";
|
|
116
|
+
|
|
117
|
+
// Basic usage (with topic convention)
|
|
118
|
+
const ai = createAIHelper(`workflow.${runId}.stage.${stageId}`, aiCallLogger);
|
|
119
|
+
|
|
120
|
+
// With log context (for batch persistence)
|
|
121
|
+
const logContext = {
|
|
122
|
+
workflowRunId: "run-123",
|
|
123
|
+
stageRecordId: "stage-456",
|
|
124
|
+
createLog: (data) => persistence.createLog(data),
|
|
125
|
+
};
|
|
126
|
+
const ai = createAIHelper("workflow.run-123", aiCallLogger, logContext);
|
|
127
|
+
|
|
128
|
+
// From runtime (preferred in stages)
|
|
129
|
+
const ai = runtime.createAIHelper(`workflow.${ctx.workflowRunId}.stage.${ctx.stageId}`);
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## AIHelper Interface
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
interface AIHelper {
|
|
136
|
+
readonly topic: string;
|
|
137
|
+
|
|
138
|
+
generateText(modelKey, prompt, options?): Promise<AITextResult>;
|
|
139
|
+
generateObject(modelKey, prompt, schema, options?): Promise<AIObjectResult>;
|
|
140
|
+
embed(modelKey, text, options?): Promise<AIEmbedResult>;
|
|
141
|
+
streamText(modelKey, input, options?): AIStreamResult;
|
|
142
|
+
batch(modelKey, provider?): AIBatch;
|
|
143
|
+
|
|
144
|
+
createChild(segment, id?): AIHelper;
|
|
145
|
+
recordCall(params): void;
|
|
146
|
+
getStats(): Promise<AIHelperStats>;
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## generateText
|
|
151
|
+
|
|
152
|
+
Generate text from a prompt.
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
const result = await ai.generateText(
|
|
156
|
+
"gemini-2.5-flash", // Model key
|
|
157
|
+
"Explain quantum computing in simple terms",
|
|
158
|
+
{
|
|
159
|
+
temperature: 0.7, // 0-2, default 0.7
|
|
160
|
+
maxTokens: 1000, // Max output tokens
|
|
161
|
+
}
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
console.log(result.text); // Generated text
|
|
165
|
+
console.log(result.inputTokens); // Token count
|
|
166
|
+
console.log(result.outputTokens);
|
|
167
|
+
console.log(result.cost); // Calculated cost
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Multimodal Input
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
// With images/PDFs
|
|
174
|
+
const result = await ai.generateText("gemini-2.5-flash", [
|
|
175
|
+
{ type: "text", text: "What's in this image?" },
|
|
176
|
+
{
|
|
177
|
+
type: "file",
|
|
178
|
+
data: imageBuffer, // Buffer, Uint8Array, or base64 string
|
|
179
|
+
mediaType: "image/png",
|
|
180
|
+
filename: "image.png",
|
|
181
|
+
},
|
|
182
|
+
]);
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### With Tools
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
import { tool } from "ai";
|
|
189
|
+
|
|
190
|
+
const result = await ai.generateText("gemini-2.5-flash", "What's the weather?", {
|
|
191
|
+
tools: {
|
|
192
|
+
getWeather: tool({
|
|
193
|
+
description: "Get weather for a location",
|
|
194
|
+
parameters: z.object({ location: z.string() }),
|
|
195
|
+
execute: async ({ location }) => {
|
|
196
|
+
return { temperature: 72, condition: "sunny" };
|
|
197
|
+
},
|
|
198
|
+
}),
|
|
199
|
+
},
|
|
200
|
+
toolChoice: "auto", // or "required", "none", { type: "tool", toolName: "getWeather" }
|
|
201
|
+
onStepFinish: async (step) => {
|
|
202
|
+
console.log("Tool results:", step.toolResults);
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## generateObject
|
|
208
|
+
|
|
209
|
+
Generate structured output with Zod schema validation.
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
const OutputSchema = z.object({
|
|
213
|
+
title: z.string(),
|
|
214
|
+
tags: z.array(z.string()),
|
|
215
|
+
sentiment: z.enum(["positive", "negative", "neutral"]),
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
const result = await ai.generateObject(
|
|
219
|
+
"gemini-2.5-flash",
|
|
220
|
+
"Analyze this article: ...",
|
|
221
|
+
OutputSchema,
|
|
222
|
+
{
|
|
223
|
+
temperature: 0, // Lower for structured output
|
|
224
|
+
maxTokens: 500,
|
|
225
|
+
}
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
console.log(result.object); // Typed as z.infer<typeof OutputSchema>
|
|
229
|
+
// { title: "...", tags: ["tech", "ai"], sentiment: "positive" }
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Multimodal with Schema
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
const result = await ai.generateObject(
|
|
236
|
+
"gemini-2.5-flash",
|
|
237
|
+
[
|
|
238
|
+
{ type: "text", text: "Extract text from this document" },
|
|
239
|
+
{ type: "file", data: pdfBuffer, mediaType: "application/pdf" },
|
|
240
|
+
],
|
|
241
|
+
z.object({
|
|
242
|
+
title: z.string(),
|
|
243
|
+
content: z.string(),
|
|
244
|
+
pageCount: z.number(),
|
|
245
|
+
})
|
|
246
|
+
);
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## embed
|
|
250
|
+
|
|
251
|
+
Generate embeddings for text.
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
// Single text
|
|
255
|
+
const result = await ai.embed(
|
|
256
|
+
"text-embedding-004",
|
|
257
|
+
"The quick brown fox",
|
|
258
|
+
{
|
|
259
|
+
dimensions: 768, // Output dimensions (default: 768)
|
|
260
|
+
taskType: "RETRIEVAL_DOCUMENT", // or "RETRIEVAL_QUERY", "SEMANTIC_SIMILARITY"
|
|
261
|
+
}
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
console.log(result.embedding); // number[] (768 dimensions)
|
|
265
|
+
console.log(result.dimensions); // 768
|
|
266
|
+
console.log(result.inputTokens);
|
|
267
|
+
console.log(result.cost);
|
|
268
|
+
|
|
269
|
+
// Multiple texts (batch)
|
|
270
|
+
const result = await ai.embed("text-embedding-004", [
|
|
271
|
+
"First document",
|
|
272
|
+
"Second document",
|
|
273
|
+
"Third document",
|
|
274
|
+
]);
|
|
275
|
+
|
|
276
|
+
console.log(result.embeddings); // number[][] (3 embeddings)
|
|
277
|
+
console.log(result.embedding); // First embedding (convenience)
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## streamText
|
|
281
|
+
|
|
282
|
+
Stream text generation.
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
const result = ai.streamText(
|
|
286
|
+
"gemini-2.5-flash",
|
|
287
|
+
{ prompt: "Write a story about a robot" },
|
|
288
|
+
{
|
|
289
|
+
temperature: 0.9,
|
|
290
|
+
onChunk: (chunk) => process.stdout.write(chunk),
|
|
291
|
+
}
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
// Consume the stream
|
|
295
|
+
for await (const chunk of result.stream) {
|
|
296
|
+
console.log(chunk);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Get usage after stream completes
|
|
300
|
+
const usage = await result.getUsage();
|
|
301
|
+
console.log(usage.cost);
|
|
302
|
+
|
|
303
|
+
// Or use raw AI SDK result for UI streaming
|
|
304
|
+
const response = result.rawResult.toUIMessageStreamResponse();
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### With Messages
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
const result = ai.streamText("gemini-2.5-flash", {
|
|
311
|
+
system: "You are a helpful assistant.",
|
|
312
|
+
messages: [
|
|
313
|
+
{ role: "user", content: "Hello!" },
|
|
314
|
+
{ role: "assistant", content: "Hi! How can I help?" },
|
|
315
|
+
{ role: "user", content: "Tell me a joke." },
|
|
316
|
+
],
|
|
317
|
+
});
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## batch
|
|
321
|
+
|
|
322
|
+
Submit batch operations for 50% cost savings.
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
const batch = ai.batch<OutputType>("claude-sonnet-4-20250514", "anthropic");
|
|
326
|
+
|
|
327
|
+
// Submit requests
|
|
328
|
+
const handle = await batch.submit([
|
|
329
|
+
{ id: "req-1", prompt: "Summarize: ..." },
|
|
330
|
+
{ id: "req-2", prompt: "Summarize: ...", schema: SummarySchema },
|
|
331
|
+
]);
|
|
332
|
+
|
|
333
|
+
console.log(handle.id); // Batch ID
|
|
334
|
+
console.log(handle.status); // "pending"
|
|
335
|
+
console.log(handle.provider); // "anthropic"
|
|
336
|
+
|
|
337
|
+
// Check status
|
|
338
|
+
const status = await batch.getStatus(handle.id);
|
|
339
|
+
// { id: "...", status: "processing" | "completed" | "failed", provider: "anthropic" }
|
|
340
|
+
|
|
341
|
+
// Get results (when completed)
|
|
342
|
+
const results = await batch.getResults(handle.id);
|
|
343
|
+
// [
|
|
344
|
+
// { id: "req-1", result: "...", inputTokens: 100, outputTokens: 50, status: "succeeded" },
|
|
345
|
+
// { id: "req-2", result: { parsed: "object" }, inputTokens: 100, outputTokens: 50, status: "succeeded" },
|
|
346
|
+
// ]
|
|
347
|
+
|
|
348
|
+
// Check if already recorded (avoid duplicate logging)
|
|
349
|
+
const recorded = await batch.isRecorded(handle.id);
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### Batch Providers
|
|
353
|
+
|
|
354
|
+
| Provider | Models | Discount |
|
|
355
|
+
|----------|--------|----------|
|
|
356
|
+
| `anthropic` | Claude models | 50% |
|
|
357
|
+
| `google` | Gemini models | 50% |
|
|
358
|
+
| `openai` | GPT models | 50% |
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
// Provider auto-detected based on model
|
|
362
|
+
const batch = ai.batch("claude-sonnet-4-20250514"); // Uses anthropic
|
|
363
|
+
|
|
364
|
+
// Or specify explicitly
|
|
365
|
+
const batch = ai.batch("gemini-2.5-flash", "google");
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
## Child Helpers
|
|
369
|
+
|
|
370
|
+
Create child helpers for hierarchical topic tracking.
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
const rootAI = createAIHelper("workflow.abc123", logger);
|
|
374
|
+
|
|
375
|
+
// Create child for a specific stage
|
|
376
|
+
const stageAI = rootAI.createChild("stage", "extraction");
|
|
377
|
+
// topic: "workflow.abc123.stage.extraction"
|
|
378
|
+
|
|
379
|
+
// Create child for a tool call
|
|
380
|
+
const toolAI = stageAI.createChild("tool", "search");
|
|
381
|
+
// topic: "workflow.abc123.stage.extraction.tool.search"
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
## Manual Recording
|
|
385
|
+
|
|
386
|
+
Record AI calls made outside the helper (e.g., direct SDK usage).
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
// Object-based API
|
|
390
|
+
ai.recordCall({
|
|
391
|
+
modelKey: "gemini-2.5-flash",
|
|
392
|
+
callType: "text",
|
|
393
|
+
prompt: "...",
|
|
394
|
+
response: "...",
|
|
395
|
+
inputTokens: 100,
|
|
396
|
+
outputTokens: 50,
|
|
397
|
+
metadata: { custom: "data" },
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
// Legacy positional API
|
|
401
|
+
ai.recordCall(
|
|
402
|
+
"gemini-2.5-flash",
|
|
403
|
+
"prompt text",
|
|
404
|
+
"response text",
|
|
405
|
+
{ input: 100, output: 50 },
|
|
406
|
+
{ callType: "text", isBatch: false }
|
|
407
|
+
);
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
## Statistics
|
|
411
|
+
|
|
412
|
+
Get aggregated stats for a topic prefix.
|
|
413
|
+
|
|
414
|
+
```typescript
|
|
415
|
+
const stats = await ai.getStats();
|
|
416
|
+
// {
|
|
417
|
+
// totalCalls: 42,
|
|
418
|
+
// totalInputTokens: 50000,
|
|
419
|
+
// totalOutputTokens: 25000,
|
|
420
|
+
// totalCost: 0.15,
|
|
421
|
+
// perModel: {
|
|
422
|
+
// "gemini-2.5-flash": { calls: 30, inputTokens: 40000, outputTokens: 20000, cost: 0.10 },
|
|
423
|
+
// "claude-sonnet-4-20250514": { calls: 12, inputTokens: 10000, outputTokens: 5000, cost: 0.05 },
|
|
424
|
+
// },
|
|
425
|
+
// }
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
## Model Configuration
|
|
429
|
+
|
|
430
|
+
### Available Models
|
|
431
|
+
|
|
432
|
+
```typescript
|
|
433
|
+
import { AVAILABLE_MODELS, listModels } from "@bratsos/workflow-engine";
|
|
434
|
+
|
|
435
|
+
// List all models
|
|
436
|
+
const models = listModels();
|
|
437
|
+
// [
|
|
438
|
+
// { key: "gemini-2.5-flash", id: "google/gemini-2.5-flash-preview-05-20", ... },
|
|
439
|
+
// { key: "claude-sonnet-4-20250514", id: "anthropic/claude-sonnet-4-20250514", ... },
|
|
440
|
+
// ...
|
|
441
|
+
// ]
|
|
442
|
+
|
|
443
|
+
// Filter models
|
|
444
|
+
const flashModels = listModels({ provider: "google", capabilities: ["embedding"] });
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### Register Custom Models
|
|
448
|
+
|
|
449
|
+
```typescript
|
|
450
|
+
import { registerModels } from "@bratsos/workflow-engine";
|
|
451
|
+
|
|
452
|
+
registerModels({
|
|
453
|
+
"my-custom-model": {
|
|
454
|
+
id: "openrouter/my-model",
|
|
455
|
+
provider: "openrouter",
|
|
456
|
+
inputCostPerMillion: 0.5,
|
|
457
|
+
outputCostPerMillion: 1.0,
|
|
458
|
+
contextWindow: 128000,
|
|
459
|
+
maxOutput: 4096,
|
|
460
|
+
capabilities: ["text", "vision"],
|
|
461
|
+
},
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
// Now usable
|
|
465
|
+
const result = await ai.generateText("my-custom-model", prompt);
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
### Cost Calculation
|
|
469
|
+
|
|
470
|
+
```typescript
|
|
471
|
+
import { calculateCost } from "@bratsos/workflow-engine";
|
|
472
|
+
|
|
473
|
+
const cost = calculateCost("gemini-2.5-flash", 1000, 500);
|
|
474
|
+
// {
|
|
475
|
+
// inputCost: 0.00015,
|
|
476
|
+
// outputCost: 0.0003,
|
|
477
|
+
// totalCost: 0.00045,
|
|
478
|
+
// }
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
## Result Types
|
|
482
|
+
|
|
483
|
+
```typescript
|
|
484
|
+
interface AITextResult {
|
|
485
|
+
text: string;
|
|
486
|
+
inputTokens: number;
|
|
487
|
+
outputTokens: number;
|
|
488
|
+
cost: number;
|
|
489
|
+
output?: any; // Present when experimental_output is used
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
interface AIObjectResult<T> {
|
|
493
|
+
object: T;
|
|
494
|
+
inputTokens: number;
|
|
495
|
+
outputTokens: number;
|
|
496
|
+
cost: number;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
interface AIEmbedResult {
|
|
500
|
+
embedding: number[]; // First embedding
|
|
501
|
+
embeddings: number[][]; // All embeddings
|
|
502
|
+
dimensions: number;
|
|
503
|
+
inputTokens: number;
|
|
504
|
+
cost: number;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
interface AIStreamResult {
|
|
508
|
+
stream: AsyncIterable<string>;
|
|
509
|
+
getUsage(): Promise<{ inputTokens, outputTokens, cost }>;
|
|
510
|
+
rawResult: AISDKStreamResult;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
interface AIBatchResult<T = string> {
|
|
514
|
+
id: string;
|
|
515
|
+
prompt: string;
|
|
516
|
+
result: T;
|
|
517
|
+
inputTokens: number;
|
|
518
|
+
outputTokens: number;
|
|
519
|
+
status: "succeeded" | "failed";
|
|
520
|
+
error?: string;
|
|
521
|
+
}
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
## Complete Example
|
|
525
|
+
|
|
526
|
+
```typescript
|
|
527
|
+
import {
|
|
528
|
+
createAIHelper,
|
|
529
|
+
createPrismaAICallLogger,
|
|
530
|
+
registerModels,
|
|
531
|
+
} from "@bratsos/workflow-engine";
|
|
532
|
+
import { z } from "zod";
|
|
533
|
+
|
|
534
|
+
// Setup
|
|
535
|
+
const logger = createPrismaAICallLogger(prisma);
|
|
536
|
+
const ai = createAIHelper("document-processor", logger);
|
|
537
|
+
|
|
538
|
+
// Register a custom model if needed
|
|
539
|
+
registerModels({
|
|
540
|
+
"fast-model": {
|
|
541
|
+
id: "openrouter/fast-model",
|
|
542
|
+
provider: "openrouter",
|
|
543
|
+
inputCostPerMillion: 0.1,
|
|
544
|
+
outputCostPerMillion: 0.2,
|
|
545
|
+
},
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
// Processing pipeline
|
|
549
|
+
async function processDocument(content: string) {
|
|
550
|
+
// 1. Extract structure
|
|
551
|
+
const { object: structure } = await ai.generateObject(
|
|
552
|
+
"gemini-2.5-flash",
|
|
553
|
+
`Extract structure from:\n\n${content}`,
|
|
554
|
+
z.object({
|
|
555
|
+
title: z.string(),
|
|
556
|
+
sections: z.array(z.object({
|
|
557
|
+
heading: z.string(),
|
|
558
|
+
content: z.string(),
|
|
559
|
+
})),
|
|
560
|
+
})
|
|
561
|
+
);
|
|
562
|
+
|
|
563
|
+
// 2. Generate embeddings for each section
|
|
564
|
+
const embeddings = await ai.embed(
|
|
565
|
+
"text-embedding-004",
|
|
566
|
+
structure.sections.map(s => s.content)
|
|
567
|
+
);
|
|
568
|
+
|
|
569
|
+
// 3. Generate summary
|
|
570
|
+
const { text: summary } = await ai.generateText(
|
|
571
|
+
"gemini-2.5-flash",
|
|
572
|
+
`Summarize in 2 sentences:\n\n${content}`,
|
|
573
|
+
{ maxTokens: 100 }
|
|
574
|
+
);
|
|
575
|
+
|
|
576
|
+
// 4. Check stats
|
|
577
|
+
const stats = await ai.getStats();
|
|
578
|
+
console.log(`Total cost: $${stats.totalCost.toFixed(4)}`);
|
|
579
|
+
|
|
580
|
+
return {
|
|
581
|
+
structure,
|
|
582
|
+
embeddings: embeddings.embeddings,
|
|
583
|
+
summary,
|
|
584
|
+
cost: stats.totalCost,
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
```
|