当前位置: 首页 > article >正文

Spring AI 开发本地deepseek对话快速上手笔记

Spring AI

Spring AI是一个旨在推进生成式人工智能应用程序发展的项目,Spring AI的核心目标是提供高度抽象化的组件,作为开发AI应用程序的基础,使得开发者能够以最少的代码改动便捷地交换和优化功能模块‌

在开发之前先得引入大模型,这里选择deepseek

至于导入deepseek,咱这里选用ollama 大模型工具来进行本地化部署和管理

ollama下载与启动

进入ollama官网:Ollama

下载对应版本

直接install

deepseek模型下载

下载deepseek模型,这里选择的是r1:8b版本的,各位可以根据自己的电脑配置进行选择

执行ollama run deepseek-r1:8b

看到显示了success即运行成功

spring依赖引入

         <!-- WebFlux 响应式支持 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId></dependency><!--ollama spring ai依赖--><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-ollama-spring-boot-starter</artifactId><version>1.0.0-M6</version></dependency><!-- Swagger3-knife4j依赖 --><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId><version>4.5.0</version></dependency>

ChatbotController

简单对话直接返回
@Slf4j
@RestController
public class ChatBotController {//注入模型,配置文件中的模型,或者可以在方法中指定模型@Resourceprivate OllamaChatModel model;
@GetMapping("/chat")public String chat(@RequestParam("message") String message){String call = model.call(message);System.out.println(call);return call;}}

启动

postman请求

返回

流式对话响应
@Slf4j
@RestController
public class ChatBotController {
//注入模型,配置文件中的模型,或者可以在方法中指定模型@Resourceprivate OllamaChatModel model;
@Resourceprivate StringRedisTemplate stringRedisTemplate;//引入存储消息服务@Resourceprivate ChatService chatService;@GetMapping(value = "/streamChat", produces = "text/event-stream;charset=UTF-8")public Flux<String> streamChat(@RequestParam("message")String message,@RequestParam String sessionId){Long userId = UserHolder.getUser().getId();return Flux.concat(processAnswering(message, sessionId, userId));}/*** 处理回答阶段*/private Flux<String> processAnswering(String message, String sessionId, Long userId) {return buildPromptWithContext(sessionId, message).flatMapMany(prompt ->model.stream(prompt).index().map(tuple -> {// 第一个消息添加标识if (tuple.getT1() == 0L) {return "[ANSWER] " + tuple.getT2();}return tuple.getT2();})).doOnNext(content -> saveMessage(sessionId, userId, message, content)).delayElements(Duration.ofMillis(30));}/*** 保存消息到Redis和数据库(带事务)*/@Transactionalprotected void saveMessage(String sessionId, Long userId, String question, String answer) {
//        // 保存用户问题
//        ChatContent userMsg = new ChatContent();
//        userMsg.setSessionId(sessionId);
//        userMsg.setMessage(question);
//        chatService.save(userMsg);// 保存AI回答ChatContent aiMsg = new ChatContent();
//        aiMsg.setSessionId(sessionId);aiMsg.setReceiveUserId(userId);aiMsg.setSendUserId(Long.valueOf(sessionId));aiMsg.setMessage(answer);
//        aiMsg.setType("ASSISTANT");chatService.save(aiMsg);// 更新Redis上下文stringRedisTemplate.executePipelined((RedisCallback<Object>) connection -> {connection.lPush((CONTEXT_PREFIX + sessionId).getBytes(),question.getBytes(),answer.getBytes());connection.lTrim((CONTEXT_PREFIX + sessionId).getBytes(), 0, MAX_CONTEXT_LENGTH * 2 - 1);return null;});}
}

请求postman

返回

可以看到返回的数据为流式的

前端引入

UI

      <div class="bot-chat-container"><!-- 聊天消息区域 --><div class="bot-chat-messages" ref="messagesContainer"><div v-for="message in bot_messages" :key="message.id":class="['message', message.sender]"><div class="avatar"><img :src="message.sender === 'user' ? userAvatar : botAvatar" alt="avatar"></div><div class="bubble"><div class="content"  v-html="renderMarkdown(message.content)"></div><!--<div class="content" v-else>{{ message.content }}</div>--><div class="status"><span class="time">{{ message.timestamp }}</span><span v-if="message.loading" class="typing-indicator"><span class="dot"></span><span class="dot"></span><span class="dot"></span></span></div></div></div></div><!-- 输入区域 --><div class="bot-input-area"><textarea v-model="inputMessage"@keydown.enter.exact.prevent="sendMessage"placeholder="输入你的消息..."></textarea><button @click="sendMessage" :disabled="isSending"><span v-if="!isSending">发送</span><span v-else class="sending-indicator"></span></button></div></div>

函数typescript

const sendMessage = async () => {if (!inputMessage.value.trim() || isSending.value) return// 用户消息const userMsg: ChatMessage = {id: Date.now().toString(),content: inputMessage.value.trim(),sender: 'user',timestamp: new Date().toLocaleTimeString()}bot_messages.push(userMsg)// 机器人响应占位const botMsg: ChatMessage = {id: `bot-${Date.now()}`,content: '',sender: 'bot',timestamp: new Date().toLocaleTimeString(),loading: true}bot_messages.push(botMsg)inputMessage.value = ''isSending.value = true// scrollToBottom()try {const sessionId = crypto.randomUUID()// const eventSource = new EventSource(`api/bot/streamChat?message=${encodeURIComponent(userMsg.content)}`)// 发起带有 Authorization 头的流式请求await fetchEventSource(`api/streamChat?message=${encodeURIComponent(userMsg.content)}&sessionId=333`, {method: 'GET',   // 或 POST(需服务端支持)headers: {'Authorization': sessionStorage.getItem("token"),  // 注入认证头 :ml-citation{ref="8" data="citationList"}},onopen(response) {if (response.ok) return;  // 连接成功throw new Error('连接失败');},onmessage(event) {// 处理流式数据(与原 EventSource 逻辑相同)const index = bot_messages.findIndex(m => m.id === botMsg.id)if (index !== -1) {bot_messages[index].content += event.databot_messages[index].loading = falsebot_messages[index].parsedContent = renderMarkdown(bot_messages[index].content)// scrollToBottom()}},onerror(err) {console.error('流式请求异常:', err);}});eventSource.onmessage = (event) => {const index = bot_messages.findIndex(m => m.id === botMsg.id)if (index !== -1) {bot_messages[index].content += event.databot_messages[index].loading = falsebot_messages[index].parsedContent = renderMarkdown(bot_messages[index].content)// scrollToBottom()}}eventSource.onerror = () => {eventSource.close()isSending.value = false}} catch (error) {console.error('Error:', error)isSending.value = false}
}

css

.bot-chat-container {display: flex;flex-direction: column;height: 100vh;background: #f5f5f5;
}.bot-chat-messages {flex: 1;overflow-y: auto;padding: 20px;background: linear-gradient(180deg, #f0f2f5 0%, #ffffff 100%);
}.message {display: flex;margin-bottom: 20px;gap: 12px;
}.message.user {flex-direction: row-reverse;
}.avatar img {width: 40px;height: 40px;border-radius: 50%;box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}.bubble {max-width: 70%;position: relative;
}.bubble .content {padding: 12px 16px;border-radius: 12px;line-height: 1.5;font-size: 14px;
}.message.bot .content {background: white;border: 1px solid #e5e7eb;border-radius: 12px 12px 12px 4px;
}.message.user .content {background: #3875f6;color: white;border-radius: 12px 12px 4px 12px;
}.status {display: flex;align-items: center;gap: 8px;margin-top: 4px;font-size: 12px;color: #666;
}.message.user .status {justify-content: flex-end;
}.typing-indicator {display: inline-flex;gap: 4px;
}.dot {width: 6px;height: 6px;background: #999;border-radius: 50%;animation: bounce 1.4s infinite ease-in-out;
}.dot:nth-child(2) {animation-delay: 0.2s;
}.dot:nth-child(3) {animation-delay: 0.4s;
}@keyframes bounce {0%, 80%, 100% { transform: translateY(0); }40% { transform: translateY(-4px); }
}.bot-input-area {display: flex;gap: 12px;padding: 20px;border-top: 1px solid #e5e7eb;background: white;
}textarea {flex: 1;padding: 12px;border: 1px solid #e5e7eb;border-radius: 8px;resize: none;min-height: 44px;max-height: 120px;font-family: inherit;
}button {padding: 0 20px;background: #3875f6;color: white;border: none;border-radius: 8px;cursor: pointer;transition: opacity 0.2s;
}button:disabled {background: #a0aec0;cursor: not-allowed;
}.sending-indicator {display: inline-block;width: 20px;height: 20px;border: 2px solid #fff;border-top-color: transparent;border-radius: 50%;animation: spin 0.8s linear infinite;
}@keyframes spin {to { transform: rotate(360deg); }
}
.bubble :deep() pre {background: #f8f8f8;padding: 12px;border-radius: 6px;overflow-x: auto;
}.bubble :deep() code {font-family: 'JetBrains Mono', monospace;font-size: 14px;
}.bubble :deep() ul,
.bubble :deep() ol {padding-left: 20px;margin: 8px 0;
}.bubble :deep() blockquote {border-left: 4px solid #ddd;margin: 8px 0;padding-left: 12px;color: #666;
}

发送消息

至此简单的AI对话完成了

源码地址

后端:https://github.com/enjoykanyu/chat_serve

前端:https://github.com/enjoykanyu/kChat_web

觉得不错得话,可以帮点个star呀,感谢

若在执行部署过程中有任何问题,欢迎githup提issue

相关文章:

Spring AI 开发本地deepseek对话快速上手笔记

Spring AI Spring AI是一个旨在推进生成式人工智能应用程序发展的项目&#xff0c;Spring AI的核心目标是提供高度抽象化的组件&#xff0c;作为开发AI应用程序的基础&#xff0c;使得开发者能够以最少的代码改动便捷地交换和优化功能模块‌ 在开发之前先得引入大模型&#xf…...

SpringBoot中的拦截器

SpringBoot中的拦截器 Filter 典型场景 全局鉴权/接口耗时统计 WebFilter("/*") public class CostFilter implements Filter {Overridepublic void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {long start System.currentTimeMill…...

Spark,IDEA编写Maven项目

以下是在IDEA中使用Maven构建Spark项目的步骤&#xff1a; 一、环境准备 1. 安装JDK - 确保IDEA配置了JDK 8&#xff08;推荐11&#xff09;。 2. 安装Maven - 配置Maven环境变量&#xff0c;IDEA中设置Maven路径&#xff08; File > Settings > Build > Maven &#…...

半小时快速入门Spring AI:使用腾讯云编程助手CodeBuddy 开发简易聊天程序

引言 随着人工智能&#xff08;AI&#xff09;技术的飞速发展&#xff0c;越来越多的开发者开始探索如何将AI集成到自己的应用中。人工智能正在迅速改变各行各业的工作方式&#xff0c;从自动化客服到智能推荐系统&#xff0c;AI的应用几乎无处不在。Spring AI作为一种开源框架…...

【PostgreSQL数据分析实战:从数据清洗到可视化全流程】金融风控分析案例-10.3 风险指标可视化监控

&#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 文章大纲 PostgreSQL金融风控分析之风险指标可视化监控实战一、引言二、案例背景三、数据准备&#xff08;一&#xff09;数据来源与字段说明&#xff08;二&#xff09;数据清洗 四、…...

数学复习笔记 6

前言 复习一下行列式的一些基本的题。感觉网课有点没跟上了。今天花点时间跟上网课的进度。要紧跟进度&#xff0c;然后剩下的时间再去复习前面的内容。多复习&#xff0c;提升自己的解题能力。 行列式和矩阵 三年级&#xff0c;我现在是三年级下册。。。马上就要结束大学的…...

微服务的“导航系统”:使用Spring Cloud Eureka实现服务注册与发现

在上一篇中&#xff0c;我们理解了微服务架构的核心理念以及Spring Cloud为我们提供的强大工具集。我们提到&#xff0c;微服务架构的一个核心挑战在于&#xff0c;服务实例的网络位置是动态的&#xff0c;服务之间需要一种机制来互相定位。 想象一下&#xff0c;你开了一家新…...

geoserver发布arcgis瓦片地图服务(最新版本)

第一步&#xff1a;下载geoserver服务&#xff0c;进入bin目录启动 需要提前安装好JDK环境&#xff0c;1.8及以上版本 安装完成&#xff0c;页面访问端口&#xff0c;进入控制台界面,默认用户名密码admin/geoserver 第二步&#xff1a;下载地图 破解版全能电子地图下载器&…...

多边形,矩形,长方体设置

在cesium中,我们可以通过既有的库来进行对地图的构建 // 向场景中添加一个几何体&#xff08;立方体&#xff09; scene.primitives.add(new Cesium.Primitive({// 定义几何体实例geometryInstances: new Cesium.GeometryInstance({// 使用BoxGeometry.fromDimensions方法创建…...

Spring Boot 框架概述

1. 简介 Spring Boot 是由 Pivotal 团队开发的一个用于简化 Spring 应用开发的框架。它通过提供默认配置、嵌入式服务器和自动配置等特性&#xff0c;让开发者能够更快速地构建独立的、生产级别的 Spring 应用。 Spring Boot 的主要特点包括&#xff1a; 快速创建独立的 Spri…...

(C语言)超市管理系统(测试版)(指针)(数据结构)(二进制文件读写)

目录 前言&#xff1a; 源代码&#xff1a; product.h product.c fileio.h fileio.c main.c 代码解析&#xff1a; fileio模块&#xff08;文件&#xff08;二进制&#xff09;&#xff09; 写文件&#xff08;保存&#xff09; 函数功能 代码逐行解析 关键知识点 读文…...

React百日学习计划-Grok3

关键点 研究表明&#xff0c;100天内学习React是可行的&#xff0c;尤其是你已有HTML、JS和CSS基础。该计划包括基础知识、hooks、状态管理、路由、样式化及综合项目&#xff0c;适合初学者。建议每天花2-3小时学习&#xff0c;结合免费教程和社区支持。 开始学习 学习React…...

一文辨析Java基本数据类型与包装类

Java 基本数据类型与包装类深度解析 前言一、Java 基本数据类型详解1.1 数值型1.1.1 整型1.1.2 浮点型 1.2 字符型1.3 布尔型 二、Java 包装类详解2.1 包装类与基本数据类型的对应关系2.2 包装类的常用方法 三、基本数据类型与包装类的转换3.1 装箱&#xff08;Boxing&#xff…...

Java游戏服务器开发流水账(3)游戏数据的缓存简介

简介 游戏服务器数据缓存是一种在游戏服务器运行过程中&#xff0c;用于临时存储经常访问的数据的技术手段&#xff0c;旨在提高游戏性能、降低数据库负载以及优化玩家体验。游戏开发中数据的缓存可以使用Java自身的内存也可以使用MemCache&#xff0c;Redis&#xff0c;注意M…...

SiLM59xx系列:高可靠性隔离驱动架构在新能源与工业电源中的关键设计解析

SiLM59xx系列产品选型&#xff1a; SiLM5932SHOCG-DG SiLM5992SHCG-DG SiLM5991SHCG-DG SiLM5932SHOCG-AQ SiLM5992SHCG-AQ SiLM5991SHCG-AQ 一、高功率密度驱动的核心挑战与解决方案 高压场景下的驱动需求 在新能源汽车主逆变器、光伏逆变器及工业电机控制…...

nRF Connect 下载

官方下载路径 点击&#xff0c;或往下拉 选对应的版本 下载成功&#xff0c;数字代表版本好...

基于Arduino的贪吃蛇游戏机

3D 打印迷你贪吃蛇游戏机&#xff1a; 在数字娱乐高度发达的今天&#xff0c;我们常常怀念那些经典的复古游戏。其中&#xff0c;贪吃蛇游戏无疑是许多人童年的记忆。今天&#xff0c;我将带你走进一个有趣的 DIY 项目——3D 打印迷你贪吃蛇游戏机。这个项目不仅能够让你重温经…...

talk-linux 不同用户之间终端通信

好的&#xff01;下面是一个完整的指南和脚本&#xff0c;用于在两台 Linux 主机上配置并使用 talk 聊天功能&#xff08;假设它们在同一个局域网内&#xff09;。 ⸻ &#x1f9fe; 一、需求说明 我们需要在两台主机上&#xff1a; 1. 安装 talk 和 talkd 2. 启用 talkd 服…...

【PmHub后端篇】Redis分布式锁:保障PmHub流程状态更新的关键

在分布式系统中&#xff0c;确保数据一致性和操作的正确执行是至关重要的。PmHub项目中&#xff0c;通过集成Redis分布式锁来保障流程状态更新&#xff0c;这是一个非常关键的技术点&#xff0c;以下将详细介绍其原理、实现。 1 本地锁的问题 1.1 常见的本地锁 在Java中&…...

MySQL基础入门:MySQL简介与环境搭建

引言 在数字化转型浪潮中&#xff0c;MySQL作为数据存储的"基石引擎"&#xff0c;支撑着从电商交易到金融风控的各类核心业务。其高并发处理能力、灵活的架构设计及跨平台兼容性&#xff0c;使其成为开发者技术栈中的"常青树"。本章节将通过历史溯源、技术…...

力扣-543.二叉树的直径

题目描述 给你一棵二叉树的根节点&#xff0c;返回该树的 直径 。 二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root 。 两节点之间路径的 长度 由它们之间边数表示。 class Solution { public:int maxLength(TreeNode *…...

Starrocks的主键表涉及到的MOR Delete+Insert更新策略

背景 写这个文章的作用主要是做一些总结和梳理&#xff0c;特别是正对大数据场景下的实时写入更新策略 COW 和 MOR 以及 DeleteInsert 的技术策略的演进&#xff0c; 这也适用于其他大数据的计算存储系统。该文章主要参考了Primary Key table. 分析总结 Starrocks 的主键表主…...

《操作系统真象还原》第十四章(2)——文件描述符、文件操作基础函数

文章目录 前言文件描述符简介文件描述符原理文件描述符实现修改thread.h修改thread.c 文件操作相关的基础函数inode操作相关函数文件相关函数编写file.h编写file.c 目录相关函数完善fs/dir.h编写fs/dir.c 路径解析相关函数实现文件检索功能修改fs.h继续完善fs.c makefile 结语 …...

EMQX v5.0通过连接器和规则同步数据

1 概述 EMQX数据集成功能&#xff0c;帮助用户将所有的业务数据无需额外编写代码即可快速完成处理与分发。 数据集成能力由连接器和规则两部分组成&#xff0c;用户可以使用数据桥接或 MQTT 主题来接入数据&#xff0c;使用规则处理数据后&#xff0c;再通过数据桥接将数据发…...

2. 盒模型/布局模块 - 响应式产品展示页_案例:电商产品网格布局

2. 盒模型/布局模块 - 响应式产品展示页 案例&#xff1a;电商产品网格布局 <!DOCTYPE html> <html><head><meta charset"utf-8"><title></title></head><style type"text/css">:root {--primary-color…...

LVGL的三层屏幕结构

文章目录 &#x1f31f; LVGL 的三层屏幕架构1. **Top Layer&#xff08;顶层&#xff09;**2. **System Layer&#xff08;系统层&#xff09;**3. **Active Screen&#xff08;当前屏幕层&#xff09;** &#x1f9e0; 总结对比&#x1f50d; 整体作用✅ 普通屏幕层对象&…...

【PDF】使用Adobe Acrobat dc添加水印和加密

【PDF】使用Adobe Acrobat dc添加水印和加密 文章目录 [TOC](文章目录) 前言一、添加保护加密口令二、添加水印三、实验四、参考文章总结 实验工具&#xff1a; 1.Adobe Acrobat dc 前言 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、添加保护加…...

AI 搜索引擎 MindSearch

背景 RAG是一种利用文档减少大模型的幻觉&#xff0c;AI搜索也是 AI 搜索引擎 MindSearch 是一个开源的 AI 搜索引擎框架&#xff0c;具有与 Perplexity.ai Pro 相同的性能。您可以轻松部署它来构建您自己的搜索引擎&#xff0c;可以使用闭源 LLM&#xff08;如 GPT、Claude…...

Windows下安装mysql8.0

一、下载安装离线安装包 &#xff08;下载过了&#xff0c;可以跳过&#xff09; 下载网站&#xff1a;MySQL :: Download MySQL Installerhttps://dev.mysql.com/downloads/installer/ 二、安装mysql 三、安装完成验证...

【android bluetooth 框架分析 02】【Module详解 7】【VendorSpecificEventManager 模块介绍】

1. 背景 我们在 gd_shim_module 介绍章节中&#xff0c;看到 我们将 VendorSpecificEventManager 模块加入到了 modules 中。 // system/main/shim/stack.cc modules.add<hci::VendorSpecificEventManager>();在 ModuleRegistry::Start 函数中我们对 加入的所有 module…...