如何使用SpringAI、React和Docker构建AI聊天机器人 原创

发布于 2024-9-30 13:06
浏览
0收藏

嘿,Java开发人员们,有个重大好消息要告诉你们:Spring如今已正式支持通过SpringAI模块来构建AI应用程序。

在本教程中,我们将使用SpringBoot、React、Docker以及OpenAI来构建一个聊天机器人的应用程序。此应用程序能够让用户与由AI驱动的聊天机器人进行交互,可以向其提出问题,并实时获取回复。

文中提到的全部源代码已在GitHub存储库中予以提供。欢迎给它加星标,然后搬运该源代码库进行尝试体验。

为了让你对所要构建的内容有个概念,最终AI应用程序的样子如下:

如何使用SpringAI、React和Docker构建AI聊天机器人-AI.x社区

你感兴趣么?让我们从头开始吧!

目录

前提条件

获取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”(创建新密钥)按钮以生成你自己的密钥:

如何使用SpringAI、React和Docker构建AI聊天机器人-AI.x社区

复制密钥并将其妥善保存于安全之处,因为后续你需要凭借此密钥将你的应用程序与OpenAI API相连接。

你可以查阅OpenAI API参考指南,以获取更多关于如何调用API、它所接受的请求类型以及它给出的响应内容等方面的信息。

使用SpringBoot构建REST API

让我们前往Spring Initializer(用于快速创建Spring Boot项目的基础结构的一个网络工具)来生成样板文件代码:

如何使用SpringAI、React和Docker构建AI聊天机器人-AI.x社区

你可以自行选择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:这定义了我们想要运行的服务。
  1. backend: 此服务使用位于./backend 目录中的 Dockerfile 构建后端,并暴露 8080 端口。
  2. 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

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2024-9-30 13:11:56修改
收藏
回复
举报
回复
相关推荐