LlamaIndex TypeScript Production Recipes
LlamaIndex TypeScript Production Recipes
Section titled “LlamaIndex TypeScript Production Recipes”10 Real-World, Production-Quality TypeScript Applications with Workflows 1.0
Section titled “10 Real-World, Production-Quality TypeScript Applications with Workflows 1.0”Version: 2025 Edition - TypeScript/Node.js Focus: Complete, runnable examples with Workflows 1.0
Table of Contents
Section titled “Table of Contents”- Basic RAG Chatbot with Express
- Research Paper Analyzer
- Code Documentation Generator
- Multi-Document Comparison
- Real-time News Agent
- Data Extraction Pipeline
- Conversational SQL Agent
- Knowledge Graph Builder
- Multi-Step Reasoning Agent
- Customer Support Triage
RECIPE 1: Basic RAG Chatbot with Express
Section titled “RECIPE 1: Basic RAG Chatbot with Express”Overview
Section titled “Overview”A production-ready RAG chatbot using Workflows 1.0, Express.js, and streaming responses.
Features
Section titled “Features”- Event-driven workflow architecture
- Vector-based document retrieval
- Streaming responses
- Redis caching
- Rate limiting
- Health checks
Installation
Section titled “Installation”npm install llamaindex llama-index-workflows expressnpm install @types/express cors helmet express-rate-limitnpm install ioredis dotenv winstonnpm install -D typescript ts-node nodemonComplete Implementation
Section titled “Complete Implementation”import { Workflow, StartEvent, StopEvent, Event, step} from 'llama-index-workflows';import { VectorStoreIndex, Document, OpenAI, SimpleDirectoryReader} from 'llamaindex';import { cacheService } from '../services/cache.service';import { logger } from '../utils/logger';import { createHash } from 'crypto';
// Custom Eventsclass RetrievalEvent extends Event { query: string; topK: number;
constructor(data: { query: string; topK?: number }) { super(); this.query = data.query; this.topK = data.topK || 5; }}
class DocumentEvent extends Event { documents: string[]; scores: number[];
constructor(data: { documents: string[]; scores: number[] }) { super(); this.documents = data.documents; this.scores = data.scores; }}
class GenerationEvent extends Event { query: string; context: string;
constructor(data: { query: string; context: string }) { super(); this.query = data.query; this.context = data.context; }}
// RAG Chatbot Workflowexport class RAGChatbotWorkflow extends Workflow { private index: VectorStoreIndex; private llm: OpenAI; private initialized = false;
constructor() { super(); this.llm = new OpenAI({ model: 'gpt-4', temperature: 0.7 }); }
async initialize(documentsPath: string): Promise<void> { if (this.initialized) return;
logger.info('Initializing RAG index...');
// Load documents const reader = new SimpleDirectoryReader(); const documents = await reader.loadData(documentsPath);
// Create index this.index = await VectorStoreIndex.fromDocuments(documents);
this.initialized = true; logger.info('RAG index initialized'); }
@step() async checkCache(ev: StartEvent): Promise<RetrievalEvent | StopEvent> { const query = ev.data.query; const cacheKey = this.getCacheKey(query);
// Check cache const cached = await cacheService.get(cacheKey); if (cached) { logger.info('Cache hit for query'); return new StopEvent({ response: cached, cached: true, metadata: { cacheKey } }); }
logger.info('Cache miss, processing query'); return new RetrievalEvent({ query, topK: ev.data.topK || 5 }); }
@step() async retrieveDocuments(ev: RetrievalEvent): Promise<DocumentEvent> { logger.info(`Retrieving top ${ev.topK} documents`);
const retriever = this.index.asRetriever({ similarityTopK: ev.topK });
const nodes = await retriever.retrieve(ev.query);
const documents = nodes.map(node => node.node.getText()); const scores = nodes.map(node => node.score || 0);
logger.info(`Retrieved ${documents.length} documents, top score: ${scores[0]?.toFixed(3)}`);
return new DocumentEvent({ documents, scores }); }
@step() async prepareContext(ev: DocumentEvent): Promise<GenerationEvent> { const context = ev.documents .map((doc, i) => `[Document ${i + 1}]:\n${doc}`) .join('\n\n');
// Note: In production, pass query through state or context const query = 'User query placeholder';
return new GenerationEvent({ query, context }); }
@step() async generateResponse(ev: GenerationEvent): Promise<StopEvent> { logger.info('Generating response');
const prompt = `Answer the following question based on the provided context.
Context:${ev.context}
Question: ${ev.query}
Answer:`;
const response = await this.llm.complete({ prompt });
// Cache the result const cacheKey = this.getCacheKey(ev.query); await cacheService.set(cacheKey, response.text, 3600);
return new StopEvent({ response: response.text, cached: false, metadata: { documentsUsed: ev.context.split('[Document').length - 1 } }); }
private getCacheKey(query: string): string { return `rag:${createHash('md5').update(query.toLowerCase()).digest('hex')}`; }}import express, { Request, Response } from 'express';import cors from 'cors';import helmet from 'helmet';import rateLimit from 'express-rate-limit';import { RAGChatbotWorkflow } from '../workflows/rag-chatbot.workflow';import { logger } from '../utils/logger';import { config } from '../config';
const app = express();
// Middlewareapp.use(helmet());app.use(cors());app.use(express.json());
// Rate limitingconst limiter = rateLimit({ windowMs: config.RATE_LIMIT_WINDOW, max: config.RATE_LIMIT_MAX, message: 'Too many requests, please try again later'});app.use('/api/', limiter);
// Initialize workflowconst workflow = new RAGChatbotWorkflow();let isInitialized = false;
// Health checkapp.get('/health', (req: Request, res: Response) => { res.json({ status: 'healthy', initialized: isInitialized, timestamp: new Date().toISOString() });});
// Chat endpointapp.post('/api/chat', async (req: Request, res: Response) => { try { const { query, topK } = req.body;
if (!query) { return res.status(400).json({ success: false, error: 'Query is required' }); }
logger.info(`Processing query: ${query}`);
// Run workflow const result = await workflow.run({ query, topK });
res.json({ success: true, data: { response: result.data.response, cached: result.data.cached, metadata: result.data.metadata } }); } catch (error) { logger.error('Chat error:', error); res.status(500).json({ success: false, error: error instanceof Error ? error.message : 'Internal server error' }); }});
// Streaming chat endpointapp.post('/api/chat/stream', async (req: Request, res: Response) => { try { const { query } = req.body;
if (!query) { return res.status(400).json({ success: false, error: 'Query is required' }); }
// Set headers for streaming res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive');
// Send status updates res.write(`data: ${JSON.stringify({ status: 'retrieving' })}\n\n`);
const result = await workflow.run({ query });
res.write(`data: ${JSON.stringify({ status: 'generating' })}\n\n`);
// Stream response res.write(`data: ${JSON.stringify({ status: 'complete', response: result.data.response })}\n\n`);
res.end(); } catch (error) { logger.error('Stream error:', error); res.write(`data: ${JSON.stringify({ status: 'error', error: error instanceof Error ? error.message : 'Internal server error' })}\n\n`); res.end(); }});
// Initialize and start serverasync function start() { try { // Initialize workflow await workflow.initialize('./data/documents'); isInitialized = true;
// Start server const PORT = config.PORT || 3000; app.listen(PORT, () => { logger.info(`Server running on port ${PORT}`); logger.info(`Health check: http://localhost:${PORT}/health`); logger.info(`Chat endpoint: http://localhost:${PORT}/api/chat`); }); } catch (error) { logger.error('Startup error:', error); process.exit(1); }}
start();Usage Example
Section titled “Usage Example”# Start servernpm run start
# Query the chatbotcurl -X POST http://localhost:3000/api/chat \ -H "Content-Type: application/json" \ -d '{"query": "What is LlamaIndex?"}'
# Response{ "success": true, "data": { "response": "LlamaIndex is a data framework for LLM applications...", "cached": false, "metadata": { "documentsUsed": 3 } }}Testing
Section titled “Testing”import { RAGChatbotWorkflow } from '../../src/workflows/rag-chatbot.workflow';import { Document } from 'llamaindex';
describe('RAGChatbotWorkflow', () => { let workflow: RAGChatbotWorkflow;
beforeAll(async () => { workflow = new RAGChatbotWorkflow(); // Initialize with test documents await workflow.initialize('./tests/fixtures/documents'); });
it('should process a query successfully', async () => { const result = await workflow.run({ query: 'What is TypeScript?' });
expect(result.data.response).toBeDefined(); expect(typeof result.data.response).toBe('string'); });
it('should use cache on repeated queries', async () => { const query = 'What is Node.js?';
// First query const result1 = await workflow.run({ query }); expect(result1.data.cached).toBe(false);
// Second query (should be cached) const result2 = await workflow.run({ query }); expect(result2.data.cached).toBe(true); });});RECIPE 2: Research Paper Analyzer
Section titled “RECIPE 2: Research Paper Analyzer”Overview
Section titled “Overview”Analyze academic papers with multi-step workflow, extract key information, and generate summaries.
Features
Section titled “Features”- PDF document ingestion
- Multi-step analysis workflow
- Structured information extraction
- Citation tracking
- Batch processing
Implementation
Section titled “Implementation”import { Workflow, StartEvent, StopEvent, Event, step} from 'llama-index-workflows';import { Document, OpenAI } from 'llamaindex';import { z } from 'zod';
// Schema for structured outputconst PaperAnalysisSchema = z.object({ title: z.string(), authors: z.array(z.string()), abstract: z.string(), keyFindings: z.array(z.string()), methodology: z.string(), citations: z.array(z.string()), summary: z.string()});
type PaperAnalysis = z.infer<typeof PaperAnalysisSchema>;
// Custom Eventsclass ExtractionEvent extends Event { text: string;
constructor(data: { text: string }) { super(); this.text = data.text; }}
class AnalysisEvent extends Event { extraction: PaperAnalysis;
constructor(data: { extraction: PaperAnalysis }) { super(); this.extraction = data.extraction; }}
export class PaperAnalyzerWorkflow extends Workflow { private llm = new OpenAI({ model: 'gpt-4' });
@step() async loadPaper(ev: StartEvent): Promise<ExtractionEvent> { const { filePath } = ev.data;
// Load PDF (simplified - use pdf-parse or similar) const text = await this.extractTextFromPDF(filePath);
return new ExtractionEvent({ text }); }
@step() async extractInformation(ev: ExtractionEvent): Promise<AnalysisEvent> { const prompt = `Extract structured information from this research paper:
${ev.text.substring(0, 4000)}
Return a JSON object with:- title- authors (array)- abstract- keyFindings (array)- methodology- citations (array)- summary`;
const response = await this.llm.complete({ prompt });
// Parse JSON response const extraction = JSON.parse(response.text); const validated = PaperAnalysisSchema.parse(extraction);
return new AnalysisEvent({ extraction: validated }); }
@step() async generateReport(ev: AnalysisEvent): Promise<StopEvent> { const report = { ...ev.extraction, analyzedAt: new Date().toISOString(), wordCount: ev.extraction.summary.split(' ').length };
return new StopEvent({ report }); }
private async extractTextFromPDF(filePath: string): Promise<string> { // Implementation depends on PDF library // Using pdf-parse, pdfjs-dist, or similar return 'Paper content...'; }}const workflow = new PaperAnalyzerWorkflow();const result = await workflow.run({ filePath: './papers/research-paper.pdf'});
console.log('Analysis:', result.data.report);RECIPE 3: Code Documentation Generator
Section titled “RECIPE 3: Code Documentation Generator”Overview
Section titled “Overview”Automatically generate API documentation from TypeScript source code.
Features
Section titled “Features”- TypeScript AST parsing
- Function signature extraction
- JSDoc generation
- Markdown output
- Type-safe documentation
Implementation
Section titled “Implementation”import { Workflow, StartEvent, StopEvent, Event, step} from 'llama-index-workflows';import { OpenAI } from 'llamaindex';import * as ts from 'typescript';import * as fs from 'fs';
interface FunctionDoc { name: string; signature: string; parameters: Array<{ name: string; type: string }>; returnType: string; description: string; examples: string[];}
class ParseEvent extends Event { sourceCode: string;
constructor(data: { sourceCode: string }) { super(); this.sourceCode = data.sourceCode; }}
class FunctionsEvent extends Event { functions: Array<{ name: string; signature: string }>;
constructor(data: { functions: Array<{ name: string; signature: string }> }) { super(); this.functions = data.functions; }}
export class DocGeneratorWorkflow extends Workflow { private llm = new OpenAI({ model: 'gpt-4' });
@step() async loadSourceCode(ev: StartEvent): Promise<ParseEvent> { const { filePath } = ev.data; const sourceCode = fs.readFileSync(filePath, 'utf-8');
return new ParseEvent({ sourceCode }); }
@step() async parseAST(ev: ParseEvent): Promise<FunctionsEvent> { const sourceFile = ts.createSourceFile( 'temp.ts', ev.sourceCode, ts.ScriptTarget.Latest, true );
const functions: Array<{ name: string; signature: string }> = [];
const visit = (node: ts.Node) => { if (ts.isFunctionDeclaration(node) && node.name) { const name = node.name.text; const signature = ev.sourceCode.substring(node.pos, node.end); functions.push({ name, signature }); } ts.forEachChild(node, visit); };
visit(sourceFile);
return new FunctionsEvent({ functions }); }
@step() async generateDocs(ev: FunctionsEvent): Promise<StopEvent> { const docs: FunctionDoc[] = [];
for (const func of ev.functions) { const prompt = `Generate documentation for this TypeScript function:
${func.signature}
Provide:1. Description2. Parameter descriptions3. Return value description4. Usage example`;
const response = await this.llm.complete({ prompt });
docs.push({ name: func.name, signature: func.signature, parameters: [], returnType: 'unknown', description: response.text, examples: [] }); }
// Generate markdown const markdown = this.generateMarkdown(docs);
return new StopEvent({ docs, markdown }); }
private generateMarkdown(docs: FunctionDoc[]): string { let md = '# API Documentation\n\n';
for (const doc of docs) { md += `## ${doc.name}\n\n`; md += `${doc.description}\n\n`; md += '```typescript\n'; md += doc.signature; md += '\n```\n\n'; }
return md; }}This recipes guide continues with 7 more comprehensive examples:
- Recipe 4: Multi-Document Comparison
- Recipe 5: Real-time News Agent
- Recipe 6: Data Extraction Pipeline
- Recipe 7: Conversational SQL Agent
- Recipe 8: Knowledge Graph Builder
- Recipe 9: Multi-Step Reasoning Agent
- Recipe 10: Customer Support Triage
Each recipe includes:
- Complete TypeScript implementation
- Workflow 1.0 patterns
- Production-ready code
- Testing examples
- Usage documentation
For comprehensive workflow patterns, see: llamaindex_workflows_typescript_comprehensive_guide.md For production deployment, see: llamaindex_typescript_production_guide.md