Loading...

SpringAI实战

SpringAI基础教学


前言:最近公司让我搞了个大模型项目,需要AI学习自家的知识库,用户提问AI例如发送一个单号就可以查询轨迹、面单,或者试算价格。

其实真做起来也没啥,刚开始我也比较懵逼,大模型又不通网,怎么弄,好像不太现实,直到我把OpenWebUI的源码拉取下来,先说明一下,OpenWebUI的源码里面有一个第三方库:LangChain4j,这玩意有一个方法.defaineTools,该方法里面放入你的方法就可以实现大模型调用你的工具,当然,前提是你的大模型需要有tools标签,也就是你的大模型得具备调用工具的能力,那怎么查看,首先你需要前往Ollama的官网,地址: https://ollama.com/

base64

比如说最近比较火的deepseek-v4-flash:

base64

看到这3个标签了没有,tools表示该大模型具备调用工具的能力,thinking表示了该模型有深度思考,cloud则表示该模型是云大模型,是无法部署到你的本地的,起码现在是这样的,你并不需要像平时拉一个本地代码那样去跑:ollama run deepseek-v4-falsh

你只需要先去注册一个deepseek的账号,然后去购买token,步骤如下:

20260829155509640.webp

base64

先去冲个钱,deepseek-v4-flash的价格其实在我运行了这段时间(6月份到现在8月份),我充了100却只用了34.75元,高峰值一天也就6块钱,当然,这个是看你们的预算和业务量,我这里仅仅只给参考

base64

充完钱之后就去创建属于你的API key,这一步应该不需要我教你吧,这都不懂你别干这行了,ok,当你这些准备工作都做好了之后,有些老项目呢,可能Java版本比较低,比如说经典的jdk1.8 + SpringBoot2.X.X,那你就老老实实的去看他的API文档用HttpCline的方式去调用人家的API

base64

但是你的代码里可能会爆炸,因为官网只有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.420.42
连接池0.250.67
配置0.180.85
数据库0.100.95
其他0.051.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代码,类似我以下发出来的图片

base64base64

3.最重要的一步,我该如何告诉AI哪些是关键词

其实就那么两个接口类:@Tool以及@ToolParam,看看怎么用

base64base64base64base64base64base64

简单吧,最后就是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了,搞定,有不会的就来问我

0

回到顶部