最近社区列了一些Jmanus的todo list. 以下是个人的一些思路
实现方案
- 创建高德地图工具集成:参考现有的百度地图工具实现
- 设计MCP协议接口:使用MCP服务器配置来处理地理位置请求
- 实现路线规划代理:创建专门的代理来处理用户输入并生成路线
- 构建用户交互界面:提供简单的Web界面让用户输入地点并查看规划路线
具体实现1. 高德地图工具实现
首先,我们需要创建一个类似于BaiDuMapTools
的高德地图工具类:
java
体验AI代码助手
代码解读
复制代码
package com.alibaba.cloud.ai.toolcalling.amap; import com.alibaba.cloud.ai.toolcalling.common.CommonToolCallUtils; import com.alibaba.cloud.ai.toolcalling.common.JsonParseTool; import com.alibaba.cloud.ai.toolcalling.common.WebClientTool; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.core.type.TypeReference; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; import java.util.List; import java.util.Objects; public final class AMapTools { private final AMapProperties amapProperties; private final WebClientTool webClientTool; private final JsonParseTool jsonParseTool; public AMapTools(AMapProperties amapProperties, WebClientTool webClientTool, JsonParseTool jsonParseTool) { this.amapProperties = amapProperties; this.webClientTool = webClientTool; this.jsonParseTool = jsonParseTool; if (Objects.isNull(amapProperties.getApiKey())) { throw new RuntimeException("请在application.yml文件中配置高德地图API密钥"); } } /** * 获取地点详细信息 * @param keyword 地点关键词 * @return 地点详细信息 */ public String getPlaceInfo(String keyword) { String path = "/v3/place/text"; MultiValueMap<String, String> params = CommonToolCallUtils.<String, String>multiValueMapBuilder() .add("key", amapProperties.getApiKey()) .add("keywords", keyword) .add("output", "json") .build(); try { return webClientTool.get(path, params).block(); } catch (Exception e) { throw new RuntimeException("获取地点信息失败", e); } } /** * 规划路线 * @param origin 起点坐标(经度,纬度) * @param destination 终点坐标(经度,纬度) * @param type 路线类型:driving(驾车)、walking(步行)、transit(公交)、riding(骑行) * @return 路线规划结果 */ public String planRoute(String origin, String destination, String type) { String path = "/v3/direction/" + type; MultiValueMap<String, String> params = CommonToolCallUtils.<String, String>multiValueMapBuilder() .add("key", amapProperties.getApiKey()) .add("origin", origin) .add("destination", destination) .add("output", "json") .build(); try { return webClientTool.get(path, params).block(); } catch (Exception e) { throw new RuntimeException("路线规划失败", e); } } }
对应的属性配置类:
typescript
体验AI代码助手
代码解读
复制代码
package com.alibaba.cloud.ai.toolcalling.amap; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "spring.ai.tool.amap") public class AMapProperties { private String apiKey; private String baseUrl = "https://restapi.amap.com"; public String getApiKey() { return apiKey; } public void setApiKey(String apiKey) { this.apiKey = apiKey; } public String getBaseUrl() { return baseUrl; } public void setBaseUrl(String baseUrl) { this.baseUrl = baseUrl; } }
2. 创建路线规划服务
接下来,我们创建一个路线规划服务,用于处理用户输入并调用高德地图API:
less
体验AI代码助手
代码解读
复制代码
package com.alibaba.cloud.ai.toolcalling.amap; import com.fasterxml.jackson.annotation.JsonClassDescription; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyDescription; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.function.Function; @JsonClassDescription("使用高德地图API规划从起点到终点的路线") public class AMapRouteService implements Function<AMapRouteService.Request, AMapRouteService.Response> { private final AMapTools amapTools; private final ObjectMapper objectMapper = new ObjectMapper(); public AMapRouteService(AMapTools amapTools) { this.amapTools = amapTools; } @Override public Response apply(Request request) { try { // 1. 获取起点坐标 String originInfo = amapTools.getPlaceInfo(request.origin()); JsonNode originNode = objectMapper.readTree(originInfo); String originCoord = extractCoordinates(originNode); // 2. 获取终点坐标 String destInfo = amapTools.getPlaceInfo(request.destination()); JsonNode destNode = objectMapper.readTree(destInfo); String destCoord = extractCoordinates(destNode); // 3. 规划路线 String routeInfo = amapTools.planRoute(originCoord, destCoord, request.travelMode()); // 4. 处理结果 JsonNode routeNode = objectMapper.readTree(routeInfo); String routeSummary = extractRouteSummary(routeNode, request.travelMode()); return new Response(routeSummary); } catch (Exception e) { return new Response("路线规划失败: " + e.getMessage()); } } private String extractCoordinates(JsonNode placeNode) { if (placeNode.has("pois") && placeNode.get("pois").size() > 0) { JsonNode firstPoi = placeNode.get("pois").get(0); if (firstPoi.has("location")) { return firstPoi.get("location").asText(); } } throw new RuntimeException("无法获取地点坐标"); } private String extractRouteSummary(JsonNode routeNode, String travelMode) { StringBuilder summary = new StringBuilder(); if (routeNode.has("route")) { JsonNode route = routeNode.get("route"); // 提取基本信息 if (route.has("paths") && route.get("paths").size() > 0) { JsonNode path = route.get("paths").get(0); // 总距离和时间 if (path.has("distance") && path.has("duration")) { double distance = path.get("distance").asDouble() / 1000.0; // 转换为公里 int duration = path.get("duration").asInt() / 60; // 转换为分钟 summary.append(String.format("总距离: %.1f公里, 预计用时: %d分钟\n\n", distance, duration)); } // 提取路线步骤 if (path.has("steps") && path.get("steps").size() > 0) { summary.append("路线指引:\n"); JsonNode steps = path.get("steps"); for (int i = 0; i < steps.size(); i++) { JsonNode step = steps.get(i); if (step.has("instruction")) { summary.append(i + 1).append(". ") .append(step.get("instruction").asText()) .append("\n"); } } } } } return summary.toString(); } @JsonClassDescription("路线规划请求") public record Request( @JsonProperty(required = true) @JsonPropertyDescription("起点位置,如:北京市海淀区") String origin, @JsonProperty(required = true) @JsonPropertyDescription("终点位置,如:北京市朝阳区") String destination, @JsonProperty(required = false) @JsonPropertyDescription("出行方式:driving(驾车)、walking(步行)、transit(公交)、riding(骑行)") String travelMode) { } public record Response(String routePlan) { } }
3. 配置自动装配
创建自动配置类,使工具可以被Spring自动装配:
typescript
体验AI代码助手
代码解读
复制代码
package com.alibaba.cloud.ai.toolcalling.amap; import com.alibaba.cloud.ai.toolcalling.common.JsonParseTool; import com.alibaba.cloud.ai.toolcalling.common.WebClientTool; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.reactive.function.client.WebClient; @Configuration @EnableConfigurationProperties(AMapProperties.class) public class AMapAutoConfiguration { @Bean @ConditionalOnMissingBean public WebClientTool amapWebClientTool(AMapProperties amapProperties) { return new WebClientTool(WebClient.builder() .baseUrl(amapProperties.getBaseUrl()) .build()); } @Bean @ConditionalOnMissingBean public JsonParseTool amapJsonParseTool() { return new JsonParseTool(); } @Bean @ConditionalOnMissingBean public AMapTools amapTools(AMapProperties amapProperties, WebClientTool webClientTool, JsonParseTool jsonParseTool) { return new AMapTools(amapProperties, webClientTool, jsonParseTool); } @Bean @ConditionalOnMissingBean public AMapRouteService amapRouteService(AMapTools amapTools) { return new AMapRouteService(amapTools); } }
4. 创建路线规划代理
基于OpenManus框架创建一个专门的路线规划代理:
typescript
体验AI代码助手
代码解读
复制代码
package com.alibaba.cloud.ai.example.manus.agent; import com.alibaba.cloud.ai.example.manus.config.ManusProperties; import com.alibaba.cloud.ai.example.manus.llm.LlmService; import com.alibaba.cloud.ai.example.manus.recorder.PlanExecutionRecorder; import org.springframework.ai.model.tool.ToolCallingManager; import java.util.Map; public class RouteAgent extends BaseAgent { private static final String AGENT_NAME = "ROUTE_AGENT"; private static final String AGENT_DESCRIPTION = "专门处理地点查询和路线规划的智能体"; public RouteAgent(LlmService llmService, ToolCallingManager toolCallingManager, PlanExecutionRecorder recorder, ManusProperties manusProperties) { super(llmService, toolCallingManager, recorder, manusProperties); } @Override public String getName() { return AGENT_NAME; } @Override public String getDescription() { return AGENT_DESCRIPTION; } @Override protected String getThinkPrompt() { return """ 你是一个专门处理地点查询和路线规划的智能体。 你需要理解用户的出行需求,包括起点、终点以及可能的出行方式偏好。 然后使用高德地图API来规划最佳路线。 请按照以下步骤思考: 1. 分析用户请求,确定起点和终点 2. 确定合适的出行方式(驾车、步行、公交或骑行) 3. 使用高德地图API查询路线 4. 整理路线信息并提供给用户 你可以使用以下工具: - AMapRouteService:规划从起点到终点的路线 """; } @Override protected String getNextPrompt() { return """ 请根据用户的需求,使用高德地图API规划路线。 确保提供清晰的路线指引,包括总距离、预计时间以及详细的路线步骤。 如果用户没有明确指定出行方式,请默认使用驾车方式。 """; } }
5. 修改PlanningFlow配置
typescript
体验AI代码助手
代码解读
复制代码
@Bean @Scope("prototype") public PlanningFlow planningFlow(LlmService llmService, ToolCallingManager toolCallingManager, DynamicAgentLoader dynamicAgentLoader) { List<BaseAgent> agentList = new ArrayList<>(); Map<String, ToolCallBackContext> toolCallbackMap = new HashMap<>(); // 添加路线规划代理 RouteAgent routeAgent = new RouteAgent(llmService, toolCallingManager, recorder, manusProperties); toolCallbackMap = toolCallbackMap(routeAgent); routeAgent.setToolCallbackMap(toolCallbackMap); agentList.add(routeAgent); // 添加所有动态代理 for (DynamicAgentEntity agentEntity : dynamicAgentLoader.getAllAgents()) { DynamicAgent agent = dynamicAgentLoader.loadAgent(agentEntity.getAgentName()); toolCallbackMap = toolCallbackMap(agent); agent.setToolCallbackMap(toolCallbackMap); agentList.add(agent); } Map<String, Object> data = new HashMap<>(); return new PlanningFlow(agentList, data, recorder, toolCallbackMap); } // 工具回调映射方法 private Map<String, ToolCallBackContext> toolCallbackMap(BaseAgent agent) { Map<String, ToolCallBackContext> toolCallbackMap = new HashMap<>(); // 添加高德地图路线规划工具 AMapRouteService amapRouteService = context.getBean(AMapRouteService.class); ToolCallBiFunctionDef amapRouteTool = new ToolCallBiFunctionDef() { @Override public String getName() { return "amap_route"; } @Override public String getDescription() { return "使用高德地图API规划从起点到终点的路线"; } @Override public String getParameters() { return """ { "type": "object", "properties": { "origin": { "type": "string", "description": "起点位置,如:北京市海淀区" }, "destination": { "type": "string", "description": "终点位置,如:北京市朝阳区" }, "travelMode": { "type": "string", "enum": ["driving", "walking", "transit", "riding"], "description": "出行方式:driving(驾车)、walking(步行)、transit(公交)、riding(骑行)", "default": "driving" } }, "required": ["origin", "destination"] } """; } @Override public Class<?> getInputType() { return String.class; } @Override public boolean isReturnDirect() { return true; } @Override public void setAgent(BaseAgent agent) { // 设置关联的Agent } @Override public String getCurrentToolStateString() { return "Ready"; } @Override public void cleanup(String planId) { // 清理资源 } @Override public ToolExecuteResult apply(String input, ToolContext context) { try { ObjectMapper mapper = new ObjectMapper(); JsonNode node = mapper.readTree(input); String origin = node.get("origin").asText(); String destination = node.get("destination").asText(); String travelMode = node.has("travelMode") ? node.get("travelMode").asText() : "driving"; AMapRouteService.Request request = new AMapRouteService.Request( origin, destination, travelMode); AMapRouteService.Response response = amapRouteService.apply(request); return new ToolExecuteResult(response.routePlan()); } catch (Exception e) { return new ToolExecuteResult("路线规划失败: " + e.getMessage()); } } }; // 添加终止工具 TerminateTool terminateTool = new TerminateTool(agent); // 将工具添加到回调映射中 toolCallbackMap.put(amapRouteTool.getName(), new ToolCallBackContext( FunctionToolCallback.builder(amapRouteTool.getName(), amapRouteTool) .description(amapRouteTool.getDescription()) .inputSchema(amapRouteTool.getParameters()) .inputType(amapRouteTool.getInputType()) .build(), amapRouteTool )); toolCallbackMap.put("terminate", new ToolCallBackContext( FunctionToolCallback.builder("terminate", terminateTool) .description("终止当前任务执行") .inputSchema(terminateTool.getParameters()) .inputType(String.class) .build(), terminateTool )); return toolCallbackMap; }
- PlanningFlow Bean配置:
-
- 创建了一个prototype作用域的PlanningFlow Bean
- 添加了自定义的RouteAgent到代理列表中
- 为每个代理配置了工具回调映射
- 加载了所有动态代理
- 工具回调映射方法:
-
- 创建了一个专门用于配置工具回调的方法
- 实现了高德地图路线规划工具的ToolCallBiFunctionDef接口
- 添加了终止工具,用于结束代理执行
- 将工具添加到回调映射中,以便代理可以调用
这个配置确保了路线规划代理可以使用高德地图API工具来处理用户的路线规划请求。当用户输入起点和终点时,代理会通过工具调用高德地图API,获取路线信息并返回给用户。
6.前端界面
xml
体验AI代码助手
代码解读
复制代码
<!DOCTYPE html> <html> <head> <title>智能路线规划</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; } .form-group { margin-bottom: 15px; } label { display: block; margin-bottom: 5px; } input, textarea { width: 100%; padding: 8px; box-sizing: border-box; } button { background-color: #4CAF50; color: white; padding: 10px 15px; border: none; cursor: pointer; } #result { margin-top: 20px; border: 1px solid #ddd; padding: 15px; white-space: pre-wrap; } </style> </head> <body> <h1>智能路线规划</h1> <div class="form-group"> <label for="userInput">请描述您的出行需求(包含起点和终点):</label> <textarea id="userInput" rows="4" placeholder="例如:我想从北京南站到北京故宫,请帮我规划路线"></textarea> </div> <button onclick="planRoute()">规划路线</button> <div id="result"></div> <script> async function planRoute() { const userInput = document.getElementById('userInput').value; const resultDiv = document.getElementById('result'); resultDiv.innerHTML = "正在规划路线,请稍候..."; try { const response = await fetch('/api/route/plan', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ userInput }) }); const data = await response.json(); resultDiv.innerHTML = data.route; } catch (error) { resultDiv.innerHTML = "规划路线时出错:" + error.message; } } </script> </body> </html>
7.创建Controller接口
创建一个REST接口,用于接收用户输入并返回规划结果:
kotlin
体验AI代码助手
代码解读
复制代码
package com.alibaba.cloud.ai.example.manus.controller; import com.alibaba.cloud.ai.example.manus.agent.RouteNavigationAgent; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/api/route") public class RouteNavigationController { private final RouteNavigationAgent routeNavigationAgent; public RouteNavigationController(RouteNavigationAgent routeNavigationAgent) { this.routeNavigationAgent = routeNavigationAgent; } @PostMapping("/plan") public RouteResponse planRoute(@RequestBody RouteRequest request) { String result = routeNavigationAgent.planRoute(request.userInput); return new RouteResponse(result); } public record RouteRequest(String userInput) {} public record RouteResponse(String route) {} }
8. 配置MCP与高德地图集成
在application.yml
中添加必要的配置:
yaml
体验AI代码助手
代码解读
复制代码
spring: ai: alibaba: functioncalling: amap: web-api-key: ${AMAP_API_KEY:your_amap_api_key} mcp: client: model-name: qwen enabled: true