Skip to content

Commit b19d8d8

Browse files
Add MongoDB-backed ChatMemoryStore implementation
1 parent 6cf0d98 commit b19d8d8

File tree

6 files changed

+440
-0
lines changed

6 files changed

+440
-0
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# MongoDB Chat Memory Store
2+
3+
This module provides a MongoDB-backed implementation of `ChatMemoryStore` for persisting chat history.
4+
5+
## Maven Dependency
6+
7+
```xml
8+
<dependency>
9+
<groupId>dev.langchain4j</groupId>
10+
<artifactId>langchain4j-community-mongodb</artifactId>
11+
<version>1.13.0-beta23-SNAPSHOT</version>
12+
</dependency>
13+
```
14+
15+
## Usage
16+
17+
```java
18+
ChatMemoryStore store = MongoDbChatMemoryStore.builder()
19+
.mongoClient(mongoClient)
20+
.databaseName("my_db")
21+
.collectionName("chat_history")
22+
.expireAfterSeconds(3600L) // optional TTL
23+
.build();
24+
```
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
5+
<parent>
6+
<groupId>dev.langchain4j</groupId>
7+
<artifactId>langchain4j-community</artifactId>
8+
<version>1.13.0-beta23-SNAPSHOT</version>
9+
<relativePath>../../pom.xml</relativePath>
10+
</parent>
11+
12+
<artifactId>langchain4j-community-mongodb</artifactId>
13+
<name>LangChain4j :: Community :: Integration :: MongoDB</name>
14+
15+
<properties>
16+
<mongodb-client.version>5.6.0</mongodb-client.version>
17+
</properties>
18+
19+
<dependencies>
20+
<dependency>
21+
<groupId>dev.langchain4j</groupId>
22+
<artifactId>langchain4j-core</artifactId>
23+
<version>${langchain4j.core.version}</version>
24+
</dependency>
25+
26+
<dependency>
27+
<groupId>org.mongodb</groupId>
28+
<artifactId>mongodb-driver-sync</artifactId>
29+
<version>${mongodb-client.version}</version>
30+
</dependency>
31+
32+
<dependency>
33+
<groupId>org.slf4j</groupId>
34+
<artifactId>slf4j-api</artifactId>
35+
</dependency>
36+
37+
<!-- test dependencies -->
38+
39+
<dependency>
40+
<groupId>dev.langchain4j</groupId>
41+
<artifactId>langchain4j-core</artifactId>
42+
<version>${langchain4j.core.version}</version>
43+
<classifier>tests</classifier>
44+
<type>test-jar</type>
45+
<scope>test</scope>
46+
</dependency>
47+
48+
<dependency>
49+
<groupId>org.junit.jupiter</groupId>
50+
<artifactId>junit-jupiter-engine</artifactId>
51+
<scope>test</scope>
52+
</dependency>
53+
54+
<dependency>
55+
<groupId>org.assertj</groupId>
56+
<artifactId>assertj-core</artifactId>
57+
<scope>test</scope>
58+
</dependency>
59+
60+
<dependency>
61+
<groupId>org.testcontainers</groupId>
62+
<artifactId>junit-jupiter</artifactId>
63+
<scope>test</scope>
64+
</dependency>
65+
66+
<dependency>
67+
<groupId>org.testcontainers</groupId>
68+
<artifactId>mongodb</artifactId>
69+
<scope>test</scope>
70+
</dependency>
71+
</dependencies>
72+
73+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
package dev.langchain4j.community.store.memory.chat.mongodb;
2+
3+
import static dev.langchain4j.internal.ValidationUtils.ensureNotBlank;
4+
import static dev.langchain4j.internal.ValidationUtils.ensureNotNull;
5+
6+
import com.mongodb.MongoCommandException;
7+
import com.mongodb.client.MongoClient;
8+
import com.mongodb.client.MongoCollection;
9+
import com.mongodb.client.model.Filters;
10+
import com.mongodb.client.model.IndexOptions;
11+
import com.mongodb.client.model.Indexes;
12+
import com.mongodb.client.model.ReplaceOptions;
13+
import dev.langchain4j.data.message.ChatMessage;
14+
import dev.langchain4j.data.message.ChatMessageDeserializer;
15+
import dev.langchain4j.data.message.ChatMessageSerializer;
16+
import dev.langchain4j.store.memory.chat.ChatMemoryStore;
17+
import java.util.ArrayList;
18+
import java.util.Date;
19+
import java.util.List;
20+
import java.util.concurrent.TimeUnit;
21+
import org.bson.Document;
22+
23+
/**
24+
* Implementation of {@link ChatMemoryStore} backed by MongoDB.
25+
* <p>
26+
* It persists chat messages as serialized JSON within a MongoDB collection, and
27+
* supports automatic message expiration via a MongoDB TTL index.
28+
*/
29+
public class MongoDbChatMemoryStore implements ChatMemoryStore {
30+
31+
private final MongoCollection<Document> collection;
32+
33+
/**
34+
* Constructor for MongoDbChatMemoryStore.
35+
*
36+
* @param mongoClient The MongoDB client instance.
37+
* @param databaseName The name of the MongoDB database.
38+
* @param collectionName The name of the MongoDB collection to store chat memory.
39+
* @param expireAfterSeconds The TTL in seconds for the chat memory. If {@code null} or {@code <= 0}, no TTL is applied.
40+
*/
41+
public MongoDbChatMemoryStore(
42+
MongoClient mongoClient, String databaseName, String collectionName, Long expireAfterSeconds) {
43+
ensureNotNull(mongoClient, "mongoClient");
44+
ensureNotBlank(databaseName, "databaseName");
45+
ensureNotBlank(collectionName, "collectionName");
46+
47+
this.collection = mongoClient.getDatabase(databaseName).getCollection(collectionName);
48+
49+
if (expireAfterSeconds != null && expireAfterSeconds > 0) {
50+
IndexOptions indexOptions = new IndexOptions().expireAfter(expireAfterSeconds, TimeUnit.SECONDS);
51+
try {
52+
this.collection.createIndex(Indexes.ascending("updatedAt"), indexOptions);
53+
} catch (MongoCommandException e) {
54+
if (e.getErrorCode() == 85) {
55+
throw new RuntimeException(
56+
"An index on 'updatedAt' already exists with different options. "
57+
+ "Drop the existing index manually in MongoDB to apply the new expireAfterSeconds value.",
58+
e);
59+
} else {
60+
throw new RuntimeException("Failed to create TTL index for ChatMemoryStore", e);
61+
}
62+
}
63+
}
64+
}
65+
66+
@Override
67+
public List<ChatMessage> getMessages(Object memoryId) {
68+
Document doc = collection.find(Filters.eq("_id", memoryId.toString())).first();
69+
if (doc == null || !doc.containsKey("messages")) {
70+
return new ArrayList<>();
71+
}
72+
String json = doc.getString("messages");
73+
return ChatMessageDeserializer.messagesFromJson(json);
74+
}
75+
76+
@Override
77+
public void updateMessages(Object memoryId, List<ChatMessage> messages) {
78+
String json = ChatMessageSerializer.messagesToJson(messages);
79+
Document doc = new Document("_id", memoryId.toString())
80+
.append("messages", json)
81+
.append("updatedAt", new Date());
82+
83+
collection.replaceOne(Filters.eq("_id", memoryId.toString()), doc, new ReplaceOptions().upsert(true));
84+
}
85+
86+
@Override
87+
public void deleteMessages(Object memoryId) {
88+
collection.deleteOne(Filters.eq("_id", memoryId.toString()));
89+
}
90+
91+
/**
92+
* Creates a new builder for {@link MongoDbChatMemoryStore}.
93+
*
94+
* @return A new builder instance.
95+
*/
96+
public static Builder builder() {
97+
return new Builder();
98+
}
99+
100+
/**
101+
* Builder for {@link MongoDbChatMemoryStore}.
102+
*/
103+
public static class Builder {
104+
private MongoClient mongoClient;
105+
private String databaseName;
106+
private String collectionName = "chat_memory";
107+
private Long expireAfterSeconds;
108+
109+
/**
110+
* Sets the MongoDB client.
111+
*
112+
* @param mongoClient The MongoDB client.
113+
* @return This builder.
114+
*/
115+
public Builder mongoClient(MongoClient mongoClient) {
116+
this.mongoClient = mongoClient;
117+
return this;
118+
}
119+
120+
/**
121+
* Sets the database name.
122+
*
123+
* @param databaseName The database name.
124+
* @return This builder.
125+
*/
126+
public Builder databaseName(String databaseName) {
127+
this.databaseName = databaseName;
128+
return this;
129+
}
130+
131+
/**
132+
* Sets the collection name. Defaults to "chat_memory".
133+
*
134+
* @param collectionName The collection name.
135+
* @return This builder.
136+
*/
137+
public Builder collectionName(String collectionName) {
138+
this.collectionName = collectionName;
139+
return this;
140+
}
141+
142+
/**
143+
* Sets the expiration time for chat memory documents.
144+
*
145+
* @param expireAfterSeconds The number of seconds after which documents expire.
146+
* @return This builder.
147+
*/
148+
public Builder expireAfterSeconds(Long expireAfterSeconds) {
149+
this.expireAfterSeconds = expireAfterSeconds;
150+
return this;
151+
}
152+
153+
/**
154+
* Builds the {@link MongoDbChatMemoryStore}.
155+
*
156+
* @return A configured instance of {@link MongoDbChatMemoryStore}.
157+
*/
158+
public MongoDbChatMemoryStore build() {
159+
return new MongoDbChatMemoryStore(mongoClient, databaseName, collectionName, expireAfterSeconds);
160+
}
161+
}
162+
}

0 commit comments

Comments
 (0)