In these tutorials, we implemented Chat Memory with LangChain4j's low-level API. Now, we'll see how to achieve the same results more elegantly using AI Services, cutting down on boilerplate code.
Stateful Conversations for Single User
Chat memory enables AI Services to maintain conversation history, allowing the LLM to reference previous messages and maintain context across multiple interactions. For single-user applications, you can use a simple global memory instance that remembers the entire conversation.
When to Use Single User Memory
Single user memory is ideal for:
- Personal assistant applications
- Command-line tools with one user
- Desktop applications
- Batch processing scripts
- Single-session web applications
Memory Configuration Options
LangChain4j provides several memory implementations:
- MessageWindowChatMemory: Keeps only the last N messages (prevents context overflow)
- TokenWindowChatMemory: Keeps messages until token limit is reached
- PersistentChatMemory: Saves memory to disk/database
- InMemoryChatMemory: Simple in-memory storage (default)
Setting Up Single User Memory
For single user applications, you can directly configure a ChatMemory instance without needing @MemoryId annotation or ChatMemoryProvider (we will see the use of these in the next tutorial):
Assistant assistant = AiServices.builder(Assistant.class)
.chatModel(model)
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.build();
Memory Lifecycle
The memory instance persists as long as the AI Service instance exists. All method calls share the same memory, maintaining conversation continuity.
ChatMemoryAccess Concept and Usage
ChatMemoryAccess is an interface that allows your AI Service to expose memory management capabilities. When your AI Service interface extends ChatMemoryAccess, it gains methods to directly interact with the underlying chat memory. This enables you to inspect, modify, or clear conversation history programmatically. Use ChatMemoryAccess when you need to access memory contents for debugging, implement custom memory persistence, or provide users with conversation history features.
Definition of ChatMemoryAccessVersion: 1.10.0 package dev.langchain4j.service.memory;
public interface ChatMemoryAccess {
ChatMemory getChatMemory(Object memoryId); 1
boolean evictChatMemory(Object memoryId); 2
}
Example
package com.logicbig.example;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.TextContent;
import dev.langchain4j.memory.ChatMemory;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.ollama.OllamaChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.memory.ChatMemoryAccess;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.List;
public class SingleUserMemoryExample {
// Simple assistant with memory
interface Assistant {
@SystemMessage("You are a helpful personal assistant. "
+ "Remember details about the user and "
+ "maintain conversation context. Always give short answers, in 1 sentence")
String chat(@UserMessage String message);
}
// Create assistant that extends ChatMemoryAccess to inspect memory
interface InspectableAssistant extends ChatMemoryAccess {
String chat(@UserMessage String message);
}
public static void main(String[] args) {
// Create model
OllamaChatModel model =
OllamaChatModel.builder()
.baseUrl("http://localhost:11434")
.modelName("phi3:mini-128k")
.timeout(Duration.of(5, ChronoUnit.MINUTES))
.numCtx(4096)
.temperature(0.7)
.build();
System.out.println("=== Example 1: Basic Conversation Memory ===");
// Create assistant with memory (keeps last 10 messages)
Assistant assistant = AiServices.builder(Assistant.class)
.chatModel(model)
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.build();
// Conversation demonstrating memory
String msg = "List primitives types in Java in one sentence.";
System.out.println("User :" + msg);
String response1 = assistant.chat(msg);
System.out.println("Assistant: " + response1);
String msg2 = "If I create a whole number less than 100, "
+ "which primitive data type should I use, no explanation needed.";
System.out.println("\nUser: " + msg2);
String response2 = assistant.chat(msg2);
System.out.println("Assistant: " + response2);
String msg3 = "Which ones is good for floating point calculations, no explanation needed.";
System.out.println("\nUser: " + msg3);
String response3 = assistant.chat(msg3);
System.out.println("Assistant: " + response3);
System.out.println("\n=== Example 2: Task-Oriented Memory ===");
// Reset with new memory
Assistant taskAssistant =
AiServices.builder(Assistant.class)
.chatModel(model)
.chatMemory(MessageWindowChatMemory.withMaxMessages(5))
.build();
System.out.println("User: I need to buy groceries");
String task1 = taskAssistant.chat("I need to buy groceries");
System.out.println("Assistant: " + task1);
System.out.println("\nUser: Add milk to the list");
String task2 = taskAssistant.chat("Add milk to the list");
System.out.println("Assistant: " + task2);
System.out.println("\nUser: Also add bread and eggs");
String task3 = taskAssistant.chat("Also add bread and eggs");
System.out.println("Assistant: " + task3);
System.out.println("\nUser: What's on my grocery list?");
String task4 = taskAssistant.chat("What's on my grocery list?");
System.out.println("Assistant: " + task4);
System.out.println("\n=== Example 3: Inspecting Memory ===");
ChatMemory memory = MessageWindowChatMemory.withMaxMessages(10);
InspectableAssistant inspectableAssistant =
AiServices.builder(InspectableAssistant.class)
.chatModel(model)
.chatMemory(memory)
.build();
inspectableAssistant.chat("Remember that my favorite color is blue");
inspectableAssistant.chat("And I live in New York");
// Access memory directly
System.out.println("\nCurrent conversation messages:");
List<ChatMessage> messages = memory.messages();
for (ChatMessage message : messages) {
System.out.println(message.type() + ": "
+ (message instanceof TextContent tc ?
tc.text() : message.toString()));
}
// Clear memory if needed
memory.clear();
System.out.println("\nMemory cleared. Messages count: " + memory.messages().size());
}
}
Output=== Example 1: Basic Conversation Memory === User :List primitives types in Java in one sentence. Assistant: The primitive types in Java are byte, short, int, long, float, double, char, and boolean.
User: If I create a whole number less than 100, which primitive data type should I use, no explanation needed. Assistant: int
User: Which ones is good for floating point calculations, no explanation needed. Assistant: float or double
=== Example 2: Task-Oriented Memory === User: I need to buy groceries Assistant: Sure, I'll add that to your shopping list!
User: Add milk to the list Assistant: Got it, added milk to the shopping list.
User: Also add bread and eggs Assistant: Milk, bread, and eggs have been added to your grocneries list. Anything else?
User: What's on my grocery list? Assistant: Your current shopping list includes milk, bread, and eggs. Need anything more?
=== Example 3: Inspecting Memory ===
Current conversation messages: USER: UserMessage { name = null, contents = [TextContent { text = "Remember that my favorite color is blue" }], attributes = {} } AI: AiMessage { text = "I've made a note of your preference. Your favorite color, as mentioned by you, is indeed blue. If there are any occasions where knowing this information might be relevant or if it can enhance our interaction in some way, please let me know how I may assist you further with respect to this detail about yourself.", thinking = null, toolExecutionRequests = [], attributes = {} } USER: UserMessage { name = null, contents = [TextContent { text = "And I live in New York" }], attributes = {} } AI: AiMessage { text = "Noted! You reside within the bustling metropolis of New York City. The city that never sleeps offers a unique backdrop for countless experiences and adventures. Should your lifestyle or preferences require any specific considerations, feel free to share more about what interests you in this dynamic urban environment so I can tailor my assistance accordingly.", thinking = null, toolExecutionRequests = [], attributes = {} }
Memory cleared. Messages count: 0
Conclusion
The output demonstrates how the AI Service maintains conversation context across multiple calls. The assistant remembers previous conversation and uses it in subsequent responses. The memory automatically stores both user messages and AI responses, creating a continuous conversation flow without manual context management.
Example ProjectDependencies and Technologies Used: - langchain4j 1.10.0 (Build LLM-powered applications in Java: chatbots, agents, RAG, and much more)
- langchain4j-ollama 1.10.0 (LangChain4j :: Integration :: Ollama)
- slf4j-simple 2.0.9 (SLF4J Simple Provider)
- JDK 17
- Maven 3.9.11
|
|