From a01c773f04a7550612bf14f7d2b845e972d22b16 Mon Sep 17 00:00:00 2001 From: Ruslan Popov Date: Sun, 2 Nov 2025 19:27:19 +0100 Subject: [PATCH 1/3] feat(ai): create AI framework --- .../EmbeddingComputationException.java | 14 +++ .../embeddings/EmbeddingException.java | 14 +++ .../framework/embeddings/EmbeddingModel.java | 19 ++++ .../EmbeddingModelNotAvailableException.java | 14 +++ .../framework/embeddings/EmbeddingType.java | 16 ++++ .../llms/LlmAuthenticationException.java | 14 +++ .../llms/LlmConnectionException.java | 14 +++ .../framework/llms/LlmInferenceException.java | 14 +++ .../llms/LlmInferenceParameters.java | 66 +++++++++++++ .../logic/ai/framework/llms/LlmModel.java | 22 +++++ .../framework/llms/LlmRateLimitException.java | 14 +++ .../ai/framework/messages/ChatMessage.java | 26 +++++ .../ai/framework/messages/LlmMessage.java | 16 ++++ .../ai/framework/messages/SystemMessage.java | 16 ++++ .../ai/framework/messages/UserMessage.java | 16 ++++ .../templates/ChatMessageTemplate.java | 31 ++++++ .../ai/framework/templates/ChatTemplate.java | 35 +++++++ .../templates/LlmMessageTemplate.java | 27 ++++++ .../framework/templates/PromptTemplate.java | 40 ++++++++ .../templates/SystemMessageTemplate.java | 27 ++++++ .../templates/UserMessageTemplate.java | 27 ++++++ .../ai/framework/vectordb/VectorDatabase.java | 39 ++++++++ .../VectorDatabaseConnectionException.java | 14 +++ .../vectordb/VectorDatabaseException.java | 14 +++ .../VectorDatabaseFindParameters.java | 95 +++++++++++++++++++ .../VectorDatabaseQueryException.java | 14 +++ .../framework/vectordb/VectorFindResult.java | 52 ++++++++++ 27 files changed, 710 insertions(+) create mode 100644 jablib/src/main/java/org/jabref/logic/ai/framework/embeddings/EmbeddingComputationException.java create mode 100644 jablib/src/main/java/org/jabref/logic/ai/framework/embeddings/EmbeddingException.java create mode 100644 jablib/src/main/java/org/jabref/logic/ai/framework/embeddings/EmbeddingModel.java create mode 100644 jablib/src/main/java/org/jabref/logic/ai/framework/embeddings/EmbeddingModelNotAvailableException.java create mode 100644 jablib/src/main/java/org/jabref/logic/ai/framework/embeddings/EmbeddingType.java create mode 100644 jablib/src/main/java/org/jabref/logic/ai/framework/llms/LlmAuthenticationException.java create mode 100644 jablib/src/main/java/org/jabref/logic/ai/framework/llms/LlmConnectionException.java create mode 100644 jablib/src/main/java/org/jabref/logic/ai/framework/llms/LlmInferenceException.java create mode 100644 jablib/src/main/java/org/jabref/logic/ai/framework/llms/LlmInferenceParameters.java create mode 100644 jablib/src/main/java/org/jabref/logic/ai/framework/llms/LlmModel.java create mode 100644 jablib/src/main/java/org/jabref/logic/ai/framework/llms/LlmRateLimitException.java create mode 100644 jablib/src/main/java/org/jabref/logic/ai/framework/messages/ChatMessage.java create mode 100644 jablib/src/main/java/org/jabref/logic/ai/framework/messages/LlmMessage.java create mode 100644 jablib/src/main/java/org/jabref/logic/ai/framework/messages/SystemMessage.java create mode 100644 jablib/src/main/java/org/jabref/logic/ai/framework/messages/UserMessage.java create mode 100644 jablib/src/main/java/org/jabref/logic/ai/framework/templates/ChatMessageTemplate.java create mode 100644 jablib/src/main/java/org/jabref/logic/ai/framework/templates/ChatTemplate.java create mode 100644 jablib/src/main/java/org/jabref/logic/ai/framework/templates/LlmMessageTemplate.java create mode 100644 jablib/src/main/java/org/jabref/logic/ai/framework/templates/PromptTemplate.java create mode 100644 jablib/src/main/java/org/jabref/logic/ai/framework/templates/SystemMessageTemplate.java create mode 100644 jablib/src/main/java/org/jabref/logic/ai/framework/templates/UserMessageTemplate.java create mode 100644 jablib/src/main/java/org/jabref/logic/ai/framework/vectordb/VectorDatabase.java create mode 100644 jablib/src/main/java/org/jabref/logic/ai/framework/vectordb/VectorDatabaseConnectionException.java create mode 100644 jablib/src/main/java/org/jabref/logic/ai/framework/vectordb/VectorDatabaseException.java create mode 100644 jablib/src/main/java/org/jabref/logic/ai/framework/vectordb/VectorDatabaseFindParameters.java create mode 100644 jablib/src/main/java/org/jabref/logic/ai/framework/vectordb/VectorDatabaseQueryException.java create mode 100644 jablib/src/main/java/org/jabref/logic/ai/framework/vectordb/VectorFindResult.java diff --git a/jablib/src/main/java/org/jabref/logic/ai/framework/embeddings/EmbeddingComputationException.java b/jablib/src/main/java/org/jabref/logic/ai/framework/embeddings/EmbeddingComputationException.java new file mode 100644 index 00000000000..a09eee9b4a3 --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/ai/framework/embeddings/EmbeddingComputationException.java @@ -0,0 +1,14 @@ +package org.jabref.logic.ai.framework.embeddings; + +import org.jabref.logic.l10n.Localization; + +/** + * Exception thrown when embedding computation fails. + */ +public class EmbeddingComputationException extends EmbeddingException { + + @Override + public String getLocalizedMessage() { + return Localization.lang("Failed to compute embeddings."); + } +} diff --git a/jablib/src/main/java/org/jabref/logic/ai/framework/embeddings/EmbeddingException.java b/jablib/src/main/java/org/jabref/logic/ai/framework/embeddings/EmbeddingException.java new file mode 100644 index 00000000000..09aacd8910e --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/ai/framework/embeddings/EmbeddingException.java @@ -0,0 +1,14 @@ +package org.jabref.logic.ai.framework.embeddings; + +import org.jabref.logic.l10n.Localization; + +/** + * Exception thrown when embedding operations fail. + */ +public class EmbeddingException extends Exception { + + @Override + public String getLocalizedMessage() { + return Localization.lang("An error occurred during embedding computation."); + } +} diff --git a/jablib/src/main/java/org/jabref/logic/ai/framework/embeddings/EmbeddingModel.java b/jablib/src/main/java/org/jabref/logic/ai/framework/embeddings/EmbeddingModel.java new file mode 100644 index 00000000000..0a5b89d8783 --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/ai/framework/embeddings/EmbeddingModel.java @@ -0,0 +1,19 @@ +package org.jabref.logic.ai.framework.embeddings; + +import java.util.List; + +/** + * Interface for embedding model implementations. + */ +public interface EmbeddingModel { + + /** + * Computes an embedding vector for the given text. + * + * @param text the text to embed + * @param type the type of embedding to generate + * @return the embedding vector as a list of floats + * @throws EmbeddingException if embedding computation fails + */ + List compute(String text, EmbeddingType type) throws EmbeddingException; +} diff --git a/jablib/src/main/java/org/jabref/logic/ai/framework/embeddings/EmbeddingModelNotAvailableException.java b/jablib/src/main/java/org/jabref/logic/ai/framework/embeddings/EmbeddingModelNotAvailableException.java new file mode 100644 index 00000000000..6fa4d253278 --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/ai/framework/embeddings/EmbeddingModelNotAvailableException.java @@ -0,0 +1,14 @@ +package org.jabref.logic.ai.framework.embeddings; + +import org.jabref.logic.l10n.Localization; + +/** + * Exception thrown when the embedding model is not available. + */ +public class EmbeddingModelNotAvailableException extends EmbeddingException { + + @Override + public String getLocalizedMessage() { + return Localization.lang("Embedding model is not available."); + } +} diff --git a/jablib/src/main/java/org/jabref/logic/ai/framework/embeddings/EmbeddingType.java b/jablib/src/main/java/org/jabref/logic/ai/framework/embeddings/EmbeddingType.java new file mode 100644 index 00000000000..5be42020f6a --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/ai/framework/embeddings/EmbeddingType.java @@ -0,0 +1,16 @@ +package org.jabref.logic.ai.framework.embeddings; + +/** + * Types of embeddings that can be generated for different use cases. + */ +public enum EmbeddingType { + /** + * Embedding optimized for question-like content. + */ + QUESTION, + + /** + * Embedding optimized for answer-like content. + */ + ANSWER +} diff --git a/jablib/src/main/java/org/jabref/logic/ai/framework/llms/LlmAuthenticationException.java b/jablib/src/main/java/org/jabref/logic/ai/framework/llms/LlmAuthenticationException.java new file mode 100644 index 00000000000..62dab852cf9 --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/ai/framework/llms/LlmAuthenticationException.java @@ -0,0 +1,14 @@ +package org.jabref.logic.ai.framework.llms; + +import org.jabref.logic.l10n.Localization; + +/** + * Exception thrown when LLM authentication fails. + */ +public class LlmAuthenticationException extends LlmInferenceException { + + @Override + public String getLocalizedMessage() { + return Localization.lang("LLM authentication failed."); + } +} diff --git a/jablib/src/main/java/org/jabref/logic/ai/framework/llms/LlmConnectionException.java b/jablib/src/main/java/org/jabref/logic/ai/framework/llms/LlmConnectionException.java new file mode 100644 index 00000000000..8adabbc56fe --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/ai/framework/llms/LlmConnectionException.java @@ -0,0 +1,14 @@ +package org.jabref.logic.ai.framework.llms; + +import org.jabref.logic.l10n.Localization; + +/** + * Exception thrown when connection to LLM service fails. + */ +public class LlmConnectionException extends LlmInferenceException { + + @Override + public String getLocalizedMessage() { + return Localization.lang("Failed to connect to LLM service."); + } +} diff --git a/jablib/src/main/java/org/jabref/logic/ai/framework/llms/LlmInferenceException.java b/jablib/src/main/java/org/jabref/logic/ai/framework/llms/LlmInferenceException.java new file mode 100644 index 00000000000..433bae32fd0 --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/ai/framework/llms/LlmInferenceException.java @@ -0,0 +1,14 @@ +package org.jabref.logic.ai.framework.llms; + +import org.jabref.logic.l10n.Localization; + +/** + * Exception thrown when LLM inference fails. + */ +public class LlmInferenceException extends Exception { + + @Override + public String getLocalizedMessage() { + return Localization.lang("An error occurred during LLM inference."); + } +} diff --git a/jablib/src/main/java/org/jabref/logic/ai/framework/llms/LlmInferenceParameters.java b/jablib/src/main/java/org/jabref/logic/ai/framework/llms/LlmInferenceParameters.java new file mode 100644 index 00000000000..25dc107a43c --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/ai/framework/llms/LlmInferenceParameters.java @@ -0,0 +1,66 @@ +package org.jabref.logic.ai.framework.llms; + +/** + * Parameters for controlling LLM inference behavior. + */ +public class LlmInferenceParameters { + private final double temperature; + + private LlmInferenceParameters(double temperature) { + this.temperature = temperature; + } + + /** + * Creates a builder for LlmInferenceParameters. + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Creates default inference parameters. + * + * @return default parameters with temperature 0.7 + */ + public static LlmInferenceParameters defaultParameters() { + return builder().temperature(0.7).build(); + } + + /** + * Returns the temperature parameter. + * + * @return the temperature value + */ + public double getTemperature() { + return temperature; + } + + /** + * Builder for LlmInferenceParameters. + */ + public static class Builder { + private double temperature = 0.7; + + /** + * Sets the temperature parameter. + * + * @param temperature Controls randomness (0.0 = deterministic, 1.0 = very random) + * @return this builder + */ + public Builder temperature(double temperature) { + this.temperature = temperature; + return this; + } + + /** + * Builds the LlmInferenceParameters instance. + * + * @return the built parameters + */ + public LlmInferenceParameters build() { + return new LlmInferenceParameters(temperature); + } + } +} diff --git a/jablib/src/main/java/org/jabref/logic/ai/framework/llms/LlmModel.java b/jablib/src/main/java/org/jabref/logic/ai/framework/llms/LlmModel.java new file mode 100644 index 00000000000..0c8cbcf6357 --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/ai/framework/llms/LlmModel.java @@ -0,0 +1,22 @@ +package org.jabref.logic.ai.framework.llms; + +import java.util.List; + +import org.jabref.logic.ai.framework.messages.ChatMessage; +import org.jabref.logic.ai.framework.messages.LlmMessage; + +/** + * Interface for Large Language Model implementations. + */ +public interface LlmModel { + + /** + * Sends a chat conversation to the LLM and returns the generated response. + * + * @param messages the conversation messages in chronological order + * @param parameters inference parameters controlling the generation + * @return the LLM's response message + * @throws LlmInferenceException if the inference fails + */ + LlmMessage chat(List messages, LlmInferenceParameters parameters) throws LlmInferenceException; +} diff --git a/jablib/src/main/java/org/jabref/logic/ai/framework/llms/LlmRateLimitException.java b/jablib/src/main/java/org/jabref/logic/ai/framework/llms/LlmRateLimitException.java new file mode 100644 index 00000000000..14f7032f56f --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/ai/framework/llms/LlmRateLimitException.java @@ -0,0 +1,14 @@ +package org.jabref.logic.ai.framework.llms; + +import org.jabref.logic.l10n.Localization; + +/** + * Exception thrown when LLM service rate limit is exceeded. + */ +public class LlmRateLimitException extends LlmInferenceException { + + @Override + public String getLocalizedMessage() { + return Localization.lang("LLM service rate limit exceeded."); + } +} diff --git a/jablib/src/main/java/org/jabref/logic/ai/framework/messages/ChatMessage.java b/jablib/src/main/java/org/jabref/logic/ai/framework/messages/ChatMessage.java new file mode 100644 index 00000000000..4d0b17e2d1a --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/ai/framework/messages/ChatMessage.java @@ -0,0 +1,26 @@ +package org.jabref.logic.ai.framework.messages; + +/** + * Base class for all chat messages in conversations. + */ +public class ChatMessage { + private final String text; + + /** + * Creates a new chat message. + * + * @param text the message content + */ + public ChatMessage(String text) { + this.text = text; + } + + /** + * Returns the message text. + * + * @return the message content + */ + public String getText() { + return text; + } +} diff --git a/jablib/src/main/java/org/jabref/logic/ai/framework/messages/LlmMessage.java b/jablib/src/main/java/org/jabref/logic/ai/framework/messages/LlmMessage.java new file mode 100644 index 00000000000..32825700814 --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/ai/framework/messages/LlmMessage.java @@ -0,0 +1,16 @@ +package org.jabref.logic.ai.framework.messages; + +/** + * Represents an LLM response message in a chat conversation. + */ +public class LlmMessage extends ChatMessage { + + /** + * Creates a new LLM message. + * + * @param text the message content + */ + public LlmMessage(String text) { + super(text); + } +} diff --git a/jablib/src/main/java/org/jabref/logic/ai/framework/messages/SystemMessage.java b/jablib/src/main/java/org/jabref/logic/ai/framework/messages/SystemMessage.java new file mode 100644 index 00000000000..3467b545fea --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/ai/framework/messages/SystemMessage.java @@ -0,0 +1,16 @@ +package org.jabref.logic.ai.framework.messages; + +/** + * Represents a system message in a chat conversation. + */ +public class SystemMessage extends ChatMessage { + + /** + * Creates a new system message. + * + * @param text the message content + */ + public SystemMessage(String text) { + super(text); + } +} diff --git a/jablib/src/main/java/org/jabref/logic/ai/framework/messages/UserMessage.java b/jablib/src/main/java/org/jabref/logic/ai/framework/messages/UserMessage.java new file mode 100644 index 00000000000..dec4685e810 --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/ai/framework/messages/UserMessage.java @@ -0,0 +1,16 @@ +package org.jabref.logic.ai.framework.messages; + +/** + * Represents a user message in a chat conversation. + */ +public class UserMessage extends ChatMessage { + + /** + * Creates a new user message. + * + * @param text the message content + */ + public UserMessage(String text) { + super(text); + } +} diff --git a/jablib/src/main/java/org/jabref/logic/ai/framework/templates/ChatMessageTemplate.java b/jablib/src/main/java/org/jabref/logic/ai/framework/templates/ChatMessageTemplate.java new file mode 100644 index 00000000000..693e3550851 --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/ai/framework/templates/ChatMessageTemplate.java @@ -0,0 +1,31 @@ +package org.jabref.logic.ai.framework.templates; + +import org.apache.velocity.VelocityContext; + +import org.jabref.logic.ai.framework.messages.ChatMessage; + +/** + * Template for generating chat messages. + */ +public class ChatMessageTemplate extends PromptTemplate { + + /** + * Creates a new chat message template. + * + * @param template the Velocity template string + */ + public ChatMessageTemplate(String template) { + super(template); + } + + /** + * Renders the template with the given context. + * + * @param context the Velocity context containing variables + * @return the rendered chat message + */ + public ChatMessage render(VelocityContext context) { + String content = super.apply(context); + return new ChatMessage(content); // Default fallback + } +} diff --git a/jablib/src/main/java/org/jabref/logic/ai/framework/templates/ChatTemplate.java b/jablib/src/main/java/org/jabref/logic/ai/framework/templates/ChatTemplate.java new file mode 100644 index 00000000000..73b62137aa1 --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/ai/framework/templates/ChatTemplate.java @@ -0,0 +1,35 @@ +package org.jabref.logic.ai.framework.templates; + +import org.apache.velocity.VelocityContext; + +import java.util.List; + +import org.jabref.logic.ai.framework.messages.ChatMessage; + +/** + * Template for generating entire chat conversations from multiple message templates. + */ +public class ChatTemplate { + private final List messageTemplates; + + /** + * Creates a new chat template. + * + * @param messageTemplates the ordered list of message templates to render + */ + public ChatTemplate(List messageTemplates) { + this.messageTemplates = List.copyOf(messageTemplates); // defensive copy + } + + /** + * Renders all message templates with the given context to create a chat conversation. + * + * @param context the Velocity context containing variables + * @return the list of rendered chat messages + */ + public List render(VelocityContext context) { + return messageTemplates.stream() + .map(template -> template.render(context)) + .toList(); + } +} diff --git a/jablib/src/main/java/org/jabref/logic/ai/framework/templates/LlmMessageTemplate.java b/jablib/src/main/java/org/jabref/logic/ai/framework/templates/LlmMessageTemplate.java new file mode 100644 index 00000000000..7175f072dfe --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/ai/framework/templates/LlmMessageTemplate.java @@ -0,0 +1,27 @@ +package org.jabref.logic.ai.framework.templates; + +import org.apache.velocity.VelocityContext; + +import org.jabref.logic.ai.framework.messages.ChatMessage; +import org.jabref.logic.ai.framework.messages.LlmMessage; + +/** + * Template for generating LLM messages. + */ +public class LlmMessageTemplate extends ChatMessageTemplate { + + /** + * Creates a new LLM message template. + * + * @param template the Velocity template string + */ + public LlmMessageTemplate(String template) { + super(template); + } + + @Override + public ChatMessage render(VelocityContext context) { + String content = super.apply(context); + return new LlmMessage(content); + } +} diff --git a/jablib/src/main/java/org/jabref/logic/ai/framework/templates/PromptTemplate.java b/jablib/src/main/java/org/jabref/logic/ai/framework/templates/PromptTemplate.java new file mode 100644 index 00000000000..76724cd3ff4 --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/ai/framework/templates/PromptTemplate.java @@ -0,0 +1,40 @@ +package org.jabref.logic.ai.framework.templates; + +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.VelocityEngine; + +import java.io.StringWriter; + +/** + * Base class for prompt templates using Apache Velocity templating. + */ +public class PromptTemplate { + private static final VelocityEngine VELOCITY_ENGINE = new VelocityEngine(); + + static { + VELOCITY_ENGINE.init(); + } + + private final String template; + + /** + * Creates a new prompt template. + * + * @param template the Velocity template string + */ + public PromptTemplate(String template) { + this.template = template; + } + + /** + * Applies the template with the given Velocity context. + * + * @param context the Velocity context containing variables + * @return the rendered template string + */ + public String apply(VelocityContext context) { + StringWriter writer = new StringWriter(); + VELOCITY_ENGINE.evaluate(context, writer, "template", template); + return writer.toString(); + } +} diff --git a/jablib/src/main/java/org/jabref/logic/ai/framework/templates/SystemMessageTemplate.java b/jablib/src/main/java/org/jabref/logic/ai/framework/templates/SystemMessageTemplate.java new file mode 100644 index 00000000000..c838f34da66 --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/ai/framework/templates/SystemMessageTemplate.java @@ -0,0 +1,27 @@ +package org.jabref.logic.ai.framework.templates; + +import org.apache.velocity.VelocityContext; + +import org.jabref.logic.ai.framework.messages.ChatMessage; +import org.jabref.logic.ai.framework.messages.SystemMessage; + +/** + * Template for generating system messages. + */ +public class SystemMessageTemplate extends ChatMessageTemplate { + + /** + * Creates a new system message template. + * + * @param template the Velocity template string + */ + public SystemMessageTemplate(String template) { + super(template); + } + + @Override + public ChatMessage render(VelocityContext context) { + String content = super.apply(context); + return new SystemMessage(content); + } +} diff --git a/jablib/src/main/java/org/jabref/logic/ai/framework/templates/UserMessageTemplate.java b/jablib/src/main/java/org/jabref/logic/ai/framework/templates/UserMessageTemplate.java new file mode 100644 index 00000000000..be118699dcf --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/ai/framework/templates/UserMessageTemplate.java @@ -0,0 +1,27 @@ +package org.jabref.logic.ai.framework.templates; + +import org.apache.velocity.VelocityContext; + +import org.jabref.logic.ai.framework.messages.ChatMessage; +import org.jabref.logic.ai.framework.messages.UserMessage; + +/** + * Template for generating user messages. + */ +public class UserMessageTemplate extends ChatMessageTemplate { + + /** + * Creates a new user message template. + * + * @param template the Velocity template string + */ + public UserMessageTemplate(String template) { + super(template); + } + + @Override + public ChatMessage render(VelocityContext context) { + String content = super.apply(context); + return new UserMessage(content); + } +} diff --git a/jablib/src/main/java/org/jabref/logic/ai/framework/vectordb/VectorDatabase.java b/jablib/src/main/java/org/jabref/logic/ai/framework/vectordb/VectorDatabase.java new file mode 100644 index 00000000000..8d18cdd2e6a --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/ai/framework/vectordb/VectorDatabase.java @@ -0,0 +1,39 @@ +package org.jabref.logic.ai.framework.vectordb; + +import java.util.List; +import java.util.Map; + +/** + * Interface for vector database implementations. + */ +public interface VectorDatabase { + + /** + * Adds a document with its embedding and metadata to the database. + * + * @param text the text content of the document + * @param embedding the embedding vector for the text + * @param metadata additional metadata to associate with the document + * @throws VectorDatabaseException if the operation fails + */ + void add(String text, List embedding, Map metadata) throws VectorDatabaseException; + + /** + * Finds documents similar to the query embedding. + * + * @param queryEmbedding the embedding vector to search for + * @param params parameters controlling the search (minimum score, max results) + * @param filter metadata filter for narrowing search scope (empty map means no filter) + * @return list of similar documents ordered by relevance + * @throws VectorDatabaseException if the operation fails + */ + List find(List queryEmbedding, VectorDatabaseFindParameters params, Map filter) throws VectorDatabaseException; + + /** + * Removes documents matching the filter criteria. + * + * @param filter metadata filter specifying which documents to remove + * @throws VectorDatabaseException if the operation fails + */ + void remove(Map filter) throws VectorDatabaseException; +} diff --git a/jablib/src/main/java/org/jabref/logic/ai/framework/vectordb/VectorDatabaseConnectionException.java b/jablib/src/main/java/org/jabref/logic/ai/framework/vectordb/VectorDatabaseConnectionException.java new file mode 100644 index 00000000000..e2b50a18736 --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/ai/framework/vectordb/VectorDatabaseConnectionException.java @@ -0,0 +1,14 @@ +package org.jabref.logic.ai.framework.vectordb; + +import org.jabref.logic.l10n.Localization; + +/** + * Exception thrown when connection to vector database fails. + */ +public class VectorDatabaseConnectionException extends VectorDatabaseException { + + @Override + public String getLocalizedMessage() { + return Localization.lang("Failed to connect to vector database."); + } +} diff --git a/jablib/src/main/java/org/jabref/logic/ai/framework/vectordb/VectorDatabaseException.java b/jablib/src/main/java/org/jabref/logic/ai/framework/vectordb/VectorDatabaseException.java new file mode 100644 index 00000000000..b1991953e77 --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/ai/framework/vectordb/VectorDatabaseException.java @@ -0,0 +1,14 @@ +package org.jabref.logic.ai.framework.vectordb; + +import org.jabref.logic.l10n.Localization; + +/** + * Exception thrown when vector database operations fail. + */ +public class VectorDatabaseException extends Exception { + + @Override + public String getLocalizedMessage() { + return Localization.lang("An error occurred in the vector database."); + } +} diff --git a/jablib/src/main/java/org/jabref/logic/ai/framework/vectordb/VectorDatabaseFindParameters.java b/jablib/src/main/java/org/jabref/logic/ai/framework/vectordb/VectorDatabaseFindParameters.java new file mode 100644 index 00000000000..d64b5bdfd1b --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/ai/framework/vectordb/VectorDatabaseFindParameters.java @@ -0,0 +1,95 @@ +package org.jabref.logic.ai.framework.vectordb; + +/** + * Parameters for vector database similarity search operations. + */ +public class VectorDatabaseFindParameters { + private final double minimumScore; + private final int maximumDocumentsCount; + + private VectorDatabaseFindParameters(double minimumScore, int maximumDocumentsCount) { + if (minimumScore <= 0) { + throw new IllegalArgumentException("minimumScore must be positive"); + } + if (maximumDocumentsCount <= 0) { + throw new IllegalArgumentException("maximumDocumentsCount must be positive"); + } + this.minimumScore = minimumScore; + this.maximumDocumentsCount = maximumDocumentsCount; + } + + /** + * Creates a builder for VectorDatabaseFindParameters. + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Creates default find parameters. + * + * @return default parameters with minimumScore 0.7 and maximumDocumentsCount 10 + */ + public static VectorDatabaseFindParameters defaultParameters() { + return builder().minimumScore(0.7).maximumDocumentsCount(10).build(); + } + + /** + * Returns the minimum similarity score for results to be included. + * + * @return the minimum score + */ + public double getMinimumScore() { + return minimumScore; + } + + /** + * Returns the maximum number of documents to return. + * + * @return the maximum count + */ + public int getMaximumDocumentsCount() { + return maximumDocumentsCount; + } + + /** + * Builder for VectorDatabaseFindParameters. + */ + public static class Builder { + private double minimumScore = 0.7; + private int maximumDocumentsCount = 10; + + /** + * Sets the minimum similarity score for results to be included. + * + * @param minimumScore minimum similarity score (must be positive) + * @return this builder + */ + public Builder minimumScore(double minimumScore) { + this.minimumScore = minimumScore; + return this; + } + + /** + * Sets the maximum number of documents to return. + * + * @param maximumDocumentsCount maximum number of documents (must be positive) + * @return this builder + */ + public Builder maximumDocumentsCount(int maximumDocumentsCount) { + this.maximumDocumentsCount = maximumDocumentsCount; + return this; + } + + /** + * Builds the VectorDatabaseFindParameters instance. + * + * @return the built parameters + */ + public VectorDatabaseFindParameters build() { + return new VectorDatabaseFindParameters(minimumScore, maximumDocumentsCount); + } + } +} diff --git a/jablib/src/main/java/org/jabref/logic/ai/framework/vectordb/VectorDatabaseQueryException.java b/jablib/src/main/java/org/jabref/logic/ai/framework/vectordb/VectorDatabaseQueryException.java new file mode 100644 index 00000000000..1be4a4fb596 --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/ai/framework/vectordb/VectorDatabaseQueryException.java @@ -0,0 +1,14 @@ +package org.jabref.logic.ai.framework.vectordb; + +import org.jabref.logic.l10n.Localization; + +/** + * Exception thrown when vector database query operations fail. + */ +public class VectorDatabaseQueryException extends VectorDatabaseException { + + @Override + public String getLocalizedMessage() { + return Localization.lang("Vector database query failed."); + } +} diff --git a/jablib/src/main/java/org/jabref/logic/ai/framework/vectordb/VectorFindResult.java b/jablib/src/main/java/org/jabref/logic/ai/framework/vectordb/VectorFindResult.java new file mode 100644 index 00000000000..dc980c50245 --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/ai/framework/vectordb/VectorFindResult.java @@ -0,0 +1,52 @@ +package org.jabref.logic.ai.framework.vectordb; + +import java.util.Map; + +/** + * Result of a vector database similarity search operation. + */ +public class VectorFindResult { + private final String text; + private final double score; + private final Map metadata; + + /** + * Creates a new vector find result. + * + * @param text the text content of the found document + * @param score the similarity score (typically 0.0 to 1.0) + * @param metadata additional metadata associated with the document + */ + public VectorFindResult(String text, double score, Map metadata) { + this.text = text; + this.score = score; + this.metadata = Map.copyOf(metadata); // defensive copy + } + + /** + * Returns the text content of the found document. + * + * @return the text + */ + public String getText() { + return text; + } + + /** + * Returns the similarity score. + * + * @return the score + */ + public double getScore() { + return score; + } + + /** + * Returns the metadata associated with the document. + * + * @return the metadata map + */ + public Map getMetadata() { + return metadata; + } +} From cfed62ea46026da30cbe61a59890f6127038c67d Mon Sep 17 00:00:00 2001 From: Ruslan Popov Date: Sun, 2 Nov 2025 19:38:07 +0100 Subject: [PATCH 2/3] refactor(ai): migrate chat messages to the new framework --- .../logic/ai/chatting/ChatHistoryService.java | 2 +- .../chathistory/ChatHistoryStorage.java | 2 +- .../storages/MVStoreChatHistoryStorage.java | 38 ++++++++----------- .../jabref/logic/ai/util/ErrorMessage.java | 24 ++---------- .../chathistory/ChatHistoryStorageTest.java | 10 ++--- 5 files changed, 27 insertions(+), 49 deletions(-) diff --git a/jablib/src/main/java/org/jabref/logic/ai/chatting/ChatHistoryService.java b/jablib/src/main/java/org/jabref/logic/ai/chatting/ChatHistoryService.java index 0a1de1274a1..095c94c4acf 100644 --- a/jablib/src/main/java/org/jabref/logic/ai/chatting/ChatHistoryService.java +++ b/jablib/src/main/java/org/jabref/logic/ai/chatting/ChatHistoryService.java @@ -20,7 +20,7 @@ import org.jabref.model.groups.GroupTreeNode; import com.google.common.eventbus.Subscribe; -import dev.langchain4j.data.message.ChatMessage; +import org.jabref.logic.ai.framework.messages.ChatMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/jablib/src/main/java/org/jabref/logic/ai/chatting/chathistory/ChatHistoryStorage.java b/jablib/src/main/java/org/jabref/logic/ai/chatting/chathistory/ChatHistoryStorage.java index 1a30a4332e7..ddfb6aa4685 100644 --- a/jablib/src/main/java/org/jabref/logic/ai/chatting/chathistory/ChatHistoryStorage.java +++ b/jablib/src/main/java/org/jabref/logic/ai/chatting/chathistory/ChatHistoryStorage.java @@ -3,7 +3,7 @@ import java.nio.file.Path; import java.util.List; -import dev.langchain4j.data.message.ChatMessage; +import org.jabref.logic.ai.framework.messages.ChatMessage; public interface ChatHistoryStorage { List loadMessagesForEntry(Path bibDatabasePath, String citationKey); diff --git a/jablib/src/main/java/org/jabref/logic/ai/chatting/chathistory/storages/MVStoreChatHistoryStorage.java b/jablib/src/main/java/org/jabref/logic/ai/chatting/chathistory/storages/MVStoreChatHistoryStorage.java index 31e13930b3e..df6b1f7168d 100644 --- a/jablib/src/main/java/org/jabref/logic/ai/chatting/chathistory/storages/MVStoreChatHistoryStorage.java +++ b/jablib/src/main/java/org/jabref/logic/ai/chatting/chathistory/storages/MVStoreChatHistoryStorage.java @@ -12,9 +12,9 @@ import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.NotificationService; -import dev.langchain4j.data.message.AiMessage; -import dev.langchain4j.data.message.ChatMessage; -import dev.langchain4j.data.message.UserMessage; +import org.jabref.logic.ai.framework.messages.ChatMessage; +import org.jabref.logic.ai.framework.messages.LlmMessage; +import org.jabref.logic.ai.framework.messages.UserMessage; import kotlin.ranges.IntRange; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,34 +33,28 @@ public static ChatHistoryRecord fromLangchainMessage(ChatMessage chatMessage) { } private static String getContentFromLangchainMessage(ChatMessage chatMessage) { - String content; - - switch (chatMessage) { - case AiMessage aiMessage -> - content = aiMessage.text(); - case UserMessage userMessage -> - content = userMessage.singleText(); - case ErrorMessage errorMessage -> - content = errorMessage.getText(); - default -> { - LOGGER.warn("ChatHistoryRecord supports only AI, user. and error messages, but added message has other type: {}", chatMessage.type().name()); - return ""; - } + if (chatMessage instanceof LlmMessage llmMessage) { + return llmMessage.getText(); + } else if (chatMessage instanceof UserMessage userMessage) { + return userMessage.getText(); + } else if (chatMessage instanceof ErrorMessage errorMessage) { + return errorMessage.getText(); + } else { + LOGGER.warn("ChatHistoryRecord supports only AI, user, and error messages, but added message has other type: {}", chatMessage.getClass().getSimpleName()); + return ""; } - - return content; } public ChatMessage toLangchainMessage() { - if (className.equals(AiMessage.class.getName())) { - return new AiMessage(content); - } else if (className.equals(UserMessage.class.getName())) { + if (className.equals(LlmMessage.class.getName()) || className.equals("dev.langchain4j.data.message.AiMessage")) { + return new LlmMessage(content); + } else if (className.equals(UserMessage.class.getName()) || className.equals("dev.langchain4j.data.message.UserMessage")) { return new UserMessage(content); } else if (className.equals(ErrorMessage.class.getName())) { return new ErrorMessage(content); } else { LOGGER.warn("ChatHistoryRecord supports only AI and user messages, but retrieved message has other type: {}. Will treat as an AI message.", className); - return new AiMessage(content); + return new LlmMessage(content); } } } diff --git a/jablib/src/main/java/org/jabref/logic/ai/util/ErrorMessage.java b/jablib/src/main/java/org/jabref/logic/ai/util/ErrorMessage.java index cbbaa3e833c..a4913bf1bb7 100644 --- a/jablib/src/main/java/org/jabref/logic/ai/util/ErrorMessage.java +++ b/jablib/src/main/java/org/jabref/logic/ai/util/ErrorMessage.java @@ -1,31 +1,15 @@ package org.jabref.logic.ai.util; -import dev.langchain4j.data.message.ChatMessage; -import dev.langchain4j.data.message.ChatMessageType; +import org.jabref.logic.ai.framework.messages.ChatMessage; /** * Class representing an error from AI side. - * This is a dummy class, that extends from langchain4j's {@link ChatMessage}, but it should not be used in any - * of langchain4j's classes or algorithms as langchain4j does not support adding new message types. + * This is a dummy class that extends from our {@link ChatMessage}. * The primary use of this class is to be stored in a chat history and displayed in the UI. */ -public class ErrorMessage implements ChatMessage { - String text; +public class ErrorMessage extends ChatMessage { public ErrorMessage(String text) { - this.text = text; - } - - public ChatMessageType type() { - // In order to make new chat message type you need to: - // 1. Make a class that implements {@link ChatMessage}. - // 2. Add it to {@link ChatMessageType}. - // Only the first point is possible to do in the external code. - - return ChatMessageType.AI; - } - - public String getText() { - return text; + super(text); } } diff --git a/jablib/src/test/java/org/jabref/logic/ai/chatting/chathistory/ChatHistoryStorageTest.java b/jablib/src/test/java/org/jabref/logic/ai/chatting/chathistory/ChatHistoryStorageTest.java index 49528b4abb8..3d74048db12 100644 --- a/jablib/src/test/java/org/jabref/logic/ai/chatting/chathistory/ChatHistoryStorageTest.java +++ b/jablib/src/test/java/org/jabref/logic/ai/chatting/chathistory/ChatHistoryStorageTest.java @@ -3,9 +3,9 @@ import java.nio.file.Path; import java.util.List; -import dev.langchain4j.data.message.AiMessage; -import dev.langchain4j.data.message.ChatMessage; -import dev.langchain4j.data.message.UserMessage; +import org.jabref.logic.ai.framework.messages.ChatMessage; +import org.jabref.logic.ai.framework.messages.LlmMessage; +import org.jabref.logic.ai.framework.messages.UserMessage; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -41,7 +41,7 @@ void tearDown() { void entryChatHistory() { List messages = List.of( new UserMessage("hi!"), - new AiMessage("hello!") + new LlmMessage("hello!") ); storage.storeMessagesForEntry(tempDir.resolve("test.bib"), "citationKey", messages); @@ -53,7 +53,7 @@ void entryChatHistory() { void groupChatHistory() { List messages = List.of( new UserMessage("hi!"), - new AiMessage("hello!") + new LlmMessage("hello!") ); storage.storeMessagesForGroup(tempDir.resolve("test.bib"), "group", messages); From 37b7b6ea2010fa064bd9031ad5d5631706527f74 Mon Sep 17 00:00:00 2001 From: Ruslan Popov Date: Thu, 6 Nov 2025 11:53:03 +0100 Subject: [PATCH 3/3] refactor(ai): start migrating messages to new framework --- .../ai/components/aichat/AiChatComponent.java | 12 ++-- .../aichat/AiChatGuardedComponent.java | 2 +- .../ai/components/aichat/AiChatWindow.java | 2 +- .../chathistory/ChatHistoryComponent.java | 2 +- .../chatmessage/ChatMessageComponent.java | 57 +++++++++---------- .../jabref/gui/groups/GroupTreeViewModel.java | 2 +- 6 files changed, 38 insertions(+), 39 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/AiChatComponent.java b/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/AiChatComponent.java index 17abb13534c..c41fe6efe44 100644 --- a/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/AiChatComponent.java +++ b/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/AiChatComponent.java @@ -41,9 +41,9 @@ import com.airhacks.afterburner.views.ViewLoader; import com.google.common.annotations.VisibleForTesting; -import dev.langchain4j.data.message.AiMessage; -import dev.langchain4j.data.message.ChatMessage; -import dev.langchain4j.data.message.UserMessage; +import org.jabref.logic.ai.framework.messages.ChatMessage; +import org.jabref.logic.ai.framework.messages.LlmMessage; +import org.jabref.logic.ai.framework.messages.UserMessage; import org.controlsfx.control.PopOver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -264,11 +264,11 @@ private void onSendMessage(String userPrompt) { updatePromptHistory(); setLoading(true); - BackgroundTask task = + BackgroundTask task = BackgroundTask .wrap(() -> aiChatLogic.execute(userMessage)) .showToUser(true) - .onSuccess(aiMessage -> { + .onSuccess(llmMessage -> { setLoading(false); chatPrompt.requestPromptFocus(); }) @@ -299,7 +299,7 @@ private void addError(String error) { private void updatePromptHistory() { chatPrompt.getHistory().clear(); - chatPrompt.getHistory().addAll(getReversedUserMessagesStream().map(UserMessage::singleText).toList()); + chatPrompt.getHistory().addAll(getReversedUserMessagesStream().map(UserMessage::getText).toList()); } private Stream getReversedUserMessagesStream() { diff --git a/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/AiChatGuardedComponent.java b/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/AiChatGuardedComponent.java index 4a22e6d1519..a802e44571c 100644 --- a/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/AiChatGuardedComponent.java +++ b/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/AiChatGuardedComponent.java @@ -14,7 +14,7 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; -import dev.langchain4j.data.message.ChatMessage; +import org.jabref.logic.ai.framework.messages.ChatMessage; /** * Main class for AI chatting. It checks if the AI features are enabled and if the embedding model is properly set up. diff --git a/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/AiChatWindow.java b/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/AiChatWindow.java index 8e1fd36bea7..a47adaf7c03 100644 --- a/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/AiChatWindow.java +++ b/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/AiChatWindow.java @@ -15,7 +15,7 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; -import dev.langchain4j.data.message.ChatMessage; +import org.jabref.logic.ai.framework.messages.ChatMessage; public class AiChatWindow extends BaseWindow { private final AiService aiService; diff --git a/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/chathistory/ChatHistoryComponent.java b/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/chathistory/ChatHistoryComponent.java index c99e125b8e7..bfd62b912db 100644 --- a/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/chathistory/ChatHistoryComponent.java +++ b/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/chathistory/ChatHistoryComponent.java @@ -10,7 +10,7 @@ import org.jabref.gui.util.UiTaskExecutor; import com.airhacks.afterburner.views.ViewLoader; -import dev.langchain4j.data.message.ChatMessage; +import org.jabref.logic.ai.framework.messages.ChatMessage; public class ChatHistoryComponent extends ScrollPane { @FXML private VBox vBox; diff --git a/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/chatmessage/ChatMessageComponent.java b/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/chatmessage/ChatMessageComponent.java index f620938dea3..ad5b2a13d14 100644 --- a/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/chatmessage/ChatMessageComponent.java +++ b/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/chatmessage/ChatMessageComponent.java @@ -18,9 +18,9 @@ import org.jabref.logic.l10n.Localization; import com.airhacks.afterburner.views.ViewLoader; -import dev.langchain4j.data.message.AiMessage; -import dev.langchain4j.data.message.ChatMessage; -import dev.langchain4j.data.message.UserMessage; +import org.jabref.logic.ai.framework.messages.ChatMessage; +import org.jabref.logic.ai.framework.messages.LlmMessage; +import org.jabref.logic.ai.framework.messages.UserMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -74,32 +74,31 @@ public void setOnDelete(Consumer onDeleteCallback) { } private void loadChatMessage() { - switch (chatMessage.get()) { - case UserMessage userMessage -> { - setColor("-jr-ai-message-user", "-jr-ai-message-user-border"); - setNodeOrientation(NodeOrientation.RIGHT_TO_LEFT); - wrapperHBox.setAlignment(Pos.TOP_RIGHT); - sourceLabel.setText(Localization.lang("User")); - markdownTextFlow.setMarkdown(userMessage.singleText()); - } - - case AiMessage aiMessage -> { - setColor("-jr-ai-message-ai", "-jr-ai-message-ai-border"); - setNodeOrientation(NodeOrientation.LEFT_TO_RIGHT); - wrapperHBox.setAlignment(Pos.TOP_LEFT); - sourceLabel.setText(Localization.lang("AI")); - markdownTextFlow.setMarkdown(aiMessage.text()); - } - - case ErrorMessage errorMessage -> { - setColor("-jr-ai-message-error", "-jr-ai-message-error-border"); - setNodeOrientation(NodeOrientation.LEFT_TO_RIGHT); - sourceLabel.setText(Localization.lang("Error")); - markdownTextFlow.setMarkdown(errorMessage.getText()); - } - - default -> - LOGGER.error("ChatMessageComponent supports only user, AI, or error messages, but other type was passed: {}", chatMessage.get().type().name()); + ChatMessage message = chatMessage.get(); + + if (message instanceof UserMessage userMessage) { + setColor("-jr-ai-message-user", "-jr-ai-message-user-border"); + setNodeOrientation(NodeOrientation.RIGHT_TO_LEFT); + wrapperHBox.setAlignment(Pos.TOP_RIGHT); + sourceLabel.setText(Localization.lang("User")); + markdownTextFlow.setMarkdown(userMessage.getText()); + } else if (message instanceof LlmMessage llmMessage) { + setColor("-jr-ai-message-ai", "-jr-ai-message-ai-border"); + setNodeOrientation(NodeOrientation.LEFT_TO_RIGHT); + wrapperHBox.setAlignment(Pos.TOP_LEFT); + sourceLabel.setText(Localization.lang("AI")); + markdownTextFlow.setMarkdown(llmMessage.getText()); + } else if (message instanceof ErrorMessage errorMessage) { + setColor("-jr-ai-message-error", "-jr-ai-message-error-border"); + setNodeOrientation(NodeOrientation.LEFT_TO_RIGHT); + sourceLabel.setText(Localization.lang("Error")); + markdownTextFlow.setMarkdown(errorMessage.getText()); + } else { + setColor("-jr-ai-message-ai", "-jr-ai-message-ai-border"); + setNodeOrientation(NodeOrientation.LEFT_TO_RIGHT); + wrapperHBox.setAlignment(Pos.TOP_LEFT); + sourceLabel.setText(Localization.lang("Unknown")); + markdownTextFlow.setMarkdown(message.getText()); } } diff --git a/jabgui/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java b/jabgui/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java index 3048874495e..61c81fd95a1 100644 --- a/jabgui/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java @@ -47,7 +47,7 @@ import org.jabref.model.metadata.MetaData; import com.tobiasdiez.easybind.EasyBind; -import dev.langchain4j.data.message.ChatMessage; +import org.jabref.logic.ai.framework.messages.ChatMessage; import org.jspecify.annotations.NonNull; public class GroupTreeViewModel extends AbstractViewModel {