💬 概述
随着 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 应用!