本文将介绍如何使用spring-ai构建一个集成市面各大mcp服务与自有知识库的mcp客户端外加一个自己提供的mcp服务,快速把一个自有的系统变成一个可以调用自有能力,利用自有知识库,联网搜索,位置定位等能力的智能助手
Spring AI 项目由 Spring 官方开源并维护的 AI 应用开发框架,该项目目标是简化包含人工智能(AI)功能的应用程序的开发,避免不必要的复杂性。该项目从著名的 Python 项目(例如 LangChain 和 LlamaIndex)中汲取灵感,但 Spring AI 并非这些项目的直接移植,该项目的成立基于这样的信念:下一波生成式 AI 应用将不仅面向 Python 开发人员,还将遍及多种编程语言。从本质上讲,Spring AI 解决了 AI 集成的基本挑战:Connecting your enterprise Data and APIs with the AI Models。
MCP客户端
实现智能助手我们需要引入以下客户端,依赖版本我们使用截止目前最新的1.0.0-M6
xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>${springai.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-client-webflux-spring-boot-starter</artifactId>
<version>${springai.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-qianfan-spring-boot-starter</artifactId>
<version>${springai.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-milvus-store-spring-boot-starter</artifactId>
<version>${springai.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-tika-document-reader</artifactId>
<version>${springai.version}</version>
</dependency>
- 因为我们使用的是deepseek作为底座的大模型,所以我们这里引入的是spring-ai-openai-spring-boot-starter
- 百度千帆作为嵌入模型(Embedding Model)
- 向量库使用milvus
- 文档切分使用spring-ai-tika-document-reader
客户端的配置文件部分
yaml
ai:
openai:
api-key: ********************
base-url: https://api.deepseek.com
chat:
options:
model: deepseek-chat
temperature: 0.8
embedding:
enabled: false
chat:
observations:
include-completion: true
include-error-logging: true
client:
observations:
include-input: true
mcp:
client:
enabled: true
name: ysj-mcp-client
type: ASYNC
stdio:
servers-configuration: classpath:/mcp-servers-config.json
sse:
connections:
server1:
url: http://127.0.0.1:8420
qianfan:
chat:
enabled: false
api-key: ***************
secret-key: **************
embedding:
options:
model: tao_8k
vectorstore:
milvus:
initialize-schema: true
client:
host: ***.***.***.**
port: *****
username: *****
password: *****
databaseName: "default"
collectionName: "vector_store"
embeddingDimension: 1536
indexType: IVF_FLAT
metricType: COSINE
需要特别注意禁用qianfan的chatclient qianfan: chat: enabled: false
这个配置方式在下个版本中会有变更,以下是官方文档原文
Enabling and disabling of the chat auto-configurations are now configured via top level properties with the prefix
spring.ai.model.chat
. To enable, spring.ai.model.chat=openai (It is enabled by default) To disable, spring.ai.model.chat=none (or any value which doesn't match openai) This change is done to allow configuration of multiple models.
mcp客户端集成配置mcp-servers-config.json
swift
{
"mcpServers": {
"tavily": {
"command": "C:\Program Files\nodejs\npx.cmd",
"args": ["-y", "@mcptools/mcp-tavily"],
"env": {
"TAVILY_API_KEY": "**************"
}
},
"baidu-map": {
"command": "C:\Program Files\nodejs\npx.cmd",
"args": [
"-y",
"@baidumap/mcp-server-baidu-map"
],
"env": {
"BAIDU_MAP_API_KEY": "****************"
}
}
}
}
这里集成的是一个AI搜索的第三方服务和一个百度地图的mcp服务,正在尝试接入一个text2Sql可本地私有化部署的mcp服务实现自然语言的数据库查询,还未完整实现先不放出,实现后再补充
mcp客户端代码部分
1.定义chatclient
scss
@Configuration
public class ChatClientConfig {
@Resource
private VectorStore vectorStore;
/**
* 配置ChatClient,注册系统指令和工具函数
*/
@Bean
public ChatClient chatClient(ChatClient.Builder builder,
List<McpSyncClient> mcpSyncClients,
ToolCallbackProvider tools) {
//添加顾问
Advisor retrievalAugmentationAdvisor = RetrievalAugmentationAdvisor.builder()
.documentRetriever(VectorStoreDocumentRetriever.builder()
.similarityThreshold(0.5)
.vectorStore(vectorStore)
.build())
.queryAugmenter(ContextualQueryAugmenter.builder()
.allowEmptyContext(true)
.build())
.build();
return builder
.defaultSystem("""
你是企业微信的智能助理------闪闪!
你性格活泼轻松,擅长以亲切友好的方式为用户提供帮助。
你的主要职责包括办理日常业务、与企业微信交互等实用功能,让用户的工作更高效、更便捷。
请以专业但不失幽默的语气与用户交流,确保沟通愉快且高效!
请说中文
返回的所有内容使用MarkDown格式展示
返回的MarkDown数据要适应前端流式展示的需求
""")
// 注册工具方法
.defaultTools(tools)
.defaultAdvisors(retrievalAugmentationAdvisor)
.build();
}
}
Advisor是springai封装的用来拦截、修改和增强 Spring 应用程序中的 AI 驱动交互,我们可以很便捷的设置各种知识库的特性,spring-ai把复杂的逻辑都帮我们封装好了使得我们可以很简单就实现一个复杂的知识库助手
超时配置
针对超时配置的部分官方文档的配置中有提供超时参数,但是我尝试下来没有生效,这里使用另一种方式实现超时参数的配置
typescript
@Configuration
public class McpClientConfig implements McpAsyncClientCustomizer {
@Override
public void customize(String name, McpClient.AsyncSpec spec) {
spec.requestTimeout(Duration.ofSeconds(30));
}
}
对话接口
less
@RestController
@RequestMapping("/chat-client")
public class McpController {
@Resource
private ChatClient chatClient;
private final ChatMemory chatMemory = new InMemoryChatMemory();
@GetMapping("/generate_stream")
public Flux<ChatResponse> generateStream(HttpServletResponse response, @RequestParam("id") String id,
@RequestParam("prompt") String prompt) {
response.setCharacterEncoding("UTF-8");
var messageChatMemoryAdvisor = new MessageChatMemoryAdvisor(chatMemory, id, 10);
return this.chatClient.prompt(prompt)
.advisors(messageChatMemoryAdvisor)
.stream()
.chatResponse();
}
}
使用webflux实现流式数据传输,配合前端实现打字机效果
文档分隔向量化存储到向量库中
less
@RestController
@RequestMapping("/vectorization")
public class VectorizationController {
@Resource
private VectorStore vectorStore;
@GetMapping("/txt")
private void txt() {
File documentation = new File("D:\rag_knowledge_base_test_data.txt");
TikaDocumentReader tikaDocumentReader = new TikaDocumentReader(new FileSystemResource(documentation));
TokenTextSplitter splitter = new TokenTextSplitter(300, 200, 10, 400, true);
List<Document> documents = splitter.apply(tikaDocumentReader.get());
documents.forEach(document -> {
vectorStore.add(List.of(new Document(document.getFormattedContent())));
});
}
}
前段部分
前端部分使用AI生成一个聊天界面,现在的代码生成能力已经很强了,经过多轮对话对于简单需求已经能达到很好的效果了

MCP服务端
需要引入的依赖
xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>${springai.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-webflux-spring-boot-starter</artifactId>
<version>${springai.version}</version>
</dependency>
服务端配置
yaml
ai:
openai:
api-key: **********
base-url: https://api.deepseek.com
chat:
options:
model: deepseek-chat
temperature: 0.8
embedding:
enabled: false
chat:
observations:
include-completion: true
include-error-logging: true
client:
observations:
include-input: true
mcp:
server:
name: ysj-mcp-server
version: 1.0.0
type: ASYNC
sse-message-endpoint: /mcp/message
应该是不需要spring-ai-openai-spring-boot-starter和大模型的相关配置的,请求大模型相关的在客户端,这里我暂时没有去掉先一起列出来
注册工具
kotlin
@Configuration
public class McpServerConfig {
/**
* 注册工具
*
* @param chatService 工具列表
* @return 工具回调提供者
*/
@Bean
public ToolCallbackProvider chatToolCallbackProvider(ChatService chatService) {
return MethodToolCallbackProvider.builder()
.toolObjects(chatService)
.build();
}
}
自定义MCP服务的工具
typescript
@Service
@Slf4j
public class ChatServiceImpl implements ChatService {
String districtUrl = "https://restapi.amap.com/v3/config/district?subdistrict=0&key=***********=";
String weatherUrl = "https://api.map.baidu.com/weather/v1/";
@Override
@Tool(name = "weatherQueries", description = "查询某个地方最近的天气情况")
public WeatherResponse weatherQueries(@ToolParam(description = "当前地点") String location) {
log.debug("调用天气工具成功,获取到的入参:{}", location);
DistrictResponse districtResponse = getDistrict(location);
if ("1".equals(districtResponse.getStatus())) {
String adCode = districtResponse.getDistricts().getFirst().getAdcode();
return getWeather(adCode);
} else {
return new WeatherResponse().setStatus(-1).setMessage("获取天气异常,天气工具执行失败!");
}
}
/**
* 发送消息到企业微信
* @date: 2025/3/31 下午3:59
* @author ysj
* @param employeeName:
* @return com.ysj.ai.assistant.mcp.entity.response.QwResponse
*/
@Override
@Tool(name = "sendMessageToUser", description = "根据名称发送消息到某个员工的企微")
public QwResponse sendMessageToUser(@ToolParam(description = "员工名称") String employeeName,
@ToolParam(description = "发送消息的内容") String message) {
Map<String,Object> params = Map.of("employeeName", employeeName, "message", message);
HttpResponse httpResponse = HttpUtil.createPost(CommonConstant.MESSAGE_TO_USER_URL)
.addHeaders(CommonUtils.getQwHeaders().toSingleValueMap())
.body(JSONUtil.toJsonStr(params))
.execute();
return JSONUtil.toBean(JSONUtil.parse(httpResponse.body(), JSONConfig.create().setIgnoreCase(true)), QwResponse.class, true);
}
private DistrictResponse getDistrict(String location) {
HttpResponse httpResponse = HttpUtil.createGet(districtUrl+location).execute();
return JSONUtil.toBean(httpResponse.body(), DistrictResponse.class);
}
private WeatherResponse getWeather(String adCode) {
weatherUrl += "?data_type=all&district_id="+adCode+"&ak=*********";
HttpResponse httpResponse = HttpUtil.createGet(weatherUrl).execute();
return JSONUtil.toBean(httpResponse.body(), WeatherResponse.class);
}
}
这里我们实现了2个工具,一个天气查询的工具和一个给企微发送消息的工具
这样mcp客户端就可以连上我们的mcp服务端自动的调度需要调用哪些服务,我们把一个陈旧的系统加上智能化的助手只需要简单几步,大模型会使用Function Calling帮我们设置好接口的请求参数
这里有一点需要注意,spring ai的mcp客户端在使用webflux接入了mcp服务同时又接入了知识库可能会出现 block()/blockFirst()/blockLast() are blocking, which is not supported in thread 这个错误,issues中已经有相关问题并且在M7版本应该解决了,公共仓库中暂时还没有M7版本,应该在不久后就可以下载到,到时候我会再验证一遍