最近社区列了一些Jmanus的todo list. 以下是个人的一些思路
实现方案
- 创建高德地图工具集成:参考现有的百度地图工具实现
- 设计MCP协议接口:使用MCP服务器配置来处理地理位置请求
- 实现路线规划代理:创建专门的代理来处理用户输入并生成路线
- 构建用户交互界面:提供简单的Web界面让用户输入地点并查看规划路线
具体实现1. 高德地图工具实现
首先,我们需要创建一个类似于BaiDuMapTools的高德地图工具类:
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);
}
}
}
对应的属性配置类:
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:
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自动装配:
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框架创建一个专门的路线规划代理:
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配置
@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.前端界面
<!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接口,用于接收用户输入并返回规划结果:
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中添加必要的配置:
spring:
ai:
alibaba:
functioncalling:
amap:
web-api-key: ${AMAP_API_KEY:your_amap_api_key}
mcp:
client:
model-name: qwen
enabled: true