@ax-llm/ax 19.0.15 → 19.0.17

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.
@@ -0,0 +1,402 @@
1
+ ---
2
+ name: ax-flow
3
+ description: This skill helps an LLM generate correct AxFlow workflow code using @ax-llm/ax. Use when the user asks about flow(), AxFlow, workflow orchestration, parallel execution, DAG workflows, conditional routing, map/reduce patterns, or multi-node AI pipelines.
4
+ version: "19.0.17"
5
+ ---
6
+
7
+ # AxFlow Codegen Rules (@ax-llm/ax)
8
+
9
+ Use this skill to generate `AxFlow` workflow code. Prefer short, modern, copyable patterns. Do not write tutorial prose unless the user explicitly asks for explanation.
10
+
11
+ ## Use These Defaults
12
+
13
+ - Use `flow()` factory, not `new AxFlow()`.
14
+ - Import: `import { ai, flow, f } from '@ax-llm/ax';`
15
+ - `autoParallel: true` is the default; independent executes run in parallel automatically.
16
+ - Node results are stored as `${nodeName}Result` in state.
17
+ - Always define `.node()` before `.execute()` for that node.
18
+ - Use `.returns()` (or `.r()`) as the last step to lock the output type.
19
+ - Use descriptive node names: `documentSummarizer`, not `proc1`.
20
+ - Use descriptive field names: `userInput`, `responseText`, not `text`, `result`.
21
+
22
+ ## Critical Rules
23
+
24
+ - Use `flow()` factory syntax for new code.
25
+ - Node results in state follow the pattern `state.${nodeName}Result.${fieldName}`.
26
+ - `.execute()` maps current state to node inputs; `.map()` transforms state without AI calls.
27
+ - `.returns()` maps final state to the flow output type.
28
+ - Always define nodes before executing them; reversed order throws at runtime.
29
+ - Keep state flat; avoid deep nesting in `.map()`.
30
+ - Ensure loop conditions can change to avoid infinite loops.
31
+ - Structure independent executes to maximize auto-parallelization.
32
+ - Use `flow<InputType, OutputType>()` for typed flows.
33
+ - Aliases: `.n()` = `.node()`, `.nx()` = `.nodeExtended()`, `.m()` = `.map()`, `.r()` = `.returns()`.
34
+
35
+ ## Canonical Pattern
36
+
37
+ ```typescript
38
+ import { ai, flow } from '@ax-llm/ax';
39
+
40
+ const llm = ai({ name: 'openai', apiKey: process.env.OPENAI_APIKEY! });
41
+
42
+ const wf = flow<{ userInput: string }, { responseText: string }>()
43
+ .node('testNode', 'userInput:string -> responseText:string')
44
+ .execute('testNode', (state) => ({ userInput: state.userInput }))
45
+ .returns((state) => ({ responseText: state.testNodeResult.responseText }));
46
+
47
+ const result = await wf.forward(llm, { userInput: 'Hello world' });
48
+ console.log(result.responseText);
49
+ ```
50
+
51
+ ## Factory Options
52
+
53
+ ```typescript
54
+ // Basic
55
+ const wf = flow();
56
+
57
+ // With options
58
+ const wf = flow({ autoParallel: false });
59
+
60
+ // Typed
61
+ const wf = flow<InputType, OutputType>();
62
+
63
+ // Typed with options
64
+ const wf = flow<InputType, OutputType>({ autoParallel: true, batchSize: 5 });
65
+ ```
66
+
67
+ ## State Evolution
68
+
69
+ State grows with each executed node. Results are stored as `${nodeName}Result`:
70
+
71
+ ```typescript
72
+ // Initial state: { userInput: 'Hello' }
73
+ flow.execute('processor', (state) => ({ input: state.userInput }));
74
+ // State: { userInput: 'Hello', processorResult: { output: '...' } }
75
+
76
+ flow.execute('analyzer', (state) => ({ text: state.processorResult.output }));
77
+ // State: { ..., analyzerResult: { sentiment: '...', confidence: 0.8 } }
78
+ ```
79
+
80
+ ## Node Definition
81
+
82
+ ```typescript
83
+ // String signature (creates AxGen automatically)
84
+ flow.node('processor', 'input:string -> output:string');
85
+
86
+ // Multiple outputs
87
+ flow.node('analyzer', 'text:string -> sentiment:string, confidence:number');
88
+
89
+ // Array outputs
90
+ flow.node('extractor', 'documentText:string -> entities:string[]');
91
+
92
+ // Short alias
93
+ flow.n('processor', 'input:string -> output:string');
94
+ ```
95
+
96
+ ## Extended Nodes (nx)
97
+
98
+ Add fields to a base signature without rewriting it:
99
+
100
+ ```typescript
101
+ import { f, flow } from '@ax-llm/ax';
102
+
103
+ // Chain-of-thought reasoning
104
+ flow.nx('reasoner', 'question:string -> answer:string', {
105
+ prependOutputs: [
106
+ { name: 'reasoning', type: f.internal(f.string('Step-by-step reasoning')) },
107
+ ],
108
+ });
109
+
110
+ // Add confidence scoring
111
+ flow.nx('analyzer', 'input:string -> result:string', {
112
+ appendOutputs: [{ name: 'confidence', type: f.number('Confidence 0-1') }],
113
+ });
114
+
115
+ // Add optional context input
116
+ flow.nx('processor', 'query:string -> response:string', {
117
+ appendInputs: [{ name: 'context', type: f.optional(f.string('Extra context')) }],
118
+ });
119
+ ```
120
+
121
+ Extension options: `prependInputs`, `appendInputs`, `prependOutputs`, `appendOutputs`.
122
+
123
+ ## Execute With Input Mapping
124
+
125
+ ```typescript
126
+ flow.execute('summarizer', (state) => ({ documentText: state.document }));
127
+
128
+ // With AI override (use a different model for this node)
129
+ flow.execute('processor', (state) => ({ input: state.data }), { ai: alternativeAI });
130
+ ```
131
+
132
+ ## Map (State Transformation)
133
+
134
+ Use `map()` for data shaping without AI calls:
135
+
136
+ ```typescript
137
+ // Sync
138
+ flow.map((state) => ({ ...state, upperText: state.rawText.toUpperCase() }));
139
+
140
+ // Async
141
+ flow.map(async (state) => {
142
+ const data = await fetchFromAPI(state.query);
143
+ return { ...state, enrichedData: data };
144
+ });
145
+
146
+ // Parallel async transforms
147
+ flow.map([
148
+ async (state) => ({ ...state, result1: await api1(state.data) }),
149
+ async (state) => ({ ...state, result2: await api2(state.data) }),
150
+ ], { parallel: true });
151
+ ```
152
+
153
+ ## Returns (Final Output)
154
+
155
+ ```typescript
156
+ const wf = flow<{ input: string }>()
157
+ .map((state) => ({ ...state, upper: state.input.toUpperCase(), len: state.input.length }))
158
+ .returns((state) => ({ upper: state.upper, isLong: state.len > 20 }));
159
+
160
+ // Result is typed as { upper: string; isLong: boolean }
161
+ const result = await wf.forward(llm, { input: 'test' });
162
+ ```
163
+
164
+ ## Sequential Processing
165
+
166
+ ```typescript
167
+ const wf = flow<{ input: string }, { finalResult: string }>()
168
+ .node('step1', 'input:string -> intermediate:string')
169
+ .node('step2', 'intermediate:string -> output:string')
170
+ .execute('step1', (state) => ({ input: state.input }))
171
+ .execute('step2', (state) => ({ intermediate: state.step1Result.intermediate }))
172
+ .returns((state) => ({ finalResult: state.step2Result.output }));
173
+ ```
174
+
175
+ ## Auto-Parallel Execution
176
+
177
+ Independent executes run in parallel automatically (`autoParallel: true` by default):
178
+
179
+ ```typescript
180
+ const wf = flow<{ text: string }, { combined: string }>()
181
+ .node('sentimentAnalyzer', 'text:string -> sentiment:string')
182
+ .node('topicExtractor', 'text:string -> topics:string[]')
183
+ .node('entityRecognizer', 'text:string -> entities:string[]')
184
+ // These three run in parallel (all depend only on state.text)
185
+ .execute('sentimentAnalyzer', (state) => ({ text: state.text }))
186
+ .execute('topicExtractor', (state) => ({ text: state.text }))
187
+ .execute('entityRecognizer', (state) => ({ text: state.text }))
188
+ // This waits for all three
189
+ .returns((state) => ({
190
+ combined: JSON.stringify({
191
+ sentiment: state.sentimentAnalyzerResult.sentiment,
192
+ topics: state.topicExtractorResult.topics,
193
+ entities: state.entityRecognizerResult.entities,
194
+ }),
195
+ }));
196
+
197
+ // Inspect execution plan
198
+ const plan = wf.getExecutionPlan();
199
+ console.log(plan.parallelGroups, plan.maxParallelism);
200
+ ```
201
+
202
+ Disable auto-parallel:
203
+
204
+ ```typescript
205
+ const wf = flow({ autoParallel: false });
206
+ // or per execution:
207
+ await wf.forward(llm, input, { autoParallel: false });
208
+ ```
209
+
210
+ ## Conditional Branching
211
+
212
+ ```typescript
213
+ const wf = flow<{ query: string; expertMode: boolean }, { response: string }>()
214
+ .node('simple', 'query:string -> response:string')
215
+ .node('expert', 'query:string -> response:string')
216
+ .branch((state) => state.expertMode)
217
+ .when(true)
218
+ .execute('expert', (state) => ({ query: state.query }))
219
+ .when(false)
220
+ .execute('simple', (state) => ({ query: state.query }))
221
+ .merge()
222
+ .returns((state) => ({
223
+ response: state.expertResult?.response ?? state.simpleResult?.response,
224
+ }));
225
+ ```
226
+
227
+ After `.merge()`, only the taken branch's result exists; use optional chaining (`?.`) on untaken branch results.
228
+
229
+ ## While Loops
230
+
231
+ ```typescript
232
+ const wf = flow<{ content: string }, { finalContent: string }>()
233
+ .node('processor', 'content:string -> processedContent:string')
234
+ .node('qualityChecker', 'content:string -> qualityScore:number')
235
+ .map((state) => ({ currentContent: state.content, iteration: 0, qualityScore: 0 }))
236
+ .while((state) => state.iteration < 3 && state.qualityScore < 0.8)
237
+ .map((state) => ({ ...state, iteration: state.iteration + 1 }))
238
+ .execute('processor', (state) => ({ content: state.currentContent }))
239
+ .execute('qualityChecker', (state) => ({
240
+ content: state.processorResult.processedContent,
241
+ }))
242
+ .map((state) => ({
243
+ ...state,
244
+ currentContent: state.processorResult.processedContent,
245
+ qualityScore: state.qualityCheckerResult.qualityScore,
246
+ }))
247
+ .endWhile()
248
+ .returns((state) => ({ finalContent: state.currentContent }));
249
+ ```
250
+
251
+ Rules:
252
+ - Every `.while()` needs a matching `.endWhile()`.
253
+ - Ensure the loop condition can change to avoid infinite loops.
254
+
255
+ ## Feedback Loops (label/feedback)
256
+
257
+ ```typescript
258
+ const wf = flow<{ prompt: string }, { result: string }>()
259
+ .node('gen', 'prompt:string -> result:string, quality:number')
260
+ .map((state) => ({ ...state, tries: 0 }))
261
+ .label('retry')
262
+ .map((state) => ({ ...state, tries: state.tries + 1 }))
263
+ .execute('gen', (state) => ({ prompt: state.prompt }))
264
+ .feedback((state) => state.genResult.quality < 0.9 && state.tries < 3, 'retry')
265
+ .returns((state) => ({ result: state.genResult.result }));
266
+ ```
267
+
268
+ Rules:
269
+ - Define the label before referencing it in `.feedback()`.
270
+ - Always include a max-iteration guard to avoid infinite loops.
271
+
272
+ ## Explicit Parallel Sub-Flows
273
+
274
+ ```typescript
275
+ flow
276
+ .parallel([
277
+ (sub) => sub.execute('analyzer1', (state) => ({ text: state.input })),
278
+ (sub) => sub.execute('analyzer2', (state) => ({ text: state.input })),
279
+ (sub) => sub.execute('analyzer3', (state) => ({ text: state.input })),
280
+ ])
281
+ .merge('combinedResults', (r1, r2, r3) => ({
282
+ a1: r1.analyzer1Result.analysis,
283
+ a2: r2.analyzer2Result.analysis,
284
+ a3: r3.analyzer3Result.analysis,
285
+ }));
286
+ ```
287
+
288
+ ## Derive (Batch/Array Processing)
289
+
290
+ ```typescript
291
+ const wf = flow<{ items: string[] }, { processed: string[] }>({ batchSize: 3 })
292
+ .derive('processed', 'items', (item, index) => `processed-${item}-${index}`, {
293
+ batchSize: 2,
294
+ });
295
+ ```
296
+
297
+ ## Dynamic AI Context (Multi-Model)
298
+
299
+ Route nodes to different AI providers:
300
+
301
+ ```typescript
302
+ const fast = ai({ name: 'groq', apiKey: '...' });
303
+ const smart = ai({ name: 'anthropic', apiKey: '...' });
304
+
305
+ const wf = flow<{ text: string }, { out: string }>()
306
+ .node('draft', 'text:string -> out:string')
307
+ .node('refine', 'text:string -> out:string')
308
+ .execute('draft', (state) => ({ text: state.text }), { ai: fast })
309
+ .execute('refine', (state) => ({ text: state.draftResult.out }), { ai: smart })
310
+ .returns((state) => ({ out: state.refineResult.out }));
311
+ ```
312
+
313
+ ## Description and toFunction
314
+
315
+ ```typescript
316
+ const wf = flow<{ userQuestion: string }, { responseText: string }>()
317
+ .node('qa', 'userQuestion:string -> responseText:string')
318
+ .execute('qa', (state) => ({ userQuestion: state.userQuestion }))
319
+ .returns((state) => ({ responseText: state.qaResult.responseText }))
320
+ .description('Question Answerer', 'Answers user questions concisely.');
321
+
322
+ const fn = wf.toFunction();
323
+ // fn.name, fn.parameters (JSON Schema), fn.func
324
+ ```
325
+
326
+ ## Instrumentation (Tracing)
327
+
328
+ ```typescript
329
+ import { ai, flow } from '@ax-llm/ax';
330
+ import { context, trace } from '@opentelemetry/api';
331
+
332
+ const tracer = trace.getTracer('axflow');
333
+ const llm = ai({ name: 'openai', apiKey: '...' });
334
+
335
+ const wf = flow<{ userQuestion: string }>()
336
+ .node('summarizer', 'documentText:string -> summaryText:string')
337
+ .execute('summarizer', (s) => ({ documentText: s.userQuestion }))
338
+ .returns((s) => ({ answer: s.summarizerResult.summaryText }));
339
+
340
+ const result = await wf.forward(llm, { userQuestion: 'hi' }, {
341
+ tracer,
342
+ traceContext: context.active(),
343
+ });
344
+ ```
345
+
346
+ ## Program IDs and Demos
347
+
348
+ ```typescript
349
+ const wf = flow<{ input: string }>()
350
+ .node('summarizer', 'text:string -> summary:string')
351
+ .node('classifier', 'text:string -> category:string');
352
+
353
+ // Discover program IDs
354
+ console.log(wf.namedPrograms());
355
+ // [{ id: 'root.summarizer', ... }, { id: 'root.classifier', ... }]
356
+
357
+ // Set demos (TypeScript catches typos)
358
+ wf.setDemos([{ programId: 'root.summarizer', traces: [] }]);
359
+
360
+ // Apply optimization
361
+ wf.applyOptimization(optimizedProgram);
362
+ ```
363
+
364
+ ## Error Handling
365
+
366
+ ```typescript
367
+ try {
368
+ const result = await wf.forward(llm, input);
369
+ } catch (error) {
370
+ console.error('Flow execution failed:', error);
371
+ }
372
+ ```
373
+
374
+ Common errors:
375
+ - `"Node 'x' not found"` -- define `.node()` before `.execute()`.
376
+ - `"endWhile() without matching while()"` -- every `.while()` needs `.endWhile()`.
377
+ - `"when() without matching branch()"` -- `.when()` must be inside `.branch()`/`.merge()`.
378
+ - `"merge() without matching branch()"` -- every `.branch()` needs `.merge()`.
379
+ - `"Label 'x' not found"` -- define `.label()` before `.feedback()` references it.
380
+
381
+ ## Examples
382
+
383
+ Fetch these for full working code:
384
+
385
+ - [Flow](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/ax-flow.ts) — complete flow usage
386
+ - [Auto-Parallel](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/ax-flow-auto-parallel.ts) — auto-parallelization
387
+ - [Async Map](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/ax-flow-async-map.ts) — async map transforms
388
+ - [Enhanced Demo](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/ax-flow-enhanced-demo.ts) — instance-based nodes
389
+ - [Flow as Function](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/ax-flow-to-function.ts) — flow as callable function
390
+ - [Fluent Builder](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/fluent-flow-example.ts) — fluent builder pattern
391
+ - [Flow Logging](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/flow-logging-simple.ts) — flow logging
392
+ - [Load Balancing](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/balancer.ts) — load balancing
393
+
394
+ ## Do Not Generate
395
+
396
+ - Do not use `new AxFlow(...)` for new code.
397
+ - Do not execute a node before defining it with `.node()`.
398
+ - Do not use generic field names like `text`, `result`, `data`, `input`, `output`.
399
+ - Do not create deep-nested state objects in `.map()`.
400
+ - Do not create loop conditions that can never change.
401
+ - Do not add unnecessary dependencies between executes (kills auto-parallelism).
402
+ - Do not forget to use optional chaining on branch results after `.merge()`.