译者 | 刘涛
审校 | 重楼
嘿,Java开发人员们,有个重大好消息要告诉你们:Spring如今已正式支持通过SpringAI模块来构建AI应用程序。
在本教程中,我们将使用SpringBoot、React、Docker以及OpenAI来构建一个聊天机器人的应用程序。此应用程序能够让用户与由AI驱动的聊天机器人进行交互,可以向其提出问题,并实时获取回复。
文中提到的全部源代码已在GitHub存储库中予以提供。欢迎给它加星标,然后搬运该源代码库进行尝试体验。
为了让你对所要构建的内容有个概念,最终AI应用程序的样子如下:
你感兴趣么?让我们从头开始吧!
目录
前提条件
获取OpenAI密钥
使用Spring Boot构建REST API
使用Reactjs构建Chat UI
如何将AI应用程序Docker化
运行AI应用程序
前提条件
在我们深入研究构建聊天机器人之前,你需要熟悉以下几点:
- 需具备Java和Spring Boot的基本知识。
- 需要对React和CSS有基本的了解。
- 需要把JDK、NodePackageManager和Docker安装到本地计算机中。
获取OpenAI密钥
首先,如果你没有Open AI账户,那么需要先注册一个,在登录之后,你会来到它的主页。
在右上角,单击“Dashboard”(控制面板)菜单。在侧边栏上,单击“API Keys”(应用程序编程接口密钥),然后单击“Create new secretkey”(创建新密钥)按钮以生成你自己的密钥:
复制密钥并将其妥善保存于安全之处,因为后续你需要凭借此密钥将你的应用程序与OpenAI API相连接。
你可以查阅OpenAI API参考指南,以获取更多关于如何调用API、它所接受的请求类型以及它给出的响应内容等方面的信息。
使用SpringBoot构建REST API
让我们前往Spring Initializer(用于快速创建Spring Boot项目的基础结构的一个网络工具)来生成样板文件代码:
你可以自行选择group(反向域名)、artifact(项目唯一标识符)、name(项目名称)、description(项目描述)和package(Java包名)。我们使用Maven(Java项目的依赖管理和构建工具)作为构建工具,SpringBoot版本为3.3.3,打包选项为Jar(JavaArchive的缩写,是Java应用程序的标准打包格式),Java版本为17。(注:Dependency Management:依赖管理)
点击生成按钮,将会下载一个zip文件。解压该文件,然后将其作为Maven项目导入到你喜欢的IDE中(我用的是Intellij)。
在Spring中配置你的OpenAI密钥
你可以使用现有的application.properties文件,或者创建一个application.yaml文件。我喜欢使用Yaml,所以创建了一个application.yaml文件,我可以在其中放置所有的SpringBoot配置。
接下来在你的application.yaml文件中添加OpenAIKey、Model和Temperature:
spring:
ai:
openai:
chat:
options:
model:"gpt-3.5-turbo"
temperature:"0.7"
key:"PUTYOUROPEN_API_KEYHERE"
application.properties中的类似配置可能如下所示:
spring.ai.openai.chat.options.model=gpt-3.5-turbo
spring.ai.openai.chat.options.temperature=0.7
spring.ai.openai.key="PUTYOUROPEN_API_KEYHERE"
构建ChatController
让我们创建一个URL为/ai/chat/string的GET API和一个处理逻辑的方法:
@RestController
publicclass ChatController{
@Autowired
private final OpenAiChatModel chatModel;
@GetMapping("/ai/chat/string")
public Flux<String>generateString(@RequestParam(value="message",defaultValue="Tellmeajoke")Stringmessage){
return chatModel.stream(message);
}
}
- 首先,我们添加@RestController注解将ChatController类标记为我们的Spring控制器。
- 然后,我们注入dependency项(inject the dependency:一种设计模式,在 Spring 框架中广泛使用。它允许我们在不直接创建对象的情况下,让 Spring 容器为我们管理和提供所需的对象)来获取OpenAiChatModel类的实例。这个类是由我们之前添加的 Spring AI dependency库提供的。
- OpenAiChatModel带有一个stream(message)方法,它接受一个String类型的提示并返回一个String响应(技术上来说,它是一个String的Flux,因为我们使用了该方法的响应式版本)。
- 在内部,OpenAiChatModel.stream(message)将调用OpenAI API并从那里获取响应。OpenAI调用将使用你在application.yaml文件中提到的配置,所以请确保使用有效的OpenAI密钥。
- 我们创建了一个方法来处理GET API调用,该方法接受消息并返回Flux<String>作为响应。
构建、运行和测试 REST API
./mvnw clean install spring-boot:run
理想情况下,它会在8080端口上运行,除非你自定义了端口。请确保该端口空闲,以成功运行应用程序。
你可以使用Postman 或Curl命令来测试你的 REST API:
curl --location 'http://localhost:8080/ai/chat/string?message=How%20are%20you%3F'
使用Reactjs构建Chat UI
我们使用useState来管理状态:
const [messages, setMessages] = useState([]);
const [input, setInput] = useState('');
const [loading, setLoading] = useState(false);
- messages:它将存储聊天中的所有消息。每条消息都有text(文本内容)和sender(发送者,可能是'user'或'ai')这两个属性。
- input:用于保存用户在文本框中输入的内容。
- loading:当聊天机器人等待 AI 响应时,此状态被设置为“true”;在收到响应时,则设置为“false”。
接下来,让我们创建一个handleSend 函数,并在用户通过点击按钮或按下回车键发送消息时进行调用:
const handleSend = async () => {
if (input.trim() === '') return;
const newMessage = { text: input, sender: 'user' };
setMessages([...messages, newMessage]);
setInput('');
setLoading(true);
try {
const response = await axios.get('http://localhost:8080/ai/chat/string?message=' + input);
const aiMessage = { text: response.data, sender: 'ai' };
setMessages([...messages, newMessage, aiMessage]);
} catch (error) {
console.error("Error fetching AI response", error);
} finally {
setLoading(false);
}
};
以下是逐步发生的过程:
- 检查空输入:倘若输入字段为空,函数将提前返回(不会发送任何内容)。
- 用户新消息:一条新消息被添加到 messages 数组中。这条消息包含 text(用户输入的内容)属性,并标记为由'user'发送。
- 重置输入:消息发送完毕后,输入字段被清空。
- 开始加载:等待 AI 响应时,loading 设置为 true 以显示加载指示器。
- 发起 API 请求:代码使用 axios 向 AI 聊天机器人 API 发送请求,传递用户的消息。在收到响应后,AI 的新消息被添加到聊天中。
- 错误处理:如果获取 AI 响应时出现问题,错误会被记录至控制台。
- 停止加载:最终加载状态被关闭。
让我们编写一个函数,当用户在输入字段中输入内容,对input状态进行更新:
const handleInputChange = (e) => {
setInput(e.target.value);
};
然后,我们创建一个函数来检查用户是否按下了 Enter 键。如果出现这种情形,它会调用 handleSend()函数来发送消息:
const handleKeyPress = (e) => {
if (e.key === 'Enter') {
handleSend();
}
};
现在让我们创建 UI 元素来呈现聊天消息:
{messages.map((message, index) => (
<div key={index} className={`message-container ${message.sender}`}>
<img
src={message.sender === 'user' ? 'user-icon.png' : 'ai-assistant.png'}
alt={`${message.sender} avatar`}
className="avatar"
/>
<div className={`message ${message.sender}`}>
{message.text}
</div>
</div>
))}
这个代码块呈现聊天中的所有消息:
- 遍历消息:使用 .map() 函数将每条消息显示为一个div。
- 消息样式:消息的类名根据发送者(user或ai)而变化,清楚地表明谁发送了消息。
- 头像图片:每条消息显示一个小头像,用户和 AI 使用不同的图片。
让我们创建一些基于某个标志来显示加载器的逻辑:
{loading && (
<div className="message-container ai">
<img src="ai-assistant.png" alt="AI avatar" className="avatar" />
<div className="message ai">...</div>
</div>
)}
当 AI 处于思考状态(即 loading 为 true 时),我们显示一条加载消息(...),以便让用户知道响应即将到来。
最后,创建一个用于点击发送消息的按钮:
<button onClick={handleSend}>
<FaPaperPlane />
</button>
当此按钮被点击时,将会触发 handleSend() 函数。这里所采用的图标为一个纸飞机,这是“发送”按钮的常见图标。
完整的 Chatbot.js 如下所示:
import React, { useState } from 'react';
import axios from 'axios';
import { FaPaperPlane } from 'react-icons/fa';
import './Chatbot.css';
const Chatbot = () => {
const [messages, setMessages] = useState([]);
const [input, setInput] = useState('');
const [loading, setLoading] = useState(false);
const handleSend = async () => {
if (input.trim() === '') return;
const newMessage = { text: input, sender: 'user' };
setMessages([...messages, newMessage]);
setInput('');
setLoading(true);
try {
const response = await axios.get('http://localhost:8080/ai/chat/string?message=' + input);
const aiMessage = { text: response.data, sender: 'ai' };
setMessages([...messages, newMessage, aiMessage]);
} catch (error) {
console.error("Error fetching AI response", error);
} finally {
setLoading(false);
}
};
const handleInputChange = (e) => {
setInput(e.target.value);
};
const handleKeyPress = (e) => {
if (e.key === 'Enter') {
handleSend();
}
};
return (
<div className="chatbot-container">
<div className="chat-header">
<img src="ChatBot.png" alt="Chatbot Logo" className="chat-logo" />
<div className="breadcrumb">Home > Chat</div>
</div>
<div className="chatbox">
{messages.map((message, index) => (
<div key={index} className={`message-container ${message.sender}`}>
<img
src={message.sender === 'user' ? 'user-icon.png' : 'ai-assistant.png'}
alt={`${message.sender} avatar`}
className="avatar"
/>
<div className={`message ${message.sender}`}>
{message.text}
</div>
</div>
))}
{loading && (
<div className="message-container ai">
<img src="ai-assistant.png" alt="AI avatar" className="avatar" />
<div className="message ai">...</div>
</div>
)}
</div>
<div className="input-container">
<input
type="text"
value={input}
onChange={handleInputChange}
onKeyPress={handleKeyPress}
placeholder="Type your message..."
/>
<button onClick={handleSend}>
<FaPaperPlane />
</button>
</div>
</div>
);
};
export default Chatbot;
在 App.js 中使用 <Chatbot/>来加载聊天机器人 UI:
function App() {
return (
<div className="App">
<Chatbot />
</div>
);
}
除此之外,我们还使用 CSS 来使我们的聊天机器人更加美观。你可以参考 App.css 和 Chatbot.css 文件来了解具体样式。
运行前端
使用npm命令运行应用程序:
npm start
程序会在URL http://localhost:3000上运行前端。此时应用程序已能够进行测试。
然而,分别运行后端和前端稍显繁琐。因此,让我们借助Docker来使整个构建流程更加简单。
如何将AI应用程序Docker化
让我们将整个应用程序Docker化,以便轻松打包和部署到任何地方。你可以从Docker的官方网站安装并配置Docker。
Docker化后端
聊天机器人的后端是用Spring Boot构建的,因此我们将创建一个Dockerfile,它将Spring Boot应用程序构建为可执行JAR文件并在容器中运行。
让我们为其编写Dockerfile:
# Start with an official image that has Java installed
FROM openjdk:17-jdk-alpine
# Set the working directory inside the container
WORKDIR /app
# Copy the Maven/Gradle build file and source code into the container
COPY target/chatbot-backend.jar /app/chatbot-backend.jar
# Expose the application’s port
EXPOSE 8080
# Command to run the Spring Boot app
CMD ["java", "-jar", "chatbot-backend.jar"]
- FROM openjdk:17-jdk-alpine:这条语句指定了Docker基于包含JDK 17的轻量级Alpine Linux镜像,JDK 17是运行Spring Boot所必备的。
- WORKDIR /app:这条语句设置了Docker内部的工作目录为/app,这是我们应用程序文件将要存放的位置。
- COPY target/chatbot-backend.jar /app/chatbot-backend.jar:这条语句将本地机器上(通常在使用Maven或Gradle构建项目后,位于target文件夹中)构建的JAR文件复制到容器中。
- EXPOSE 8080:这条语句告诉Docker,应用程序将在端口8080上监听请求。
- CMD ["java", "-jar", "chatbot-backend.jar"]:这条语句指定了容器启动时将要执行的命令。它运行JAR文件以启动Spring Boot应用程序。
Docker化前端
聊天机器人的前端用React构建而成,通过创建一个Dockerfile来对其进行Docker化处理,该Dockerfile会安装必要的依赖项( dependencies)、构建应用程序,并借助像NGINX这类轻量级的Web服务器来提供服务。
让我们为React前端编写Dockerfile:
# Use a Node image to build the React app
FROM node:16-alpine AS build
# Set the working directory inside the container
WORKDIR /app
# Copy the package.json and install the dependencies
COPY package.json package-lock.json ./
RUN npm install
# Copy the rest of the application code and build it
COPY . .
RUN npm run build
# Use a lightweight NGINX server to serve the built app
FROM nginx:alpine
COPY --from=build /app/build /usr/share/nginx/html
# Expose port 80 for the web traffic
EXPOSE 80
# Start NGINX
CMD ["nginx", "-g", "daemon off;"]
- FROM node:16-alpine AS build: 使用轻量级的Node.js镜像来构建React应用。我们在这个容器内部安装所有依赖项并构建应用。
- WORKDIR /app: 设置容器内部的工作目录为/app。
- COPY package.json package-lock.json ./: 复制package.js和package-lock.json文件以便安装依赖项。
- RUN npm install: 安装package.json中列出的所有依赖项。
- COPY . .: 将当前目录下的所有前端源代码文件复制到容器中。
- RUN npm run build: 将构建完React后的文件置入一个名为build的文件夹中。
- FROM nginx:alpine: 在构建完应用后,基于nginx Web服务器镜像启动了一个新的容器。
- COPY --from=build /app/build /usr/share/nginx/html: 将第一个容器(即构建React应用的容器)中的build文件夹复制到nginx容器中,这是nginx默认用于提供文件的目录。
- EXPOSE 80: 开放端口80,nginx将使用此端口来提供Web流量。
- CMD ["nginx", "-g", "daemon off;"]: 以前台模式启动nginx服务器,以提供你的React应用。
使用Docker Compose同时运行前后端
现在我们已经为前端和后端分别创建了Dockerfile,我们将使用 docker-compose来协调同时运行这两个容器。
让我们在项目的根目录下编写docker-compose.yml 文件:
version: '3'
services:
backend:
build: ./backend
ports:
- "8080:8080"
networks:
- chatbot-network
frontend:
build: ./frontend
ports:
- "3000:80"
depends_on:
- backend
networks:
- chatbot-network
networks:
chatbot-network:
driver: bridge
- version: ’3’:这定义了所使用的 Docker Compose 的版本。
- services:这定义了我们想要运行的服务。
- backend: 此服务使用位于./backend 目录中的 Dockerfile 构建后端,并暴露 8080 端口。
- frontend: 此服务使用位于./frontend 目录中的 Dockerfile 构建前端。它将主机上的 3000 端口映射到容器内的 80 端口。
- depends_on: 这确保前端在启动之前等待后端准备就绪。
- networks: 此部分定义了一个共享网络,以便后端和前端能够相互通信。
运行AI应用程序
要运行整个应用程序(前端和后端),你可以使用以下命令:
docker-compose up --build
这个命令将会实现:
- 构建前端和后端的镜像。
- 启动两个容器(后端在 8080 端口,前端在 3000 端口)。
- 设置网络,使两个服务可以相互通信。
现在,你可以访问 http://localhost:3000 加载聊天机器人 UI,并开始向 AI 提问。
你已经成功使用 Spring Boot、React、Docker 和 OpenAI 构建了一个全栈聊天机器人应用。
项目中展示的源代码可在 Github 上获取,如果你觉得它有帮助,请加星标,并随意搬运该源代码库进行尝试体验。
译者介绍
刘涛,51CTO社区编辑,某大型央企系统上线检测管控负责人。
原文标题:How to Build an AI Chatbot with Spring AI, React, and Docker,作者:Vikas Rajput