基于 SpringAI 整合 DeepSeek 模型实现 AI 聊天对话

目录

1、Ollama 的下载配置 与 DeepSeek 的本地部署流程

1.1 下载安装 Ollama

1.2 搜索模型并进行本地部署

2、基于 SpringAI 调用 Ollama 模型

2.1 基于OpenAI 的接口规范(其他模型基本遵循)

2.2 在 IDEA 中进行创建 SpringAI 项目并调用 DS 模型

3、基于 SpringAI 实现会话记忆功能

4、基于 SpringAI 实现会话历史功能

需求一:需要进行展示历史会话的列表信息

需求二:点击会话列表,展示对应的历史会话记录信息


📜后端代码地址:

MySpringAI: 基于 SpringAI + 大模型 实现基本的对话功能(只提供后端)https://gitee.com/MIMIDeK/my-spring-ai.git

1、Ollama 的下载配置 与 DeepSeek 的本地部署流程

很多云平台都提供了一键部署大模型的功能,这里不再赘述(阿里云百炼平台、火山方舟-火山引擎等)


官网地址如下:

Ollamahttps://ollama.com/

1.1 下载安装 Ollama

首先,我们需要下载一个Ollama的客户端,在官网提供了各种不同版本的Ollama,大家可以根据自己的需要下载

注意:

Ollama默认安装目录是C盘的用户目录,如果不希望安装在C盘的话(其实C盘如果足够大放C盘也没事),就不能直接双击安装了,需要通过命令行安装

OllamaSetup.exe 所在目录打开cmd命令行,然后命令如下:

OllamaSetup.exe /DIR=你要安装的目录位置

安装完成后,需要进行环境变量的配置:

OLLAMA_MODELS=你想要保存模型的目录

1.2 搜索模型并进行本地部署

Ollama 是一个模型管理工具和平台,它提供了很多国内外常见的模型,我们可以在其官网上搜索自己需要的模型,这里我们部署的是 DeepSeek R1 模型(我选择的是 7b,根据需求进行选择):

其中,Ollama 的命令很像 Docker 的,选择好对应的模型后,在控制台中进行运行命令:

ollama run deepseek-r1:7b

首次下载的话会有些慢,耐性等待即可

以下是本地下载部署完成后,进行对话的场景,ctrl + d 进行退出与 DeepSeek 的对话:


2、基于 SpringAI 调用 Ollama 模型

2.1 基于OpenAI 的接口规范(其他模型基本遵循)

目前大多数大模型都遵循 OpenAI 的接口规范,是基于Http协议的接口;因此请求路径、参数、返回值信息都是类似的,可能会有一些小的差别,具体需要查看大模型的官方API文档

以下是基于 Python 的代码示范:

# Please install OpenAI SDK first: `pip3 install openai`from openai import OpenAI# 1.初始化OpenAI客户端,要指定两个参数:api_key、base_url
client = OpenAI(api_key="<DeepSeek API Key>", base_url="https://api.deepseek.com")# 2.发送http请求到大模型,参数比较多
response = client.chat.completions.create(model="deepseek-chat", # 2.1.选择要访问的模型messages=[ # 2.2.发送给大模型的消息{"role": "system", "content": "You are a helpful assistant"},{"role": "user", "content": "Hello"},],stream=False # 2.3.是否以流式返回结果
)print(response.choices[0].message.content)

接口说明

  • 请求方式):通常是POST,因为要传递JSON风格的参数
  • 请求路径):与平台有关
    • DeepSeek官方平台:https://api.deepseek.com
    • 阿里云百炼平台:https://dashscope.aliyuncs.com/compatible-mode/v1
    • 本地ollama部署的模型:http://localhost:11434
  • 安全校验):开放平台 都需要提供 API_KEY 来校验权限,本地 ollama 则不需要
  • 请求参数)(这里只列举常用的):
    • model:要访问的模型名称
    • messages:发送给大模型的消息,是一个数组
    • stream:true,代表响应结果流式返回;false,代表响应结果一次性返回,但需要等待
    • temperature:取值范围[0:2),代表大模型生成结果的随机性,越小随机性越低。DeepSeek-R1不支持
  • 这里请求参数中的 messages 是一个消息数组,其中包含两个属性):
    • role:消息对应的角色
    • content:消息内容

2.2 在 IDEA 中进行创建 SpringAI 项目并调用 DS 模型

以下是创建项目的基本依赖需求(基于SpringBoot3、JDK17):

.yaml 配置:

这里是基于 Ollama 的(默认端口是11434)

这里是基于 openAI 的,进行调用开放平台的 API,需要输入 API-KEY(后面就不具体写明了,这里主要是基于 Ollama 的调用演示)

Config 配置类:(固定写法)

@Configuration
public class CommonConfig {@Beanpublic ChatClient chatClient(OllamaChatModel model) {return ChatClient.builder(model).build();}
}

Controller 控制响应类:(这里是以流式的方式进行响应)

@RestController
@RequestMapping("/ai")
public class ChatController {@Resourceprivate ChatClient chatClient;// 由于以流式方式调用模型,展示的数据会出现乱码,需要进行规定编码格式@GetMapping(value = "/chat", produces = "text/html; charset=utf-8")public Flux<String> chat(String msg) {return chatClient.prompt().user(msg).stream().content();}
}

进行页面的调用(若非流式访问,则需要等待 DS 生成完毕,才会出现响应信息):

注意,进行接口的调用时,需要启动 ollama 中的模型

🔖也可以加上日志的控制台打印输出,方便调试

.yml 中的配置:

然后在 Config 配置类中添加这一行代码,即可开启日志的打印:


3、基于 SpringAI 实现会话记忆功能

大致步骤:

定义会话存储方式(默认基于Map集合) ➡️ 配置会话记忆(关联上下文) ➡️ 添加会话ID(保证每个会话独立)

这里使用默认方式,由源码可知是基于 Map 直接存入内存中的;当然也可以重写 ChatMemory,比如存入到 Redis 中做缓存之类的(根据业务需求)

然后将 "存储方式" 配置到 "会话的上下文" 中,以处理会话的内容

运行结果:

之前的msg提问信息是:现在有15个苹果,6个人应该怎么分才好

我现在直接问它x个人应该怎么分,很明显它会进行关联之前的会话,根据上下文,来进行作答

存在问题:

然而,以上会话的处理会比较混乱,不管是谁来进行提问,模型都会自动关联所有的会话上下文来进行回答,这显然是不符合需求的;这时就需要与前端进行交接,即传递对应的会话 ID 过来进行处理,以实现用户的每个独立会话内容互不干扰

由前端响应接口可知,不同的会话对应着不同的会话ID

这时需要在 Controller 接口中添加以下配置,来接收存储不同会话下的ID


4、基于 SpringAI 实现会话历史功能

存在问题:目前,只要页面一刷新,之前的会话列表和对话记录都会被清除,不会被保留展示

前言:以下的 type 参数可根据具体需求进行更换,chatId 建议作保留以记录唯一会话

需求一:需要进行展示历史会话的列表信息

以下是对应的 UI 页面,以及对应的接口地址(模拟):

以下是 自定义 的 方法接口 与 实现类,主要用于 保存 和 获取 会话信息

public interface ChatHistoryService {/*** 保存会话记录(这里的 type 可根据需求进行更改,chatId 建议保留以作为会话的唯一ID)*/void save(String type, String chatId);/*** 获取会话列表(这里的 type 可根据需求进行更改)*/List<String> getChatIds(String type);
}
@Service
public class ChatHistoryServiceImpl implements ChatHistoryService {// 使用 map 集合进行存储private final Map<String, List<String>> chatHistoryMap = new HashMap<>();/*** 保存会话记录(这里的 type 可根据需求进行更改,chatId 建议保留以作为会话的唯一ID)*/@Overridepublic void save(String type, String chatId) {// 1.判断当前是否存在对应的 type 类型,若不存在则新增if (! chatHistoryMap.containsKey(type)) {chatHistoryMap.put(type, new ArrayList<>());}// 2.判断当前的 type 对应的 会话集合ID 是否含有当前的会话IDList<String> chatIds = chatHistoryMap.get(type);if (chatIds.contains(chatId)) {return;}chatIds.add(chatId);}/*** 获取会话列表信息(这里的 type 可根据需求进行更改)*/@Overridepublic List<String> getChatIds(String type) {List<String> chatIds = chatHistoryMap.get(type);return chatIds == null ? new ArrayList<>() : chatIds;}
}

每次用户在进行模型对话的时候,都需要将会话ID做保存(这里的 key 根据自己的业务需求来定,比如使用 用户ID 来进行拼接等,这里先写死)

然后获取当前对应 key 的会话 ID 集合,进行会话列表信息展示

@RestController
@RequestMapping("/ai/history")
public class ChatHistoryController {@Resourceprivate ChatHistoryService chatHistoryService;/*** 获取历史会话列表信息(这里的 type 可根据需求进行更改)*/@RequestMapping("/{type}")public List<String> getChatIds(@PathVariable("type") String type) {return chatHistoryService.getChatIds(type);}
}

需求二:点击会话列表,展示对应的历史会话记录信息

像以下UI所示,随便点击其中一个会话列表,对应的历史会话记录依然存在,并进行全部展示

接口地址(模拟):

会话的存储是在 ChatMemory 类中进行的,所以需要跟进源码进行查看

主要还是这个 get 方法,来进行获取对应会话的对话记录

然后 返回值 Message 类型,内部有继承以及实现的类

其中的 Content 代表着对话记录的内容,MessageType 代表着当前对话记录的类型

以上结论:由于需要满足当前接口的返回值,所以需要创建一个 DTO 来进行满足

@Data
@NoArgsConstructor
public class MessageDTO implements Serializable {/*** 角色类型*/private String role;/*** 对应的内容信息*/private String content;public MessageDTO(Message message) {// 判断当前会话的类型switch (message.getMessageType()) {case USER:this.role = "user";break;case ASSISTANT:this.role = "assistant";break;case SYSTEM:this.role = "system";break;case TOOL:this.role = "tool";break;default:this.role = "";break;}this.content = message.getText();}@Serialprivate static final long serialVersionUID = 1L;
}

接下来实现 “查询对话记录” 接口,与前端交互,即可实现展现历史对话记录

    /*** 查询会话记录详情(这里的 type 可根据需求进行更改,chatId 建议保留以作为会话的唯一ID)*/@GetMapping("/{type}/{chatId}")public List<MessageDTO> getChatHistory(@PathVariable("type") String type, @PathVariable("chatId") String chatId) {// 这里的展示记录条数最大值规定,直接使用的 int 型最大值,具体自己根据需求规定List<Message> messages = chatMemory.get(chatId, Integer.MAX_VALUE);if (messages == null) {return List.of();}// 进行封装返回类型return messages.stream().map(new Function<Message, MessageDTO>() {@Overridepublic MessageDTO apply(Message message) {return new MessageDTO(message);}}).toList();}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/80048.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在线查看【免费】 dcm、drawio,dcm wps文件格式网站

可以免费在线查看 .docx/wps/Office/wmf/ psd/ psd/eml/epub/dwg, dxf/ txt/zip, rar/ jpg/mp3 m.gszh.xyz m.gszh.xyz 免费支持以下格式文件在线查看类型 支持 doc, docx, xls, xlsx, xlsm, ppt, pptx, csv, tsv, dotm, xlt, xltm, dot, dotx, xlam, xla, pages 等 Office 办…

低光环境下双目云台摄像头监控性能解析

双目云台摄像头在低光环境下的监控效果主要取决于其硬件配置和软件优化能力。以下是对双目云台摄像头在低光环境下监控效果的详细分析&#xff1a; 一、硬件配置对低光监控效果的影响 镜头与焦距 &#xff1a; 双目云台摄像头通常配备超大广角固定镜头和360视角的移动镜头&a…

继承相关知识

概念 定义类时&#xff0c;代码中有共性的成员&#xff0c;还有自己的属性&#xff0c;使用继承可以减少重复的代码&#xff0c; 继承的语法 class 子类&#xff1a;继承方式 父类 继承方式有&#xff1a;public&#xff0c;private&#xff0c;protected 公共继承&#x…

【Python进阶】数据可视化:Matplotlib从入门到实战

Python数据可视化&#xff1a;Matplotlib完全指南 前言技术背景与价值当前技术痛点解决方案概述目标读者说明 一、技术原理剖析核心概念图解核心作用讲解关键技术模块说明技术选型对比 二、实战演示环境配置要求核心代码实现案例1&#xff1a;折线图&#xff08;股票趋势&#…

Java高效合并Excel报表实战:GcExcel让数据处理更简单

前言&#xff1a;为什么需要自动化合并Excel&#xff1f; 在日常办公场景中&#xff0c;Excel报表合并是数据分析的基础操作。根据2023年企业办公效率报告显示&#xff1a; 财务人员平均每周花费6.2小时在Excel合并操作上人工合并的错误率高达15%90%的中大型企业已采用自动化…

Python 列表与元组深度解析:从基础概念到函数实现全攻略

在 Python 编程的广袤天地中&#xff0c;列表&#xff08;List&#xff09;和元组&#xff08;Tuple&#xff09;是两种不可或缺的数据结构。它们如同程序员手中的瑞士军刀&#xff0c;能高效地处理各类数据。从简单的数值存储到复杂的数据组织&#xff0c;列表和元组都发挥着关…

Java中的方法重写(Override)与方法重载(Overload)详解

一、基本概念对比 特性方法重写(Override)方法重载(Overload)定义子类重新定义父类中已有的方法同一个类中多个同名方法&#xff0c;参数不同作用范围继承关系中&#xff08;父子类之间&#xff09;同一个类内方法签名必须相同&#xff08;方法名参数列表&#xff09;必须不同…

发布一个npm包,更新包,删除包

发布一个npm包&#xff0c;更新包&#xff0c;删除包 如何将自己的项目 发布为一个 npm 包&#xff0c;并掌握 更新 和 删除 的操作流程。 &#x1f680; 一、发布一个 npm 包的完整流程 ✅ 1. 注册并登录 npm 账号 如果还没有账号&#xff0c;先注册&#xff1a; 官网注册&…

Linux系统之----进程的概念

1.进程 1.1基本概念 课本概念 &#xff1a;进程是程序的一个执行实例&#xff0c;是正在执行的程序。当程序被执行时&#xff0c;系统会为其创建一个进程&#xff0c;包含程序代码、数据以及运行时所需的资源。 内核观点 &#xff1a;进程是担当分配系统资源&#xff08;CPU…

Shell脚本中的字符串截取和规则变化

文章目录 前言if通配符判断if判断多个条件规则变化字符串的两个示例改变中间段数字改变末尾段数字 总结 前言 科技的发展会带来习惯的改变&#xff0c;特别是对于我们这批敲代码的&#xff0c;之前还积累一些奇巧淫技&#xff0c;想着在必要的时候卖弄一下&#xff0c;自从生成…

c语言修炼秘籍 - - 禁(进)忌(阶)秘(技)术(巧)【第七式】程序的编译

c语言修炼秘籍 - - 禁(进)忌(阶)秘(技)术(巧)【第七式】程序的编译 【心法】 【第零章】c语言概述 【第一章】分支与循环语句 【第二章】函数 【第三章】数组 【第四章】操作符 【第五章】指针 【第六章】结构体 【第七章】const与c语言中一些错误代码 【禁忌秘术】 【第一式】…

Feign 深度解析:Java 声明式 HTTP 客户端的终极指南

Feign 深度解析&#xff1a;Java 声明式 HTTP 客户端的终极指南 Feign 是由 Netflix 开源的 ​声明式 HTTP 客户端&#xff0c;后成为 Spring Cloud 生态的核心组件&#xff08;现由 OpenFeign 维护&#xff09;。它通过注解和接口定义简化了服务间 RESTful 通信&#xff0c;并…

如何Ubuntu 22.04.5 LTS 64 位 操作系统部署运行SLAM3! 详细流程

以下是在本地部署运行 ORB-SLAM3 的详细步骤&#xff0c;基于官方 README.md 和最佳实践整理&#xff0c;适用于 Ubuntu 16.04/18.04/20.04/22.04 系统&#xff1a; 一、系统要求与依赖项安装 1. 基础系统要求 操作系统&#xff1a;Ubuntu 16.04/18.04/20.04/22.04&#xff…

USB 共享神器 VirtualHere 局域网内远程使用打印机与扫描仪

本文首发于只抄博客,欢迎点击原文链接了解更多内容。 前言 很久之前,有分享过使用 CUPS 和路由器来实现局域网内共享打印机,但由于 SANE 支持的打印机较少以及扫描驱动的缺失,试了很多种方法都没有办法成功远程使用打印机的扫描功能。 后面偶然发现 VirtualHere 可以曲线…

一洽智能硬件行业解决方案探索与实践

一、智能硬件行业发展现状剖析 在数字化浪潮推动下&#xff0c;智能硬件行业呈现蓬勃发展态势。软硬件一体化的深度融合&#xff0c;构建起智能化服务的核心架构&#xff0c;而移动应用作为连接用户与设备的重要桥梁&#xff0c;其作用愈发关键。深入研究该行业&#xff0c;可…

【C++ 类和数据抽象】构造函数

目录 一、构造函数的基本概念 1.1 构造函数核心特性 1.2 构造函数的作用 1.3 构造函数类型体系 二、构造函数的类型 2.1 默认构造函数 2.2 带参数的构造函数 2.3 拷贝构造函数 2.4 移动构造函数&#xff08;C11 及以后&#xff09; 三、初始化关键技术 3.1 成员初始…

图数据库nebula测试指南

概述 Nebula是一个开源的分布式图数据库系统&#xff0c;专为处理超大规模关联数据而设计。可以将复杂的关联关系存在nebula图数据库中&#xff0c;提供可视化平台用于案件关联查询及调查。测试的前提是了解nebula图数据库&#xff0c;会使用基本的插入语句和查询语句&#xf…

dispaly: inline-flex 和 display: flex 的区别

display: inline-flex 和 display: flex 都是 CSS 中用于创建弹性盒子布局&#xff08;Flexbox&#xff09;的属性值&#xff0c;但它们之间有一些关键的区别&#xff0c;主要体现在元素如何在页面上被渲染和它们对周围元素的影响。 主要区别 1&#xff0c;块级 vs 行内块级 d…

Sqlserver安全篇之_Sqlcmd命令使用windows域账号认证sqlserver遇到问题如何处理的案例

sqlcmd https://learn.microsoft.com/zh-cn/sql/tools/sqlcmd/sqlcmd-connect-database-engine?viewsql-server-ver16 sqlcmd -S指定的数据库连接字符串必须有对应的有效的SPN信息&#xff0c;否则会报错SSPI Provider: Server not found in Kerberos database. 正常连接 1、…

电脑硬盘常见的几种接口类型

一、传统接口&#xff08;机械硬盘为主&#xff09; 1. SATA 接口&#xff08;Serial ATA&#xff09; 特点&#xff1a; 最主流的机械硬盘&#xff08;HDD&#xff09;接口&#xff0c;广泛用于台式机和笔记本电脑。传输速度较慢&#xff0c;理论最大带宽为 6 Gbps&#xff…