SpringAI 使用通义千问进行聊天对话开发
文章目录
- 前言
- 一、正文
- 1.1 项目结构
- 1.2 项目环境
- 1.3 完整代码
- 1.3.1 spring-ai-demo的pom文件
- 1.3.2 spring-ai-chat-server 的pom文件
- 1.3.3 ChatConfig
- 1.3.4 WebfluxConfig
- 1.3.5 ChatController
- 1.3.6 MainApplication
- 1.4 完整配置
- 1.4.1 application.yaml
- 1.5 调用效果
- 二、附录
- 2.1 参考文档
- 2.2 建议
前言
最近学习了下使用 Spring AI 进行和大模型的聊天对话,其中涉及到对话记忆,向量数据库,RAG 检索增强。
全部代码已经提交到代码仓库中,具体的可以去gitte中看看。
一、正文
1.1 项目结构
项目继承于 spring-ai-demo项目,父级主要控制依赖版本,子模块用于具体的功能实现。
1.2 项目环境
本次实践会使用到向量数据库 Qdrant,需要在 Qdrant下载地址 中下载,并安装启动。
另外,对外提供了聊天接口,向量数据库新增和清空文档接口,获取文档接口等。
java 版本选择21,springboot版本是3.4.2
1.3 完整代码
1.3.1 spring-ai-demo的pom文件
<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>org.pine</groupId><artifactId>spring-ai-demo</artifactId><version>1.0-SNAPSHOT</version><packaging>pom</packaging><name>spring-ai-demo</name><url>http://maven.apache.org</url><modules><module>dynamic-datasource-mcp-sse-server</module><module>dynamic-datasource-mcp-stdio-server</module><module>spring-ai-chat-server</module></modules><properties><java.version>21</java.version><spring-boot.version>3.4.2</spring-boot.version><spring-ai.version>1.0.0</spring-ai.version><spring-ai-alibaba.version>1.0.0.1</spring-ai-alibaba.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding></properties><dependencyManagement><dependencies><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>com.alibaba.cloud.ai</groupId><artifactId>spring-ai-alibaba-starter-dashscope</artifactId><version>${spring-ai-alibaba.version}</version></dependency></dependencies></dependencyManagement></project>
1.3.2 spring-ai-chat-server 的pom文件
<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>org.pine</groupId><artifactId>spring-ai-demo</artifactId><version>1.0-SNAPSHOT</version></parent><artifactId>spring-ai-chat-server</artifactId><packaging>jar</packaging><name>spring-ai-chat-server</name><url>http://maven.apache.org</url><dependencies><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.34</version><optional>true</optional></dependency><dependency><groupId>io.grpc</groupId><artifactId>grpc-api</artifactId><version>1.65.1</version></dependency><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-mcp-server-webflux</artifactId></dependency><!-- 阿里ai的starter --><dependency><groupId>com.alibaba.cloud.ai</groupId><artifactId>spring-ai-alibaba-starter-dashscope</artifactId></dependency><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-vector-store-qdrant</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><inherited>true</inherited><configuration><source>${java.version}</source><target>${java.version}</target><parameters>true</parameters><showWarnings>true</showWarnings></configuration></plugin><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>${spring-boot.version}</version><configuration><mainClass>org.feng.MainApplication</mainClass></configuration><executions><execution><id>repackage</id><goals><goal>repackage</goal></goals></execution></executions></plugin></plugins></build>
</project>
1.3.3 ChatConfig
package org.feng.config;import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingModel;
import io.qdrant.client.QdrantClient;
import lombok.SneakyThrows;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.ai.vectorstore.qdrant.QdrantVectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** 聊天配置** @author pine* @version v1.0* @since 2025-08-12 20:22*/
@Configuration
public class ChatConfig {/*** 配置ChatClient,注册系统指令和工具函数*/@Bean@SneakyThrowspublic ChatClient chatClient(ChatClient.Builder builder) {return builder.defaultSystem("""你是一个资深的java专家,请在开发中遵循如下规则:- 严格遵循 SOLID、DRY、KISS、YAGNI 原则- 遵循 OWASP 安全最佳实践(如输入验证、SQL注入防护)- 采用 分层架构设计,确保职责分离- 代码变更需通过 单元测试覆盖(测试覆盖率 ≥ 80%)""").build();}/*** 内存聊天记忆*/@Beanpublic MessageWindowChatMemory chatMemory() {return MessageWindowChatMemory.builder().maxMessages(100).build();}/*** 向量数据库 qdrant*/@Beanpublic VectorStore vectorStore(@Autowired QdrantClient qdrantClient, @Autowired DashScopeEmbeddingModel dashScopeEmbeddingModel) {return QdrantVectorStore.builder(qdrantClient, dashScopeEmbeddingModel).build();}
}
1.3.4 WebfluxConfig
package org.feng.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestClient;/*** webflux配置** @author pine* @version v1.0* @since 2025-08-12 20:27*/
@Configuration
public class WebfluxConfig {@Beanpublic RestClient.Builder restClientBuilder() {return RestClient.builder();}
}
1.3.5 ChatController
package org.feng.controller;import jakarta.annotation.Resource;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.document.Document;
import org.springframework.ai.rag.Query;
import org.springframework.ai.rag.advisor.RetrievalAugmentationAdvisor;
import org.springframework.ai.rag.generation.augmentation.ContextualQueryAugmenter;
import org.springframework.ai.rag.postretrieval.document.DocumentPostProcessor;
import org.springframework.ai.rag.preretrieval.query.expansion.MultiQueryExpander;
import org.springframework.ai.rag.retrieval.join.ConcatenationDocumentJoiner;
import org.springframework.ai.rag.retrieval.search.VectorStoreDocumentRetriever;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;/*** 聊天控制器** @author pine* @version v1.0* @since 2025-08-12 20:28*/
@Controller
@Slf4j
@RequestMapping("/api")
public class ChatController {@Resourceprivate ChatClient chatClient;@Resourceprivate ChatMemory chatMemory;@Resourceprivate VectorStore vectorStore;private final ChatClient.Builder chatBuilder;public ChatController(ChatClient.Builder chatBuilder) {this.chatBuilder = chatBuilder;}/*** 聊天*/@GetMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)@SneakyThrows@ResponseBodypublic Flux<String> chat(@RequestParam(name="conversationId", required = false, defaultValue = "feng123") String conversationId, @RequestParam(name="message", defaultValue = "你好,你有什么功能") String message) {log.info("chat(), conversationId:{},message:{}", conversationId, message);// 查询扩展MultiQueryExpander queryExpander = MultiQueryExpander.builder()// 扩展数量:3.numberOfQueries(3).chatClientBuilder(chatBuilder)// 包括源查询条件.includeOriginal(true).build();// 增加上下文的信息ContextualQueryAugmenter contextualQueryAugmenter = ContextualQueryAugmenter.builder().allowEmptyContext(true).build();// RAGRetrievalAugmentationAdvisor retrievalAugmentationAdvisor = RetrievalAugmentationAdvisor.builder()// 使用向量数据库作为文档源.documentRetriever(VectorStoreDocumentRetriever.builder().vectorStore(vectorStore).build())// 扩充查询条件.queryExpander(queryExpander)// 拼接查询到的文档.documentJoiner(new ConcatenationDocumentJoiner())// 取多个doc中的第一个.documentPostProcessors(new SelectedFirstDocumentPostProcessor())// 对生成的查询增强上下文.queryAugmenter(contextualQueryAugmenter).build();return chatClient.prompt(message)// 日志.advisors(new SimpleLoggerAdvisor())// 聊天记忆(暂时使用内存,后续支持jdbc).advisors(MessageChatMemoryAdvisor.builder(chatMemory).conversationId(conversationId).build())// RAG.advisors(retrievalAugmentationAdvisor).stream().content();}/*** 聊天记录*/@GetMapping(value = "/messages")@SneakyThrows@ResponseBodypublic List<Message> messages(@RequestParam(name="conversationId", defaultValue = "feng123") String conversationId) {return chatMemory.get(conversationId);}/*** 清空聊天记录*/@GetMapping(value = "/clearMessages")@SneakyThrows@ResponseBodypublic String clearMessages(@RequestParam(name="conversationId", defaultValue = "feng123") String conversationId) {chatMemory.clear(conversationId);return "success";}/*** 向量数据库增加数据*/@ResponseBody@SneakyThrows@PostMapping(value = "/addDocuments")public String addDocuments(@RequestParam(name = "documents") List<String> documents) {List<Document> collected = documents.stream().map(Document::new).collect(Collectors.toList());vectorStore.add(collected);return "success";}@SuppressWarnings("all")private static class SelectedFirstDocumentPostProcessor implements DocumentPostProcessor {@Overridepublic List<Document> process(Query query, List<Document> documents) {if (documents.isEmpty()) {return Collections.emptyList();}return Collections.singletonList(documents.get(0));}}
}
1.3.6 MainApplication
package org.feng;import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.web.bind.annotation.CrossOrigin;
/*** 启动类** @author pine* @version v1.0* @since 2025-08-12 20:23*/
@SpringBootApplication
@CrossOrigin(origins = "*",allowedHeaders = "*",exposedHeaders = {"Cache-Control", "Connection"} // 暴露必要头
)
@ConfigurationPropertiesScan
public class MainApplication {public static void main(String[] args) {SpringApplication springApplication = new SpringApplication(MainApplication.class);springApplication.setWebApplicationType(WebApplicationType.REACTIVE);springApplication.run(args);}
}
1.4 完整配置
1.4.1 application.yaml
server:port: 8181tomcat:uri-encoding: UTF-8keep-alive-timeout: 30000max-connections: 100servlet:encoding:charset: UTF-8force: trueenabled: truecompression:enabled: false # 禁用压缩(否则流式数据可能被缓冲)spring:main:allow-bean-definition-overriding: trueweb-application-type: reactivebanner-mode: consoleapplication:name: spring-ai-chat-demoai:# 配置阿里的密钥,模型dashscope:api-key: sk#你自己的keychat:options:model: qwen-plus# 向量数据库vectorstore:qdrant:host: localhostport: 6334collection-name: vector-storeuse-tls: falseinitialize-schema: true# 日志增强
logging:level:org:springframework:ai:chat:client:advisor: DEBUG
1.5 调用效果
使用get请求,进行聊天对话。
二、附录
2.1 参考文档
- https://docs.spring.io/spring-ai/reference/api/chatclient.html
- https://docs.spring.io/spring-ai/reference/api/retrieval-augmented-generation.html
- https://docs.spring.io/spring-ai/reference/api/vectordbs/qdrant.html
- https://springdoc.cn/spring-ai/index.html
2.2 建议
https://element-plus-x.com/zh/
前端页面可以采取 element-plus-x 来实现。