Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/main/java/com/devoxx/genie/model/Constant.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ private Constant() {
public static final Integer MAX_OUTPUT_TOKENS = 4000;
public static final Integer MAX_RETRIES = 1;
public static final Integer TIMEOUT = 180;
public static final Integer MAX_MEMORY = 10;
public static final Integer MAX_MEMORY = 50;

// Hide Search Button
public static final Boolean ENABLE_WEB_SEARCH = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ public static MessageCreationService getInstance() {
* @param chatMessageContext the chat message context
*/
public void addUserMessageToContext(@NotNull ChatMessageContext chatMessageContext) {
log.debug("Adding user message to context: userPrompt={}", chatMessageContext.getUserPrompt());

// Check if user message already exists to prevent duplicates
if (chatMessageContext.getUserMessage() != null) {
// Message already exists, skip creating another one
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import dev.langchain4j.data.message.SystemMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.memory.ChatMemory;
import dev.langchain4j.model.bedrock.BedrockChatModel;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@
@Slf4j
public class ChatMemoryService implements ChatMemoryProvider {

public static final String CHAT_MEMORY_NOT_INITIALIZED_FOR_PROJECT = "Chat memory not initialized for project: ";
public static final String FAILED_TO_REMOVE_MESSAGES_FROM_MEMORY = "Failed to remove messages from memory";
public static final String FAILED_TO_GET_CHAT_MEMORY_FOR_PROJECT_HASH = "Failed to get chat memory for project hash: ";
public static final String FAILED_TO_INITIALIZE_CHAT_MEMORY_FOR_PROJECT = "Failed to initialize chat memory for project: ";
public static final String FAILED_TO_CLEAR_MEMORY = "Failed to clear memory";
public static final String FAILED_TO_ADD_MESSAGE_TO_MEMORY = "Failed to add message to memory";
public static final String FAILED_TO_GET_MESSAGES_FROM_MEMORY = "Failed to get messages from memory";
public static final String FAILED_TO_CHECK_IF_MEMORY_IS_EMPTY = "Failed to check if memory is empty";
public static final String FAILED_TO_REMOVE_LAST_MESSAGE_FROM_MEMORY = "Failed to remove last message from memory";

private final Map<String, MessageWindowChatMemory> projectConversations = new ConcurrentHashMap<>();
private final InMemoryChatMemoryStore inMemoryChatMemoryStore = new InMemoryChatMemoryStore();

Expand All @@ -48,7 +58,7 @@ public void initialize(@NotNull Project project, int chatMemorySize) {

createChatMemory(projectHash, chatMemorySize);
} catch (Exception e) {
throw new MemoryException("Failed to initialize chat memory for project: " + projectHash, e);
throw new MemoryException(FAILED_TO_INITIALIZE_CHAT_MEMORY_FOR_PROJECT + projectHash, e);
}
}

Expand All @@ -67,7 +77,7 @@ public void clearMemory(@NotNull Project project) {
log.warn("Attempted to clear memory for non-existent project: {}", projectHash);
}
} catch (Exception e) {
throw new MemoryException("Failed to clear memory", e);
throw new MemoryException(FAILED_TO_CLEAR_MEMORY, e);
}
}

Expand Down Expand Up @@ -101,11 +111,11 @@ public void addMessage(@NotNull Project project, ChatMessage chatMessage) {
memory.add(chatMessage);
log.debug("Successfully added message to project: {}, message type: {}", projectHash, chatMessage.getClass().getSimpleName());
} else {
throw new MemoryException("Chat memory not initialized for project: " + projectHash);
throw new MemoryException(CHAT_MEMORY_NOT_INITIALIZED_FOR_PROJECT + projectHash);
}
} catch (Exception e) {
if (!(e instanceof MemoryException)) {
throw new MemoryException("Failed to add message to memory", e);
throw new MemoryException(FAILED_TO_ADD_MESSAGE_TO_MEMORY, e);
}
throw e;
}
Expand All @@ -123,11 +133,11 @@ public List<ChatMessage> getMessages(@NotNull Project project) {
if (memory != null) {
return memory.messages();
} else {
throw new MemoryException("Chat memory not initialized for project: " + projectHash);
throw new MemoryException(CHAT_MEMORY_NOT_INITIALIZED_FOR_PROJECT + projectHash);
}
} catch (Exception e) {
if (!(e instanceof MemoryException)) {
throw new MemoryException("Failed to get messages from memory", e);
throw new MemoryException(FAILED_TO_GET_MESSAGES_FROM_MEMORY, e);
}
throw e;
}
Expand All @@ -149,7 +159,7 @@ public boolean isEmpty(@NotNull Project project) {
return true;
}
} catch (Exception e) {
throw new MemoryException("Failed to check if memory is empty", e);
throw new MemoryException(FAILED_TO_CHECK_IF_MEMORY_IS_EMPTY, e);
}
}

Expand All @@ -176,11 +186,11 @@ public void removeLastMessage(@NotNull Project project) {
lastMessage.getClass().getSimpleName(), projectHash);
}
} else {
throw new MemoryException("Chat memory not initialized for project: " + projectHash);
throw new MemoryException(CHAT_MEMORY_NOT_INITIALIZED_FOR_PROJECT + projectHash);
}
} catch (Exception e) {
if (!(e instanceof MemoryException)) {
throw new MemoryException("Failed to remove last message from memory", e);
throw new MemoryException(FAILED_TO_REMOVE_LAST_MESSAGE_FROM_MEMORY, e);
}
throw e;
}
Expand Down Expand Up @@ -212,11 +222,11 @@ public void removeMessages(@NotNull Project project, List<ChatMessage> messagesT
messagesToRemove.size(), projectHash);
}
} else {
throw new MemoryException("Chat memory not initialized for project: " + projectHash);
throw new MemoryException(CHAT_MEMORY_NOT_INITIALIZED_FOR_PROJECT + projectHash);
}
} catch (Exception e) {
if (!(e instanceof MemoryException)) {
throw new MemoryException("Failed to remove messages from memory", e);
throw new MemoryException(FAILED_TO_REMOVE_MESSAGES_FROM_MEMORY, e);
}
throw e;
}
Expand All @@ -242,7 +252,7 @@ public ChatMemory get(Object projectHash) {
try {
return projectConversations.get(projectHash.toString());
} catch (Exception e) {
throw new MemoryException("Failed to get chat memory for project hash: " + projectHash, e);
throw new MemoryException(FAILED_TO_GET_CHAT_MEMORY_FOR_PROJECT_HASH + projectHash, e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
import com.devoxx.genie.service.prompt.error.ExecutionException;
import com.devoxx.genie.service.prompt.error.PromptErrorHandler;
import com.devoxx.genie.service.prompt.memory.ChatMemoryManager;
import com.devoxx.genie.service.prompt.memory.ChatMemoryService;
import com.devoxx.genie.service.prompt.result.PromptResult;
import com.devoxx.genie.service.prompt.threading.PromptTask;
import com.devoxx.genie.service.prompt.threading.ThreadPoolManager;
import com.devoxx.genie.ui.panel.PromptOutputPanel;
import com.intellij.openapi.project.Project;
import dev.langchain4j.data.message.UserMessage;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;

Expand Down Expand Up @@ -126,11 +128,18 @@ protected abstract void executeStrategySpecific(ChatMessageContext context,
public void prepareMemory(ChatMessageContext context) {
// Prepare memory with system message if needed and add user message
log.debug("Before memory preparation - context ID: {}", context.getId());

chatMemoryManager.prepareMemory(context);

// Add context information to the user message before adding to memory
messageCreationService.addUserMessageToContext(context);
// Now add the enriched user message to chat memory
chatMemoryManager.addUserMessage(context);

// Check if user message was properly created
if (context.getUserMessage() == null) {
log.error("Failed to create user message for context ID: {}", context.getId());
// Create a fallback user message if needed
context.setUserMessage(UserMessage.from(context.getUserPrompt()));
}
}

/**
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
<Li>Feat #586 : Added Gemini 2.5 Pro model by @stephanj</LI>
<LI>Fix #588 : [Regression] Fix streaming responses by @stephanj</LI>
<LI>Feat #590 : User can now configure which keys to use for newline by @stephanj</LI>
<LI>Fix #595 : At least one message is required by @stephanj</LI>
</UL>
<h2>v0.5.2</h2>
<UL>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,21 +83,6 @@ private void mockStaticDependencies() {
}
}

@Test
void prepareMemory_shouldCallMessageCreationServiceBeforeChatMemoryManager() {
// Arrange
when(mockProject.getName()).thenReturn("TestProject");

// Act
testStrategy.prepareMemory(mockChatMessageContext);

// Assert - verify correct order of operations
InOrder inOrder = inOrder(mockChatMemoryManager, mockMessageCreationService);
inOrder.verify(mockChatMemoryManager).prepareMemory(mockChatMessageContext);
inOrder.verify(mockMessageCreationService).addUserMessageToContext(mockChatMessageContext);
inOrder.verify(mockChatMemoryManager).addUserMessage(mockChatMessageContext);
}

/**
* Test implementation of the abstract class
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,30 +155,6 @@ void setUp() {
);
}


@Test
void executeStrategySpecific_shouldNotCallAddUserMessageToContextAgain() {
// Arrange
// Setup to execute the actual prepareMemory method which will call addUserMessageToContext
doCallRealMethod().when(mockChatMemoryManager).prepareMemory(any());

// Act
strategy.executeStrategySpecific(mockChatMessageContext, mockPanel, mockResultTask);

// Assert - verify addUserMessageToContext is called exactly once (by the parent class's prepareMemory method)
// The NonStreamingPromptExecutionService should not call it again
verify(mockMessageCreationService, times(1)).addUserMessageToContext(mockChatMessageContext);
}

@Test
void executeStrategySpecific_shouldUseChatMemoryManagerToAddUserMessage() {
// Act
strategy.executeStrategySpecific(mockChatMessageContext, mockPanel, mockResultTask);

// Assert - verify prepareMemory is called, which internally handles addUserMessage
verify(mockChatMemoryManager).prepareMemory(mockChatMessageContext);
}

@org.junit.jupiter.api.AfterEach
void tearDown() {
// Close all static mocks to prevent memory leaks
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ void setUp() {
// Setup services in the application
when(mockApplication.getService(ChatMemoryManager.class)).thenReturn(mockChatMemoryManager);
when(mockApplication.getService(ThreadPoolManager.class)).thenReturn(mockThreadPoolManager);
when(mockApplication.getService(MessageCreationService.class)).thenReturn(mockMessageCreationService);
// when(mockApplication.getService(MessageCreationService.class)).thenReturn(mockMessageCreationService);
when(mockApplication.getService(ChatMemoryService.class)).thenReturn(mockChatMemoryService);

// Setup static getInstance methods
Expand All @@ -122,6 +122,16 @@ void setUp() {
when(mockThreadPoolManager.getPromptExecutionPool()).thenReturn(mockExecutor);
when(mockChatMessageContext.getProject()).thenReturn(mockProject);
when(mockChatMessageContext.getStreamingChatLanguageModel()).thenReturn(mockStreamingModel);
// Mock userPrompt to prevent "text cannot be null or blank" exception
when(mockChatMessageContext.getUserPrompt()).thenReturn("Test user prompt");

// Mock the behavior of addUserMessageToContext to set a user message on the context
doAnswer(invocation -> {
ChatMessageContext ctx = invocation.getArgument(0);
when(ctx.getUserMessage()).thenReturn(dev.langchain4j.data.message.UserMessage.from("Test user prompt"));
return null;
}).when(mockMessageCreationService).addUserMessageToContext(any(ChatMessageContext.class));

List<ChatMessage> messages = new ArrayList<>();
when(mockChatMemoryService.getMessages(any(Project.class))).thenReturn(messages);

Expand Down Expand Up @@ -150,7 +160,7 @@ void setUp() {
void executeStrategySpecific_shouldFollowCorrectMessageFlowOrder() {
// Arrange
// Use inOrder to verify the exact sequence of method calls
org.mockito.InOrder inOrder = inOrder(mockChatMemoryManager, mockMessageCreationService);
org.mockito.InOrder inOrder = inOrder(mockChatMemoryManager);

// Act
strategy.executeStrategySpecific(mockChatMessageContext, mockPanel, mockResultTask);
Expand Down