Add rag module: Spring AI 2.0 RAG with pgvector, chunking, reranking and a faithfulness check
Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01B38FGKKam5SCGgwgduVAh3
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user