spring ai 适配 流式回答、mcp、milvus向量数据库、rag、聊天会话记忆

项目结构,本文章只列出了mcp-client的示例代码


添加依赖

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.kujie.ai</groupId>
  <artifactId>mcp-demo</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>pom</packaging>
  <modules>
    <module>spring-ai-mcp-server-demo</module>
    <module>spring-ai-mcp-client-demo</module>
  </modules>

  <properties>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <spring.boot.version>3.5.0</spring.boot.version>
    <spring.ai.version>1.1.0-SNAPSHOT</spring.ai.version>
    <spring-ai-open-ai.version>1.1.0-SNAPSHOT</spring-ai-open-ai.version>
    <spring-ai-milvus-store.version>1.0.0-SNAPSHOT</spring-ai-milvus-store.version>
    <mysql.version>8.0.32</mysql.version>
  </properties>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <!-- 版本与spring-boot-starter-web保持一致 -->
        <version>${spring.boot.version}</version>
      </dependency>

      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>${spring.boot.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      
      <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-bom</artifactId>
        <version>${spring.ai.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>

      <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-milvus-store</artifactId>
        <version>${spring-ai-milvus-store.version}</version>
      </dependency>
      
      <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>${hutool.version}</version>
      </dependency>

      <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>${mysql.version}</version>
      </dependency>
    </dependencies>
  </dependencyManagement>

</project>
xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.kujie.ai</groupId>
        <artifactId>mcp-demo</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>spring-ai-mcp-client-demo</artifactId>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-mcp-client</artifactId>
        </dependency>

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

        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-model-chat-memory-repository-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-vector-store-milvus</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-advisors-vector-store</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-rag</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-web</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
    </dependencies>
</project>

yml配置

yaml 复制代码
server:
  port: 9999
  servlet:
    encoding:
      charset: UTF-8
spring:
  servlet:
    multipart:
      max-file-size: 10MB      # 单个文件最大大小
      max-request-size: 50MB   # 整个请求最大大小
  datasource:
    url: jdbc:mysql://localhost:3306/ck_test?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&allowMultiQueries=true&tinyInt1isBit=false&allowLoadLocalInfile=true&allowLocalInfile=true&allowUrl
    username: root
    password: password
    driver-class-name: com.mysql.cj.jdbc.Driver

  application:
    name: spring-ai-mcp-client-demo
  ai:
    vectorstore:
      milvus:
        client:
            host: 你的milvus服务器ip
            port: 19530
            username:
            password:
            connectTimeoutMs: 30000  # 30 秒
            keepAliveTimeMs: 60000   # 60 秒
        databaseName: "default"
        collectionName: "default"
        embeddingDimension: 1024
        indexType: IVF_FLAT
        metricType: COSINE
    ollama:
      base-url: http://你的ollama服务器ip:11434
      chat:
        options:
          model: qwen3:8b
          f16-k-v: true
          temperature: 0.7
      # embedding模型dimension需要与向量数据库配置的dimension一致不然会报错。
      # 如果报错则看下面的EmbeddingClientConfig代码
      embedding:
        options:
          model: mxbai-embed-large:latest
          f16-k-v: true
          temperature: 0.7
    mcp:
      client:
        type: ASYNC
        sse:
          connections:
            server1:
              url: http://你的mcp服务ip:9998
              sse-endpoint: /sse
        stdio:
          # 第三方提供的mcp服务比如:高德地图等等
          servers-configuration: classpath:mcp-servers-config.json
    chat:
      memory:
        repository:
          jdbc:
            initialize-schema: always
            # spring ai会话记忆自动创建数据库表,详细的可以参照源码JdbcChatMemoryRepositoryRuntimeHints类
            schema: classpath:org/springframework/ai/chat/memory/repository/jdbc/schema-mariadb.sql

聊天会话记录参考下面两张图:

mcp-servers-config.json参考下面:

使用时请注意,我这里实例是高德的mcp服务,调用之前需要在环境变量中配置AMAP_MAPS_API_KEY,这个值可以去高德开放平台申请。还需要安装npx。

perl 复制代码
{
  "mcpServers": {
    "amap-maps": {
      "command": "cmd",
      "args": [
        "/c",
        "npx",
        "-y",
        "@amap/amap-maps-mcp-server"
      ],
      "env": {}
    }
  }
}

配置

rag配置:

kotlin 复制代码
package com.kujie.config;

import org.springframework.ai.chat.client.advisor.api.Advisor;
import org.springframework.ai.rag.advisor.RetrievalAugmentationAdvisor;
import org.springframework.ai.rag.generation.augmentation.ContextualQueryAugmenter;
import org.springframework.ai.rag.retrieval.search.VectorStoreDocumentRetriever;
import org.springframework.ai.vectorstore.milvus.MilvusVectorStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * rag配置
 * 
 * @author check
 * @date 2025-07-26
 */
@Configuration
public class AdvisorConfig {

    @Bean
    public Advisor advisorAdvisor(MilvusVectorStore milvusVectorStore) {
        return RetrievalAugmentationAdvisor.builder()
        .documentRetriever(VectorStoreDocumentRetriever.builder()
                           .similarityThreshold(0.50)
                           .vectorStore(milvusVectorStore)
                           .build())
        .queryAugmenter(ContextualQueryAugmenter.builder()
                        .allowEmptyContext(true)
                        .build())
        .build();
    }
}

聊天会话客户端配置:

java 复制代码
package com.kujie.config;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.api.Advisor;
import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.ChatMemoryRepository;
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.ai.chat.memory.repository.jdbc.JdbcChatMemoryRepository;
import org.springframework.ai.chat.memory.repository.jdbc.MysqlChatMemoryRepositoryDialect;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.vectorstore.milvus.MilvusVectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.JdbcTransactionManager;

import javax.sql.DataSource;
import java.util.List;

/**
 * 客户端 配置
 *
 * @author check
 * @date 2025-07-24
 */
@Configuration
public class ChatClientConfig {
    @Autowired
    private DataSource dataSource;

    @Autowired
    private ChatMemoryRepository chatMemoryRepository;

    @Bean
    public ChatMemory mysqlChatMemoryRepository() {
        // 会话记忆数据库配置,会话记忆有两种一种是jdbc会话记忆一种直接存在内存中
        // 我的配置是jdbc
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        ChatMemoryRepository chatMemoryRepository = JdbcChatMemoryRepository.builder()
        .jdbcTemplate(jdbcTemplate)
        .dialect(new MysqlChatMemoryRepositoryDialect())
        .transactionManager(new JdbcTransactionManager(dataSource))
        .build();

        return MessageWindowChatMemory.builder()
        .chatMemoryRepository(chatMemoryRepository)
        .maxMessages(10)
        .build();
    }

    @Bean
    public ChatClient buildCharClient(ChatClient.Builder ollamaChatModel,
                                      ToolCallbackProvider tools,
                                      ChatMemory chatMemory,
                                      MilvusVectorStore milvusVectorStore,
                                      Advisor advisor) {
        return ollamaChatModel
        .defaultToolCallbacks(tools.getToolCallbacks())
        .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build(), new QuestionAnswerAdvisor(milvusVectorStore), advisor)
        .build();
    }
}

milvus向量数据库在连接时会做一层缓存,参考源码AbstractMilvusGrpcClient类

如果第一次向向量数据库中存储数据时报错,则需要清除对应的collectionName和databaseName的缓存不然dimension就算修改了使用同样的collectionName和databaseName也会报错

向量数据库配置

less 复制代码
package com.kujie.config;

import io.milvus.client.MilvusServiceClient;
import io.milvus.param.*;
import io.milvus.param.collection.DropCollectionParam;
import org.springframework.ai.embedding.TokenCountBatchingStrategy;
import org.springframework.ai.ollama.OllamaEmbeddingModel;
import org.springframework.ai.vectorstore.milvus.MilvusVectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 手动配置向量数据库,在yaml文件中配置了就不需要配置这个了
 *
 * @author check
 * @date 2025-07-25
 */
@Configuration
public class MilvusVectorClientConfig {
    @Bean
    public MilvusVectorStore vectorStore(@Autowired MilvusServiceClient milvusClient, @Autowired OllamaEmbeddingModel ollamaEmbeddingModel) {
        // 清除缓存
        R<RpcStatus> rpcStatusR = milvusClient.dropCollection(DropCollectionParam.newBuilder().withCollectionName("default").withDatabaseName("default").build());

        return MilvusVectorStore.builder(milvusClient, ollamaEmbeddingModel)
        .collectionName("default")
        .databaseName("default")
        .indexType(IndexType.IVF_FLAT)
        .metricType(MetricType.COSINE)
        .batchingStrategy(new TokenCountBatchingStrategy())
        .initializeSchema(true)
        .embeddingDimension(1024)
        .build();
    }

    @Bean
    public MilvusServiceClient milvusClient() {
        return new MilvusServiceClient(ConnectParam.newBuilder().withHost("你的milvus服务器ip").withPort(19530).build());
    }
}

通过接口流式回答:

java 复制代码
package com.kujie.controller;

import com.kujie.utils.TimeUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.SystemPromptTemplate;
import org.springframework.ai.ollama.api.OllamaOptions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.MediaType;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.TextStyle;
import java.time.temporal.WeekFields;
import java.util.List;
import java.util.Locale;
import java.util.Map;

@Slf4j
@RestController
@RequestMapping("/ai")
@CrossOrigin(origins = "*")
public class ChatController {

    @Autowired
    private ChatClient chatClient;

    @GetMapping(value = "/steamChat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> steamChat(@RequestParam("input") String input) {
        String systemPrompt = null;
        try {
            // 这里读取的是我的提示词模板
            ClassPathResource resource = new ClassPathResource("template/system-prompt.txt");
            systemPrompt = StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8);
        } catch (IOException e) {
            log.error("提示词提取失败", e);
        }

        SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemPrompt);
        String systemMessage = systemPromptTemplate.render(Map.of("test","test");

        Prompt prompt = new Prompt(
            List.of(
                new SystemMessage(systemMessage),
                new UserMessage(input)
            ),
            OllamaOptions.builder().build()
        );

        // 流式回答
        return this.chatClient.prompt(prompt)
        .stream()
        .content();
    }
}
相关推荐
想要成为计算机高手6 分钟前
11. isaacsim4.2教程-Transform 树与Odometry
人工智能·机器人·自动驾驶·ros·rviz·isaac sim·仿真环境
静心问道1 小时前
InstructBLIP:通过指令微调迈向通用视觉-语言模型
人工智能·多模态·ai技术应用
宇称不守恒4.01 小时前
2025暑期—06神经网络-常见网络2
网络·人工智能·神经网络
小楓12012 小时前
醫護行業在未來會被AI淘汰嗎?
人工智能·醫療·護理·職業
数据与人工智能律师2 小时前
数字迷雾中的安全锚点:解码匿名化与假名化的法律边界与商业价值
大数据·网络·人工智能·云计算·区块链
chenchihwen2 小时前
大模型应用班-第2课 DeepSeek使用与提示词工程课程重点 学习ollama 安装 用deepseek-r1:1.5b 分析PDF 内容
人工智能·学习
说私域2 小时前
公域流量向私域流量转化策略研究——基于开源AI智能客服、AI智能名片与S2B2C商城小程序的融合应用
人工智能·小程序
Java樱木2 小时前
AI 编程工具 Trae 重要的升级。。。
人工智能
凪卄12133 小时前
图像预处理 二
人工智能·python·深度学习·计算机视觉·pycharm
AI赋能3 小时前
自动驾驶训练-tub详解
人工智能·深度学习·自动驾驶