Spring AI天气查询MCP客户端构建踩坑记录

37 阅读3分钟

前言

前文构建了mcp服务端并使用inspector测试,本文在完善了mcp服务端功能的基础上,记录在构建基于Spring ai的客户端程序过程中遇到的问题。客户端的完整实现可直接参考官网,本文不再赘述。

背景

官方构建客户端使用anthropic模型,而本文使用基于阿里云百炼的deepseek模型。spring ai官方给的demo无法工作且存在bug,本文记录解决的这些问题。

环境搭建与基础配置

  • 开发环境
    JDK17、SpringBoot3.5.8、SpringAI1.1.0

客户端搭建问题记录

依赖更改

原文使用anthropic模型,而本文使用基于阿里云百炼的deepseek-v3.2-exp模型,spring ai官方已提供对接deepseek的依赖,于是将spring-ai-starter-model-anthropic替换为spring-ai-starter-model-deepseek,如下所示:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-deepseek</artifactId>
</dependency>

在完成上述替换后,启动客户端时会报api-key无效的错误,而我另一个项目却可以使用,原来是deepseek依赖默认访问deepseek官方的大模型,而我使用的是阿里云百炼的deepseek模型,于是添加如下配置

ai:
  deepseek:
    api-key: your api-key
    #大模型地址
    base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
    chat:
      options:
        #使用的模型名称,不指定会默认官方名称而报错
        model: deepseek-v3.2-exp

在完成上述配置后,项目应该可以启动成功了。官方示例使用了CommandLineRunner,在应用启动后完成会自动执行任务,任务定义如下:

@Bean
public CommandLineRunner predefinedQuestions(ChatClient chatClient, ToolCallbackProvider mcpToolProvider) { // (3)
    return args -> System.out.println(
            chatClient.prompt(userPrompt) // (4)
                    .toolContext(Map.of("progressToken", "token-" + new Random().nextInt())) // (5)
                    .toolCallbacks(mcpToolProvider) // (6)
                    .call()
                    .content());
}

其中userPrompt的定义如下:

String userPrompt = """ Check the weather in Amsterdam right now and show 
the creative response! Please incorporate all creative responses from all LLM providers. """;

这段prompt向大模型询问Amsterdam的天气。在执行这个任务时发生Json解析错误,从错误日志可知返回的内容json格式不正确。查看服务端,可知服务端返回的是一段字符串文本,代码如下:

return """ Weather Poem: %s about the weather: %s°C at location: (%s, %s) """.formatted(epicPoem, weatherResponse.current().temperature_2m(), latitude, longitude);

解决这个问题有两个方向,一种是修改客户端的接收方式,使其能处理文本内容,另一种就是直接修改返回内容的格式为json,本文使用第二种,修改后的返回格式如下,注意数字部分格式化添加的双引号:

return """
{
    "poem": "Weather Poem2: %s",
    "temperature": "%s",  //这里双引号不能省略,否则返回的json格式仍不正确
    "latitude": "%s",
    "longitude": %s,
    "description": "about the weather: %s°C at location with latitude: %s and longitude: %s"
}
""".formatted(
        epicPoem,
        weatherResponse.current().temperature_2m(),
        latitude,
        longitude,
        weatherResponse.current().temperature_2m(),
        latitude,
        longitude
);

在修改服务端返回值格式后,CommandLineRunner的任务可以正常运行了,大模型的返回是无法获取指定城市的纬度和经度。于是我修改了prompt,直接告诉大模型要查询天气城市的纬度和经度,prompt定义如下:

String userPrompt = """
        Check the weather in shanghai right now and show the creative response!
        The latitude of shanghai is 21.3 and longitude of shanghai is 121.5.
              Please incorporate all creative responses from all LLM providers.
        """;

修改后重新启动客户端,可以看到任务成功执行,并完成了一系列日志、进度追踪以及Sampling等功能。

思考和改进

官方提供的示例并没有告诉大模型所查城市的经纬度,我猜想可能是anthropic训练的数据集有各城市的经纬度从而可以直接用经纬度去查询。而本文为了快速验证功能性通过修改prompt告诉了大模型城市的经纬度。有没有办法不用在prompt中提供经纬度而直接查询城市天气呢?一种方法是在客户端提供查询城市经纬度的方法,最后生成含有经纬度的prompt再输入给大模型,另一种是在服务端提供按城市名查询天气的tool,这需要天气查询网站支持。还有一种方法是在服务端提供根据城市查询其经纬度的tool,和查询天气的tool形成一套工具链。