diff --git a/agentscope-distribution/agentscope-all/pom.xml b/agentscope-distribution/agentscope-all/pom.xml index 9bddd8288..d86f28f9b 100644 --- a/agentscope-distribution/agentscope-all/pom.xml +++ b/agentscope-distribution/agentscope-all/pom.xml @@ -69,28 +69,28 @@ io.agentscope - agentscope-extensions-autocontext-memory + agentscope-extensions-chat-completions-web compile true io.agentscope - agentscope-extensions-chat-completions-web + agentscope-extensions-higress compile true io.agentscope - agentscope-extensions-higress + agentscope-extensions-training compile true io.agentscope - agentscope-extensions-training + agentscope-extensions-autocontext-memory compile true @@ -104,42 +104,49 @@ io.agentscope - agentscope-extensions-rag-bailian + agentscope-extensions-reme compile true io.agentscope - agentscope-extensions-rag-dify + agentscope-extensions-memory-bailian compile true io.agentscope - agentscope-extensions-rag-ragflow + agentscope-extensions-rag-bailian compile true io.agentscope - agentscope-extensions-rag-haystack + agentscope-extensions-rag-dify compile true io.agentscope - agentscope-extensions-rag-simple + agentscope-extensions-rag-ragflow compile true io.agentscope - agentscope-extensions-reme + agentscope-extensions-rag-haystack + compile + true + + + + io.agentscope + agentscope-extensions-rag-simple compile true diff --git a/agentscope-distribution/agentscope-bom/pom.xml b/agentscope-distribution/agentscope-bom/pom.xml index 36ec9c9fb..f63c5697f 100644 --- a/agentscope-distribution/agentscope-bom/pom.xml +++ b/agentscope-distribution/agentscope-bom/pom.xml @@ -115,17 +115,17 @@ ${project.version} - + io.agentscope - agentscope-extensions-autocontext-memory + agentscope-extensions-chat-completions-web ${project.version} - + io.agentscope - agentscope-extensions-chat-completions-web + agentscope-extensions-autocontext-memory ${project.version} @@ -136,6 +136,20 @@ ${project.version} + + + io.agentscope + agentscope-extensions-reme + ${project.version} + + + + + io.agentscope + agentscope-extensions-memory-bailian + ${project.version} + + io.agentscope @@ -171,13 +185,6 @@ ${project.version} - - - io.agentscope - agentscope-extensions-reme - ${project.version} - - io.agentscope diff --git a/agentscope-examples/advanced/pom.xml b/agentscope-examples/advanced/pom.xml index 7fb293b86..c263ab28a 100644 --- a/agentscope-examples/advanced/pom.xml +++ b/agentscope-examples/advanced/pom.xml @@ -84,7 +84,12 @@ io.agentscope agentscope-extensions-reme - ${revision} + + + + + io.agentscope + agentscope-extensions-memory-bailian diff --git a/agentscope-examples/advanced/src/main/java/io/agentscope/examples/advanced/BailianMemoryExample.java b/agentscope-examples/advanced/src/main/java/io/agentscope/examples/advanced/BailianMemoryExample.java new file mode 100644 index 000000000..3775393a6 --- /dev/null +++ b/agentscope-examples/advanced/src/main/java/io/agentscope/examples/advanced/BailianMemoryExample.java @@ -0,0 +1,132 @@ +/* + * Copyright 2024-2026 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.agentscope.examples.advanced; + +import io.agentscope.core.ReActAgent; +import io.agentscope.core.agent.user.UserAgent; +import io.agentscope.core.memory.LongTermMemoryMode; +import io.agentscope.core.memory.bailian.BailianLongTermMemory; +import io.agentscope.core.message.Msg; +import io.agentscope.core.model.DashScopeChatModel; +import java.util.Map; + +/** + * BailianMemoryExample - Demonstrates Bailian Memory Library + * long-term memory using Bailian backend. + * + *

Prerequisites: + *

+ * + *

Environment Configuration: + *

+ * export DASHSCOPE_API_KEY=sk-xxxx
+ * export BAILIAN_USER_ID=your_user_id
+ * 
+ * + *

Optional Configuration: + *

+ * export BAILIAN_MEMORY_LIBRARY_ID=your_library_id
+ * export BAILIAN_PROJECT_ID=your_project_id
+ * export BAILIAN_PROFILE_SCHEMA=your_profile_schema
+ * 
+ * + *

This example demonstrates: + *

+ */ +public class BailianMemoryExample { + + public static void main(String[] args) throws Exception { + String description = + """ + This example demonstrates using Bailian long-term memory: + - Creating an agent with Bailian long-term memory + - Using STATIC_CONTROL mode for memory operations + - Interactive chat with persistent memory + + Bailian Memory Prompt Example: + [User (USER)]: I am Mike, and I drink water on time at 9 a.m. every morning. + [Assistant (ASSISTANT)]: Hi Mike! That’s a great habit—staying hydrated first thing in the morning helps kickstart your metabolism, supports cognitive function, and sets a positive tone for the day. + + [User (USER)]: Who am I and what I need to do at 9 a.m. every morning? + [Assistant (ASSISTANT)]: You are **Mike**, and you need to **drink water** at 9 a.m. every morning. + """; + ExampleUtils.printWelcome("Bailian Memory Example", description); + + String apiKey = ExampleUtils.getDashScopeApiKey(); + + // Set user id + String userId = System.getenv("BAILIAN_USER_ID"); + if (userId == null || userId.isBlank()) { + userId = "user-001"; + } + + BailianLongTermMemory.Builder memoryBuilder = + BailianLongTermMemory.builder() + .apiKey(apiKey) + .userId(userId) + .metadata(Map.of("location_name", "Beijing")); + + // Set memory library id + String memoryLibraryId = System.getenv("BAILIAN_MEMORY_LIBRARY_ID"); + if (memoryLibraryId != null && !memoryLibraryId.isBlank()) { + memoryBuilder.memoryLibraryId(memoryLibraryId); + } + + // Set project id + String projectId = System.getenv("BAILIAN_PROJECT_ID"); + if (projectId != null && !projectId.isBlank()) { + memoryBuilder.projectId(projectId); + } + + // Set profile schema + String profileSchema = System.getenv("BAILIAN_PROFILE_SCHEMA"); + if (profileSchema != null && !profileSchema.isBlank()) { + memoryBuilder.profileSchema(profileSchema); + } + + BailianLongTermMemory longTermMemory = memoryBuilder.build(); + + ReActAgent agent = + ReActAgent.builder() + .name("Assistant") + .model( + DashScopeChatModel.builder() + .apiKey(apiKey) + .modelName("qwen-plus") + .build()) + .longTermMemory(longTermMemory) + .longTermMemoryMode(LongTermMemoryMode.STATIC_CONTROL) + .build(); + + UserAgent userAgent = UserAgent.builder().name("User").build(); + + Msg msg = null; + while (true) { + msg = userAgent.call(msg).block(); + if (msg.getTextContent().equals("exit")) { + break; + } + msg = agent.call(msg).block(); + } + } +} diff --git a/agentscope-extensions/agentscope-extensions-memory-bailian/pom.xml b/agentscope-extensions/agentscope-extensions-memory-bailian/pom.xml new file mode 100644 index 000000000..5508833a3 --- /dev/null +++ b/agentscope-extensions/agentscope-extensions-memory-bailian/pom.xml @@ -0,0 +1,48 @@ + + + + + 4.0.0 + + io.agentscope + agentscope-extensions + ${revision} + ../pom.xml + + + AgentScope Java - Extensions - Bailian Long Term Memory + AgentScope Extensions - Bailian Long Term Memory + agentscope-extensions-memory-bailian + + + + io.agentscope + agentscope-core + true + provided + + + + + com.squareup.okhttp3 + mockwebserver3 + test + + + \ No newline at end of file diff --git a/agentscope-extensions/agentscope-extensions-memory-bailian/src/main/java/io/agentscope/core/memory/bailian/BailianAddRequest.java b/agentscope-extensions/agentscope-extensions-memory-bailian/src/main/java/io/agentscope/core/memory/bailian/BailianAddRequest.java new file mode 100644 index 000000000..c10fa7278 --- /dev/null +++ b/agentscope-extensions/agentscope-extensions-memory-bailian/src/main/java/io/agentscope/core/memory/bailian/BailianAddRequest.java @@ -0,0 +1,307 @@ +/* + * Copyright 2024-2026 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.agentscope.core.memory.bailian; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import java.util.Map; + +/** + * Request object for adding memories to Bailian Memory API. + * + *

This request is sent to the POST /api/v2/apps/memory/add endpoint to + * record new memories. Bailian will process the messages and extract memorable + * information using LLM-powered inference. + * + *

The metadata fields are used to organize and filter memories, enabling + * multi-tenant and multi-user scenarios. + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public class BailianAddRequest { + + /** User identifier for memory organization (required). */ + @JsonProperty("user_id") + private String userId; + + /** List of conversation messages to process for memory extraction. */ + private List messages; + + /** Custom content to directly store as memory (alternative to messages). */ + @JsonProperty("custom_content") + private String customContent; + + /** Memory library identifier for memory organization. */ + @JsonProperty("memory_library_id") + private String memoryLibraryId; + + /** Project identifier for memory rules. */ + @JsonProperty("project_id") + private String projectId; + + /** Profile schema identifier for user profile extraction. */ + @JsonProperty("profile_schema") + private String profileSchema; + + /** Additional metadata for storing context about the memory. */ + @JsonProperty("meta_data") + private Map metadata; + + /** Default constructor for Jackson. */ + public BailianAddRequest() {} + + /** + * Gets the user ID. + * + * @return the user ID + */ + public String getUserId() { + return userId; + } + + /** + * Sets the user ID. + * + * @param userId the user ID + */ + public void setUserId(String userId) { + this.userId = userId; + } + + /** + * Gets the messages. + * + * @return the messages list + */ + public List getMessages() { + return messages; + } + + /** + * Sets the messages. + * + * @param messages the messages list + */ + public void setMessages(List messages) { + this.messages = messages; + } + + /** + * Gets the custom content. + * + * @return the custom content + */ + public String getCustomContent() { + return customContent; + } + + /** + * Sets the custom content. + * + * @param customContent the custom content + */ + public void setCustomContent(String customContent) { + this.customContent = customContent; + } + + /** + * Gets the memory library ID. + * + * @return the memory library ID + */ + public String getMemoryLibraryId() { + return memoryLibraryId; + } + + /** + * Sets the memory library ID. + * + * @param memoryLibraryId the memory library ID + */ + public void setMemoryLibraryId(String memoryLibraryId) { + this.memoryLibraryId = memoryLibraryId; + } + + /** + * Gets the project ID. + * + * @return the project ID + */ + public String getProjectId() { + return projectId; + } + + /** + * Sets the project ID. + * + * @param projectId the project ID + */ + public void setProjectId(String projectId) { + this.projectId = projectId; + } + + /** + * Gets the profile schema. + * + * @return the profile schema + */ + public String getProfileSchema() { + return profileSchema; + } + + /** + * Sets the profile schema. + * + * @param profileSchema the profile schema + */ + public void setProfileSchema(String profileSchema) { + this.profileSchema = profileSchema; + } + + /** + * Gets the metadata. + * + * @return the metadata map + */ + public Map getMetadata() { + return metadata; + } + + /** + * Sets the metadata. + * + * @param metadata the metadata map + */ + public void setMetadata(Map metadata) { + this.metadata = metadata; + } + + /** + * Creates a new builder for BailianAddRequest. + * + * @return a new Builder instance + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for BailianAddRequest. + */ + public static class Builder { + private String userId; + private List messages; + private String customContent; + private String memoryLibraryId; + private String projectId; + private String profileSchema; + private Map metadata; + + /** + * Sets the user ID. + * + * @param userId the user ID + * @return this builder + */ + public Builder userId(String userId) { + this.userId = userId; + return this; + } + + /** + * Sets the messages. + * + * @param messages the messages list + * @return this builder + */ + public Builder messages(List messages) { + this.messages = messages; + return this; + } + + /** + * Sets the custom content. + * + * @param customContent the custom content + * @return this builder + */ + public Builder customContent(String customContent) { + this.customContent = customContent; + return this; + } + + /** + * Sets the memory library ID. + * + * @param memoryLibraryId the memory library ID + * @return this builder + */ + public Builder memoryLibraryId(String memoryLibraryId) { + this.memoryLibraryId = memoryLibraryId; + return this; + } + + /** + * Sets the project ID. + * + * @param projectId the project ID + * @return this builder + */ + public Builder projectId(String projectId) { + this.projectId = projectId; + return this; + } + + /** + * Sets the profile schema. + * + * @param profileSchema the profile schema + * @return this builder + */ + public Builder profileSchema(String profileSchema) { + this.profileSchema = profileSchema; + return this; + } + + /** + * Sets the metadata. + * + * @param metadata the metadata map + * @return this builder + */ + public Builder metadata(Map metadata) { + this.metadata = metadata; + return this; + } + + /** + * Builds the BailianAddRequest instance. + * + * @return a new BailianAddRequest instance + */ + public BailianAddRequest build() { + BailianAddRequest request = new BailianAddRequest(); + request.setUserId(userId); + request.setMessages(messages); + request.setCustomContent(customContent); + request.setMemoryLibraryId(memoryLibraryId); + request.setProjectId(projectId); + request.setProfileSchema(profileSchema); + request.setMetadata(metadata); + return request; + } + } +} diff --git a/agentscope-extensions/agentscope-extensions-memory-bailian/src/main/java/io/agentscope/core/memory/bailian/BailianAddResponse.java b/agentscope-extensions/agentscope-extensions-memory-bailian/src/main/java/io/agentscope/core/memory/bailian/BailianAddResponse.java new file mode 100644 index 000000000..dbcbeb79e --- /dev/null +++ b/agentscope-extensions/agentscope-extensions-memory-bailian/src/main/java/io/agentscope/core/memory/bailian/BailianAddResponse.java @@ -0,0 +1,175 @@ +/* + * Copyright 2024-2026 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.agentscope.core.memory.bailian; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + +/** + * Response object from Bailian Memory API's add memory operation. + * + *

This response is returned from the POST /api/v2/apps/memory/add endpoint + * after successfully adding memories. It contains the extracted memories and + * the request ID. + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class BailianAddResponse { + + /** Request ID for tracing. */ + @JsonProperty("request_id") + private String requestId; + + /** List of memory nodes that were created or updated. */ + @JsonProperty("memory_nodes") + private List memoryNodes; + + /** Default constructor for Jackson. */ + public BailianAddResponse() {} + + /** + * Gets the request ID. + * + * @return the request ID + */ + public String getRequestId() { + return requestId; + } + + /** + * Sets the request ID. + * + * @param requestId the request ID + */ + public void setRequestId(String requestId) { + this.requestId = requestId; + } + + /** + * Gets the memory nodes. + * + * @return the memory nodes list + */ + public List getMemoryNodes() { + return memoryNodes; + } + + /** + * Sets the memory nodes. + * + * @param memoryNodes the memory nodes list + */ + public void setMemoryNodes(List memoryNodes) { + this.memoryNodes = memoryNodes; + } + + /** + * Represents a memory node in the response. + */ + public static class BailianMemoryNode { + + /** Unique identifier for the memory node. */ + @JsonProperty("memory_node_id") + private String memoryNodeId; + + /** The memory content. */ + private String content; + + /** Event type (ADD, UPDATE, DELETE). */ + private String event; + + /** Previous content (only for UPDATE events). */ + @JsonProperty("old_content") + private String oldContent; + + /** Default constructor for Jackson. */ + public BailianMemoryNode() {} + + /** + * Gets the memory node ID. + * + * @return the memory node ID + */ + public String getMemoryNodeId() { + return memoryNodeId; + } + + /** + * Sets the memory node ID. + * + * @param memoryNodeId the memory node ID + */ + public void setMemoryNodeId(String memoryNodeId) { + this.memoryNodeId = memoryNodeId; + } + + /** + * Gets the content. + * + * @return the content + */ + public String getContent() { + return content; + } + + /** + * Sets the content. + * + * @param content the content + */ + public void setContent(String content) { + this.content = content; + } + + /** + * Gets the event type. + * + * @return the event type + */ + public String getEvent() { + return event; + } + + /** + * Sets the event type. + * + * @param event the event type + */ + public void setEvent(String event) { + this.event = event; + } + + /** + * Gets the old content. + * + * @return the old content + */ + public String getOldContent() { + return oldContent; + } + + /** + * Sets the old content. + * + * @param oldContent the old content + */ + public void setOldContent(String oldContent) { + this.oldContent = oldContent; + } + } +} diff --git a/agentscope-extensions/agentscope-extensions-memory-bailian/src/main/java/io/agentscope/core/memory/bailian/BailianLongTermMemory.java b/agentscope-extensions/agentscope-extensions-memory-bailian/src/main/java/io/agentscope/core/memory/bailian/BailianLongTermMemory.java new file mode 100644 index 000000000..40af984b2 --- /dev/null +++ b/agentscope-extensions/agentscope-extensions-memory-bailian/src/main/java/io/agentscope/core/memory/bailian/BailianLongTermMemory.java @@ -0,0 +1,472 @@ +/* + * Copyright 2024-2026 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.agentscope.core.memory.bailian; + +import io.agentscope.core.memory.LongTermMemory; +import io.agentscope.core.message.Msg; +import io.agentscope.core.message.MsgRole; +import io.agentscope.core.message.ToolUseBlock; +import io.agentscope.core.model.transport.HttpTransport; +import io.agentscope.core.model.transport.HttpTransportFactory; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import reactor.core.publisher.Mono; + +/** + * Long-term memory implementation using Bailian Long Term Memory service. + * + *

This implementation integrates with Alibaba Cloud Bailian's memory service, + * which provides persistent, searchable memory storage using semantic similarity + * and LLM-powered memory extraction. + * + *

Key Features: + *

+ * + *

Usage Example: + *

{@code
+ * BailianLongTermMemory memory = BailianLongTermMemory.builder()
+ *     .apiKey(System.getenv("DASHSCOPE_API_KEY"))
+ *     .userId("user_001")
+ *     .memoryLibraryId("memory_library_123")
+ *     .projectId(project_id_123);
+ *     .profileSchema(profile_schema_123);
+ *     .build();
+ *
+ * // Record messages
+ * Msg msg = Msg.builder()
+ *     .role(MsgRole.USER)
+ *     .content("Remind me to drink water at 9 a.m. every day")
+ *     .build();
+ * memory.record(List.of(msg)).block();
+ *
+ * // Retrieve memories
+ * Msg query = Msg.builder()
+ *     .role(MsgRole.USER)
+ *     .content("What reminder do I have?")
+ *     .build();
+ * String memories = memory.retrieve(query).block();
+ * }
+ * + * @see LongTermMemory + * @see BailianMemoryClient + */ +public class BailianLongTermMemory implements LongTermMemory { + + private static final String DEFAULT_BASE_URL = "https://dashscope.aliyuncs.com"; + private static final Integer DEFAULT_TOP_K = 10; + private static final Double DEFAULT_MIN_SCORE = 0.3; + + private final BailianMemoryClient client; + private final String userId; + private final String memoryLibraryId; + private final String projectId; + private final String profileSchema; + private final Integer topK; + private final Double minScore; + private final Boolean enableRerank; + private final Boolean enableJudge; + private final Boolean enableRewrite; + private final Map metadata; + + /** + * Creates a new {@link BailianLongTermMemory} instance. + * + * @param builder the builder for configuring the memory + */ + private BailianLongTermMemory(Builder builder) { + if (builder.apiKey == null || builder.apiKey.isBlank()) { + throw new IllegalArgumentException("apiKey cannot be null or blank"); + } + if (builder.userId == null || builder.userId.isBlank()) { + throw new IllegalArgumentException("userId cannot be null or blank"); + } + this.userId = builder.userId; + this.memoryLibraryId = builder.memoryLibraryId; + this.projectId = builder.projectId; + this.profileSchema = builder.profileSchema; + this.topK = builder.topK; + this.minScore = builder.minScore; + this.enableRerank = builder.enableRerank; + this.enableJudge = builder.enableJudge; + this.enableRewrite = builder.enableRewrite; + this.metadata = builder.metadata; + this.client = + BailianMemoryClient.builder() + .apiBaseUrl(builder.apiBaseUrl) + .apiKey(builder.apiKey) + .httpTransport(builder.httpTransport) + .build(); + } + + /** + * Records messages to long-term memory. + * + *

This method converts each message to a BailianMessage object, preserving the + * conversation structure (role and content). The messages are sent to Bailian API + * which uses LLM inference to extract memorable information. + * + *

Only USER and ASSISTANT messages are recorded. For ASSISTANT messages, + * only those without ToolUseBlock (pure assistant replies) are kept, filtering + * out tool call requests. TOOL and SYSTEM messages are also filtered out to keep + * the conversation history clean and focused on user-assistant interactions. + * + *

Messages containing compressed history markers (<compressed_history>) are + * filtered out to avoid storing redundant compressed information. + * + *

Null messages and messages with empty text content are filtered out before + * processing. Empty message lists are handled gracefully without error. + * + * @param msgs list of messages to record + * @return a Mono that completes when recording is finished + */ + @Override + public Mono record(List msgs) { + if (msgs == null || msgs.isEmpty()) { + return Mono.empty(); + } + + List bailianMessages = + msgs.stream() + .filter(Objects::nonNull) + .filter( + msg -> { + // Filter by role: only USER and ASSISTANT + MsgRole role = msg.getRole(); + if (role != MsgRole.USER && role != MsgRole.ASSISTANT) { + return false; + } + + // For ASSISTANT messages, exclude those with ToolUseBlock + // (tool call requests should not be recorded) + if (role == MsgRole.ASSISTANT + && msg.hasContentBlocks(ToolUseBlock.class)) { + return false; + } + + // Check for non-blank text content + String textContent = msg.getTextContent(); + if (textContent == null || textContent.isBlank()) { + return false; + } + + // Exclude messages with compressed history + if (textContent.contains("")) { + return false; + } + + return true; + }) + .map(this::convertToBailianMessage) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + if (bailianMessages.isEmpty()) { + return Mono.empty(); + } + + BailianAddRequest.Builder addRequestBuilder = + BailianAddRequest.builder().userId(userId).messages(bailianMessages); + + if (memoryLibraryId != null && !memoryLibraryId.isBlank()) { + addRequestBuilder.memoryLibraryId(memoryLibraryId); + } + + if (projectId != null && !projectId.isBlank()) { + addRequestBuilder.projectId(projectId); + } + + if (profileSchema != null && !profileSchema.isBlank()) { + addRequestBuilder.profileSchema(profileSchema); + } + + if (metadata != null && !metadata.isEmpty()) { + addRequestBuilder.metadata(metadata); + } + + return client.add(addRequestBuilder.build()).then(); + } + + /** + * Retrieves relevant memories based on the input message. + * + *

Uses semantic search to find memories relevant to the message content. + * Returns memory text as a newline-separated string, or empty string if no + * relevant memories are found. + * + * @param msg the message to use as a search query + * @return a Mono emitting the retrieved memory text (maybe empty) + */ + @Override + public Mono retrieve(Msg msg) { + if (msg == null) { + return Mono.just(""); + } + + String query = msg.getTextContent(); + if (query == null || query.isBlank()) { + return Mono.just(""); + } + + BailianMessage searchMessage = BailianMessage.builder().role("user").content(query).build(); + + BailianSearchRequest.Builder searchRequestBuilder = + BailianSearchRequest.builder() + .userId(userId) + .messages(List.of(searchMessage)) + .topK(topK) + .minScore(minScore) + .enableRerank(enableRerank) + .enableJudge(enableJudge) + .enableRewrite(enableRewrite); + + if (memoryLibraryId != null && !memoryLibraryId.isBlank()) { + searchRequestBuilder.memoryLibraryId(memoryLibraryId); + } + + if (projectId != null && !projectId.isBlank()) { + searchRequestBuilder.projectIds(List.of(projectId)); + } + + return client.search(searchRequestBuilder.build()) + .map( + response -> { + if (response.getMemoryNodes() == null + || response.getMemoryNodes().isEmpty()) { + return ""; + } + + return response.getMemoryNodes().stream() + .map(BailianMemoryNode::getContent) + .filter(s -> s != null && !s.isBlank()) + .collect(Collectors.joining("\n")); + }) + .onErrorReturn(""); + } + + /** + * Converts a Msg to a BailianMessage. + * + *

Role mapping: + *

    + *
  • USER -> "user"
  • + *
  • ASSISTANT -> "assistant" (only pure assistant replies without ToolUseBlock)
  • + *
+ * + *

Returns null for unsupported message types (TOOL, SYSTEM, or ASSISTANT with ToolUseBlock), + * which will be filtered out by the caller. + */ + private BailianMessage convertToBailianMessage(Msg msg) { + String role = + switch (msg.getRole()) { + case USER -> "user"; + case ASSISTANT -> "assistant"; + default -> null; // Filter out unsupported message types + }; + + if (role == null) { + return null; + } + + return BailianMessage.builder().role(role).content(msg.getTextContent()).build(); + } + + /** + * Creates a new builder for {@link BailianLongTermMemory}. + * + * @return a new builder instance + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for {@link BailianLongTermMemory}. + */ + public static class Builder { + private String apiKey; + private String apiBaseUrl = DEFAULT_BASE_URL; + private String userId; + private String memoryLibraryId; + private String projectId; + private String profileSchema; + private Integer topK = DEFAULT_TOP_K; + private Double minScore = DEFAULT_MIN_SCORE; + private Boolean enableRerank = false; + private Boolean enableJudge = false; + private Boolean enableRewrite = false; + private Map metadata; + private HttpTransport httpTransport = HttpTransportFactory.getDefault(); + + /** + * Sets the Bailian API key. + * + * @param apiKey the DASHSCOPE_API_KEY + * @return this builder + */ + public Builder apiKey(String apiKey) { + this.apiKey = apiKey; + return this; + } + + /** + * Sets the Bailian API base URL. + * + * @param apiBaseUrl the base URL + * @return this builder + */ + public Builder apiBaseUrl(String apiBaseUrl) { + this.apiBaseUrl = apiBaseUrl; + return this; + } + + /** + * Sets the user identifier for memory organization. + * + * @param userId the user ID (required) + * @return this builder + */ + public Builder userId(String userId) { + this.userId = userId; + return this; + } + + /** + * Sets the memory library identifier. + * + * @param memoryLibraryId the memory library ID + * @return this builder + */ + public Builder memoryLibraryId(String memoryLibraryId) { + this.memoryLibraryId = memoryLibraryId; + return this; + } + + /** + * Sets the project identifier. + * + * @param projectId the project ID + * @return this builder + */ + public Builder projectId(String projectId) { + this.projectId = projectId; + return this; + } + + /** + * Sets the profile schema identifier. + * + * @param profileSchema the profile schema ID + * @return this builder + */ + public Builder profileSchema(String profileSchema) { + this.profileSchema = profileSchema; + return this; + } + + /** + * Sets the maximum number of search results. + * + * @param topK the top K value + * @return this builder + */ + public Builder topK(Integer topK) { + this.topK = topK; + return this; + } + + /** + * Sets the minimum similarity score threshold. + * + * @param minScore the minimum score threshold [0,1] + * @return this builder + */ + public Builder minScore(Double minScore) { + this.minScore = minScore; + return this; + } + + /** + * Sets whether to enable reranking. + * + * @param enableRerank true to enable reranking, false otherwise + * @return this builder + */ + public Builder enableRerank(Boolean enableRerank) { + this.enableRerank = enableRerank; + return this; + } + + /** + * Sets whether to enable judge. + * + * @param enableJudge true to enable judge, false otherwise + * @return this builder + */ + public Builder enableJudge(Boolean enableJudge) { + this.enableJudge = enableJudge; + return this; + } + + /** + * Sets whether to enable rewrite. + * + * @param enableRewrite true to enable rewrite, false otherwise + * @return this builder + */ + public Builder enableRewrite(Boolean enableRewrite) { + this.enableRewrite = enableRewrite; + return this; + } + + /** + * Sets custom metadata to be stored with memories. + * + * @param metadata custom metadata map + * @return this builder + */ + public Builder metadata(Map metadata) { + this.metadata = metadata; + return this; + } + + /** + * Sets the HTTP transport. + * + * @param httpTransport the HTTP transport + * @return this builder + */ + public Builder httpTransport(HttpTransport httpTransport) { + this.httpTransport = httpTransport; + return this; + } + + /** + * Builds the {@link BailianLongTermMemory} instance. + * + * @return a new {@link BailianLongTermMemory} instance + */ + public BailianLongTermMemory build() { + return new BailianLongTermMemory(this); + } + } +} diff --git a/agentscope-extensions/agentscope-extensions-memory-bailian/src/main/java/io/agentscope/core/memory/bailian/BailianMemoryClient.java b/agentscope-extensions/agentscope-extensions-memory-bailian/src/main/java/io/agentscope/core/memory/bailian/BailianMemoryClient.java new file mode 100644 index 000000000..65ea988ca --- /dev/null +++ b/agentscope-extensions/agentscope-extensions-memory-bailian/src/main/java/io/agentscope/core/memory/bailian/BailianMemoryClient.java @@ -0,0 +1,267 @@ +/* + * Copyright 2024-2026 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.agentscope.core.memory.bailian; + +import io.agentscope.core.model.transport.HttpRequest; +import io.agentscope.core.model.transport.HttpResponse; +import io.agentscope.core.model.transport.HttpTransport; +import io.agentscope.core.model.transport.HttpTransportFactory; +import io.agentscope.core.util.JsonCodec; +import io.agentscope.core.util.JsonUtils; +import java.io.IOException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; + +/** + * HTTP client for interacting with Bailian Long Term Memory API. + * + *

This client provides reactive API methods for memory operations including + * adding memories and searching for relevant memories using semantic similarity. + * + *

Example usage: + *

{@code
+ * // Using builder
+ * BailianMemoryClient client = BailianMemoryClient.builder()
+ *     .apiKey(System.getenv("DASHSCOPE_API_KEY"))
+ *     .build();
+ *
+ * // Add memory
+ * BailianAddRequest addRequest = BailianAddRequest.builder()
+ *     .userId("user_001")
+ *     .memoryLibraryId("memory_library_123")
+ *     .projectId(project_id_123);
+ *     .profileSchema(profile_schema_123);
+ *     .messages(messages)
+ *     .build();
+ * client.add(addRequest).block();
+ *
+ * // Search memory
+ * BailianSearchRequest searchRequest = BailianSearchRequest.builder()
+ *     .userId("user_001")
+ *     .messages(searchMessages)
+ *     .memoryLibraryId("memory_library_123")
+ *     .projectIds(List.of("project_id_123"));
+ *     .topK(5)
+ *     .build();
+ * BailianSearchResponse response = client.search(searchRequest).block();
+ *
+ * // Close the client when done
+ * client.close();
+ * }
+ */ +public class BailianMemoryClient { + + private static final Logger log = LoggerFactory.getLogger(BailianMemoryClient.class); + private static final String DEFAULT_BASE_URL = "https://dashscope.aliyuncs.com"; + private static final String ADD_MEMORY_ENDPOINT = "/api/v2/apps/memory/add"; + private static final String SEARCH_MEMORY_ENDPOINT = "/api/v2/apps/memory/memory_nodes/search"; + + private final String apiBaseUrl; + private final String apiKey; + private final HttpTransport httpTransport; + private final JsonCodec jsonCodec; + + /** + * Creates a new {@link BailianMemoryClient} instance. + * + * @param builder the builder for configuring the client + */ + private BailianMemoryClient(Builder builder) { + if (builder.apiBaseUrl == null || builder.apiBaseUrl.isBlank()) { + throw new IllegalArgumentException("apiBaseUrl cannot be null or blank"); + } + if (builder.apiKey == null || builder.apiKey.isBlank()) { + throw new IllegalArgumentException("apiKey cannot be null or blank"); + } + if (builder.httpTransport == null) { + throw new IllegalArgumentException("httpTransport cannot be null"); + } + this.apiBaseUrl = builder.apiBaseUrl; + this.apiKey = builder.apiKey; + this.httpTransport = builder.httpTransport; + this.jsonCodec = JsonUtils.getJsonCodec(); + } + + /** + * Adds memories to Bailian Long Term Memory. + * + *

This method calls the POST /api/v2/apps/memory/add endpoint to store + * conversation messages as memory nodes. Bailian will automatically extract + * key information from the messages. + * + * @param request the add memory request containing messages and metadata + * @return a Mono emitting the add memory response + */ + public Mono add(BailianAddRequest request) { + return executePost(ADD_MEMORY_ENDPOINT, request, BailianAddResponse.class, "add memory"); + } + + /** + * Searches for relevant memories based on semantic similarity. + * + *

This method calls the POST /api/v2/apps/memory/memory_nodes/search endpoint + * to find memories relevant to the provided query messages. Results are ordered + * by relevance score. + * + * @param request the search memory request containing query and filters + * @return a Mono emitting the search memory response + */ + public Mono search(BailianSearchRequest request) { + return executePost( + SEARCH_MEMORY_ENDPOINT, request, BailianSearchResponse.class, "search memory"); + } + + /** + * Executes a POST request to the Bailian API. + * + *

This method serializes the request object to JSON, sends it to the + * specified endpoint, and parses the response. All HTTP operations are + * performed on the bounded elastic scheduler. + * + * @param endPoint the API endPoint (e.g., "/api/v2/apps/memory/add") + * @param request the request object to serialize + * @param responseType the response class type for parsing + * @param operationName the operation name for logging + * @param the request type + * @param the response type + * @return a Mono emitting the parsed response + */ + private Mono executePost( + String endPoint, T request, Class responseType, String operationName) { + return Mono.fromCallable( + () -> { + String json = jsonCodec.toJson(request); + + HttpRequest httpRequest = + HttpRequest.builder() + .url(apiBaseUrl + endPoint) + .method("POST") + .header("Authorization", "Bearer " + apiKey) + .header("Content-Type", "application/json") + .body(json) + .build(); + + log.debug( + "Executing {} request to {}", + operationName, + httpRequest.getUrl()); + + HttpResponse response = httpTransport.execute(httpRequest); + + if (!response.isSuccessful()) { + throw new IOException( + "Bailian API " + + operationName + + " failed with status " + + response.getStatusCode() + + ": " + + response.getBody()); + } + + String responseBody = response.getBody(); + if (responseBody == null || responseBody.isEmpty()) { + throw new IOException( + "Bailian API " + + operationName + + " returned empty response"); + } + + return jsonCodec.fromJson(responseBody, responseType); + }) + .onErrorMap( + th -> { + if (th instanceof IOException) { + return th; + } + return new IOException( + "Bailian API " + operationName + " execute error", th); + }) + .subscribeOn(Schedulers.boundedElastic()); + } + + /** + * Closes the HTTP transport and releases resources. + */ + public void close() { + if (httpTransport != null) { + httpTransport.close(); + } + } + + /** + * Creates a new builder for {@link BailianMemoryClient}. + * @return a new Builder instance + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for {@link BailianMemoryClient}. + */ + public static class Builder { + private String apiBaseUrl = DEFAULT_BASE_URL; + private String apiKey; + private HttpTransport httpTransport = HttpTransportFactory.getDefault(); + + /** + * Sets the base URL for the Bailian API. + * + * @param apiBaseUrl the base URL for the Bailian API + * @return this builder + */ + public Builder apiBaseUrl(String apiBaseUrl) { + this.apiBaseUrl = apiBaseUrl; + return this; + } + + /** + * Sets the API key for authentication. + * + * @param apiKey the Bailian API key + * @return this builder + */ + public Builder apiKey(String apiKey) { + this.apiKey = apiKey; + return this; + } + + /** + * Sets the HTTP transport for API requests. + * + *

If not specified, the default HTTP transport from + * {@link HttpTransportFactory#getDefault()} will be used. + * + * @param httpTransport the HTTP transport + * @return this builder + */ + public Builder httpTransport(HttpTransport httpTransport) { + this.httpTransport = httpTransport; + return this; + } + + /** + * Builds the {@link BailianMemoryClient} instance. + * + * @return a new {@link BailianMemoryClient} instance + */ + public BailianMemoryClient build() { + return new BailianMemoryClient(this); + } + } +} diff --git a/agentscope-extensions/agentscope-extensions-memory-bailian/src/main/java/io/agentscope/core/memory/bailian/BailianMemoryNode.java b/agentscope-extensions/agentscope-extensions-memory-bailian/src/main/java/io/agentscope/core/memory/bailian/BailianMemoryNode.java new file mode 100644 index 000000000..433f046df --- /dev/null +++ b/agentscope-extensions/agentscope-extensions-memory-bailian/src/main/java/io/agentscope/core/memory/bailian/BailianMemoryNode.java @@ -0,0 +1,163 @@ +/* + * Copyright 2024-2026 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.agentscope.core.memory.bailian; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Represents a memory node in the response. + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class BailianMemoryNode { + + /** Unique identifier for the memory node. */ + @JsonProperty("memory_node_id") + private String memoryNodeId; + + /** The memory content. */ + @JsonProperty("content") + private String content; + + /** Event type (ADD, UPDATE, DELETE). */ + @JsonProperty("event") + private String event; + + /** Previous content (only for UPDATE events). */ + @JsonProperty("old_content") + private String oldContent; + + /** Creation timestamp (Unix timestamp). */ + @JsonProperty("created_at") + private Long createdAt; + + /** Update timestamp (Unix timestamp). */ + @JsonProperty("updated_at") + private Long updatedAt; + + /** Default constructor for Jackson. */ + public BailianMemoryNode() {} + + /** + * Gets the memory node ID. + * + * @return the memory node ID + */ + public String getMemoryNodeId() { + return memoryNodeId; + } + + /** + * Sets the memory node ID. + * + * @param memoryNodeId the memory node ID + */ + public void setMemoryNodeId(String memoryNodeId) { + this.memoryNodeId = memoryNodeId; + } + + /** + * Gets the content. + * + * @return the content + */ + public String getContent() { + return content; + } + + /** + * Sets the content. + * + * @param content the content + */ + public void setContent(String content) { + this.content = content; + } + + /** + * Gets the event type. + * + * @return the event type + */ + public String getEvent() { + return event; + } + + /** + * Sets the event type. + * + * @param event the event type + */ + public void setEvent(String event) { + this.event = event; + } + + /** + * Gets the old content. + * + * @return the old content + */ + public String getOldContent() { + return oldContent; + } + + /** + * Sets the old content. + * + * @param oldContent the old content + */ + public void setOldContent(String oldContent) { + this.oldContent = oldContent; + } + + /** + * Gets the creation timestamp. + * + * @return the creation timestamp + */ + public Long getCreatedAt() { + return createdAt; + } + + /** + * Sets the creation timestamp. + * + * @param createdAt the creation timestamp + */ + public void setCreatedAt(Long createdAt) { + this.createdAt = createdAt; + } + + /** + * Gets the update timestamp. + * + * @return the update timestamp + */ + public Long getUpdatedAt() { + return updatedAt; + } + + /** + * Sets the update timestamp. + * + * @param updatedAt the update timestamp + */ + public void setUpdatedAt(Long updatedAt) { + this.updatedAt = updatedAt; + } +} diff --git a/agentscope-extensions/agentscope-extensions-memory-bailian/src/main/java/io/agentscope/core/memory/bailian/BailianMessage.java b/agentscope-extensions/agentscope-extensions-memory-bailian/src/main/java/io/agentscope/core/memory/bailian/BailianMessage.java new file mode 100644 index 000000000..46b0a110c --- /dev/null +++ b/agentscope-extensions/agentscope-extensions-memory-bailian/src/main/java/io/agentscope/core/memory/bailian/BailianMessage.java @@ -0,0 +1,132 @@ +/* + * Copyright 2024-2026 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.agentscope.core.memory.bailian; + +import com.fasterxml.jackson.annotation.JsonInclude; + +/** + * Represents a message in the Bailian Memory API format. + * + *

Messages are used for both adding memories and searching for relevant memories. + * Each message has a role (user or assistant) and content. + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public class BailianMessage { + + /** Role of the message sender, "user" or "assistant". */ + private String role; + + /** The actual text content of the message. */ + private String content; + + /** Default constructor for Jackson deserialization. */ + public BailianMessage() {} + + /** + * Creates a new BailianMessage with specified role and content. + * + * @param role the role (e.g., "user", "assistant") + * @param content the message content + */ + public BailianMessage(String role, String content) { + this.role = role; + this.content = content; + } + + /** + * Gets the role. + * + * @return the role + */ + public String getRole() { + return role; + } + + /** + * Sets the role. + * + * @param role the role + */ + public void setRole(String role) { + this.role = role; + } + + /** + * Gets the content. + * + * @return the content + */ + public String getContent() { + return content; + } + + /** + * Sets the content. + * + * @param content the content + */ + public void setContent(String content) { + this.content = content; + } + + /** + * Creates a new builder for BailianMessage. + * + * @return a new Builder instance + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for BailianMessage. + */ + public static class Builder { + private String role; + private String content; + + /** + * Sets the role. + * + * @param role the role + * @return this builder + */ + public Builder role(String role) { + this.role = role; + return this; + } + + /** + * Sets the content. + * + * @param content the content + * @return this builder + */ + public Builder content(String content) { + this.content = content; + return this; + } + + /** + * Builds the BailianMessage instance. + * + * @return a new BailianMessage instance + */ + public BailianMessage build() { + return new BailianMessage(role, content); + } + } +} diff --git a/agentscope-extensions/agentscope-extensions-memory-bailian/src/main/java/io/agentscope/core/memory/bailian/BailianSearchRequest.java b/agentscope-extensions/agentscope-extensions-memory-bailian/src/main/java/io/agentscope/core/memory/bailian/BailianSearchRequest.java new file mode 100644 index 000000000..669a7d16c --- /dev/null +++ b/agentscope-extensions/agentscope-extensions-memory-bailian/src/main/java/io/agentscope/core/memory/bailian/BailianSearchRequest.java @@ -0,0 +1,375 @@ +/* + * Copyright 2024-2026 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.agentscope.core.memory.bailian; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + +/** + * Request object for searching memories in Bailian Memory API. + * + *

This request is sent to the POST /api/v2/apps/memory/memory_nodes/search endpoint + * to retrieve relevant memories based on semantic similarity to the query messages. + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public class BailianSearchRequest { + + /** User identifier for filtering memories (required). */ + @JsonProperty("user_id") + private String userId; + + /** List of query messages for semantic similarity search. */ + private List messages; + + /** Memory library identifier for filtering. */ + @JsonProperty("memory_library_id") + private String memoryLibraryId; + + /** + * List project identifiers for memory rules. + * Multi-memory fragment rule identifiers can be passed in for mixed retrieval. + */ + @JsonProperty("project_ids") + private List projectIds; + + /** Maximum number of results to return (default: 10). */ + @JsonProperty("top_k") + private Integer topK; + + /** Minimum similarity score threshold [0, 1], (default: 0.3). */ + @JsonProperty("min_score") + private Double minScore; + + /** Whether to turn on reordering of search results (default: false). */ + @JsonProperty("enable_rerank") + private Boolean enableRerank; + + /** Whether to turn on the intent discrimination callback (default: false). */ + @JsonProperty("enable_judge") + private Boolean enableJudge; + + /** Whether to turn on query rewrite (default: false). */ + @JsonProperty("enable_rewrite") + private Boolean enableRewrite; + + /** Default constructor for Jackson. */ + public BailianSearchRequest() {} + + /** + * Gets the user ID. + * + * @return the user ID + */ + public String getUserId() { + return userId; + } + + /** + * Sets the user ID. + * + * @param userId the user ID + */ + public void setUserId(String userId) { + this.userId = userId; + } + + /** + * Gets the messages. + * + * @return the messages list + */ + public List getMessages() { + return messages; + } + + /** + * Sets the messages. + * + * @param messages the messages list + */ + public void setMessages(List messages) { + this.messages = messages; + } + + /** + * Gets the memory library ID. + * + * @return the memory library ID + */ + public String getMemoryLibraryId() { + return memoryLibraryId; + } + + /** + * Sets the memory library ID. + * + * @param memoryLibraryId the memory library ID + */ + public void setMemoryLibraryId(String memoryLibraryId) { + this.memoryLibraryId = memoryLibraryId; + } + + /** + * Gets the project IDs. + * + * @return the project IDs + */ + public List getProjectIds() { + return projectIds; + } + + /** + * Sets the project IDs. + * + * @param projectIds the project IDs + */ + public void setProjectIds(List projectIds) { + this.projectIds = projectIds; + } + + /** + * Gets the top K. + * + * @return the top K value + */ + public Integer getTopK() { + return topK; + } + + /** + * Sets the top K. + * + * @param topK the top K value + */ + public void setTopK(Integer topK) { + this.topK = topK; + } + + /** + * Gets the minimum score. + * + * @return the minimum score threshold + */ + public Double getMinScore() { + return minScore; + } + + /** + * Sets the minimum score. + * + * @param minScore the minimum score threshold + */ + public void setMinScore(Double minScore) { + this.minScore = minScore; + } + + /** + * Gets the enable rerank value. + * + * @return the enable rerank value + */ + public Boolean getEnableRerank() { + return enableRerank; + } + + /** + * Sets the enable rerank value. + * + * @param enableRerank the enable rerank value + */ + public void setEnableRerank(Boolean enableRerank) { + this.enableRerank = enableRerank; + } + + /** + * Gets the enable judge value. + * + * @return the enable judge value + */ + public Boolean getEnableJudge() { + return enableJudge; + } + + /** + * Sets the enable judge value. + * + * @param enableJudge the enable judge value + */ + public void setEnableJudge(Boolean enableJudge) { + this.enableJudge = enableJudge; + } + + /** + * Gets the enable rewrite value. + * + * @return the enable rewrite value + */ + public Boolean getEnableRewrite() { + return enableRewrite; + } + + /** + * Sets the enable rewrite value. + * + * @param enableRewrite the enable rewrite value + */ + public void setEnableRewrite(Boolean enableRewrite) { + this.enableRewrite = enableRewrite; + } + + /** + * Creates a new builder for BailianSearchRequest. + * + * @return a new Builder instance + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for BailianSearchRequest. + */ + public static class Builder { + private String userId; + private List messages; + private String memoryLibraryId; + private List projectIds; + private Integer topK; + private Double minScore; + private Boolean enableRerank; + private Boolean enableJudge; + private Boolean enableRewrite; + + /** + * Sets the user ID. + * + * @param userId the user ID + * @return this builder + */ + public Builder userId(String userId) { + this.userId = userId; + return this; + } + + /** + * Sets the messages. + * + * @param messages the messages list + * @return this builder + */ + public Builder messages(List messages) { + this.messages = messages; + return this; + } + + /** + * Sets the memory library ID. + * + * @param memoryLibraryId the memory library ID + * @return this builder + */ + public Builder memoryLibraryId(String memoryLibraryId) { + this.memoryLibraryId = memoryLibraryId; + return this; + } + + /** + * Sets the project IDs. + * + * @param projectIds the project IDs + * @return this builder + */ + public Builder projectIds(List projectIds) { + this.projectIds = projectIds; + return this; + } + + /** + * Sets the top K. + * + * @param topK the top K value + * @return this builder + */ + public Builder topK(Integer topK) { + this.topK = topK; + return this; + } + + /** + * Sets the minimum score. + * + * @param minScore the minimum score threshold + * @return this builder + */ + public Builder minScore(Double minScore) { + this.minScore = minScore; + return this; + } + + /** + * Sets the enableRerank value. + * + * @param enableRerank the enableRerank value + * @return this builder + */ + public Builder enableRerank(Boolean enableRerank) { + this.enableRerank = enableRerank; + return this; + } + + /** + * Sets the enableJudge value. + * + * @param enableJudge the enableJudge value + * @return this builder + */ + public Builder enableJudge(Boolean enableJudge) { + this.enableJudge = enableJudge; + return this; + } + + /** + * Sets the enableRewrite value. + * + * @param enableRewrite the enableRewrite value + * @return this builder + */ + public Builder enableRewrite(Boolean enableRewrite) { + this.enableRewrite = enableRewrite; + return this; + } + + /** + * Builds the BailianSearchRequest instance. + * + * @return a new BailianSearchRequest instance + */ + public BailianSearchRequest build() { + BailianSearchRequest request = new BailianSearchRequest(); + request.setUserId(userId); + request.setMessages(messages); + request.setMemoryLibraryId(memoryLibraryId); + request.setProjectIds(projectIds); + request.setTopK(topK); + request.setMinScore(minScore); + request.setEnableRerank(enableRerank); + request.setEnableJudge(enableJudge); + request.setEnableRewrite(enableRewrite); + return request; + } + } +} diff --git a/agentscope-extensions/agentscope-extensions-memory-bailian/src/main/java/io/agentscope/core/memory/bailian/BailianSearchResponse.java b/agentscope-extensions/agentscope-extensions-memory-bailian/src/main/java/io/agentscope/core/memory/bailian/BailianSearchResponse.java new file mode 100644 index 000000000..537176e69 --- /dev/null +++ b/agentscope-extensions/agentscope-extensions-memory-bailian/src/main/java/io/agentscope/core/memory/bailian/BailianSearchResponse.java @@ -0,0 +1,80 @@ +/* + * Copyright 2024-2026 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.agentscope.core.memory.bailian; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + +/** + * Response object from Bailian Memory API's search memory operation. + * + *

This response is returned from the POST /api/v2/apps/memory/memory_nodes/search endpoint + * after performing a semantic search. It contains a list of memory nodes ordered by + * relevance. + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class BailianSearchResponse { + + /** Request ID for tracing. */ + @JsonProperty("request_id") + private String requestId; + + /** List of memory nodes, ordered by relevance. */ + @JsonProperty("memory_nodes") + private List memoryNodes; + + /** Default constructor for Jackson. */ + public BailianSearchResponse() {} + + /** + * Gets the request ID. + * + * @return the request ID + */ + public String getRequestId() { + return requestId; + } + + /** + * Sets the request ID. + * + * @param requestId the request ID + */ + public void setRequestId(String requestId) { + this.requestId = requestId; + } + + /** + * Gets the memory nodes. + * + * @return the memory nodes list + */ + public List getMemoryNodes() { + return memoryNodes; + } + + /** + * Sets the memory nodes. + * + * @param memoryNodes the memory nodes list + */ + public void setMemoryNodes(List memoryNodes) { + this.memoryNodes = memoryNodes; + } +} diff --git a/agentscope-extensions/agentscope-extensions-memory-bailian/src/test/java/io/agentscope/core/memory/bailian/BailianAddRequestTest.java b/agentscope-extensions/agentscope-extensions-memory-bailian/src/test/java/io/agentscope/core/memory/bailian/BailianAddRequestTest.java new file mode 100644 index 000000000..95fc42d73 --- /dev/null +++ b/agentscope-extensions/agentscope-extensions-memory-bailian/src/test/java/io/agentscope/core/memory/bailian/BailianAddRequestTest.java @@ -0,0 +1,204 @@ +/* + * Copyright 2024-2026 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.agentscope.core.memory.bailian; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +/** Unit tests for {@link BailianAddRequest}. */ +class BailianAddRequestTest { + + @Test + void testBuilderWithUserId() { + BailianAddRequest request = BailianAddRequest.builder().userId("user123").build(); + + assertEquals("user123", request.getUserId()); + } + + @Test + void testBuilderWithMessages() { + List messages = + List.of(BailianMessage.builder().role("user").content("Test message").build()); + + BailianAddRequest request = BailianAddRequest.builder().messages(messages).build(); + + assertEquals(messages, request.getMessages()); + } + + @Test + void testBuilderWithCustomContent() { + BailianAddRequest request = + BailianAddRequest.builder().customContent("Custom memory content").build(); + + assertEquals("Custom memory content", request.getCustomContent()); + } + + @Test + void testBuilderWithProfileSchema() { + BailianAddRequest request = + BailianAddRequest.builder().profileSchema("profile_123").build(); + + assertEquals("profile_123", request.getProfileSchema()); + } + + @Test + void testBuilderWithMemoryLibraryId() { + BailianAddRequest request = BailianAddRequest.builder().memoryLibraryId("lib_456").build(); + + assertEquals("lib_456", request.getMemoryLibraryId()); + } + + @Test + void testBuilderWithProjectId() { + BailianAddRequest request = BailianAddRequest.builder().projectId("proj_789").build(); + + assertEquals("proj_789", request.getProjectId()); + } + + @Test + void testBuilderWithMetadata() { + Map metadata = new HashMap<>(); + metadata.put("key1", "value1"); + metadata.put("key2", 123); + + BailianAddRequest request = BailianAddRequest.builder().metadata(metadata).build(); + + assertEquals(metadata, request.getMetadata()); + } + + @Test + void testBuilderWithAllFields() { + List messages = + List.of(BailianMessage.builder().role("user").content("Test message").build()); + Map metadata = new HashMap<>(); + metadata.put("key1", "value1"); + + BailianAddRequest request = + BailianAddRequest.builder() + .userId("user123") + .messages(messages) + .customContent("Custom content") + .profileSchema("profile_123") + .memoryLibraryId("lib_456") + .projectId("proj_789") + .metadata(metadata) + .build(); + + assertEquals("user123", request.getUserId()); + assertEquals(messages, request.getMessages()); + assertEquals("Custom content", request.getCustomContent()); + assertEquals("profile_123", request.getProfileSchema()); + assertEquals("lib_456", request.getMemoryLibraryId()); + assertEquals("proj_789", request.getProjectId()); + assertEquals(metadata, request.getMetadata()); + } + + @Test + void testSettersAndGetters() { + BailianAddRequest request = new BailianAddRequest(); + + request.setUserId("user456"); + assertEquals("user456", request.getUserId()); + + List messages = + List.of(BailianMessage.builder().role("user").content("Message").build()); + request.setMessages(messages); + assertEquals(messages, request.getMessages()); + + request.setCustomContent("Custom"); + assertEquals("Custom", request.getCustomContent()); + + request.setProfileSchema("schema"); + assertEquals("schema", request.getProfileSchema()); + + request.setMemoryLibraryId("library"); + assertEquals("library", request.getMemoryLibraryId()); + + request.setProjectId("project"); + assertEquals("project", request.getProjectId()); + + Map metadata = new HashMap<>(); + metadata.put("key", "value"); + request.setMetadata(metadata); + assertEquals(metadata, request.getMetadata()); + } + + @Test + void testDefaultConstructor() { + BailianAddRequest request = new BailianAddRequest(); + + assertNull(request.getUserId()); + assertNull(request.getMessages()); + assertNull(request.getCustomContent()); + assertNull(request.getProfileSchema()); + assertNull(request.getMemoryLibraryId()); + assertNull(request.getProjectId()); + assertNull(request.getMetadata()); + } + + @Test + void testBuilderChain() { + BailianAddRequest request = + BailianAddRequest.builder() + .userId("user123") + .profileSchema("profile_123") + .memoryLibraryId("lib_456") + .projectId("proj_789") + .build(); + + assertNotNull(request); + assertEquals("user123", request.getUserId()); + assertEquals("profile_123", request.getProfileSchema()); + assertEquals("lib_456", request.getMemoryLibraryId()); + assertEquals("proj_789", request.getProjectId()); + } + + @Test + void testBuilderWithEmptyMetadata() { + BailianAddRequest request = BailianAddRequest.builder().metadata(new HashMap<>()).build(); + + assertNotNull(request.getMetadata()); + assertEquals(0, request.getMetadata().size()); + } + + @Test + void testBuilderWithNullValues() { + BailianAddRequest request = + BailianAddRequest.builder() + .userId(null) + .messages(null) + .customContent(null) + .profileSchema(null) + .memoryLibraryId(null) + .projectId(null) + .metadata(null) + .build(); + + assertNull(request.getUserId()); + assertNull(request.getMessages()); + assertNull(request.getCustomContent()); + assertNull(request.getProfileSchema()); + assertNull(request.getMemoryLibraryId()); + assertNull(request.getProjectId()); + assertNull(request.getMetadata()); + } +} diff --git a/agentscope-extensions/agentscope-extensions-memory-bailian/src/test/java/io/agentscope/core/memory/bailian/BailianAddResponseTest.java b/agentscope-extensions/agentscope-extensions-memory-bailian/src/test/java/io/agentscope/core/memory/bailian/BailianAddResponseTest.java new file mode 100644 index 000000000..6f6b10cea --- /dev/null +++ b/agentscope-extensions/agentscope-extensions-memory-bailian/src/test/java/io/agentscope/core/memory/bailian/BailianAddResponseTest.java @@ -0,0 +1,157 @@ +/* + * Copyright 2024-2026 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.agentscope.core.memory.bailian; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.List; +import org.junit.jupiter.api.Test; + +/** Unit tests for {@link BailianAddResponse}. */ +class BailianAddResponseTest { + + @Test + void testSettersAndGetters() { + BailianAddResponse response = new BailianAddResponse(); + + response.setRequestId("req_123"); + assertEquals("req_123", response.getRequestId()); + + List memoryNodes = + List.of(new BailianAddResponse.BailianMemoryNode()); + response.setMemoryNodes(memoryNodes); + assertEquals(memoryNodes, response.getMemoryNodes()); + } + + @Test + void testDefaultConstructor() { + BailianAddResponse response = new BailianAddResponse(); + + assertNull(response.getRequestId()); + assertNull(response.getMemoryNodes()); + } + + @Test + void testNestedBailianMemoryNodeSettersAndGetters() { + BailianAddResponse.BailianMemoryNode node = new BailianAddResponse.BailianMemoryNode(); + + node.setMemoryNodeId("mem_123"); + assertEquals("mem_123", node.getMemoryNodeId()); + + node.setContent("Memory content"); + assertEquals("Memory content", node.getContent()); + + node.setEvent("ADD"); + assertEquals("ADD", node.getEvent()); + + node.setOldContent("Old content"); + assertEquals("Old content", node.getOldContent()); + } + + @Test + void testNestedBailianMemoryNodeDefaultConstructor() { + BailianAddResponse.BailianMemoryNode node = new BailianAddResponse.BailianMemoryNode(); + + assertNull(node.getMemoryNodeId()); + assertNull(node.getContent()); + assertNull(node.getEvent()); + assertNull(node.getOldContent()); + } + + @Test + void testBailianMemoryNodeWithAddEvent() { + BailianAddResponse.BailianMemoryNode node = new BailianAddResponse.BailianMemoryNode(); + node.setMemoryNodeId("mem_1"); + node.setContent("New memory"); + node.setEvent("ADD"); + + assertEquals("mem_1", node.getMemoryNodeId()); + assertEquals("New memory", node.getContent()); + assertEquals("ADD", node.getEvent()); + assertNull(node.getOldContent()); + } + + @Test + void testBailianMemoryNodeWithUpdateEvent() { + BailianAddResponse.BailianMemoryNode node = new BailianAddResponse.BailianMemoryNode(); + node.setMemoryNodeId("mem_2"); + node.setContent("Updated memory"); + node.setEvent("UPDATE"); + node.setOldContent("Old memory"); + + assertEquals("mem_2", node.getMemoryNodeId()); + assertEquals("Updated memory", node.getContent()); + assertEquals("UPDATE", node.getEvent()); + assertEquals("Old memory", node.getOldContent()); + } + + @Test + void testBailianMemoryNodeWithDeleteEvent() { + BailianAddResponse.BailianMemoryNode node = new BailianAddResponse.BailianMemoryNode(); + node.setMemoryNodeId("mem_3"); + node.setContent("Deleted memory"); + node.setEvent("DELETE"); + + assertEquals("mem_3", node.getMemoryNodeId()); + assertEquals("Deleted memory", node.getContent()); + assertEquals("DELETE", node.getEvent()); + } + + @Test + void testResponseWithMultipleMemoryNodes() { + BailianAddResponse response = new BailianAddResponse(); + response.setRequestId("req_456"); + + BailianAddResponse.BailianMemoryNode node1 = new BailianAddResponse.BailianMemoryNode(); + node1.setMemoryNodeId("mem_1"); + node1.setContent("Memory 1"); + node1.setEvent("ADD"); + + BailianAddResponse.BailianMemoryNode node2 = new BailianAddResponse.BailianMemoryNode(); + node2.setMemoryNodeId("mem_2"); + node2.setContent("Memory 2"); + node2.setEvent("ADD"); + + List nodes = List.of(node1, node2); + response.setMemoryNodes(nodes); + + assertEquals("req_456", response.getRequestId()); + assertEquals(2, response.getMemoryNodes().size()); + assertEquals("mem_1", response.getMemoryNodes().get(0).getMemoryNodeId()); + assertEquals("mem_2", response.getMemoryNodes().get(1).getMemoryNodeId()); + } + + @Test + void testResponseWithEmptyMemoryNodes() { + BailianAddResponse response = new BailianAddResponse(); + response.setRequestId("req_empty"); + response.setMemoryNodes(List.of()); + + assertEquals("req_empty", response.getRequestId()); + assertNotNull(response.getMemoryNodes()); + assertEquals(0, response.getMemoryNodes().size()); + } + + @Test + void testResponseWithNullMemoryNodes() { + BailianAddResponse response = new BailianAddResponse(); + response.setRequestId("req_null"); + + assertNull(response.getMemoryNodes()); + } +} diff --git a/agentscope-extensions/agentscope-extensions-memory-bailian/src/test/java/io/agentscope/core/memory/bailian/BailianLongTermMemoryTest.java b/agentscope-extensions/agentscope-extensions-memory-bailian/src/test/java/io/agentscope/core/memory/bailian/BailianLongTermMemoryTest.java new file mode 100644 index 000000000..db6cfa463 --- /dev/null +++ b/agentscope-extensions/agentscope-extensions-memory-bailian/src/test/java/io/agentscope/core/memory/bailian/BailianLongTermMemoryTest.java @@ -0,0 +1,1025 @@ +/* + * Copyright 2024-2026 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.agentscope.core.memory.bailian; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.agentscope.core.message.Msg; +import io.agentscope.core.message.MsgRole; +import io.agentscope.core.message.TextBlock; +import io.agentscope.core.message.ToolUseBlock; +import io.agentscope.core.model.transport.OkHttpTransport; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import reactor.test.StepVerifier; + +/** Unit tests for {@link BailianLongTermMemory}. */ +class BailianLongTermMemoryTest { + + private MockWebServer mockServer; + private String baseUrl; + private OkHttpTransport httpTransport; + + @BeforeEach + void setUp() throws Exception { + mockServer = new MockWebServer(); + mockServer.start(); + baseUrl = mockServer.url("/").toString().replaceAll("/$", ""); + httpTransport = OkHttpTransport.builder().build(); + } + + @AfterEach + void tearDown() throws Exception { + if (mockServer != null) { + mockServer.shutdown(); + } + } + + @Test + void testBuilderWithUserId() { + BailianLongTermMemory memory = + BailianLongTermMemory.builder().apiKey("test-key").userId("user123").build(); + + assertNotNull(memory); + } + + @Test + void testBuilderWithAllFields() { + BailianLongTermMemory memory = + BailianLongTermMemory.builder() + .apiKey("test-key") + .apiBaseUrl(baseUrl) + .userId("user123") + .profileSchema("profile_123") + .memoryLibraryId("lib_456") + .projectId("proj_789") + .topK(5) + .minScore(0.5) + .enableRerank(true) + .enableJudge(true) + .enableRewrite(true) + .build(); + + assertNotNull(memory); + } + + @Test + void testBuilderWithMetadata() { + Map metadata = new HashMap<>(); + metadata.put("key1", "value1"); + metadata.put("key2", 123); + + BailianLongTermMemory memory = + BailianLongTermMemory.builder() + .apiKey("test-key") + .userId("user123") + .metadata(metadata) + .build(); + + assertNotNull(memory); + } + + @Test + void testBuilderRequiresApiKey() { + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + () -> BailianLongTermMemory.builder().userId("user123").build()); + + assertEquals("apiKey cannot be null or blank", exception.getMessage()); + } + + @Test + void testBuilderWithEmptyApiKey() { + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + () -> BailianLongTermMemory.builder().apiKey("").userId("user123").build()); + + assertEquals("apiKey cannot be null or blank", exception.getMessage()); + } + + @Test + void testBuilderWithBlankApiKey() { + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + () -> + BailianLongTermMemory.builder() + .apiKey(" ") + .userId("user123") + .build()); + + assertEquals("apiKey cannot be null or blank", exception.getMessage()); + } + + @Test + void testBuilderRequiresUserId() { + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + () -> BailianLongTermMemory.builder().apiKey("test-key").build()); + + assertEquals("userId cannot be null or blank", exception.getMessage()); + } + + @Test + void testBuilderWithEmptyUserId() { + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + () -> + BailianLongTermMemory.builder() + .apiKey("test-key") + .userId("") + .build()); + + assertEquals("userId cannot be null or blank", exception.getMessage()); + } + + @Test + void testBuilderWithBlankUserId() { + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + () -> + BailianLongTermMemory.builder() + .apiKey("test-key") + .userId(" ") + .build()); + + assertEquals("userId cannot be null or blank", exception.getMessage()); + } + + @Test + void testRecordWithValidMessages() throws Exception { + mockServer.enqueue( + new MockResponse() + .setBody("{\"request_id\":\"req_123\",\"memory_nodes\":[]}") + .setResponseCode(200)); + + BailianLongTermMemory memory = + BailianLongTermMemory.builder() + .apiKey("test-key") + .apiBaseUrl(baseUrl) + .httpTransport(httpTransport) + .userId("user123") + .build(); + + List messages = new ArrayList<>(); + messages.add( + Msg.builder() + .role(MsgRole.USER) + .content(TextBlock.builder().text("I prefer dark mode").build()) + .build()); + messages.add( + Msg.builder() + .role(MsgRole.ASSISTANT) + .content(TextBlock.builder().text("Noted").build()) + .build()); + + StepVerifier.create(memory.record(messages)).verifyComplete(); + + RecordedRequest recordedRequest = mockServer.takeRequest(); + String requestBody = recordedRequest.getBody().readUtf8(); + assertTrue(requestBody.contains("\"user_id\":\"user123\"")); + } + + @Test + void testRecordWithNullMessages() { + BailianLongTermMemory memory = + BailianLongTermMemory.builder() + .apiKey("test-key") + .apiBaseUrl(baseUrl) + .userId("user123") + .build(); + + StepVerifier.create(memory.record(null)).verifyComplete(); + } + + @Test + void testRecordWithEmptyMessages() { + BailianLongTermMemory memory = + BailianLongTermMemory.builder() + .apiKey("test-key") + .apiBaseUrl(baseUrl) + .userId("user123") + .build(); + + StepVerifier.create(memory.record(new ArrayList<>())).verifyComplete(); + } + + @Test + void testRecordFiltersNullMessages() throws Exception { + mockServer.enqueue( + new MockResponse() + .setBody("{\"request_id\":\"req_456\",\"memory_nodes\":[]}") + .setResponseCode(200)); + + BailianLongTermMemory memory = + BailianLongTermMemory.builder() + .apiKey("test-key") + .apiBaseUrl(baseUrl) + .httpTransport(httpTransport) + .userId("user123") + .build(); + + List messages = new ArrayList<>(); + messages.add( + Msg.builder() + .role(MsgRole.USER) + .content(TextBlock.builder().text("Valid message").build()) + .build()); + messages.add(null); + messages.add( + Msg.builder() + .role(MsgRole.ASSISTANT) + .content(TextBlock.builder().text("Another valid").build()) + .build()); + + StepVerifier.create(memory.record(messages)).verifyComplete(); + } + + @Test + void testRecordFiltersEmptyContentMessages() throws Exception { + mockServer.enqueue( + new MockResponse() + .setBody("{\"request_id\":\"req_789\",\"memory_nodes\":[]}") + .setResponseCode(200)); + + BailianLongTermMemory memory = + BailianLongTermMemory.builder() + .apiKey("test-key") + .apiBaseUrl(baseUrl) + .httpTransport(httpTransport) + .userId("user123") + .build(); + + List messages = new ArrayList<>(); + messages.add( + Msg.builder() + .role(MsgRole.USER) + .content(TextBlock.builder().text("Valid message").build()) + .build()); + messages.add( + Msg.builder() + .role(MsgRole.USER) + .content(TextBlock.builder().text("").build()) + .build()); + + StepVerifier.create(memory.record(messages)).verifyComplete(); + } + + @Test + void testRecordFiltersSystemMessages() throws Exception { + mockServer.enqueue( + new MockResponse() + .setBody("{\"request_id\":\"req_sys\",\"memory_nodes\":[]}") + .setResponseCode(200)); + + BailianLongTermMemory memory = + BailianLongTermMemory.builder() + .apiKey("test-key") + .apiBaseUrl(baseUrl) + .httpTransport(httpTransport) + .userId("user123") + .build(); + + List messages = new ArrayList<>(); + messages.add( + Msg.builder() + .role(MsgRole.USER) + .content(TextBlock.builder().text("User message").build()) + .build()); + messages.add( + Msg.builder() + .role(MsgRole.SYSTEM) + .content(TextBlock.builder().text("System message").build()) + .build()); + messages.add( + Msg.builder() + .role(MsgRole.ASSISTANT) + .content(TextBlock.builder().text("Assistant message").build()) + .build()); + + StepVerifier.create(memory.record(messages)).verifyComplete(); + + RecordedRequest recordedRequest = mockServer.takeRequest(); + String requestBody = recordedRequest.getBody().readUtf8(); + assertTrue(requestBody.contains("\"role\":\"user\"")); + assertTrue(requestBody.contains("\"role\":\"assistant\"")); + } + + @Test + void testRecordFiltersToolMessages() throws Exception { + mockServer.enqueue( + new MockResponse() + .setBody("{\"request_id\":\"req_tool\",\"memory_nodes\":[]}") + .setResponseCode(200)); + + BailianLongTermMemory memory = + BailianLongTermMemory.builder() + .apiKey("test-key") + .apiBaseUrl(baseUrl) + .httpTransport(httpTransport) + .userId("user123") + .build(); + + List messages = new ArrayList<>(); + messages.add( + Msg.builder() + .role(MsgRole.USER) + .content(TextBlock.builder().text("User message").build()) + .build()); + messages.add( + Msg.builder() + .role(MsgRole.TOOL) + .content(TextBlock.builder().text("Tool result").build()) + .build()); + + StepVerifier.create(memory.record(messages)).verifyComplete(); + + RecordedRequest recordedRequest = mockServer.takeRequest(); + String requestBody = recordedRequest.getBody().readUtf8(); + assertTrue(requestBody.contains("\"role\":\"user\"")); + } + + @Test + void testRecordFiltersAssistantWithToolUseBlock() throws Exception { + mockServer.enqueue( + new MockResponse() + .setBody("{\"request_id\":\"req_tooluse\",\"memory_nodes\":[]}") + .setResponseCode(200)); + + BailianLongTermMemory memory = + BailianLongTermMemory.builder() + .apiKey("test-key") + .apiBaseUrl(baseUrl) + .httpTransport(httpTransport) + .userId("user123") + .build(); + + List messages = new ArrayList<>(); + messages.add( + Msg.builder() + .role(MsgRole.USER) + .content(TextBlock.builder().text("User message").build()) + .build()); + messages.add( + Msg.builder() + .role(MsgRole.ASSISTANT) + .content(ToolUseBlock.builder().name("tool_name").build()) + .build()); + + StepVerifier.create(memory.record(messages)).verifyComplete(); + + RecordedRequest recordedRequest = mockServer.takeRequest(); + String requestBody = recordedRequest.getBody().readUtf8(); + assertTrue(requestBody.contains("\"role\":\"user\"")); + } + + @Test + void testRecordFiltersCompressedHistory() throws Exception { + mockServer.enqueue( + new MockResponse() + .setBody("{\"request_id\":\"req_compress\",\"memory_nodes\":[]}") + .setResponseCode(200)); + + BailianLongTermMemory memory = + BailianLongTermMemory.builder() + .apiKey("test-key") + .apiBaseUrl(baseUrl) + .httpTransport(httpTransport) + .userId("user123") + .build(); + + List messages = new ArrayList<>(); + messages.add( + Msg.builder() + .role(MsgRole.USER) + .content(TextBlock.builder().text("Valid message").build()) + .build()); + messages.add( + Msg.builder() + .role(MsgRole.USER) + .content( + TextBlock.builder() + .text( + "old" + + " conversation") + .build()) + .build()); + + StepVerifier.create(memory.record(messages)).verifyComplete(); + + RecordedRequest recordedRequest = mockServer.takeRequest(); + String requestBody = recordedRequest.getBody().readUtf8(); + assertTrue(!requestBody.contains("compressed_history")); + } + + @Test + void testRecordWithOnlyInvalidMessages() { + BailianLongTermMemory memory = + BailianLongTermMemory.builder() + .apiKey("test-key") + .apiBaseUrl(baseUrl) + .userId("user123") + .build(); + + List messages = new ArrayList<>(); + messages.add(null); + messages.add( + Msg.builder() + .role(MsgRole.USER) + .content(TextBlock.builder().text("").build()) + .build()); + + StepVerifier.create(memory.record(messages)).verifyComplete(); + } + + @Test + void testRecordWithMemoryLibraryId() throws Exception { + mockServer.enqueue( + new MockResponse() + .setBody("{\"request_id\":\"req_lib\",\"memory_nodes\":[]}") + .setResponseCode(200)); + + BailianLongTermMemory memory = + BailianLongTermMemory.builder() + .apiKey("test-key") + .apiBaseUrl(baseUrl) + .httpTransport(httpTransport) + .userId("user123") + .memoryLibraryId("lib_456") + .build(); + + List messages = new ArrayList<>(); + messages.add( + Msg.builder() + .role(MsgRole.USER) + .content(TextBlock.builder().text("Test message").build()) + .build()); + + StepVerifier.create(memory.record(messages)).verifyComplete(); + + RecordedRequest recordedRequest = mockServer.takeRequest(); + String requestBody = recordedRequest.getBody().readUtf8(); + assertTrue(requestBody.contains("\"memory_library_id\":\"lib_456\"")); + } + + @Test + void testRecordWithProfileSchema() throws Exception { + mockServer.enqueue( + new MockResponse() + .setBody("{\"request_id\":\"req_schema\",\"memory_nodes\":[]}") + .setResponseCode(200)); + + BailianLongTermMemory memory = + BailianLongTermMemory.builder() + .apiKey("test-key") + .apiBaseUrl(baseUrl) + .httpTransport(httpTransport) + .userId("user123") + .profileSchema("profile_123") + .build(); + + List messages = new ArrayList<>(); + messages.add( + Msg.builder() + .role(MsgRole.USER) + .content(TextBlock.builder().text("Test message").build()) + .build()); + + StepVerifier.create(memory.record(messages)).verifyComplete(); + + RecordedRequest recordedRequest = mockServer.takeRequest(); + String requestBody = recordedRequest.getBody().readUtf8(); + assertTrue(requestBody.contains("\"profile_schema\":\"profile_123\"")); + } + + @Test + void testRecordWithProjectId() throws Exception { + mockServer.enqueue( + new MockResponse() + .setBody("{\"request_id\":\"req_proj\",\"memory_nodes\":[]}") + .setResponseCode(200)); + + BailianLongTermMemory memory = + BailianLongTermMemory.builder() + .apiKey("test-key") + .apiBaseUrl(baseUrl) + .httpTransport(httpTransport) + .userId("user123") + .projectId("proj_789") + .build(); + + List messages = new ArrayList<>(); + messages.add( + Msg.builder() + .role(MsgRole.USER) + .content(TextBlock.builder().text("Test message").build()) + .build()); + + StepVerifier.create(memory.record(messages)).verifyComplete(); + + RecordedRequest recordedRequest = mockServer.takeRequest(); + String requestBody = recordedRequest.getBody().readUtf8(); + assertTrue(requestBody.contains("\"project_id\":\"proj_789\"")); + } + + @Test + void testRecordWithMetadata() throws Exception { + mockServer.enqueue( + new MockResponse() + .setBody("{\"request_id\":\"req_meta\",\"memory_nodes\":[]}") + .setResponseCode(200)); + + Map metadata = new HashMap<>(); + metadata.put("key1", "value1"); + metadata.put("key2", 123); + + BailianLongTermMemory memory = + BailianLongTermMemory.builder() + .apiKey("test-key") + .apiBaseUrl(baseUrl) + .httpTransport(httpTransport) + .userId("user123") + .metadata(metadata) + .build(); + + List messages = new ArrayList<>(); + messages.add( + Msg.builder() + .role(MsgRole.USER) + .content(TextBlock.builder().text("Test message").build()) + .build()); + + StepVerifier.create(memory.record(messages)).verifyComplete(); + + RecordedRequest recordedRequest = mockServer.takeRequest(); + String requestBody = recordedRequest.getBody().readUtf8(); + assertTrue(requestBody.contains("\"meta_data\"")); + } + + @Test + void testRetrieveWithValidQuery() throws Exception { + String responseJson = + "{\"request_id\":\"req_search\",\"memory_nodes\":[" + + "{\"memory_node_id\":\"mem_1\",\"content\":\"User prefers dark mode\"}," + + "{\"memory_node_id\":\"mem_2\",\"content\":\"User likes coffee\"}" + + "]}"; + + mockServer.enqueue(new MockResponse().setBody(responseJson).setResponseCode(200)); + + BailianLongTermMemory memory = + BailianLongTermMemory.builder() + .apiKey("test-key") + .apiBaseUrl(baseUrl) + .httpTransport(httpTransport) + .userId("user123") + .build(); + + Msg query = + Msg.builder() + .role(MsgRole.USER) + .content(TextBlock.builder().text("What are my preferences?").build()) + .build(); + + StepVerifier.create(memory.retrieve(query)) + .assertNext( + result -> { + assertNotNull(result); + assertEquals("User prefers dark mode\nUser likes coffee", result); + }) + .verifyComplete(); + } + + @Test + void testRetrieveWithNoResults() throws Exception { + String responseJson = "{\"request_id\":\"req_empty\",\"memory_nodes\":[]}"; + + mockServer.enqueue(new MockResponse().setBody(responseJson).setResponseCode(200)); + + BailianLongTermMemory memory = + BailianLongTermMemory.builder() + .apiKey("test-key") + .apiBaseUrl(baseUrl) + .httpTransport(httpTransport) + .userId("user123") + .build(); + + Msg query = + Msg.builder() + .role(MsgRole.USER) + .content(TextBlock.builder().text("query").build()) + .build(); + + StepVerifier.create(memory.retrieve(query)) + .assertNext(result -> assertEquals("", result)) + .verifyComplete(); + } + + @Test + void testRetrieveWithNullMessage() { + BailianLongTermMemory memory = + BailianLongTermMemory.builder() + .apiKey("test-key") + .apiBaseUrl(baseUrl) + .userId("user123") + .build(); + + StepVerifier.create(memory.retrieve(null)) + .assertNext(result -> assertEquals("", result)) + .verifyComplete(); + } + + @Test + void testRetrieveWithEmptyQuery() { + BailianLongTermMemory memory = + BailianLongTermMemory.builder() + .apiKey("test-key") + .apiBaseUrl(baseUrl) + .userId("user123") + .build(); + + Msg query = + Msg.builder() + .role(MsgRole.USER) + .content(TextBlock.builder().text("").build()) + .build(); + + StepVerifier.create(memory.retrieve(query)) + .assertNext(result -> assertEquals("", result)) + .verifyComplete(); + } + + @Test + void testRetrieveWithNullQuery() { + BailianLongTermMemory memory = + BailianLongTermMemory.builder() + .apiKey("test-key") + .apiBaseUrl(baseUrl) + .userId("user123") + .build(); + + Msg query = Msg.builder().role(MsgRole.USER).build(); + + StepVerifier.create(memory.retrieve(query)) + .assertNext(result -> assertEquals("", result)) + .verifyComplete(); + } + + @Test + void testRetrieveWithHttpError() throws Exception { + mockServer.enqueue( + new MockResponse().setBody("{\"error\":\"Not found\"}").setResponseCode(404)); + + BailianLongTermMemory memory = + BailianLongTermMemory.builder() + .apiKey("test-key") + .apiBaseUrl(baseUrl) + .httpTransport(httpTransport) + .userId("user123") + .build(); + + Msg query = + Msg.builder() + .role(MsgRole.USER) + .content(TextBlock.builder().text("query").build()) + .build(); + + StepVerifier.create(memory.retrieve(query)) + .assertNext(result -> assertEquals("", result)) + .verifyComplete(); + } + + @Test + void testRetrieveFiltersNullMemories() throws Exception { + String responseJson = + "{\"request_id\":\"req_null\",\"memory_nodes\":[" + + "{\"memory_node_id\":\"mem_1\",\"content\":\"Valid memory\"}," + + "{\"memory_node_id\":\"mem_2\",\"content\":null}," + + "{\"memory_node_id\":\"mem_3\",\"content\":\"Another valid\"}" + + "]}"; + + mockServer.enqueue(new MockResponse().setBody(responseJson).setResponseCode(200)); + + BailianLongTermMemory memory = + BailianLongTermMemory.builder() + .apiKey("test-key") + .apiBaseUrl(baseUrl) + .httpTransport(httpTransport) + .userId("user123") + .build(); + + Msg query = + Msg.builder() + .role(MsgRole.USER) + .content(TextBlock.builder().text("query").build()) + .build(); + + StepVerifier.create(memory.retrieve(query)) + .assertNext(result -> assertEquals("Valid memory\nAnother valid", result)) + .verifyComplete(); + } + + @Test + void testRetrieveFiltersEmptyMemories() throws Exception { + String responseJson = + "{\"request_id\":\"req_blank\",\"memory_nodes\":[" + + "{\"memory_node_id\":\"mem_1\",\"content\":\"Valid memory\"}," + + "{\"memory_node_id\":\"mem_2\",\"content\":\" \"}," + + "{\"memory_node_id\":\"mem_3\",\"content\":\"Another valid\"}" + + "]}"; + + mockServer.enqueue(new MockResponse().setBody(responseJson).setResponseCode(200)); + + BailianLongTermMemory memory = + BailianLongTermMemory.builder() + .apiKey("test-key") + .apiBaseUrl(baseUrl) + .httpTransport(httpTransport) + .userId("user123") + .build(); + + Msg query = + Msg.builder() + .role(MsgRole.USER) + .content(TextBlock.builder().text("query").build()) + .build(); + + StepVerifier.create(memory.retrieve(query)) + .assertNext(result -> assertEquals("Valid memory\nAnother valid", result)) + .verifyComplete(); + } + + @Test + void testRetrieveWithMemoryLibraryId() throws Exception { + String responseJson = "{\"request_id\":\"req_lib_search\",\"memory_nodes\":[]}"; + + mockServer.enqueue(new MockResponse().setBody(responseJson).setResponseCode(200)); + + BailianLongTermMemory memory = + BailianLongTermMemory.builder() + .apiKey("test-key") + .apiBaseUrl(baseUrl) + .httpTransport(httpTransport) + .userId("user123") + .memoryLibraryId("lib_456") + .build(); + + Msg query = + Msg.builder() + .role(MsgRole.USER) + .content(TextBlock.builder().text("test query").build()) + .build(); + + StepVerifier.create(memory.retrieve(query)) + .assertNext(result -> assertEquals("", result)) + .verifyComplete(); + + RecordedRequest recordedRequest = mockServer.takeRequest(); + String requestBody = recordedRequest.getBody().readUtf8(); + assertTrue(requestBody.contains("\"memory_library_id\":\"lib_456\"")); + } + + @Test + void testRetrieveWithProjectId() throws Exception { + String responseJson = "{\"request_id\":\"req_proj_search\",\"memory_nodes\":[]}"; + + mockServer.enqueue(new MockResponse().setBody(responseJson).setResponseCode(200)); + + BailianLongTermMemory memory = + BailianLongTermMemory.builder() + .apiKey("test-key") + .apiBaseUrl(baseUrl) + .userId("user123") + .projectId("proj_789") + .build(); + + Msg query = + Msg.builder() + .role(MsgRole.USER) + .content(TextBlock.builder().text("test query").build()) + .build(); + + StepVerifier.create(memory.retrieve(query)) + .assertNext(result -> assertEquals("", result)) + .verifyComplete(); + + RecordedRequest recordedRequest = mockServer.takeRequest(); + String requestBody = recordedRequest.getBody().readUtf8(); + assertTrue(requestBody.contains("\"project_ids\":[\"proj_789\"]")); + } + + @Test + void testRetrieveWithCustomTopK() throws Exception { + String responseJson = "{\"request_id\":\"req_topk\",\"memory_nodes\":[]}"; + + mockServer.enqueue(new MockResponse().setBody(responseJson).setResponseCode(200)); + + BailianLongTermMemory memory = + BailianLongTermMemory.builder() + .apiKey("test-key") + .apiBaseUrl(baseUrl) + .userId("user123") + .topK(15) + .build(); + + Msg query = + Msg.builder() + .role(MsgRole.USER) + .content(TextBlock.builder().text("test query").build()) + .build(); + + StepVerifier.create(memory.retrieve(query)) + .assertNext(result -> assertEquals("", result)) + .verifyComplete(); + + RecordedRequest recordedRequest = mockServer.takeRequest(); + String requestBody = recordedRequest.getBody().readUtf8(); + assertTrue(requestBody.contains("\"top_k\":15")); + } + + @Test + void testRetrieveWithCustomMinScore() throws Exception { + String responseJson = "{\"request_id\":\"req_score\",\"memory_nodes\":[]}"; + + mockServer.enqueue(new MockResponse().setBody(responseJson).setResponseCode(200)); + + BailianLongTermMemory memory = + BailianLongTermMemory.builder() + .apiKey("test-key") + .apiBaseUrl(baseUrl) + .userId("user123") + .minScore(0.7) + .build(); + + Msg query = + Msg.builder() + .role(MsgRole.USER) + .content(TextBlock.builder().text("test query").build()) + .build(); + + StepVerifier.create(memory.retrieve(query)) + .assertNext(result -> assertEquals("", result)) + .verifyComplete(); + + RecordedRequest recordedRequest = mockServer.takeRequest(); + String requestBody = recordedRequest.getBody().readUtf8(); + assertTrue(requestBody.contains("\"min_score\":0.7")); + } + + @Test + void testRetrieveWithEnableRerank() throws Exception { + String responseJson = "{\"request_id\":\"req_rerank\",\"memory_nodes\":[]}"; + + mockServer.enqueue(new MockResponse().setBody(responseJson).setResponseCode(200)); + + BailianLongTermMemory memory = + BailianLongTermMemory.builder() + .apiKey("test-key") + .apiBaseUrl(baseUrl) + .userId("user123") + .enableRerank(true) + .build(); + + Msg query = + Msg.builder() + .role(MsgRole.USER) + .content(TextBlock.builder().text("test query").build()) + .build(); + + StepVerifier.create(memory.retrieve(query)) + .assertNext(result -> assertEquals("", result)) + .verifyComplete(); + + RecordedRequest recordedRequest = mockServer.takeRequest(); + String requestBody = recordedRequest.getBody().readUtf8(); + assertTrue(requestBody.contains("\"enable_rerank\":true")); + } + + @Test + void testRetrieveWithEnableJudge() throws Exception { + String responseJson = "{\"request_id\":\"req_judge\",\"memory_nodes\":[]}"; + + mockServer.enqueue(new MockResponse().setBody(responseJson).setResponseCode(200)); + + BailianLongTermMemory memory = + BailianLongTermMemory.builder() + .apiKey("test-key") + .apiBaseUrl(baseUrl) + .userId("user123") + .enableJudge(true) + .build(); + + Msg query = + Msg.builder() + .role(MsgRole.USER) + .content(TextBlock.builder().text("test query").build()) + .build(); + + StepVerifier.create(memory.retrieve(query)) + .assertNext(result -> assertEquals("", result)) + .verifyComplete(); + + RecordedRequest recordedRequest = mockServer.takeRequest(); + String requestBody = recordedRequest.getBody().readUtf8(); + assertTrue(requestBody.contains("\"enable_judge\":true")); + } + + @Test + void testRetrieveWithEnableRewrite() throws Exception { + String responseJson = "{\"request_id\":\"req_rewrite\",\"memory_nodes\":[]}"; + + mockServer.enqueue(new MockResponse().setBody(responseJson).setResponseCode(200)); + + BailianLongTermMemory memory = + BailianLongTermMemory.builder() + .apiKey("test-key") + .apiBaseUrl(baseUrl) + .userId("user123") + .enableRewrite(true) + .build(); + + Msg query = + Msg.builder() + .role(MsgRole.USER) + .content(TextBlock.builder().text("test query").build()) + .build(); + + StepVerifier.create(memory.retrieve(query)) + .assertNext(result -> assertEquals("", result)) + .verifyComplete(); + + RecordedRequest recordedRequest = mockServer.takeRequest(); + String requestBody = recordedRequest.getBody().readUtf8(); + assertTrue(requestBody.contains("\"enable_rewrite\":true")); + } + + @Test + void testRoleMapping() throws Exception { + mockServer.enqueue( + new MockResponse() + .setBody("{\"request_id\":\"req_role\",\"memory_nodes\":[]}") + .setResponseCode(200)); + + BailianLongTermMemory memory = + BailianLongTermMemory.builder() + .apiKey("test-key") + .apiBaseUrl(baseUrl) + .userId("user123") + .build(); + + List messages = new ArrayList<>(); + messages.add( + Msg.builder() + .role(MsgRole.USER) + .content(TextBlock.builder().text("User message").build()) + .build()); + messages.add( + Msg.builder() + .role(MsgRole.ASSISTANT) + .content(TextBlock.builder().text("Assistant message").build()) + .build()); + + StepVerifier.create(memory.record(messages)).verifyComplete(); + + RecordedRequest recordedRequest = mockServer.takeRequest(); + String requestBody = recordedRequest.getBody().readUtf8(); + assertTrue(requestBody.contains("\"role\":\"user\"")); + assertTrue(requestBody.contains("\"role\":\"assistant\"")); + } + + @Test + void testDefaultValues() { + BailianLongTermMemory memory = + BailianLongTermMemory.builder().apiKey("test-key").userId("user123").build(); + + assertNotNull(memory); + } +} diff --git a/agentscope-extensions/agentscope-extensions-memory-bailian/src/test/java/io/agentscope/core/memory/bailian/BailianMemoryClientTest.java b/agentscope-extensions/agentscope-extensions-memory-bailian/src/test/java/io/agentscope/core/memory/bailian/BailianMemoryClientTest.java new file mode 100644 index 000000000..bea295495 --- /dev/null +++ b/agentscope-extensions/agentscope-extensions-memory-bailian/src/test/java/io/agentscope/core/memory/bailian/BailianMemoryClientTest.java @@ -0,0 +1,628 @@ +/* + * Copyright 2024-2026 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.agentscope.core.memory.bailian; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.agentscope.core.model.transport.HttpTransport; +import io.agentscope.core.model.transport.HttpTransportFactory; +import io.agentscope.core.model.transport.OkHttpTransport; +import java.util.List; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import reactor.test.StepVerifier; + +/** Unit tests for {@link BailianMemoryClient}. */ +class BailianMemoryClientTest { + + private MockWebServer mockServer; + private BailianMemoryClient client; + + @BeforeEach + void setUp() throws Exception { + mockServer = new MockWebServer(); + mockServer.start(); + String baseUrl = mockServer.url("/").toString().replaceAll("/$", ""); + client = + BailianMemoryClient.builder() + .apiBaseUrl(baseUrl) + .apiKey("test-api-key") + .httpTransport(OkHttpTransport.builder().build()) + .build(); + } + + @AfterEach + void tearDown() throws Exception { + if (client != null) { + client.close(); + } + if (mockServer != null) { + mockServer.shutdown(); + } + } + + @Test + void testBuilderWithDefaultBaseUrl() { + BailianMemoryClient client = BailianMemoryClient.builder().apiKey("test-key").build(); + + assertNotNull(client); + client.close(); + } + + @Test + void testBuilderWithCustomBaseUrl() { + BailianMemoryClient client = + BailianMemoryClient.builder() + .apiBaseUrl("https://custom.api.url") + .apiKey("test-key") + .build(); + + assertNotNull(client); + client.close(); + } + + @Test + void testBuilderWithCustomHttpTransport() { + HttpTransport httpTransport = HttpTransportFactory.getDefault(); + BailianMemoryClient client = + BailianMemoryClient.builder() + .apiBaseUrl("https://api.example.com") + .apiKey("test-key") + .httpTransport(httpTransport) + .build(); + + assertNotNull(client); + client.close(); + } + + @Test + void testBuilderWithNullBaseUrl() { + assertThrows( + IllegalArgumentException.class, + () -> BailianMemoryClient.builder().apiBaseUrl(null).apiKey("test-key").build()); + } + + @Test + void testBuilderWithEmptyBaseUrl() { + assertThrows( + IllegalArgumentException.class, + () -> BailianMemoryClient.builder().apiBaseUrl("").apiKey("test-key").build()); + } + + @Test + void testBuilderWithBlankBaseUrl() { + assertThrows( + IllegalArgumentException.class, + () -> BailianMemoryClient.builder().apiBaseUrl(" ").apiKey("test-key").build()); + } + + @Test + void testBuilderWithNullApiKey() { + assertThrows( + IllegalArgumentException.class, + () -> + BailianMemoryClient.builder() + .apiBaseUrl("https://api.example.com") + .apiKey(null) + .build()); + } + + @Test + void testBuilderWithEmptyApiKey() { + assertThrows( + IllegalArgumentException.class, + () -> + BailianMemoryClient.builder() + .apiBaseUrl("https://api.example.com") + .apiKey("") + .build()); + } + + @Test + void testBuilderWithBlankApiKey() { + assertThrows( + IllegalArgumentException.class, + () -> + BailianMemoryClient.builder() + .apiBaseUrl("https://api.example.com") + .apiKey(" ") + .build()); + } + + @Test + void testBuilderWithNullHttpTransport() { + assertThrows( + IllegalArgumentException.class, + () -> + BailianMemoryClient.builder() + .apiBaseUrl("https://api.example.com") + .apiKey("test-key") + .httpTransport(null) + .build()); + } + + @Test + void testAddRequestSuccess() throws Exception { + String responseJson = + "{\"request_id\":\"req_123\",\"memory_nodes\":[{\"memory_node_id\":\"mem_1\",\"content\":\"User" + + " prefers dark mode\",\"event\":\"ADD\"}]}"; + + mockServer.enqueue(new MockResponse().setBody(responseJson).setResponseCode(200)); + + BailianAddRequest request = + BailianAddRequest.builder() + .userId("user123") + .messages( + List.of( + BailianMessage.builder() + .role("user") + .content("I prefer dark mode") + .build())) + .build(); + + StepVerifier.create(client.add(request)) + .assertNext( + response -> { + assertNotNull(response); + assertEquals("req_123", response.getRequestId()); + assertNotNull(response.getMemoryNodes()); + assertEquals(1, response.getMemoryNodes().size()); + assertEquals( + "mem_1", response.getMemoryNodes().get(0).getMemoryNodeId()); + assertEquals( + "User prefers dark mode", + response.getMemoryNodes().get(0).getContent()); + assertEquals("ADD", response.getMemoryNodes().get(0).getEvent()); + }) + .verifyComplete(); + + RecordedRequest recordedRequest = mockServer.takeRequest(); + assertEquals("POST", recordedRequest.getMethod()); + assertTrue(recordedRequest.getPath().contains("/api/v2/apps/memory/add")); + assertEquals("Bearer test-api-key", recordedRequest.getHeader("Authorization")); + assertTrue(recordedRequest.getHeader("Content-Type").contains("application/json")); + } + + @Test + void testAddRequestWithAllFields() throws Exception { + String responseJson = "{\"request_id\":\"req_456\",\"memory_nodes\":[]}"; + + mockServer.enqueue(new MockResponse().setBody(responseJson).setResponseCode(200)); + + BailianAddRequest request = + BailianAddRequest.builder() + .userId("user123") + .messages( + List.of( + BailianMessage.builder() + .role("user") + .content("Test message") + .build())) + .profileSchema("profile_123") + .memoryLibraryId("lib_456") + .projectId("proj_789") + .build(); + + StepVerifier.create(client.add(request)) + .assertNext( + response -> { + assertNotNull(response); + assertEquals("req_456", response.getRequestId()); + }) + .verifyComplete(); + + RecordedRequest recordedRequest = mockServer.takeRequest(); + String requestBody = recordedRequest.getBody().readUtf8(); + assertTrue(requestBody.contains("\"user_id\":\"user123\"")); + assertTrue(requestBody.contains("\"profile_schema\":\"profile_123\"")); + assertTrue(requestBody.contains("\"memory_library_id\":\"lib_456\"")); + assertTrue(requestBody.contains("\"project_id\":\"proj_789\"")); + } + + @Test + void testAddRequestHttpError() { + mockServer.enqueue( + new MockResponse().setBody("{\"error\":\"Bad request\"}").setResponseCode(400)); + + BailianAddRequest request = + BailianAddRequest.builder() + .userId("user123") + .messages( + List.of( + BailianMessage.builder() + .role("user") + .content("Test") + .build())) + .build(); + + StepVerifier.create(client.add(request)) + .expectErrorMatches( + error -> + error.getMessage().contains("failed with status 400") + && error.getMessage().contains("add memory")) + .verify(); + } + + @Test + void testAddRequestInvalidJson() { + mockServer.enqueue(new MockResponse().setBody("invalid json").setResponseCode(200)); + + BailianAddRequest request = + BailianAddRequest.builder() + .userId("user123") + .messages( + List.of( + BailianMessage.builder() + .role("user") + .content("Test") + .build())) + .build(); + + StepVerifier.create(client.add(request)).expectError().verify(); + } + + @Test + void testSearchRequestSuccess() throws Exception { + String responseJson = + "{\"request_id\":\"req_789\",\"memory_nodes\":[{\"memory_node_id\":\"mem_1\",\"content\":\"User" + + " prefers dark" + + " mode\",\"event\":\"ADD\"},{\"memory_node_id\":\"mem_2\",\"content\":\"User" + + " likes coffee\",\"event\":\"ADD\"}]}"; + + mockServer.enqueue(new MockResponse().setBody(responseJson).setResponseCode(200)); + + BailianSearchRequest request = + BailianSearchRequest.builder() + .userId("user123") + .messages( + List.of( + BailianMessage.builder() + .role("user") + .content("What are my preferences?") + .build())) + .topK(5) + .minScore(0.5) + .build(); + + StepVerifier.create(client.search(request)) + .assertNext( + response -> { + assertNotNull(response); + assertEquals("req_789", response.getRequestId()); + assertNotNull(response.getMemoryNodes()); + assertEquals(2, response.getMemoryNodes().size()); + assertEquals( + "User prefers dark mode", + response.getMemoryNodes().get(0).getContent()); + assertEquals( + "User likes coffee", + response.getMemoryNodes().get(1).getContent()); + }) + .verifyComplete(); + + RecordedRequest recordedRequest = mockServer.takeRequest(); + assertEquals("POST", recordedRequest.getMethod()); + assertTrue(recordedRequest.getPath().contains("/api/v2/apps/memory/memory_nodes/search")); + } + + @Test + void testSearchRequestWithAllFields() throws Exception { + String responseJson = "{\"request_id\":\"req_999\",\"memory_nodes\":[]}"; + + mockServer.enqueue(new MockResponse().setBody(responseJson).setResponseCode(200)); + + BailianSearchRequest request = + BailianSearchRequest.builder() + .userId("user123") + .messages( + List.of( + BailianMessage.builder() + .role("user") + .content("Test query") + .build())) + .memoryLibraryId("lib_456") + .projectIds(List.of("proj_1", "proj_2")) + .topK(10) + .minScore(0.3) + .enableRerank(true) + .enableJudge(true) + .enableRewrite(true) + .build(); + + StepVerifier.create(client.search(request)) + .assertNext( + response -> { + assertNotNull(response); + assertEquals("req_999", response.getRequestId()); + }) + .verifyComplete(); + + RecordedRequest recordedRequest = mockServer.takeRequest(); + String requestBody = recordedRequest.getBody().readUtf8(); + assertTrue(requestBody.contains("\"user_id\":\"user123\"")); + assertTrue(requestBody.contains("\"memory_library_id\":\"lib_456\"")); + assertTrue(requestBody.contains("\"project_ids\":[\"proj_1\",\"proj_2\"]")); + assertTrue(requestBody.contains("\"top_k\":10")); + assertTrue(requestBody.contains("\"min_score\":0.3")); + assertTrue(requestBody.contains("\"enable_rerank\":true")); + assertTrue(requestBody.contains("\"enable_judge\":true")); + assertTrue(requestBody.contains("\"enable_rewrite\":true")); + } + + @Test + void testSearchRequestEmptyResults() throws Exception { + String responseJson = "{\"request_id\":\"req_empty\",\"memory_nodes\":[]}"; + + mockServer.enqueue(new MockResponse().setBody(responseJson).setResponseCode(200)); + + BailianSearchRequest request = + BailianSearchRequest.builder() + .userId("user123") + .messages( + List.of( + BailianMessage.builder() + .role("user") + .content("test") + .build())) + .build(); + + StepVerifier.create(client.search(request)) + .assertNext( + response -> { + assertNotNull(response); + assertNotNull(response.getMemoryNodes()); + assertEquals(0, response.getMemoryNodes().size()); + }) + .verifyComplete(); + } + + @Test + void testSearchRequestHttpError() { + mockServer.enqueue( + new MockResponse().setBody("{\"error\":\"Not found\"}").setResponseCode(404)); + + BailianSearchRequest request = + BailianSearchRequest.builder() + .userId("user123") + .messages( + List.of( + BailianMessage.builder() + .role("user") + .content("test") + .build())) + .build(); + + StepVerifier.create(client.search(request)) + .expectErrorMatches( + error -> + error.getMessage().contains("failed with status 404") + && error.getMessage().contains("search memory")) + .verify(); + } + + @Test + void testSearchRequestInvalidJson() { + mockServer.enqueue(new MockResponse().setBody("not json").setResponseCode(200)); + + BailianSearchRequest request = + BailianSearchRequest.builder() + .userId("user123") + .messages( + List.of( + BailianMessage.builder() + .role("user") + .content("test") + .build())) + .build(); + + StepVerifier.create(client.search(request)).expectError().verify(); + } + + @Test + void testClose() { + BailianMemoryClient client = + BailianMemoryClient.builder() + .apiBaseUrl("https://api.example.com") + .apiKey("test-key") + .build(); + + client.close(); + } + + @Test + void testCloseMultipleTimes() { + BailianMemoryClient client = + BailianMemoryClient.builder() + .apiBaseUrl("https://api.example.com") + .apiKey("test-key") + .build(); + + client.close(); + client.close(); + } + + @Test + void testRequestBodySerializationForAdd() throws Exception { + mockServer.enqueue( + new MockResponse() + .setBody("{\"request_id\":\"req_test\",\"memory_nodes\":[]}") + .setResponseCode(200)); + + BailianAddRequest request = + BailianAddRequest.builder() + .userId("user123") + .messages( + List.of( + BailianMessage.builder() + .role("user") + .content("Test message") + .build(), + BailianMessage.builder() + .role("assistant") + .content("Assistant response") + .build())) + .build(); + + StepVerifier.create(client.add(request)) + .assertNext(response -> assertNotNull(response)) + .verifyComplete(); + + RecordedRequest recordedRequest = mockServer.takeRequest(); + String requestBody = recordedRequest.getBody().readUtf8(); + assertTrue(requestBody.contains("\"user_id\":\"user123\"")); + assertTrue(requestBody.contains("\"role\":\"user\"")); + assertTrue(requestBody.contains("\"content\":\"Test message\"")); + assertTrue(requestBody.contains("\"role\":\"assistant\"")); + assertTrue(requestBody.contains("\"content\":\"Assistant response\"")); + } + + @Test + void testRequestBodySerializationForSearch() throws Exception { + mockServer.enqueue( + new MockResponse() + .setBody("{\"request_id\":\"req_test\",\"memory_nodes\":[]}") + .setResponseCode(200)); + + BailianSearchRequest request = + BailianSearchRequest.builder() + .userId("user123") + .messages( + List.of( + BailianMessage.builder() + .role("user") + .content("Search query") + .build())) + .topK(5) + .minScore(0.7) + .build(); + + StepVerifier.create(client.search(request)) + .assertNext(response -> assertNotNull(response)) + .verifyComplete(); + + RecordedRequest recordedRequest = mockServer.takeRequest(); + String requestBody = recordedRequest.getBody().readUtf8(); + assertTrue(requestBody.contains("\"user_id\":\"user123\"")); + assertTrue(requestBody.contains("\"role\":\"user\"")); + assertTrue(requestBody.contains("\"content\":\"Search query\"")); + assertTrue(requestBody.contains("\"top_k\":5")); + assertTrue(requestBody.contains("\"min_score\":0.7")); + } + + @Test + void testAuthorizationHeader() throws Exception { + mockServer.enqueue( + new MockResponse() + .setBody("{\"request_id\":\"req_auth\",\"memory_nodes\":[]}") + .setResponseCode(200)); + + BailianAddRequest request = + BailianAddRequest.builder() + .userId("user123") + .messages( + List.of( + BailianMessage.builder() + .role("user") + .content("Test") + .build())) + .build(); + + StepVerifier.create(client.add(request)) + .assertNext(response -> assertNotNull(response)) + .verifyComplete(); + + RecordedRequest recordedRequest = mockServer.takeRequest(); + assertEquals("Bearer test-api-key", recordedRequest.getHeader("Authorization")); + } + + @Test + void testContentTypeHeader() throws Exception { + mockServer.enqueue( + new MockResponse() + .setBody("{\"request_id\":\"req_content\",\"memory_nodes\":[]}") + .setResponseCode(200)); + + BailianAddRequest request = + BailianAddRequest.builder() + .userId("user123") + .messages( + List.of( + BailianMessage.builder() + .role("user") + .content("Test") + .build())) + .build(); + + StepVerifier.create(client.add(request)) + .assertNext(response -> assertNotNull(response)) + .verifyComplete(); + + RecordedRequest recordedRequest = mockServer.takeRequest(); + String contentType = recordedRequest.getHeader("Content-Type"); + assertNotNull(contentType); + assertTrue(contentType.contains("application/json")); + } + + @Test + void testAddResponseWithMultipleMemoryNodes() throws Exception { + String responseJson = + "{\"request_id\":\"req_multi\",\"memory_nodes\":[{\"memory_node_id\":\"mem_1\",\"content\":\"First" + + " memory\",\"event\":\"ADD\"},{\"memory_node_id\":\"mem_2\",\"content\":\"Second" + + " memory\",\"event\":\"UPDATE\",\"old_content\":\"Old" + + " content\"},{\"memory_node_id\":\"mem_3\",\"content\":\"Third" + + " memory\",\"event\":\"DELETE\"}]}"; + + mockServer.enqueue(new MockResponse().setBody(responseJson).setResponseCode(200)); + + BailianAddRequest request = + BailianAddRequest.builder() + .userId("user123") + .messages( + List.of( + BailianMessage.builder() + .role("user") + .content("Test") + .build())) + .build(); + + StepVerifier.create(client.add(request)) + .assertNext( + response -> { + assertNotNull(response); + assertEquals(3, response.getMemoryNodes().size()); + assertEquals( + "mem_1", response.getMemoryNodes().get(0).getMemoryNodeId()); + assertEquals("ADD", response.getMemoryNodes().get(0).getEvent()); + assertEquals( + "mem_2", response.getMemoryNodes().get(1).getMemoryNodeId()); + assertEquals("UPDATE", response.getMemoryNodes().get(1).getEvent()); + assertEquals( + "Old content", + response.getMemoryNodes().get(1).getOldContent()); + assertEquals( + "mem_3", response.getMemoryNodes().get(2).getMemoryNodeId()); + assertEquals("DELETE", response.getMemoryNodes().get(2).getEvent()); + }) + .verifyComplete(); + } +} diff --git a/agentscope-extensions/agentscope-extensions-memory-bailian/src/test/java/io/agentscope/core/memory/bailian/BailianMemoryNodeTest.java b/agentscope-extensions/agentscope-extensions-memory-bailian/src/test/java/io/agentscope/core/memory/bailian/BailianMemoryNodeTest.java new file mode 100644 index 000000000..65ed3cbb3 --- /dev/null +++ b/agentscope-extensions/agentscope-extensions-memory-bailian/src/test/java/io/agentscope/core/memory/bailian/BailianMemoryNodeTest.java @@ -0,0 +1,208 @@ +/* + * Copyright 2024-2026 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.agentscope.core.memory.bailian; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.junit.jupiter.api.Test; + +/** Unit tests for {@link BailianMemoryNode}. */ +class BailianMemoryNodeTest { + + @Test + void testDefaultConstructor() { + BailianMemoryNode node = new BailianMemoryNode(); + + assertNull(node.getMemoryNodeId()); + assertNull(node.getContent()); + assertNull(node.getEvent()); + assertNull(node.getOldContent()); + assertNull(node.getCreatedAt()); + assertNull(node.getUpdatedAt()); + } + + @Test + void testSettersAndGetters() { + BailianMemoryNode node = new BailianMemoryNode(); + + node.setMemoryNodeId("mem_123"); + assertEquals("mem_123", node.getMemoryNodeId()); + + node.setContent("Memory content"); + assertEquals("Memory content", node.getContent()); + + node.setEvent("ADD"); + assertEquals("ADD", node.getEvent()); + + node.setOldContent("Old content"); + assertEquals("Old content", node.getOldContent()); + + node.setCreatedAt(1234567890L); + assertEquals(1234567890L, node.getCreatedAt()); + + node.setUpdatedAt(1234567900L); + assertEquals(1234567900L, node.getUpdatedAt()); + } + + @Test + void testWithAddEvent() { + BailianMemoryNode node = new BailianMemoryNode(); + node.setMemoryNodeId("mem_1"); + node.setContent("New memory"); + node.setEvent("ADD"); + node.setCreatedAt(1234567890L); + node.setUpdatedAt(1234567890L); + + assertEquals("mem_1", node.getMemoryNodeId()); + assertEquals("New memory", node.getContent()); + assertEquals("ADD", node.getEvent()); + assertNull(node.getOldContent()); + assertEquals(1234567890L, node.getCreatedAt()); + assertEquals(1234567890L, node.getUpdatedAt()); + } + + @Test + void testWithUpdateEvent() { + BailianMemoryNode node = new BailianMemoryNode(); + node.setMemoryNodeId("mem_2"); + node.setContent("Updated memory"); + node.setEvent("UPDATE"); + node.setOldContent("Old memory"); + node.setCreatedAt(1234567890L); + node.setUpdatedAt(1234567900L); + + assertEquals("mem_2", node.getMemoryNodeId()); + assertEquals("Updated memory", node.getContent()); + assertEquals("UPDATE", node.getEvent()); + assertEquals("Old memory", node.getOldContent()); + assertEquals(1234567890L, node.getCreatedAt()); + assertEquals(1234567900L, node.getUpdatedAt()); + } + + @Test + void testWithDeleteEvent() { + BailianMemoryNode node = new BailianMemoryNode(); + node.setMemoryNodeId("mem_3"); + node.setContent("Deleted memory"); + node.setEvent("DELETE"); + node.setCreatedAt(1234567890L); + node.setUpdatedAt(1234567910L); + + assertEquals("mem_3", node.getMemoryNodeId()); + assertEquals("Deleted memory", node.getContent()); + assertEquals("DELETE", node.getEvent()); + assertNull(node.getOldContent()); + assertEquals(1234567890L, node.getCreatedAt()); + assertEquals(1234567910L, node.getUpdatedAt()); + } + + @Test + void testWithAllFields() { + BailianMemoryNode node = new BailianMemoryNode(); + node.setMemoryNodeId("mem_full"); + node.setContent("Full memory content"); + node.setEvent("UPDATE"); + node.setOldContent("Previous content"); + node.setCreatedAt(1234567880L); + node.setUpdatedAt(1234567920L); + + assertEquals("mem_full", node.getMemoryNodeId()); + assertEquals("Full memory content", node.getContent()); + assertEquals("UPDATE", node.getEvent()); + assertEquals("Previous content", node.getOldContent()); + assertEquals(1234567880L, node.getCreatedAt()); + assertEquals(1234567920L, node.getUpdatedAt()); + } + + @Test + void testWithNullValues() { + BailianMemoryNode node = new BailianMemoryNode(); + node.setMemoryNodeId(null); + node.setContent(null); + node.setEvent(null); + node.setOldContent(null); + node.setCreatedAt(null); + node.setUpdatedAt(null); + + assertNull(node.getMemoryNodeId()); + assertNull(node.getContent()); + assertNull(node.getEvent()); + assertNull(node.getOldContent()); + assertNull(node.getCreatedAt()); + assertNull(node.getUpdatedAt()); + } + + @Test + void testWithEmptyContent() { + BailianMemoryNode node = new BailianMemoryNode(); + node.setMemoryNodeId("mem_empty"); + node.setContent(""); + node.setEvent("ADD"); + + assertEquals("mem_empty", node.getMemoryNodeId()); + assertEquals("", node.getContent()); + assertEquals("ADD", node.getEvent()); + } + + @Test + void testWithDifferentEventTypes() { + BailianMemoryNode addNode = new BailianMemoryNode(); + addNode.setEvent("ADD"); + + BailianMemoryNode updateNode = new BailianMemoryNode(); + updateNode.setEvent("UPDATE"); + + BailianMemoryNode deleteNode = new BailianMemoryNode(); + deleteNode.setEvent("DELETE"); + + assertEquals("ADD", addNode.getEvent()); + assertEquals("UPDATE", updateNode.getEvent()); + assertEquals("DELETE", deleteNode.getEvent()); + } + + @Test + void testWithTimestamps() { + BailianMemoryNode node = new BailianMemoryNode(); + long now = System.currentTimeMillis() / 1000 * 1000; + + node.setCreatedAt(now); + node.setUpdatedAt(now + 1000); + + assertEquals(now, node.getCreatedAt()); + assertEquals(now + 1000, node.getUpdatedAt()); + } + + @Test + void testWithNullOldContentForAddEvent() { + BailianMemoryNode node = new BailianMemoryNode(); + node.setEvent("ADD"); + node.setOldContent(null); + + assertEquals("ADD", node.getEvent()); + assertNull(node.getOldContent()); + } + + @Test + void testWithOldContentOnlyForUpdateEvent() { + BailianMemoryNode node = new BailianMemoryNode(); + node.setEvent("UPDATE"); + node.setOldContent("Previous version"); + + assertEquals("UPDATE", node.getEvent()); + assertEquals("Previous version", node.getOldContent()); + } +} diff --git a/agentscope-extensions/agentscope-extensions-memory-bailian/src/test/java/io/agentscope/core/memory/bailian/BailianMessageTest.java b/agentscope-extensions/agentscope-extensions-memory-bailian/src/test/java/io/agentscope/core/memory/bailian/BailianMessageTest.java new file mode 100644 index 000000000..89eb810dc --- /dev/null +++ b/agentscope-extensions/agentscope-extensions-memory-bailian/src/test/java/io/agentscope/core/memory/bailian/BailianMessageTest.java @@ -0,0 +1,138 @@ +/* + * Copyright 2024-2026 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.agentscope.core.memory.bailian; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.junit.jupiter.api.Test; + +/** Unit tests for {@link BailianMessage}. */ +class BailianMessageTest { + + @Test + void testConstructorWithRoleAndContent() { + BailianMessage message = new BailianMessage("user", "Test message"); + + assertEquals("user", message.getRole()); + assertEquals("Test message", message.getContent()); + } + + @Test + void testDefaultConstructor() { + BailianMessage message = new BailianMessage(); + + assertNull(message.getRole()); + assertNull(message.getContent()); + } + + @Test + void testSettersAndGetters() { + BailianMessage message = new BailianMessage(); + + message.setRole("assistant"); + assertEquals("assistant", message.getRole()); + + message.setContent("Assistant response"); + assertEquals("Assistant response", message.getContent()); + } + + @Test + void testBuilderWithRole() { + BailianMessage message = BailianMessage.builder().role("user").build(); + + assertEquals("user", message.getRole()); + } + + @Test + void testBuilderWithContent() { + BailianMessage message = BailianMessage.builder().content("Test content").build(); + + assertEquals("Test content", message.getContent()); + } + + @Test + void testBuilderWithRoleAndContent() { + BailianMessage message = + BailianMessage.builder().role("assistant").content("Assistant message").build(); + + assertEquals("assistant", message.getRole()); + assertEquals("Assistant message", message.getContent()); + } + + @Test + void testBuilderChain() { + BailianMessage message = + BailianMessage.builder().role("user").content("User message").build(); + + assertNotNull(message); + assertEquals("user", message.getRole()); + assertEquals("User message", message.getContent()); + } + + @Test + void testBuilderWithNullValues() { + BailianMessage message = BailianMessage.builder().role(null).content(null).build(); + + assertNull(message.getRole()); + assertNull(message.getContent()); + } + + @Test + void testSetRole() { + BailianMessage message = new BailianMessage(); + message.setRole("user"); + assertEquals("user", message.getRole()); + + message.setRole("assistant"); + assertEquals("assistant", message.getRole()); + } + + @Test + void testSetContent() { + BailianMessage message = new BailianMessage(); + message.setContent("First content"); + assertEquals("First content", message.getContent()); + + message.setContent("Second content"); + assertEquals("Second content", message.getContent()); + } + + @Test + void testWithEmptyContent() { + BailianMessage message = BailianMessage.builder().role("user").content("").build(); + + assertEquals("user", message.getRole()); + assertEquals("", message.getContent()); + } + + @Test + void testWithNullRole() { + BailianMessage message = BailianMessage.builder().role(null).content("Content").build(); + + assertNull(message.getRole()); + assertEquals("Content", message.getContent()); + } + + @Test + void testWithNullContent() { + BailianMessage message = BailianMessage.builder().role("user").content(null).build(); + + assertEquals("user", message.getRole()); + assertNull(message.getContent()); + } +} diff --git a/agentscope-extensions/agentscope-extensions-memory-bailian/src/test/java/io/agentscope/core/memory/bailian/BailianSearchRequestTest.java b/agentscope-extensions/agentscope-extensions-memory-bailian/src/test/java/io/agentscope/core/memory/bailian/BailianSearchRequestTest.java new file mode 100644 index 000000000..b461635a8 --- /dev/null +++ b/agentscope-extensions/agentscope-extensions-memory-bailian/src/test/java/io/agentscope/core/memory/bailian/BailianSearchRequestTest.java @@ -0,0 +1,249 @@ +/* + * Copyright 2024-2026 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.agentscope.core.memory.bailian; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.List; +import org.junit.jupiter.api.Test; + +/** Unit tests for {@link BailianSearchRequest}. */ +class BailianSearchRequestTest { + + @Test + void testBuilderWithUserId() { + BailianSearchRequest request = BailianSearchRequest.builder().userId("user123").build(); + + assertEquals("user123", request.getUserId()); + } + + @Test + void testBuilderWithMessages() { + List messages = + List.of(BailianMessage.builder().role("user").content("Search query").build()); + + BailianSearchRequest request = BailianSearchRequest.builder().messages(messages).build(); + + assertEquals(messages, request.getMessages()); + } + + @Test + void testBuilderWithMemoryLibraryId() { + BailianSearchRequest request = + BailianSearchRequest.builder().memoryLibraryId("lib_456").build(); + + assertEquals("lib_456", request.getMemoryLibraryId()); + } + + @Test + void testBuilderWithProjectIds() { + List projectIds = List.of("proj_1", "proj_2", "proj_3"); + + BailianSearchRequest request = + BailianSearchRequest.builder().projectIds(projectIds).build(); + + assertEquals(projectIds, request.getProjectIds()); + } + + @Test + void testBuilderWithTopK() { + BailianSearchRequest request = BailianSearchRequest.builder().topK(15).build(); + + assertEquals(15, request.getTopK()); + } + + @Test + void testBuilderWithMinScore() { + BailianSearchRequest request = BailianSearchRequest.builder().minScore(0.7).build(); + + assertEquals(0.7, request.getMinScore()); + } + + @Test + void testBuilderWithEnableRerank() { + BailianSearchRequest request = BailianSearchRequest.builder().enableRerank(true).build(); + + assertEquals(true, request.getEnableRerank()); + } + + @Test + void testBuilderWithEnableJudge() { + BailianSearchRequest request = BailianSearchRequest.builder().enableJudge(true).build(); + + assertEquals(true, request.getEnableJudge()); + } + + @Test + void testBuilderWithEnableRewrite() { + BailianSearchRequest request = BailianSearchRequest.builder().enableRewrite(true).build(); + + assertEquals(true, request.getEnableRewrite()); + } + + @Test + void testBuilderWithAllFields() { + List messages = + List.of(BailianMessage.builder().role("user").content("Search query").build()); + List projectIds = List.of("proj_1", "proj_2"); + + BailianSearchRequest request = + BailianSearchRequest.builder() + .userId("user123") + .messages(messages) + .memoryLibraryId("lib_456") + .projectIds(projectIds) + .topK(10) + .minScore(0.5) + .enableRerank(true) + .enableJudge(true) + .enableRewrite(true) + .build(); + + assertEquals("user123", request.getUserId()); + assertEquals(messages, request.getMessages()); + assertEquals("lib_456", request.getMemoryLibraryId()); + assertEquals(projectIds, request.getProjectIds()); + assertEquals(10, request.getTopK()); + assertEquals(0.5, request.getMinScore()); + assertEquals(true, request.getEnableRerank()); + assertEquals(true, request.getEnableJudge()); + assertEquals(true, request.getEnableRewrite()); + } + + @Test + void testSettersAndGetters() { + BailianSearchRequest request = new BailianSearchRequest(); + + request.setUserId("user456"); + assertEquals("user456", request.getUserId()); + + List messages = + List.of(BailianMessage.builder().role("user").content("Query").build()); + request.setMessages(messages); + assertEquals(messages, request.getMessages()); + + request.setMemoryLibraryId("library"); + assertEquals("library", request.getMemoryLibraryId()); + + List projectIds = List.of("proj_a", "proj_b"); + request.setProjectIds(projectIds); + assertEquals(projectIds, request.getProjectIds()); + + request.setTopK(20); + assertEquals(20, request.getTopK()); + + request.setMinScore(0.8); + assertEquals(0.8, request.getMinScore()); + + request.setEnableRerank(false); + assertEquals(false, request.getEnableRerank()); + + request.setEnableJudge(false); + assertEquals(false, request.getEnableJudge()); + + request.setEnableRewrite(false); + assertEquals(false, request.getEnableRewrite()); + } + + @Test + void testDefaultConstructor() { + BailianSearchRequest request = new BailianSearchRequest(); + + assertNull(request.getUserId()); + assertNull(request.getMessages()); + assertNull(request.getMemoryLibraryId()); + assertNull(request.getProjectIds()); + assertNull(request.getTopK()); + assertNull(request.getMinScore()); + assertNull(request.getEnableRerank()); + assertNull(request.getEnableJudge()); + assertNull(request.getEnableRewrite()); + } + + @Test + void testBuilderChain() { + BailianSearchRequest request = + BailianSearchRequest.builder() + .userId("user123") + .memoryLibraryId("lib_456") + .topK(10) + .minScore(0.5) + .enableRerank(true) + .build(); + + assertNotNull(request); + assertEquals("user123", request.getUserId()); + assertEquals("lib_456", request.getMemoryLibraryId()); + assertEquals(10, request.getTopK()); + assertEquals(0.5, request.getMinScore()); + assertEquals(true, request.getEnableRerank()); + } + + @Test + void testBuilderWithNullValues() { + BailianSearchRequest request = + BailianSearchRequest.builder() + .userId(null) + .messages(null) + .memoryLibraryId(null) + .projectIds(null) + .topK(null) + .minScore(null) + .enableRerank(null) + .enableJudge(null) + .enableRewrite(null) + .build(); + + assertNull(request.getUserId()); + assertNull(request.getMessages()); + assertNull(request.getMemoryLibraryId()); + assertNull(request.getProjectIds()); + assertNull(request.getTopK()); + assertNull(request.getMinScore()); + assertNull(request.getEnableRerank()); + assertNull(request.getEnableJudge()); + assertNull(request.getEnableRewrite()); + } + + @Test + void testBuilderWithEmptyProjectIds() { + BailianSearchRequest request = BailianSearchRequest.builder().projectIds(List.of()).build(); + + assertNotNull(request.getProjectIds()); + assertEquals(0, request.getProjectIds().size()); + } + + @Test + void testBuilderWithSingleProjectId() { + BailianSearchRequest request = + BailianSearchRequest.builder().projectIds(List.of("proj_1")).build(); + + assertNotNull(request.getProjectIds()); + assertEquals(1, request.getProjectIds().size()); + assertEquals("proj_1", request.getProjectIds().get(0)); + } + + @Test + void testBuilderWithBooleanDefaults() { + BailianSearchRequest request = BailianSearchRequest.builder().build(); + + assertNull(request.getEnableRerank()); + assertNull(request.getEnableJudge()); + assertNull(request.getEnableRewrite()); + } +} diff --git a/agentscope-extensions/agentscope-extensions-memory-bailian/src/test/java/io/agentscope/core/memory/bailian/BailianSearchResponseTest.java b/agentscope-extensions/agentscope-extensions-memory-bailian/src/test/java/io/agentscope/core/memory/bailian/BailianSearchResponseTest.java new file mode 100644 index 000000000..e4af71707 --- /dev/null +++ b/agentscope-extensions/agentscope-extensions-memory-bailian/src/test/java/io/agentscope/core/memory/bailian/BailianSearchResponseTest.java @@ -0,0 +1,140 @@ +/* + * Copyright 2024-2026 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.agentscope.core.memory.bailian; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.List; +import org.junit.jupiter.api.Test; + +/** Unit tests for {@link BailianSearchResponse}. */ +class BailianSearchResponseTest { + + @Test + void testSettersAndGetters() { + BailianSearchResponse response = new BailianSearchResponse(); + + response.setRequestId("req_123"); + assertEquals("req_123", response.getRequestId()); + + List memoryNodes = List.of(new BailianMemoryNode()); + response.setMemoryNodes(memoryNodes); + assertEquals(memoryNodes, response.getMemoryNodes()); + } + + @Test + void testDefaultConstructor() { + BailianSearchResponse response = new BailianSearchResponse(); + + assertNull(response.getRequestId()); + assertNull(response.getMemoryNodes()); + } + + @Test + void testResponseWithSingleMemoryNode() { + BailianSearchResponse response = new BailianSearchResponse(); + response.setRequestId("req_456"); + + BailianMemoryNode node = new BailianMemoryNode(); + node.setMemoryNodeId("mem_1"); + node.setContent("Memory content"); + node.setEvent("ADD"); + + response.setMemoryNodes(List.of(node)); + + assertEquals("req_456", response.getRequestId()); + assertNotNull(response.getMemoryNodes()); + assertEquals(1, response.getMemoryNodes().size()); + assertEquals("mem_1", response.getMemoryNodes().get(0).getMemoryNodeId()); + assertEquals("Memory content", response.getMemoryNodes().get(0).getContent()); + } + + @Test + void testResponseWithMultipleMemoryNodes() { + BailianSearchResponse response = new BailianSearchResponse(); + response.setRequestId("req_789"); + + BailianMemoryNode node1 = new BailianMemoryNode(); + node1.setMemoryNodeId("mem_1"); + node1.setContent("First memory"); + node1.setEvent("ADD"); + + BailianMemoryNode node2 = new BailianMemoryNode(); + node2.setMemoryNodeId("mem_2"); + node2.setContent("Second memory"); + node2.setEvent("ADD"); + + BailianMemoryNode node3 = new BailianMemoryNode(); + node3.setMemoryNodeId("mem_3"); + node3.setContent("Third memory"); + node3.setEvent("ADD"); + + response.setMemoryNodes(List.of(node1, node2, node3)); + + assertEquals("req_789", response.getRequestId()); + assertEquals(3, response.getMemoryNodes().size()); + assertEquals("First memory", response.getMemoryNodes().get(0).getContent()); + assertEquals("Second memory", response.getMemoryNodes().get(1).getContent()); + assertEquals("Third memory", response.getMemoryNodes().get(2).getContent()); + } + + @Test + void testResponseWithEmptyMemoryNodes() { + BailianSearchResponse response = new BailianSearchResponse(); + response.setRequestId("req_empty"); + response.setMemoryNodes(List.of()); + + assertEquals("req_empty", response.getRequestId()); + assertNotNull(response.getMemoryNodes()); + assertEquals(0, response.getMemoryNodes().size()); + } + + @Test + void testResponseWithNullMemoryNodes() { + BailianSearchResponse response = new BailianSearchResponse(); + response.setRequestId("req_null"); + + assertNull(response.getMemoryNodes()); + } + + @Test + void testResponseWithMemoryNodeHavingAllFields() { + BailianSearchResponse response = new BailianSearchResponse(); + response.setRequestId("req_full"); + + BailianMemoryNode node = new BailianMemoryNode(); + node.setMemoryNodeId("mem_123"); + node.setContent("Full memory content"); + node.setEvent("UPDATE"); + node.setOldContent("Old memory content"); + node.setCreatedAt(1234567890L); + node.setUpdatedAt(1234567900L); + + response.setMemoryNodes(List.of(node)); + + assertEquals("req_full", response.getRequestId()); + assertNotNull(response.getMemoryNodes()); + assertEquals(1, response.getMemoryNodes().size()); + assertEquals("mem_123", response.getMemoryNodes().get(0).getMemoryNodeId()); + assertEquals("Full memory content", response.getMemoryNodes().get(0).getContent()); + assertEquals("UPDATE", response.getMemoryNodes().get(0).getEvent()); + assertEquals("Old memory content", response.getMemoryNodes().get(0).getOldContent()); + assertEquals(1234567890L, response.getMemoryNodes().get(0).getCreatedAt()); + assertEquals(1234567900L, response.getMemoryNodes().get(0).getUpdatedAt()); + } +} diff --git a/agentscope-extensions/pom.xml b/agentscope-extensions/pom.xml index 15ffd175e..41e350167 100644 --- a/agentscope-extensions/pom.xml +++ b/agentscope-extensions/pom.xml @@ -35,6 +35,7 @@ agentscope-extensions-autocontext-memory agentscope-extensions-skill-git-repository agentscope-extensions-mem0 + agentscope-extensions-memory-bailian agentscope-extensions-rag-bailian agentscope-extensions-rag-dify agentscope-extensions-rag-ragflow diff --git a/docs/en/task/memory.md b/docs/en/task/memory.md index 49f1259fa..783099b71 100644 --- a/docs/en/task/memory.md +++ b/docs/en/task/memory.md @@ -312,6 +312,56 @@ cd examples/advanced mvn exec:java -Dexec.mainClass="io.agentscope.examples.advanced.ReMeExample" ``` +### BailianLongTermMemory + +Long-term memory implementation based on [Bailian Memory Library](https://help.aliyun.com/zh/model-studio/memory-library). + +**Usage Example**: + +```java +import io.agentscope.core.ReActAgent; +import io.agentscope.core.memory.LongTermMemoryMode; +import io.agentscope.core.memory.bailian.BailianLongTermMemory; + +BailianLongTermMemory longTermMemory = BailianLongTermMemory.builder() + .apiKey(System.getenv("DASHSCOPE_API_KEY")) + .userId("your_user_id") + .memoryLibraryId("your_memory_library_id") + .projectId("your_project_id") + .profileSchema("your_profile_schema") + .build(); + +ReActAgent agent = ReActAgent.builder() + .name("Assistant") + .model(model) + .longTermMemory(longTermMemory) + .longTermMemoryMode(LongTermMemoryMode.STATIC_CONTROL) + .build(); +``` + +**Configuration Notes**: + +- `apiKey`: Alibaba Cloud DashScope API key (required) +- `userId`: User ID (required) +- `memoryLibraryId`: Bailian memory library ID (optional) +- `projectId`: Bailian project ID (optional) +- `profileSchema`: Bailian configuration Schema ID (optional) + +**Complete Example**: `agentscope-examples/advanced/src/main/java/io/agentscope/examples/advanced/BailianMemoryExample.java` + +**Run Example**: + +```bash +export DASHSCOPE_API_KEY=sk-xxxx +export BAILIAN_USER_ID=your_user_id +export BAILIAN_MEMORY_LIBRARY_ID=your_library_id +export BAILIAN_PROJECT_ID=your_project_id +export BAILIAN_PROFILE_SCHEMA=your_profile_schema + +cd agentscope-examples/advanced +mvn exec:java -Dexec.mainClass="io.agentscope.examples.advanced.BailianMemoryExample" +``` + ## Related Documentation - [AutoContextMemory Documentation](https://github.com/agentscope-ai/agentscope-java/blob/main/agentscope-extensions/agentscope-extensions-autocontext-memory/README.md) diff --git a/docs/zh/task/memory.md b/docs/zh/task/memory.md index e25ac67b2..43be3fc85 100644 --- a/docs/zh/task/memory.md +++ b/docs/zh/task/memory.md @@ -312,6 +312,57 @@ cd examples/advanced mvn exec:java -Dexec.mainClass="io.agentscope.examples.advanced.ReMeExample" ``` +### BailianLongTermMemory + +基于 [百炼记忆库](https://help.aliyun.com/zh/model-studio/memory-library) 的长期记忆实现。 + +**使用示例**: + +```java +import io.agentscope.core.ReActAgent; +import io.agentscope.core.memory.LongTermMemoryMode; +import io.agentscope.core.memory.bailian.BailianLongTermMemory; + +BailianLongTermMemory longTermMemory = BailianLongTermMemory.builder() + .apiKey(System.getenv("DASHSCOPE_API_KEY")) + .userId("your_user_id") + .memoryLibraryId("your_memory_library_id") + .projectId("your_project_id") + .profileSchema("your_profile_schema") + .metadata(Map.of("location_name", "Beijing")) + .build(); + +ReActAgent agent = ReActAgent.builder() + .name("Assistant") + .model(model) + .longTermMemory(longTermMemory) + .longTermMemoryMode(LongTermMemoryMode.STATIC_CONTROL) + .build(); +``` + +**配置说明**: + +- `apiKey`:阿里云 DashScope API 密钥(必需) +- `userId`:用户 ID(必需) +- `memoryLibraryId`:百炼记忆库 ID(可选) +- `projectId`:百炼记忆片段规则 ID(可选) +- `profileSchema`:百炼用户画像规则 ID(可选) + +**完整示例**:`agentscope-examples/advanced/src/main/java/io/agentscope/examples/advanced/BailianMemoryExample.java` + +**运行示例**: + +```bash +export DASHSCOPE_API_KEY=sk-xxxx +export BAILIAN_USER_ID=your_user_id +export BAILIAN_MEMORY_LIBRARY_ID=your_library_id +export BAILIAN_PROJECT_ID=your_project_id +export BAILIAN_PROFILE_SCHEMA=your_profile_schema + +cd agentscope-examples/advanced +mvn exec:java -Dexec.mainClass="io.agentscope.examples.advanced.BailianMemoryExample" +``` + ## 相关文档 - [AutoContextMemory 详细文档](https://github.com/agentscope-ai/agentscope-java/blob/main/agentscope-extensions/agentscope-extensions-autocontext-memory/README_zh.md)