diff --git a/mateclaw-server/src/main/java/vip/mate/memory/search/SessionSearchTool.java b/mateclaw-server/src/main/java/vip/mate/memory/search/SessionSearchTool.java index dfc22605..cc10f0b4 100644 --- a/mateclaw-server/src/main/java/vip/mate/memory/search/SessionSearchTool.java +++ b/mateclaw-server/src/main/java/vip/mate/memory/search/SessionSearchTool.java @@ -38,7 +38,7 @@ public class SessionSearchTool { 注意:只会搜索已完成的会话,不会返回当前正在运行中的其他会话内容。 """) public String session_search( - @ToolParam(description = "当前 Agent 的 ID") Long agentId, + @ToolParam(description = "当前 Agent 的 ID。必须作为字符串传入,避免大整数精度丢失") String agentId, @ToolParam(description = "搜索模式:recent 或 search") String mode, @ToolParam(description = "搜索关键词(mode=search 时必填)", required = false) String query, @ToolParam(description = "返回结果数量上限,默认 10", required = false) Integer limit, @@ -64,13 +64,14 @@ public class SessionSearchTool { int effectiveLimit = limit != null && limit > 0 ? limit : 10; try { + Long parsedAgentId = parseAgentId(agentId); if ("recent".equalsIgnoreCase(mode.trim())) { - return handleRecent(agentId, currentConversationId, effectiveLimit); + return handleRecent(parsedAgentId, currentConversationId, effectiveLimit); } else if ("search".equalsIgnoreCase(mode.trim())) { if (query == null || query.isBlank()) { return error("mode=search 时 query 不能为空"); } - return handleSearch(agentId, currentConversationId, query, effectiveLimit); + return handleSearch(parsedAgentId, currentConversationId, query, effectiveLimit); } else { return error("无效的 mode: " + mode + ",请使用 recent 或 search"); } @@ -117,4 +118,16 @@ public class SessionSearchTool { result.set("message", message); return JSONUtil.toJsonPrettyStr(result); } + + private Long parseAgentId(String agentId) { + String trimmed = agentId != null ? agentId.trim() : ""; + if (trimmed.isEmpty()) { + throw new IllegalArgumentException("agentId 不能为空"); + } + try { + return Long.parseLong(trimmed); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("agentId 必须是数字字符串"); + } + } } diff --git a/mateclaw-server/src/test/java/vip/mate/memory/search/SessionSearchToolIdSchemaTest.java b/mateclaw-server/src/test/java/vip/mate/memory/search/SessionSearchToolIdSchemaTest.java new file mode 100644 index 00000000..0a35d6ae --- /dev/null +++ b/mateclaw-server/src/test/java/vip/mate/memory/search/SessionSearchToolIdSchemaTest.java @@ -0,0 +1,35 @@ +package vip.mate.memory.search; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.ai.support.ToolCallbacks; +import org.springframework.ai.tool.ToolCallback; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +class SessionSearchToolIdSchemaTest { + private static final ObjectMapper MAPPER = new ObjectMapper(); + + @Test + @DisplayName("session_search publishes agentId as a string parameter so LLM tool calls preserve precision") + void sessionSearchAgentIdSchemaIsString() throws Exception { + SessionSearchTool tool = new SessionSearchTool(mock(SessionSearchService.class)); + + String schema = callback(tool, "session_search").getToolDefinition().inputSchema(); + JsonNode root = MAPPER.readTree(schema); + + assertThat(root.at("/properties/agentId/type").asText()).isEqualTo("string"); + } + + private static ToolCallback callback(Object tool, String name) { + for (ToolCallback callback : ToolCallbacks.from(tool)) { + if (name.equals(callback.getToolDefinition().name())) { + return callback; + } + } + throw new AssertionError("Missing tool callback: " + name); + } +}