Java 开发 AI 应用利器:Spring AI

💬 概述

随着 AI 技术的快速发展,大语言模型应用在各个行业领域相继应运而生。尽管 Python 依然是 AI 应用开发的主流语言,但如今用其他编程语言开发 AI 应用也能成为新的选项。从 2023 年的 Langchain4j 到 2024 年的 Spring AI 的出现,让开发者在 Java 应用程序中利用大型语言模型构建复杂的 AI 应用也成为可能。

本文将基于 Spring Boot + Spring AI + doubao API 开发一个简易的 AI 聊天机器人,探索 Java 开发 AI 应用的可能性。

🪴 Spring AI 介绍

Spring AI 是 Spring 官方发布的 AI 应用开发框架。通过 Spring AI 框架可以简化 AI 功能相关应用程序的开发,而降低不必要的复杂配置、架构设计等起步成本。Spring AI 从一些著名的 Python 项目,例如 LangChain 和 LlamaIndex 中汲取灵感,但并非这些项目的直接移植,该项目的成立基于这样的信念:下一波生成式 AI 应用将不仅面向 Python 开发人员,还将遍及多种编程语言。从本质上讲,Spring AI 解决了 AI 集成的基本挑战:将企业数据和应用程序编程接口(APIs)与人工智能模型相连接。

⛳ 技术选型

目前 Spring AI 最新版本为1.0.0-M,支持 Spring Boot 3.2.x and 3.3.x

  • Spring Boot 3.3.5
  • Spring AI 1.0.0-M3
  • JDK 21
  • 大语言模型:doubao-lite-128k
  • OpenAI 接口管理 & 分发系统:One API

🔮 环境准备

获取 LLM API Key

开发一个 AI 应用,首先需要选定一个接入的大型语言模型。虽然 OpenAI 是首选,不过由于在国内有魔法限制所以最好还是选择国内的,这里我用的是豆包大模型。

在豆包大模型官网(www.volcengine.com/product/dou... 注册,免费额度有50万tokens,足够个人使用了。

进入控制台,在 "在线推理" 中创建推理接入点,模型任意选择,这里我选择Doubao-lite-128k/240828。创建好之后复制保存接入点 ID

之后在 "API Key 管理" 中创建 API Key,然后复制保存API Key

部署 One API

One API 是一个 OpenAI 接口管理 & 分发系统,它通过标准的 OpenAI API 格式访问所有的大模型,开箱即用。这样我们就只需要按照 OpenAI API 格式就可以调用豆包大模型的 API 了,可以减少差异化编码和配置。

One API 可以通过 Docker Compose 本地快速部署,然后在浏览器访问:http://localhost:3000/

yaml 复制代码
version: "3"
services:
  one-api:
    image: justsong/one-api
    container_name: one-api
    volumes:
      - ./data:/data
    ports:
      - "3000:3000"
    privileged: true
    environment:
      TZ: Asia/Shanghai

进入 One API 主页,在 "渠道" 中 "添加新的渠道",或者选择一个渠道点击 "测试",保证渠道连通性。

以下是我的渠道配置,主要关注以下配置:

  • 类型:类型选择字节跳动豆包
  • 模型:这里的模型不是doubao-lite-128k,对于豆包需要使用接入点 ID来指示所使用的模型。
  • 密钥:也就是前面获取的 LLM API Key

然后在 One API 主页的 "令牌" 中 "添加新的令牌",模型范围选择刚才 "渠道" 中使用的模型。然后复制生成的令牌,这个令牌可以看作是API Key的代理,之后在开发中是用这个令牌作为api-key配置的。

🤖 聊天机器人

准备工作完毕,开始进入 AI 应用开发,本次我们将实现一个聊天机器人。

开发依赖

首先我们的 AI 应用基于 RESTful API 调用,其次我们会通过标准的 OpenAI API 格式访问豆包大模型,这是由 One API 实现的。

创建一个新项目,使用 Spring AI 需要 Spring Boot 3.2.x 及以上版本,然后在依赖中添加 Spring Web,Open AI。

查看所引入的依赖,如果使用阿里云镜像可能无法拉取,需要先临时注释掉阿里云镜像配置。

xml 复制代码
<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>3.3.5</version>
  <relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
  <spring-ai.version>1.0.0-M3</spring-ai.version>
</properties>
xml 复制代码
<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
  </dependency>
</dependencies>

依赖版本管理中引入了 Spring AI BOM,用于声明给定版本 Spring AI 所使用依赖项的推荐版本。

xml 复制代码
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.ai</groupId>
      <artifactId>spring-ai-bom</artifactId>
      <version>${spring-ai.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

属性配置

打开application.yml,这里关注以下配置:

  • spring.ai.openai.api-key:这里填写在 One API 中所创建的令牌
  • spring.ai.openai.base-url:One API 访问地址
  • spring.ai.openai.chat.options.model:填写接入点 ID,默认为gpt-4o
yaml 复制代码
server:
  port: 8080

spring:
  application:
    name: aichat

  ai:
    openai:
      api-key: API_KEY
      base-url: http://localhost:3000
      chat:
        options:
          model: ep-2024******-z5csm

简单对话

一切准备就绪,开始编码。

编写aichat/controller/AIChatController.java

我们直接注入使用 Spring AI 帮我们自动装配的OpenAiChatModel,然后写一个简单的 API,实现对话。

java 复制代码
@RestController
public class AIChatController {

    /* spring-ai 自动装配的,直接注入使用 */
    @Resource
    private OpenAiChatModel openAiChatModel;

    /**
     * 调用 OpenAI 的接口
     * @param message 用户提问
     * @return
     */
    @RequestMapping("/ai/chat")
    public String chat(@RequestParam(value = "message", defaultValue = "来个开场白吧?") String message) {
        String call = openAiChatModel.call(message);
        return call;
    }
}

调用http://localhost:8080/ai/chat,可以看到 AI 成功响应了✨。

使用 Prompt

接着我们把代码改造一下,在模型调用方法中传入Prompt,这样我们就可以得到 JSON 结构的响应数据。其中result.output.content就是 AI 的回应内容。此外result.metadata.finishReason是我们程序判断是否回应完成的依据。

java 复制代码
@RequestMapping("/ai/chat")
public ChatResponse chat(@RequestParam(value = "message", defaultValue = "来个开场白吧?") String message) {
    ChatResponse chatResponse = openAiChatModel.call(new Prompt(message));
    return chatResponse;
}

流式调用

前面的对话示例中,是要等待所有文本完成后才一次性返回。如果文本很长就要等很久,这对用户体验来说并不好,所以我们接着改造代码用流式输出实时响应。

java 复制代码
@RequestMapping(value = "/ai/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> chat(@RequestParam(value = "message", defaultValue = "来个开场白吧?") String message) {
    Flux<ChatResponse> responseFlux = openAiChatModel.stream(new Prompt(message));
    return responseFlux.map(e -> e.getResult().getOutput().getContent());
}

在浏览器中发起调用,然后可以查看到请求的事件流数据,这样就完成了流式调用。

Web AI 聊天

最后我们完善一下我们的 AI 程序实现一个 Web AI 聊天,让它看起来更像平时用的 AI 聊天应用。

Web AI 聊天界面中只需要对话消息列表区域和一个发送消息区域即可。

html 复制代码
<template>
  <div class="chat-container">
    <div class="chat-messages" ref="chatMessages">
      <!-- 这里将显示对话消息 -->
      <div class="message" v-for="(message, index) in messages" :key="index" :class="['message', message.sender === 'You'? 'user-message' : 'ai-message']">
        <p>{{ message.text }}</p>
        <small>{{ message.sender }}</small>
      </div>
    </div>
    <div class="chat-input">
      <input type="text" v-model="newMessage" @keyup.enter="sendMessage" placeholder="Type your message...">
      <button @click="sendMessage">Send</button>
    </div>
  </div>
</template>

逻辑实现中,主要是使用EventSource处理 SSE 流。每当接收到一段消息后触发onmessage,首先判断响应数据是否完成,也就是前面提到的result.metadata.finishReason,然后把消息渲染到消息列表区域,同时为了让消息看起来像打字机一样一点一点输出的,可以将利用响应式数据拼接分段消息实现。

js 复制代码
<script setup lang="ts">
import { ref } from 'vue';

const messages = ref([
  { text: '你好,我是AI助手。有什么我可以帮助你的吗?', sender: 'AI' },
  // 可以在这里添加更多的初始消息
]);

const newMessage = ref('');

const sendMessage = () => {
  if (newMessage.value.trim()!== '') {
    messages.value.push({ text: newMessage.value, sender: 'You' });

    // 使用 EventSource 处理 SSE 流
    const eventSource = new EventSource(`http://localhost:8080/ai/chat/stream?message=${newMessage.value}`);
    newMessage.value = '';

    let currentMessage = '';
    eventSource.onmessage = (event) => {
      const data = JSON.parse(event.data);
      if (data.result.metadata.finishReason === 'STOP') {
        eventSource.close();
        return;
      }

      if (currentMessage === '') {
        currentMessage += data.result.output.content;
        messages.value.push({ text: currentMessage, sender: 'AI' });
      } else {
        currentMessage += data.result.output.content;
        messages.value[messages.value.length - 1].text = currentMessage
      }
    };

    eventSource.onerror = (error) => {
      console.error('Error:', error);
    };
  }
};
</script>

Java 代码如下,在之前流式调用的接口中修改响应类型为Flux<ChatResponse>

java 复制代码
@RequestMapping(value = "/ai/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ChatResponse> chatStream(@RequestParam(value = "message", defaultValue = "来个开场白吧?") String message) {
    Flux<ChatResponse> responseFlux = openAiChatModel.stream(new Prompt(message));
    return responseFlux;
}

启动应用,下面是实际的效果演示。至此,一个简易的 Web AI 聊天机器就完成了。

🏆 总结

通过前面一步步地对 Spring AI 的了解,开发前的对接准备以及开发配置、实现,最终完成了一个 AI 聊天机器人。这其实也让我们看到在 Java 应用程序中利用大型语言模型构建 AI 应用也并非难事,随着 AI 技术的快速发展,我想编程语言的应用范围也会越来越广泛,甚至不了解编程语言的群体也有朝一日也能够开发出属于自己的 AI 应用!

相关推荐
码到成功>_<11 分钟前
Spring Boot实现License生成和校验
数据库·spring boot·后端
Ztiddler1 小时前
【npm设置代理-解决npm网络连接error network失败问题】
前端·后端·npm·node.js·vue
货拉拉技术1 小时前
多元消息融合分发平台
javascript·后端·架构
醒过来摸鱼1 小时前
【Golang】协程
开发语言·后端·golang
谷大羽1 小时前
Kafka Stream实战教程
spring boot·后端·中间件·kafka·stream
2401_857636391 小时前
实验室管理平台:Spring Boot技术构建
java·spring boot·后端
luckywuxn1 小时前
Spring Cloud Alibaba、Spring Cloud 与 Spring Boot各版本的对应关系
spring boot·spring·spring cloud
问窗2 小时前
微服务中Spring boot的包扫描范围
java·spring boot·微服务
夜色呦2 小时前
中小企业人事管理自动化:SpringBoot实践
运维·spring boot·自动化
灭掉c与java2 小时前
第五章springboot实现web的常用功能
java·spring boot·spring