diff --git a/src/main/java/com/github/llamara/ai/internal/chat/aiservice/ChatModelAiService.java b/src/main/java/com/github/llamara/ai/internal/chat/AiService.java similarity index 84% rename from src/main/java/com/github/llamara/ai/internal/chat/aiservice/ChatModelAiService.java rename to src/main/java/com/github/llamara/ai/internal/chat/AiService.java index 8ecf954..202b4ff 100644 --- a/src/main/java/com/github/llamara/ai/internal/chat/aiservice/ChatModelAiService.java +++ b/src/main/java/com/github/llamara/ai/internal/chat/AiService.java @@ -17,14 +17,13 @@ * limitations under the License. * #L% */ -package com.github.llamara.ai.internal.chat.aiservice; +package com.github.llamara.ai.internal.chat; import java.util.UUID; import dev.langchain4j.service.MemoryId; import dev.langchain4j.service.Result; import dev.langchain4j.service.SystemMessage; -import dev.langchain4j.service.TokenStream; import dev.langchain4j.service.UserMessage; /** @@ -38,7 +37,7 @@ * * @author Florian Hotze - Initial contribution */ -public interface ChatModelAiService { +public interface AiService { String SYSTEM_MESSAGE = """ You are LLAMARA, the Large Language Assistant for Model Augmented Retrieval and Analysis: @@ -50,14 +49,9 @@ public interface ChatModelAiService { """; @SystemMessage(SYSTEM_MESSAGE) - Result chat(@MemoryId UUID sessionId, boolean history, @UserMessage String prompt); + Result chat(@MemoryId UUID sessionId, @UserMessage String prompt); - Result chatWithoutSystemMessage( - @MemoryId UUID sessionId, boolean history, @UserMessage String prompt); - - @SystemMessage(SYSTEM_MESSAGE) - TokenStream chatAndStreamResponse( - @MemoryId UUID sessionId, boolean history, @UserMessage String prompt); + Result chatWithoutSystemMessage(@MemoryId UUID sessionId, @UserMessage String prompt); /** * Clean the given text by removing unnecessary noise and formatting it. diff --git a/src/main/java/com/github/llamara/ai/internal/chat/ChatModel.java b/src/main/java/com/github/llamara/ai/internal/chat/ChatModel.java new file mode 100644 index 0000000..3017926 --- /dev/null +++ b/src/main/java/com/github/llamara/ai/internal/chat/ChatModel.java @@ -0,0 +1,142 @@ +/* + * #%L + * llamara-backend + * %% + * Copyright (C) 2024 - 2025 Contributors to the LLAMARA project + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package com.github.llamara.ai.internal.chat; + +import com.github.llamara.ai.config.chat.ChatModelConfig; +import com.github.llamara.ai.internal.MetadataKeys; +import com.github.llamara.ai.internal.chat.history.ChatHistoryStore; +import com.github.llamara.ai.internal.chat.history.ChatMessageRecord; +import com.github.llamara.ai.internal.chat.response.ChatResponseRecord; +import com.github.llamara.ai.internal.chat.response.RagSourceRecord; + +import java.time.Instant; +import java.util.List; +import java.util.UUID; + +import dev.langchain4j.data.message.ChatMessageType; +import dev.langchain4j.service.Result; + +/** + * The {@link ChatModel} provides the interface to chat with the chat models. It takes care of + * storing the chat history, if enabled, and applies the system prompt, if enabled. + * + * @author Florian Hotze - Initial contribution + */ +public class ChatModel { + private final ChatModelConfig.ModelConfig config; + private final AiService aiService; + private final ChatHistoryStore historyStore; + + public ChatModel( + ChatModelConfig.ModelConfig config, + AiService aiService, + ChatHistoryStore historyStore) { + this.config = config; + this.aiService = aiService; + this.historyStore = historyStore; + } + + /** + * Send a prompt to a chat model and get the response. + * + * @param sessionId the session ID + * @param history whether to save the conversation to the chat history + * @param prompt the prompt to send to the chat model + * @return the response from the chat model + */ + public ChatResponseRecord chat(UUID sessionId, boolean history, String prompt) { + if (history) { + storePrompt(sessionId, prompt); + } + + Result result; + if (config.systemPromptEnabled()) { + result = aiService.chat(sessionId, prompt); + } else { + result = aiService.chatWithoutSystemMessage(sessionId, prompt); + } + ChatResponseRecord response = + new ChatResponseRecord( + result.content(), getSourcesFromResult(result, result.content())); + + if (history) { + storeResponse(sessionId, response); + } + + return response; + } + + /** + * Get the sources from the result. Filters out sources that were not used by the chat model to + * generate the response. + * + * @param result the {@link Result} from the chat model + * @param text the text of the response + * @return the sources used by the chat model to generate the response + */ + private List getSourcesFromResult(Result result, String text) { + return result.sources().stream() + .map(dev.langchain4j.rag.content.Content::textSegment) + .filter(ts -> text.contains(ts.metadata().getString(MetadataKeys.KNOWLEDGE_ID))) + .map( + ts -> + new RagSourceRecord( + ts.metadata().getUUID(MetadataKeys.KNOWLEDGE_ID), + ts.text())) + .toList(); + } + + /** + * Store the prompt in the chat history. + * + * @param sessionId the session ID + * @param prompt the prompt to store + */ + private void storePrompt(UUID sessionId, String prompt) { + historyStore + .addMessage( + sessionId, + new ChatMessageRecord( + ChatMessageType.USER, prompt, Instant.now(), null, null, null)) + .subscribe() + .with(item -> {}, failure -> {}); + } + + /** + * Store the response in the chat history. + * + * @param sessionId the session ID + * @param response the response to store + */ + private void storeResponse(UUID sessionId, ChatResponseRecord response) { + historyStore + .addMessage( + sessionId, + new ChatMessageRecord( + ChatMessageType.AI, + response.response(), + Instant.now(), + response.sources(), + config.provider(), + config.model())) + .subscribe() + .with(item -> {}, failure -> {}); + } +} diff --git a/src/main/java/com/github/llamara/ai/internal/chat/ChatModelContainer.java b/src/main/java/com/github/llamara/ai/internal/chat/ChatModelContainer.java index 4144868..9d3305a 100644 --- a/src/main/java/com/github/llamara/ai/internal/chat/ChatModelContainer.java +++ b/src/main/java/com/github/llamara/ai/internal/chat/ChatModelContainer.java @@ -20,20 +20,18 @@ package com.github.llamara.ai.internal.chat; import com.github.llamara.ai.config.chat.ChatModelConfig; -import com.github.llamara.ai.internal.chat.aiservice.ChatModelAiService; import com.fasterxml.jackson.annotation.JsonIgnore; /** * Container for a chat model, containing information about the chat model and the {@link - * ChatModelAiService}s. + * ChatModel}. * * @param uid the UID of the chat model * @param label the label of the chat model * @param description the description of the chat model * @param provider the {@link ChatModelConfig.ChatModelProvider} - * @param config the {@link ChatModelConfig.ModelConfig} - * @param service the {@link ChatModelAiService} of the chat model + * @param model the {@link ChatModel} * @author Florian Hotze - Initial contribution */ public record ChatModelContainer( @@ -41,5 +39,4 @@ public record ChatModelContainer( String label, String description, ChatModelConfig.ChatModelProvider provider, - @JsonIgnore ChatModelConfig.ModelConfig config, - @JsonIgnore ChatModelAiService service) {} + @JsonIgnore ChatModel model) {} diff --git a/src/main/java/com/github/llamara/ai/internal/chat/ChatModelProvider.java b/src/main/java/com/github/llamara/ai/internal/chat/ChatModelProvider.java index f097fe7..4fcbff2 100644 --- a/src/main/java/com/github/llamara/ai/internal/chat/ChatModelProvider.java +++ b/src/main/java/com/github/llamara/ai/internal/chat/ChatModelProvider.java @@ -20,15 +20,14 @@ package com.github.llamara.ai.internal.chat; import com.github.llamara.ai.config.chat.ChatModelConfig; -import com.github.llamara.ai.internal.chat.aiservice.ChatModelAiService; import java.util.Collection; /** * The chat model provider provides access to the configured {@link ChatModelContainer}s. * - *

It processes the {@link ChatModelConfig} and creates the {@link ChatModelAiService}s to - * interface with the configured models. + *

It processes the {@link ChatModelConfig} and creates the {@link AiService}s to interface with + * the configured models. * * @author Florian Hotze - Initial contribution */ diff --git a/src/main/java/com/github/llamara/ai/internal/chat/ChatModelProviderImpl.java b/src/main/java/com/github/llamara/ai/internal/chat/ChatModelProviderImpl.java index e1ecd7d..1c00d48 100644 --- a/src/main/java/com/github/llamara/ai/internal/chat/ChatModelProviderImpl.java +++ b/src/main/java/com/github/llamara/ai/internal/chat/ChatModelProviderImpl.java @@ -25,9 +25,7 @@ import com.github.llamara.ai.config.EnvironmentVariables; import com.github.llamara.ai.config.chat.ChatModelConfig; import com.github.llamara.ai.internal.StartupException; -import com.github.llamara.ai.internal.chat.aiservice.ChatModelAiService; import com.github.llamara.ai.internal.chat.history.ChatHistoryStore; -import com.github.llamara.ai.internal.chat.history.HistoryInterceptingAiService; import java.util.Collection; import java.util.Collections; @@ -106,15 +104,15 @@ private void initializeChatModels() { ChatLanguageModel clm = produceChatLanguageModel(config); StreamingChatLanguageModel sclm = produceStreamingChatLanguageModel(config); - ChatModelAiService service = - new HistoryInterceptingAiService( - AiServices.builder(ChatModelAiService.class) + ChatModel model = + new ChatModel( + config, + AiServices.builder(AiService.class) .chatLanguageModel(clm) .streamingChatLanguageModel(sclm) .chatMemoryProvider(chatMemoryProvider) .retrievalAugmentor(retrievalAugmentor) .build(), - config, chatHistoryStore); ChatModelContainer cm = @@ -123,8 +121,7 @@ private void initializeChatModels() { config.label().orElse(config.uid()), config.description().orElse(config.provider() + " " + config.model()), config.provider(), - config, - service); + model); chatModels.put(config.uid(), cm); } } diff --git a/src/main/java/com/github/llamara/ai/internal/chat/aiservice/DelegatingChatModelAiService.java b/src/main/java/com/github/llamara/ai/internal/chat/aiservice/DelegatingChatModelAiService.java deleted file mode 100644 index 786b990..0000000 --- a/src/main/java/com/github/llamara/ai/internal/chat/aiservice/DelegatingChatModelAiService.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * #%L - * llamara-backend - * %% - * Copyright (C) 2024 - 2025 Contributors to the LLAMARA project - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ -package com.github.llamara.ai.internal.chat.aiservice; - -import com.github.llamara.ai.config.chat.ChatModelConfig; - -import java.util.UUID; - -import dev.langchain4j.service.Result; -import dev.langchain4j.service.TokenStream; - -/** - * {@link ChatModelAiService} implementation that delegates to a supplied {@link ChatModelAiService} - * instance and stores its {@link ChatModelConfig.ModelConfig}. Allows overriding specific methods - * and accessing the chat model configuration. - * - * @author Florian Hotze - Initial contribution - */ -public abstract class DelegatingChatModelAiService implements ChatModelAiService { - private final ChatModelAiService delegate; - private final ChatModelConfig.ModelConfig config; - - protected DelegatingChatModelAiService( - ChatModelAiService delegate, ChatModelConfig.ModelConfig config) { - this.delegate = delegate; - this.config = config; - } - - /** - * Get the model configuration of this {@link ChatModelAiService}. - * - * @return configuration of the chat model - */ - public ChatModelConfig.ModelConfig config() { - return config; - } - - @Override - public Result chat(UUID sessionId, boolean history, String prompt) { - return delegate.chat(sessionId, history, prompt); - } - - @Override - public Result chatWithoutSystemMessage(UUID sessionId, boolean history, String prompt) { - return delegate.chatWithoutSystemMessage(sessionId, history, prompt); - } - - @Override - public TokenStream chatAndStreamResponse(UUID sessionId, boolean history, String prompt) { - return delegate.chatAndStreamResponse(sessionId, history, prompt); - } - - @Override - public String clean(String text) { - return delegate.clean(text); - } -} diff --git a/src/main/java/com/github/llamara/ai/internal/chat/aiservice/DelegatingTokenStream.java b/src/main/java/com/github/llamara/ai/internal/chat/aiservice/DelegatingTokenStream.java deleted file mode 100644 index ba2d5f3..0000000 --- a/src/main/java/com/github/llamara/ai/internal/chat/aiservice/DelegatingTokenStream.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * #%L - * llamara-backend - * %% - * Copyright (C) 2024 - 2025 Contributors to the LLAMARA project - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ -package com.github.llamara.ai.internal.chat.aiservice; - -import java.util.List; -import java.util.function.Consumer; - -import dev.langchain4j.data.message.AiMessage; -import dev.langchain4j.model.chat.response.ChatResponse; -import dev.langchain4j.model.output.Response; -import dev.langchain4j.rag.content.Content; -import dev.langchain4j.service.TokenStream; -import dev.langchain4j.service.tool.ToolExecution; - -/** - * {@link TokenStream} implementation that delegates to a supplied {@link TokenStream} instance. - * Allows overriding specific methods. - * - * @author Florian Hotze - Initial contribution - */ -public abstract class DelegatingTokenStream implements TokenStream { - - private final TokenStream delegate; - - protected DelegatingTokenStream(TokenStream delegate) { - this.delegate = delegate; - } - - @Override - public TokenStream onPartialResponse(Consumer consumer) { - delegate.onPartialResponse(consumer); - return this; - } - - @Override - public TokenStream onNext(Consumer tokenHandler) { // NOSONAR: we need this - delegate.onNext(tokenHandler); // NOSONAR: we need this - return this; - } - - @Override - public TokenStream onRetrieved(Consumer> contentHandler) { - delegate.onRetrieved(contentHandler); - return this; - } - - @Override - public TokenStream onToolExecuted(Consumer toolExecuteHandler) { - delegate.onToolExecuted(toolExecuteHandler); - return this; - } - - @Override - public TokenStream onCompleteResponse(Consumer consumer) { - delegate.onCompleteResponse(consumer); - return this; - } - - @Override - public TokenStream onComplete( - Consumer> completionHandler) { // NOSONAR: we need this - delegate.onComplete(completionHandler); // NOSONAR: we need this - return this; - } - - @Override - public TokenStream onError(Consumer errorHandler) { - delegate.onError(errorHandler); - return this; - } - - @Override - public TokenStream ignoreErrors() { - delegate.ignoreErrors(); - return this; - } - - @Override - public void start() { - delegate.start(); - } -} diff --git a/src/main/java/com/github/llamara/ai/internal/chat/history/ChatMessageRecord.java b/src/main/java/com/github/llamara/ai/internal/chat/history/ChatMessageRecord.java index b72d617..738aa1a 100644 --- a/src/main/java/com/github/llamara/ai/internal/chat/history/ChatMessageRecord.java +++ b/src/main/java/com/github/llamara/ai/internal/chat/history/ChatMessageRecord.java @@ -20,8 +20,10 @@ package com.github.llamara.ai.internal.chat.history; import com.github.llamara.ai.config.chat.ChatModelConfig; +import com.github.llamara.ai.internal.chat.response.RagSourceRecord; import java.time.Instant; +import java.util.List; import com.fasterxml.jackson.annotation.JsonInclude; import dev.langchain4j.data.message.ChatMessageType; @@ -32,6 +34,8 @@ * @param type the type of the message, e.g. AI or USER * @param text the text of the message * @param timestamp the timestamp of the message + * @param sources if the message is an AI message: the sources used by the chat model to generate + * the response * @param modelProvider if the message is an AI message: the provider of the chat model, else * null * @param modelName if the message is an AI message: the name of the chat model, else null @@ -42,5 +46,6 @@ public record ChatMessageRecord( ChatMessageType type, String text, Instant timestamp, + @JsonInclude(JsonInclude.Include.NON_NULL) List sources, @JsonInclude(JsonInclude.Include.NON_NULL) ChatModelConfig.ChatModelProvider modelProvider, @JsonInclude(JsonInclude.Include.NON_NULL) String modelName) {} diff --git a/src/main/java/com/github/llamara/ai/internal/chat/history/HistoryInterceptingAiService.java b/src/main/java/com/github/llamara/ai/internal/chat/history/HistoryInterceptingAiService.java deleted file mode 100644 index 695dada..0000000 --- a/src/main/java/com/github/llamara/ai/internal/chat/history/HistoryInterceptingAiService.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * #%L - * llamara-backend - * %% - * Copyright (C) 2024 - 2025 Contributors to the LLAMARA project - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ -package com.github.llamara.ai.internal.chat.history; - -import com.github.llamara.ai.config.chat.ChatModelConfig; -import com.github.llamara.ai.internal.chat.aiservice.ChatModelAiService; -import com.github.llamara.ai.internal.chat.aiservice.DelegatingChatModelAiService; -import com.github.llamara.ai.internal.chat.aiservice.DelegatingTokenStream; - -import java.time.Instant; -import java.util.UUID; -import java.util.function.BiConsumer; -import java.util.function.Consumer; - -import dev.langchain4j.data.message.ChatMessageType; -import dev.langchain4j.model.chat.response.ChatResponse; -import dev.langchain4j.service.Result; -import dev.langchain4j.service.TokenStream; - -/** - * Delegate allowing intercepting prompts and responses for a supplied {@link ChatModelAiService} to - * store them in the {@link ChatHistoryStore}. - * - * @author Florian Hotze - Initial contribution - */ -public class HistoryInterceptingAiService extends DelegatingChatModelAiService { - private final BiConsumer promptConsumer; - private final BiConsumer responseConsumer; - - public HistoryInterceptingAiService( - ChatModelAiService delegate, - ChatModelConfig.ModelConfig config, - ChatHistoryStore store) { - super(delegate, config); - - this.promptConsumer = - (sessionId, prompt) -> - store.addMessage( - sessionId, - new ChatMessageRecord( - ChatMessageType.USER, - prompt, - Instant.now(), - null, - null)) - .subscribe() - .with(item -> {}, failure -> {}); - this.responseConsumer = - (sessionId, response) -> - store.addMessage( - sessionId, - new ChatMessageRecord( - ChatMessageType.AI, - response, - Instant.now(), - config.provider(), - config.model())) - .subscribe() - .with(item -> {}, failure -> {}); - } - - @Override - public Result chat(UUID sessionId, boolean history, String prompt) { - if (!history) { - return super.chat(sessionId, false, prompt); - } - - promptConsumer.accept(sessionId, prompt); - Result response = super.chat(sessionId, true, prompt); - responseConsumer.accept(sessionId, response.content()); - return response; - } - - @Override - public Result chatWithoutSystemMessage(UUID sessionId, boolean history, String prompt) { - if (!history) { - return super.chatWithoutSystemMessage(sessionId, false, prompt); - } - - promptConsumer.accept(sessionId, prompt); - Result response = super.chatWithoutSystemMessage(sessionId, true, prompt); - responseConsumer.accept(sessionId, response.content()); - return response; - } - - @Override - public TokenStream chatAndStreamResponse(UUID sessionId, boolean history, String prompt) { - if (!history) { - return super.chatAndStreamResponse(sessionId, false, prompt); - } - - promptConsumer.accept(sessionId, prompt); - return new CompletionInterceptingTokenStream( - super.chatAndStreamResponse(sessionId, true, prompt), - response -> responseConsumer.accept(sessionId, response.aiMessage().text())); - } - - /** - * Delegate allowing intercepting {@link TokenStream#onComplete(Consumer)} for a supplied {@link - * TokenStream}. - */ - private static class CompletionInterceptingTokenStream extends DelegatingTokenStream { - private final Consumer completionHandler; - - CompletionInterceptingTokenStream( - TokenStream delegate, Consumer completionHandler) { - super(delegate); - this.completionHandler = completionHandler; - } - - @Override - public TokenStream onCompleteResponse(Consumer completionHandler) { - super.onCompleteResponse( - response -> { - this.completionHandler.accept(response); - completionHandler.accept(response); - }); - return this; - } - } -} diff --git a/src/main/java/com/github/llamara/ai/internal/chat/response/ChatResponseRecord.java b/src/main/java/com/github/llamara/ai/internal/chat/response/ChatResponseRecord.java new file mode 100644 index 0000000..32dfd40 --- /dev/null +++ b/src/main/java/com/github/llamara/ai/internal/chat/response/ChatResponseRecord.java @@ -0,0 +1,31 @@ +/* + * #%L + * llamara-backend + * %% + * Copyright (C) 2024 - 2025 Contributors to the LLAMARA project + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package com.github.llamara.ai.internal.chat.response; + +import java.util.List; + +/** + * Record for a chat response. + * + * @param response the response from the chat model + * @param sources the sources used by the chat model to generate the response + * @author Florian Hotze - Initial contribution + */ +public record ChatResponseRecord(String response, List sources) {} diff --git a/src/main/java/com/github/llamara/ai/internal/chat/response/RagSourceRecord.java b/src/main/java/com/github/llamara/ai/internal/chat/response/RagSourceRecord.java new file mode 100644 index 0000000..951f469 --- /dev/null +++ b/src/main/java/com/github/llamara/ai/internal/chat/response/RagSourceRecord.java @@ -0,0 +1,31 @@ +/* + * #%L + * llamara-backend + * %% + * Copyright (C) 2024 - 2025 Contributors to the LLAMARA project + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package com.github.llamara.ai.internal.chat.response; + +import java.util.UUID; + +/** + * Record for the source of a RAG response. + * + * @param knowledgeId the knowledge ID the source belongs to + * @param content the content of the source + * @author Florian Hotze - Initial contribution + */ +public record RagSourceRecord(UUID knowledgeId, String content) {} diff --git a/src/main/java/com/github/llamara/ai/internal/rest/ChatResource.java b/src/main/java/com/github/llamara/ai/internal/rest/ChatResource.java index d1bba01..069ef2b 100644 --- a/src/main/java/com/github/llamara/ai/internal/rest/ChatResource.java +++ b/src/main/java/com/github/llamara/ai/internal/rest/ChatResource.java @@ -19,17 +19,17 @@ */ package com.github.llamara.ai.internal.rest; -import com.github.llamara.ai.internal.MetadataKeys; +import com.github.llamara.ai.internal.chat.ChatModel; import com.github.llamara.ai.internal.chat.ChatModelContainer; import com.github.llamara.ai.internal.chat.ChatModelNotFoundException; import com.github.llamara.ai.internal.chat.ChatModelProvider; import com.github.llamara.ai.internal.chat.history.ChatMessageRecord; +import com.github.llamara.ai.internal.chat.response.ChatResponseRecord; import com.github.llamara.ai.internal.security.Roles; import com.github.llamara.ai.internal.security.session.Session; import com.github.llamara.ai.internal.security.session.SessionManager; import com.github.llamara.ai.internal.security.session.SessionNotFoundException; -import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.UUID; @@ -46,7 +46,6 @@ import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.MediaType; -import dev.langchain4j.service.Result; import io.quarkus.security.identity.SecurityIdentity; import io.smallrye.common.annotation.Blocking; import io.smallrye.common.annotation.NonBlocking; @@ -113,11 +112,11 @@ public Collection getModels() { @APIResponse( responseCode = "200", description = "OK", - content = @Content(schema = @Schema(implementation = ChatResponseDTO.class))) + content = @Content(schema = @Schema(implementation = ChatResponseRecord.class))) @APIResponse( responseCode = "404", description = "No chat model or no session with given ID found.") - public ChatResponseDTO prompt( + public ChatResponseRecord prompt( @QueryParam("uid") @Parameter( name = "uid", @@ -133,17 +132,8 @@ public ChatResponseDTO prompt( String prompt) throws ChatModelNotFoundException, SessionNotFoundException { sessionManager.enforceSessionValid(sessionId); - ChatModelContainer chatModel = chatModelProvider.getModel(uid); - Result result; - if (chatModel.config().systemPromptEnabled()) { - result = chatModel.service().chat(sessionId, !identity.isAnonymous(), prompt); - } else { - result = - chatModel - .service() - .chatWithoutSystemMessage(sessionId, !identity.isAnonymous(), prompt); - } - return new ChatResponseDTO(result); + ChatModel chatModel = chatModelProvider.getModel(uid).model(); + return chatModel.chat(sessionId, !identity.isAnonymous(), prompt); } /* @@ -183,7 +173,7 @@ public Multi streamingPrompt( String prompt) throws ChatModelNotFoundException, SessionNotFoundException { sessionManager.enforceSessionValid(sessionId); - ChatModelAiService chatModelAiService = chatModelProvider.getModel(uid).service(); + AiService chatModelAiService = chatModelProvider.getModel(uid).service(); Multi sourceMulti = Multi.createFrom() .emitter( @@ -318,24 +308,4 @@ public void keepAliveAnonymousSession( throws SessionNotFoundException { sessionManager.enforceSessionValid(sessionId); } - - public static class ChatResponseDTO { - public final String response; - public final List sources = new ArrayList<>(); - - public ChatResponseDTO(Result result) { - this.response = result.content(); - result.sources().stream() - .map(dev.langchain4j.rag.content.Content::textSegment) - .forEach( - ts -> - sources.add( - new SourceRecord( - ts.metadata() - .getUUID(MetadataKeys.KNOWLEDGE_ID), - ts.text()))); - } - - public record SourceRecord(UUID knowledgeId, String content) {} - } }