Flyinsky's Codes
1737 字
9 分钟
SpringBoot集成SprnigAI调用OpenAI及第三方接口

SpringBoot集成SprnigAI调用OpenAI及第三方接口#

概述#

​ 2023年底发布Snapshot版的Spring AI不久前发布了正式的Release并在官网放出了开发文档,SpringAI正式加入Spring全家桶。本文作为一个SpringAI的简单的上手(截至本文发布日期版本号为0.8.1)。SpringAI官方文档

​ 如果选择直接在pom.xml中添加SpringAI的dependency可能因SpringAI的依赖发布不久,国内各大镜像源没有同步更新,如果你使用的maven镜像是国内的,可能会出现无法找到SpringAI依赖的情况,可以换回国外默认镜像后科学上网加速下载。不推荐

​ 如果懒于调整配置文件或者担心影响其他maven项目的依赖设置,可直接在IDEA的依赖编辑器中添加SpringAI,IDEA会自动添加Spring官方源的信息。后续步骤会以此方式为示例。(可能需要最新版本的IDEA支持)推荐

创建项目#

首先创建一个新的SpringBoot项目,其他参数无所谓,只需要保证创建的是Maven项目,JDK版本在17+。如果你的IDEA爆红提示无法连接到start.spring.io请你科学上网或者多试几遍。

Next,进入依赖编辑器,勾选需要的依赖简单演示我只选择了Web和OpenAI还有Lombok。如果有其他例如数据库的需求可以自行添加。选择合适的依赖后直接Create

获取DeepSeek的免费接口额度#

DeepSeek是一个国产基于ChatGPT的问答数据训练的大语言模型。其性能和问答质量较其他国产模型较出色,主要是给500K的免费Token(白嫖万岁嘿嘿),大概算ChatGPT3.8?

​ 进入官网,注册账号,进入后台管理给自己的账号添加Key,记下即可。

代码编写#

​ 首先需要对application.yml(默认为applidcation.properties,我习惯使用yml配置文件)把我们刚刚拿到的key配置好。

application.yml

spring:
  ai:
    openai:
      api-key: #此处填写你的密钥
      base-url: https://api.deepseek.com #接口请求地址

​ 接下来我们写一个简单的Service业务层上先实现使用SpringAI的OpenAI客户端以修改BASE_URL的方式请求第三方接口。

ChatService.java

package com.example.demo1.Service;

/**
 * @author Flyinsky
 * @email w2084151024@gmail.com
 * @date 2024/05/20 Mon 4:02:11 pm
 */
public interface ChatService {
    String AIChatRequest(String question);
}

ChatServiceImpl.java

package com.example.demo1.Service.Impl;

import com.example.demo1.Service.ChatService;
import org.springframework.ai.chat.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.openai.OpenAiChatClient;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.OpenAiImageClient;

/**
 * @author Flyinsky
 * @email w2084151024@gmail.com
 * @date 2024/05/20 Mon 4:03:31 pm
 */
@Service
public class ChatServiceImpl implements ChatService {
    private final OpenAiChatClient chatClient;
    @Autowired
    public ChatServiceImpl(OpenAiChatClient chatClient) {
        this.chatClient = chatClient;
    }
    @Override
    public String AIChatRequest(String question) {
        ChatResponse res = chatClient.call(
                new Prompt(
                        question,
                        OpenAiChatOptions.builder()
                                .withModel("deepseek-chat")
                                .withTemperature(0.5F)
                                .build()
                ));
        return res.getResult().getOutput().getContent();
    }
}

​ 非常简单,创建一个Spring帮我们写好的客户端ChatClient对象,之后我们可以写一个控制类接受用户的问题传入到AIChatRequest再由该客户端请求DeepSeek,等待返回后将结果返回。

​ 接下来我们简单写一个控制类来实现一下这个请求。

ChatController.java

package com.example.demo1.Controller;

import com.example.demo1.Service.ChatService;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author Flyinsky
 * @email w2084151024@gmail.com
 * @date 2024/05/20 Mon 4:19:15 pm
 */
@RestController
@RequestMapping("api/chat")
public class ChatController {
    @Resource
    private ChatService chatService;
    @PostMapping("v0")
    public String v0(@RequestParam("question") String question) {
        return chatService.AIChatRequest(question);
    }
}

​ 我们可以跑一下程序使用接口测试工具看看效果:

​ 这就实现了一个简单的SpringAI调用第三方接口示例。可以在此基础上结合一些选题开发更高级的功能。

实现具备上下文对话#

​ 之前写了一个简单的例子,但是它是不具备历史记录的,也就是说每一次和AI发生对话它都是不知道你说了什么的,那如何让它实现这个功能呢?

​ 首先需要前端提供历史对话记录内容,因为从后端层面来讲开放对话接口,每时每刻都可能会有不同的用户请求,后端很难记忆每一个用户的历史记录,所以只能由前端在请求体中提供。

​ 这次为了方便理解,我们先从控制类里开始写:

​ 但是呢,历史记录包含问题和答案,那肯定要分开提交,不然后端又要专门写一个解析器把他们隔离开来。比如我要附带最近三次对话的历史记录,那问题加答案就是六条提交的参数,还有一个本次的问题,也就是七条参数,每个都用上述的RequestParam肯定不现实,当然如果你比较勤快你也可以这么写,也能实现,没有问题。

​ 但是我们其实可以做一个DTO(Data Transfer Object)来节省代码量,首先新建一个类,里面包含所有请求的参数:

V1RequestBody.java

package com.example.demo1.Entity.DTO;

import lombok.Data;

/**
 * @author Flyinsky
 * @email w2084151024@gmail.com
 * @date 2024/05/20 Mon 4:27:27 pm
 */
@Data
public class V1RequestBody {
    private String question;
    private String historyQuestion1;
    private String historyQuestion2;
    private String historyQuestion3;
    private String historyAnswer1;
    private String historyAnswer2;
    private String historyAnswer3;
}

然后在写接口,我们要把RequestParam改为RequestBody,同时使用接口测试工具时提交的也应该是json,这样SpringBoot会自动解析json里的内容为我们刚刚创建的V1RequestBody对象,拿到提交的对话对象后将里面包含的对话信息插入到Spring设计好的Message中,其中用户问题对应UserMessage,AI回答对应AssistantMessage将他们按照顺序加入到信息集合中去即可,最后交到服务类,由服务类请求后返回:

ChatController.java

@PostMapping("v1")
public String v1(@RequestBody V1RequestBody v1RequestBody){
        List<Message> historyMessages = new ArrayList<>();
        historyMessages.add(new UserMessage(v1RequestBody.getHistoryQuestion1()));
        historyMessages.add(new AssistantMessage(v1RequestBody.getHistoryQuestion1()));
        historyMessages.add(new UserMessage(v1RequestBody.getHistoryQuestion2()));
        historyMessages.add(new AssistantMessage(v1RequestBody.getHistoryQuestion2()));
        historyMessages.add(new UserMessage(v1RequestBody.getHistoryQuestion3()));
        historyMessages.add(new AssistantMessage(v1RequestBody.getHistoryQuestion3()));
        historyMessages.add(new UserMessage(v1RequestBody.getQuestion()));
        return chatService.AIChatResponseV1(historyMessages);
}

服务类里其实就没有什么新鲜玩意了,直接把上面的question换成messages即可。

ChatServiceImpl.java

@Override
    public String AIChatResponseV1(List<Message> messages) {
        ChatResponse res = chatClient.call(
            new Prompt(
                    messages,
                    OpenAiChatOptions.builder()
                            .withModel("deepseek-chat")
                            .withTemperature(0.5F)
                            .build()
            ));
        return res.getResult().getOutput().getContent();
    }

最后我们来跑一下这个接口试试,我提供一个请求体:

requestBody.json

{
    "question": "请你总结上一个问题",
    "historyQuestion1": "",
    "historyQuestion2": "",
    "historyQuestion3": "C++是什么",
    "historyAnswer1": "",
    "historyAnswer2": "",
    "historyAnswer3": " C++是一种高级编程语言,它是由Bjarne Stroustrup在1980年代初期设计的,作为C语言的扩展。C++旨在提供面向对象编程的能力,同时保留了C语言的高效性和底层硬件访问能力。C++是一种静态类型、多范式编程语言,支持过程化编程、数据抽象、面向对象编程、泛型编程和部分函数式编程的特性。"
}

​ 要注意AI认定上一次对话是最近添加的那条消息,例如再此次示例中从AI视角来看上一个问题是historyQuestion3 上上个问题是historyQuestion2,这和再控制类中add的顺序有关,要注意。

​ 至此携带上下文的SpringAI应用就实现了,可以与此结合,写出自己的AI对话程序,AI分析助手等等应用…祝好。