Retrieval-Augmented Generation (RAG) Architecture
This document details the RAG architecture, chunking pipeline, object storage options (MinIO / S3 vs Local), and vector retrieval mechanisms implemented in tuluat-engine.
1. High-Level RAG Architecture
The RAG subsystem (ADR 008) enhances AI agent prompts with context retrieved from internal domain documents, runbooks, or knowledge bases.
sequenceDiagram
participant App as Application / Operator
participant RagSvc as RagService
participant Storage as MinIO / S3 ObjectStorage
participant Chunker as RecursiveCharacterChunker
participant Embedder as EmbeddingProvider (Spring AI / Local)
participant VectorDB as PgVectorRetriever (PostgreSQL + HNSW)
rect rgb(235, 245, 255)
note over App, VectorDB: Document Ingestion Flow
App->>RagSvc: ingest(sourceRef, content, chunkConfig)
RagSvc->>Storage: put("documents/{sourceRef}/{docId}.txt", rawBytes)
RagSvc->>Chunker: chunk(content, sourceRef, config)
Chunker-->>RagSvc: List<TextChunk> (1200 chars, 150 overlap)
loop For each chunk
RagSvc->>Embedder: embed(chunk.content)
Embedder-->>RagSvc: float[1536] vector
RagSvc->>VectorDB: storeChunk(chunk, vector, docId)
end
end
rect rgb(240, 255, 240)
note over App, VectorDB: Agent Retrieval & Grounding Flow
App->>RagSvc: retrieveAsPrompt(query, topK=3)
RagSvc->>Embedder: embed(query)
Embedder-->>RagSvc: queryVector[1536]
RagSvc->>VectorDB: retrieve(queryVector, topK=3)
VectorDB-->>RagSvc: List<RetrievedChunk> (Cosine Similarity >= threshold)
RagSvc-->>App: Formatted RagContext System Prompt Payload
end
2. Document Chunking Strategy (RecursiveCharacterChunker)
Document text is processed by RecursiveCharacterChunker to create retrieval-optimized context windows:
- Target Chunk Size:
1200characters (configurable viaChunkConfig). - Chunk Overlap:
150characters shared between consecutive chunks to prevent semantic boundary loss. - Hierarchy of Separators:
\n\n(Paragraph boundaries)\n(Line boundaries).(Sentence boundaries)(Word boundaries)- Fallback character split if single segment exceeds target size.
3. Object Storage Options (MinIO / S3 vs Local)
Raw uploaded documents are preserved in binary object storage for auditability and re-indexing.
3.1 MinIO / S3 Compatibility (S3ObjectStorage)
- Active when:
tuluat.rag.storage.type=s3 - SDK: Uses MinIO Java SDK (
io.minio:minio:8.5.12). - Ingestion Pipeline: When
RagService.ingest("manuals/k8s", text)is called, the original document is stored in bucketrag-documentsatdocuments/manuals/k8s/{docId}.txtand concurrently chunked & stored inpgvector.
3.2 Development Filesystem Storage (LocalObjectStorage)
- Active when:
tuluat.rag.storage.type=local(default) - Stores raw files locally under
./data/rag/documents/...with path traversal protection.
4. Vector Storage & Search (PgVectorRetriever)
Chunks and embeddings are stored in PostgreSQL using the pgvector extension:
CREATE TABLE IF NOT EXISTS rag_chunks (
id BIGSERIAL PRIMARY KEY,
doc_id VARCHAR(255) NOT NULL,
source_ref VARCHAR(512) NOT NULL,
chunk_index INT NOT NULL,
content TEXT NOT NULL,
embedding vector(1536),
metadata JSONB DEFAULT '{}'::jsonb,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- Cosine distance indexing via HNSW
CREATE INDEX IF NOT EXISTS idx_rag_chunks_embedding
ON rag_chunks USING hnsw (embedding vector_cosine_ops);
5. Spring Boot Property Configuration
tuluat:
rag:
retriever: pgvector # "memory" (dev/test) or "pgvector" (production)
embedding: spring-ai # "local-hash" (testing) or "spring-ai" (OpenAI/Ollama)
embedding-dimension: 1536
storage:
type: s3 # "local" or "s3"
local-dir: ./data/rag
s3:
endpoint: http://minio-service:9000
bucket: rag-documents
access-key: ${MINIO_ACCESS_KEY}
secret-key: ${MINIO_SECRET_KEY}