譯者 | 劉濤
審校 | 重樓
嘿,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