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:
+ *
+ * - Alibaba Cloud DashScope API key
+ * - Bailian Long Term Memory service access
+ *
+ *
+ * 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:
+ *
+ * - Creating an agent with Bailian long-term memory
+ * - Using STATIC_CONTROL mode for memory operations
+ * - Interactive chat with persistent memory
+ *
+ */
+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:
+ *
+ * - Semantic memory search using vector embeddings
+ * - LLM-powered memory extraction and inference
+ * - Multi-tenant memory isolation (user_id, memory_library_id)
+ * - Reactive, non-blocking operations
+ *
+ *
+ * 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)