RAG Pipeline
CaseSolo's retrieval-augmented generation pipeline stores firm-scoped source chunks in PostgreSQL pgvector and converts retrieval hits into citation-ready SourceSpan records for AI outputs.
Core Flow
- Source text comes from a
DocumentorNote. chunkText()creates token-bounded, overlapping chunks with original character offsets.embedTexts()generates embeddings through the configured provider.writeChunks()replaces stale chunks for the source and writes fresh rows todocument_chunks.semanticSearch()embeds a query and performs firm-scoped pgvector cosine search.retrieveCitations()maps search hits into source spans that can be attached to AI envelopes and persisted onAgentRun.sourceSpans.
Important Files
src/lib/ai/chunking.ts- token-aware chunking and offsets.src/lib/ai/embedding-providers.ts- OpenAI, Voyage, Cohere, and fallback provider chain.src/lib/ai/embeddings.ts- stable embedding facade and pgvector literal helpers.src/lib/ai/rag-pipeline.ts- namespace helpers, ingestion job IDs, and vector-dimension compatibility guard.src/lib/ai/ingestion.ts- document/note ingestion and transactional chunk writes.src/lib/ai/retrieval.ts- firm/case/source scoped semantic search.src/lib/ai/citation.ts- source-span and prompt-source formatting helpers.src/lib/jobs/processors/rag-ingestion.ts- dedicated document/note RAG ingestion job processor.src/lib/jobs/workers.ts-enqueueRagIngestion()and worker registration.
Namespace And Tenant Safety
Every chunk row stores firmId, optional caseId, sourceType, and source ID. Retrieval requires firmId, and optional case/source filters are applied in fixed SQL templates rather than dynamically interpolated SQL. buildRagNamespace() produces deterministic keys for firm-level and case-level telemetry and future cache keys.
Vector Dimension Policy
The database column is currently document_chunks.embedding vector(1536). assertRagVectorCompatibility() fails fast when the active embedding provider emits a different dimension. This prevents a 1024-dimension provider configuration from reaching the database and producing harder-to-debug pgvector errors.
If the team intentionally switches to a 1024-dimension provider, ship a migration that changes the vector column and HNSW index, then re-ingest all chunks.
Job Path
Use enqueueRagIngestion() for explicit RAG ingestion jobs:
await enqueueRagIngestion({
sourceType: 'DOCUMENT',
sourceId: documentId,
firmId,
caseId,
requestedBy: userId,
})
When REDIS_URL is not configured, the enqueue helper processes synchronously. When jobs are configured, it writes to the BullMQ-compatible casesolo-rag-ingestion queue with deterministic job IDs.
The document-processing worker still runs OCR first and then calls ingestDocument() directly so uploaded files become searchable without requiring a second queue hop.
Verification
Run the focused RAG suite:
npm run test:run -- rag-pipeline rag-ingestion ingestion retrieval citation
Run targeted lint and typecheck:
npx eslint src/lib/ai/rag-pipeline.ts src/lib/ai/ingestion.ts src/lib/ai/retrieval.ts src/lib/jobs/processors/rag-ingestion.ts src/lib/jobs/queue.ts src/lib/jobs/workers.ts src/__tests__/lib/rag-pipeline.test.ts src/__tests__/lib/rag-ingestion.test.ts src/__tests__/lib/ingestion.test.ts src/__tests__/lib/retrieval.test.ts
npm run typecheck