Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Provides Retrieval-Augmented Generation (RAG) implementation patterns with LangChain4j for Java. Generates document ingestion pipelines, embedding stores, vector search, and semantic search capabilities. Use when building chat-with-documents systems, document Q&A over PDFs or text files, AI assistants with knowledge bases, semantic search over document repositories, or knowledge-enhanced AI applications with source attribution.
.claude/skills/giuseppe-trisciuoglio-langchain4j-rag-implementation-patterns/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-13 | ✗→✓ | ▲ Improved | 195% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 24% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 35% | 0% |
| case-17 | ✓→✗ | ▼ Worse | 97% | 0% |
| case-18 | ✓→✗ | ▼ Worse | 71% | 0% |
Implements RAG systems with LangChain4j: document ingestion pipelines, embedding stores, and vector search for chat-with-documents and knowledge-enhanced AI applications.
Create a new Spring Boot project with required dependencies:
pom.xml:
xml<dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-spring-boot-starter</artifactId> <version>1.8.0</version> </dependency> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-open-ai</artifactId> <version>1.8.0</version> </dependency>
Configure document loading and processing with validation:
Validation Checkpoint: After ingestion, verify embedding count matches segment count and test retrieval with a sample query.
java@Configuration public class RAGConfiguration { @Bean public EmbeddingModel embeddingModel() { return OpenAiEmbeddingModel.builder() .apiKey(System.getenv("OPENAI_API_KEY")) .modelName("text-embedding-3-small") .build(); } @Bean public EmbeddingStore<TextSegment> embeddingStore() { return new InMemoryEmbeddingStore<>(); } }
Create document ingestion service:
java@Service @RequiredArgsConstructor public class DocumentIngestionService { private final EmbeddingModel embeddingModel; private final EmbeddingStore<TextSegment> embeddingStore; public void ingestDocument(String filePath, Map<String, Object> metadata) { Document document = FileSystemDocumentLoader.loadDocument(filePath); document.metadata().putAll(metadata); DocumentSplitter splitter = DocumentSplitters.recursive( 500, 50, new OpenAiTokenCountEstimator("text-embedding-3-small") ); List<TextSegment> segments = splitter.split(document); List<Embedding> embeddings = embeddingModel.embedAll(segments).content(); embeddingStore.addAll(embeddings, segments); // Validation: verify embedding count matches segments if (embeddings.size() != segments.size()) { throw new IllegalStateException("Embedding count mismatch: expected " + segments.size() + ", got " + embeddings.size()); } } public boolean validateIngestion(String testQuery) { // Validation: test retrieval with sample query Embedding queryEmbedding = embeddingModel.embed(testQuery).content(); List<EmbeddingMatch<TextSegment>> results = embeddingStore.search( EmbeddingSearchRequest.builder() .queryEmbedding(queryEmbedding) .maxResults(1) .build() ).matches(); return !results.isEmpty(); } }
Setup content retrieval with filtering:
Validation Checkpoint: After configuration, test retrieval with a known query to verify embeddings are searchable.
java@Configuration public class ContentRetrieverConfiguration { @Bean public ContentRetriever contentRetriever( EmbeddingStore<TextSegment> embeddingStore, EmbeddingModel embeddingModel) { return EmbeddingStoreContentRetriever.builder() .embeddingStore(embeddingStore) .embeddingModel(embeddingModel) .maxResults(5) .minScore(0.7) .build(); } }
Define AI service with context retrieval:
javainterface KnowledgeAssistant { @SystemMessage(""" You are a knowledgeable assistant with access to a comprehensive knowledge base. When answering questions: 1. Use the provided context from the knowledge base 2. If information is not in the context, clearly state this 3. Provide accurate, helpful responses 4. When possible, reference specific sources 5. If the context is insufficient, ask for clarification """) String answerQuestion(String question); } @Service @RequiredArgsConstructor public class KnowledgeService { private final KnowledgeAssistant assistant; public KnowledgeService(ChatModel chatModel, ContentRetriever contentRetriever) { this.assistant = AiServices.builder(KnowledgeAssistant.class) .chatModel(chatModel) .contentRetriever(contentRetriever) .build(); } public String answerQuestion(String question) { return assistant.answerQuestion(question); } }
javapublic class BasicRAGExample { public static void main(String[] args) { var embeddingStore = new InMemoryEmbeddingStore<TextSegment>(); var embeddingModel = OpenAiEmbeddingModel.builder() .apiKey(System.getenv("OPENAI_API_KEY")) .modelName("text-embedding-3-small") .build(); var ingestor = EmbeddingStoreIngestor.builder() .embeddingModel(embeddingModel) .embeddingStore(embeddingStore) .build(); ingestor.ingest(Document.from("Spring Boot is a framework for building Java applications with minimal configuration.")); var retriever = EmbeddingStoreContentRetriever.builder() .embeddingStore(embeddingStore) .embeddingModel(embeddingModel) .build(); } }
javainterface MultiDomainAssistant { @SystemMessage(""" You are an expert assistant with access to multiple knowledge domains: - Technical documentation - Company policies - Product information - Customer support guides Tailor your response based on the type of question and available context. Always indicate which domain the information comes from. """) String answerQuestion(@MemoryId String userId, String question); }
java@Service @RequiredArgsConstructor public class HierarchicalRAGService { private final EmbeddingStore<TextSegment> chunkStore; private final EmbeddingStore<TextSegment> summaryStore; private final EmbeddingModel embeddingModel; public String performHierarchicalRetrieval(String query) { List<EmbeddingMatch<TextSegment>> summaryMatches = searchSummaries(query); List<TextSegment> relevantChunks = new ArrayList<>(); for (EmbeddingMatch<TextSegment> summaryMatch : summaryMatches) { String documentId = summaryMatch.embedded().metadata().getString("documentId"); List<EmbeddingMatch<TextSegment>> chunkMatches = searchChunksInDocument(query, documentId); chunkMatches.stream() .map(EmbeddingMatch::embedded) .forEach(relevantChunks::add); } return generateResponseWithChunks(query, relevantChunks); } }
java@RequiredArgsConstructor @Service public class SimpleRAGPipeline { private final EmbeddingModel embeddingModel; private final EmbeddingStore<TextSegment> embeddingStore; private final ChatModel chatModel; public String answerQuestion(String question) { Embedding queryEmbedding = embeddingModel.embed(question).content(); EmbeddingSearchRequest request = EmbeddingSearchRequest.builder() .queryEmbedding(queryEmbedding) .maxResults(3) .build(); List<TextSegment> segments = embeddingStore.search(request).matches().stream() .map(EmbeddingMatch::embedded) .collect(Collectors.toList()); String context = segments.stream() .map(TextSegment::text) .collect(Collectors.joining("\n\n")); return chatModel.generate(context + "\n\nQuestion: " + question + "\nAnswer:"); } }
java@Service @RequiredArgsConstructor public class HybridSearchService { private final EmbeddingStore<TextSegment> vectorStore; private final FullTextSearchEngine keywordEngine; private final EmbeddingModel embeddingModel; public List<Content> hybridSearch(String query, int maxResults) { // Vector search List<Content> vectorResults = performVectorSearch(query, maxResults); // Keyword search List<Content> keywordResults = performKeywordSearch(query, maxResults); // Combine and re-rank using RRF algorithm return combineResults(vectorResults, keywordResults, maxResults); } }
Embedding Count Mismatch: Thrown when segments != embeddings. Check splitter configuration and model availability.
Empty Retrieval Results: Call validateIngestion(testQuery) to verify embeddings are searchable. Check if document was ingested successfully.
Low Retrieval Scores: Verify minScore threshold (default 0.7) is not too high for your use case. Test with known queries.
Poor Retrieval Results
Slow Performance
High Memory Usage
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-20 | pass→pass | 10,284 | 8,557 | -17% | 1 | 1 | 0% | 1,982 | 4,442 | +124% | 0 | 0 | — |
case-21 | pass→pass | 12,937 | 18,487 | +43% | 1 | 1 | 0% | 2,607 | 4,377 | +68% | 0 | 0 | — |
case-22 | pass→pass | 10,181 | 8,463 | -17% | 1 | 1 | 0% | 1,905 | 4,392 | +131% | 0 | 0 | — |
case-13 | fail→pass | 7,728 | 6,457 | -16% | 1 | 1 | 0% | 1,343 | 3,956 | +195% | 0 | 0 | — |
case-14 | pass→pass | 14,110 | 17,765 | +26% | 1 | 1 | 0% | 2,774 | 4,728 | +70% | 0 | 0 | — |
case-01 | fail→fail | 9,325 | 7,816 | -16% | 1 | 1 | 0% | 1,975 | 4,441 | +125% | 0 | 0 | — |
case-02 | fail→fail | 14,541 | 13,695 | -6% | 1 | 1 | 0% | 2,780 | 5,223 | +88% | 0 | 0 | — |
case-03 | fail→pass | 18,460 | 8,652 | -53% | 1 | 1 | 0% | 3,508 | 4,360 | +24% | 0 | 0 | — |
case-04 | pass→pass | 14,686 | 25,437 | +73% | 1 | 1 | 0% | 2,598 | 4,920 | +89% | 0 | 0 | — |
case-05 | pass→pass | 10,773 | 6,359 | -41% | 1 | 1 | 0% | 2,068 | 3,813 | +84% | 0 | 0 | — |
case-06 | fail→fail | 11,119 | 9,541 | -14% | 1 | 1 | 0% | 1,809 | 4,211 | +133% | 0 | 0 | — |
case-07 | fail→fail | 16,591 | 14,771 | -11% | 1 | 1 | 0% | 3,405 | 5,784 | +70% | 0 | 0 | — |
case-08 | pass→pass | 16,490 | 16,511 | +0% | 1 | 1 | 0% | 3,284 | 6,111 | +86% | 0 | 0 | — |
case-09 | pass→pass | 19,098 | 12,730 | -33% | 1 | 1 | 0% | 3,608 | 5,160 | +43% | 0 | 0 | — |
case-10 | pass→pass | 11,197 | 9,385 | -16% | 1 | 1 | 0% | 1,985 | 4,417 | +123% | 0 | 0 | — |
case-11 | fail→pass | 14,326 | 4,824 | -66% | 1 | 1 | 0% | 2,538 | 3,434 | +35% | 0 | 0 | — |
case-12 | pass→pass | 25,204 | 9,455 | -62% | 1 | 1 | 0% | 2,397 | 4,288 | +79% | 0 | 0 | — |
case-15 | pass→pass | 9,014 | 4,869 | -46% | 1 | 1 | 0% | 1,705 | 3,578 | +110% | 0 | 0 | — |
case-16 | fail→fail | 12,146 | 9,024 | -26% | 1 | 1 | 0% | 1,938 | 3,942 | +103% | 0 | 0 | — |
case-17 | pass→fail | 17,977 | 13,649 | -24% | 1 | 1 | 0% | 2,536 | 5,000 | +97% | 0 | 0 | — |
case-18 | pass→fail | 17,982 | 12,821 | -29% | 1 | 1 | 0% | 3,035 | 5,186 | +71% | 0 | 0 | — |
case-19 | pass→pass | 18,901 | 15,195 | -20% | 1 | 1 | 0% | 2,870 | 4,965 | +73% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +5 percentage points is the difference between those two pass rates over the 22 comparable cases. 2 cases got worse with the skill loaded, and they are included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.