掘金 人工智能 8小时前
spring ai 适配 流式回答、mcp、milvus向量数据库、rag、聊天会话记忆
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文详细介绍了如何使用 Spring AI 框架,结合 Milvus 向量数据库和 Ollama 模型,构建一个功能强大的聊天机器人。文章涵盖了项目依赖配置、Milvus 和 Ollama 的集成、向量数据库的配置与使用,以及通过 API 实现流式回答的聊天客户端开发。通过具体的代码示例和配置说明,帮助开发者快速理解和实践 Spring AI 在构建智能应用中的应用,特别是 RAG(检索增强生成)和会话记忆的管理。

📦 **项目结构与依赖管理:** 文章首先展示了 Spring AI 项目的 Maven POM 文件,包括父项目 `mcp-demo` 和子项目 `spring-ai-mcp-client-demo`。`mcp-demo` 负责管理项目依赖和模块,定义了 Spring Boot、Spring AI、Milvus、Ollama 等核心组件的版本。`spring-ai-mcp-client-demo` 则引入了 `spring-boot-starter-web`、`spring-ai-starter-mcp-client`、`spring-ai-starter-model-ollama`、`spring-ai-starter-vector-store-milvus` 等关键依赖,为构建聊天机器人奠定基础。

⚙️ **核心组件配置:** 文章详细阐述了 `application.yml` 文件中的配置项,包括服务器端口、数据源(MySQL)、Milvus 向量数据库连接信息(host, port, dimension, index type, metric type)、Ollama 模型配置(base-url, chat options, embedding options)以及 MCP 客户端配置。这些配置项是实现 Milvus 和 Ollama 集成的关键。

💡 **RAG 与会话记忆配置:** 通过 `AdvisorConfig` 类,配置了 `RetrievalAugmentationAdvisor`,用于实现 RAG 功能,结合 Milvus 向量数据库进行文档检索和查询增强。`ChatClientConfig` 类则负责配置会话记忆,支持 JDBC 存储,并集成了 MySQL 方言,确保了聊天过程中的上下文连贯性。`QuestionAnswerAdvisor` 也被用于处理问答场景。

🚀 **Milvus 向量数据库集成:** `MilvusVectorClientConfig` 类提供了手动配置 Milvus 向量数据库的 Bean。其中,`milvusClient` Bean 用于建立与 Milvus 服务器的连接,而 `vectorStore` Bean 则负责创建和初始化 Milvus 向量存储,包括设置集合名称、数据库名称、索引类型、度量类型、批处理策略以及 embedding 维度。特别地,在初始化时会尝试删除同名集合以清除缓存,避免因 dimension 不匹配导致的错误。

💬 **流式聊天客户端实现:** `ChatController` 类展示了如何通过 `/ai/steamChat` 接口实现流式回答。它接收用户输入,加载预定义的系统提示词模板,构建 `Prompt` 对象,并利用 `ChatClient` 的 `stream()` 方法返回 `Flux`,实现实时的聊天交互体验。

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


添加依赖

<?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 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配置

server:  port: 9999  servlet:    encoding:      charset: UTF-8spring:  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。

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

配置

rag配置:

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 */@Configurationpublic 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();    }}

聊天会话客户端配置:

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 */@Configurationpublic 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也会报错

向量数据库配置

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 */@Configurationpublic 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());    }}

通过接口流式回答:

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();    }}

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

Spring AI Milvus Ollama 向量数据库 RAG 聊天机器人
相关文章