diff --git a/agentscope-core/src/main/java/io/agentscope/core/rag/KnowledgeRetrievalTools.java b/agentscope-core/src/main/java/io/agentscope/core/rag/KnowledgeRetrievalTools.java index 84974d104..4c7e01535 100644 --- a/agentscope-core/src/main/java/io/agentscope/core/rag/KnowledgeRetrievalTools.java +++ b/agentscope-core/src/main/java/io/agentscope/core/rag/KnowledgeRetrievalTools.java @@ -133,7 +133,7 @@ public String retrieveKnowledge( // Set default value if (limit == null) { - limit = 5; + limit = defaultConfig.getLimit(); } // Extract conversation history from agent if available diff --git a/agentscope-core/src/main/java/io/agentscope/core/rag/model/Document.java b/agentscope-core/src/main/java/io/agentscope/core/rag/model/Document.java index 3793a3072..40cf0acb2 100644 --- a/agentscope-core/src/main/java/io/agentscope/core/rag/model/Document.java +++ b/agentscope-core/src/main/java/io/agentscope/core/rag/model/Document.java @@ -214,7 +214,7 @@ private static String generateDocumentId(DocumentMetadata metadata) { Map keyMap = new LinkedHashMap<>(); keyMap.put("doc_id", metadata.getDocId()); keyMap.put("chunk_id", metadata.getChunkId()); - keyMap.put("content", metadata.getContent()); + keyMap.put("content", metadata.getContentText()); // Serialize to JSON (ensure_ascii=False in Python, so we use default UTF-8) String jsonKey = JsonUtils.getJsonCodec().toJson(keyMap); diff --git a/agentscope-core/src/test/java/io/agentscope/core/rag/KnowledgeRetrievalToolsTest.java b/agentscope-core/src/test/java/io/agentscope/core/rag/KnowledgeRetrievalToolsTest.java new file mode 100644 index 000000000..53e3b86e0 --- /dev/null +++ b/agentscope-core/src/test/java/io/agentscope/core/rag/KnowledgeRetrievalToolsTest.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.rag; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.agentscope.core.message.TextBlock; +import io.agentscope.core.rag.model.Document; +import io.agentscope.core.rag.model.DocumentMetadata; +import io.agentscope.core.rag.model.RetrieveConfig; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; + +/** + * Unit tests for KnowledgeRetrievalTools. + */ +@Tag("unit") +@DisplayName("KnowledgeRetrievalTools Unit Tests") +class KnowledgeRetrievalToolsTest { + + private static Document makeDoc(String text) { + return new Document( + new DocumentMetadata(TextBlock.builder().text(text).build(), "doc-1", "0")); + } + + @Test + @DisplayName("When LLM omits limit, defaultConfig.getLimit() should be used") + void testNullLimitFallsBackToDefaultConfig() { + AtomicReference capturedConfig = new AtomicReference<>(); + + Knowledge knowledge = + new Knowledge() { + @Override + public Mono addDocuments(List documents) { + return Mono.empty(); + } + + @Override + public Mono> retrieve(String query, RetrieveConfig config) { + capturedConfig.set(config); + return Mono.just(List.of()); + } + }; + + RetrieveConfig defaultConfig = RetrieveConfig.builder().limit(8).build(); + KnowledgeRetrievalTools tools = new KnowledgeRetrievalTools(knowledge, defaultConfig); + + // Simulate LLM not providing limit (null) + tools.retrieveKnowledge("test query", null, null); + + assertNotNull(capturedConfig.get()); + assertEquals( + 8, + capturedConfig.get().getLimit(), + "When limit is null, defaultConfig.getLimit() should be used, not hardcoded 5"); + } + + @Test + @DisplayName("When LLM provides explicit limit, it should override defaultConfig") + void testExplicitLimitOverridesDefault() { + AtomicReference capturedConfig = new AtomicReference<>(); + + Knowledge knowledge = + new Knowledge() { + @Override + public Mono addDocuments(List documents) { + return Mono.empty(); + } + + @Override + public Mono> retrieve(String query, RetrieveConfig config) { + capturedConfig.set(config); + return Mono.just(List.of()); + } + }; + + RetrieveConfig defaultConfig = RetrieveConfig.builder().limit(8).build(); + KnowledgeRetrievalTools tools = new KnowledgeRetrievalTools(knowledge, defaultConfig); + + tools.retrieveKnowledge("test query", 3, null); + + assertNotNull(capturedConfig.get()); + assertEquals( + 3, + capturedConfig.get().getLimit(), + "Explicit limit from LLM should override defaultConfig.getLimit()"); + } + + @Test + @DisplayName("formatDocumentsForTool should return no-results message for empty list") + void testFormatEmptyResults() { + Knowledge knowledge = + new Knowledge() { + @Override + public Mono addDocuments(List docs) { + return Mono.empty(); + } + + @Override + public Mono> retrieve(String query, RetrieveConfig config) { + return Mono.just(List.of()); + } + }; + + KnowledgeRetrievalTools tools = new KnowledgeRetrievalTools(knowledge); + String result = tools.retrieveKnowledge("query", null, null); + + assertTrue( + result.contains("No relevant documents found"), + "Empty results should return no-results message"); + } + + @Test + @DisplayName("formatDocumentsForTool should include document content and score") + void testFormatDocumentsWithScore() { + Document doc = makeDoc("Important content"); + doc.setScore(0.87); + + Knowledge knowledge = + new Knowledge() { + @Override + public Mono addDocuments(List docs) { + return Mono.empty(); + } + + @Override + public Mono> retrieve(String query, RetrieveConfig config) { + return Mono.just(List.of(doc)); + } + }; + + KnowledgeRetrievalTools tools = new KnowledgeRetrievalTools(knowledge); + String result = tools.retrieveKnowledge("query", null, null); + + assertTrue(result.contains("Important content")); + assertTrue(result.contains("0.870")); + } +} diff --git a/agentscope-core/src/test/java/io/agentscope/core/rag/model/DocumentTest.java b/agentscope-core/src/test/java/io/agentscope/core/rag/model/DocumentTest.java index d2da546d0..ecafdba4a 100644 --- a/agentscope-core/src/test/java/io/agentscope/core/rag/model/DocumentTest.java +++ b/agentscope-core/src/test/java/io/agentscope/core/rag/model/DocumentTest.java @@ -69,6 +69,29 @@ void testDocumentIdConsistency() { assertEquals(doc1.getId(), doc2.getId()); } + @Test + @DisplayName("Should generate ID based on text content, not ContentBlock object structure") + void testDocumentIdUsesTextNotContentBlockObject() { + // Two TextBlock instances with identical text but different object references + // must produce the same document ID, proving the ID is derived from the text + // string rather than the ContentBlock object's serialized form. + TextBlock content1 = TextBlock.builder().text("hello world").build(); + TextBlock content2 = TextBlock.builder().text("hello world").build(); + DocumentMetadata metadata1 = new DocumentMetadata(content1, "doc-1", "0"); + DocumentMetadata metadata2 = new DocumentMetadata(content2, "doc-1", "0"); + + Document doc1 = new Document(metadata1); + Document doc2 = new Document(metadata2); + + assertEquals(doc1.getId(), doc2.getId()); + + // Also verify that different text produces a different ID + TextBlock differentContent = TextBlock.builder().text("different text").build(); + DocumentMetadata metadata3 = new DocumentMetadata(differentContent, "doc-1", "0"); + Document doc3 = new Document(metadata3); + assertFalse(doc1.getId().equals(doc3.getId())); + } + @Test @DisplayName("Should generate different IDs for different content") void testDocumentIdUniqueness() {