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.
This commit is contained in:
@@ -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");
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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<ChatResponse> response = streamingChatClient.stream(new Prompt(List.of(userMessage),
|
||||
OpenAiChatOptions.builder().withModel(OpenAiApi.ChatModel.GPT_4_VISION_PREVIEW.getValue()).build()));
|
||||
|
||||
@@ -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<Document> splitDocuments = textSplitter.apply(jsonReader.get());
|
||||
List<Document> 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<Document> documents = jsonReader.read();
|
||||
List<Document> splitDocuments = tokenTextSplitter.split(documents);
|
||||
vectorStore.write(splitDocuments);
|
||||
|
||||
// Now in java.util.Function style.
|
||||
|
||||
Supplier<List<Document>> docs = jsonReader::read;
|
||||
|
||||
}
|
||||
|
||||
@SpringBootConfiguration
|
||||
static class Config {
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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<Media> mediaData;
|
||||
protected final List<Media> 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<Media> mediaData) {
|
||||
this(messageType, textContent, mediaData, Map.of(MESSAGE_TYPE, messageType));
|
||||
protected AbstractMessage(MessageType messageType, String textContent, List<Media> media) {
|
||||
this(messageType, textContent, media, Map.of(MESSAGE_TYPE, messageType));
|
||||
}
|
||||
|
||||
protected AbstractMessage(MessageType messageType, String textContent, List<Media> mediaData,
|
||||
protected AbstractMessage(MessageType messageType, String textContent, Collection<Media> media,
|
||||
Map<String, Object> 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<Media> getMedia() {
|
||||
return this.mediaData;
|
||||
public List<Media> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<Media> mediaList, Map<String, Object> metadata) {
|
||||
public UserMessage(String textContent, Collection<Media> mediaList, Map<String, Object> metadata) {
|
||||
super(MessageType.USER, textContent, mediaList, metadata);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Media> getMedia() {
|
||||
public List<Media> getMedia(String... dummy) {
|
||||
return this.media;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,4 +20,8 @@ import java.util.function.Supplier;
|
||||
|
||||
public interface DocumentReader extends Supplier<List<Document>> {
|
||||
|
||||
default List<Document> read() {
|
||||
return get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,4 +20,8 @@ import java.util.function.Function;
|
||||
|
||||
public interface DocumentTransformer extends Function<List<Document>, List<Document>> {
|
||||
|
||||
default List<Document> transform(List<Document> transform) {
|
||||
return apply(transform);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,4 +23,8 @@ import java.util.function.Consumer;
|
||||
*/
|
||||
public interface DocumentWriter extends Consumer<List<Document>> {
|
||||
|
||||
default void write(List<Document> documents) {
|
||||
accept(documents);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<Media> getMedia();
|
||||
default Collection<Media> 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<Media> getMedia(String... dummy);
|
||||
|
||||
/**
|
||||
* return Get the metadata associated with the content.
|
||||
|
||||
@@ -43,6 +43,14 @@ public abstract class TextSplitter implements DocumentTransformer {
|
||||
return doSplitDocuments(documents);
|
||||
}
|
||||
|
||||
public List<Document> split(List<Document> documents) {
|
||||
return this.apply(documents);
|
||||
}
|
||||
|
||||
public List<Document> split(Document document) {
|
||||
return this.apply(List.of(document));
|
||||
}
|
||||
|
||||
public void setCopyContentFormatter(boolean copyContentFormatter) {
|
||||
this.copyContentFormatter = copyContentFormatter;
|
||||
}
|
||||
|
||||
@@ -68,10 +68,10 @@ public class TokenTextSplitter extends TextSplitter {
|
||||
|
||||
@Override
|
||||
protected List<String> splitText(String text) {
|
||||
return split(text, this.defaultChunkSize);
|
||||
return doSplit(text, this.defaultChunkSize);
|
||||
}
|
||||
|
||||
public List<String> split(String text, int chunkSize) {
|
||||
protected List<String> doSplit(String text, int chunkSize) {
|
||||
if (text == null || text.trim().isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
@@ -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<List<Document>> {
|
||||
|
||||
default List<Document> 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<Document> 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<Document> 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<Document> 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<Document>, List<Document>> {
|
||||
|
||||
default List<Document> transform(List<Document> 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<List<Document>> {
|
||||
|
||||
default void write(List<Document> documents) {
|
||||
accept(documents);
|
||||
}
|
||||
}
|
||||
```
|
||||
==== FileDocumentWriter
|
||||
|
||||
Reference in New Issue
Block a user