# Ax Framework Documentation # Generated from project documentation # Last updated: 2025-08-29T23:21:19.692Z ================================================================================ # Documentation # Source: README.md # Ax - Build Reliable AI Apps in TypeScript # Ax: Build Reliable AI Apps in TypeScript **Stop wrestling with prompts. Start shipping AI features.** Ax brings DSPy's revolutionary approach to TypeScript – just describe what you want, and let the framework handle the rest. Production-ready, type-safe, and works with all major LLMs. [![NPM Package](https://img.shields.io/npm/v/@ax-llm/ax?style=for-the-badge&color=green)](https://www.npmjs.com/package/@ax-llm/ax) [![Twitter](https://img.shields.io/twitter/follow/dosco?style=for-the-badge&color=red)](https://twitter.com/dosco) [![Discord Chat](https://img.shields.io/discord/1078454354849304667?style=for-the-badge&color=green)](https://discord.gg/DSHg3dU7dW) ## Transform Your AI Development in 30 Seconds ```typescript import { ai, ax } from "@ax-llm/ax"; // 1. Pick any LLM const llm = ai({ name: "openai", apiKey: process.env.OPENAI_APIKEY! }); // 2. Say what you want const classifier = ax( 'review:string -> sentiment:class "positive, negative, neutral"', ); // 3. Get type-safe results const result = await classifier.forward(llm, { review: "This product is amazing!", }); console.log(result.sentiment); // "positive" ✨ ``` **That's it.** No prompt engineering. No trial and error. It works with GPT-4, Claude, Gemini, or any LLM. ## Why Thousands of Developers Choose Ax ### 🎯 **Define Once, Run Anywhere** Write your logic once. Switch between OpenAI, Anthropic, Google, or 15+ providers with one line. No rewrites needed. ### ⚑ **Ship 10x Faster** Stop tweaking prompts. Define inputs β†’ outputs. The framework generates optimal prompts automatically. ### πŸ›‘οΈ **Production-Ready from Day One** Built-in streaming, validation, error handling, observability. Used by startups in production handling millions of requests. ### πŸš€ **Gets Smarter Over Time** Train your programs with examples. Watch accuracy improve automatically. No ML expertise needed. ## Real Apps, Real Simple ### Extract Structured Data from Customer Emails ```typescript const extractor = ax(` customerEmail:string, currentDate:datetime -> priority:class "high, normal, low", sentiment:class "positive, negative, neutral", ticketNumber?:number, nextSteps:string[], estimatedResponseTime:string `); const result = await extractor.forward(ai, { customerEmail: "Order #12345 hasn't arrived. Need this resolved immediately!", currentDate: new Date(), }); // Automatically extracts all fields with proper types and validation ``` ### Build Agents That Use Tools (ReAct Pattern) ```typescript const assistant = ax( "question:string -> answer:string", { functions: [ { name: "getCurrentWeather", func: weatherAPI }, { name: "searchNews", func: newsAPI }, ], }, ); const result = await assistant.forward(ai, { question: "What's the weather in Tokyo and any news about it?", }); // AI automatically calls both functions and combines results ``` ### Multi-Modal Analysis with Images ```typescript const analyzer = ax(` image:image, question:string -> description:string, mainColors:string[], category:class "electronics, clothing, food, other", estimatedPrice:string `); // Process images and text together seamlessly ``` ## Quick Start ### Install ```bash npm install @ax-llm/ax ``` ### Your First AI Feature (2 minutes) ```typescript import { ai, ax } from "@ax-llm/ax"; const llm = ai({ name: "openai", apiKey: process.env.OPENAI_APIKEY! }); const translator = ax(` text:string, language:string -> translation:string `); const result = await translator.forward(llm, { text: "Hello world", language: "Spanish", }); console.log(result.translation); // "Hola mundo" ``` ## Powerful Features, Zero Complexity - βœ… **15+ LLM Providers** - OpenAI, Anthropic, Google, Mistral, Ollama, and more - βœ… **Type-Safe Everything** - Full TypeScript support with auto-completion - βœ… **Streaming First** - Real-time responses with validation - βœ… **Multi-Modal** - Images, audio, text in the same signature - βœ… **Smart Optimization** - Automatic prompt tuning with MiPRO - βœ… **Production Observability** - OpenTelemetry tracing built-in - βœ… **Advanced Workflows** - Compose complex pipelines with AxFlow - βœ… **Enterprise RAG** - Multi-hop retrieval with quality loops - βœ… **Agent Framework** - Agents that can use tools and call other agents - βœ… **Zero Dependencies** - Lightweight, fast, reliable ## Learn More ### πŸš€ Quick Wins - [**Getting Started Guide**](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/docs/QUICKSTART.md) - Set up in 5 minutes - [**Examples Guide**](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/docs/EXAMPLES.md) - Comprehensive examples with explanations - [**DSPy Concepts**](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/docs/DSPY.md) - Understand the revolutionary approach ### πŸ“š Deep Dives - [**AxFlow Workflows**](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/docs/AXFLOW.md) - Build complex AI systems - [**Optimization Guide**](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/docs/OPTIMIZE.md) - Make your programs smarter - [**Advanced RAG**](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/docs/AXRAG.md) - Production search & retrieval - [**API Reference**](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/docs/API.md) - Complete documentation ## Examples Run any example: ```bash OPENAI_APIKEY=your-key npm run tsx ./src/examples/[example-name].ts ``` ### Core Examples - [extract.ts](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/extract.ts) - Extract structured data from text - [react.ts](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/react.ts) - ReAct pattern with function calling - [agent.ts](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/agent.ts) - Multi-agent collaboration - [streaming1.ts](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/streaming1.ts) - Real-time streaming responses - [multi-modal.ts](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/multi-modal.ts) - Image + text processing ### Production Patterns - [customer-support.ts](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/customer-support.ts) - Complete support system - [food-search.ts](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/food-search.ts) - Restaurant recommendations with tools - [simple-optimizer-test.ts](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/simple-optimizer-test.ts) - Automatic optimization - [mipro-python-optimizer.ts](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/mipro-python-optimizer.ts) - Advanced MIPRO optimization - [ax-flow-enhanced-demo.ts](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/ax-flow-enhanced-demo.ts) - Complex workflows [πŸ“š **View Full Examples Guide** β†’](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/docs/EXAMPLES.md)\ [View All 70+ Examples β†’](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/) ## Join the Community - 🐦 [Follow on Twitter](https://twitter.com/dosco) - Latest updates - πŸ’¬ [Discord Community](https://discord.gg/DSHg3dU7dW) - Get help, share ideas - ⭐ [Star on GitHub](https://github.com/ax-llm/ax) - Support the project - πŸ“– [Ask DeepWiki](https://deepwiki.com/ax-llm/ax) - AI-powered docs ## Production Ready - βœ… **Battle-tested** - Used by startups in production - βœ… **No breaking changes** - Stable minor versions - βœ… **Comprehensive tests** - Large test coverage - βœ… **OpenTelemetry** - Built-in observability - βœ… **TypeScript first** - Type-safe by design ## License MIT - Use it anywhere, build anything. --- **Ready to build the future?** Stop fighting with prompts. Start shipping with signatures. ```bash npm install @ax-llm/ax ``` _Built with ❀️ by developers, for developers._ ================================================================================ # Quick Start # Source: QUICKSTART.md # Get from zero to your first AI application in 5 minutes # Quick Start Guide This guide will get you from zero to your first AI application in 5 minutes. ## Prerequisites - Node.js 20 or higher - An API key from OpenAI, Anthropic, or Google (we'll use OpenAI in this guide) ## Installation ```bash npm install @ax-llm/ax ``` ## Step 1: Set Up Your API Key Create a `.env` file in your project root: ```bash OPENAI_APIKEY=your-api-key-here ``` Or export it in your terminal: ```bash export OPENAI_APIKEY=your-api-key-here ``` ## Step 2: Your First AI Program Create a file called `hello-ai.ts`: ```typescript import { ai, ax } from "@ax-llm/ax"; // Initialize your AI provider const llm = ai({ name: "openai", apiKey: process.env.OPENAI_APIKEY! }); // Create a simple classifier const sentimentAnalyzer = ax( 'reviewText:string -> sentiment:class "positive, negative, neutral"' ); // Use it! async function analyze() { const result = await sentimentAnalyzer.forward(llm, { reviewText: "This product exceeded all my expectations!" }); console.log(`Sentiment: ${result.sentiment}`); } analyze(); ``` ## Step 3: Run Your Program ```bash npx tsx hello-ai.ts ``` You should see: ``` Sentiment: positive ``` ## What Just Happened? 1. **No prompt engineering** - You didn't write any prompts, just described what you wanted 2. **Type safety** - TypeScript knows that `result.sentiment` is one of your three classes 3. **Automatic optimization** - The framework generated an optimal prompt for you 4. **Provider agnostic** - This same code works with Claude, Gemini, or any other LLM ## Next: Add Streaming Want to see results as they generate? Add one parameter: ```typescript const result = await sentimentAnalyzer.forward( llm, { reviewText: "Great product!" }, { stream: true } // ← Enable streaming ); ``` ## Next: Multi-Modal (Images) Work with images just as easily: ```typescript import fs from "fs"; const imageAnalyzer = ax( 'photo:image, question:string -> answer:string' ); const imageData = fs.readFileSync("photo.jpg").toString("base64"); const result = await imageAnalyzer.forward(llm, { photo: { mimeType: "image/jpeg", data: imageData }, question: "What's in this image?" }); ``` ## Next: Complex Workflows Build multi-step processes: ```typescript const documentProcessor = ax(` documentText:string -> summary:string "2-3 sentences", keyPoints:string[] "main points", sentiment:class "positive, negative, neutral" `); const result = await documentProcessor.forward(llm, { documentText: "Your long document here..." }); console.log(`Summary: ${result.summary}`); console.log(`Key Points: ${result.keyPoints.join(", ")}`); console.log(`Sentiment: ${result.sentiment}`); ``` ## Using Different Providers ### OpenAI ```typescript const llm = ai({ name: "openai", apiKey: process.env.OPENAI_APIKEY!, config: { model: "gpt-4o" } // Optional: specify model }); ``` ### Anthropic Claude ```typescript const llm = ai({ name: "anthropic", apiKey: process.env.ANTHROPIC_APIKEY!, config: { model: "claude-3-5-sonnet-20241022" } }); ``` ### Google Gemini ```typescript const llm = ai({ name: "google-gemini", apiKey: process.env.GOOGLE_APIKEY!, config: { model: "gemini-1.5-pro" } }); ``` ### Local Ollama ```typescript const llm = ai({ name: "ollama", config: { model: "llama3.2" } }); ``` ## Field Types Reference | Type | Example | Description | |------|---------|-------------| | `string` | `name:string` | Text input/output | | `number` | `score:number` | Numeric values | | `boolean` | `isValid:boolean` | True/false | | `class` | `category:class "a,b,c"` | Enumeration | | `string[]` | `tags:string[]` | Array of strings | | `json` | `data:json` | Any JSON object | | `image` | `photo:image` | Image input | | `audio` | `recording:audio` | Audio input | | `date` | `dueDate:date` | Date value | | `?` | `notes?:string` | Optional field | ## Common Patterns ### Classification ```typescript const classifier = ax( 'text:string -> category:class "option1, option2, option3"' ); ``` ### Extraction ```typescript const extractor = ax( 'document:string -> names:string[], dates:date[], amounts:number[]' ); ``` ### Question Answering ```typescript const qa = ax( 'context:string, question:string -> answer:string' ); ``` ### Translation ```typescript const translator = ax( 'text:string, targetLanguage:string -> translation:string' ); ``` ## Error Handling ```typescript try { const result = await gen.forward(llm, input); } catch (error) { console.error("Generation failed:", error); } ``` ## Debug Mode See what's happening under the hood: ```typescript const llm = ai({ name: "openai", apiKey: process.env.OPENAI_APIKEY!, options: { debug: true } // Enable debug logging }); ``` ## What's Next? Now that you have the basics: 1. **Explore Examples** - Check out the [examples directory](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/) for real-world patterns 2. **Learn DSPy Concepts** - Understand the [revolutionary approach](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/DSPY.md) 3. **Build Workflows** - Create complex systems with [AxFlow](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/AXFLOW.md) 4. **Optimize Performance** - Make your programs smarter with [optimization](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/OPTIMIZE.md) 5. **Add Observability** - Monitor production apps with [telemetry](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/TELEMETRY.md) ## Need Help? - πŸ’¬ [Join our Discord](https://discord.gg/DSHg3dU7dW) - πŸ“– [Read the docs](https://github.com/ax-llm/ax) - 🐦 [Follow on Twitter](https://twitter.com/dosco) --- Remember: **You're not writing prompts, you're declaring capabilities.** Let the framework handle the complexity while you focus on building. ================================================================================ # DSPy Concepts # Source: DSPY.md # The revolutionary approach to building with LLMs # DSPy in TypeScript: The Future of Building with LLMs ## The Problem: LLMs Are Powerful but Unpredictable Working with LLMs today feels like herding cats. You write prompts, tweak them endlessly, and still get inconsistent results. When you switch models or providers, everything breaks. Sound familiar? **What if you could just describe what you want, and let the system figure out the best way to get it?** ## Enter DSPy: A Revolutionary Approach DSPy (Demonstrate–Search–Predict) changes everything. Instead of writing prompts, you write **signatures** – simple declarations of what goes in and what comes out. The framework handles the rest. Think of it like this: - **Traditional approach**: "Please analyze the sentiment of this review, considering positive, negative, and neutral tones..." - **DSPy approach**: `reviewText:string -> sentiment:class "positive, negative, neutral"` That's it. The system generates optimal prompts, validates outputs, and even improves itself over time. ## See It in Action (30 Seconds) ```typescript import { ai, ax } from "@ax-llm/ax"; // 1. Pick your LLM const llm = ai({ name: "openai", apiKey: process.env.OPENAI_APIKEY! }); // 2. Declare what you want const classifier = ax('reviewText:string -> sentiment:class "positive, negative, neutral"'); // 3. Just use it const result = await classifier.forward(llm, { reviewText: "This product exceeded my expectations!" }); console.log(result.sentiment); // "positive" ``` **That's a complete, production-ready sentiment analyzer.** No prompt engineering. No trial and error. ## Why DSPy Will Change How You Build ### 1. 🎯 **Write Once, Run Anywhere** Your code works with OpenAI, Google, Anthropic, or any LLM. Switch providers with one line. No rewrites. ### 2. ⚑ **Stream Everything** Get results as they generate. Validate on-the-fly. Fail fast. Ship faster. ```typescript const gen = ax("question:string -> answer:string"); // Stream responses in real-time await gen.forward(llm, { question: "Hello" }, { stream: true }); ``` ### 3. πŸ›‘οΈ **Built-in Quality Control** Add assertions that run during generation. Catch issues before they reach users. ```typescript gen.addAssert( ({ answer }) => answer.length > 10, "Answer must be detailed" ); ``` ### 4. πŸš€ **Automatic Optimization** Train your programs with examples. Watch them improve automatically. ```typescript const optimizer = new AxMiPRO({ studentAI: llm, examples: trainingData }); const improved = await optimizer.compile(classifier, examples, metric); // Your classifier just got 30% more accurate! ``` ### 5. 🎨 **Multi-Modal Native** Images, audio, text – all in the same signature. It just works. ```typescript const vision = ax("photo:image, question:string -> description:string"); ``` ## Real-World Power: Build Complex Systems Simply ### Smart Customer Support in 5 Lines ```typescript const supportBot = ax(` customerMessage:string -> category:class "billing, technical, general", priority:class "high, medium, low", suggestedResponse:string `); // That's it. You have intelligent ticket routing and response generation. ``` ### Multi-Step Reasoning? Trivial. ```typescript const researcher = ax(` question:string -> searchQueries:string[] "3-5 queries", analysis:string, confidence:number "0-1" `); ``` ## Beyond Simple Generation: Production Features ### Complete Observability - OpenTelemetry tracing built-in - Track every decision, optimization, and retry - Monitor costs, latency, and quality in real-time ### Enterprise-Ready Workflows AxFlow lets you compose signatures into complex pipelines with automatic parallelization: ```typescript new AxFlow() .node("analyzer", "text:string -> sentiment:string") .node("summarizer", "text:string -> summary:string") .execute("analyzer", (state) => ({ text: state.text })) .execute("summarizer", (state) => ({ text: state.text })) // Both run in parallel automatically! ``` ### Advanced RAG Out of the Box ```typescript const rag = axRAG(vectorDB, { maxHops: 3, // Multi-hop retrieval qualityTarget: 0.85, // Self-healing quality loops }); // Enterprise RAG in 3 lines ``` ## Start Now: From Zero to Production ### Install (30 seconds) ```bash npm install @ax-llm/ax ``` ### Your First Intelligent App (2 minutes) ```typescript import { ai, ax } from "@ax-llm/ax"; const llm = ai({ name: "openai", apiKey: process.env.OPENAI_APIKEY! }); // Create any AI capability with a signature const translator = ax(` text:string, targetLanguage:string -> translation:string, confidence:number "0-1" `); const result = await translator.forward(llm, { text: "Hello world", targetLanguage: "French" }); // { translation: "Bonjour le monde", confidence: 0.95 } ``` ## The Bottom Line **Stop fighting with prompts. Start building with signatures.** DSPy isn't just another LLM library. It's a fundamental shift in how we build AI systems: - **Deterministic** where it matters (structure, types, validation) - **Flexible** where you need it (providers, models, optimization) - **Production-ready** from day one (streaming, observability, scaling) ## Ready to Build the Future? ### Quick Wins - [Simple Examples](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/) - Start here - [Streaming Magic](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/streaming1.ts) - Real-time validation - [Multi-Modal](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/multi-modal.ts) - Images + text together ### Level Up - [Optimization Guide](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/OPTIMIZE.md) - Make your programs smarter - [AxFlow Workflows](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/AXFLOW.md) - Build complex systems - [Advanced RAG](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/AXRAG.md) - Production search & retrieval ### Join the Revolution - 🐦 [Follow Updates](https://twitter.com/dosco) - πŸ’¬ [Discord Community](https://discord.gg/DSHg3dU7dW) - ⭐ [Star on GitHub](https://github.com/ax-llm/ax) --- **Remember**: Every prompt you write today is technical debt. Every signature you write is an asset that gets better over time. Welcome to the future of building with LLMs. Welcome to DSPy with Ax. ================================================================================ # Signatures Guide # Source: SIGNATURES.md # Complete guide to DSPy signatures - from basics to advanced patterns # The Complete Guide to DSPy Signatures in Ax ## Introduction: Why Signatures Beat Prompts Traditional prompt engineering is like writing assembly code – tedious, fragile, and requires constant tweaking. DSPy signatures are like high-level programming – you describe **what** you want, not **how** to get it. ### The Problem with Prompts ```typescript // ❌ Traditional approach - fragile and verbose const prompt = `You are a sentiment analyzer. Given a customer review, analyze the sentiment and return exactly one of: positive, negative, or neutral. Be sure to only return the sentiment word, nothing else. Review: ${review} Sentiment:`; // Hope the LLM follows instructions... ``` ### The Power of Signatures ```typescript // βœ… Signature approach - clear and type-safe const analyzer = ax('review:string -> sentiment:class "positive, negative, neutral"'); // Guaranteed structured output with TypeScript types! const result = await analyzer.forward(llm, { review }); console.log(result.sentiment); // TypeScript knows this is "positive" | "negative" | "neutral" ``` ## Understanding Signature Syntax A signature defines the contract between your code and the LLM: ``` [description] input1:type, input2:type -> output1:type, output2:type ``` ### Basic Structure 1. **Optional Description**: Overall purpose in quotes 2. **Input Fields**: What you provide to the LLM 3. **Arrow (`->`)**: Separates inputs from outputs 4. **Output Fields**: What the LLM returns ### Examples ```typescript // Simple Q&A 'userQuestion:string -> aiAnswer:string' // With description '"Answer questions about TypeScript" question:string -> answer:string, confidence:number' // Multiple inputs and outputs 'document:string, query:string -> summary:string, relevantQuotes:string[]' ``` ## Field Types Reference Ax supports a rich type system that maps directly to TypeScript types: ### Basic Types | Type | Signature Syntax | TypeScript Type | Example | |------|-----------------|-----------------|---------| | String | `:string` | `string` | `userName:string` | | Number | `:number` | `number` | `score:number` | | Boolean | `:boolean` | `boolean` | `isValid:boolean` | | JSON | `:json` | `any` | `metadata:json` | ### Date and Time Types | Type | Signature Syntax | TypeScript Type | Example | |------|-----------------|-----------------|---------| | Date | `:date` | `Date` | `birthDate:date` | | DateTime | `:datetime` | `Date` | `timestamp:datetime` | ### Media Types (Input Only) | Type | Signature Syntax | TypeScript Type | Example | |------|-----------------|-----------------|---------| | Image | `:image` | `{mimeType: string, data: string}` | `photo:image` | | Audio | `:audio` | `{format?: 'wav', data: string}` | `recording:audio` | | File | `:file` | `{mimeType: string, data: string}` | `document:file` | | URL | `:url` | `string` | `website:url` | ### Special Types | Type | Signature Syntax | TypeScript Type | Example | |------|-----------------|-----------------|---------| | Code | `:code` | `string` | `pythonScript:code` | | Classification | `:class "opt1, opt2"` | `"opt1" \| "opt2"` | `mood:class "happy, sad, neutral"` | ## Arrays and Optional Fields ### Arrays Add `[]` after any type to make it an array: ```typescript // String array 'tags:string[] -> processedTags:string[]' // Number array 'scores:number[] -> average:number, median:number' // Classification array 'documents:string[] -> categories:class[] "news, blog, tutorial"' ``` ### Optional Fields Add `?` before the colon to make a field optional: ```typescript // Optional input 'query:string, context?:string -> response:string' // Optional output 'text:string -> summary:string, keywords?:string[]' // Both optional 'message?:string -> reply?:string, confidence:number' ``` ## Advanced Features ### Internal Fields (Output Only) Use `!` to mark output fields as internal (for reasoning/chain-of-thought): ```typescript // Internal fields are hidden from the final output but guide LLM reasoning 'problem:string -> reasoning!:string, solution:string' ``` ### Classification Fields (Output Only) Classifications provide type-safe enums: ```typescript // Single classification 'email:string -> priority:class "urgent, normal, low"' // Multiple options with pipe separator 'text:string -> sentiment:class "positive | negative | neutral"' // Array of classifications 'reviews:string[] -> sentiments:class[] "positive, negative, neutral"' ``` ## Creating Signatures: Three Approaches ### 1. String-Based (Recommended) ```typescript import { ax, s } from '@ax-llm/ax'; // Direct generator creation const generator = ax('input:string -> output:string'); // Create signature first, then generator const sig = s('query:string -> response:string'); const gen = ax(sig.toString()); ``` ### 2. Programmatic Builder API ```typescript import { f } from '@ax-llm/ax'; // Using the fluent builder const signature = f() .input('userMessage', f.string('User input')) .input('context', f.optional(f.string('Additional context'))) .output('response', f.string('AI response')) .output('confidence', f.number('Confidence score 0-1')) .build(); ``` ### 3. Hybrid Approach ```typescript import { s, f } from '@ax-llm/ax'; // Start with string, add fields programmatically const sig = s('base:string -> result:string') .appendInputField('extra', f.optional(f.json('Metadata'))) .appendOutputField('score', f.number('Quality score')); ``` ## Field Naming Best Practices Ax enforces descriptive field names to improve LLM understanding: ### βœ… Good Field Names - `userQuestion`, `customerEmail`, `documentText` - `analysisResult`, `summaryContent`, `responseMessage` - `confidenceScore`, `categoryType`, `priorityLevel` ### ❌ Bad Field Names (Will Error) - `text`, `data`, `input`, `output` (too generic) - `a`, `x`, `val` (too short) - `1field`, `123name` (starts with number) ## Real-World Examples ### Email Classifier ```typescript const emailClassifier = ax(` emailSubject:string "Email subject line", emailBody:string "Full email content" -> category:class "sales, support, spam, newsletter" "Email category", priority:class "urgent, normal, low" "Priority level", summary:string "Brief summary of the email" `); const result = await emailClassifier.forward(llm, { emailSubject: "Urgent: Server Down", emailBody: "Our production server is experiencing issues..." }); console.log(result.category); // "support" console.log(result.priority); // "urgent" ``` ### Document Analyzer with Chain-of-Thought ```typescript const analyzer = ax(` documentText:string "Document to analyze" -> reasoning!:string "Step-by-step analysis", mainTopics:string[] "Key topics discussed", sentiment:class "positive, negative, neutral, mixed" "Overall tone", readability:class "elementary, high-school, college, graduate" "Reading level", keyInsights:string[] "Important takeaways" `); // The reasoning field guides the LLM but isn't returned const result = await analyzer.forward(llm, { documentText: "..." }); // result.reasoning is undefined (internal field) // result.mainTopics, sentiment, etc. are available ``` ### Multi-Modal Analysis ```typescript const imageAnalyzer = ax(` imageData:image "Image to analyze", question?:string "Specific question about the image" -> description:string "What's in the image", objects:string[] "Identified objects", textFound?:string "Any text detected in the image", answerToQuestion?:string "Answer if question was provided" `); ``` ### Data Extraction ```typescript const extractor = ax(` invoiceText:string "Raw invoice text" -> invoiceNumber:string "Invoice ID", invoiceDate:date "Date of invoice", dueDate:date "Payment due date", totalAmount:number "Total amount due", lineItems:json[] "Array of {description, quantity, price}", vendor:json "{ name, address, taxId }" `); ``` ## Streaming Support All signatures support streaming by default: ```typescript const storyteller = ax(` prompt:string "Story prompt", genre:class "fantasy, sci-fi, mystery, romance" -> title:string "Story title", story:string "The complete story", wordCount:number "Approximate word count" `); // Stream the response for await (const chunk of storyteller.stream(llm, { prompt: "A detective discovers their partner is a time traveler", genre: "mystery" })) { if (chunk.story) { process.stdout.write(chunk.story); // Real-time streaming } } ``` ## Type Safety and IntelliSense Signatures provide full TypeScript type inference: ```typescript const typed = ax(` userId:number, includeDetails?:boolean -> userName:string, userEmail:string, metadata?:json `); // TypeScript knows the exact types const result = await typed.forward(llm, { userId: 123, // βœ… number required includeDetails: true // βœ… boolean optional // userEmail: "..." // ❌ TypeScript error: not an input field }); console.log(result.userName); // βœ… TypeScript knows this is string console.log(result.metadata?.x); // βœ… TypeScript knows this is any | undefined // console.log(result.userId); // ❌ TypeScript error: not an output field ``` ## Common Patterns ### 1. Chain of Thought Reasoning ```typescript // Use internal fields for reasoning steps const reasoner = ax(` problem:string -> thoughts!:string "Internal reasoning process", answer:string "Final answer" `); ``` ### 2. Structured Data Extraction ```typescript // Extract structured data from unstructured text const parser = ax(` messyData:string -> structured:json "Clean JSON representation" `); ``` ### 3. Multi-Step Classification ```typescript // Hierarchical classification const classifier = ax(` text:string -> mainCategory:class "technical, business, creative", subCategory:class "based on main category", confidence:number "0-1 confidence score" `); ``` ### 4. Validation and Checking ```typescript // Validate and explain const validator = ax(` code:code "Code to review", language:string "Programming language" -> isValid:boolean "Is the code syntactically correct", errors?:string[] "List of errors if any", suggestions?:string[] "Improvement suggestions" `); ``` ## Error Handling Signatures provide clear, actionable error messages: ```typescript // ❌ This will throw a descriptive error try { const bad = ax('text:string -> result:string'); } catch (error) { // Error: Field name "text" is too generic. // Use a more descriptive name like "inputText" or "documentText" } // ❌ Invalid type try { const bad = ax('userInput:str -> result:string'); } catch (error) { // Error: Unknown type "str". Did you mean "string"? } // ❌ Duplicate field names try { const bad = ax('data:string, data:number -> result:string'); } catch (error) { // Error: Duplicate field name "data" in inputs } ``` ## Migration from Traditional Prompts ### Before (Prompt Engineering) ```typescript const prompt = ` Analyze the sentiment of the following review. Rate it on a scale of 1-5. Identify the main topics discussed. Format your response as JSON with keys: rating, sentiment, topics Review: ${review} `; const response = await llm.generate(prompt); const parsed = JSON.parse(response); // Hope it's valid JSON... ``` ### After (Signatures) ```typescript const analyzer = ax(` review:string -> rating:number "1-5 rating", sentiment:class "very positive, positive, neutral, negative, very negative", topics:string[] "Main topics discussed" `); const result = await analyzer.forward(llm, { review }); // Guaranteed structure, no parsing needed! ``` ## Performance Tips 1. **Use specific types**: `class` is more token-efficient than `string` for enums 2. **Leverage arrays**: Process multiple items in one call 3. **Optional fields**: Only request what you need 4. **Internal fields**: Use `!` for reasoning without returning it ## Conclusion DSPy signatures in Ax transform LLM interactions from fragile prompt engineering to robust, type-safe programming. By describing what you want instead of how to get it, you can: - Write more maintainable code - Get guaranteed structured outputs - Leverage TypeScript's type system - Switch LLM providers without changing logic - Build production-ready AI features faster Start using signatures today and experience the difference! ================================================================================ # AI Providers # Source: AI.md # Complete guide to all supported AI providers and their features # API Reference Complete API documentation for Ax - the DSPy framework for TypeScript. ## Core Functions ### `ai(options)` - Create AI Instance Factory function to create an AI provider instance. ```typescript const llm = ai({ name: "openai" | "anthropic" | "google-gemini" | "mistral" | "groq" | "cohere" | "together" | "deepseek" | "ollama" | "huggingface" | "openrouter" | "azure-openai" | "reka" | "x-grok", apiKey?: string, config?: { model?: string, baseURL?: string, headers?: Record, thinking?: { includeThoughts: boolean } }, options?: { debug?: boolean, logger?: AxLoggerFunction, tracer?: Tracer, rateLimiter?: RateLimiter, fetch?: typeof fetch, corsProxy?: string // For browser usage } }); ``` ### `ax(signature, options?)` - Create Generator Factory function to create a DSPy generator from a signature. ```typescript const generator = ax( signature: string, // "input:type -> output:type" options?: { description?: string, examples?: Array, functions?: Array, asserts?: Array, streamingAsserts?: Map, maxCompletionTokens?: number, maxRetries?: number, maxSteps?: number, promptTemplate?: string, logger?: AxLoggerFunction, tracer?: Tracer, meter?: Meter } ); ``` ### `agent(options)` - Create Agent Factory function to create an AI agent. ```typescript const myAgent = agent({ name: string, description: string, signature: string | AxSignature | AxGen, definition?: string, functions?: Array, agents?: Array, ai?: AxAI, options?: { debug?: boolean, logger?: AxLoggerFunction, tracer?: Tracer } }); ``` ### `s(signature)` - Create Signature Helper function to create type-safe signatures. ```typescript const signature = s('input:string -> output:string'); ``` ### `f` - Field Helpers Field creation utilities for building complex signatures. ```typescript // Basic types f.string(description?: string) f.number(description?: string) f.boolean(description?: string) f.json(description?: string) f.date(description?: string) f.datetime(description?: string) f.code(description?: string) f.url(description?: string) f.file(description?: string) // Media types f.image(description?: string) f.audio(description?: string) // Modifiers f.array(field: AxField) // Creates array type f.optional(field: AxField) // Makes field optional f.class(options: string[], description?: string) // Enumeration // Builder f() // Returns fluent builder for complex signatures .input(name: string, field: AxField) .output(name: string, field: AxField) .build() ``` ## Generator Methods ### `forward(ai, input, options?)` Execute the generator and get results. ```typescript const result = await generator.forward( ai: AxAI, input: InputType, options?: { model?: string, maxTokens?: number, temperature?: number, stream?: boolean, thinkingTokenBudget?: "minimal" | "low" | "medium" | "high", examples?: Array, maxRetries?: number, maxSteps?: number, logger?: AxLoggerFunction, tracer?: Tracer, span?: Span } ): Promise ``` ### `streamingForward(ai, input, options?)` Stream results as they generate. ```typescript const stream = await generator.streamingForward( ai: AxAI, input: InputType, options?: ForwardOptions ): AsyncGenerator> for await (const chunk of stream) { console.log(chunk); } ``` ### `addAssert(fn, message)` Add validation that runs after generation. ```typescript generator.addAssert( (output: OutputType) => boolean, errorMessage: string ); ``` ### `addStreamingAssert(fieldName, fn, message)` Add validation that runs during streaming. ```typescript generator.addStreamingAssert( fieldName: string, (value: any) => boolean, errorMessage: string ); ``` ### `addFieldProcessor(fieldName, processor)` Add field transformation/validation. ```typescript generator.addFieldProcessor( fieldName: string, processor: (value: any, state: any) => any ); ``` ### `applyOptimization(program)` Apply optimized configuration from training. ```typescript generator.applyOptimization(optimizedProgram: AxOptimizedProgram); ``` ## Agent Methods ### `forward(ai, input, options?)` Execute agent with input. ```typescript const result = await agent.forward( ai: AxAI, input: InputType, options?: ForwardOptions ): Promise ``` ### `setFunctions(functions)` Set available functions for the agent. ```typescript agent.setFunctions(functions: Array); ``` ### `setAgents(agents)` Set sub-agents the agent can use. ```typescript agent.setAgents(agents: Array); ``` ## AI Service Methods ### `chat(options)` Direct chat with the LLM. ```typescript const response = await llm.chat({ chatPrompt: Array<{ role: "system" | "user" | "assistant", content: string }>, model?: string, maxTokens?: number, temperature?: number, topP?: number, stream?: boolean, streamingHandler?: (chunk: string) => void, functions?: Array, functionCall?: "none" | "auto" | { name: string } }); ``` ### `embed(options)` Generate embeddings for text. ```typescript const { embeddings } = await llm.embed({ texts: string[], model?: string }); ``` ### `getModelList()` Get available models. ```typescript const models = llm.getModelList(); // Returns: Array<{ key: string, model: string, description?: string }> ``` ## AxFlow - Workflow Orchestration ### Creating Workflows ```typescript const workflow = new AxFlow() .node(name: string, signature: string | AxGen) .execute(nodeName: string, mapper: (state) => NodeInput) .decision(condition: (state) => boolean) .yes((flow) => flow.execute(...)) .no((flow) => flow.execute(...)) .loop(condition: (state) => boolean, (flow) => flow.execute(...)) .returns(mapper: (state) => OutputState); ``` ### Workflow Execution ```typescript const result = await workflow.forward( ai: AxAI, input: InputState, options?: WorkflowOptions ); ``` ## Vector Database ### AxDB - Vector Database Interface ```typescript const db = new AxDB({ name: "memory" | "weaviate" | "pinecone" | "cloudflare", apiKey?: string, host?: string, namespace?: string }); // Insert vectors await db.upsert({ id: string, table: string, values: number[], metadata?: Record }); // Query similar vectors const matches = await db.query({ table: string, values: number[], topK?: number, where?: Record }); ``` ### AxDBManager - Smart Document Management ```typescript const manager = new AxDBManager({ ai: AxAI, db: AxDB, chunkSize?: number, chunkOverlap?: number, reranker?: AxReranker, queryRewriter?: AxQueryRewriter }); // Insert and chunk text await manager.insert(text: string, metadata?: Record); // Query with reranking const results = await manager.query( query: string, options?: { topK?: number } ); ``` ## RAG - Retrieval Augmented Generation ### axRAG - Advanced RAG Pipeline ```typescript const rag = axRAG( queryFunction: (query: string) => Promise>, options?: { maxHops?: number, maxIterations?: number, qualityThreshold?: number, qualityTarget?: number, debug?: boolean } ); const result = await rag.forward(ai: AxAI, { originalQuestion: string }); ``` ## Optimization ### AxMiPRO - MiPRO v2 Optimizer ```typescript const optimizer = new AxMiPRO({ studentAI: AxAI, teacherAI?: AxAI, examples: Array, options?: { maxBootstrapAttempts?: number, maxLabeledCandidates?: number, maxErrors?: number, maxRoundsPerDepth?: number, minDatapointsPerDepth?: number[], requiredDatapointsPerDepth?: number[], endWhenOptimal?: boolean, checkpointCallback?: (state: CheckpointState) => void } }); const result = await optimizer.compile( program: AxGen, examples: Array, metric: (prediction: any, example: any) => number ); ``` ### AxBootstrapFewShot - Bootstrap Optimizer ```typescript const optimizer = new AxBootstrapFewShot({ ai: AxAI, examples: Array, options?: { maxBootstrappedDemos?: number, maxLabeledDemos?: number, maxRounds?: number, maxErrors?: number } }); const result = await optimizer.compile( program: AxGen, examples: Array, metric: MetricFunction ); ``` ## MCP - Model Context Protocol ### AxMCPClient ```typescript const client = new AxMCPClient( transport: AxMCPTransport, options?: { debug?: boolean } ); await client.init(); // Use with agents or generators const agent = agent({ functions: [client] }); ``` ### Transports ```typescript // Stdio transport (local servers) const transport = new AxMCPStdioTransport({ command: string, args?: string[], env?: Record }); // HTTP transport (remote servers) const transport = new AxMCPStreambleHTTPTransport( url: string, headers?: Record ); ``` ## Utilities ### Multi-Service Router ```typescript const router = AxMultiServiceRouter.create([service1, service2]); // Routes to appropriate service based on model await router.chat({ model: "gpt-4", ... }); ``` ### Load Balancer ```typescript const balancer = AxBalancer.create([service1, service2]); // Automatically balances load and handles failures await balancer.chat({ ... }); ``` ### Document Processing ```typescript // Apache Tika integration const tika = new AxApacheTika(url?: string); const text = await tika.convert(filePath: string); ``` ### Telemetry ```typescript import { axGlobals } from "@ax-llm/ax"; // Set global tracer axGlobals.tracer = trace.getTracer("my-app"); // Set global meter axGlobals.meter = metrics.getMeter("my-app"); ``` ## Type Definitions ### Field Types - `string` - Text field - `number` - Numeric field - `boolean` - Boolean field - `json` - JSON object field - `date` - Date field (YYYY-MM-DD) - `datetime` - DateTime field (ISO 8601) - `image` - Image input ({ mimeType, data }) - `audio` - Audio input ({ format, data }) - `file` - File input - `url` - URL field - `code` - Code field - `class` - Enumeration (one of specified values) ### Field Modifiers - `[]` - Array (e.g., `string[]`) - `?` - Optional (e.g., `field?:string`) - `!` - Internal/reasoning field (not in output) ### Signature Format ``` "fieldName:type \"description\", ... -> outputField:type \"description\", ..." ``` Examples: ```typescript // Simple "question:string -> answer:string" // With descriptions "text:string \"Input text\" -> summary:string \"Brief summary\"" // Multiple fields "name:string, age:number -> greeting:string" // Complex types "items:string[] -> selected:class \"a,b,c\", count:number" // Optional and internal "required:string, optional?:string, reasoning!:string -> result:string" ``` ## Error Handling All methods can throw: - `AxError` - Base error class - `AxValidationError` - Signature/field validation errors - `AxGenerationError` - Generation failures - `AxTimeoutError` - Operation timeouts - `AxRateLimitError` - Rate limit exceeded ```typescript try { const result = await gen.forward(llm, input); } catch (error) { if (error instanceof AxValidationError) { // Handle validation error } else if (error instanceof AxGenerationError) { // Handle generation error } } ``` ## Browser Usage For browser environments, use a CORS proxy: ```typescript const llm = ai({ name: "openai", apiKey: "your-key", options: { corsProxy: "http://localhost:3001" } }); ``` ## Environment Variables Common environment variables: - `OPENAI_APIKEY` - OpenAI API key - `ANTHROPIC_APIKEY` - Anthropic API key - `GOOGLE_APIKEY` - Google API key - `MISTRAL_APIKEY` - Mistral API key - `GROQ_APIKEY` - Groq API key - `TOGETHER_APIKEY` - Together API key - `DEEPSEEK_APIKEY` - DeepSeek API key - `HUGGINGFACE_APIKEY` - Hugging Face API key --- For more examples and patterns, see the [examples directory](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/). ================================================================================ # Optimization Guide # Source: OPTIMIZE.md # LLM Optimization Made Simple: A Beginner's Guide to Ax # LLM Optimization Made Simple: A Beginner's Guide to Ax **Goal**: Learn how to make your AI programs smarter, faster, and cheaper through automatic optimization. **Time to first results**: 5 minutes ## πŸ“‹ Table of Contents - [What is LLM Optimization?](#what-is-llm-optimization) - [πŸš€ 5-Minute Quick Start](#-5-minute-quick-start) ← **Start here!** - [Step 6: Save Your Optimization Results πŸ’Ύ](#step-6-save-your-optimization-results-) - [Step 7: Load and Use in Production πŸš€](#step-7-load-and-use-in-production-) - [πŸ“š Understanding the Basics](#-understanding-the-basics) - [🎯 Common Use Cases](#-common-use-cases-copy--paste-ready) - [πŸ’° Saving Money: Teacher-Student Setup](#-saving-money-teacher-student-setup) - [πŸ”§ Making It Better: Practical Tips](#-making-it-better-practical-tips) - [πŸ› οΈ Troubleshooting Guide](#️-troubleshooting-guide) - [πŸŽ“ Next Steps: Level Up Your Skills](#-next-steps-level-up-your-skills) - [πŸ“– Complete Working Example](#-complete-working-example) - [🎯 Key Takeaways](#-key-takeaways) --- ## What is LLM Optimization? Think of optimization like having a writing tutor for your AI. Instead of manually tweaking prompts and examples, Ax automatically: - **Writes better prompts** for your AI programs - **Picks the best examples** to show your AI what you want - **Saves you money** by making cheaper models work as well as expensive ones - **Improves accuracy** without you having to be a prompt engineering expert **Real example**: A sentiment analysis that goes from 70% accuracy to 90% accuracy automatically, while reducing costs by 80%. --- ### Step 1: Install and Setup ```bash npm install @ax-llm/ax ``` ```typescript // Create a .env file with your OpenAI API key // OPENAI_APIKEY=your_key_here import { ai, ax, AxMiPRO } from "@ax-llm/ax"; ``` **Important**: Ax optimizers depend on a Python optimization service (Optuna). For MiPRO v2 and production-scale optimization, you must start the Python service before running any optimization. See "Python Optimization Service Integration" below. Quick start: ```bash cd src/optimizer uv sync uv run ax-optimizer server start --debug ``` ### Step 2: Create Your First Optimizable Program ```typescript // This is a simple sentiment analyzer - we'll make it smarter! const sentimentAnalyzer = ax( 'reviewText:string "Customer review" -> sentiment:class "positive, negative, neutral" "How the customer feels"', ); // Set up your AI const llm = ai({ name: "openai", apiKey: process.env.OPENAI_APIKEY!, config: { model: "gpt-4o-mini" }, // Start with the cheaper model }); ``` ### Step 3: Provide Training Examples ```typescript // Just 3-5 examples are enough to start! const examples = [ { reviewText: "I love this product!", sentiment: "positive" }, { reviewText: "This is terrible quality", sentiment: "negative" }, { reviewText: "It works fine, nothing special", sentiment: "neutral" }, { reviewText: "Best purchase ever!", sentiment: "positive" }, { reviewText: "Waste of money", sentiment: "negative" }, ]; ``` ### Step 4: Define Success (Your Metric) ```typescript // This tells the optimizer what "good" looks like const metric = ({ prediction, example }) => { // Simple: 1 point for correct answer, 0 for wrong return prediction.sentiment === example.sentiment ? 1 : 0; }; ``` ### Step 5: Run the Magic ✨ ```typescript // Create the optimizer const optimizer = new AxMiPRO({ studentAI: llm, examples, options: { verbose: true }, // Show progress }); // Let it optimize (takes 1-2 minutes) console.log("πŸ”„ Optimizing your AI program..."); const result = await optimizer.compile(sentimentAnalyzer, examples, metric); // Apply the improvements if (result.demos) { sentimentAnalyzer.setDemos(result.demos); } console.log( `βœ… Done! Improved from baseline to ${result.bestScore * 100}% accuracy`, ); ``` ### Step 6: Save Your Optimization Results πŸ’Ύ **This is crucial for production!** The new unified `AxOptimizedProgram` contains everything needed to reproduce your optimization: ```typescript import { promises as fs } from "fs"; // Apply the optimized configuration using the unified approach if (result.optimizedProgram) { // Apply all optimizations in one clean call sentimentAnalyzer.applyOptimization(result.optimizedProgram); console.log(`✨ Applied optimized configuration:`); console.log(` Score: ${result.optimizedProgram.bestScore.toFixed(3)}`); console.log(` Optimizer: ${result.optimizedProgram.optimizerType}`); console.log( ` Converged: ${result.optimizedProgram.converged ? "βœ…" : "❌"}`, ); // Save the complete optimization result await fs.writeFile( "sentiment-analyzer-optimization.json", JSON.stringify( { version: "2.0", bestScore: result.optimizedProgram.bestScore, instruction: result.optimizedProgram.instruction, demos: result.optimizedProgram.demos, modelConfig: result.optimizedProgram.modelConfig, optimizerType: result.optimizedProgram.optimizerType, optimizationTime: result.optimizedProgram.optimizationTime, totalRounds: result.optimizedProgram.totalRounds, converged: result.optimizedProgram.converged, stats: result.optimizedProgram.stats, timestamp: new Date().toISOString(), }, null, 2, ), ); console.log( "βœ… Complete optimization saved to sentiment-analyzer-optimization.json", ); } // What you just saved: console.log("Saved data contains:"); console.log("- Optimized few-shot examples (demos)"); console.log("- Optimized instruction prompts"); console.log("- Model configuration (temperature, etc.)"); console.log("- Complete performance metrics"); console.log("- Optimization metadata and timing"); console.log(`- Performance score: ${result.bestScore}`); ``` ### Step 7: Load and Use in Production πŸš€ In your production code, recreate and apply the saved optimization: ```typescript import { AxOptimizedProgramImpl } from "@ax-llm/ax"; // Production app - load pre-optimized configuration const sentimentAnalyzer = ax( 'reviewText:string "Customer review" -> sentiment:class "positive, negative, neutral" "How the customer feels"', ); // Load the saved optimization results const savedData = JSON.parse( await fs.readFile("sentiment-analyzer-optimization.json", "utf8"), ); // Recreate the optimized program const optimizedProgram = new AxOptimizedProgramImpl({ bestScore: savedData.bestScore, stats: savedData.stats, instruction: savedData.instruction, demos: savedData.demos, modelConfig: savedData.modelConfig, optimizerType: savedData.optimizerType, optimizationTime: savedData.optimizationTime, totalRounds: savedData.totalRounds, converged: savedData.converged, }); // Apply the complete optimization (demos, instruction, model config, etc.) sentimentAnalyzer.applyOptimization(optimizedProgram); console.log(`πŸš€ Loaded optimization v${savedData.version}`); console.log(` Score: ${optimizedProgram.bestScore.toFixed(3)}`); console.log(` Optimizer: ${optimizedProgram.optimizerType}`); // Now your AI performs at the optimized level const analysis = await sentimentAnalyzer.forward(llm, { reviewText: "The product arrived quickly but the quality was disappointing", }); console.log("Analysis:", analysis.sentiment); // Much more accurate! ``` ### Step 8: Understanding What You Get πŸ“Š The new unified optimization result provides comprehensive information in one object: ```typescript const result = await optimizer.compile(sentimentAnalyzer, examples, metric); // New unified approach - everything in one place: if (result.optimizedProgram) { console.log({ // Performance metrics bestScore: result.optimizedProgram.bestScore, // Best performance (0-1) converged: result.optimizedProgram.converged, // Did optimization converge? totalRounds: result.optimizedProgram.totalRounds, // Number of optimization rounds optimizationTime: result.optimizedProgram.optimizationTime, // Time taken (ms) // Program configuration instruction: result.optimizedProgram.instruction, // Optimized prompt demos: result.optimizedProgram.demos?.length, // Number of few-shot examples modelConfig: result.optimizedProgram.modelConfig, // Model settings (temperature, etc.) // Optimization metadata optimizerType: result.optimizedProgram.optimizerType, // Which optimizer was used stats: result.optimizedProgram.stats, // Detailed statistics }); } // The unified result contains everything: // - Optimized few-shot examples (demos) // - Optimized instruction text // - Model configuration (temperature, maxTokens, etc.) // - Complete performance statistics // - Optimization metadata (type, time, convergence) // - Everything needed to reproduce the performance ``` ### Step 9: Production Best Practices πŸ“ **File Organization:** ``` your-app/ β”œβ”€β”€ optimizations/ β”‚ β”œβ”€β”€ sentiment-analyzer-v2.0.json ← Complete optimization (new format) β”‚ β”œβ”€β”€ email-classifier-v2.0.json ← Different task β”‚ └── product-reviewer-v2.0.json ← Another task β”œβ”€β”€ legacy-optimizations/ β”‚ β”œβ”€β”€ sentiment-analyzer-demos.json ← Legacy demos (v1.0 format) β”‚ └── email-classifier-demos.json ← Old format β”œβ”€β”€ src/ β”‚ β”œβ”€β”€ train-models.ts ← Training script β”‚ └── production-app.ts ← Production app ``` **Environment-specific Loading:** ```typescript import { AxOptimizedProgramImpl } from "@ax-llm/ax"; // Load different optimizations for different environments const optimizationFile = process.env.NODE_ENV === "production" ? "optimizations/sentiment-analyzer-prod-v2.0.json" : "optimizations/sentiment-analyzer-dev-v2.0.json"; const savedData = JSON.parse(await fs.readFile(optimizationFile, "utf8")); // Handle both new unified format and legacy format if (savedData.version === "2.0") { // New unified format const optimizedProgram = new AxOptimizedProgramImpl(savedData); sentimentAnalyzer.applyOptimization(optimizedProgram); console.log(`πŸš€ Loaded unified optimization v${savedData.version}`); } else { // Legacy format (backward compatibility) sentimentAnalyzer.setDemos(savedData.demos || savedData); console.log("⚠️ Loaded legacy demo format - consider upgrading"); } ``` **Version Your Optimizations:** ```typescript // The new format includes comprehensive versioning by default const optimizationData = { version: "2.0", // Format version modelVersion: "1.3.0", // Your model version created: new Date().toISOString(), bestScore: result.optimizedProgram.bestScore, instruction: result.optimizedProgram.instruction, demos: result.optimizedProgram.demos, modelConfig: result.optimizedProgram.modelConfig, optimizerType: result.optimizedProgram.optimizerType, optimizationTime: result.optimizedProgram.optimizationTime, totalRounds: result.optimizedProgram.totalRounds, converged: result.optimizedProgram.converged, stats: result.optimizedProgram.stats, environment: process.env.NODE_ENV || "development", modelUsed: "gpt-4o-mini", trainingDataSize: examples.length, }; await fs.writeFile( "sentiment-analyzer-v1.3.0.json", JSON.stringify(optimizationData, null, 2), ); ``` **πŸŽ‰ Congratulations!** You now understand the complete unified optimization workflow: 1. **Train** with examples and metrics 2. **Apply** optimization using `program.applyOptimization(result.optimizedProgram)` 3. **Save** the complete optimization configuration (demos + instruction + model config) 4. **Load** and recreate optimization in production using `AxOptimizedProgramImpl` 5. **Version** and manage your optimizations with comprehensive metadata --- ## πŸ“š Understanding the Basics ### What Just Happened? 1. **The Optimizer** tried different ways to ask your AI the question 2. **It tested** each approach using your examples 3. **It kept** the best-performing version 4. **Your program** now uses the optimized prompt and examples ### Key Terms (Simple Explanations) - **Student AI**: The model you want to optimize (usually cheaper/faster) - **Teacher AI**: Optional expensive model that helps create better instructions - **Examples**: Your training data showing correct answers - **Metric**: How you measure if the AI is doing well - **Demos**: The best examples the optimizer found to show your AI ### What Does Optimization Actually Produce? 🎯 **The main output is DEMOS** - these are not just "demo data" but **optimized few-shot examples** that dramatically improve your AI's performance: ```typescript // What demos contain: { "traces": [ { "reviewText": "I love this product!", // Input that works well "sentiment": "positive" // Expected output }, { "reviewText": "This is terrible quality", // Another good example "sentiment": "negative" // Expected output } ], "instruction": "Analyze customer sentiment..." // Optimized prompt (MiPRO) } ``` **Why demos are powerful:** - βœ… **Portable**: Save as JSON, load anywhere - βœ… **Fast**: No re-optimization in production - βœ… **Effective**: Often 2-5x performance improvement - βœ… **Cost-effective**: Reduce API calls by using cheaper models better **The workflow:** 1. **Training**: `optimizer.compile()` β†’ produces `result.demos` 2. **Save**: `JSON.stringify(result.demos)` β†’ save to file/database 3. **Production**: Load demos β†’ `program.setDemos(demos)` β†’ improved performance ### When to Use Optimization > **🎯 Perfect for beginners**: Start with classification tasks like sentiment > analysis, email categorization, or content moderation where you have clear > right/wrong answers. βœ… **Great for:** - Classification tasks (sentiment, categories, etc.) - When you have some example data (even just 5-10 examples!) - When accuracy matters more than speed - When you want to save money on API calls - Repetitive tasks you do often ❌ **Skip for now:** - Simple one-off tasks - When you have no training examples - Creative writing tasks (poems, stories) - When you need results immediately (optimization takes 1-5 minutes) --- ## 🎯 Common Use Cases (Copy & Paste Ready) ### 1. Email Classification ```typescript const emailClassifier = ax(` emailContent:string "Email text" -> category:class "urgent, normal, spam" "Email priority", needsReply:class "yes, no" "Does this need a response?" `); const examples = [ { emailContent: "URGENT: Server is down!", category: "urgent", needsReply: "yes", }, { emailContent: "Thanks for your help yesterday", category: "normal", needsReply: "no", }, { emailContent: "You won a million dollars! Click here!", category: "spam", needsReply: "no", }, ]; const metric = ({ prediction, example }) => { let score = 0; if (prediction.category === example.category) score += 0.7; if (prediction.needsReply === example.needsReply) score += 0.3; return score; }; // Same optimization pattern as before... ``` ### 2. Customer Support Routing ```typescript const supportRouter = ax(` customerMessage:string "Customer inquiry" -> department:class "billing, technical, general" "Which team should handle this", urgency:class "low, medium, high" "How urgent is this" `); const examples = [ { customerMessage: "I was charged twice for my subscription", department: "billing", urgency: "high", }, { customerMessage: "How do I reset my password?", department: "technical", urgency: "medium", }, { customerMessage: "What are your business hours?", department: "general", urgency: "low", }, ]; ``` ### 3. Content Moderation ```typescript const contentModerator = ax(` userPost:string "User-generated content" -> safe:class "yes, no" "Is this content appropriate?", reason:string "Why was this flagged (if unsafe)" `); const examples = [ { userPost: "Great weather today!", safe: "yes", reason: "" }, { userPost: "This product sucks and so do you!", safe: "no", reason: "Inappropriate language", }, { userPost: "Check out my new blog post", safe: "yes", reason: "" }, ]; ``` --- ## πŸ’° Saving Money: Teacher-Student Setup **The Problem**: GPT-4 is smart but expensive. GPT-4-mini is cheap but sometimes not as accurate. **The Solution**: Use GPT-4 as a "teacher" to make GPT-4-mini as smart as GPT-4, but at 1/10th the cost! ### Simple Teacher-Student Setup ```typescript // Teacher: Smart but expensive (only used during optimization) const teacherAI = ai({ name: "openai", apiKey: process.env.OPENAI_APIKEY!, config: { model: "gpt-4o" }, // The expensive one }); // Student: Fast and cheap (used for actual work) const studentAI = ai({ name: "openai", apiKey: process.env.OPENAI_APIKEY!, config: { model: "gpt-4o-mini" }, // The cheap one }); const optimizer = new AxMiPRO({ studentAI, // This is what gets optimized teacherAI, // This helps create better instructions examples, options: { verbose: true }, }); // The magic: cheap model performs like expensive model! const result = await optimizer.compile(program, examples, metric); ``` **Real savings**: Instead of paying $0.03 per 1K tokens, you pay $0.0006 per 1K tokens after optimization - that's 50x cheaper! --- ## πŸ”§ Making It Better: Practical Tips ### 1. Better Examples = Better Results ❌ **Bad examples** (too similar): ```typescript const badExamples = [ { text: "I love it", sentiment: "positive" }, { text: "I like it", sentiment: "positive" }, { text: "I enjoy it", sentiment: "positive" }, ]; ``` βœ… **Good examples** (diverse): ```typescript const goodExamples = [ { text: "I love this product!", sentiment: "positive" }, { text: "Terrible quality, broke immediately", sentiment: "negative" }, { text: "It works fine, nothing special", sentiment: "neutral" }, { text: "Best purchase ever made!", sentiment: "positive" }, { text: "Completely useless waste of money", sentiment: "negative" }, ]; ``` ### 2. Better Metrics = Better Optimization ❌ **Too simple**: ```typescript const simpleMetric = ({ prediction, example }) => { return prediction.category === example.category ? 1 : 0; }; ``` βœ… **More nuanced**: ```typescript const betterMetric = ({ prediction, example }) => { let score = 0; // Main task (80% of score) if (prediction.category === example.category) { score += 0.8; } // Bonus for confidence (20% of score) if (prediction.confidence && prediction.confidence > 0.7) { score += 0.2; } return score; }; ``` ### 3. Start Small, Then Scale **Phase 1**: Start with 5-10 examples ```typescript const optimizer = new AxMiPRO({ studentAI, examples: examples.slice(0, 10), // Just first 10 options: { numTrials: 3, // Quick test verbose: true, }, }); ``` **Phase 2**: Scale up if results are good ```typescript const optimizer = new AxMiPRO({ studentAI, teacherAI, examples: allExamples, // All your data options: { numTrials: 8, // More thorough verbose: true, }, }); ``` --- ## πŸ› οΈ Troubleshooting Guide ### "My optimization score is low!" **Check your examples**: ```typescript // Are they diverse enough? console.log("Unique categories:", [ ...new Set(examples.map((e) => e.category)), ]); // Are they correct? examples.forEach((ex, i) => { console.log(`Example ${i}: ${ex.text} -> ${ex.category}`); }); ``` **Try a better metric**: ```typescript // Add logging to see what's happening const debugMetric = ({ prediction, example }) => { const correct = prediction.category === example.category; console.log( `Predicted: ${prediction.category}, Expected: ${example.category}, Correct: ${correct}`, ); return correct ? 1 : 0; }; ``` ### "It's too expensive!" **Set a budget**: ```typescript import { AxDefaultCostTracker } from "@ax-llm/ax"; const costTracker = new AxDefaultCostTracker({ maxTokens: 10000, // Stop after 10K tokens maxCost: 5, // Stop after $5 }); const optimizer = new AxMiPRO({ studentAI, examples, costTracker, // Automatic budget control options: { numTrials: 3, // Fewer trials earlyStoppingTrials: 2, // Stop early if no improvement }, }); ``` ### "It's taking too long!" **Speed it up**: ```typescript const optimizer = new AxMiPRO({ studentAI, examples: examples.slice(0, 20), // Fewer examples options: { numCandidates: 3, // Fewer candidates to try numTrials: 5, // Fewer trials minibatch: true, // Process in smaller batches verbose: true, }, }); ``` ### "Results are inconsistent!" **Make it reproducible**: ```typescript const optimizer = new AxMiPRO({ studentAI: ai({ name: "openai", apiKey: process.env.OPENAI_APIKEY!, config: { model: "gpt-4o-mini", temperature: 0.1, // Lower = more consistent }, }), examples, seed: 42, // Same results every time options: { verbose: true }, }); ``` --- ## πŸŽ“ Next Steps: Level Up Your Skills ### 1. Try Different Optimizers **For few-shot learning** (when you have good examples): ```typescript import { AxBootstrapFewShot } from "@ax-llm/ax"; const optimizer = new AxBootstrapFewShot({ studentAI, examples, options: { maxDemos: 5, // Show 5 examples to AI maxRounds: 3, // 3 rounds of improvement verboseMode: true, }, }); ``` ### 2. Multi-Objective Optimization with `compilePareto` **The Problem**: Sometimes you care about multiple things at once - accuracy AND speed AND cost. Traditional optimization only handles one objective at a time. **The Solution**: `compilePareto` finds the optimal trade-offs between multiple objectives using Pareto frontier analysis. #### What is Pareto Optimization? A solution is "Pareto optimal" if you can't improve one objective without making another objective worse. The collection of all such solutions is called the "Pareto frontier." **Example**: - Solution A: 90% accuracy, 100ms response time, $0.10 cost - Solution B: 85% accuracy, 50ms response time, $0.05 cost - Solution C: 80% accuracy, 200ms response time, $0.08 cost Solutions A and B are both Pareto optimal (A is more accurate but slower/expensive, B is faster/cheaper but less accurate). Solution C is dominated by both A and B. #### When to Use `compilePareto` βœ… **Perfect for:** - Content moderation (accuracy vs speed vs cost) - Customer service routing (response time vs routing accuracy vs resource usage) - Email classification (precision vs recall vs processing speed) - Product recommendations (relevance vs diversity vs computation cost) ❌ **Skip for:** - Single clear objective (use regular `compile`) - When one objective is clearly most important - Quick prototyping (more complex than single-objective) #### Complete Working Example ```typescript import { ai, ax, AxMiPRO } from "@ax-llm/ax"; // Content moderation with multiple objectives const contentModerator = ax(` userPost:string "User-generated content" -> isSafe:class "safe, unsafe" "Content safety", confidence:number "Confidence 0-1", reason:string "Explanation if unsafe" `); // Training examples const examples = [ { userPost: "Great weather today!", isSafe: "safe", confidence: 0.95, reason: "", }, { userPost: "This product sucks and the company is terrible!", isSafe: "unsafe", confidence: 0.8, reason: "Aggressive language", }, // ... more examples ]; // Multi-objective metric function const multiMetric = ({ prediction, example }) => { // Calculate multiple scores const accuracy = prediction.isSafe === example.isSafe ? 1 : 0; // Reward high confidence when correct, penalize when wrong const confidenceScore = prediction.isSafe === example.isSafe ? (prediction.confidence || 0) : (1 - (prediction.confidence || 0)); // Reward explanations for unsafe content const explanationScore = example.isSafe === "unsafe" ? (prediction.reason && prediction.reason.length > 10 ? 1 : 0) : 1; // No penalty for safe content // Return multiple objectives return { accuracy, // Correctness of safety classification confidence: confidenceScore, // Quality of confidence calibration explanation: explanationScore, // Quality of reasoning }; }; // Set up optimizer const optimizer = new AxMiPRO({ studentAI: ai({ name: "openai", apiKey: process.env.OPENAI_APIKEY!, config: { model: "gpt-4o-mini" }, }), examples, options: { verbose: true }, }); // Run multi-objective optimization console.log("πŸ”„ Finding optimal trade-offs..."); const result = await optimizer.compilePareto( contentModerator, examples, multiMetric, ); console.log(`βœ… Found ${result.paretoFrontSize} optimal solutions!`); console.log(`πŸ“Š Hypervolume: ${result.hypervolume?.toFixed(4) || "N/A"}`); // Explore the Pareto frontier result.paretoFront.forEach((solution, index) => { console.log(`\n🎯 Solution ${index + 1}:`); console.log(` Accuracy: ${(solution.scores.accuracy * 100).toFixed(1)}%`); console.log( ` Confidence: ${(solution.scores.confidence * 100).toFixed(1)}%`, ); console.log( ` Explanation: ${(solution.scores.explanation * 100).toFixed(1)}%`, ); console.log(` Strategy: ${solution.configuration.strategy}`); console.log(` Dominates: ${solution.dominatedSolutions} other solutions`); }); ``` #### Choosing the Best Solution ```typescript // Option 1: Pick the solution that dominates the most others const mostDominant = result.paretoFront.reduce((best, current) => current.dominatedSolutions > best.dominatedSolutions ? current : best ); // Option 2: Pick based on your priorities (weighted combination) const priorities = { accuracy: 0.6, confidence: 0.3, explanation: 0.1 }; const bestWeighted = result.paretoFront.reduce((best, current) => { const currentScore = Object.entries(current.scores) .reduce((sum, [obj, score]) => sum + score * (priorities[obj] || 0), 0); const bestScore = Object.entries(best.scores) .reduce((sum, [obj, score]) => sum + score * (priorities[obj] || 0), 0); return currentScore > bestScore ? current : best; }); // Option 3: Interactive selection based on business requirements const businessOptimal = result.paretoFront.find((solution) => solution.scores.accuracy >= 0.85 && // Must be at least 85% accurate solution.scores.confidence >= 0.7 && // Must be well-calibrated solution.scores.explanation >= 0.8 // Must explain unsafe content well ); // Apply the chosen solution if (businessOptimal?.demos) { contentModerator.setDemos(businessOptimal.demos); console.log("🎯 Applied business-optimal solution"); } ``` #### Advanced Multi-Objective Patterns **Cost-Quality Trade-off**: ```typescript const multiMetric = ({ prediction, example }) => ({ accuracy: prediction.category === example.category ? 1 : 0, cost: 1 / (estimateTokenCost(prediction) + 1), // Inverse cost (higher = cheaper) speed: 1 / (prediction.responseTime || 1000), // Inverse time (higher = faster) }); ``` **Precision-Recall Optimization**: ```typescript const multiMetric = ({ prediction, example }) => { const truePositive = prediction.category === "positive" && example.category === "positive" ? 1 : 0; const falsePositive = prediction.category === "positive" && example.category !== "positive" ? 1 : 0; const falseNegative = prediction.category !== "positive" && example.category === "positive" ? 1 : 0; return { precision: falsePositive === 0 ? 1 : (truePositive / (truePositive + falsePositive)), recall: falseNegative === 0 ? 1 : (truePositive / (truePositive + falseNegative)), }; }; ``` **Customer Satisfaction vs Efficiency**: ```typescript const multiMetric = ({ prediction, example }) => ({ customerSatisfaction: calculateSatisfactionScore(prediction, example), resourceEfficiency: 1 / (prediction.processingSteps || 1), resolutionSpeed: prediction.resolutionTime ? (1 / prediction.resolutionTime) : 0, }); ``` #### Understanding the Results ```typescript const result = await optimizer.compilePareto(program, multiMetric); // Key properties of AxParetoResult: console.log(`Pareto frontier size: ${result.paretoFrontSize}`); console.log( `Total solutions generated: ${result.finalConfiguration?.numSolutions}`, ); console.log(`Best single score: ${result.bestScore}`); console.log(`Hypervolume (2D only): ${result.hypervolume}`); // Each solution on the frontier contains: result.paretoFront.forEach((solution) => { solution.demos; // Optimized examples for this solution solution.scores; // Scores for each objective solution.configuration; // How this solution was generated solution.dominatedSolutions; // How many other solutions this beats }); ``` #### Performance Considerations - **Runtime**: `compilePareto` runs multiple single-objective optimizations, so it takes 3-10x longer than regular `compile` - **Cost**: Uses more API calls due to multiple optimization runs - **Complexity**: Only use when you genuinely need multiple objectives - **Scalability**: Works best with 2-4 objectives; more objectives = exponentially more solutions #### Tips for Success 1. **Start with 2-3 objectives**: More objectives make it harder to choose solutions 2. **Make objectives independent**: Avoid highly correlated objectives 3. **Scale objectives similarly**: Ensure all objectives range 0-1 for fair comparison 4. **Use business constraints**: Filter the Pareto frontier by minimum requirements 5. **Validate solutions**: Test multiple Pareto-optimal solutions in practice ### 3. Chain Multiple Programs ```typescript // First program: Extract key info const extractor = ax( 'emailContent:string "Email content" -> keyPoints:string[] "Important points"', ); // Second program: Classify based on extracted info const classifier = ax( 'keyPoints:string[] "Key points" -> priority:class "low, medium, high" "Email priority"', ); // Optimize them separately, then chain them const extractResult = await extractOptimizer.compile( extractor, extractExamples, extractMetric, ); const classifyResult = await classifyOptimizer.compile( classifier, classifyExamples, classifyMetric, ); // Use them together const emailContent = "Meeting moved to 3pm tomorrow, please confirm"; const keyPoints = await extractor.forward(llm, { emailContent }); const priority = await classifier.forward(llm, { keyPoints: keyPoints.keyPoints, }); ``` --- ## πŸ“– Complete Working Example Here's a full example you can copy, paste, and run: ```typescript import { ai, ax, AxMiPRO } from "@ax-llm/ax"; // 1. Define the task const productReviewer = ax(` productReview:string "Customer product review" -> rating:class "1, 2, 3, 4, 5" "Star rating 1-5", aspect:class "quality, price, shipping, service" "Main concern", recommendation:class "buy, avoid, maybe" "Would you recommend?" `); // 2. Training examples const examples = [ { productReview: "Amazing quality, worth every penny!", rating: "5", aspect: "quality", recommendation: "buy", }, { productReview: "Too expensive for what you get", rating: "2", aspect: "price", recommendation: "avoid", }, { productReview: "Good product but took forever to arrive", rating: "3", aspect: "shipping", recommendation: "maybe", }, { productReview: "Great value, fast delivery, happy customer!", rating: "5", aspect: "price", recommendation: "buy", }, { productReview: "Customer service was rude when I had issues", rating: "1", aspect: "service", recommendation: "avoid", }, ]; // 3. AI setup const llm = ai({ name: "openai", apiKey: process.env.OPENAI_APIKEY!, config: { model: "gpt-4o-mini" }, }); // 4. Success metric const metric = ({ prediction, example }) => { let score = 0; if (prediction.rating === example.rating) score += 0.5; if (prediction.aspect === example.aspect) score += 0.3; if (prediction.recommendation === example.recommendation) score += 0.2; return score; }; // 5. Optimize const optimizer = new AxMiPRO({ studentAI: llm, examples, options: { verbose: true }, }); console.log("πŸ”„ Starting optimization..."); const result = await optimizer.compile(productReviewer, examples, metric); console.log( `βœ… Optimization complete! Score improved to ${ (result.bestScore * 100).toFixed(1) }%`, ); // 6. Apply and save the optimization results using the unified approach if (result.optimizedProgram) { const fs = await import("fs/promises"); // Apply all optimizations at once productReviewer.applyOptimization(result.optimizedProgram); console.log(`✨ Applied optimized configuration:`); console.log(` Score: ${result.optimizedProgram.bestScore.toFixed(3)}`); console.log(` Optimizer: ${result.optimizedProgram.optimizerType}`); console.log( ` Converged: ${result.optimizedProgram.converged ? "βœ…" : "❌"}`, ); // Save complete optimization configuration await fs.writeFile( "product-reviewer-optimization.json", JSON.stringify( { version: "2.0", bestScore: result.optimizedProgram.bestScore, instruction: result.optimizedProgram.instruction, demos: result.optimizedProgram.demos, modelConfig: result.optimizedProgram.modelConfig, optimizerType: result.optimizedProgram.optimizerType, optimizationTime: result.optimizedProgram.optimizationTime, totalRounds: result.optimizedProgram.totalRounds, converged: result.optimizedProgram.converged, stats: result.optimizedProgram.stats, created: new Date().toISOString(), }, null, 2, ), ); console.log( "πŸ’Ύ Complete optimization saved to product-reviewer-optimization.json!", ); } // 7. Test the optimized version const testReview = "The item was okay but customer support was unhelpful when I had questions"; const analysis = await productReviewer.forward(llm, { productReview: testReview, }); console.log("Analysis:", analysis); // Expected: rating: '2' or '3', aspect: 'service', recommendation: 'avoid' or 'maybe' // 8. Later in production - load complete optimization: // import { AxOptimizedProgramImpl } from '@ax-llm/ax'; // const savedData = JSON.parse(await fs.readFile('product-reviewer-optimization.json', 'utf8')); // const optimizedProgram = new AxOptimizedProgramImpl(savedData); // productReviewer.applyOptimization(optimizedProgram); // console.log(`πŸš€ Loaded complete optimization v${savedData.version} with score ${savedData.bestScore.toFixed(3)}`); ``` --- ## 🎯 Key Takeaways 1. **Start simple**: 5 examples and basic optimization can give you 20-30% improvement 2. **Use the unified approach**: `program.applyOptimization(result.optimizedProgram)` - one call does everything! 3. **Save complete optimizations**: New v2.0 format includes demos, instruction, model config, and metadata 4. **Load optimizations cleanly**: Use `AxOptimizedProgramImpl` to recreate saved optimizations 5. **Teacher-student saves money**: Use expensive models to teach cheap ones 6. **Good examples matter more than lots of examples**: 10 diverse examples beat 100 similar ones 7. **Measure what matters**: Your metric defines what the AI optimizes for 8. **Version comprehensively**: Track optimization versions, scores, convergence, and metadata 9. **Backward compatibility**: Legacy demo format still works, but upgrade for better experience 10. **Production-ready**: The unified approach is designed for enterprise production use **Ready to optimize your first AI program?** Copy the examples above and start experimenting! **Questions?** Check the `src/examples/` folder for more real-world examples, or refer to the troubleshooting section above. --- ## πŸ“š Quick Reference ### Essential Imports ```typescript import { ai, ax, AxMiPRO, AxOptimizedProgramImpl } from "@ax-llm/ax"; ``` ### Basic Pattern (Copy This!) ```typescript // 1. Define program const program = ax( 'inputText:string "description" -> output:class "a, b" "description"', ); // 2. Create AI const llm = ai({ name: "openai", apiKey: process.env.OPENAI_APIKEY!, config: { model: "gpt-4o-mini" }, }); // 3. Add examples const examples = [{ inputText: "example", output: "a" }]; // 4. Define metric const metric = ({ prediction, example }) => prediction.output === example.output ? 1 : 0; // 5. Optimize const optimizer = new AxMiPRO({ studentAI: llm, examples, options: { verbose: true }, }); const result = await optimizer.compile(program, examples, metric); // 6. Apply optimization (unified approach) if (result.optimizedProgram) { program.applyOptimization(result.optimizedProgram); } ``` ### Common Field Types - `fieldName:string "description"` - Text input/output - `fieldName:class "option1, option2" "description"` - Classification - `fieldName:number "description"` - Numeric values - `fieldName:string[] "description"` - Lists - `fieldName:boolean "description"` - True/false ### Budget Control ```typescript import { AxDefaultCostTracker } from "@ax-llm/ax"; const costTracker = new AxDefaultCostTracker({ maxTokens: 10000, maxCost: 5 }); // Add to optimizer: costTracker ``` ### Teacher-Student (Cost Savings) ```typescript const teacherAI = ai({ name: "openai", config: { model: "gpt-4o" } }); // Expensive const studentAI = ai({ name: "openai", config: { model: "gpt-4o-mini" }, }); // Cheap // Use both in optimizer: { studentAI, teacherAI, ... } ``` ### Unified Optimization (New in v14.0+) ```typescript // Save complete optimization const savedData = { version: "2.0", bestScore: result.optimizedProgram.bestScore, instruction: result.optimizedProgram.instruction, demos: result.optimizedProgram.demos, modelConfig: result.optimizedProgram.modelConfig, // temperature, etc. optimizerType: result.optimizedProgram.optimizerType, // ... all other optimization data }; // Load and apply in production const optimizedProgram = new AxOptimizedProgramImpl(savedData); program.applyOptimization(optimizedProgram); // One call does everything! // Benefits: // βœ… Single object contains all optimization data // βœ… One method call applies everything // βœ… Complete metadata tracking // βœ… Backward compatibility with legacy demos // βœ… Production-ready versioning and deployment ``` --- _πŸ’‘ Remember: Optimization is like having a personal AI tutor. You provide the examples and goals, and it figures out the best way to teach your AI. Start simple, measure results, and gradually make it more sophisticated as you learn what works!_ --- ## πŸ’Ύ Checkpointing (Fault Tolerance) Long-running optimizations can be expensive and time-consuming. Ax provides simple function-based checkpointing to save optimization progress and recover from failures. ### Why Use Checkpointing? - **Cost Protection**: Don't lose expensive optimization work due to crashes - **Fault Tolerance**: Resume optimization after interruptions - **Experimentation**: Save optimization state at different points for analysis ### How It Works Implement two simple functions to save and load checkpoint data: ```typescript import { type AxCheckpointSaveFn, type AxCheckpointLoadFn } from '@ax-llm/ax' const checkpointSave: AxCheckpointSaveFn = async (checkpoint) => { // JSON serialize the checkpoint and save it wherever you want: // - Memory: map.set(id, checkpoint) // - localStorage: localStorage.setItem(id, JSON.stringify(checkpoint)) // - Database: await db.create({ data: checkpoint }) // - Files: await fs.writeFile(`${id}.json`, JSON.stringify(checkpoint)) // - Cloud: await s3.putObject({ Key: id, Body: JSON.stringify(checkpoint) }) const id = `checkpoint_${Date.now()}` // Your storage implementation here return id } const checkpointLoad: AxCheckpointLoadFn = async (id) => { // Load and JSON parse the checkpoint data // Return null if not found return /* your loaded checkpoint */ || null } // Use with any optimizer const optimizer = new AxMiPRO({ studentAI: llm, examples, checkpointSave, checkpointLoad, checkpointInterval: 10, // Save every 10 rounds resumeFromCheckpoint: 'checkpoint_12345', // Resume from specific checkpoint options: { numTrials: 50, verbose: true } }) ``` ### Key Points - **Simple**: Just two functions - save and load - **Storage Agnostic**: Works with any storage (memory, files, databases, cloud) - **JSON Serializable**: Checkpoint data is just JSON - store it anywhere - **Complete State**: Contains all optimization progress (scores, configurations, examples) - **Browser Compatible**: No filesystem dependencies The checkpoint contains complete optimization state, so you can resume exactly where you left off, even after crashes or interruptions. --- ## 🐍 Python Optimization Service Integration For advanced optimization scenarios requiring sophisticated Bayesian optimization, Ax uses a production-ready Python service using Optuna. This is required for MiPro v2 optimization with complex parameter spaces. ### When to Use Python Service βœ… **Great for:** - Complex parameter optimization (10+ parameters) - Bayesian optimization with acquisition functions - Long-running optimization jobs (100+ trials) - Production deployments requiring fault tolerance - Distributed optimization across multiple machines - Advanced pruning and sampling strategies ❌ **Note:** MiPro v2 requires the Python service; local TypeScript fallback is no longer supported. ### Quick Setup with uv The Python service uses `uv` for fast, modern Python package management: ```bash # 1. Install uv (if not already installed) curl -LsSf https://astral.sh/uv/install.sh | sh # 2. Navigate to optimizer directory cd src/optimizer # 3. Install and run (that's it!) uv sync uv run ax-optimizer server start --debug ``` The service runs with an in-memory queue by default - no Redis or configuration needed! #### Production Setup (With Redis for Scaling) ```bash # Install with Redis support uv sync --group redis # Start Redis (in another terminal) docker run -p 6379:6379 redis:7-alpine # Start the service uv run ax-optimizer server start --debug ``` ### CLI Usage The service provides a comprehensive CLI for all operations: ```bash # Server management uv run ax-optimizer server start --host 0.0.0.0 --port 8000 --debug uv run ax-optimizer server status uv run ax-optimizer server stop # Create MiPro optimization configuration uv run ax-optimizer mipro create-config --output mipro_config.json # Start optimization job uv run ax-optimizer optimize --config mipro_config.json --monitor # Monitor existing job uv run ax-optimizer monitor # Get parameter suggestions (manual optimization loop) uv run ax-optimizer suggest # Report trial results uv run ax-optimizer evaluate # Get final results uv run ax-optimizer results # List all jobs uv run ax-optimizer list --limit 20 ``` ### Docker Setup (Production) For production deployments, use the provided Docker setup: ```bash # Start all services (Redis, PostgreSQL, API, Workers) cd src/optimizer docker-compose up -d # View logs docker-compose logs -f # Scale workers for performance docker-compose up -d --scale worker=3 # Stop services docker-compose down ``` ### MiPro with Python Service Here's how to use MiPro with the Python optimization service: ```typescript import { ai, ax, type AxMetricFn, AxMiPRO } from "@ax-llm/ax"; // Email classification example const emailClassifier = ax( 'emailText:string "Email content" -> priority:class "critical, normal, low" "Email priority"', ); const examples = [ { emailText: "URGENT: Server down!", priority: "critical" }, { emailText: "Meeting reminder", priority: "normal" }, { emailText: "Newsletter update", priority: "low" }, // ... more examples ]; const metric: AxMetricFn = ({ prediction, example }) => { return (prediction as any).priority === (example as any).priority ? 1.0 : 0.0; }; // Configure MiPro with Python service const optimizer = new AxMiPRO({ studentAI: ai({ name: "openai", apiKey: process.env.OPENAI_APIKEY!, config: { model: "gpt-4o-mini" }, }), teacherAI: ai({ name: "openai", apiKey: process.env.OPENAI_APIKEY!, config: { model: "gpt-4" }, }), examples, // Python service configuration optimizerEndpoint: "http://localhost:8000", optimizerTimeout: 60000, optimizerRetries: 3, // Enhanced MiPro settings for Python service numTrials: 100, // More trials with Python bayesianOptimization: true, acquisitionFunction: "expected_improvement", explorationWeight: 0.15, // Self-consistency (MiPRO v2) // Ask the model for multiple independent samples and pick the best with a default majority-vote picker sampleCount: 3, // Progress tracking onProgress: (update) => { console.log(`Trial ${update.round}: ${update.currentScore.toFixed(3)}`); }, }); // Run optimization const result = await optimizer.compile(emailClassifier, examples, metric); console.log(`Best score: ${result.bestScore}`); ``` #### Custom Result Picker (Advanced) ```ts import { type AxResultPickerFunction } from "@ax-llm/ax"; // Example: prefer higher confidence, break ties by shortest explanation const myPicker: AxResultPickerFunction = async (data) => { if (data.type === "function") { // Choose first non-error function execution const ix = data.results.findIndex((r) => !r.isError); return ix >= 0 ? ix : 0; } // Fields: choose highest confidence; tie-breaker shortest explanation let bestIx = 0; let bestScore = -Infinity; for (const r of data.results) { const sample = r.sample as { confidence?: number; explanation?: string }; const score = (sample.confidence ?? 0) - (sample.explanation?.length ?? 0) / 1000; if (score > bestScore) { bestScore = score; bestIx = r.index; } } return bestIx; }; const optimizer = new AxMiPRO({ studentAI: llm, examples, // Use 5 samples per example and custom picker sampleCount: 5, resultPicker: myPicker, }); ``` When to use: - Use a custom picker when your task has a clear selection heuristic (e.g., confidence, shortness, scoring rubric) or you want to implement an LLM-judge selection. - For classification tasks, the built-in majority-vote default often works well. #### New/Updated Options (MiPRO v2) - `sampleCount?: number` (default: 1) - When > 1, MiPRO evaluates each example with multiple samples and uses a default result picker to select the best output per example. Great for tasks where self-consistency helps. - Early stopping - Controlled by `earlyStoppingTrials` and `minImprovementThreshold`. MiPRO will stop if no trial improves the best score by at least the threshold for the configured number of trials. - Minibatch scheduling - When `minibatch` is true, evaluations run on random minibatches. Every `minibatchFullEvalSteps` trials, MiPRO runs a full evaluation to correct drift from minibatch noise. - Expanded logging - Progress is emitted each trial with score and configuration; early stopping is logged; final result includes score/configuration histories and accurate `optimizationTime`. Note: MiPRO now applies suggested `bootstrappedDemos` during evaluation so that the optimizer can learn their true effect on your metric. #### Hyperparameters vs. MiPRO MiPRO primarily optimizes the program-level levers emphasized in DSPy/MiPRO (instructions, few-shot demos, data-aware proposals). Model hyperparameters (e.g., `temperature`, `topP`, penalties) can be included for practical gains; tuning `temperature` often helps self-consistency. The original MiPRO work focuses on program synthesis and demo selection rather than broad model hyperparameter sweeps. If you decide to extend the search space: - Prefer a small, impactful set (e.g., `temperature`, occasionally `topP`). - Keep ranges conservative to avoid noisy evaluations. - Measure costs: a larger hyperparameter space increases trials. Optional: Include topP in MiPRO ```ts const optimizer = new AxMiPRO({ studentAI: llm, examples, // Keep it off by default; turn on if diversity helps your task optimizeTopP: true, // adds topP (0.7–1.0) to the optimizer search space sampleCount: 3, // pairs well with self-consistency }); ``` ### Environment Variables Configure the service with environment variables: ```bash # .env file for Python service HOST=0.0.0.0 PORT=8000 DEBUG=false REDIS_URL=redis://localhost:6379/0 DATABASE_URL=postgresql://user:password@localhost/optimizer USE_MEMORY_STORAGE=true # Set to false for PostgreSQL persistence MAX_TRIALS_PER_STUDY=1000 DEFAULT_TIMEOUT_SECONDS=3600 MAX_CONCURRENT_JOBS=10 ``` ### Production Features The Python service includes enterprise-ready features: **Fault Tolerance:** - Automatic checkpointing and resumption - Redis-based task queue with ARQ - Background job processing - Health checks and monitoring **Scalability:** - Horizontal scaling with multiple workers - Database persistence with PostgreSQL - Connection pooling and resource management - Rate limiting and timeout controls **Observability:** - Comprehensive logging with structured output - Metrics export for monitoring systems - Job status tracking and history - Error reporting and debugging tools ### Advanced Parameter Templates The service includes optimized parameter templates for different scenarios: ```python # Using the Python adapter directly from app.mipro_adapter import MiProAdapter, MiProConfiguration # Light optimization (fast, good for development) config = MiProConfiguration(optimization_level="light") adapter = MiProAdapter(config) request = adapter.create_optimization_request( study_name="email_classification", parameter_sets=["instruction_generation", "demo_selection"] ) # Medium optimization (balanced, good for most use cases) config = MiProConfiguration(optimization_level="medium") # Heavy optimization (thorough, good for production) config = MiProConfiguration(optimization_level="heavy") ``` ### Integration with TypeScript Switch between local and Python optimization seamlessly: ```typescript const optimizer = new AxMiPRO({ studentAI, examples, numTrials: 100, optimizerEndpoint: process.env.OPTIMIZER_ENDPOINT || "http://localhost:8000", bayesianOptimization: true, acquisitionFunction: "expected_improvement", onProgress: (update) => { console.log(`Trial ${update.round}: ${update.currentScore.toFixed(3)}`); }, }); ``` ### Development Workflow 1. **Start with TypeScript** for quick prototyping: ```bash npm run tsx ./src/examples/mipro-python-optimizer.ts ``` 2. **Scale to Python** for production optimization: ```bash # Terminal 1: Start Python service cd src/optimizer && uv run ax-optimizer server start # Terminal 2: Run with Python service USE_PYTHON_OPTIMIZER=true npm run tsx ./src/examples/mipro-python-optimizer.ts ``` 3. **Deploy to production** with Docker: ```bash cd src/optimizer && docker-compose up -d ``` This provides a smooth development path from prototype to production with the same codebase! ================================================================================ # AxFlow Guide # Source: AXFLOW.md # AxFlow - Orchestration framework for building AI workflows with Ax # AxFlow Documentation **AxFlow** is a powerful workflow orchestration system for building complex AI applications with automatic dependency analysis, parallel execution, and flexible control flow patterns. ## Table of Contents - [Quick Start](#quick-start) - [Why AxFlow is the Future](#why-axflow-is-the-future) - [Core Concepts](#core-concepts) - [API Reference](#api-reference) - [Control Flow Patterns](#control-flow-patterns) - [Asynchronous Operations](#5-asynchronous-operations) - [Advanced Features](#advanced-features) - [Best Practices](#best-practices) - [Examples](#examples) ## Quick Start ### Basic Flow ```typescript import { ai, AxFlow } from "@ax-llm/ax"; // Create AI instance const llm = ai({ name: "openai", apiKey: process.env.OPENAI_APIKEY! }); // Create a simple flow const flow = new AxFlow<{ userInput: string }, { responseText: string }>() .node("testNode", "userInput:string -> responseText:string") .execute("testNode", (state) => ({ userInput: state.userInput })) .map((state) => ({ responseText: state.testNodeResult.responseText })); // Execute the flow const result = await flow.forward(llm, { userInput: "Hello world" }); console.log(result.responseText); ``` ### Constructor Options ```typescript // Basic constructor const flow = new AxFlow(); // With options const flow = new AxFlow({ autoParallel: false }); // With explicit typing const flow = new AxFlow(); // With options and typing const flow = new AxFlow({ autoParallel: true, batchSize: 5, }); ``` ## Why AxFlow is the Future **πŸš€ Automatic Performance Optimization:** - **Zero-Config Parallelization**: Automatically runs independent operations in parallel (1.5-3x speedup) - **Intelligent Dependency Analysis**: AI-powered analysis of input/output dependencies - **Optimal Execution Planning**: Automatically groups operations into parallel levels - **Concurrency Control**: Smart resource management with configurable limits - **Runtime Control**: Enable/disable auto-parallelization per execution as needed **πŸ›‘οΈ Production-Ready Resilience:** - **Exponential Backoff**: Smart retry strategies with configurable delays - **Graceful Degradation**: Fallback mechanisms for continuous operation - **Error Isolation**: Prevent cascading failures across workflow components - **Resource Monitoring**: Adaptive scaling based on system performance **Compared to Traditional Approaches:** - **10x More Compact**: Ultra-concise syntax with powerful aliases - **Zero Boilerplate**: Automatic state management and context threading - **Multi-Modal Ready**: Native support for text, images, audio, and streaming - **Self-Optimizing**: Built-in compatibility with MiPRO and other advanced optimizers - **Enterprise Ready**: Circuit breakers, retries, and monitoring built-in - **Production Hardened**: Used by startups scaling to millions of users **Real-World Superpowers:** - **Autonomous Agents**: Self-healing, self-improving AI workflows - **Multi-Model Orchestration**: Route tasks to the perfect AI for each job - **Adaptive Pipelines**: Workflows that evolve based on real-time feedback - **Cost Intelligence**: Automatic optimization between speed, quality, and cost - **Mission Critical**: Built for production with enterprise-grade reliability > _"AxFlow doesn't just execute AI workflowsβ€”it orchestrates the future of > intelligent systems with automatic performance optimization"_ **Ready to build the impossible?** AxFlow extends `AxProgramWithSignature`, giving you access to the entire Ax ecosystem: optimization, streaming, tracing, function calling, and more. The future of AI development is declarative, adaptive, and beautiful. ## Core Concepts ### 1. Node Definition Nodes define the available operations in your flow. You must define nodes before executing them. ```typescript // String signature (creates AxGen automatically) flow.node("processor", "input:string -> output:string"); // With multiple outputs flow.node("analyzer", "text:string -> sentiment:string, confidence:number"); // Complex field types flow.node( "extractor", "documentText:string -> processedResult:string, entities:string[]", ); ``` ### 2. State Evolution State grows as you execute nodes, with results stored in `{nodeName}Result` format: ```typescript // Initial state: { userInput: "Hello" } flow.execute("processor", (state) => ({ input: state.userInput })); // State becomes: { userInput: "Hello", processorResult: { output: "Processed Hello" } } flow.execute("analyzer", (state) => ({ text: state.processorResult.output })); // State becomes: { // userInput: "Hello", // processorResult: { output: "Processed Hello" }, // analyzerResult: { sentiment: "positive", confidence: 0.8 } // } ``` ### 3. State Transformation Use `map()` to transform state between operations: ```typescript flow.map((state) => ({ ...state, processedInput: state.userInput.toLowerCase(), timestamp: Date.now(), })); ``` ## API Reference ### Core Methods #### `node(name: string, signature: string, options?: object)` Define a node with the given signature. ```typescript flow.node("summarizer", "documentText:string -> summary:string"); flow.node("classifier", "text:string -> category:string", { debug: true }); ``` **Alias:** `n()` - Short alias for `node()` ```typescript flow.n("summarizer", "documentText:string -> summary:string"); ``` #### `nodeExtended(name: string, baseSignature: string | AxSignature, extensions: object)` Create a node with extended signature by adding additional input/output fields. ```typescript // Add chain-of-thought reasoning flow.nodeExtended("reasoner", "question:string -> answer:string", { prependOutputs: [ { name: "reasoning", type: f.internal(f.string("Step-by-step reasoning")) }, ], }); // Add context and confidence flow.nodeExtended("analyzer", "input:string -> output:string", { appendInputs: [{ name: "context", type: f.optional(f.string("Context")) }], appendOutputs: [{ name: "confidence", type: f.number("Confidence score") }], }); ``` **Extension Options:** - `prependInputs` - Add fields at the beginning of input signature - `appendInputs` - Add fields at the end of input signature - `prependOutputs` - Add fields at the beginning of output signature - `appendOutputs` - Add fields at the end of output signature **Alias:** `nx()` - Short alias for `nodeExtended()` ```typescript flow.nx("reasoner", "question:string -> answer:string", { prependOutputs: [ { name: "reasoning", type: f.internal(f.string("Step-by-step reasoning")) }, ], }); ``` #### `execute(nodeName: string, mapping: Function, options?: object)` Execute a node with input mapping. ```typescript flow.execute("summarizer", (state) => ({ documentText: state.document, })); // With AI override flow.execute("processor", mapping, { ai: alternativeAI }); ``` #### `map(transform: Function)` Transform the current state synchronously or asynchronously. ```typescript // Synchronous transformation flow.map((state) => ({ ...state, upperCaseResult: state.processorResult.output.toUpperCase(), })); // Asynchronous transformation flow.map(async (state) => { const apiData = await fetchFromAPI(state.query); return { ...state, enrichedData: apiData, }; }); // Parallel asynchronous transformations flow.map([ async (state) => ({ ...state, result1: await api1(state.data) }), async (state) => ({ ...state, result2: await api2(state.data) }), async (state) => ({ ...state, result3: await api3(state.data) }), ], { parallel: true }); ``` **Alias:** `m()` - Short alias for `map()` (supports both sync and async) ```typescript // Async with alias flow.m(async (state) => { const processed = await processAsync(state.input); return { ...state, processed }; }); ``` #### `returns(transform: Function)` / `r(transform: Function)` Terminal transformation that sets the final output type of the flow. Use this as the last transformation to get proper TypeScript type inference for the flow result. ```typescript const typedFlow = flow<{ input: string }>() .map((state) => ({ ...state, processed: true, count: 42 })) .returns((state) => ({ result: state.processed ? "done" : "pending", totalCount: state.count, })); // TypeScript now properly infers the output type // Result is typed as { result: string; totalCount: number } const result = await typedFlow.forward(llm, { input: "test" }); console.log(result.result); // Type-safe access console.log(result.totalCount); // Type-safe access ``` **Key Benefits:** - **Proper Type Inference**: TypeScript automatically infers the correct return type - **Clear Intent**: Explicitly marks the final transformation of your flow - **Type Safety**: Full autocomplete and type checking on the result object **Aliases:** - `returns()` - Full descriptive name - `r()` - Short alias (matches `m()` pattern) ```typescript // These are equivalent: flow.returns((state) => ({ output: state.value })); flow.r((state) => ({ output: state.value })); ``` **When to Use:** - When you want proper TypeScript type inference for complex flows - As the final step in flows that transform the state into a specific output format - When building reusable flows that need clear output contracts ### Control Flow Methods #### `while(condition: Function)` / `endWhile()` Create loops that execute while condition is true. ```typescript flow .map((state) => ({ ...state, counter: 0 })) .while((state) => state.counter < 3) .map((state) => ({ ...state, counter: state.counter + 1 })) .execute("processor", (state) => ({ input: `iteration ${state.counter}` })) .endWhile(); ``` #### `branch(predicate: Function)` / `when(value)` / `merge()` Conditional branching based on predicate evaluation. ```typescript flow .branch((state) => state.complexity) .when("simple") .execute("simpleProcessor", mapping) .when("complex") .execute("complexProcessor", mapping) .merge() .map((state) => ({ result: state.simpleProcessorResult?.output || state.complexProcessorResult?.output, })); ``` #### `parallel(subFlows: Function[])` / `merge(key: string, mergeFunction: Function)` Execute multiple sub-flows in parallel. ```typescript flow .parallel([ (subFlow) => subFlow.execute("analyzer1", (state) => ({ text: state.input })), (subFlow) => subFlow.execute("analyzer2", (state) => ({ text: state.input })), (subFlow) => subFlow.execute("analyzer3", (state) => ({ text: state.input })), ]) .merge("combinedResults", (result1, result2, result3) => ({ analysis1: result1.analyzer1Result.analysis, analysis2: result2.analyzer2Result.analysis, analysis3: result3.analyzer3Result.analysis, })); ``` #### `label(name: string)` / `feedback(condition: Function, labelName: string, maxIterations?: number)` Create labeled points for feedback loops. ```typescript flow .map((state) => ({ ...state, attempts: 0 })) .label("retry-point") .map((state) => ({ ...state, attempts: state.attempts + 1 })) .execute("processor", (state) => ({ input: state.userInput })) .execute("validator", (state) => ({ output: state.processorResult.output })) .feedback( (state) => !state.validatorResult.isValid && state.attempts < 3, "retry-point", ); ``` ### Advanced Methods #### `derive(outputField: string, inputField: string, transform: Function, options?: object)` Create derived fields from array or scalar inputs with parallel processing support. ```typescript // Derive from array with parallel processing flow.derive( "processedItems", "items", (item, index) => `processed-${item}-${index}`, { batchSize: 2, }, ); // Derive from scalar flow.derive("upperText", "inputText", (text) => text.toUpperCase()); ``` ## Control Flow Patterns ### 1. Sequential Processing ```typescript const sequentialFlow = new AxFlow<{ input: string }, { finalResult: string }>() .node("step1", "input:string -> intermediate:string") .node("step2", "intermediate:string -> output:string") .execute("step1", (state) => ({ input: state.input })) .execute( "step2", (state) => ({ intermediate: state.step1Result.intermediate }), ) .map((state) => ({ finalResult: state.step2Result.output })); ``` ### 2. Conditional Processing ```typescript const conditionalFlow = new AxFlow< { query: string; isComplex: boolean }, { response: string } >() .node("simpleHandler", "query:string -> response:string") .node("complexHandler", "query:string -> response:string") .branch((state) => state.isComplex) .when(true) .execute("complexHandler", (state) => ({ query: state.query })) .when(false) .execute("simpleHandler", (state) => ({ query: state.query })) .merge() .map((state) => ({ response: state.complexHandlerResult?.response || state.simpleHandlerResult?.response, })); ``` ### 3. Iterative Processing ```typescript const iterativeFlow = new AxFlow< { content: string }, { finalContent: string } >() .node("processor", "content:string -> processedContent:string") .node("qualityChecker", "content:string -> qualityScore:number") .map((state) => ({ currentContent: state.content, iteration: 0 })) .while((state) => state.iteration < 3 && (state.qualityScore || 0) < 0.8) .map((state) => ({ ...state, iteration: state.iteration + 1 })) .execute("processor", (state) => ({ content: state.currentContent })) .execute( "qualityChecker", (state) => ({ content: state.processorResult.processedContent }), ) .map((state) => ({ ...state, currentContent: state.processorResult.processedContent, qualityScore: state.qualityCheckerResult.qualityScore, })) .endWhile() .map((state) => ({ finalContent: state.currentContent })); ``` ### 4. Parallel Processing with Auto-Parallelization AxFlow automatically detects independent operations and runs them in parallel: ```typescript const autoParallelFlow = new AxFlow< { text: string }, { combinedAnalysis: string } >() .node("sentimentAnalyzer", "text:string -> sentiment:string") .node("topicExtractor", "text:string -> topics:string[]") .node("entityRecognizer", "text:string -> entities:string[]") // These three execute automatically in parallel! ⚑ .execute("sentimentAnalyzer", (state) => ({ text: state.text })) .execute("topicExtractor", (state) => ({ text: state.text })) .execute("entityRecognizer", (state) => ({ text: state.text })) // This waits for all three to complete .map((state) => ({ combinedAnalysis: JSON.stringify({ sentiment: state.sentimentAnalyzerResult.sentiment, topics: state.topicExtractorResult.topics, entities: state.entityRecognizerResult.entities, }), })); // Check execution plan const plan = autoParallelFlow.getExecutionPlan(); console.log("Parallel groups:", plan.parallelGroups); console.log("Max parallelism:", plan.maxParallelism); ``` ### 5. Asynchronous Operations AxFlow supports asynchronous transformations in map operations, enabling API calls, database queries, and other async operations within your flow: ```typescript const asyncFlow = new AxFlow< { userQuery: string }, { enrichedData: string; apiCallTime: number } >() .node("processor", "enrichedData:string -> processedResult:string") // Single async map - API enrichment .map(async (state) => { const startTime = Date.now(); const apiData = await fetchFromExternalAPI(state.userQuery); const duration = Date.now() - startTime; return { ...state, enrichedData: apiData, apiCallTime: duration, }; }) // Execute AI processing on enriched data .execute("processor", (state) => ({ enrichedData: state.enrichedData })) // Parallel async operations .map([ async (state) => { const sentiment = await analyzeSentiment(state.processedResult); return { ...state, sentiment }; }, async (state) => { const entities = await extractEntities(state.processedResult); return { ...state, entities }; }, async (state) => { const summary = await generateSummary(state.processedResult); return { ...state, summary }; }, ], { parallel: true }) .map((state) => ({ enrichedData: state.enrichedData, apiCallTime: state.apiCallTime, })); ``` #### Mixed Sync/Async Processing You can mix synchronous and asynchronous operations seamlessly: ```typescript const mixedFlow = new AxFlow<{ rawData: string }, { result: string }>() // Sync preprocessing .map((state) => ({ ...state, cleanedData: state.rawData.trim().toLowerCase(), })) // Async validation .map(async (state) => { const isValid = await validateWithAPI(state.cleanedData); return { ...state, isValid }; }) // More sync processing .map((state) => ({ ...state, timestamp: Date.now(), })) // Final async processing .map(async (state) => { if (state.isValid) { const processed = await processData(state.cleanedData); return { result: processed }; } return { result: "Invalid data" }; }); ``` #### Performance Considerations - **Parallel async maps**: Multiple async operations run concurrently - **Sequential async maps**: Each async operation waits for the previous one - **Batch control**: Use `batchSize` option to control parallelism ```typescript // Parallel execution (faster) flow.map([asyncOp1, asyncOp2, asyncOp3], { parallel: true }); // Sequential execution (slower but controlled) flow .map(asyncOp1) .map(asyncOp2) .map(asyncOp3); ``` ### 6. Self-Healing with Feedback Loops ```typescript const selfHealingFlow = new AxFlow<{ input: string }, { output: string }>() .node("processor", "input:string -> output:string, confidence:number") .node("validator", "output:string -> isValid:boolean, issues:string[]") .node("fixer", "output:string, issues:string[] -> fixedOutput:string") .map((state) => ({ ...state, attempts: 0 })) .label("process") .map((state) => ({ ...state, attempts: state.attempts + 1 })) .execute("processor", (state) => ({ input: state.input })) .execute("validator", (state) => ({ output: state.processorResult.output })) .feedback( (state) => !state.validatorResult.isValid && state.attempts < 3, "process", ) // If still invalid after retries, try to fix .branch((state) => state.validatorResult.isValid) .when(false) .execute("fixer", (state) => ({ output: state.processorResult.output, issues: state.validatorResult.issues, })) .map((state) => ({ output: state.fixerResult.fixedOutput, })) .when(true) .map((state) => ({ output: state.processorResult.output, })) .merge(); ``` ## Advanced Features ### Instrumentation and Optimization (v13.0.24+) - Deprecation: prefer `flow()` factory over `new AxFlow()`. - Tracing: pass a Tracer via `flow({ tracer })` or `flow.forward(llm, input, { tracer, traceContext })`. - A parent span is created at the flow boundary (if `tracer` is provided). - The parent span context is propagated to all node `.forward()` calls via `options.traceContext`. - Pass an OpenTelemetry Context for `traceContext` (not a Span). Use `@opentelemetry/api` `context.active()` or similar. - Meter: pass `meter` the same way as `tracer`; it is propagated to node forwards. - Demos/Examples routing: `flow.setDemos(demos)` routes by `programId` to the correct prompts, similar to DSPy. The flow maintains an internal `AxProgram` and registers all child nodes; each node filters demos by `programId`. - Optimization: `flow.applyOptimization(optimizedProgram)` applies to the flow’s internal program and all registered child nodes. - Parallel map: `flow.map([...], { parallel: true })` merges all transform outputs back into state. Example ```ts import { ai, flow } from "@ax-llm/ax"; import { context, trace } from "@opentelemetry/api"; const llm = ai({ name: "mock" }); const tracer = trace.getTracer("axflow"); const wf = flow<{ userQuestion: string }>() .node("summarizer", "documentText:string -> summaryText:string") .execute("summarizer", (s) => ({ documentText: s.userQuestion })) .returns((s) => ({ finalAnswer: (s as any).summarizerResult.summaryText })); const parentCtx = context.active(); const out = await wf.forward(llm, { userQuestion: "hi" }, { tracer, traceContext: parentCtx, }); ``` ### 1. Auto-Parallelization AxFlow automatically analyzes dependencies and runs independent operations in parallel: ```typescript // Disable auto-parallelization globally const sequentialFlow = new AxFlow({ autoParallel: false }); // Disable for specific execution const result = await flow.forward(llm, input, { autoParallel: false }); // Get execution plan information const plan = flow.getExecutionPlan(); console.log( `Will run ${plan.parallelGroups} parallel groups with max ${plan.maxParallelism} concurrent operations`, ); ``` ### 2. Dynamic AI Context Use different AI services for different nodes: ```typescript flow .execute("fastProcessor", mapping, { ai: speedAI }) .execute("powerfulAnalyzer", mapping, { ai: powerAI }) .execute("defaultProcessor", mapping); // Uses default AI from forward() ``` ### 3. Batch Processing with Derive ```typescript const batchFlow = new AxFlow<{ items: string[] }, { processedItems: string[] }>( { autoParallel: true, batchSize: 3, // Process 3 items at a time }, ) .derive("processedItems", "items", (item, index) => { return `processed-${item}-${index}`; }, { batchSize: 2 }); // Override batch size for this operation ``` ### 4. Error Handling ```typescript try { const result = await flow.forward(llm, input); } catch (error) { console.error("Flow execution failed:", error); } ``` ### 5. Program Integration AxFlow integrates with the dspy-ts ecosystem: ```typescript // Get signature const signature = flow.getSignature(); // Set examples (if applicable) flow.setExamples(examples); // Get traces and usage const traces = flow.getTraces(); const usage = flow.getUsage(); ``` ## Best Practices ### 1. Node Naming Use descriptive names that clearly indicate the node's purpose: ```typescript // ❌ Unclear flow.node("proc1", signature); // βœ… Clear flow.node("documentSummarizer", signature); flow.node("sentimentAnalyzer", signature); ``` ### 2. State Management Keep state flat and predictable: ```typescript // βœ… Good - flat structure flow.map((state) => ({ ...state, processedText: state.rawText.toLowerCase(), timestamp: Date.now(), })); // ❌ Avoid - deep nesting flow.map((state) => ({ data: { processed: { text: state.rawText.toLowerCase(), }, }, })); ``` ### 3. Error Prevention Always define nodes before executing them: ```typescript // βœ… Correct order flow .node("processor", signature) .execute("processor", mapping); // ❌ Will throw error flow .execute("processor", mapping) // Node not defined yet! .node("processor", signature); ``` ### 4. Loop Safety Ensure loop conditions can change: ```typescript // βœ… Safe - counter increments flow .map((state) => ({ ...state, counter: 0 })) .while((state) => state.counter < 5) .map((state) => ({ ...state, counter: state.counter + 1 })) // Condition changes .execute("processor", mapping) .endWhile(); // ❌ Infinite loop - condition never changes flow .while((state) => state.isProcessing) // This never changes! .execute("processor", mapping) .endWhile(); ``` ### 5. Parallel Design Structure flows to maximize automatic parallelization: ```typescript // βœ… Parallel-friendly - independent operations flow .execute("analyzer1", (state) => ({ text: state.input })) // Can run in parallel .execute("analyzer2", (state) => ({ text: state.input })) // Can run in parallel .execute("combiner", (state) => ({ // Waits for both input1: state.analyzer1Result.output, input2: state.analyzer2Result.output, })); // ❌ Sequential - unnecessary dependencies flow .execute("analyzer1", (state) => ({ text: state.input })) .execute("analyzer2", (state) => ({ text: state.input, context: state.analyzer1Result.output, // Creates dependency! })); ``` ## Examples ### Extended Node Patterns with `nx` ```typescript import { AxFlow, f } from "@ax-llm/ax"; // Chain-of-thought reasoning pattern const reasoningFlow = new AxFlow<{ question: string }, { answer: string }>() .nx("reasoner", "question:string -> answer:string", { prependOutputs: [ { name: "reasoning", type: f.internal(f.string("Step-by-step reasoning")), }, ], }) .execute("reasoner", (state) => ({ question: state.question })) .map((state) => ({ answer: state.reasonerResult.answer })); // Confidence scoring pattern const confidenceFlow = new AxFlow< { input: string }, { result: string; confidence: number } >() .nx("analyzer", "input:string -> result:string", { appendOutputs: [ { name: "confidence", type: f.number("Confidence score 0-1") }, ], }) .execute("analyzer", (state) => ({ input: state.input })) .map((state) => ({ result: state.analyzerResult.result, confidence: state.analyzerResult.confidence, })); // Contextual processing pattern const contextualFlow = new AxFlow< { query: string; context?: string }, { response: string } >() .nx("processor", "query:string -> response:string", { appendInputs: [ { name: "context", type: f.optional(f.string("Additional context")) }, ], }) .execute("processor", (state) => ({ query: state.query, context: state.context, })) .map((state) => ({ response: state.processorResult.response })); ``` ### Document Processing Pipeline ```typescript const documentPipeline = flow<{ document: string }>() .node("summarizer", "documentText:string -> summary:string") .node("sentimentAnalyzer", "documentText:string -> sentiment:string") .node("keywordExtractor", "documentText:string -> keywords:string[]") // These run automatically in parallel .execute("summarizer", (state) => ({ documentText: state.document })) .execute("sentimentAnalyzer", (state) => ({ documentText: state.document })) .execute("keywordExtractor", (state) => ({ documentText: state.document })) // Use returns() for proper type inference .returns((state) => ({ summary: state.summarizerResult.summary, sentiment: state.sentimentAnalyzerResult.sentiment, keywords: state.keywordExtractorResult.keywords, })); // TypeScript now knows the exact return type: // { summary: string; sentiment: string; keywords: string[] } const result = await documentPipeline.forward(llm, { document: "..." }); console.log(result.summary); // Fully typed console.log(result.sentiment); // Fully typed console.log(result.keywords); // Fully typed ``` ### Quality-Driven Content Creation ```typescript const contentCreator = new AxFlow< { topic: string; targetQuality: number }, { finalContent: string; iterations: number } >() .node("writer", "topic:string -> content:string") .node("qualityChecker", "content:string -> score:number, feedback:string") .node("improver", "content:string, feedback:string -> improvedContent:string") .map((state) => ({ currentContent: "", iteration: 0, bestScore: 0 })) // Initial writing .execute("writer", (state) => ({ topic: state.topic })) .map((state) => ({ ...state, currentContent: state.writerResult.content, iteration: 1, })) // Improvement loop .while((state) => state.iteration < 5 && state.bestScore < state.targetQuality ) .execute("qualityChecker", (state) => ({ content: state.currentContent })) .branch((state) => state.qualityCheckerResult.score > state.bestScore) .when(true) .execute("improver", (state) => ({ content: state.currentContent, feedback: state.qualityCheckerResult.feedback, })) .map((state) => ({ ...state, currentContent: state.improverResult.improvedContent, bestScore: state.qualityCheckerResult.score, iteration: state.iteration + 1, })) .when(false) .map((state) => ({ ...state, iteration: 5 })) // Exit loop .merge() .endWhile() .map((state) => ({ finalContent: state.currentContent, iterations: state.iteration, })); ``` ### Async Data Enrichment Pipeline ```typescript const enrichmentPipeline = new AxFlow< { userQuery: string }, { finalResult: string; metadata: object } >() .node("analyzer", "enrichedData:string -> analysis:string") // Parallel async data enrichment from multiple sources .map([ async (state) => { const userProfile = await fetchUserProfile(state.userQuery); return { ...state, userProfile }; }, async (state) => { const contextData = await fetchContextData(state.userQuery); return { ...state, contextData }; }, async (state) => { const historicalData = await fetchHistoricalData(state.userQuery); return { ...state, historicalData }; }, ], { parallel: true }) // Combine enriched data .map(async (state) => { const combinedData = await combineDataSources({ userProfile: state.userProfile, context: state.contextData, historical: state.historicalData, }); return { ...state, enrichedData: combinedData, metadata: { sources: ["userProfile", "contextData", "historicalData"], timestamp: Date.now(), }, }; }) // Process with AI .execute("analyzer", (state) => ({ enrichedData: state.enrichedData })) // Final async post-processing .map(async (state) => { const enhanced = await enhanceResult(state.analyzerResult.analysis); return { finalResult: enhanced, metadata: state.metadata, }; }); ``` ### Real-time Data Processing with Async Maps ```typescript const realTimeProcessor = new AxFlow< { streamData: string[] }, { processedResults: string[]; stats: object } >() // Async preprocessing of each item in parallel .map(async (state) => { const startTime = Date.now(); // Process each item in batches with async operations const processedItems = await Promise.all( state.streamData.map(async (item, index) => { const enriched = await enrichDataItem(item); const validated = await validateItem(enriched); return { item: validated, index, timestamp: Date.now() }; }), ); const processingTime = Date.now() - startTime; return { ...state, processedItems, processingStats: { totalItems: state.streamData.length, processingTime, itemsPerSecond: state.streamData.length / (processingTime / 1000), }, }; }) // Parallel async quality checks .map([ async (state) => { const qualityScore = await calculateQualityScore(state.processedItems); return { ...state, qualityScore }; }, async (state) => { const anomalies = await detectAnomalies(state.processedItems); return { ...state, anomalies }; }, async (state) => { const trends = await analyzeTrends(state.processedItems); return { ...state, trends }; }, ], { parallel: true }) // Final aggregation .map((state) => ({ processedResults: state.processedItems.map((item) => item.item), stats: { ...state.processingStats, qualityScore: state.qualityScore, anomaliesFound: state.anomalies?.length || 0, trendsDetected: state.trends?.length || 0, }, })); ``` ### Multi-Model Research System ```typescript const researchSystem = new AxFlow< { query: string }, { answer: string; sources: string[]; confidence: number } >() .node("queryGenerator", "researchQuestion:string -> searchQuery:string") .node("retriever", "searchQuery:string -> retrievedDocument:string") .node( "answerGenerator", "retrievedDocument:string, researchQuestion:string -> researchAnswer:string", ) .execute("queryGenerator", (state) => ({ researchQuestion: state.query })) .execute( "retriever", (state) => ({ searchQuery: state.queryGeneratorResult.searchQuery }), ) .execute("answerGenerator", (state) => ({ retrievedDocument: state.retrieverResult.retrievedDocument, researchQuestion: state.query, })) .map((state) => ({ answer: state.answerGeneratorResult.researchAnswer, sources: [state.retrieverResult.retrievedDocument], confidence: 0.85, })); ``` ## Troubleshooting ### Common Errors 1. **"Node 'nodeName' not found"** - Ensure you call `.node()` before `.execute()` 2. **"endWhile() called without matching while()"** - Every `.while()` needs a matching `.endWhile()` 3. **"when() called without matching branch()"** - Every `.when()` needs to be inside a `.branch()` / `.merge()` block 4. **"merge() called without matching branch()"** - Every `.branch()` needs a matching `.merge()` 5. **"Label 'labelName' not found"** - Ensure the label exists before using it in `.feedback()` ### Performance Issues 1. **Operations running sequentially instead of parallel** - Check for unnecessary dependencies in your mappings - Use `flow.getExecutionPlan()` to debug 2. **Memory issues with large datasets** - Use `batchSize` option to control parallel execution - Consider using `.derive()` for array processing ### Type Errors 1. **State property not found** - Use `.map()` to ensure required properties exist - Check the spelling of result field names (`{nodeName}Result`) This documentation provides a comprehensive guide to AxFlow based on the actual implementation and test cases. All examples have been verified against the test suite to ensure accuracy. ================================================================================ # Telemetry Guide # Source: TELEMETRY.md # Observability and monitoring for Ax applications # Telemetry Guide **🎯 Goal**: Learn how to monitor, trace, and observe your AI applications with industry-standard OpenTelemetry integration. **⏱️ Time to first results**: 5 minutes\ **πŸ” Value**: Understand performance, debug issues, and optimize costs with comprehensive observability ## πŸ“‹ Table of Contents - [What is Telemetry in Ax?](#what-is-telemetry-in-ax) - [πŸš€ 5-Minute Quick Start](#-5-minute-quick-start) ← **Start here!** - [πŸ“Š Metrics Overview](#-metrics-overview) - [πŸ” Tracing Overview](#-tracing-overview) - [🎯 Common Observability Patterns](#-common-observability-patterns) - [πŸ—οΈ Production Setup](#️-production-setup) - [⚑ Advanced Configuration](#-advanced-configuration) - [πŸ› οΈ Troubleshooting Guide](#️-troubleshooting-guide) - [πŸŽ“ Best Practices](#-best-practices) - [πŸ“– Complete Examples](#-complete-examples) - [🎯 Key Takeaways](#-key-takeaways) --- ## What is Telemetry in Ax? Think of telemetry as **X-ray vision for your AI applications**. Instead of guessing what's happening, you get: - **Real-time metrics** on performance, costs, and usage - **Distributed tracing** to follow requests through your entire AI pipeline - **Automatic instrumentation** of all LLM operations, vector databases, and function calls - **Industry-standard OpenTelemetry** integration for any observability platform - **Zero-configuration** setup that works out of the box **Real example**: A production AI system that went from "it's slow sometimes" to "we can see exactly which model calls are taking 3+ seconds and why." --- ### Step 1: Basic Setup with Console Export ```typescript import { ax, AxAI, f } from "@ax-llm/ax"; import { metrics, trace } from "@opentelemetry/api"; import { BasicTracerProvider, ConsoleSpanExporter, SimpleSpanProcessor, } from "@opentelemetry/sdk-trace-base"; import { ConsoleMetricExporter, MeterProvider, PeriodicExportingMetricReader, } from "@opentelemetry/sdk-metrics"; // Set up basic tracing const tracerProvider = new BasicTracerProvider(); tracerProvider.addSpanProcessor( new SimpleSpanProcessor(new ConsoleSpanExporter()), ); trace.setGlobalTracerProvider(tracerProvider); // Set up basic metrics const meterProvider = new MeterProvider({ readers: [ new PeriodicExportingMetricReader({ exporter: new ConsoleMetricExporter(), exportIntervalMillis: 5000, }), ], }); metrics.setGlobalMeterProvider(meterProvider); // Get your tracer and meter const tracer = trace.getTracer("my-ai-app"); const meter = metrics.getMeter("my-ai-app"); ``` ### Step 2: Create AI with Telemetry ```typescript // Create AI instance with telemetry enabled const ai = new AxAI({ name: "openai", apiKey: process.env.OPENAI_APIKEY!, config: { model: "gpt-4o-mini" }, options: { tracer, meter, debug: true, // Enable detailed logging }, }); // Create a simple generator const sentimentAnalyzer = ax` reviewText:${f.string("Customer review")} -> sentiment:${f.class(["positive", "negative", "neutral"], "Sentiment")} `; ``` ### Step 3: Run and Observe ```typescript // This will automatically generate traces and metrics const result = await sentimentAnalyzer.forward(ai, { reviewText: "This product is amazing! I love it!", }); console.log("Result:", result.sentiment); ``` **οΏ½οΏ½ Congratulations!** You now have full observability. Check your console for: - **Traces**: Complete request flow with timing and metadata - **Metrics**: Performance counters, histograms, and gauges - **Logs**: Detailed debug information --- ## πŸ“Š Metrics Overview Ax automatically tracks comprehensive metrics across all operations. Here's what you get: ### πŸ€– AI Service Metrics **Request Metrics** - `ax_llm_requests_total` - Total requests by service/model - `ax_llm_request_duration_ms` - Request latency distribution - `ax_llm_errors_total` - Error counts by type - `ax_llm_error_rate` - Current error rate percentage **Token Usage** - `ax_llm_tokens_total` - Total tokens consumed - `ax_llm_input_tokens_total` - Input/prompt tokens - `ax_llm_output_tokens_total` - Output/completion tokens - `ax_llm_thinking_budget_usage_total` - Thinking tokens used **Cost & Performance** - `ax_llm_estimated_cost_total` - Estimated costs in USD - `ax_llm_request_size_bytes` - Request payload sizes - `ax_llm_response_size_bytes` - Response payload sizes - `ax_llm_context_window_usage_ratio` - Context window utilization **Streaming & Functions** - `ax_llm_streaming_requests_total` - Streaming request count - `ax_llm_function_calls_total` - Function call counts - `ax_llm_function_call_latency_ms` - Function call timing ### 🧠 AxGen Metrics **Generation Flow** - `ax_gen_generation_requests_total` - Total generation requests - `ax_gen_generation_duration_ms` - End-to-end generation time - `ax_gen_generation_errors_total` - Generation failures **Multi-Step Processing** - `ax_gen_multistep_generations_total` - Multi-step generations - `ax_gen_steps_per_generation` - Steps taken per generation - `ax_gen_max_steps_reached_total` - Max steps limit hits **Error Correction** - `ax_gen_validation_errors_total` - Validation failures - `ax_gen_assertion_errors_total` - Assertion failures - `ax_gen_error_correction_attempts` - Retry attempts - `ax_gen_error_correction_success_total` - Successful corrections **Function Integration** - `ax_gen_functions_enabled_generations_total` - Function-enabled requests - `ax_gen_function_call_steps_total` - Steps with function calls - `ax_gen_functions_executed_per_generation` - Functions per generation ### πŸ”§ Optimizer Metrics **Optimization Flow** - `ax_optimizer_optimization_requests_total` - Total optimization requests - `ax_optimizer_optimization_duration_ms` - End-to-end optimization time - `ax_optimizer_optimization_errors_total` - Optimization failures **Convergence Tracking** - `ax_optimizer_convergence_rounds` - Rounds until convergence - `ax_optimizer_convergence_score` - Current best score - `ax_optimizer_convergence_improvement` - Score improvement from baseline - `ax_optimizer_stagnation_rounds` - Rounds without improvement - `ax_optimizer_early_stopping_total` - Early stopping events **Resource Usage** - `ax_optimizer_token_usage_total` - Total tokens used during optimization - `ax_optimizer_cost_usage_total` - Total cost incurred - `ax_optimizer_memory_usage_bytes` - Peak memory usage - `ax_optimizer_duration_ms` - Optimization duration **Teacher-Student Interactions** - `ax_optimizer_teacher_student_usage_total` - Teacher-student interactions - `ax_optimizer_teacher_student_latency_ms` - Interaction latency - `ax_optimizer_teacher_student_score_improvement` - Score improvement from teacher **Checkpointing** - `ax_optimizer_checkpoint_save_total` - Checkpoint saves - `ax_optimizer_checkpoint_load_total` - Checkpoint loads - `ax_optimizer_checkpoint_save_latency_ms` - Save operation latency - `ax_optimizer_checkpoint_load_latency_ms` - Load operation latency **Pareto Optimization** - `ax_optimizer_pareto_optimizations_total` - Pareto optimization runs - `ax_optimizer_pareto_front_size` - Size of Pareto frontier - `ax_optimizer_pareto_hypervolume` - Hypervolume of Pareto frontier - `ax_optimizer_pareto_solutions_generated` - Solutions generated **Program Complexity** - `ax_optimizer_program_input_fields` - Input fields in optimized program - `ax_optimizer_program_output_fields` - Output fields in optimized program - `ax_optimizer_examples_count` - Training examples used - `ax_optimizer_validation_set_size` - Validation set size ### πŸ“Š Database Metrics **Vector Operations** - `db_operations_total` - Total DB operations - `db_query_duration_ms` - Query latency - `db_upsert_duration_ms` - Upsert latency - `db_vector_dimensions` - Vector dimensions ### πŸ“ˆ Example Metrics Output ```json { "name": "ax_llm_request_duration_ms", "description": "Duration of LLM requests in milliseconds", "unit": "ms", "data": { "resourceMetrics": [{ "scopeMetrics": [{ "metrics": [{ "name": "ax_llm_request_duration_ms", "histogram": { "dataPoints": [{ "attributes": { "operation": "chat", "ai_service": "openai", "model": "gpt-4o-mini" }, "sum": 2450.5, "count": 10, "bounds": [1, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000], "bucketCounts": [0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 2] }] } }] }] }] } } ``` --- ## πŸ” Tracing Overview Ax provides comprehensive distributed tracing following OpenTelemetry standards and the new `gen_ai` semantic conventions. ### 🎯 Trace Structure **Root Spans** - `Chat Request` - Complete chat completion flow - `AI Embed Request` - Embedding generation - `AxGen` - AxGen generation pipeline - `DB Query Request` - Vector database operations **Child Spans** - `API Call` - HTTP requests to AI providers - `Function Call` - Tool/function execution - `Validation` - Response validation - `Extraction` - Value extraction from responses ### πŸ“‹ Standard Attributes **LLM Attributes** (`gen_ai.*`) ```typescript { 'gen_ai.system': 'openai', 'gen_ai.operation.name': 'chat', 'gen_ai.request.model': 'gpt-4o-mini', 'gen_ai.request.max_tokens': 500, 'gen_ai.request.temperature': 0.1, 'gen_ai.request.llm_is_streaming': false, 'gen_ai.usage.input_tokens': 150, 'gen_ai.usage.output_tokens': 200, 'gen_ai.usage.total_tokens': 350 } ``` **Database Attributes** (`db.*`) ```typescript { 'db.system': 'weaviate', 'db.operation.name': 'query', 'db.table': 'documents', 'db.namespace': 'default', 'db.vector.query.top_k': 10 } ``` **Custom Ax Attributes** ```typescript { 'signature': 'JSON representation of signature', 'examples': 'JSON representation of examples', 'provided_functions': 'function1,function2', 'thinking_token_budget': 'low', 'show_thoughts': true, 'max_steps': 5, 'max_retries': 3 } ``` ### πŸ“Š Standard Events **Message Events** - `gen_ai.user.message` - User input content - `gen_ai.system.message` - System prompt content - `gen_ai.assistant.message` - Assistant response content - `gen_ai.tool.message` - Function call results **Usage Events** - `gen_ai.usage` - Token usage information - `gen_ai.choice` - Response choices ### πŸ“ˆ Example Trace Output ```json { "traceId": "ddc7405e9848c8c884e53b823e120845", "name": "Chat Request", "id": "d376daad21da7a3c", "kind": "SERVER", "timestamp": 1716622997025000, "duration": 14190456.542, "attributes": { "gen_ai.system": "openai", "gen_ai.operation.name": "chat", "gen_ai.request.model": "gpt-4o-mini", "gen_ai.request.max_tokens": 500, "gen_ai.request.temperature": 0.1, "gen_ai.request.llm_is_streaming": false, "gen_ai.usage.input_tokens": 150, "gen_ai.usage.output_tokens": 200, "gen_ai.usage.total_tokens": 350 }, "events": [ { "name": "gen_ai.user.message", "timestamp": 1716622997025000, "attributes": { "content": "What is the capital of France?" } }, { "name": "gen_ai.assistant.message", "timestamp": 1716622997025000, "attributes": { "content": "The capital of France is Paris." } } ] } ``` ### πŸ› οΈ Function/Tool Call Tracing Ax traces each function/tool invocation as a child span of the active generation (AxGen) span. This works for both native function calling and signature prompt-mode tool routing. What you get automatically when a tracer is configured: - A child span per tool call with clear parent-child relationships - Standardized attributes and events - Content redaction that respects `excludeContentFromTrace` Span naming and attributes ```typescript // Span name // Tool: // Standard attributes (examples) { 'tool.name': '', 'tool.mode': 'native' | 'prompt', 'function.id': '', 'session.id': '' // Optionally, inherited or added context like gen_ai.system/model from parent } ``` Events ```typescript // On success { name: 'gen_ai.tool.message', attributes: { args?: string, // omitted when excludeContentFromTrace=true result?: string // omitted when excludeContentFromTrace=true } } // On error { name: 'function.error', attributes: { name: '', message: '', fixing_instructions?: '' // when available } } ``` Prompt-mode vs native - Native: `tool.mode='native'` spans are created when the provider returns a function call. - Prompt-mode: `tool.mode='prompt'` spans are created when signature-injected tool fields trigger execution. Developer guidance - Do not pass tracer or meter into your tool functions. Ax starts spans around your tool handler and relies on OpenTelemetry context propagation. - If you need correlation inside your function, use `trace.getActiveSpan()` or read `extra.sessionId`/`extra.traceId` parameters your handler already receives. Modern example with factory functions ```ts import { ai, ax } from "@ax-llm/ax"; const llm = ai({ name: "openai", apiKey: process.env.OPENAI_APIKEY! }); const summarize = ax( 'documentText:string "Text to summarize" -> summaryText:string "Summary"', ); // Provide tools; Ax will create a child span per call const tools = [ { name: "fetchUrl", description: "Fetches content from a URL", parameters: { type: "object", properties: { url: { type: "string", description: "The URL to fetch" }, }, required: ["url"], }, async func(args) { const res = await fetch(args.url); return await res.text(); }, }, ]; const result = await summarize.forward(llm, { documentText: "..." }, { functions: tools, functionCallMode: "auto", // native when supported, prompt-mode otherwise }); ``` --- ## 🎯 Common Observability Patterns ### 1. Performance Monitoring ```typescript // Monitor latency percentiles const ai = new AxAI({ name: "openai", apiKey: process.env.OPENAI_APIKEY!, options: { tracer, meter, // Custom latency thresholds timeout: 30000, }, }); // Set up alerts on high latency // P95 > 5s, P99 > 10s ``` ### 2. Cost Tracking ```typescript // Track costs by model and operation const costOptimizer = new AxAI({ name: "openai", apiKey: process.env.OPENAI_APIKEY!, config: { model: "gpt-4o-mini" }, // Cheaper model options: { tracer, meter }, }); // Monitor estimated costs // Alert when daily spend > $100 ``` ### 3. Error Rate Monitoring ```typescript // Track error rates by service const reliableAI = new AxAI({ name: "openai", apiKey: process.env.OPENAI_APIKEY!, options: { tracer, meter, // Retry configuration maxRetries: 3, retryDelay: 1000, }, }); // Set up alerts on error rate > 5% ``` ### 4. Function Call Monitoring ```typescript // Monitor function call success rates const functionAI = new AxAI({ name: "openai", apiKey: process.env.OPENAI_APIKEY!, options: { tracer, meter }, }); // Track function call latency and success rates // Alert on function call failures ``` ### 5. Streaming Performance ```typescript // Monitor streaming response times const streamingAI = new AxAI({ name: "openai", apiKey: process.env.OPENAI_APIKEY!, config: { stream: true }, options: { tracer, meter }, }); // Track time to first token // Monitor streaming completion rates ``` --- ## πŸ—οΈ Production Setup ### 1. Jaeger Tracing Setup ```typescript import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"; import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base"; // Start Jaeger locally // docker run --rm --name jaeger -p 16686:16686 -p 4318:4318 jaegertracing/jaeger:2.6.0 const otlpExporter = new OTLPTraceExporter({ url: "http://localhost:4318/v1/traces", }); const provider = new BasicTracerProvider({ spanProcessors: [new BatchSpanProcessor(otlpExporter)], resource: defaultResource().merge( resourceFromAttributes({ "service.name": "my-ai-app", "service.version": "1.0.0", }), ), }); trace.setGlobalTracerProvider(provider); ``` ### 2. Prometheus Metrics Setup ```typescript import { PrometheusExporter } from "@opentelemetry/exporter-prometheus"; const prometheusExporter = new PrometheusExporter({ port: 9464, endpoint: "/metrics", }); const meterProvider = new MeterProvider({ readers: [ new PeriodicExportingMetricReader({ exporter: prometheusExporter, exportIntervalMillis: 1000, }), ], }); metrics.setGlobalMeterProvider(meterProvider); ``` ### 3. Cloud Observability Setup ```typescript // For AWS X-Ray, Google Cloud Trace, Azure Monitor, etc. import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"; import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http"; const cloudExporter = new OTLPTraceExporter({ url: "https://your-observability-endpoint.com/v1/traces", headers: { "Authorization": `Bearer ${process.env.OBSERVABILITY_API_KEY}`, }, }); const cloudMetricsExporter = new OTLPMetricExporter({ url: "https://your-observability-endpoint.com/v1/metrics", headers: { "Authorization": `Bearer ${process.env.OBSERVABILITY_API_KEY}`, }, }); ``` ### 4. Environment-Specific Configuration ```typescript // config/telemetry.ts export const setupTelemetry = (environment: "development" | "production") => { if (environment === "development") { // Console export for local development const consoleExporter = new ConsoleSpanExporter(); const provider = new BasicTracerProvider({ spanProcessors: [new SimpleSpanProcessor(consoleExporter)], }); trace.setGlobalTracerProvider(provider); } else { // Production setup with sampling and batching const otlpExporter = new OTLPTraceExporter({ url: process.env.OTLP_ENDPOINT!, }); const provider = new BasicTracerProvider({ spanProcessors: [ new BatchSpanProcessor(otlpExporter, { maxQueueSize: 2048, maxExportBatchSize: 512, scheduledDelayMillis: 5000, }), ], }); trace.setGlobalTracerProvider(provider); } }; ``` --- ## ⚑ Advanced Configuration ### 1. Custom Metrics ```typescript // Create custom business metrics const customMeter = metrics.getMeter("business-metrics"); const customCounter = customMeter.createCounter("business_operations_total", { description: "Total business operations", }); // Record custom metrics customCounter.add(1, { operation_type: "sentiment_analysis", customer_tier: "premium", }); ``` ### 2. Custom Spans ```typescript // Create custom spans for business logic const tracer = trace.getTracer("business-logic"); const processOrder = async (orderId: string) => { return await tracer.startActiveSpan( "Process Order", { attributes: { "order.id": orderId, "business.operation": "order_processing", }, }, async (span) => { try { // Your business logic here const result = await ai.chat({/* ... */}); span.setAttributes({ "order.status": "completed", "order.value": result.total, }); return result; } catch (error) { span.recordException(error); span.setAttributes({ "order.status": "failed" }); throw error; } finally { span.end(); } }, ); }; ``` ### 3. Sampling Configuration ```typescript // Configure sampling for high-traffic applications import { ParentBasedSampler, TraceIdRatioBasedSampler, } from "@opentelemetry/sdk-trace-base"; const sampler = new ParentBasedSampler({ root: new TraceIdRatioBasedSampler(0.1), // Sample 10% of traces }); const provider = new BasicTracerProvider({ sampler, spanProcessors: [new BatchSpanProcessor(otlpExporter)], }); ``` ### 4. Metrics Configuration ```typescript // Configure metrics collection import { axUpdateMetricsConfig, axUpdateOptimizerMetricsConfig, } from "@ax-llm/ax"; // Configure DSPy metrics axUpdateMetricsConfig({ enabled: true, enabledCategories: [ "generation", "streaming", "functions", "errors", "performance", ], maxLabelLength: 100, samplingRate: 1.0, // Collect all metrics }); // Configure optimizer metrics axUpdateOptimizerMetricsConfig({ enabled: true, enabledCategories: [ "optimization", "convergence", "resource_usage", "teacher_student", "checkpointing", "pareto", ], maxLabelLength: 100, samplingRate: 1.0, }); ``` ### 5. Optimizer Metrics Usage ```typescript // Optimizer metrics are automatically collected when using optimizers import { AxBootstrapFewShot } from "@ax-llm/ax"; const optimizer = new AxBootstrapFewShot({ studentAI: ai, examples: trainingExamples, validationSet: validationExamples, targetScore: 0.9, verbose: true, options: { maxRounds: 5, }, }); // Metrics are automatically recorded during optimization const result = await optimizer.compile(program, metricFn); // Check optimization metrics console.log("Optimization duration:", result.stats.resourceUsage.totalTime); console.log("Total tokens used:", result.stats.resourceUsage.totalTokens); console.log("Convergence info:", result.stats.convergenceInfo); ``` ### 6. Global Telemetry Setup ```typescript // Set up global telemetry for all Ax operations import { axGlobals } from "@ax-llm/ax"; // Global tracer axGlobals.tracer = trace.getTracer("global-ax-tracer"); // Global meter axGlobals.meter = metrics.getMeter("global-ax-meter"); // Now all Ax operations will use these by default const ai = new AxAI({ name: "openai", apiKey: process.env.OPENAI_APIKEY!, // No need to specify tracer/meter - uses globals }); ``` --- ## πŸ› οΈ Troubleshooting Guide ### Common Issues **1. No traces appearing** ```typescript // Check if tracer is properly configured console.log("Tracer:", trace.getTracer("test")); console.log("Provider:", trace.getTracerProvider()); // Ensure spans are being created const span = tracer.startSpan("test"); span.end(); ``` **2. Metrics not updating** ```typescript // Check meter configuration console.log("Meter:", metrics.getMeter("test")); console.log("Provider:", metrics.getMeterProvider()); // Verify metric collection const testCounter = meter.createCounter("test_counter"); testCounter.add(1); ``` **3. High memory usage** ```typescript // Reduce metric cardinality axUpdateMetricsConfig({ maxLabelLength: 50, // Shorter labels samplingRate: 0.1, // Sample 10% of metrics }); // Use batch processing for spans const batchProcessor = new BatchSpanProcessor(exporter, { maxQueueSize: 1024, // Smaller queue maxExportBatchSize: 256, // Smaller batches }); ``` **4. Slow performance** ```typescript // Use async exporters const asyncExporter = new OTLPTraceExporter({ url: "http://localhost:4318/v1/traces", timeoutMillis: 30000, }); // Configure appropriate sampling const sampler = new TraceIdRatioBasedSampler(0.01); // Sample 1% ``` ### Debug Mode ```typescript // Enable debug mode for detailed logging const ai = new AxAI({ name: "openai", apiKey: process.env.OPENAI_APIKEY!, options: { debug: true, // Detailed logging tracer, meter, }, }); // Check debug output for telemetry information ``` --- ## πŸŽ“ Best Practices ### 1. Naming Conventions ```typescript // Use consistent naming for tracers and meters const tracer = trace.getTracer("my-app.ai-service"); const meter = metrics.getMeter("my-app.ai-service"); // Use descriptive span names const span = tracer.startSpan("Sentiment Analysis Request"); ``` ### 2. Attribute Management ```typescript // Use standard attributes when possible span.setAttributes({ "gen_ai.system": "openai", "gen_ai.operation.name": "chat", "gen_ai.request.model": "gpt-4o-mini", }); // Add business context span.setAttributes({ "business.customer_id": customerId, "business.operation_type": "sentiment_analysis", }); ``` ### 3. Error Handling ```typescript // Always record exceptions in spans try { const result = await ai.chat(request); return result; } catch (error) { span.recordException(error); span.setAttributes({ "error.type": error.name }); throw error; } finally { span.end(); } ``` ### 4. Performance Optimization ```typescript // Use batch processing for high-volume applications const batchProcessor = new BatchSpanProcessor(exporter, { maxQueueSize: 2048, maxExportBatchSize: 512, scheduledDelayMillis: 5000, }); // Configure appropriate sampling const sampler = new ParentBasedSampler({ root: new TraceIdRatioBasedSampler(0.1), // 10% sampling }); ``` ### 5. Security Considerations ```typescript // Exclude sensitive content from traces const ai = new AxAI({ name: "openai", apiKey: process.env.OPENAI_APIKEY!, options: { excludeContentFromTrace: true, // Don't log prompt content tracer, }, }); // Use secure headers for cloud exporters const secureExporter = new OTLPTraceExporter({ url: process.env.OTLP_ENDPOINT!, headers: { "Authorization": `Bearer ${process.env.API_KEY}`, }, }); ``` --- ## πŸ“– Complete Examples ### 1. Full Production Setup ```typescript // examples/production-telemetry.ts import { ax, AxAI, f } from "@ax-llm/ax"; import { metrics, trace } from "@opentelemetry/api"; import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"; import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http"; import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base"; import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics"; // Production telemetry setup const setupProductionTelemetry = () => { // Tracing setup const traceExporter = new OTLPTraceExporter({ url: process.env.OTLP_TRACE_ENDPOINT!, headers: { "Authorization": `Bearer ${process.env.OTLP_API_KEY}` }, }); const traceProvider = new BasicTracerProvider({ spanProcessors: [new BatchSpanProcessor(traceExporter)], }); trace.setGlobalTracerProvider(traceProvider); // Metrics setup const metricExporter = new OTLPMetricExporter({ url: process.env.OTLP_METRIC_ENDPOINT!, headers: { "Authorization": `Bearer ${process.env.OTLP_API_KEY}` }, }); const meterProvider = new MeterProvider({ readers: [ new PeriodicExportingMetricReader({ exporter: metricExporter, exportIntervalMillis: 10000, }), ], }); metrics.setGlobalMeterProvider(meterProvider); }; // Initialize telemetry setupProductionTelemetry(); // Create AI with telemetry const ai = new AxAI({ name: "openai", apiKey: process.env.OPENAI_APIKEY!, config: { model: "gpt-4o-mini" }, options: { tracer: trace.getTracer("production-ai"), meter: metrics.getMeter("production-ai"), debug: process.env.NODE_ENV === "development", }, }); // Create generator const sentimentAnalyzer = ax` reviewText:${f.string("Customer review")} -> sentiment:${f.class(["positive", "negative", "neutral"], "Sentiment")}, confidence:${f.number("Confidence score 0-1")} `; // Usage with full observability export const analyzeSentiment = async (review: string) => { const result = await sentimentAnalyzer.forward(ai, { reviewText: review }); return result; }; ``` ### 2. Multi-Service Tracing ```typescript // examples/multi-service-tracing.ts import { AxAI, AxFlow } from "@ax-llm/ax"; import { trace } from "@opentelemetry/api"; const tracer = trace.getTracer("multi-service"); // Create AI services const fastAI = new AxAI({ name: "openai", apiKey: process.env.OPENAI_APIKEY!, config: { model: "gpt-4o-mini" }, options: { tracer }, }); const powerfulAI = new AxAI({ name: "openai", apiKey: process.env.OPENAI_APIKEY!, config: { model: "gpt-4o" }, options: { tracer }, }); // Create multi-service workflow const documentProcessor = new AxFlow< { document: string }, { summary: string; analysis: string } >() .n("summarizer", "documentText:string -> summary:string") .n("analyzer", "documentText:string -> analysis:string") .e("summarizer", (s) => ({ documentText: s.document }), { ai: fastAI }) .e("analyzer", (s) => ({ documentText: s.document }), { ai: powerfulAI }) .m((s) => ({ summary: s.summarizerResult.summary, analysis: s.analyzerResult.analysis, })); // Each step gets its own span with proper parent-child relationships export const processDocument = async (document: string) => { return await documentProcessor.forward(fastAI, { document }); }; ``` ### 3. Custom Business Metrics ```typescript // examples/custom-business-metrics.ts import { ax, AxAI, f } from "@ax-llm/ax"; import { metrics } from "@opentelemetry/api"; const meter = metrics.getMeter("business-metrics"); // Create custom business metrics const customerSatisfactionGauge = meter.createGauge( "customer_satisfaction_score", { description: "Customer satisfaction score", }, ); const orderProcessingHistogram = meter.createHistogram( "order_processing_duration_ms", { description: "Order processing time", unit: "ms", }, ); const ai = new AxAI({ name: "openai", apiKey: process.env.OPENAI_APIKEY!, options: { meter }, }); const orderAnalyzer = ax` orderText:${f.string("Order description")} -> category:${f.class(["urgent", "normal", "low"], "Priority")}, estimatedTime:${f.number("Estimated processing time in hours")} `; export const processOrder = async (orderText: string) => { const startTime = performance.now(); try { const result = await orderAnalyzer.forward(ai, { orderText }); // Record business metrics const processingTime = performance.now() - startTime; orderProcessingHistogram.record(processingTime, { category: result.category, }); // Update satisfaction score based on processing time const satisfactionScore = processingTime < 1000 ? 0.9 : 0.7; customerSatisfactionGauge.record(satisfactionScore, { order_type: result.category, }); return result; } catch (error) { // Record error metrics customerSatisfactionGauge.record(0.0, { order_type: "error", }); throw error; } }; ``` --- ## 🎯 Key Takeaways ### βœ… What You've Learned 1. **Complete Observability**: Ax provides comprehensive metrics and tracing out of the box 2. **Industry Standards**: Uses OpenTelemetry and `gen_ai` semantic conventions 3. **Zero Configuration**: Works immediately with minimal setup 4. **Production Ready**: Scales from development to enterprise environments 5. **Cost Optimization**: Track usage and costs to optimize spending ### πŸš€ Next Steps 1. **Start Simple**: Begin with console export for development 2. **Add Production**: Set up cloud observability for production 3. **Custom Metrics**: Add business-specific metrics 4. **Alerting**: Set up alerts on key metrics 5. **Optimization**: Use data to optimize performance and costs ### πŸ“š Resources - [OpenTelemetry Documentation](https://opentelemetry.io/docs/) - [Gen AI Semantic Conventions](https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai/) - [Ax Examples](https://github.com/ax-llm/ax/tree/main/src/examples) - [Telemetry Example](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/telemetry.ts) - [Metrics Export Example](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/metrics-export.ts) ### πŸŽ‰ You're Ready! You now have the knowledge to build observable, production-ready AI applications with Ax. Start with the quick setup, add production telemetry, and watch your AI systems become transparent and optimizable! --- _Need help? Check out the [Ax documentation](https://ax-llm.com) or join our [community](https://github.com/ax-llm/ax/discussions)._ ================================================================================ # AxRAG Guide # Source: AXRAG.md # Advanced RAG with multi-hop retrieval and self-healing quality loops # Advanced RAG with AxFlow: `axRAG` **`axRAG`** is a powerful, production-ready RAG (Retrieval-Augmented Generation) implementation built on AxFlow that provides advanced multi-hop retrieval, self-healing quality loops, and intelligent query refinement. ## Key Features - **πŸ” Multi-hop Retrieval**: Iteratively refines queries and accumulates context across multiple retrieval rounds - **🧠 Intelligent Query Generation**: AI-powered query expansion and refinement based on previous context - **πŸ”„ Self-healing Quality Loops**: Automatically improves answers through quality assessment and iterative healing - **⚑ Parallel Sub-query Processing**: Breaks down complex questions into parallel sub-queries for comprehensive coverage - **🎯 Gap Analysis**: Identifies missing information and generates focused follow-up queries - **πŸ₯ Answer Healing**: Retrieves additional context to address quality issues and improve final answers - **πŸ“Š Configurable Quality Thresholds**: Fine-tune performance vs. thoroughness trade-offs - **πŸ› Debug Mode**: Built-in logging to visualize the entire RAG pipeline execution ## Basic Usage ```typescript import { AxAI, axRAG } from "@ax-llm/ax"; const llm = ai({ name: "openai", apiKey: process.env.OPENAI_APIKEY, }); // Your vector database query function const queryVectorDB = async (query: string): Promise => { // Connect to your vector database (Pinecone, Weaviate, etc.) // Return relevant context for the query return await yourVectorDB.query(query); }; // Create a powerful RAG pipeline const rag = axRAG(queryVectorDB, { maxHops: 3, // Maximum retrieval iterations qualityThreshold: 0.8, // Quality score threshold (0-1) maxIterations: 2, // Max parallel sub-query iterations qualityTarget: 0.85, // Target quality for healing loops debug: true // Enable detailed logging }); // Execute RAG with complex question const result = await rag.forward(llm, { originalQuestion: "How do machine learning algorithms impact privacy in financial services?" }); console.log("Answer:", result.finalAnswer); console.log("Quality:", result.qualityAchieved); console.log("Sources:", result.retrievedContexts.length); console.log("Hops:", result.totalHops); console.log("Healing attempts:", result.healingAttempts); ``` ## Advanced Configuration ```typescript // Production-ready RAG with full configuration const advancedRAG = axRAG(queryVectorDB, { // Multi-hop retrieval settings maxHops: 4, // More thorough retrieval qualityThreshold: 0.75, // Lower threshold for faster execution // Parallel processing settings maxIterations: 3, // More sub-query iterations // Self-healing settings qualityTarget: 0.9, // Higher quality target disableQualityHealing: false, // Enable healing loops // Debug and monitoring debug: true, // Detailed execution logging logger: customLogger, // Custom logging function }); // Execute with complex research query const result = await advancedRAG.forward(llm, { originalQuestion: "What are the latest developments in quantum computing for cryptography, including both opportunities and security risks?" }); ``` ## RAG Pipeline Architecture The `axRAG` implementation uses a sophisticated 4-phase approach: **Phase 1: Multi-hop Context Retrieval** - Generates intelligent search queries based on the original question - Iteratively retrieves and contextualizes information - Assesses completeness and refines queries for subsequent hops - Accumulates comprehensive context across multiple retrieval rounds **Phase 2: Parallel Sub-query Processing** - Decomposes complex questions into focused sub-queries - Executes parallel retrieval for comprehensive coverage - Synthesizes evidence from multiple information sources - Analyzes gaps and determines need for additional information **Phase 3: Answer Generation** - Generates comprehensive answers using accumulated context - Leverages all retrieved information and synthesized evidence - Produces initial high-quality responses **Phase 4: Self-healing Quality Loops** - Validates answer quality against configurable thresholds - Identifies specific issues and areas for improvement - Retrieves targeted healing context to address deficiencies - Iteratively improves answers until quality targets are met ## AxFlow Pipeline Implementation The `axRAG` implementation showcases the power of **AxFlow** to build complex, real-world LLM-powered pipelines that solve sophisticated problems. Below is the commented AxFlow pipeline code that demonstrates how intricate multi-hop RAG systems can be elegantly constructed using AxFlow's declarative approach: ```typescript export const axRAG = ( queryFn: (query: string) => Promise, options?: { maxHops?: number; qualityThreshold?: number; maxIterations?: number; qualityTarget?: number; disableQualityHealing?: boolean; logger?: AxFlowLoggerFunction; debug?: boolean; } ) => { // Extract configuration with sensible defaults const maxHops = options?.maxHops ?? 3; const qualityThreshold = options?.qualityThreshold ?? 0.8; const maxIterations = options?.maxIterations ?? 2; const qualityTarget = options?.qualityTarget ?? 0.85; const disableQualityHealing = options?.disableQualityHealing ?? false; return ( new AxFlow< { originalQuestion: string }, { finalAnswer: string; totalHops: number; retrievedContexts: string[]; iterationCount: number; healingAttempts: number; qualityAchieved: number; } >({ logger: options?.logger, debug: options?.debug, }) // πŸ—οΈ STEP 1: Define AI-powered processing nodes // Each node represents a specialized AI task with typed inputs/outputs .node( 'queryGenerator', 'originalQuestion:string, previousContext?:string -> searchQuery:string, queryReasoning:string' ) .node( 'contextualizer', 'retrievedDocument:string, accumulatedContext?:string -> enhancedContext:string' ) .node( 'qualityAssessor', 'currentContext:string, originalQuestion:string -> completenessScore:number, missingAspects:string[]' ) .node( 'questionDecomposer', 'complexQuestion:string -> subQuestions:string[], decompositionReason:string' ) .node( 'evidenceSynthesizer', 'collectedEvidence:string[], originalQuestion:string -> synthesizedEvidence:string, evidenceGaps:string[]' ) .node( 'gapAnalyzer', 'synthesizedEvidence:string, evidenceGaps:string[], originalQuestion:string -> needsMoreInfo:boolean, focusedQueries:string[]' ) .node( 'answerGenerator', 'finalContext:string, originalQuestion:string -> comprehensiveAnswer:string, confidenceLevel:number' ) .node( 'queryRefiner', 'originalQuestion:string, currentContext:string, missingAspects:string[] -> refinedQuery:string' ) .node( 'qualityValidator', 'generatedAnswer:string, userQuery:string -> qualityScore:number, issues:string[]' ) .node( 'answerHealer', 'originalAnswer:string, healingDocument:string, issues?:string[] -> healedAnswer:string' ) // πŸš€ STEP 2: Initialize comprehensive pipeline state // AxFlow maintains this state throughout the entire pipeline execution .map((state) => ({ ...state, maxHops, qualityThreshold, maxIterations, qualityTarget, disableQualityHealing, currentHop: 0, accumulatedContext: '', retrievedContexts: [] as string[], completenessScore: 0, searchQuery: state.originalQuestion, shouldContinue: true, iteration: 0, allEvidence: [] as string[], evidenceSources: [] as string[], needsMoreInfo: true, healingAttempts: 0, currentQuality: 0, shouldContinueHealing: true, currentAnswer: '', currentIssues: [] as string[], })) // πŸ”„ PHASE 1: Multi-hop Retrieval with Iterative Refinement // AxFlow's .while() enables sophisticated looping with dynamic conditions .while( (state) => state.currentHop < state.maxHops && state.completenessScore < state.qualityThreshold && state.shouldContinue ) // Increment hop counter for each iteration .map((state) => ({ ...state, currentHop: state.currentHop + 1, })) // 🧠 Generate intelligent search query using AI .execute('queryGenerator', (state) => ({ originalQuestion: state.originalQuestion, previousContext: state.accumulatedContext || undefined, })) // πŸ“š Execute vector database retrieval using provided queryFn .map(async (state) => { const searchQuery = state.queryGeneratorResult.searchQuery as string; const retrievedDocument = await queryFn(searchQuery); return { ...state, retrievalResult: { retrievedDocument, retrievalConfidence: 0.9, }, }; }) // πŸ”— Contextualize retrieved document with existing knowledge .execute('contextualizer', (state) => ({ retrievedDocument: state.retrievalResult.retrievedDocument, accumulatedContext: state.accumulatedContext || undefined, })) // πŸ“Š Assess quality and completeness of current context .execute('qualityAssessor', (state) => ({ currentContext: state.contextualizerResult.enhancedContext, originalQuestion: state.originalQuestion, })) // πŸ“ˆ Update state with enhanced context and quality metrics .map((state) => ({ ...state, accumulatedContext: state.contextualizerResult.enhancedContext, retrievedContexts: [ ...state.retrievedContexts, state.retrievalResult.retrievedDocument, ], completenessScore: state.qualityAssessorResult.completenessScore as number, searchQuery: state.queryGeneratorResult.searchQuery as string, shouldContinue: (state.qualityAssessorResult.completenessScore as number) < state.qualityThreshold, })) // 🎯 Conditional query refinement using AxFlow's branching .branch( (state) => state.shouldContinue && state.currentHop < state.maxHops ) .when(true) .execute('queryRefiner', (state) => ({ originalQuestion: state.originalQuestion, currentContext: state.accumulatedContext, missingAspects: state.qualityAssessorResult.missingAspects, })) .map((state) => ({ ...state, searchQuery: state.queryRefinerResult?.refinedQuery || state.searchQuery, })) .when(false) .map((state) => state) // No refinement needed .merge() .endWhile() // ⚑ PHASE 2: Advanced Parallel Sub-query Processing // Initialize evidence collection from Phase 1 results .map((state) => ({ ...state, allEvidence: state.retrievedContexts.length > 0 ? state.retrievedContexts : [], })) // πŸ”„ Iterative sub-query processing loop .while( (state) => state.iteration < state.maxIterations && state.needsMoreInfo ) .map((state) => ({ ...state, iteration: state.iteration + 1, })) // 🧩 Question decomposition for first iteration .branch((state) => state.iteration === 1) .when(true) .execute('questionDecomposer', (state) => ({ complexQuestion: state.originalQuestion, })) .map((state) => ({ ...state, currentQueries: state.questionDecomposerResult.subQuestions, })) .when(false) // Use focused queries from gap analysis for subsequent iterations .map((state) => ({ ...state, currentQueries: ((state as any).gapAnalyzerResult?.focusedQueries as string[]) || [], })) .merge() // πŸš€ Parallel retrieval execution for multiple queries .map(async (state) => { const queries = state.currentQueries || []; const retrievalResults = queries.length > 0 ? await Promise.all(queries.map((query: string) => queryFn(query))) : []; return { ...state, retrievalResults, }; }) // πŸ§ͺ Synthesize evidence from multiple sources .execute('evidenceSynthesizer', (state) => { const evidence = [ ...(state.allEvidence || []), ...(state.retrievalResults || []), ].filter(Boolean); return { collectedEvidence: evidence.length > 0 ? evidence : ['No evidence collected yet'], originalQuestion: state.originalQuestion, }; }) // πŸ” Analyze information gaps and determine next steps .execute('gapAnalyzer', (state) => ({ synthesizedEvidence: state.evidenceSynthesizerResult.synthesizedEvidence, evidenceGaps: state.evidenceSynthesizerResult.evidenceGaps, originalQuestion: state.originalQuestion, })) // πŸ“Š Update state with synthesized evidence and gap analysis .map((state) => ({ ...state, allEvidence: [...state.allEvidence, ...state.retrievalResults], evidenceSources: [...state.evidenceSources, `Iteration ${state.iteration} sources`], needsMoreInfo: state.gapAnalyzerResult.needsMoreInfo, synthesizedEvidence: state.evidenceSynthesizerResult.synthesizedEvidence, })) .endWhile() // πŸ“ PHASE 3: Generate comprehensive initial answer .execute('answerGenerator', (state) => ({ finalContext: state.accumulatedContext || state.synthesizedEvidence || state.allEvidence.join('\n'), originalQuestion: state.originalQuestion, })) // πŸ₯ PHASE 4: Self-healing Quality Validation and Improvement // Conditional quality healing based on configuration .branch((state) => !state.disableQualityHealing) .when(true) // Validate initial answer quality .execute('qualityValidator', (state) => ({ generatedAnswer: state.answerGeneratorResult.comprehensiveAnswer, userQuery: state.originalQuestion, })) .map((state) => ({ ...state, currentAnswer: state.answerGeneratorResult.comprehensiveAnswer as string, currentQuality: state.qualityValidatorResult.qualityScore as number, currentIssues: state.qualityValidatorResult.issues as string[], shouldContinueHealing: (state.qualityValidatorResult.qualityScore as number) < state.qualityTarget, })) // πŸ”„ Healing loop for iterative quality improvement .while( (state) => state.healingAttempts < 3 && state.shouldContinueHealing ) .map((state) => ({ ...state, healingAttempts: state.healingAttempts + 1, })) // 🩹 Retrieve healing context to address specific issues .map(async (state) => { const healingQuery = `${state.originalQuestion} addressing issues: ${(state.currentIssues as string[])?.join(', ') || 'quality improvement'}`; const healingDocument = await queryFn(healingQuery); return { ...state, healingResult: { healingDocument }, }; }) // πŸ”§ Apply healing to improve answer quality .execute('answerHealer', (state) => ({ originalAnswer: state.currentAnswer, healingDocument: state.healingResult.healingDocument, issues: state.currentIssues, })) // βœ… Re-validate after healing application .execute('qualityValidator', (state) => ({ generatedAnswer: state.answerHealerResult.healedAnswer, userQuery: state.originalQuestion, })) .map((state) => ({ ...state, currentAnswer: state.answerHealerResult.healedAnswer as string, currentQuality: state.qualityValidatorResult.qualityScore as number, currentIssues: state.qualityValidatorResult.issues as string[], shouldContinueHealing: (state.qualityValidatorResult.qualityScore as number) < state.qualityTarget, })) .endWhile() .when(false) // Skip quality healing - use answer directly from Phase 3 .map((state) => ({ ...state, currentAnswer: state.answerGeneratorResult.comprehensiveAnswer, currentQuality: 1.0, // Assume perfect quality when disabled currentIssues: [] as string[], shouldContinueHealing: false, })) .merge() // 🎯 Final output transformation .map((state) => ({ finalAnswer: state.currentAnswer, totalHops: state.currentHop, retrievedContexts: state.retrievedContexts, iterationCount: state.iteration, healingAttempts: state.healingAttempts, qualityAchieved: state.currentQuality, })) ); }; ``` ### 🌟 Why This Demonstrates AxFlow's Power This `axRAG` implementation showcases **AxFlow's unique capabilities** for building enterprise-grade LLM pipelines: **πŸ—οΈ Complex State Management**: AxFlow seamlessly manages complex state transformations across 20+ pipeline steps, handling async operations, branching logic, and iterative loops without losing state consistency. **πŸ”„ Advanced Control Flow**: The pipeline uses AxFlow's `.while()`, `.branch()`, `.when()`, and `.merge()` operators to implement sophisticated control flow that would be complex and error-prone with traditional code. **⚑ Automatic Parallelization**: AxFlow automatically parallelizes operations where possible, such as the parallel sub-query processing in Phase 2, maximizing performance without manual coordination. **🧠 AI-Native Design**: Each `.node()` defines an AI task with typed signatures, making the pipeline self-documenting and enabling automatic prompt optimization and validation. **πŸ›‘οΈ Production Reliability**: Built-in error handling, retry logic, state recovery, and comprehensive logging make this production-ready for real-world applications. **πŸ“Š Observability**: The debug mode and logging capabilities provide complete visibility into the pipeline execution, essential for debugging and optimization. This level of sophisticationβ€”multi-hop reasoning, self-healing quality loops, parallel processing, and intelligent branchingβ€”demonstrates how **AxFlow enables developers to build the kinds of advanced LLM systems that solve real-world problems** with enterprise reliability and maintainability. ## Debug Mode Visualization Enable `debug: true` to see the complete RAG pipeline execution: ```bash πŸ”„ [ AXFLOW START ] Input Fields: originalQuestion Total Steps: 18 ═══════════════════════════════════════ ⚑ [ STEP 3 EXECUTE ] Node: contextualizer in 1.62s New Fields: contextualizerResult Result: { "enhancedContext": "Machine learning in financial services raises privacy concerns through data collection, algorithmic bias, and potential for discrimination. Regulations like GDPR require explicit consent and data protection measures." } ──────────────────────────────────────── ⚑ [ STEP 9 EXECUTE ] Node: evidenceSynthesizer in 1.42s New Fields: evidenceSynthesizerResult Result: { "synthesizedEvidence": "Comprehensive analysis of ML privacy implications including regulatory compliance, bias prevention, and consumer protection measures.", "evidenceGaps": ["Technical implementation details", "Industry-specific case studies"] } ──────────────────────────────────────── βœ… [ AXFLOW COMPLETE ] Total Time: 12.49s Steps Executed: 15 ═══════════════════════════════════════ ``` ## Integration with Vector Databases ```typescript // Weaviate integration import { axDB } from "@ax-llm/ax"; const weaviateDB = new axDB("weaviate", { url: "http://localhost:8080", }); const queryWeaviate = async (query: string): Promise => { const embedding = await llm.embed({ texts: [query] }); const results = await weaviateDB.query({ table: "documents", values: embedding.embeddings[0], limit: 5, }); return results.map(r => r.metadata.text).join('\n'); }; // Pinecone integration import { axDB } from "@ax-llm/ax"; const pineconeDB = new axDB("pinecone", { apiKey: process.env.PINECONE_API_KEY, environment: "us-west1-gcp", }); const queryPinecone = async (query: string): Promise => { const embedding = await llm.embed({ texts: [query] }); const results = await pineconeDB.query({ table: "knowledge-base", values: embedding.embeddings[0], topK: 10, }); return results.matches.map(m => m.metadata.content).join('\n'); }; ``` ## Performance Optimization ```typescript // Optimized for speed const fastRAG = axRAG(queryFn, { maxHops: 2, // Fewer hops for speed qualityThreshold: 0.7, // Lower quality threshold maxIterations: 1, // Single iteration disableQualityHealing: true, // Skip healing for speed }); // Optimized for quality const qualityRAG = axRAG(queryFn, { maxHops: 5, // Thorough retrieval qualityThreshold: 0.9, // High quality threshold maxIterations: 3, // Multiple iterations qualityTarget: 0.95, // Very high healing target disableQualityHealing: false, }); // Balanced configuration const balancedRAG = axRAG(queryFn, { maxHops: 3, qualityThreshold: 0.8, maxIterations: 2, qualityTarget: 0.85, }); ``` ## Simple RAG Alternative For basic use cases, `axRAG` also provides `axSimpleRAG`: ```typescript import { axSimpleRAG } from "@ax-llm/ax"; // Simple single-hop RAG const simpleRAG = axSimpleRAG(queryVectorDB); const result = await simpleRAG.forward(llm, { question: "What is renewable energy?" }); console.log("Answer:", result.answer); console.log("Context:", result.context); ``` ## Why axRAG is Powerful **πŸš€ Production-Ready Architecture:** - Built on AxFlow's automatic parallelization and resilience features - Self-healing quality loops prevent poor answers - Configurable trade-offs between speed and thoroughness - Comprehensive logging and debugging capabilities **🧠 Advanced Intelligence:** - Multi-hop reasoning that builds context iteratively - Intelligent query refinement based on previous results - Gap analysis to identify missing information - Parallel sub-query processing for complex questions **πŸ”§ Enterprise Features:** - Configurable quality thresholds and targets - Support for any vector database through simple query function - Built-in error handling and retry logic - Comprehensive metrics and observability **⚑ Performance Optimized:** - Automatic parallelization where possible - Intelligent caching and context reuse - Configurable performance vs. quality trade-offs - Efficient token usage through smart prompt management > _"axRAG doesn't just retrieve and generateβ€”it thinks, analyzes, and iteratively improves to deliver the highest quality answers possible"_ The `axRAG` function represents the future of RAG systems: intelligent, self-improving, and production-ready with enterprise-grade reliability built on AxFlow's powerful orchestration capabilities. ================================================================================ # Migration Guide # Source: MIGRATION.md # Complete migration guide for Ax v13.0.24+ API changes # Migration Guide: Ax v13.0.24+ API Changes This document provides comprehensive migration instructions for Ax v14.0.0+ API changes. The framework introduces significant improvements for better type safety, performance, and consistency. ## Overview of Changes **Version 14.0.0+** deprecates several patterns that will be **completely removed in v15.0.0**: 1. **Template literal syntax** for signatures and generators 2. **Constructor-based API** for core classes 3. **Legacy classes** like `AxChainOfThought` and `AxRAG` **Important**: The `f.()` field helper functions are **NOT deprecated** - they remain available in the fluent signature creation API (`f().input().output().build()`). ## What's Deprecated vs What's Not ### ❌ Deprecated (will be removed in v15.0.0) 1. **Template literal functions**: - `` ax`template` `` β†’ Use `ax('string')` - `` s`template` `` β†’ Use `s('string')` 2. **Constructor-based classes**: - `new AxAI()` β†’ Use `ai()` factory function - `new AxAgent()` β†’ Use `agent()` factory function - `new AxFlow()` β†’ Use `flow()` static method - `new AxSignature()` β†’ Use `s()` function or `AxSignature.create()` 3. **Legacy classes**: - `AxChainOfThought` β†’ Use modern thinking models (o1, etc.) - `AxRAG` β†’ Use `axRAG()` function built on AxFlow ### βœ… Still Available (NOT deprecated) 1. **Field helper functions in fluent API**: - `f.string()`, `f.number()`, `f.class()`, etc. - still work in fluent signatures - `f().input().output().build()` pattern remains fully supported 2. **All current functionality** - just accessed through new patterns ## Detailed Migration Instructions ### 1. AI Instance Creation ```typescript // ❌ DEPRECATED: Constructor const ai = new AxAI({ name: "openai", apiKey: "..." }); // βœ… CURRENT: Factory function const llm = ai({ name: "openai", apiKey: "..." }); ``` **Why migrate**: Factory functions provide better type inference and consistency. ### 2. Signature Creation #### String-Based Signatures (Recommended) ```typescript // ❌ DEPRECATED: Template literal const sig = s`input:string -> output:string`; // βœ… CURRENT: Function call const sig = s("input:string -> output:string"); ``` #### Fluent API (Still Fully Supported) ```typescript // βœ… CURRENT: Fluent API with f.() helpers const sig = f() .input("userMessage", f.string("User input")) .input("context", f.string("Background context").optional()) .output("response", f.string("Generated response")) .output( "sentiment", f.class(["positive", "negative", "neutral"], "Sentiment"), ) .build(); ``` #### Static Methods (Alternative) ```typescript // βœ… CURRENT: Static method const sig = AxSignature.create("input:string -> output:string"); ``` ### 3. Generator Creation ```typescript // ❌ DEPRECATED: Template literal const gen = ax`input:string -> output:string`; // βœ… CURRENT: Function call const gen = ax("input:string -> output:string"); ``` ### 4. Agent Creation ```typescript // ❌ DEPRECATED: Constructor const agent = new AxAgent({ name: "helper", signature: sig, ai: llm, }); // βœ… CURRENT: Factory function const agentInstance = agent({ name: "helper", signature: sig, ai: llm, }); // βœ… ALTERNATIVE: Static method const agentInstance = AxAgent.create({ name: "helper", signature: sig, ai: llm, }); ``` ### 5. Flow Creation ```typescript // ❌ DEPRECATED: Constructor const flow = new AxFlow(); // βœ… CURRENT: Static method or direct instantiation const flow = AxFlow.create(); // OR continue using: new AxFlow() (constructors work for AxFlow) ``` ### 6. RAG Usage ```typescript // ❌ DEPRECATED: AxRAG class const rag = new AxRAG({ ai: llm, db: vectorDb }); // βœ… CURRENT: axRAG function (AxFlow-based) const rag = axRAG({ ai: llm, db: vectorDb }); ``` ## Field Type Reference ### String-Based Field Syntax When using `s()` or `ax()` functions, use string-based field definitions: | Type | Syntax | Example | | ------------------ | ---------------------------------------- | ------------------------------------------------- | | **String** | `field:string "description"` | `userInput:string "User question"` | | **Number** | `field:number "description"` | `score:number "Confidence 0-1"` | | **Boolean** | `field:boolean "description"` | `isValid:boolean "Is input valid"` | | **JSON** | `field:json "description"` | `metadata:json "Extra data"` | | **Arrays** | `field:type[] "description"` | `tags:string[] "Keywords"` | | **Optional** | `field?:type "description"` | `context?:string "Optional context"` | | **Classification** | `field:class "opt1, opt2" "description"` | `category:class "urgent, normal, low" "Priority"` | | **Date** | `field:date "description"` | `dueDate:date "Due date"` | | **DateTime** | `field:datetime "description"` | `timestamp:datetime "Event time"` | | **Code** | `field:code "description"` | `script:code "Python code"` | | **Media** | `field:image/audio/file/url` | `photo:image "Profile picture"` | ### Fluent API Field Helpers (Still Available) When using the fluent API with `f().input().output().build()`, all field helpers remain available: ```typescript const sig = f() .input("text", f.string("Input text")) .input("options", f.array(f.string("Option")).optional()) .input("metadata", f.json("Extra data")) .output("result", f.string("Processed result")) .output("category", f.class(["A", "B", "C"], "Classification")) .output("confidence", f.number("Confidence score")) .build(); ``` ## Complete Migration Examples ### Example 1: Simple Text Processing ```typescript // ❌ DEPRECATED const ai = new AxAI({ name: "openai", apiKey: "..." }); const gen = ax`text:string -> summary:string`; const result = await gen.forward(ai, { text: "Long text..." }); // βœ… CURRENT const llm = ai({ name: "openai", apiKey: "..." }); const gen = ax("text:string -> summary:string"); const result = await gen.forward(llm, { text: "Long text..." }); ``` ### Example 2: Complex Agent ```typescript // ❌ DEPRECATED const ai = new AxAI({ name: "openai", apiKey: "..." }); const sig = s`question:string -> answer:string, confidence:number`; const agent = new AxAgent({ name: "assistant", signature: sig, ai: ai, }); // βœ… CURRENT const llm = ai({ name: "openai", apiKey: "..." }); const sig = s("question:string -> answer:string, confidence:number"); const agentInstance = agent({ name: "assistant", signature: sig, ai: llm, }); ``` ### Example 3: RAG Pipeline ```typescript // ❌ DEPRECATED const ai = new AxAI({ name: "openai", apiKey: "..." }); const rag = new AxRAG({ ai, db: vectorDb }); // βœ… CURRENT const llm = ai({ name: "openai", apiKey: "..." }); const rag = axRAG({ ai: llm, db: vectorDb }); ``` ## Automated Migration For large codebases, you can use find-and-replace patterns to automate migration: ### Template Literal Migration ```bash # Replace ax template literals find . -name "*.ts" -exec sed -i 's/ax`\([^`]*\)`/ax("\1")/g' {} \; # Replace s template literals find . -name "*.ts" -exec sed -i 's/s`\([^`]*\)`/s("\1")/g' {} \; ``` ### Constructor Migration ```bash # Replace AxAI constructor find . -name "*.ts" -exec sed -i 's/new AxAI(/ai(/g' {} \; # Replace AxAgent constructor find . -name "*.ts" -exec sed -i 's/new AxAgent(/agent(/g' {} \; # Replace AxRAG constructor find . -name "*.ts" -exec sed -i 's/new AxRAG(/axRAG(/g' {} \; ``` ### Import Updates ```bash # Update imports to include factory functions find . -name "*.ts" -exec sed -i 's/import { AxAI }/import { ai }/g' {} \; find . -name "*.ts" -exec sed -i 's/import { AxAgent }/import { agent }/g' {} \; ``` ## Benefits of Migration ### 1. Better Type Safety - Full TypeScript inference for all field types - Exact literal type inference for class fields - Compile-time validation of signatures ### 2. Improved Performance - No template literal processing overhead - Faster signature parsing - Reduced runtime validation ### 3. Cleaner Syntax - More readable and consistent API patterns - Better IntelliSense support - Enhanced auto-completion ### 4. Future-Proof Architecture - Aligned with framework's long-term vision - Consistent patterns across all APIs - Better extensibility for new features ## Timeline - **v13.0.24+**: Deprecated patterns still work but show warnings - **v15.0.0**: Deprecated patterns will be completely removed - **Recommendation**: Migrate as soon as possible to take advantage of improvements ## Common Migration Issues ### Issue 1: Template Literal Field Interpolation ```typescript // ❌ PROBLEMATIC: Complex template literals const dynamicType = "string"; const sig = s`input:${dynamicType} -> output:string`; // βœ… SOLUTION: Use fluent API for dynamic fields const sig = f() .input("input", f[dynamicType as keyof typeof f](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/"Input field")) .output("output", f.string("Output field")) .build(); ``` ### Issue 2: Variable Naming Conflicts ```typescript // ❌ PROBLEMATIC: Variable name conflicts const ai = ai({ name: "openai", apiKey: "..." }); // ai conflicts with function name // βœ… SOLUTION: Use recommended naming const llm = ai({ name: "openai", apiKey: "..." }); // Clear naming ``` ### Issue 3: Import Statement Updates ```typescript // ❌ OLD: Constructor imports import { AxAgent, AxAI } from "@ax-llm/ax"; // βœ… NEW: Factory function imports import { agent, ai } from "@ax-llm/ax"; ``` ## Need Help? If you encounter issues during migration: 1. Check the [examples directory](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/) for updated patterns 2. Refer to the main [README.md](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/README.md) for current API usage 3. Join our [Discord community](https://discord.gg/DSHg3dU7dW) for support 4. Open an issue on [GitHub](https://github.com/ax-llm/ax/issues) ## Summary The v13.0.24+ migration primarily involves: 1. **Replace template literals** with function calls: `` ax`...` `` β†’ `ax('...')` 2. **Replace constructors** with factory functions: `new AxAI()` β†’ `ai()` 3. **Update variable names** to avoid conflicts: Use `llm` instead of `ai` 4. **Update imports** to include new factory functions The `f.()` field helper functions remain fully supported in the fluent API and are **not deprecated**. All deprecated patterns will be removed in v15.0.0, so migrate as soon as possible to ensure compatibility and take advantage of the improved type safety and performance. ================================================================================ # Examples Guide # Source: EXAMPLES.md # Comprehensive examples showcasing Ax framework capabilities # Ax Examples Guide A comprehensive collection of examples showcasing Ax framework capabilities, from basic signatures to production-ready patterns. ## Table of Contents - [Getting Started](#getting-started) - [Core Concepts](#core-concepts) - [Advanced Features](#advanced-features) - [Production Patterns](#production-patterns) - [Optimization & Training](#optimization--training) - [Multi-Modal & Vision](#multi-modal--vision) - [Agent Systems](#agent-systems) - [Workflow Orchestration](#workflow-orchestration) ## Getting Started ### 1. Basic Signature - Email Classification The simplest way to start with Ax - define input β†’ output, get type-safe results. ```typescript import { ai, ax } from '@ax-llm/ax'; // Define your signature const classifier = ax( 'email:string -> category:class "spam, important, normal" "Email category"' ); // Choose your LLM const llm = ai({ name: 'openai', apiKey: process.env.OPENAI_APIKEY! }); // Get results const result = await classifier.forward(llm, { email: "URGENT: You've won $1,000,000! Click here now!" }); console.log(result.category); // "spam" - Type-safe! ``` **Key Concepts:** - Signatures define the contract: `input -> output` - `class` type ensures output is one of the specified values - Full TypeScript type inference ### 2. Structured Extraction Extract multiple structured fields from unstructured text in one call. ```typescript import { ax, ai } from '@ax-llm/ax'; const extractor = ax(` customerEmail:string, currentDate:datetime -> subject:string "Email subject", priority:class "high, normal, low", sentiment:class "positive, negative, neutral", ticketNumber?:number "Optional ticket number", nextSteps:string[] "Action items", estimatedResponseTime:string `); const result = await extractor.forward(ai({ name: 'openai' }), { customerEmail: ` Subject: Order #12345 hasn't arrived I ordered 2 weeks ago and still haven't received my package. This is unacceptable! I need this resolved immediately or I want a refund. The tracking shows it's been stuck for days. `, currentDate: new Date() }); console.log(result); // { // subject: "Order #12345 hasn't arrived", // priority: "high", // sentiment: "negative", // ticketNumber: 12345, // nextSteps: ["Check tracking status", "Contact shipping carrier", "Offer refund or replacement"], // estimatedResponseTime: "Within 24 hours" // } ``` ### 3. Adding Validation with Assertions Ensure outputs meet your business rules with assertions. ```typescript import { ax, ai } from '@ax-llm/ax'; const gen = ax('startNumber:number -> next10Numbers:number[]'); // Add business rules gen.addAssert( ({ next10Numbers }) => next10Numbers?.length === 10, 'Must generate exactly 10 numbers' ); gen.addAssert( ({ next10Numbers }) => next10Numbers?.every(n => n > 0), 'All numbers must be positive' ); // Ax will automatically retry if assertions fail const result = await gen.forward(ai({ name: 'openai' }), { startNumber: 1 }); ``` ## Core Concepts ### 4. Function Calling (ReAct Pattern) Let your AI use tools to answer questions - the ReAct (Reasoning + Acting) pattern. ```typescript import { ax, ai, type AxFunction } from '@ax-llm/ax'; // Define available functions const functions: AxFunction[] = [ { name: 'getCurrentWeather', description: 'Get current weather for a location', parameters: { type: 'object', properties: { location: { type: 'string', description: 'City name' }, units: { type: 'string', enum: ['celsius', 'fahrenheit'] } }, required: ['location'] }, func: async ({ location, units }) => { // Real API call would go here return { temp: 72, condition: 'sunny', location }; } }, { name: 'searchNews', description: 'Search for recent news', parameters: { type: 'object', properties: { query: { type: 'string' }, limit: { type: 'number', default: 5 } }, required: ['query'] }, func: async ({ query, limit }) => { return [`Breaking: ${query} news item 1`, `Update: ${query} item 2`]; } } ]; // Create signature with functions const assistant = ax( 'question:string -> answer:string "Detailed answer using available tools"', { functions } ); const result = await assistant.forward( ai({ name: 'openai' }), { question: "What's the weather like in Tokyo and any news about it?" } ); // AI will automatically call both functions and combine results console.log(result.answer); // "The current weather in Tokyo is 72Β°F and sunny. Recent news about Tokyo includes..." ``` ### 5. Streaming Responses Stream responses for real-time user feedback. ```typescript import { ax, ai } from '@ax-llm/ax'; const gen = ax('topic:string -> article:string "500 word article"'); // Enable streaming const stream = await gen.streamingForward( ai({ name: 'openai' }), { topic: 'The future of TypeScript' } ); // Process chunks as they arrive for await (const chunk of stream) { if (chunk.article) { process.stdout.write(chunk.article); // Real-time output } } ``` ### 6. Multi-Step Reasoning with Examples Improve accuracy by providing examples - few-shot learning made simple. ```typescript import { AxGen, ai } from '@ax-llm/ax'; const analyzer = new AxGen< { code: string }, { hasVulnerability: boolean; type?: string; severity?: string; suggestion?: string } >('code:string -> hasVulnerability:boolean, type?:string, severity?:string, suggestion?:string'); // Add examples to guide the AI analyzer.setExamples([ { code: 'const password = "admin123"', hasVulnerability: true, type: 'Hardcoded Credentials', severity: 'critical', suggestion: 'Use environment variables for sensitive data' }, { code: 'const add = (a: number, b: number) => a + b', hasVulnerability: false } ]); const result = await analyzer.forward( ai({ name: 'openai' }), { code: 'eval(userInput)' } ); // { hasVulnerability: true, type: "Code Injection", severity: "critical", ... } ``` ## Advanced Features ### 7. Multi-Modal Processing Process images and text together seamlessly. ```typescript import { ax, ai, image } from '@ax-llm/ax'; const analyzer = ax(` image:image "Product photo", question:string -> description:string, mainColors:string[], category:class "electronics, clothing, food, other", estimatedPrice:string `); const result = await analyzer.forward( ai({ name: 'openai', config: { model: 'gpt-4o' } }), { image: image('./product.jpg'), question: 'What product is this and what can you tell me about it?' } ); ``` ### 8. Smart Document Processing with Chain of Thought Process complex documents with automatic reasoning steps. ```typescript import { ax, ai } from '@ax-llm/ax'; const processor = ax(` document:string "Full document text", instructions:string -> thinking:string "Step-by-step analysis", summary:string "Executive summary", keyInsights:string[] "Main takeaways", risks:string[] "Identified risks", opportunities:string[] "Identified opportunities", recommendedActions:string[] "Concrete next steps", confidence:number "0-100 confidence score" `); const result = await processor.forward( ai({ name: 'anthropic', config: { model: 'claude-3-5-sonnet' } }), { document: businessPlan, instructions: "Analyze this business plan for investment potential" } ); // Access the reasoning process console.log('Analysis:', result.thinking); console.log('Summary:', result.summary); console.log('Confidence:', result.confidence); ``` ## Production Patterns ### 9. Customer Support Agent Complete customer support system with routing, prioritization, and response generation. ```typescript import { ax, ai } from '@ax-llm/ax'; const supportAgent = ax(` customerMessage:string, customerHistory?:string "Previous interactions", knowledgeBase?:string "Relevant KB articles" -> intent:class "question, complaint, feedback, request", department:class "billing, technical, sales, general", priority:class "urgent, high, normal, low", sentiment:number "0-10 scale", suggestedResponse:string, internalNotes:string "For support team", requiresHumanReview:boolean, tags:string[] `); // Add validation rules supportAgent.addAssert( ({ priority, sentiment }) => !(priority === 'low' && sentiment !== undefined && sentiment < 3), 'Low sentiment should not be low priority' ); const result = await supportAgent.forward( ai({ name: 'openai' }), { customerMessage: "I've been charged twice for my subscription and need a refund immediately!", customerHistory: "Premium customer since 2020, previous billing issue in March" } ); console.log(`Route to: ${result.department} (Priority: ${result.priority})`); console.log(`Response: ${result.suggestedResponse}`); ``` ### 10. Restaurant Recommendation System Multi-criteria recommendation with function calling. ```typescript import { ax, ai, type AxFunction } from '@ax-llm/ax'; const searchRestaurants: AxFunction = { name: 'searchRestaurants', description: 'Search restaurants by criteria', parameters: { type: 'object', properties: { cuisine: { type: 'string' }, priceRange: { type: 'string', enum: ['$', '$$', '$$$', '$$$$'] }, location: { type: 'string' }, features: { type: 'array', items: { type: 'string' }, description: 'outdoor seating, delivery, etc' } } }, func: async (params) => { // Database query would go here return mockRestaurantData.filter(r => r.cuisine === params.cuisine && r.priceRange === params.priceRange ); } }; const recommender = ax(` preferences:string "User's dining preferences", occasion:string, groupSize:number, location:string -> thinking:string "Analysis of preferences", recommendations:object[] "Top 3 restaurants with reasons", bestMatch:object "Single best recommendation", alternativeOptions:string "Other cuisines to consider" `, { functions: [searchRestaurants, getWeather, checkAvailability] }); const result = await recommender.forward( ai({ name: 'openai' }), { preferences: "I love spicy food and outdoor dining", occasion: "anniversary dinner", groupSize: 2, location: "San Francisco" } ); ``` ## Optimization & Training ### 11. Automatic Prompt Optimization Use Bootstrap Few-Shot optimization to improve accuracy automatically. ```typescript import { ax, ai, AxBootstrapFewShot, type AxMetricFn } from '@ax-llm/ax'; // Define your task const classifier = ax( 'email:string -> category:class "spam, important, normal", confidence:number' ); // Provide training examples const trainingData = [ { email: "Meeting at 3pm", category: "normal", confidence: 0.9 }, { email: "WINNER! Claim prize!", category: "spam", confidence: 0.95 }, { email: "Server is down", category: "important", confidence: 0.85 }, // ... more examples ]; // Define success metric const metric: AxMetricFn = ({ prediction, example }) => { const correct = prediction.category === example.category; const confidentAndCorrect = correct && prediction.confidence > 0.8; return confidentAndCorrect ? 1 : correct ? 0.5 : 0; }; // Run optimization const optimizer = new AxBootstrapFewShot({ studentAI: ai({ name: 'openai' }), teacherAI: ai({ name: 'anthropic' }), // Optional: use stronger model as teacher metric, options: { maxRounds: 5, maxDemos: 3, maxExamples: 100 } }); const optimized = await optimizer.compile(classifier, trainingData); console.log(`Improved accuracy from 65% to ${optimized.bestScore * 100}%`); // Use optimized program const result = await optimized.program.forward(ai({ name: 'openai' }), { email: "System maintenance tonight" }); ``` ## Agent Systems ### 12. Multi-Agent Collaboration Build systems where specialized agents work together. ```typescript import { AxAgent, ai } from '@ax-llm/ax'; // Specialized researcher agent const researcher = new AxAgent({ name: 'Researcher', description: 'Expert at finding and analyzing information', signature: 'question:string -> research:string "Detailed findings", sources:string[]' }); // Specialized writer agent const writer = new AxAgent({ name: 'Writer', description: 'Expert at creating engaging content', signature: 'research:string, tone:string -> article:string, title:string' }); // Specialized editor agent const editor = new AxAgent({ name: 'Editor', description: 'Expert at improving clarity and correctness', signature: 'article:string -> editedArticle:string, changes:string[]' }); // Coordinator agent that orchestrates others const coordinator = new AxAgent({ name: 'Content Creator', description: 'Creates high-quality articles using specialized agents', signature: 'topic:string, style:string -> finalArticle:string, metadata:object', agents: [researcher, writer, editor] }); // The coordinator will automatically delegate to the right agents const result = await coordinator.forward( ai({ name: 'openai' }), { topic: 'The future of TypeScript', style: 'technical but accessible' } ); console.log(result.finalArticle); // Fully researched, written, and edited article ``` ### 13. Agent with Memory and Tools Build stateful agents that remember context and use tools. ```typescript import { AxAgent, AxMemory, ai, type AxFunction } from '@ax-llm/ax'; // Create memory store const memory = new AxMemory(); // Define agent tools const tools: AxFunction[] = [ { name: 'saveNote', description: 'Save important information for later', parameters: { type: 'object', properties: { category: { type: 'string' }, content: { type: 'string' } } }, func: async ({ category, content }) => { await memory.add(category, content); return 'Saved to memory'; } }, { name: 'recall', description: 'Recall previously saved information', parameters: { type: 'object', properties: { category: { type: 'string' }, query: { type: 'string' } } }, func: async ({ category, query }) => { return await memory.search(category, query); } } ]; const assistant = new AxAgent({ name: 'Personal Assistant', description: 'Helps manage tasks and remember important information', signature: 'message:string, userId:string -> response:string, actionsTaken:string[]', functions: tools, memory }); // First interaction await assistant.forward(ai({ name: 'openai' }), { message: "Remember that my favorite color is blue", userId: "user123" }); // Later interaction - agent remembers const result = await assistant.forward(ai({ name: 'openai' }), { message: "What's my favorite color?", userId: "user123" }); // "Your favorite color is blue" ``` ## Workflow Orchestration ### 14. AxFlow - Complex Pipeline Build sophisticated data processing pipelines with AxFlow. ```typescript import { AxFlow, AxFlow, ai } from '@ax-llm/ax'; // Create a content moderation pipeline const pipeline = new AxFlow() // Step 1: Analyze content .addNode('analyzer', ax(` content:string -> hasPII:boolean "Contains personal information", hasProfanity:boolean, toxicityScore:number "0-100", topics:string[] `)) // Step 2: Redact sensitive info (only if needed) .addNode('redactor', ax(` content:string, hasPII:boolean -> redactedContent:string, redactedItems:string[] `)) // Step 3: Generate moderation decision .addNode('moderator', ax(` content:string, toxicityScore:number, hasProfanity:boolean -> decision:class "approve, flag, reject", reason:string, suggestedAction:string `)) // Define the flow .flow(({ content }) => ({ analyzer: { content }, redactor: { content, hasPII: '{{analyzer.hasPII}}' }, moderator: { content: '{{redactor.redactedContent}}', toxicityScore: '{{analyzer.toxicityScore}}', hasProfanity: '{{analyzer.hasProfanity}}' } })); const result = await pipeline.run( ai({ name: 'openai' }), { content: "John Smith (SSN: 123-45-6789) posted offensive content" } ); console.log(result.moderator.decision); // "reject" console.log(result.redactor.redactedItems); // ["SSN: XXX-XX-XXXX"] ``` ### 15. Parallel Processing with Map-Reduce Process multiple items in parallel and aggregate results. ```typescript import { AxFlow, ax, ai } from '@ax-llm/ax'; const flow = new AxFlow() // Map: Process each item in parallel .map('processor', ax(` item:object -> processed:object, quality:number, issues:string[] `)) // Reduce: Aggregate all results .reduce('aggregator', ax(` results:object[] -> summary:string, totalQuality:number, allIssues:string[], recommendations:string[] `)); const items = [ { id: 1, data: 'Item 1 data' }, { id: 2, data: 'Item 2 data' }, { id: 3, data: 'Item 3 data' } ]; const result = await flow.run( ai({ name: 'openai' }), { items } ); console.log(`Processed ${items.length} items`); console.log(`Average quality: ${result.totalQuality / items.length}`); ``` ## Running Examples All examples are in the `src/examples/` directory. To run any example: ```bash # Set your API key export OPENAI_APIKEY=your-key-here # Or for other providers: export ANTHROPIC_APIKEY=your-key export GOOGLE_APIKEY=your-key # Run an example npm run tsx ./src/examples/summarize.ts ``` ## Best Practices 1. **Start Simple**: Begin with basic signatures, add complexity as needed 2. **Use Types**: Leverage TypeScript's type system for safety 3. **Add Assertions**: Validate outputs to ensure quality 4. **Provide Examples**: Few-shot examples dramatically improve accuracy 5. **Optimize When Needed**: Use BootstrapFewShot for production accuracy 6. **Handle Errors**: Always wrap in try-catch for production 7. **Stream for UX**: Use streaming for better user experience 8. **Monitor Performance**: Use built-in telemetry for observability ## Next Steps - [Read the DSPy Concepts](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/DSPY.md) to understand the theory - [Explore the API Reference](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/API.md) for detailed documentation - [Join our Discord](https://discord.gg/DSHg3dU7dW) for help and discussions - [Star us on GitHub](https://github.com/ax-llm/ax) if you find Ax useful! ================================================================================