Skip to content

Commit ad40eb5

Browse files
feat(memory):support self-host mem0 (#403)
This pull request improves support for both platform and self-hosted Mem0 deployments in the long-term memory extension, making the API more flexible and robust. The main changes include allowing users to specify the Mem0 API type, updating endpoint handling in the client, and enhancing search request capabilities. **Mem0 API deployment flexibility and configuration:** * The `Mem0LongTermMemory` builder and `Mem0Client` now accept an `apiType` parameter, allowing users to specify `"platform"` (default) or `"self-hosted"` deployments. Endpoints are selected accordingly, and example code and documentation have been updated to reflect this. [[1]](diffhunk://#diff-9cba458daeb3e8cf4c31784a4959c2943eea0a8a6a93a4de1fbe4f5eb140d7dbR68-R75) [[2]](diffhunk://#diff-9cba458daeb3e8cf4c31784a4959c2943eea0a8a6a93a4de1fbe4f5eb140d7dbL100-R113) [[3]](diffhunk://#diff-9cba458daeb3e8cf4c31784a4959c2943eea0a8a6a93a4de1fbe4f5eb140d7dbR260) [[4]](diffhunk://#diff-9cba458daeb3e8cf4c31784a4959c2943eea0a8a6a93a4de1fbe4f5eb140d7dbR330-R341) [[5]](diffhunk://#diff-8332bcfd89ba54150c52b48221dbd3f8f7c80198636a0a7ceeb5eb3e22e19e1aR33-R64) [[6]](diffhunk://#diff-8332bcfd89ba54150c52b48221dbd3f8f7c80198636a0a7ceeb5eb3e22e19e1aL57-R95) [[7]](diffhunk://#diff-8332bcfd89ba54150c52b48221dbd3f8f7c80198636a0a7ceeb5eb3e22e19e1aR110-R122) [[8]](diffhunk://#diff-6b9103fbf3a240744117a2bf207e08f728ae356b07f26db47787eb2283beac2dL61-R64) [[9]](diffhunk://#diff-4952aadfeff4e923a4f33c8f2e1f54fea0e151561103f782103707a676572d7eR34-R48) [[10]](diffhunk://#diff-4952aadfeff4e923a4f33c8f2e1f54fea0e151561103f782103707a676572d7eR85-R93) * The `Mem0Client` class now chooses the correct API endpoints for memory add/search operations based on the `apiType`, supporting both platform and self-hosted Mem0. [[1]](diffhunk://#diff-8332bcfd89ba54150c52b48221dbd3f8f7c80198636a0a7ceeb5eb3e22e19e1aR33-R64) [[2]](diffhunk://#diff-8332bcfd89ba54150c52b48221dbd3f8f7c80198636a0a7ceeb5eb3e22e19e1aL57-R95) [[3]](diffhunk://#diff-8332bcfd89ba54150c52b48221dbd3f8f7c80198636a0a7ceeb5eb3e22e19e1aR110-R122) [[4]](diffhunk://#diff-8332bcfd89ba54150c52b48221dbd3f8f7c80198636a0a7ceeb5eb3e22e19e1aL180-R230) [[5]](diffhunk://#diff-8332bcfd89ba54150c52b48221dbd3f8f7c80198636a0a7ceeb5eb3e22e19e1aL203-R265) **Robustness and compatibility improvements:** * The `Mem0Client` search method now handles differences in response format between platform and self-hosted Mem0. It parses results accordingly to ensure compatibility. [[1]](diffhunk://#diff-8332bcfd89ba54150c52b48221dbd3f8f7c80198636a0a7ceeb5eb3e22e19e1aL203-R265) [[2]](diffhunk://#diff-8332bcfd89ba54150c52b48221dbd3f8f7c80198636a0a7ceeb5eb3e22e19e1aL221-R280) * The `Mem0AddResponse` and `Mem0SearchResponse` classes now ignore unknown JSON properties, increasing forward compatibility with future API changes. [[1]](diffhunk://#diff-e16424679ccb9aebbb4a66a2e19f617646a43edccabb49c31792aedd27f03e9cR18) [[2]](diffhunk://#diff-e16424679ccb9aebbb4a66a2e19f617646a43edccabb49c31792aedd27f03e9cR35) [[3]](diffhunk://#diff-3f16da56a4e77b05005e143f0c9319a486682653e632ae8e4522074d73666545R18) [[4]](diffhunk://#diff-3f16da56a4e77b05005e143f0c9319a486682653e632ae8e4522074d73666545R32) **Search request enhancements:** * The `Mem0SearchRequest` class and its builder now support a `userId` field, which is automatically added to filters for compatibility with the v2 API. [[1]](diffhunk://#diff-569eccfea80f2361d61a7b14915a655674edc419751dbb2cc6826baa575e93e1R81-R84) [[2]](diffhunk://#diff-569eccfea80f2361d61a7b14915a655674edc419751dbb2cc6826baa575e93e1R182-R193) [[3]](diffhunk://#diff-569eccfea80f2361d61a7b14915a655674edc419751dbb2cc6826baa575e93e1R216) [[4]](diffhunk://#diff-569eccfea80f2361d61a7b14915a655674edc419751dbb2cc6826baa575e93e1L230-R255) [[5]](diffhunk://#diff-569eccfea80f2361d61a7b14915a655674edc419751dbb2cc6826baa575e93e1R354-R357) These changes make the Mem0 extension more flexible, easier to configure for different deployment scenarios, and more robust to API changes.Change-Id: If8c877e63f8d225d9e88e3830380ec855e7c9fd2 Co-developed-by: Aone Copilot <noreply@alibaba-inc.com> ## AgentScope-Java Version [The version of AgentScope-Java you are working on, e.g. 1.0.4, check your pom.xml dependency version or run `mvn dependency:tree | grep agentscope-parent:pom`(only mac/linux)] ## Description [Please describe the background, purpose, changes made, and how to test this PR] ## Checklist Please check the following items before code is ready to be reviewed. - [ ] Code has been formatted with `mvn spotless:apply` - [ ] All tests are passing (`mvn test`) - [ ] Javadoc comments are complete and follow project conventions - [ ] Related documentation has been updated (e.g. links, examples, etc.) - [ ] Code is ready for review --------- Co-authored-by: Albumen Kevin <jhq0812@gmail.com>
1 parent 32b2455 commit ad40eb5

12 files changed

Lines changed: 590 additions & 36 deletions

File tree

agentscope-examples/advanced/src/main/java/io/agentscope/examples/advanced/AutoMemoryExample.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import io.agentscope.core.ReActAgent;
2020
import io.agentscope.core.formatter.dashscope.DashScopeChatFormatter;
21+
import io.agentscope.core.memory.LongTermMemoryMode;
2122
import io.agentscope.core.memory.autocontext.AutoContextConfig;
2223
import io.agentscope.core.memory.autocontext.AutoContextHook;
2324
import io.agentscope.core.memory.autocontext.AutoContextMemory;
@@ -78,11 +79,12 @@ public static void main(String[] args) {
7879
.memory(memory)
7980
.maxIters(50)
8081
.longTermMemory(longTermMemory)
82+
.longTermMemoryMode(LongTermMemoryMode.STATIC_CONTROL)
8183
.enablePlan()
8284
.toolkit(toolkit)
8385
.hook(new AutoContextHook()) // Register the hook for automatic setup
8486
.build();
85-
String sessionId = "session111111111111";
87+
String sessionId = "123453344";
8688
// Set up session path
8789
Path sessionPath =
8890
Paths.get(System.getProperty("user.home"), ".agentscope", "examples", "sessions");

agentscope-examples/advanced/src/main/java/io/agentscope/examples/advanced/Mem0Example.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import io.agentscope.core.ReActAgent;
1919
import io.agentscope.core.agent.user.UserAgent;
2020
import io.agentscope.core.memory.LongTermMemoryMode;
21+
import io.agentscope.core.memory.mem0.Mem0ApiType;
2122
import io.agentscope.core.memory.mem0.Mem0LongTermMemory;
2223
import io.agentscope.core.message.Msg;
2324
import io.agentscope.core.model.DashScopeChatModel;
@@ -31,14 +32,17 @@ public static void main(String[] args) throws Exception {
3132
// Get API keys
3233
String dashscopeApiKey = ExampleUtils.getDashScopeApiKey();
3334
String mem0BaseUrl = getMem0BaseUrl();
35+
Mem0ApiType mem0ApiType = getMem0ApiType();
3436

35-
Mem0LongTermMemory longTermMemory =
37+
Mem0LongTermMemory.Builder memoryBuilder =
3638
Mem0LongTermMemory.builder()
3739
.agentName("SmartAssistant")
3840
.userId("static-control01126")
3941
.apiBaseUrl(mem0BaseUrl)
4042
.apiKey(System.getenv("MEM0_API_KEY"))
41-
.build();
43+
.apiType(mem0ApiType);
44+
45+
Mem0LongTermMemory longTermMemory = memoryBuilder.build();
4246

4347
// Create agent with AGENT_CONTROL mode
4448
ReActAgent agent =
@@ -75,4 +79,17 @@ private static String getMem0BaseUrl() {
7579
}
7680
return baseUrl;
7781
}
82+
83+
/**
84+
* Gets Mem0 API type from environment variable.
85+
*
86+
* @return API type enum: PLATFORM (default) or SELF_HOSTED
87+
*/
88+
private static Mem0ApiType getMem0ApiType() {
89+
String apiTypeStr = System.getenv("MEM0_API_TYPE");
90+
if (apiTypeStr == null || apiTypeStr.isEmpty()) {
91+
return Mem0ApiType.PLATFORM;
92+
}
93+
return Mem0ApiType.fromString(apiTypeStr);
94+
}
7895
}

agentscope-extensions/agentscope-extensions-mem0/src/main/java/io/agentscope/core/memory/mem0/Mem0AddResponse.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package io.agentscope.core.memory.mem0;
1717

18+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
1819
import com.fasterxml.jackson.annotation.JsonInclude;
1920
import java.util.List;
2021
import java.util.Map;
@@ -31,6 +32,7 @@
3132
* in the input messages.
3233
*/
3334
@JsonInclude(JsonInclude.Include.NON_NULL)
35+
@JsonIgnoreProperties(ignoreUnknown = true)
3436
public class Mem0AddResponse {
3537

3638
/**
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2024-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.agentscope.core.memory.mem0;
17+
18+
/**
19+
* Enumeration of Mem0 API deployment types.
20+
*
21+
* <p>Mem0 provides two deployment options with different API endpoints:
22+
* <ul>
23+
* <li><b>PLATFORM</b>: Official cloud service with endpoints /v1/memories/ and /v2/memories/search/</li>
24+
* <li><b>SELF_HOSTED</b>: Self-deployed service with endpoints /memories and /search</li>
25+
* </ul>
26+
*/
27+
public enum Mem0ApiType {
28+
/** Platform Mem0 (default) - official cloud service. */
29+
PLATFORM,
30+
31+
/** Self-hosted Mem0 - user-deployed service. */
32+
SELF_HOSTED;
33+
34+
/**
35+
* Parses a string to Mem0ApiType (case-insensitive).
36+
*
37+
* @param value String value to parse
38+
* @return Corresponding Mem0ApiType, or PLATFORM if value is null/empty/invalid
39+
*/
40+
public static Mem0ApiType fromString(String value) {
41+
if (value == null || value.isEmpty()) {
42+
return PLATFORM;
43+
}
44+
String normalized = value.toLowerCase().replace("_", "-");
45+
if ("self-hosted".equals(normalized) || "selfhosted".equals(normalized)) {
46+
return SELF_HOSTED;
47+
}
48+
return PLATFORM; // Default to PLATFORM
49+
}
50+
}

agentscope-extensions/agentscope-extensions-mem0/src/main/java/io/agentscope/core/memory/mem0/Mem0Client.java

Lines changed: 82 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,37 @@
3030

3131
/**
3232
* HTTP client for interacting with the Mem0 API.
33+
*
34+
* <p>Supports both Platform Mem0 and self-hosted Mem0 deployments:
35+
* <ul>
36+
* <li><b>Platform Mem0:</b> Uses endpoints /v1/memories/ and /v2/memories/search/</li>
37+
* <li><b>Self-hosted Mem0:</b> Uses endpoints /memories and /memories/search</li>
38+
* </ul>
39+
*
40+
* <p>By default, the client uses Platform Mem0 endpoints. To use self-hosted Mem0,
41+
* specify "self-hosted" as the apiType parameter.
3342
*/
3443
public class Mem0Client {
3544

3645
private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
37-
private static final String MEMORIES_ENDPOINT = "/v1/memories/";
38-
private static final String SEARCH_ENDPOINT = "/v2/memories/search/";
46+
47+
// Platform Mem0 endpoints
48+
private static final String PLATFORM_MEMORIES_ENDPOINT = "/v1/memories/";
49+
private static final String PLATFORM_SEARCH_ENDPOINT = "/v2/memories/search/";
50+
51+
// Self-hosted Mem0 endpoints
52+
private static final String SELF_HOSTED_MEMORIES_ENDPOINT = "/memories";
53+
private static final String SELF_HOSTED_SEARCH_ENDPOINT = "/search";
3954

4055
private final OkHttpClient httpClient;
4156
private final String apiBaseUrl;
4257
private final String apiKey;
4358
private final ObjectMapper objectMapper;
59+
private final String addEndpoint;
60+
private final String searchEndpoint;
4461

4562
/**
46-
* Creates a new Mem0Client with specified configuration.
63+
* Creates a new Mem0Client with specified configuration (defaults to Platform Mem0).
4764
*
4865
* @param apiBaseUrl The base URL of the Mem0 API (e.g., "http://localhost:8000")
4966
* @param apiKey The API key for authentication (can be null for local deployments without
@@ -54,14 +71,27 @@ public Mem0Client(String apiBaseUrl, String apiKey) {
5471
}
5572

5673
/**
57-
* Creates a new Mem0Client with custom timeout.
74+
* Creates a new Mem0Client with custom timeout (defaults to Platform Mem0).
5875
*
5976
* @param apiBaseUrl The base URL of the Mem0 API
6077
* @param apiKey The API key for authentication (can be null for local deployments without
6178
* authentication)
6279
* @param timeout HTTP request timeout duration
6380
*/
6481
public Mem0Client(String apiBaseUrl, String apiKey, Duration timeout) {
82+
this(apiBaseUrl, apiKey, Mem0ApiType.PLATFORM, timeout);
83+
}
84+
85+
/**
86+
* Creates a new Mem0Client with API type specification.
87+
*
88+
* @param apiBaseUrl The base URL of the Mem0 API
89+
* @param apiKey The API key for authentication (can be null for local deployments without
90+
* authentication)
91+
* @param apiType API type enum
92+
* @param timeout HTTP request timeout duration
93+
*/
94+
public Mem0Client(String apiBaseUrl, String apiKey, Mem0ApiType apiType, Duration timeout) {
6595
this.apiBaseUrl =
6696
apiBaseUrl.endsWith("/")
6797
? apiBaseUrl.substring(0, apiBaseUrl.length() - 1)
@@ -76,6 +106,19 @@ public Mem0Client(String apiBaseUrl, String apiKey, Duration timeout) {
76106
.readTimeout(timeout)
77107
.writeTimeout(Duration.ofSeconds(30))
78108
.build();
109+
110+
// Determine API type (default to PLATFORM if null)
111+
Mem0ApiType resolvedApiType = apiType != null ? apiType : Mem0ApiType.PLATFORM;
112+
113+
// Select endpoints based on API type
114+
if (resolvedApiType == Mem0ApiType.SELF_HOSTED) {
115+
this.addEndpoint = SELF_HOSTED_MEMORIES_ENDPOINT;
116+
this.searchEndpoint = SELF_HOSTED_SEARCH_ENDPOINT;
117+
} else {
118+
// Default to platform endpoints
119+
this.addEndpoint = PLATFORM_MEMORIES_ENDPOINT;
120+
this.searchEndpoint = PLATFORM_SEARCH_ENDPOINT;
121+
}
79122
}
80123

81124
/**
@@ -129,6 +172,12 @@ private <T> Mono<String> executePostRaw(String endpoint, T request, String opera
129172

130173
// Return raw response body
131174
return response.body().string();
175+
} catch (IOException e) {
176+
// Re-throw IOException as-is (it already contains status code info)
177+
throw e;
178+
} catch (Exception e) {
179+
// Wrap other exceptions
180+
throw new IOException("Mem0 API " + operationName + " failed", e);
132181
}
133182
})
134183
.subscribeOn(Schedulers.boundedElastic());
@@ -177,7 +226,7 @@ private <T, R> Mono<R> executePost(
177226
* @return A Mono emitting the response with extracted memories
178227
*/
179228
public Mono<Mem0AddResponse> add(Mem0AddRequest request) {
180-
return executePost(MEMORIES_ENDPOINT, request, Mem0AddResponse.class, "add request");
229+
return executePost(addEndpoint, request, Mem0AddResponse.class, "add request");
181230
}
182231

183232
/**
@@ -200,25 +249,37 @@ public Mono<Mem0AddResponse> add(Mem0AddRequest request) {
200249
* @return A Mono emitting the search response with relevant memories
201250
*/
202251
public Mono<Mem0SearchResponse> search(Mem0SearchRequest request) {
203-
return executePostRaw(SEARCH_ENDPOINT, request, "search request")
252+
return executePostRaw(searchEndpoint, request, "search request")
204253
.map(
205254
responseBody -> {
206255
try {
207-
// Parse response as array
208-
List<Mem0SearchResult> results =
209-
objectMapper.readValue(
210-
responseBody,
211-
objectMapper
212-
.getTypeFactory()
213-
.constructCollectionType(
214-
List.class,
215-
Mem0SearchResult.class));
216-
217-
// Wrap in Mem0SearchResponse for consistency
218-
Mem0SearchResponse searchResponse = new Mem0SearchResponse();
219-
searchResponse.setResults(results);
220-
return searchResponse;
221-
} catch (IOException e) {
256+
// Platform Mem0 uses /v2/memories/search/ endpoint and returns
257+
// direct array
258+
// Self-hosted Mem0 uses /search endpoint and returns wrapped format
259+
if (searchEndpoint.contains("/v2/")) {
260+
// Platform Mem0 returns direct array
261+
List<Mem0SearchResult> results =
262+
objectMapper.readValue(
263+
responseBody,
264+
objectMapper
265+
.getTypeFactory()
266+
.constructCollectionType(
267+
List.class,
268+
Mem0SearchResult.class));
269+
270+
// Wrap in Mem0SearchResponse for consistency
271+
Mem0SearchResponse searchResponse = new Mem0SearchResponse();
272+
searchResponse.setResults(results);
273+
return searchResponse;
274+
} else {
275+
// Self-hosted Mem0 returns response wrapped in {"results":
276+
// [...]}
277+
Mem0SearchResponse searchResponse =
278+
objectMapper.readValue(
279+
responseBody, Mem0SearchResponse.class);
280+
return searchResponse;
281+
}
282+
} catch (Exception e) {
222283
throw new RuntimeException("Failed to parse search response", e);
223284
}
224285
});

agentscope-extensions/agentscope-extensions-mem0/src/main/java/io/agentscope/core/memory/mem0/Mem0LongTermMemory.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,14 @@
6565
* .apiBaseUrl("http://localhost:8000")
6666
* .build();
6767
*
68+
* // For self-hosted Mem0
69+
* Mem0LongTermMemory selfHostedMemory = Mem0LongTermMemory.builder()
70+
* .agentName("Assistant")
71+
* .userName("user_123")
72+
* .apiBaseUrl("http://localhost:8000")
73+
* .apiType(Mem0ApiType.SELF_HOSTED) // Specify self-hosted API type
74+
* .build();
75+
*
6876
* // Record a message
6977
* Msg msg = Msg.builder()
7078
* .role(MsgRole.USER)
@@ -97,7 +105,8 @@ public class Mem0LongTermMemory implements LongTermMemory {
97105
* Private constructor - use Builder instead.
98106
*/
99107
private Mem0LongTermMemory(Builder builder) {
100-
this.client = new Mem0Client(builder.apiBaseUrl, builder.apiKey, builder.timeout);
108+
Mem0ApiType apiType = builder.apiType != null ? builder.apiType : Mem0ApiType.PLATFORM;
109+
this.client = new Mem0Client(builder.apiBaseUrl, builder.apiKey, apiType, builder.timeout);
101110
this.agentId = builder.agentName;
102111
this.userId = builder.userId;
103112
this.runId = builder.runName;
@@ -244,6 +253,7 @@ public static class Builder {
244253
private String runName;
245254
private String apiBaseUrl;
246255
private String apiKey;
256+
private Mem0ApiType apiType;
247257
private java.time.Duration timeout = java.time.Duration.ofSeconds(60);
248258

249259
/**
@@ -313,6 +323,17 @@ public Builder timeout(java.time.Duration timeout) {
313323
return this;
314324
}
315325

326+
/**
327+
* Sets the Mem0 API type.
328+
*
329+
* @param apiType API type enum
330+
* @return This builder
331+
*/
332+
public Builder apiType(Mem0ApiType apiType) {
333+
this.apiType = apiType;
334+
return this;
335+
}
336+
316337
/**
317338
* Builds the Mem0LongTermMemory instance.
318339
*

0 commit comments

Comments
 (0)