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

快速入门Java+Spring Ai+deepseek 开发

Java入门Spring AI应用开发

环境

  • JDK 17

  • Springboot3.5 +spring AI 1.0 (最新版本)

  • qwen模型 or deepseek R1

  • nodejs 22 这我这里懒得弄用的 18 做了一些小操作

前置了解

此文章是学习黑马spring ai+大模型教程的 笔记+思路理解

关于推理大模型的一些小知识 :

  1. 文中所提到的大模型 指 LLM 也就是大语言模型 (Large Language Models, LLM)目前很火的LLM模型大多基于一种源于NLP(自然语言处理)中的一个神经网络模型 Transformer
  2. 这种模型可以对输入的参数进行推理 , 就是将我们给出的prompt作为开头 推理后续的内容 采用持续生成的方式 在海量的训练数据中推理出一句话 也就是比如 你问gpt 我是一个广东人 你推荐我今天吃什么 他会根据你的词 比如 广东 今天 从吃什么 去推理出 比如广东的数据里有早餐 有概率数据 比如多少人早上选择肠粉 那么他继续推理就可以推荐你今天早上吃肠粉
  3. 其他前置知识 比如什么是 llm 什么是ai应用 可以自行去搜索了解

环境选择

image-20250528164423069

这里推荐两种方式 :

  1. 选择基于 ollama本地部署 deepseek r1:8b 模型用于开发学习
  2. 选择阿里云免费送100wtokens 的首次开通服务

阿里云

现在阿里云注册送很多 免费额度 可以直接查看底下的spring ai 开发简单ai应用了

本地部署

这里 博主的主机是 12g现存的 4070s 所以可以小玩一下 之后的演示 也会基于本地部署的r1:8b模型来展示一些操作

首先 打开 ollama 的官网下载 并且安装 ollama 然后搜索deepseek-r1 选择模型参数

官网地址 : https://ollama.com/

安装完成后 直接可以在cmd中查看到 ollama相关的命令

image-20250528170311674

可以看到和 docker的命令非常的相似

这里我选择 8b 先试试水 因为也是第一次

image-20250528165227867

打开 ollama的控制台 输入命令ollama run deepseek-r1:8b 直接复制即可

image-20250528170114189

image-20250528170404859

下载完成之后 就会启动大模型 我们就可以通过控制台和本地的模型对话了 因为参数低 可能会很慢

小贴士:

什么是大模型应用?

就是通过自己平台调用 推理模型返回结果的平台 比如目前非常常见的各大厂商免费的ai问答网站 都属于大模型应用的一种

RAG应用是什么

其实就是在大模型应用的基础上外挂了一层字典 也可以理解成知识库 可以通过这个知识库快速的检索问题 从而提升回复的精准性和可靠性, 核心思想就是通过实时检索外部知识库作为回复的根基 而不是纯靠大模型

Spring AI

简单的了解

Spring ai 帮我们把接入大模型到使用简化到了 三步:

  • 正确的依赖引入
  • 配置文件
  • 简单的Spring注入

然后只需要按照官方文档提供的调用模式 调用client就可以完成对模型的prompt发送以及获取回复的信息

image-20250528172817945

简单问答助手

创建全新Springboot 项目 ai-web

依赖

    <properties><java.version>17</java.version><spring-ai.version>1.0.0</spring-ai.version></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-model-ollama</artifactId></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies>

编写配置文件 :

设置 模型的类型和ollama为我们提供的 默认在 localhost:11434端口下的 api 服务

Spring:application:name: ai-webai:ollama:base-url: http://localhost:11434chat:model: deepseek-r1:8b
server:port: 8080
#用于查看会话日志
logging:level:org.springframework.ai.chat.client.advisor: debugcom.hyc.aiweb: debug

Spring ai 其实已经非常的成熟了 我们编写配置之后只需要少量的代码就可以完成 对大模型的调用

配置一下 client

package com.hyc.aiweb.config;import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** @author 冷环渊* @date 2025/5/28 17:24* @description ModelClientConfiguration*/
@Configuration
public class ModelClientConfiguration {@Beanpublic ChatClient chatClient(OllamaChatModel model) {return ChatClient.builder(model).defaultSystem("现在你是小冷的助手,请以小冷助手来回答问题").build();}
}

简单的对话连接

package com.hyc.aiweb.controller;import lombok.RequiredArgsConstructor;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;/*** @author 冷环渊* @date 2025/5/28 17:29* @description ChatController*/
@RestController
@RequestMapping("/ai")
@RequiredArgsConstructor
public class ChatController {private final ChatClient chatClient;//    @RequestMapping("/chat")
//    public String chat(@RequestParam String prompt) {
//        //阻塞式的调用 当全部结果返回的时候才会返回
//        return chatClient.prompt()
//                .user(prompt)
//                .call()
//                .content();
//    }@RequestMapping(value = "/chat", produces = "text/html;charset=utf-8")public Flux<String> chat(@RequestParam String prompt) {//流式调用return chatClient.prompt().user(prompt).stream().content();}
}

测试

image-20250528174722819

到这里我们已经完成最简单的 本地模型 交互

会话日志

Spring AI 给我们提供了基于aop的回话增强 Advisor 用于提供一些辅助 比如交互式ai平台最常见的 对话记录

image-20250528175248574

添加基础日志环绕增强

@Configuration
public class ModelClientConfiguration {@Beanpublic ChatClient chatClient(OllamaChatModel model) {return ChatClient.builder(model).defaultSystem("你是一个可爱的助手, 名字叫小冷").defaultAdvisors(new SimpleLoggerAdvisor()).build();}
}

刷新一下网页 之后查看idea控制台的日志 就可以看到我们的请求信息和回复信息 以 json的格式

image-20250528175839103

接入前端

这里我们使用黑马提供的前端来让交互看起来更加的贴近平台, 可以去黑马程序员公众号自行的搜索获取代码资源

前端项目

这里我们需要去做一些前后端分离必须要做的事情 , 前端项目的依赖和启动

npm i
npm run start

image-20250528182610976

后端项目配置跨域

package com.hyc.aiweb.config;import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/*** @author 冷环渊* @date 2025/5/28 17:24* @description ModelClientConfiguration*/
@Configuration
public class CorsConfiguration implements WebMvcConfigurer {public void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**").allowedOrigins("*").allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS").allowedHeaders("*");}
}

重启项目 就可以在智能对话中 开始对话了

效果

image-20250528182834869

会话记忆

我们刷新界面会发现 对话的内容什么的 全部都消失了 ,现在我们需要增加 会话记录和会话记忆的小功能

大模型本身并不具备记忆功能 , 这里我们将利用 client中的 一个消息类型 ASSISTANT 来实现对大模型生产的消息进行进行反复的使用 达成一个模拟场景记忆的效果 , 以阿里云的大模型文本调试控制台为例

image-20250528183708467

Spring ai 提供了一个接口 叫做 ChatMemory 用于规范我们使用会话记忆, 这里我们的制作思路是 :

  1. 每一段会话都会有一个id
  2. 存储方式基于ChatMemory 也就是 id+消息集合的方式

定义ChatMemory

spring ai ga1 版本中 与视频有差异 可以采用builder的方式来实现定义

package com.hyc.aiweb.config;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.memory.MessageWindowChatMemory;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** @author 冷环渊* @date 2025/5/28 17:24* @description ModelClientConfiguration*/
@Configuration
public class ModelClientConfiguration {@Beanpublic ChatMemory chatMemory() {return MessageWindowChatMemory.builder().build();}/*** 注入 chatclient 用于 ai对话** @author 冷环渊* date: 2025/5/28 18:41*/@Beanpublic ChatClient chatClient(OllamaChatModel model, ChatMemory chatMemory) {return ChatClient.builder(model).defaultSystem("你是一个可爱的助手, 名字叫小冷,请以小冷的身份和语气回答问题").defaultAdvisors(new SimpleLoggerAdvisor(),MessageChatMemoryAdvisor.builder(chatMemory).build()).build();}
}

定义完之后 我们就有了对话记忆 但是我们缺少什么? 我们会发现所有的会话都是混乱的记忆

基于id来管理记忆

黑马提供的前端项目中帮我们生成了 id 我们只需要接受id 并且设置在client 中就完成了 基于id 区分和管理 对话记忆

image-20250528185352069

这里视频中的演示已经过时 最新的官方文档中提示

The main changes that impact end user code are: In VectorStoreChatMemoryAdvisor: The constant CHAT_MEMORY_RETRIEVE_SIZE_KEY has been renamed to TOP_K. The constant DEFAULT_CHAT_MEMORY_RESPONSE_SIZE (value: 100) has been renamed to DEFAULT_TOP_K with a new default value of 20. The constant CHAT_MEMORY_CONVERSATION_ID_KEY has been renamed to CONVERSATION_ID and moved from AbstractChatMemoryAdvisor to the ChatMemory interface. Update your imports to use org.springframework.ai.chat.memory.ChatMemory.CONVERSATION_ID

image-20250528190045288

根据以上规则 去 编写逻辑

    @RequestMapping(value = "/chat", produces = "text/html;charset=utf-8")public Flux<String> chat(@RequestParam String prompt, String chatId) {//流式调用return chatClient.prompt().user(prompt).advisors(a -> a.param(ChatMemory.CONVERSATION_ID,chatId)).stream().content();}

测试

默认会话 :

image-20250528190249547

这是会话1:

image-20250528190330838

实现了上下文的隔离 以及一段对话的记忆 , 查看日志

image-20250528191234587

在下一段对话响应前 会先传入上次对话的结果 类型为 ASSISTANT,再多输入一段内容 就会将上次的两个内容先传入到对话中再去执行内容 这就是对话记忆

image-20250528191422070

会话历史

根据请求的业务类型和id记录历史

image-20250528192336713

这个就是简单的业务了 :

  1. 查询左侧的会话记录并且返回 一个id集合
  2. 查询单次id的会话历史返回消息的集合
  3. 我们就是用map在内存中保存记录

创建接口

package com.hyc.aiweb.repository;import java.util.List;public interface ChatHistoryRepository {/*** 保存会话记录** @author 冷环渊* date: 2025/5/28 19:27*/void save(String type, String chatId);/*** 根据业务类型返回id列表** @author 冷环渊* date: 2025/5/28 19:27*/List<String> getChatIds(String type);
}

实现接口

package com.hyc.aiweb.repository;import org.springframework.stereotype.Component;import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** @author 冷环渊* @date 2025/5/28 19:28* @description ChatHistoryRepositoryImpl*/
@Component
public class ChatHistoryRepositoryImpl implements ChatHistoryRepository {//    存储到map中private final Map<String, List<String>> chatHistoryMap = new HashMap<>();@Overridepublic void save(String type, String chatId) {List<String> chatIds = chatHistoryMap.computeIfAbsent(type, k -> new ArrayList<>());if (chatIds.contains(chatId)) {return;}chatIds.add(chatId);}@Overridepublic List<String> getChatIds(String type) {return chatHistoryMap.getOrDefault(type, List.of());}
}

VO对象

package com.hyc.aiweb.controller.vo;import lombok.Data;
import org.springframework.ai.chat.messages.Message;/*** @author 冷环渊* @date 2025/5/28 19:37* @description MessageVO*/
@Data
public class MessageVO {private String role;private String content;public MessageVO(Message message) {switch (message.getMessageType()) {case USER:role = "user";break;case ASSISTANT:role = "assistant";break;}this.content = message.getText();}
}

controller

package com.hyc.aiweb.controller;import com.hyc.aiweb.controller.vo.MessageVO;
import com.hyc.aiweb.repository.ChatHistoryRepositoryImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;import java.util.List;/*** @author 冷环渊* @date 2025/5/28 17:29* @description ChatController*/
@RestController
@RequestMapping("/ai")
@RequiredArgsConstructor
public class ChatController {private final ChatClient chatClient;private final ChatHistoryRepositoryImpl chatHistoryRepository;private final ChatMemory chatMemory;//    @RequestMapping("/chat")
//    public String chat(@RequestParam String prompt) {
//        //阻塞式的调用 当全部结果返回的时候才会返回
//        return chatClient.prompt()
//                .user(prompt)
//                .call()
//                .content();
//    }@RequestMapping(value = "/chat", produces = "text/html;charset=utf-8")public Flux<String> chat(@RequestParam String prompt, String chatId) {chatHistoryRepository.save("chat", chatId);//流式调用return chatClient.prompt().user(prompt).advisors(a -> a.param(ChatMemory.CONVERSATION_ID, chatId)).stream().content();}@GetMapping(value = "/history/{type}")public List<String> historyChat(@PathVariable("type") String type) {return chatHistoryRepository.getChatIds(type);}@GetMapping(value = "/history/{type}/{chatId}")public List<MessageVO> historyChat(@PathVariable("type") String type, @PathVariable("chatId") String chatId) {List<MessageVO> messageList = chatMemory.get(chatId).stream().map(MessageVO::new).toList();if (messageList == null) {return List.of();}System.out.println(messageList);return messageList;}
}

测试

这是第一个会话的记录:

image-20250528200628953

第二个会话的记录:

image-20250528200649756

从测试中我们就可以看到 我们成功的保留了有内容的会话,并且每个会话都有消息历史

总结

通过这么一个短篇小文章 就已经接触到了 基于llm开发ai应用的门槛 我们在这次学习中 学习到了一些关于模型的知识 以及Spring ai的使用 ,这里我们复习一下逻辑

  1. llm是基于推理模型来完成对话的
  2. Spring ai的使用分为 配置client 如果需要上下文对话这需要开启advisors,并且添加到client配置项
  3. 如果需要记录历史 则需要配置chatMemory 并且添加新的MessageChatMemoryAdvisor.builder(chatMemory).build()到client的defaultAdvisors中之后就可以通过 chatMemory来对会话的历史进行操作

小拓展

可以将历史存入 redis中进行三十天有效 或者更长的时间来处理

http://www.xdnf.cn/news/9566.html

相关文章:

  • git 一台电脑一个git账户,对应多个仓库ssh
  • ParakeetTDT0.6BV2,语音识别ASR,极速转录, 高精度英文转录,标点支持(附整合包)
  • Dify案例实战之智能体应用构建(二)
  • IBM DB2和MYSQL在安全性、稳定性等方面的差异
  • 时间序列预测算法中的预测概率化笔记
  • GPIO驱动实例代码
  • 【客户案例】借助 DHTMLX Gantt 和 Diagram 构建高效项目与流程管理平台
  • 基于SpringBoot开发一个MCP Server
  • vue 中的ref属性
  • chown修改不成功的解决方案
  • ESP8285乐鑫SOCwifi芯片32bit MCU和2.4 GHz Wi-Fi
  • 零衍课堂 | 环境初始化部署流程
  • 从0到1:多医院陪诊小程序开发笔记(上)
  • VMware 安装 Ubuntu 实战教程
  • python学习打卡day38
  • 截图后怎么快速粘贴到notability?
  • day22-定时任务故障案例
  • 秒杀系统—2.第一版初步实现的技术文档
  • 医院闭环系统业务介绍
  • Linux基础 -- 设备树引脚复用之`/omit-if-no-ref/` 用法解析
  • 8.7 基于EAP-AKA的订阅转移
  • Springboot 集成 TDengine3.0版本
  • git stash 的使用
  • qt ubuntu 20.04 交叉编译
  • python实战:在Linux服务器上使用LibreOffice命令行批量接受Word文档的所有修订
  • MCP 与 AI 模型的用户隐私保护——如何让人工智能更懂“界限感”?
  • Python-114:字符串字符类型排序问题
  • HBO Max 中国大陆订阅与使用终极指南(2025 最新)
  • LangChain4j(17)——MCP客户端
  • 在PHP编程中包(Package)和库(Library)怎么区分?