SpringAI实战
SpringAI基础教学
前言:最近公司让我搞了个大模型项目,需要AI学习自家的知识库,用户提问AI例如发送一个单号就可以查询轨迹、面单,或者试算价格。
其实真做起来也没啥,刚开始我也比较懵逼,大模型又不通网,怎么弄,好像不太现实,直到我把OpenWebUI的源码拉取下来,先说明一下,OpenWebUI的源码里面有一个第三方库:LangChain4j,这玩意有一个方法.defaineTools,该方法里面放入你的方法就可以实现大模型调用你的工具,当然,前提是你的大模型需要有tools标签,也就是你的大模型得具备调用工具的能力,那怎么查看,首先你需要前往Ollama的官网,地址: https://ollama.com/
比如说最近比较火的deepseek-v4-flash:
看到这3个标签了没有,tools表示该大模型具备调用工具的能力,thinking表示了该模型有深度思考,cloud则表示该模型是云大模型,是无法部署到你的本地的,起码现在是这样的,你并不需要像平时拉一个本地代码那样去跑:ollama run deepseek-v4-falsh
你只需要先去注册一个deepseek的账号,然后去购买token,步骤如下:

先去冲个钱,deepseek-v4-flash的价格其实在我运行了这段时间(6月份到现在8月份),我充了100却只用了34.75元,高峰值一天也就6块钱,当然,这个是看你们的预算和业务量,我这里仅仅只给参考
充完钱之后就去创建属于你的API key,这一步应该不需要我教你吧,这都不懂你别干这行了,ok,当你这些准备工作都做好了之后,有些老项目呢,可能Java版本比较低,比如说经典的jdk1.8 + SpringBoot2.X.X,那你就老老实实的去看他的API文档用HttpCline的方式去调用人家的API
但是你的代码里可能会爆炸,因为官网只有python的代码示例,是没有Java的,所以你需要自己去摸索,自己去踩坑,我接下来要推荐的,则是SpringAI,我直接给你们贴出maven吧,我懂你们的,都是伸手党,我也是
<properties>
<spring-ai.version>1.1.5</spring-ai.version>
</properties>
<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>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
<!-- 如果你想实现现在主流AI产品那种一个一个字输出的就继承以下内容 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>先说明一下,必须jdk >= 17, SpringBoot >= 3
然后去配置文件中添加以下内容:
# Spring配置
spring:
# AI配置
ai:
openai:
# DeepSeek API 地址(与 OpenAI 兼容)
base-url: https://api.deepseek.com
# 你的 API Key
api-key: 你的 API Key
chat:
options:
# DeepSeek 推荐使用的模型
# model: deepseek-v4-pro
model: deepseek-v4-flash
# 想减少你的token消耗,就把温度调为0.1,输出会更加的保守,字数更少,如果你想实现豆包那种不太聪明的话,建议拉爆,但是比较考验你的提示词
temperature: 0.1
# 每次回答最多只消耗4096token,按需调整
max-tokens: 4096
# 核采样,是大语言模型里一个控制回复多样性的核心参数
top-p: 0.1这里我着重的说明一下top-p这个参数
top-p: 核采样
原理是模型在生成每个词的时候,会先算出所有可能的概率,top-p的作用就是觉得从哪些词里随机选一个,我举个例子:
| 候选词 | 概率 | 累计概率 |
|---|---|---|
| 网络 | 0.42 | 0.42 |
| 连接池 | 0.25 | 0.67 |
| 配置 | 0.18 | 0.85 |
| 数据库 | 0.10 | 0.95 |
| 其他 | 0.05 | 1.00 |
- 如果 top_p=0.7,累计概率达到0.7时,候选池包含“网络”和“连接池”。
- 如果 top_p=0.9,累计概率达到0.9时,候选池则包含“网络”、“连接池”、“配置”和“数据库”。
可以看出,top_p 值越高,候选池越大,输出就越多样;反之,输出就更集中、更确定
🛠️ 怎么用?实践建议
- 取值范围:通常在 0.0 到 1.0 之间。
- 默认值:很多模型的默认值是 0.9。
- 黄金法则:官方强烈建议,调整 temperature 或 top_p 中的任意一个即可,不要同时大幅度调整两个。同时调参会让结果变得难以预测和解释。
- 不同场景的参考设置:追求准确(代码、数学、事实问答):用较低的 top-p(如 0.1 - 0.5)。通用对话:使用默认值就好,比如 top_p=0.9。创意写作、头脑风暴:用较高的 top-p(如 0.95 或更高)。
ok,当你已经弄懂了这些微调的参数,以及配置成功并跑通了项目,我们就开始集成到你的项目里面,注意,以下的东西我将直接贴代码并不会直接解释,想深入研究的可以自己学也可以直接在本文章下进行留言,我看到就回,不接受添加微信,你长大了应该学会自己思考了
1.首先是调用工具的配置类
package com.ruoyi.system.config;
import com.ruoyi.system.service.AiUtilService;
import org.springframework.ai.support.ToolCallbacks;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AiToolsConfiguration {
/**
* 这里的AiUtilService是我自己写的工具类,实际的东西你可以自己来
* @param aiUtilService
*/
@Bean
public ToolCallback[] allTools(AiUtilService aiUtilService){
return ToolCallbacks.from(aiUtilService);
}
}
2.我们需要写一个AI的大脑
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.content.Media;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.ResponseFormat;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.stereotype.Service;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
import org.springframework.web.multipart.MultipartFile;
import reactor.core.publisher.Flux;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
@Service
public class AiChatService {
private final ChatClient chatClient;
// 通过构造器注入 ChatClient.Builder,并定制
public AiChatService(ChatClient.Builder builder) {
// 假设我们有一个工具类(用 @Tool 注解的方法)
// 实际项目中,AiTools 是一个包含 @Tool 方法的 Bean
// 这里为了简化,用 null 占位,实际使用请注入
this.chatClient = builder
.defaultSystem("你是一个友好的 AI 助手,用中文回答用户的问题。")
// .defaultTools(aiTools) // 注册工具
.defaultAdvisors(new SimpleLoggerAdvisor())
.defaultOptions(
OpenAiChatOptions.builder()
.temperature(0.7)
.maxTokens(2048)
.topP(0.9)
.build()
)
.build();
}
// ---------- 1. 同步调用(普通问答) ----------
public String chatSync(String userMessage) {
return chatClient.prompt()
.user(userMessage)
.call()
.content();
}
// ---------- 2. 流式调用 ----------
public Flux<String> chatStream(String userMessage) {
return chatClient.prompt()
.user(userMessage)
.stream()
.content();
// 如果需要按窗口聚合,可参考原文件的 .window(Duration.ofMillis(50))...
}
// ---------- 3. 带历史上下文的对话 ----------
public String chatWithHistory(String userMessage, List<Message> history) {
// history 可以包含 UserMessage 和 AssistantMessage
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个友好的 AI 助手。"));
messages.addAll(history); // 历史消息
messages.add(new UserMessage(userMessage));
return chatClient.prompt()
.messages(messages)
.call()
.content();
}
// ---------- 4. 多模态(图片理解) ----------
public String chatWithImage(String text, MultipartFile imageFile) throws Exception {
byte[] bytes = imageFile.getBytes();
String contentType = imageFile.getContentType();
MimeType mimeType = MimeTypeUtils.parseMimeType(contentType);
ByteArrayResource resource = new ByteArrayResource(bytes) {
@Override
public String getFilename() {
return imageFile.getOriginalFilename();
}
};
UserMessage userMessage = UserMessage.builder()
.text(text)
.media(new Media(mimeType, resource))
.build();
return chatClient.prompt()
.user(userMessage)
// 如果模型支持视觉,切换为视觉模型(如 gpt-4o)
.options(OpenAiChatOptions.builder()
.model("gpt-4o") // 或 "deepseek-v4-flash-vision-exp"
.build())
.call()
.content();
}
// ---------- 5. 结构化输出(JSON) ----------
public List<String> generateSuggestions(String userMessage) {
String systemPrompt = """
根据用户问题,生成 3~5 个相关建议问题,以 JSON 数组格式返回。
示例:["问题1","问题2","问题3"]
""";
String json = chatClient.prompt()
.system(systemPrompt)
.user(userMessage)
.options(OpenAiChatOptions.builder()
.responseFormat(ResponseFormat.builder()
.type(ResponseFormat.Type.JSON_OBJECT)
.build())
.build())
.call()
.content();
// 解析 JSON(使用 fastjson 或 Jackson)
return com.alibaba.fastjson.JSONObject.parseArray(json, String.class);
}
// ---------- 6. 动态切换模型 ----------
public String chatWithModel(String userMessage, String modelName) {
return chatClient.prompt()
.user(userMessage)
.options(OpenAiChatOptions.builder()
.model(modelName)
.build())
.call()
.content();
}
}这个类是AI的大脑,业务逻辑不需要写入这里面,但是这里可以控制AI在什么时候,遇到什么关键词去调用你的业务逻辑@Service代码,类似我以下发出来的图片
3.最重要的一步,我该如何告诉AI哪些是关键词
其实就那么两个接口类:@Tool以及@ToolParam,看看怎么用
简单吧,最后就是Controller,我这里只讲一个流式输出
@PostMapping(value = "/streamChat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> streamChat(
@RequestParam(value = "message") String message
, @RequestParam(value = "conversationId", required = false) String conversationId
, @RequestParam(value = "file", required = false) MultipartFile file
){
return chatService.chatStream(message, getUsername(), conversationId, file);
}其实最重要的无非就那么几个,第一个,先声明这是一个流式响应接口,在你的RequestMapping后面加:
produces = MediaType.TEXT_EVENT_STREAM_VALUE第二个,记得是返回一个
Flux<String>ok了,搞定,有不会的就来问我