Spring AI 1.x 系列【6】集成 DeepSeek + 智谱 GLM,实现多模型一键切换的 AI 聊天助手

Spring AI 1.x 系列【6】集成 DeepSeek + 智谱 GLM,实现多模型一键切换的 AI 聊天助手

文章目录

1. 项目介绍

从零搭建一套 AI 聊天助手,基于 Spring AI 同时集成 DeepSeek 和 智谱 GLM 两大主流模型,实现前端一键切换模型流式对话等完整功能。

1.1 功能演示

顶部下拉框一键切换 DeepSeek / 智谱GLM 模型:

在这里插入图片描述

支持消息流式输出:

在这里插入图片描述

1.2 技术栈

核心技术栈:

  • 前端Thymeleaf + SSE 流式输出。
  • 后端Spring Boot 3.5.x + Spring AI 1.1.2
  • AI模型DeepSeek Chat、智谱 GLM-4

2. 环境准备

2.1 申请 API Key

DeepSeek:前往 DeepSeek 开放平台 创建 API Key
智谱 AI:前往 智谱开放平台 创建 API Key

2.2 创建工程

工程结构如下:

在这里插入图片描述

2.3 Maven 核心依赖

pom.xml 中引入 Spring AI 相关依赖,同时支持 DeepSeek 和智谱:

<?xml version="1.0" encoding="UTF-8"?><projectxmlns="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 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>com.study</groupId><artifactId>study-spring-ai</artifactId><version>0.0.1-SNAPSHOT</version><relativePath>../pom.xml</relativePath></parent><groupId>com.example</groupId><artifactId>ai-chat-demo</artifactId><version>0.0.1-SNAPSHOT</version><name>ai-chat-demo</name><description>ai-chat-demo</description><properties><java.version>17</java.version><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!--DeepSeek--><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-model-deepseek</artifactId></dependency><!--智谱AI--><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-model-zhipuai</artifactId></dependency><!-- Thymeleaf for web UI --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

3. 后端实现

3.1 配置文件

配置两大模型的 API 信息:

server:port:8081spring:application:name: ai-chat-demo ai:chat:client:enabled:false# DeepSeek 配置deepseek:api-key: 你的 DeepSeek API Key base-url: https://api.deepseek.com model: deepseek-chat # 智谱 GLM 配置zhipu:api-key: 你的 智谱 API Key base-url: https://open.bigmodel.cn/api/paas model: glm-4

3.2 对话客户端配置类

创建两个独立的 ChatClient Bean,分别对应 DeepSeek 和智谱:

@ConfigurationpublicclassChatClientConfig{@Bean("zhiPuAiChatClient")publicChatClientzhiPuAiChatClient(ZhiPuAiChatModel zhiPuAiChatModel){returnChatClient.builder(zhiPuAiChatModel).build();}@Bean("deepSeekChatClient")publicChatClientdeepSeekChatClient(DeepSeekChatModel deepSeekChatModel){returnChatClient.builder(deepSeekChatModel).build();}}

3.3 对话生成访问接口

通过 model 参数动态选择模型,兼容普通接口和流式接口:

importorg.springframework.ai.chat.client.ChatClient;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.beans.factory.annotation.Qualifier;importorg.springframework.http.MediaType;importorg.springframework.stereotype.Controller;importorg.springframework.web.bind.annotation.*;importreactor.core.publisher.Flux;importjava.util.Map;importjava.util.UUID;@ControllerpublicclassChatController{// 注入 DeepSeek 和智谱的 ChatClientprivatefinalChatClient deepSeekChatClient;privatefinalChatClient zhiPuAiChatClient;// 构造方法注入多个 ChatClient(替换原有单一注入)@AutowiredpublicChatController(@Qualifier("deepSeekChatClient")ChatClient deepSeekChatClient,@Qualifier("zhiPuAiChatClient")ChatClient zhiPuAiChatClient){this.deepSeekChatClient = deepSeekChatClient;this.zhiPuAiChatClient = zhiPuAiChatClient;}// 首路由,返回聊天页面@GetMapping("/")publicStringchatPage(){return"chat";}/** * 非流式生成接口(支持模型切换) * * @param message 用户消息 * @param model 模型名称(deepseek/zhipu,默认deepseek) * @return 模型回复 */@GetMapping("/ai/generate")@ResponseBodypublicMap<String,String>generate(@RequestParam(value ="message", defaultValue ="你好")String message,@RequestParam(value ="model", defaultValue ="deepseek")String model){try{// 根据模型名称获取对应的 ChatClientChatClient targetClient =getChatClientByModel(model);String response = targetClient.prompt().user(message).call().content();returnMap.of("generation", response,"usedModel", model);// 新增返回使用的模型,方便前端确认}catch(Exception e){returnMap.of("generation","错误: "+ e.getMessage(),"usedModel", model);}}/** * 流式生成接口(支持模型切换) * * @param message 用户消息 * @param model 模型名称(deepseek/zhipu,默认deepseek) * @return 流式响应 */@GetMapping(value ="/ai/generate/stream", produces =MediaType.TEXT_EVENT_STREAM_VALUE)@ResponseBodypublicFlux<String>generateStream(@RequestParam(value ="message", defaultValue ="你好")String message,@RequestParam(value ="model", defaultValue ="deepseek")String model){try{// 根据模型名称获取对应的 ChatClientChatClient targetClient =getChatClientByModel(model);return targetClient.prompt().user(message).stream().content().onErrorResume(e ->Flux.just("错误: "+ e.getMessage()));}catch(IllegalArgumentException e){// 模型名称错误时返回提示returnFlux.just("错误: "+ e.getMessage());}}/** * 创建新会话,返回新的会话ID * * @return 新会话ID */@GetMapping("/api/conversation/new")@ResponseBodypublicMap<String,String>newConversation(){returnMap.of("conversationId",UUID.randomUUID().toString());}/** * 核心:根据模型名称获取对应的 ChatClient * * @param model 模型名称(deepseek/zhipu) * @return 对应的 ChatClient * @throws IllegalArgumentException 模型不支持时抛出异常 */privateChatClientgetChatClientByModel(String model){returnswitch(model.toLowerCase()){case"deepseek"-> deepSeekChatClient;case"zhipu","glm"-> zhiPuAiChatClient;default->thrownewIllegalArgumentException("不支持的模型:"+ model);};}}

4. 前端页面

resources/templates 目录下创建聊天界面 chat.html ,重点涉及:

  • 接收后端流式响应(SSE/Server-Sent Events)处理,实现实时打字效果。
  • 模型切换下拉框,传递 model 参数。
<!DOCTYPEhtml><htmllang="zh-CN"xmlns:th="http://www.thymeleaf.org"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>AI聊天助手</title><style>*{margin: 0;padding: 0;box-sizing: border-box;}body{font-family:'Arial','Microsoft YaHei', sans-serif;background:linear-gradient(135deg, #f9e7d8 0%, #f5d6b8 50%, #e8c8a0 100%);height: 100vh;overflow: hidden;position: relative;}.chat-container{width: 95%;max-width: 900px;height: 90vh;background:rgba(255, 255, 255, 0.95);border-radius: 20px;box-shadow: 0 10px 30px rgba(222, 184, 135, 0.15);display: flex;flex-direction: column;overflow: hidden;position: relative;margin: 20px auto;border: 2px solid #e6b89c;}.chat-header{background:linear-gradient(90deg, #e69c68 0%, #d98850 100%);color: white;padding: 18px;text-align: center;font-size: 1.6rem;font-weight: 600;position: relative;display: flex;justify-content: space-between;align-items: center;}.chat-header-title{flex: 1;text-align: center;}/* 新增:模型选择下拉框样式 */.model-selector{background:rgba(255, 255, 255, 0.2);border: 1px solid rgba(255, 255, 255, 0.3);color: white;padding: 8px 16px;border-radius: 20px;font-size: 0.9rem;cursor: pointer;transition: all 0.3s ease;margin-left: 10px;outline: none;}.model-selector:hover{background:rgba(255, 255, 255, 0.3);}.model-selector option{background: #d98850;color: white;border: none;}.new-chat-btn{background:rgba(255, 255, 255, 0.2);border: 1px solid rgba(255, 255, 255, 0.3);color: white;padding: 8px 16px;border-radius: 20px;cursor: pointer;font-size: 0.9rem;transition: all 0.3s ease;margin-right: 10px;}.new-chat-btn:hover{background:rgba(255, 255, 255, 0.3);transform:translateY(-2px);}.header-placeholder{width: 80px;}.chat-messages{flex: 1;padding: 20px;overflow-y: auto;background:radial-gradient(circle at 20% 30%,rgba(249, 231, 216, 0.1) 0%, transparent 50%),radial-gradient(circle at 80% 70%,rgba(232, 200, 160, 0.1) 0%, transparent 50%);}.message{margin-bottom: 20px;display: flex;animation: fadeIn 0.5s ease-out;}@keyframes fadeIn{from{opacity: 0;transform:translateY(20px)scale(0.95);}to{opacity: 1;transform:translateY(0)scale(1);}}.user-message{justify-content: flex-end;}.ai-message{justify-content: flex-start;}.message-content{max-width: 75%;padding: 15px 20px;border-radius: 20px;font-size: 1.1rem;line-height: 1.5;position: relative;box-shadow: 0 2px 10px rgba(222, 184, 135, 0.1);border: 2px solid transparent;}.user-message .message-content{background:linear-gradient(135deg, #f0b890 0%, #e69c68 100%);color: white;border-color: #d98850;border-bottom-right-radius: 8px;}.ai-message .message-content{background:linear-gradient(135deg, #faf6f0 0%, #f9e7d8 100%);color: #333;border-color: #e6b89c;border-bottom-left-radius: 8px;}.chat-input{padding: 20px;background: #faf6f0;border-top: 2px solid #e6b89c;display: flex;gap: 15px;position: relative;}.message-input{flex: 1;padding: 15px 20px;border: 2px solid #e6b89c;border-radius: 30px;font-size: 1.1rem;outline: none;background:rgba(255, 255, 255, 0.9);transition: all 0.3s ease;box-shadow: 0 2px 8px rgba(222, 184, 135, 0.1);}.message-input:focus{border-color: #d98850;box-shadow: 0 2px 15px rgba(217, 136, 80, 0.2);transform:scale(1.01);}.send-button{padding: 15px 30px;background:linear-gradient(135deg, #e69c68 0%, #d98850 100%);color: white;border: none;border-radius: 30px;font-size: 1.1rem;font-weight: 600;cursor: pointer;transition: all 0.3s ease;box-shadow: 0 2px 10px rgba(217, 136, 80, 0.2);}.send-button:hover{transform:translateY(-2px);box-shadow: 0 4px 15px rgba(217, 136, 80, 0.3);}.send-button:active{transform:translateY(0);}.send-button::after{content:"→";margin-left: 8px;display: inline-block;transition: transform 0.3s ease;}.send-button:hover::after{transform:translateX(5px);}.send-button:disabled{opacity: 0.6;cursor: not-allowed;transform: none;box-shadow: none;}.typing-indicator{display: none;padding: 15px 20px;background:linear-gradient(135deg, #faf6f0 0%, #f9e7d8 100%);border: 2px solid #e6b89c;border-radius: 20px;border-bottom-left-radius: 8px;margin-bottom: 20px;position: relative;}.typing-indicator.show{display: flex;align-items: center;}.typing-dots{display: flex;align-items: center;gap: 6px;}.typing-dot{width: 10px;height: 10px;background: #d98850;border-radius: 50%;animation: typing 1.4s infinite ease-in-out;}.typing-dot:nth-child(1){animation-delay: 0s;}.typing-dot:nth-child(2){animation-delay: 0.2s;}.typing-dot:nth-child(3){animation-delay: 0.4s;}@keyframes typing{0%, 60%, 100%{transform:translateY(0)scale(1);opacity: 0.7;}30%{transform:translateY(-8px);opacity: 1;}}.welcome-message{text-align: center;color: #555;font-size: 1.2rem;margin: 30px 0;padding: 30px;background:linear-gradient(135deg, #fff 0%, #faf6f0 100%);border-radius: 20px;border: 2px solid #e6b89c;box-shadow: 0 4px 15px rgba(222, 184, 135, 0.1);}.welcome-message h3{color: #d98850;margin-bottom: 15px;font-size: 1.5rem;font-weight: 600;}.welcome-message p{line-height: 1.6;}.chat-messages::-webkit-scrollbar{width: 8px;}.chat-messages::-webkit-scrollbar-track{background:rgba(249, 231, 216, 0.2);border-radius: 4px;}.chat-messages::-webkit-scrollbar-thumb{background:linear-gradient(135deg, #e69c68 0%, #d98850 100%);border-radius: 4px;}.chat-messages::-webkit-scrollbar-thumb:hover{background:linear-gradient(135deg, #d98850 0%, #c87840 100%);}</style></head><body><divclass="chat-container"><divclass="chat-header"><!-- 新增:模型选择下拉框 --><selectclass="model-selector"id="modelSelector"><optionvalue="deepseek">DeepSeek</option><optionvalue="zhipu">智谱GLM</option></select><spanclass="chat-header-title">AI 聊天助手</span><buttonclass="new-chat-btn"id="newChatBtn">新对话</button></div><divclass="chat-messages"id="chatMessages"><divclass="welcome-message"id="welcomeMessage"><h3>欢迎使用AI 聊天助手</h3><p>你可以随时提出问题,我会尽力解答<br> 期待与你愉快交流!</p></div></div><divclass="typing-indicator"id="typingIndicator"><divclass="typing-dots"><divclass="typing-dot"></div><divclass="typing-dot"></div><divclass="typing-dot"></div></div><spanstyle="margin-left: 15px;color: #d98850;font-weight: 600;">正在思考中...</span></div><divclass="chat-input"><inputtype="text"class="message-input"id="messageInput"placeholder="请输入你想说的话..."autocomplete="off"><buttonclass="send-button"id="sendButton">发送</button></div></div><script>const chatMessages = document.getElementById('chatMessages');const messageInput = document.getElementById('messageInput');const sendButton = document.getElementById('sendButton');const typingIndicator = document.getElementById('typingIndicator');const welcomeMessage = document.getElementById('welcomeMessage');const newChatBtn = document.getElementById('newChatBtn');// 新增:获取模型选择器DOMconst modelSelector = document.getElementById('modelSelector');// 当前会话ID(用于记忆功能)let conversationId ='default';// 创建新会话asyncfunctioncreateNewConversation(){try{const response =awaitfetch('/api/conversation/new');const data =await response.json(); conversationId = data.conversationId;// 清空聊天消息 chatMessages.innerHTML ='';// 显示欢迎消息const newWelcome = document.createElement('div'); newWelcome.className ='welcome-message'; newWelcome.id ='welcomeMessage'; newWelcome.innerHTML =` <h3>欢迎使用AI 聊天助手</h3> <p>你可以随时提出问题,我会尽力解答<br> 期待与你愉快交流!</p> `; chatMessages.appendChild(newWelcome); console.log('新会话已创建:', conversationId);}catch(error){ console.error('创建新会话失败:', error);// 即使失败也生成一个本地ID conversationId ='local-'+ Date.now();}}// 发送消息函数(使用流式接口)asyncfunctionsendMessage(){const message = messageInput.value.trim();if(!message)return;// 隐藏欢迎消息const welcomeMsg = document.getElementById('welcomeMessage');if(welcomeMsg){ welcomeMsg.style.display ='none';}// 添加用户消息到聊天界面addMessage(message,'user');// 清空输入框 messageInput.value ='';// 显示正在输入指示器showTypingIndicator();// 禁用发送按钮 sendButton.disabled =true;// 预先创建AI消息容器(用于流式显示)const aiMessageDiv =createAIMessageContainer();try{// 新增:获取选中的模型值const selectedModel = modelSelector.value;// 修改:请求URL中添加model参数const response =awaitfetch(`/ai/generate/stream?message=${encodeURIComponent(message)}&conversationId=${encodeURIComponent(conversationId)}&model=${encodeURIComponent(selectedModel)}`,{method:'GET',headers:{'Accept':'text/event-stream',}});if(!response.ok){thrownewError(`HTTP error! status: ${response.status}`);}// 隐藏正在输入指示器(开始接收数据时隐藏)hideTypingIndicator();// 读取流式响应const reader = response.body.getReader();const decoder =newTextDecoder();let fullResponse ='';while(true){const{ done, value }=await reader.read();if(done)break;// 解码数据块const chunk = decoder.decode(value,{stream:true});// SSE 格式:每行以 "data:" 开头const lines = chunk.split('\n');for(const line of lines){if(line.startsWith('data:')){const data = line.substring(5).trim();if(data){ fullResponse += data;// 更新AI消息内容updateAIMessageContent(aiMessageDiv, fullResponse);}}elseif(line.trim()&&!line.startsWith(':')){// 处理非标准SSE格式(直接返回文本) fullResponse += line;updateAIMessageContent(aiMessageDiv, fullResponse);}}}// 如果没有收到任何内容if(!fullResponse){updateAIMessageContent(aiMessageDiv,'抱歉,没有收到回复。');}}catch(error){// 隐藏正在输入指示器hideTypingIndicator();// 显示错误消息updateAIMessageContent(aiMessageDiv,'抱歉,处理你的请求时出现了错误,请稍后再试。'); console.error('Error:', error);}finally{// 重新启用发送按钮 sendButton.disabled =false; messageInput.focus();}}// 创建AI消息容器(用于流式显示)functioncreateAIMessageContainer(){const messageDiv = document.createElement('div'); messageDiv.className ='message ai-message';const contentDiv = document.createElement('div'); contentDiv.className ='message-content'; contentDiv.textContent =''; messageDiv.appendChild(contentDiv); chatMessages.appendChild(messageDiv);// 滚动到最新消息 chatMessages.scrollTop = chatMessages.scrollHeight;return contentDiv;}// 更新AI消息内容(流式更新)functionupdateAIMessageContent(contentDiv, content){ contentDiv.textContent = content;// 滚动到最新消息 chatMessages.scrollTop = chatMessages.scrollHeight;}// 添加消息到聊天界面(保留用于用户消息)functionaddMessage(content, sender){const messageDiv = document.createElement('div'); messageDiv.className =`message ${sender}-message`;const contentDiv = document.createElement('div'); contentDiv.className ='message-content'; contentDiv.textContent = content; messageDiv.appendChild(contentDiv); chatMessages.appendChild(messageDiv);// 滚动到最新消息 chatMessages.scrollTop = chatMessages.scrollHeight;}// 显示正在输入指示器functionshowTypingIndicator(){ typingIndicator.classList.add('show'); chatMessages.scrollTop = chatMessages.scrollHeight;}// 隐藏输入指示器functionhideTypingIndicator(){ typingIndicator.classList.remove('show');}// 事件监听器 sendButton.addEventListener('click', sendMessage); messageInput.addEventListener('keypress',(e)=>{if(e.key ==='Enter'&&!e.shiftKey){ e.preventDefault();sendMessage();}});// 新对话按钮 newChatBtn.addEventListener('click', createNewConversation);// 页面加载完成后聚焦输入框 document.addEventListener('DOMContentLoaded',()=>{ messageInput.focus();});</script></body></html>

Read more

可以在命令行通过大模型使用上下文协议(MCP)与外部工具交互的软件:小巧的MCPHost

可以在命令行通过大模型使用上下文协议(MCP)与外部工具交互的软件:小巧的MCPHost

小巧的MCPHost MCPHost 可以在命令行下使用,使大型语言模型(LLM)能够通过模型上下文协议(MCP)与外部工具进行交互。目前支持Claude 3.5 Sonnet和Ollama等。本次实践使用自己架设的Deepseek v3模型,跑通了Time MCP服务。  官网:GitHub - mark3labs/mcphost: A CLI host application that enables Large Language Models (LLMs) to interact with external tools through the Model Context Protocol (MCP). 下载安装 使用非常方便,直接下载解压即可使用。官网提供Windows、Linux和MacOS三个系统的压缩包: https://github.com/

By Ne0inhk
实战篇:Python开发monogod数据库mcp server看完你就会了

实战篇:Python开发monogod数据库mcp server看完你就会了

原创不易,请关注公众号:【爬虫与大模型开发】,大模型的应用开发之路,整理了大模型在现在的企业级应用的实操及大家需要注意的一些AI开发的知识点!持续输出爬虫与大模型的相关文章。 前言 目前mcp协议是给deepseek大模型插上工具链的翅膀,让大模型不仅拥有超高的推理和文本生成能力,还能具备执行大脑意识的工具能力! 如何开发一个mcp? mcp是一种协议,指的是模型上下文协议 (Model Context Protocol)。 官方结成的mcp https://github.com/modelcontextprotocol/python-sdk mcp库 pip install mcp from mcp.server.fastmcp import FastMCP 我们先来做一个简单的案例 from mcp.server.fastmcp import FastMCP import requests mcp = FastMCP("spider") @mcp.tool() def crawl(

By Ne0inhk
【大模型实战篇】基于Claude MCP协议的智能体落地示例

【大模型实战篇】基于Claude MCP协议的智能体落地示例

1. 背景         之前我们在《MCP(Model Context Protocol) 大模型智能体第一个开源标准协议》一文中,介绍了MCP的概念,虽然了解了其概念、架构、解决的问题,但还缺少具体的示例,来帮助进一步理解整套MCP框架如何落地。         今天我们基于claude的官方例子--获取天气预报【1】,来理解MCP落地的整条链路。 2. MCP示例         该案例是构建一个简单的MCP天气预报服务器,并将其连接到主机,即Claude for Desktop。从基本设置开始,然后逐步发展到更复杂的使用场景。         大模型虽然能力非常强,但其弊端就是内容是过时的,这里的过时不是说内容很旧,只是表达内容具有非实时性。比如没有获取天气预报和严重天气警报的能力。因此我们将使用MCP来解决这一问题。         构建一个服务器,该服务器提供两个工具:获取警报(get-alerts)和获取预报(get-forecast)。然后,将该服务器连接到MCP主机(在本例中为Claude for Desktop)。         首先我们配置下环

By Ne0inhk
AI Agent新范式:FastGPT+MCP协议实现工具增强型智能体构建

AI Agent新范式:FastGPT+MCP协议实现工具增强型智能体构建

AI Agent新范式:FastGPT+MCP协议实现工具增强型智能体构建 作者:高瑞冬 本文目录 * AI Agent新范式:FastGPT+MCP协议实现工具增强型智能体构建 * 一、MCP协议简介 * 二、创建MCP工具集 * 1. 获取MCP服务地址 * 2. 在FastGPT中创建MCP工具集 * 三、测试MCP工具 * 四、AI模型调用MCP工具 * 1. 调用单个工具 * 2. 调用整个工具集 * 五、私有化部署支持 * 1. 环境准备 * 2. 修改docker-compose.yml文件 * 3. 修改FastGPT配置 * 4. 重启服务 * 六、使用MCP-Proxy集成多个MCP服务 * 1. MCP-Proxy简介 * 2. 安装MCP-Proxy * 3. 配置MCP-Proxy * 4. 将MCP-Proxy与FastGPT集成 * 5. 高级配置

By Ne0inhk