Files
spring-ai/rag/docs/01-the-shape-of-a-rag-pipeline.md
T

64 lines
4.0 KiB
Markdown

# 1. The shape of a RAG pipeline
[Index](../README.md) · next: [2. Chunking](02-chunking.md)
A language model only knows what it was trained on and what is in the prompt. Retrieval-augmented generation
(RAG) puts the right few paragraphs of *your* documents into the prompt, so the model can answer from them.
Everything else in this repository is a decision about which paragraphs, how they get there, and what to do
when the answer is not in them.
## Two phases, six stages
```
ingest (once per file) query (once per question)
────────────────────── ─────────────────────────
PDF ─► read pages ─► chunk ─► embed ─► store question ─► embed ─► search ─► rerank ─► prompt ─► model ─► check
IngestionService VectorStore └────── RetrievalAugmentationAdvisor ──────┘ RagQueryService
```
| Stage | Class here | Spring AI part it stands on |
|---|---|---|
| read | [`IngestionService`](../src/main/java/com/ankurm/rag/ingest/IngestionService.java) | `PagePdfDocumentReader` |
| chunk | `DocumentTransformer chunker` in [`RagConfig`](../src/main/java/com/ankurm/rag/config/RagConfig.java) | `TokenTextSplitter` (or [`RecursiveChunker`](../src/main/java/com/ankurm/rag/chunk/RecursiveChunker.java), [`SemanticChunker`](../src/main/java/com/ankurm/rag/chunk/SemanticChunker.java)) |
| embed + store | `VectorStore` bean | `PgVectorStore`, built by the pgvector starter |
| search | `retrievalAdvisor` bean | `VectorStoreDocumentRetriever` |
| rerank | [`LlmReranker`](../src/main/java/com/ankurm/rag/query/LlmReranker.java) | the `DocumentPostProcessor` hook (Spring AI ships no reranker) |
| prompt | `retrievalAdvisor` bean | `ContextualQueryAugmenter` |
| model + check | [`RagQueryService`](../src/main/java/com/ankurm/rag/query/RagQueryService.java) | `ChatClient`, `FactCheckingEvaluator` |
The beans as the application actually wires them are in [output 01](output/01-pipeline-beans.txt).
## The smallest thing that works
One advisor, no other class of ours:
```java
ChatClient.builder(chatModel)
.defaultAdvisors(QuestionAnswerAdvisor.builder(vectorStore)
.searchRequest(SearchRequest.builder().topK(2).similarityThreshold(0.3).build())
.build())
.build();
```
That is [`SimpleAdvisorTest`](../src/test/java/com/ankurm/rag/SimpleAdvisorTest.java). [Output 11](output/11-question-answer-advisor.txt)
shows the two prompts it produces. For a question the store can answer, the chunks are pasted between two lines of
dashes. For a question nothing matches, the prompt still has the same template with an **empty** block between the
dashes, and the model is left to follow the template's last sentence ("if the answer is not in the context, inform
the user that you can't answer"). Nothing in code decides that nothing was found. That is the first thing the
larger pipeline adds: see [chapter 4](04-retrieval-and-reranking.md).
## What this repository does not test
The chat model and the embedding model are scripted stand-ins
([`FakeChatModel`](../src/test/java/com/ankurm/rag/support/FakeChatModel.java),
[`HashingEmbeddingModel`](../src/test/java/com/ankurm/rag/support/HashingEmbeddingModel.java)). That is deliberate:
anyone can run every test with no API key, and what is asserted is what Spring AI *sends* and how this code
*handles* what comes back. It also means **nothing here shows how a real model answers, how a real embedding
model scores similarity, whether reranking improves answers, or how good a real judge is at spotting an
invented claim.** Similarity scores and thresholds in the outputs belong to the hashing model; do not carry them over.
The vector store is real: every test that touches persistence runs against PostgreSQL with pgvector
(`scripts/pg-up.sh` or `docker-compose.yml`), and the end-to-end test starts the whole application over HTTP.
Next: [2. Chunking](02-chunking.md)