@opensaas/stack-rag
v0.18.2
Published
RAG and AI embeddings integration for OpenSaas Stack
Maintainers
Readme
@opensaas/stack-rag
RAG (Retrieval-Augmented Generation) and AI embeddings integration for OpenSaas Stack.
Turn your OpenSaas app into a knowledge base with semantic search capabilities powered by vector embeddings.
Features
- 🤖 Multiple Embedding Providers: OpenAI, Ollama (local), or bring your own
- 🗄️ Flexible Storage: pgvector, SQLite VSS, or JSON-based (for development)
- 🔍 Semantic Search: Natural language queries with relevance scoring
- 🔐 Access Control: All searches respect your existing access control rules
- ⚡ Automatic Embeddings: Auto-generate embeddings when content changes
- 🛠️ MCP Integration: Semantic search tools for AI assistants
- 📊 Multiple Abstraction Levels: From automatic "magic" to low-level control
- ✂️ Text Chunking: Multiple strategies for splitting long documents
- 🚀 Batch Processing: Rate-limited batch embedding generation with progress tracking
Installation
pnpm add @opensaas/stack-rag
# Install your chosen embedding provider
pnpm add openai # For OpenAI embeddings
# OR use Ollama (no package needed - just run Ollama locally)Quick Start
1. Configure RAG in your OpenSaas app
// opensaas.config.ts
import { config, list } from '@opensaas/stack-core'
import { text } from '@opensaas/stack-core/fields'
import { ragPlugin, openaiEmbeddings, pgvectorStorage } from '@opensaas/stack-rag'
import { searchable } from '@opensaas/stack-rag/fields'
export default config({
plugins: [
ragPlugin({
provider: openaiEmbeddings({
apiKey: process.env.OPENAI_API_KEY!,
model: 'text-embedding-3-small',
}),
storage: pgvectorStorage(),
}),
],
db: {
provider: 'postgresql',
url: process.env.DATABASE_URL!,
},
lists: {
Article: list({
fields: {
title: text({ validation: { isRequired: true } }),
// Use searchable() wrapper for automatic embedding generation
content: searchable(text({ validation: { isRequired: true } }), {
provider: 'openai',
dimensions: 1536,
}),
},
}),
},
})What's happening:
- The
searchable()wrapper automatically creates acontentEmbeddingfield - Embeddings are auto-generated whenever
contentchanges - The embedding field respects all your existing access control rules
2. Generate schema and push to database
pnpm generate
pnpm db:push3. Create content (embeddings generated automatically)
import { getContext } from '@/.opensaas/context'
const context = await getContext()
// Embedding is automatically generated from content
await context.db.article.create({
data: {
title: 'Introduction to AI',
content: 'Artificial intelligence is...',
// No need to manually create embedding - it's automatic!
},
})4. Perform semantic search
import { createEmbeddingProvider, createVectorStorage } from '@opensaas/stack-rag'
export async function searchArticles(query: string) {
const context = await getContext()
// Generate embedding for search query
const provider = createEmbeddingProvider({
type: 'openai',
apiKey: process.env.OPENAI_API_KEY!,
})
const queryVector = await provider.embed(query)
// Search for similar articles
const storage = createVectorStorage({ type: 'json' })
const results = await storage.search('Article', 'contentEmbedding', queryVector, {
limit: 10,
minScore: 0.7,
context, // Access control enforced
})
return results
}Local Development with Ollama
For local development without API costs:
import { config, list } from '@opensaas/stack-core'
import { ragPlugin, ollamaEmbeddings, jsonStorage } from '@opensaas/stack-rag'
export default config({
plugins: [
ragPlugin({
provider: ollamaEmbeddings({
baseURL: 'http://localhost:11434',
model: 'nomic-embed-text',
}),
storage: jsonStorage(), // No database extensions needed
}),
],
// ... rest of config
})First, install and run Ollama:
# Install Ollama from https://ollama.ai
ollama pull nomic-embed-text
ollama serveStorage Backends
JSON Storage (Development)
Good for development and small datasets. No database extensions needed.
storage: jsonStorage()pgvector (Production PostgreSQL)
Best for production apps using PostgreSQL. Requires pgvector extension.
storage: pgvectorStorage({ distanceFunction: 'cosine' })Setup:
CREATE EXTENSION vector;SQLite VSS (SQLite)
Good for SQLite-based apps. Requires sqlite-vss extension.
storage: sqliteVssStorage({ distanceFunction: 'cosine' })Field Configuration Patterns
High-Level: searchable() Wrapper (Recommended)
The easiest way to add semantic search to any field:
import { searchable } from '@opensaas/stack-rag/fields'
fields: {
content: searchable(text({ validation: { isRequired: true } }), {
provider: 'openai',
dimensions: 1536,
})
}What it does:
- Automatically creates a companion
contentEmbeddingfield - Links it to the source field (
content) - Auto-generates embeddings when content changes
- Clean, concise syntax
Options:
type SearchableOptions = {
provider?: string // Embedding provider (e.g., 'openai', 'ollama')
dimensions?: number // Vector dimensions (default: 1536)
chunking?: ChunkingConfig // Text chunking configuration
embeddingFieldName?: string // Custom embedding field name (default: `${fieldName}Embedding`)
}Custom embedding field name:
fields: {
body: searchable(text(), {
provider: 'openai',
embeddingFieldName: 'bodyVector', // Instead of 'bodyEmbedding'
})
}Low-Level: Manual embedding() Field
For advanced use cases where you need more control:
import { embedding } from '@opensaas/stack-rag/fields'
fields: {
content: text({ validation: { isRequired: true } }),
contentEmbedding: embedding({
sourceField: 'content',
provider: 'openai',
dimensions: 1536,
autoGenerate: true,
})
}When to use manual pattern:
- Need access to the embedding field in your schema
- Want to store embeddings without a source field
- Building custom embedding pipelines
- Need field-level hooks on the embedding field
Both patterns are fully supported and can be used interchangeably.
MCP Integration
Automatic semantic search tools for AI assistants:
import { config, list } from '@opensaas/stack-core'
import { text } from '@opensaas/stack-core/fields'
import { ragPlugin, openaiEmbeddings } from '@opensaas/stack-rag'
import { embedding } from '@opensaas/stack-rag/fields'
export default config({
plugins: [
ragPlugin({
provider: openaiEmbeddings({ apiKey: process.env.OPENAI_API_KEY! }),
enableMcpTools: true, // Enables semantic_search_article tool
}),
],
mcp: {
enabled: true,
auth: { type: 'better-auth', loginPage: '/sign-in' },
},
lists: {
Article: list({
fields: {
content: text(),
contentEmbedding: embedding({
sourceField: 'content',
autoGenerate: true,
}),
},
}),
},
})Runtime Utilities
The @opensaas/stack-rag/runtime package provides high-level utilities for common RAG operations.
Semantic Search
Simplified API that handles embedding generation and search in one call:
import { semanticSearch } from '@opensaas/stack-rag/runtime'
import { createEmbeddingProvider, createVectorStorage } from '@opensaas/stack-rag'
import { getContext } from '@/.opensaas/context'
const results = await semanticSearch({
listKey: 'Article',
fieldName: 'contentEmbedding',
query: 'articles about machine learning',
provider: createEmbeddingProvider({ type: 'openai', apiKey: process.env.OPENAI_API_KEY! }),
storage: createVectorStorage({ type: 'pgvector' }),
context: await getContext(),
limit: 10,
minScore: 0.7,
})Find Similar Items
Find items similar to a given item by ID:
import { findSimilar } from '@opensaas/stack-rag/runtime'
const similar = await findSimilar({
listKey: 'Article',
fieldName: 'contentEmbedding',
itemId: 'article-123',
storage: createVectorStorage({ type: 'pgvector' }),
context: await getContext(),
limit: 5,
excludeSelf: true, // Don't include the source article
})Text Chunking
Split long documents into smaller chunks for embedding:
import { chunkText } from '@opensaas/stack-rag/runtime'
// Recursive chunking (respects paragraph/sentence boundaries)
const chunks = chunkText(longDocument, {
strategy: 'recursive',
chunkSize: 1000,
chunkOverlap: 200,
})
// Sentence-based chunking (preserves sentences)
const chunks = chunkText(document, {
strategy: 'sentence',
chunkSize: 500,
chunkOverlap: 100,
})
// Token-aware chunking (for token limits)
const chunks = chunkText(document, {
strategy: 'token-aware',
tokenLimit: 500, // ~500 tokens per chunk
chunkOverlap: 50,
})Batch Processing with Rate Limiting
Process large batches of texts with automatic rate limiting:
import { batchProcess } from '@opensaas/stack-rag/runtime'
const result = await batchProcess({
provider: createEmbeddingProvider({ type: 'openai', apiKey: process.env.OPENAI_API_KEY! }),
texts: largeArrayOfTexts,
batchSize: 10,
rateLimit: 60, // 60 requests per minute
onProgress: (progress) => {
console.log(`Progress: ${progress.percentage}% (${progress.processed}/${progress.total})`)
},
})
console.log(`Successfully processed: ${result.stats.successful}`)
console.log(`Failed: ${result.stats.failed}`)Generate Embeddings with Chunking
Generate embeddings for long texts with automatic chunking:
import { generateEmbedding } from '@opensaas/stack-rag/runtime'
// Single embedding
const embedding = await generateEmbedding({
provider: createEmbeddingProvider({ type: 'openai', apiKey: process.env.OPENAI_API_KEY! }),
text: 'Short text',
})
// Chunked embeddings for long documents
const chunkedEmbeddings = await generateEmbedding({
provider: createEmbeddingProvider({ type: 'openai', apiKey: process.env.OPENAI_API_KEY! }),
text: veryLongDocument,
enableChunking: true,
chunking: { chunkSize: 1000, chunkOverlap: 200 },
})
// Each chunk has its embedding
for (const { chunk, embedding } of chunkedEmbeddings) {
console.log(`Chunk ${chunk.index}: ${chunk.text.substring(0, 50)}...`)
await saveChunkEmbedding(chunk, embedding)
}API Reference
Main Exports (@opensaas/stack-rag)
ragPlugin(config)- RAG plugin for OpenSaas Stack (v0.2.0+)openaiEmbeddings(config)- OpenAI embedding provider helperollamaEmbeddings(config)- Ollama embedding provider helperpgvectorStorage(config)- pgvector storage helpersqliteVssStorage(config)- SQLite VSS storage helperjsonStorage()- JSON-based storage helper
Field Types (@opensaas/stack-rag/fields)
embedding(options)- Vector embedding field type
Providers (@opensaas/stack-rag/providers)
createEmbeddingProvider(config)- Factory for creating embedding providersregisterEmbeddingProvider(type, factory)- Register custom providers
Storage (@opensaas/stack-rag/storage)
createVectorStorage(config)- Factory for creating storage backendsregisterVectorStorage(type, factory)- Register custom storage backends
Runtime Utilities (@opensaas/stack-rag/runtime)
semanticSearch(options)- High-level semantic searchfindSimilar(options)- Find similar items by IDchunkText(text, options)- Text chunking utilitiesgenerateEmbedding(options)- Generate embeddings with chunking supportgenerateEmbeddings(options)- Batch embedding generationbatchProcess(options)- Batch processing with rate limitingRateLimiter- Rate limiting utility classProcessingQueue- Concurrent processing queue
Documentation
See CLAUDE.md for comprehensive documentation including:
- All abstraction levels (high-level to low-level)
- Custom embedding providers
- Custom storage backends
- Text chunking strategies
- Performance optimization
- Testing patterns
- Migration guides
Examples
See examples/rag-demo for a complete working example with:
- Document search
- Chatbot with knowledge base
- MCP integration
- Multiple embedding providers
Repository
- GitHub: https://github.com/OpenSaasAU/stack
- Docs: https://stack.opensaas.au/
- Issues: https://github.com/OpenSaasAU/stack/issues
License
MIT
