ai-functions
Version:
Core AI primitives for building intelligent applications
213 lines (179 loc) • 6.13 kB
text/typescript
/**
* RAG Chatbot Example
*
* This example demonstrates building a Retrieval-Augmented Generation (RAG) chatbot
* using ai-functions. It shows how to:
* - Generate embeddings for documents
* - Perform semantic search
* - Generate context-aware responses
*
* @example
* ```bash
* ANTHROPIC_API_KEY=sk-... npx tsx examples/01-rag-chatbot.ts
* ```
*/
import {
write,
list,
configure,
EmbeddingCache,
MemoryCache,
GenerationCache,
withRetry,
} from '../src/index.js'
// ============================================================================
// Document Store (In-memory for this example)
// ============================================================================
interface Document {
id: string
content: string
embedding?: number[]
}
const documents: Document[] = [
{
id: 'doc-1',
content:
'ai-functions is a TypeScript library that simplifies AI integration. It provides template literals for natural AI calls like `const poem = await write`a haiku about TypeScript``.',
},
{
id: 'doc-2',
content:
'The library supports batch processing with 50% cost savings through provider batch APIs. Use createBatch() to process large workloads efficiently.',
},
{
id: 'doc-3',
content:
'Built-in retry logic with exponential backoff handles rate limits automatically. Use withRetry() or RetryPolicy for custom retry behavior.',
},
{
id: 'doc-4',
content:
'Budget tracking monitors token usage and costs. BudgetTracker supports alerts at configurable thresholds and enforces spending limits.',
},
{
id: 'doc-5',
content:
'The list primitive generates arrays with automatic batching. Use list`5 ideas`.map() to process each item in parallel.',
},
]
// ============================================================================
// Simple Embedding Simulation (replace with real embeddings in production)
// ============================================================================
function simulateEmbedding(text: string): number[] {
// Simple word-based embedding simulation
// In production, use a real embedding model
const words = text.toLowerCase().split(/\s+/)
const embedding = new Array(128).fill(0)
for (const word of words) {
const hash = word.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0)
embedding[hash % 128] += 1
}
// Normalize
const magnitude = Math.sqrt(embedding.reduce((acc, val) => acc + val * val, 0))
return embedding.map((val) => (magnitude > 0 ? val / magnitude : 0))
}
function cosineSimilarity(a: number[], b: number[]): number {
let dotProduct = 0
for (let i = 0; i < a.length; i++) {
dotProduct += a[i] * b[i]
}
return dotProduct
}
// ============================================================================
// RAG Chatbot Implementation
// ============================================================================
class RAGChatbot {
private documents: Document[]
private responseCache: GenerationCache
constructor(documents: Document[]) {
this.documents = documents
this.responseCache = new GenerationCache({
defaultTTL: 3600000, // 1 hour cache
})
// Generate embeddings for all documents
for (const doc of this.documents) {
doc.embedding = simulateEmbedding(doc.content)
}
}
/**
* Find the most relevant documents for a query
*/
private findRelevantDocs(query: string, topK: number = 3): Document[] {
const queryEmbedding = simulateEmbedding(query)
const scored = this.documents.map((doc) => ({
doc,
score: cosineSimilarity(queryEmbedding, doc.embedding!),
}))
return scored
.sort((a, b) => b.score - a.score)
.slice(0, topK)
.map(({ doc }) => doc)
}
/**
* Generate a response using RAG
*/
async chat(userMessage: string): Promise<string> {
console.log(`\nUser: ${userMessage}`)
console.log('---')
// Step 1: Retrieve relevant documents
const relevantDocs = this.findRelevantDocs(userMessage)
console.log(`Found ${relevantDocs.length} relevant documents`)
// Step 2: Build context from retrieved documents
const context = relevantDocs.map((doc, i) => `[Source ${i + 1}]: ${doc.content}`).join('\n\n')
// Step 3: Generate response with context
const response =
await write`You are a helpful assistant that answers questions about ai-functions library.
Context from documentation:
${context}
User Question: ${userMessage}
Please provide a helpful, accurate response based on the context above. If the context doesn't contain enough information, say so.`
console.log(`Assistant: ${response}`)
return response
}
/**
* Generate follow-up questions
*/
async suggestFollowUps(topic: string): Promise<string[]> {
const suggestions =
await list`3 follow-up questions someone might ask about ${topic} related to ai-functions library`
return suggestions
}
}
// ============================================================================
// Main Example
// ============================================================================
async function main() {
console.log('\n=== RAG Chatbot Example ===\n')
// Configure the AI provider
configure({
model: 'sonnet',
provider: 'anthropic',
})
// Initialize the chatbot with our documents
const chatbot = new RAGChatbot(documents)
// Example conversation
const questions = [
'How do I generate a list of items?',
'What are the cost savings for batch processing?',
'How does retry handling work?',
]
for (const question of questions) {
await chatbot.chat(question)
console.log('')
}
// Generate follow-up suggestions
console.log('\n--- Suggested follow-up questions ---')
const followUps = await chatbot.suggestFollowUps('batch processing')
for (const suggestion of followUps) {
console.log(`- ${suggestion}`)
}
}
main()
.then(() => {
console.log('\n=== Example Complete ===\n')
process.exit(0)
})
.catch((error) => {
console.error('\nError:', error.message)
process.exit(1)
})