From fd9c98661d42ffbd233b29b54f63fbf69af4826e Mon Sep 17 00:00:00 2001 From: Mark Pollack Date: Thu, 16 May 2024 12:29:47 +0200 Subject: [PATCH] API refactoring * Added more natural method names to DocumentReader, Transformer, Writer * Changed Content getMedia to return Collection, deprecated use of List * Change constructor for Media to accept URL and Resource, deprecate Object constructor * Update ETL documentation and a few tests to avoid now deprecated APIs. --- .../ai/anthropic/AnthropicChatClientIT.java | 4 +- .../BedrockAnthropic3ChatClientIT.java | 4 +- .../ollama/OllamaChatClientMultimodalIT.java | 4 +- .../ai/openai/chat/OpenAiChatClientIT.java | 17 +++--- ...OpenAiPromptTransformingChatServiceIT.java | 18 +++++- .../gemini/VertexAiGeminiChatClientIT.java | 4 +- .../ai/chat/messages/AbstractMessage.java | 33 +++++------ .../ai/chat/messages/Media.java | 34 +++++++++++ .../ai/chat/messages/UserMessage.java | 3 +- .../springframework/ai/document/Document.java | 7 +-- .../ai/document/DocumentReader.java | 4 ++ .../ai/document/DocumentTransformer.java | 4 ++ .../ai/document/DocumentWriter.java | 4 ++ .../org/springframework/ai/model/Content.java | 15 ++++- .../ai/transformer/splitter/TextSplitter.java | 8 +++ .../splitter/TokenTextSplitter.java | 4 +- .../modules/ROOT/pages/api/etl-pipeline.adoc | 58 ++++++++++++++----- 17 files changed, 165 insertions(+), 60 deletions(-) diff --git a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatClientIT.java b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatClientIT.java index 92ba0a6eb..ce5b45d37 100644 --- a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatClientIT.java +++ b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatClientIT.java @@ -182,12 +182,12 @@ class AnthropicChatClientIT { @Test void multiModalityTest() throws IOException { - byte[] imageData = new ClassPathResource("/test.png").getContentAsByteArray(); + var imageData = new ClassPathResource("/test.png"); var userMessage = new UserMessage("Explain what do you see on this picture?", List.of(new Media(MimeTypeUtils.IMAGE_PNG, imageData))); - ChatResponse response = chatClient.call(new Prompt(List.of(userMessage))); + var response = chatClient.call(new Prompt(List.of(userMessage))); logger.info(response.getResult().getOutput().getContent()); assertThat(response.getResult().getOutput().getContent()).contains("bananas", "apple", "basket"); diff --git a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic3/BedrockAnthropic3ChatClientIT.java b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic3/BedrockAnthropic3ChatClientIT.java index 8a98b8821..9568f4f69 100644 --- a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic3/BedrockAnthropic3ChatClientIT.java +++ b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic3/BedrockAnthropic3ChatClientIT.java @@ -206,12 +206,12 @@ class BedrockAnthropic3ChatClientIT { @Test void multiModalityTest() throws IOException { - byte[] imageData = new ClassPathResource("/test.png").getContentAsByteArray(); + var imageData = new ClassPathResource("/test.png"); var userMessage = new UserMessage("Explain what do you see o this picture?", List.of(new Media(MimeTypeUtils.IMAGE_PNG, imageData))); - ChatResponse response = client.call(new Prompt(List.of(userMessage))); + var response = client.call(new Prompt(List.of(userMessage))); logger.info(response.getResult().getOutput().getContent()); assertThat(response.getResult().getOutput().getContent()).contains("bananas", "apple", "basket"); diff --git a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatClientMultimodalIT.java b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatClientMultimodalIT.java index 4d587e005..0061ea4b3 100644 --- a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatClientMultimodalIT.java +++ b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatClientMultimodalIT.java @@ -71,12 +71,12 @@ class OllamaChatClientMultimodalIT { @Test void multiModalityTest() throws IOException { - byte[] imageData = new ClassPathResource("/test.png").getContentAsByteArray(); + var imageData = new ClassPathResource("/test.png"); var userMessage = new UserMessage("Explain what do you see on this picture?", List.of(new Media(MimeTypeUtils.IMAGE_PNG, imageData))); - ChatResponse response = client.call(new Prompt(List.of(userMessage))); + var response = client.call(new Prompt(List.of(userMessage))); logger.info(response.getResult().getOutput().getContent()); assertThat(response.getResult().getOutput().getContent()).contains("bananas", "apple", "basket"); diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatClientIT.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatClientIT.java index 099a7394b..0366aa626 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatClientIT.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatClientIT.java @@ -16,6 +16,7 @@ package org.springframework.ai.openai.chat; import java.io.IOException; +import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -249,12 +250,12 @@ class OpenAiChatClientIT extends AbstractIT { @ValueSource(strings = { "gpt-4-vision-preview", "gpt-4o" }) void multiModalityEmbeddedImage(String modelName) throws IOException { - byte[] imageData = new ClassPathResource("/test.png").getContentAsByteArray(); + var imageData = new ClassPathResource("/test.png"); var userMessage = new UserMessage("Explain what do you see on this picture?", List.of(new Media(MimeTypeUtils.IMAGE_PNG, imageData))); - ChatResponse response = chatClient + var response = chatClient .call(new Prompt(List.of(userMessage), OpenAiChatOptions.builder().withModel(modelName).build())); logger.info(response.getResult().getOutput().getContent()); @@ -266,9 +267,9 @@ class OpenAiChatClientIT extends AbstractIT { @ValueSource(strings = { "gpt-4-vision-preview", "gpt-4o" }) void multiModalityImageUrl(String modelName) throws IOException { - var userMessage = new UserMessage("Explain what do you see on this picture?", - List.of(new Media(MimeTypeUtils.IMAGE_PNG, - "https://docs.spring.io/spring-ai/reference/1.0-SNAPSHOT/_images/multimodal.test.png"))); + var userMessage = new UserMessage("Explain what do you see on this picture?", List + .of(new Media(MimeTypeUtils.IMAGE_PNG, + new URL("https://docs.spring.io/spring-ai/reference/1.0-SNAPSHOT/_images/multimodal.test.png")))); ChatResponse response = chatClient .call(new Prompt(List.of(userMessage), OpenAiChatOptions.builder().withModel(modelName).build())); @@ -281,9 +282,9 @@ class OpenAiChatClientIT extends AbstractIT { @Test void streamingMultiModalityImageUrl() throws IOException { - var userMessage = new UserMessage("Explain what do you see on this picture?", - List.of(new Media(MimeTypeUtils.IMAGE_PNG, - "https://docs.spring.io/spring-ai/reference/1.0-SNAPSHOT/_images/multimodal.test.png"))); + var userMessage = new UserMessage("Explain what do you see on this picture?", List + .of(new Media(MimeTypeUtils.IMAGE_PNG, + new URL("https://docs.spring.io/spring-ai/reference/1.0-SNAPSHOT/_images/multimodal.test.png")))); Flux response = streamingChatClient.stream(new Prompt(List.of(userMessage), OpenAiChatOptions.builder().withModel(OpenAiApi.ChatModel.GPT_4_VISION_PREVIEW.getValue()).build())); diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/service/OpenAiPromptTransformingChatServiceIT.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/service/OpenAiPromptTransformingChatServiceIT.java index b164b8343..be45cee3e 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/service/OpenAiPromptTransformingChatServiceIT.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/service/OpenAiPromptTransformingChatServiceIT.java @@ -17,6 +17,7 @@ package org.springframework.ai.openai.chat.service; import java.util.List; +import java.util.function.Supplier; import io.qdrant.client.QdrantClient; import io.qdrant.client.QdrantGrpcClient; @@ -112,7 +113,7 @@ public class OpenAiPromptTransformingChatServiceIT { void loadData() { JsonReader jsonReader = new JsonReader(bikesResource, "name", "price", "shortDescription", "description"); var textSplitter = new TokenTextSplitter(); - List splitDocuments = textSplitter.apply(jsonReader.get()); + List splitDocuments = textSplitter.split(jsonReader.get()); for (Document splitDocument : splitDocuments) { splitDocument.getMetadata().put(TransformerContentType.EXTERNAL_KNOWLEDGE, "true"); @@ -121,6 +122,21 @@ public class OpenAiPromptTransformingChatServiceIT { vectorStore.accept(splitDocuments); } + void loadData2() { + JsonReader jsonReader = null; + TokenTextSplitter tokenTextSplitter = null; + VectorStore vectorStore = null; + + List documents = jsonReader.read(); + List splitDocuments = tokenTextSplitter.split(documents); + vectorStore.write(splitDocuments); + + // Now in java.util.Function style. + + Supplier> docs = jsonReader::read; + + } + @SpringBootConfiguration static class Config { diff --git a/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatClientIT.java b/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatClientIT.java index b9cfa126c..9eff8981e 100644 --- a/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatClientIT.java +++ b/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatClientIT.java @@ -186,12 +186,12 @@ class VertexAiGeminiChatClientIT { @Test void multiModalityTest() throws IOException { - byte[] data = new ClassPathResource("/vertex.test.png").getContentAsByteArray(); + var data = new ClassPathResource("/vertex.test.png"); var userMessage = new UserMessage("Explain what do you see o this picture?", List.of(new Media(MimeTypeUtils.IMAGE_PNG, data))); - ChatResponse response = client.call(new Prompt(List.of(userMessage))); + var response = client.call(new Prompt(List.of(userMessage))); // Response should contain something like: // I see a bunch of bananas in a golden basket. The bananas are ripe and yellow. diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/messages/AbstractMessage.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/messages/AbstractMessage.java index 3c0d7a855..b58cb3c21 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/messages/AbstractMessage.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/chat/messages/AbstractMessage.java @@ -18,12 +18,7 @@ package org.springframework.ai.chat.messages; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; import org.springframework.core.io.Resource; import org.springframework.util.Assert; @@ -44,7 +39,7 @@ public abstract class AbstractMessage implements Message { protected final String textContent; - protected final List mediaData; + protected final List media; /** * Additional options for the message to influence the response, not a generative map. @@ -59,25 +54,25 @@ public abstract class AbstractMessage implements Message { Assert.notNull(messageType, "Message type must not be null"); this.messageType = messageType; this.textContent = content; - this.mediaData = new ArrayList<>(); + this.media = new ArrayList<>(); this.metadata = new HashMap<>(metadata); this.metadata.put(MESSAGE_TYPE, messageType); } - protected AbstractMessage(MessageType messageType, String textContent, List mediaData) { - this(messageType, textContent, mediaData, Map.of(MESSAGE_TYPE, messageType)); + protected AbstractMessage(MessageType messageType, String textContent, List media) { + this(messageType, textContent, media, Map.of(MESSAGE_TYPE, messageType)); } - protected AbstractMessage(MessageType messageType, String textContent, List mediaData, + protected AbstractMessage(MessageType messageType, String textContent, Collection media, Map metadata) { Assert.notNull(messageType, "Message type must not be null"); Assert.notNull(textContent, "Content must not be null"); - Assert.notNull(mediaData, "media data must not be null"); + Assert.notNull(media, "media data must not be null"); this.messageType = messageType; this.textContent = textContent; - this.mediaData = new ArrayList<>(mediaData); + this.media = new ArrayList<>(media); this.metadata = new HashMap<>(metadata); this.metadata.put(MESSAGE_TYPE, messageType); } @@ -94,7 +89,7 @@ public abstract class AbstractMessage implements Message { this.messageType = messageType; this.metadata = new HashMap<>(metadata); this.metadata.put(MESSAGE_TYPE, messageType); - this.mediaData = new ArrayList<>(); + this.media = new ArrayList<>(); try (InputStream inputStream = resource.getInputStream()) { this.textContent = StreamUtils.copyToString(inputStream, Charset.defaultCharset()); @@ -110,8 +105,8 @@ public abstract class AbstractMessage implements Message { } @Override - public List getMedia() { - return this.mediaData; + public List getMedia(String... dummy) { + return this.media; } @Override @@ -126,7 +121,7 @@ public abstract class AbstractMessage implements Message { @Override public int hashCode() { - return Objects.hash(this.messageType, this.textContent, this.mediaData, this.metadata); + return Objects.hash(this.messageType, this.textContent, this.media, this.metadata); } @Override @@ -139,8 +134,8 @@ public abstract class AbstractMessage implements Message { } AbstractMessage other = (AbstractMessage) obj; return Objects.equals(this.messageType, other.messageType) - && Objects.equals(this.textContent, other.textContent) - && Objects.equals(this.mediaData, other.mediaData) && Objects.equals(this.metadata, other.metadata); + && Objects.equals(this.textContent, other.textContent) && Objects.equals(this.media, other.media) + && Objects.equals(this.metadata, other.metadata); } } diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/messages/Media.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/messages/Media.java index ffe377938..7077230b8 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/messages/Media.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/chat/messages/Media.java @@ -15,9 +15,13 @@ */ package org.springframework.ai.chat.messages; +import org.springframework.core.io.Resource; import org.springframework.util.Assert; import org.springframework.util.MimeType; +import java.io.IOException; +import java.net.URL; + /** * The Media class represents the data and metadata of a media attachment in a message. It * consists of a MIME type and the raw data. @@ -33,16 +37,46 @@ public class Media { private final Object data; + /** + * The Media class represents the data and metadata of a media attachment in a + * message. It consists of a MIME type and the raw data. + * + * This class is used as a parameter in the constructor of the UserMessage class. + * @deprecated This constructor is deprecated since version 1.0.0 M1 and will be + * removed in a future release. + */ + @Deprecated(since = "1.0.0 M1", forRemoval = true) public Media(MimeType mimeType, Object data) { Assert.notNull(mimeType, "MimeType must not be null"); this.mimeType = mimeType; this.data = data; } + public Media(MimeType mimeType, URL url) { + Assert.notNull(mimeType, "MimeType must not be null"); + this.mimeType = mimeType; + this.data = url.toString(); + } + + public Media(MimeType mimeType, Resource resource) { + Assert.notNull(mimeType, "MimeType must not be null"); + this.mimeType = mimeType; + try { + this.data = resource.getContentAsByteArray(); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + public MimeType getMimeType() { return this.mimeType; } + /** + * Get the media data object + * @return a java.net.URL.toString() or a byte[] + */ public Object getData() { return this.data; } diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/messages/UserMessage.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/messages/UserMessage.java index e792c985b..85f071eeb 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/messages/UserMessage.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/chat/messages/UserMessage.java @@ -16,6 +16,7 @@ package org.springframework.ai.chat.messages; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.Map; @@ -44,7 +45,7 @@ public class UserMessage extends AbstractMessage { this(textContent, Arrays.asList(media)); } - public UserMessage(String textContent, List mediaList, Map metadata) { + public UserMessage(String textContent, Collection mediaList, Map metadata) { super(MessageType.USER, textContent, mediaList, metadata); } diff --git a/spring-ai-core/src/main/java/org/springframework/ai/document/Document.java b/spring-ai-core/src/main/java/org/springframework/ai/document/Document.java index 30c4b479a..dbc5fac8f 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/document/Document.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/document/Document.java @@ -15,10 +15,7 @@ */ package org.springframework.ai.document; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -112,7 +109,7 @@ public class Document implements Content { } @Override - public List getMedia() { + public List getMedia(String... dummy) { return this.media; } diff --git a/spring-ai-core/src/main/java/org/springframework/ai/document/DocumentReader.java b/spring-ai-core/src/main/java/org/springframework/ai/document/DocumentReader.java index 9d93a218b..75b4fe2b2 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/document/DocumentReader.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/document/DocumentReader.java @@ -20,4 +20,8 @@ import java.util.function.Supplier; public interface DocumentReader extends Supplier> { + default List read() { + return get(); + } + } diff --git a/spring-ai-core/src/main/java/org/springframework/ai/document/DocumentTransformer.java b/spring-ai-core/src/main/java/org/springframework/ai/document/DocumentTransformer.java index 1253d6816..8c325a7bd 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/document/DocumentTransformer.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/document/DocumentTransformer.java @@ -20,4 +20,8 @@ import java.util.function.Function; public interface DocumentTransformer extends Function, List> { + default List transform(List transform) { + return apply(transform); + } + } diff --git a/spring-ai-core/src/main/java/org/springframework/ai/document/DocumentWriter.java b/spring-ai-core/src/main/java/org/springframework/ai/document/DocumentWriter.java index b91dc9a2e..31aeaf905 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/document/DocumentWriter.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/document/DocumentWriter.java @@ -23,4 +23,8 @@ import java.util.function.Consumer; */ public interface DocumentWriter extends Consumer> { + default void write(List documents) { + accept(documents); + } + } diff --git a/spring-ai-core/src/main/java/org/springframework/ai/model/Content.java b/spring-ai-core/src/main/java/org/springframework/ai/model/Content.java index 4ecaf351b..ea1eb741a 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/model/Content.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/model/Content.java @@ -2,6 +2,7 @@ package org.springframework.ai.model; import org.springframework.ai.chat.messages.Media; +import java.util.Collection; import java.util.List; import java.util.Map; @@ -24,7 +25,19 @@ public interface Content { /** * Get the media associated with the content. */ - List getMedia(); + default Collection getMedia() { + return getMedia(""); + } + + /** + * Retrieves the collection of media attachments associated with the content. + * @param dummy a dummy parameter to ensure method signature uniqueness + * @return a list of Media objects representing the media attachments + * @deprecated This method is deprecated since version 1.0.0 M1 and will be removed in + * a future release + */ + @Deprecated(since = "1.0.0 M1", forRemoval = true) + List getMedia(String... dummy); /** * return Get the metadata associated with the content. diff --git a/spring-ai-core/src/main/java/org/springframework/ai/transformer/splitter/TextSplitter.java b/spring-ai-core/src/main/java/org/springframework/ai/transformer/splitter/TextSplitter.java index 9c5f0671b..697643af2 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/transformer/splitter/TextSplitter.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/transformer/splitter/TextSplitter.java @@ -43,6 +43,14 @@ public abstract class TextSplitter implements DocumentTransformer { return doSplitDocuments(documents); } + public List split(List documents) { + return this.apply(documents); + } + + public List split(Document document) { + return this.apply(List.of(document)); + } + public void setCopyContentFormatter(boolean copyContentFormatter) { this.copyContentFormatter = copyContentFormatter; } diff --git a/spring-ai-core/src/main/java/org/springframework/ai/transformer/splitter/TokenTextSplitter.java b/spring-ai-core/src/main/java/org/springframework/ai/transformer/splitter/TokenTextSplitter.java index c820b51a3..cc034b49c 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/transformer/splitter/TokenTextSplitter.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/transformer/splitter/TokenTextSplitter.java @@ -68,10 +68,10 @@ public class TokenTextSplitter extends TextSplitter { @Override protected List splitText(String text) { - return split(text, this.defaultChunkSize); + return doSplit(text, this.defaultChunkSize); } - public List split(String text, int chunkSize) { + protected List doSplit(String text, int chunkSize) { if (text == null || text.trim().isEmpty()) { return new ArrayList<>(); } diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/etl-pipeline.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/etl-pipeline.adoc index d0656f160..807d15f7b 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/etl-pipeline.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/etl-pipeline.adoc @@ -26,12 +26,22 @@ Let's say we have the following instances of those three ETL types * `TokenTextSplitter` an implementation of `DocumentTransformer` * `VectorStore` an implementation of `DocumentWriter` -To perform the basic loading of data into a Vector Database for use with the Retrieval Augmented Generation pattern, use the following code. +To perform the basic loading of data into a Vector Database for use with the Retrieval Augmented Generation pattern, use the following code in Java function style syntax. + [source,java] ---- vectorStore.accept(tokenTextSplitter.apply(pdfReader.get())); ---- +Alternatively, you can use method names that are more naturally expressive for the domain + +[source,java] +---- +vectorStore.write(tokenTextSplitter.split(pdfReader.read())); +---- + + + == Getting Started To begin creating a Spring AI RAG application, follow these steps: @@ -57,6 +67,9 @@ Provides a source of documents from diverse origins. ---- public interface DocumentReader extends Supplier> { + default List read() { + return get(); + } } ---- @@ -68,14 +81,17 @@ Example: [source,java] ---- @Component -public class MyAiApp { +class MyAiAppComponent { - @Value("classpath:bikes.json") // This is the json document to load - private Resource resource; + private final Resource resource; + + MyAiAppComponent(@Value("classpath:bikes.json") Resource resource) { + this.resource = resource; + } List loadJsonAsDocuments() { JsonReader jsonReader = new JsonReader(resource, "description"); - return jsonReader.get(); + return jsonReader.read(); } } ---- @@ -88,16 +104,18 @@ Example: [source,java] ---- @Component -public class MyTextReader { +class MyTextReader { - @Value("classpath:text-source.txt") // This is the text document to load - private Resource resource; + private final Resource resource; + MyTextReader(@Value("classpath:text-source.txt") Resource resource) { + this.resource = resource; + } List loadText() { TextReader textReader = new TextReader(resource); textReader.getCustomMetadata().put("filename", "text-source.txt"); - return textReader.get(); + return textReader.read(); } } ---- @@ -123,7 +141,7 @@ public class MyPagePdfDocumentReader { .withPagesPerDocument(1) .build()); - return pdfReader.get(); + return pdfReader.read(); } } @@ -153,7 +171,7 @@ public class MyPagePdfDocumentReader { .withPagesPerDocument(1) .build()); - return pdfReader.get(); + return pdfReader.read(); } } ---- @@ -167,14 +185,18 @@ Example: [source,java] ---- @Component -public class MyTikaDocumentReader { +class MyTikaDocumentReader { - @Value("classpath:/word-sample.docx") // This is the word document to load - private Resource resource; + private final Resource resource; + + MyTikaDocumentReader(@Value("classpath:/word-sample.docx") + Resource resource) { + this.resource = resource; + } List loadText() { TikaDocumentReader tikaDocumentReader = new TikaDocumentReader(resource); - return tikaDocumentReader.get(); + return tikaDocumentReader.read(); } } ---- @@ -187,6 +209,9 @@ Transforms a batch of documents as part of the processing workflow. ---- public interface DocumentTransformer extends Function, List> { + default List transform(List transform) { + return apply(transform); + } } ---- @@ -213,6 +238,9 @@ Manages the final stage of the ETL process, preparing documents for storage. ```java public interface DocumentWriter extends Consumer> { + default void write(List documents) { + accept(documents); + } } ``` ==== FileDocumentWriter