Bài 35.2: Tích hợp OpenAI API trong Next.js

Bài 35.2: Tích hợp OpenAI API trong Next.js
Trí tuệ Nhân tạo (AI) đang thay đổi cách chúng ta tương tác với công nghệ, và việc tích hợp khả năng của AI vào các ứng dụng web đã trở nên dễ dàng hơn bao giờ hết nhờ các API. Một trong những API mạnh mẽ và phổ biến nhất hiện nay chính là OpenAI API, cung cấp quyền truy cập vào các mô hình ngôn ngữ tiên tiến như GPT-3.5, GPT-4 và nhiều mô hình khác.
Trong bài viết này, chúng ta sẽ đi sâu vào việc tích hợp OpenAI API vào một ứng dụng Next.js. Chúng ta sẽ xây dựng một ví dụ đơn giản, nơi người dùng nhập một yêu cầu (prompt) và ứng dụng sẽ gọi đến OpenAI API thông qua API Route của Next.js để nhận về phản hồi được tạo bởi AI.
Chuẩn bị
Trước khi bắt đầu, bạn cần đảm bảo có những thứ sau:
- Một dự án Next.js đang hoạt động: Bạn có thể tạo mới bằng
npx create-next-app@latest
. - Một tài khoản OpenAI và API Key: Nếu chưa có, hãy đăng ký tại OpenAI website và tạo một API Key trong phần cài đặt tài khoản. Hãy giữ bí mật API Key này!
- Node.js và npm/yarn đã cài đặt: Next.js yêu cầu môi trường Node.js.
Cài đặt Thư viện OpenAI
Để tương tác với OpenAI API từ ứng dụng Next.js, chúng ta cần cài đặt thư viện chính thức của OpenAI cho Node.js.
Mở terminal trong thư mục gốc của dự án Next.js và chạy lệnh sau:
npm install openai
# hoặc với yarn
yarn add openai
Thư viện này sẽ giúp chúng ta dễ dàng gửi yêu cầu đến OpenAI API mà không cần phải tự xây dựng các HTTP request phức tạp.
Bảo mật API Key với Environment Variables
Tuyệt đối không nhúng trực tiếp API Key của bạn vào code frontend hoặc bất kỳ file nào được công khai. Cách an toàn nhất để quản lý các khóa bí mật như OpenAI API Key trong Next.js là sử dụng Environment Variables (biến môi trường).
Tạo một file mới có tên .env.local
ở thư mục gốc của dự án Next.js (ngang hàng với package.json
). File này sẽ không bị commit vào Git nếu bạn đang sử dụng file .gitignore
mặc định của Next.js.
Thêm dòng sau vào file .env.local
, thay thế your_secret_api_key_here
bằng API Key thực tế của bạn:
OPENAI_API_KEY=your_secret_api_key_here
Trong code backend của Next.js (API Routes), bạn có thể truy cập giá trị này thông qua process.env.OPENAI_API_KEY
.
Xây dựng API Route Backend
Việc gọi OpenAI API nên được thực hiện ở phía backend (server-side) thay vì frontend. Có hai lý do chính:
- Bảo mật: Giữ API Key an toàn trên server, không bao giờ tiết lộ ra trình duyệt của người dùng.
- Linh hoạt: Có thể xử lý logic phức tạp, tiền xử lý hoặc hậu xử lý dữ liệu trước khi gửi đến/từ OpenAI.
Chúng ta sẽ sử dụng tính năng API Routes của Next.js để tạo một endpoint backend đơn giản.
Tạo một file mới tại đường dẫn pages/api/generate.js
(nếu bạn đang sử dụng Pages Router) hoặc app/api/generate/route.js
(nếu sử dụng App Router). Ở đây, chúng ta sẽ tập trung vào ví dụ với Pages Router cho đơn giản, logic tương tự có thể áp dụng cho App Router.
Cấu trúc API Route cơ bản
File pages/api/generate.js
sẽ chứa code xử lý yêu cầu từ frontend và gọi đến OpenAI API.
// pages/api/generate.js
import { OpenAI } from 'openai';
// Khởi tạo client OpenAI sử dụng API Key từ biến môi trường
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
export default async function handler(req, res) {
// Logic xử lý request và gọi OpenAI API sẽ ở đây
}
Giải thích:
import { OpenAI } from 'openai';
: Import lớpOpenAI
từ thư viện vừa cài đặt.const openai = new OpenAI({ ... });
: Tạo một instance củaOpenAI client
. Chúng ta truyền API Key thông quaprocess.env.OPENAI_API_KEY
, mà Next.js đã load từ file.env.local
cho môi trường server-side.export default async function handler(req, res) { ... }
: Đây là hàm xử lý chính cho API Route này.req
là đối tượng request gửi đến,res
là đối tượng response để gửi trả về.async
được sử dụng vì chúng ta sẽ gọi các hàm bất đồng bộ của OpenAI API.
Xử lý Request và Gọi OpenAI API
Chúng ta muốn API Route này chỉ chấp nhận phương thức POST (thường dùng để gửi dữ liệu như prompt từ frontend). Chúng ta cũng cần lấy dữ liệu prompt từ body của request.
Bên trong hàm handler
, thêm logic sau:
// pages/api/generate.js (Tiếp theo)
import { OpenAI } from 'openai';
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
export default async function handler(req, res) {
// Kiểm tra phương thức request
if (req.method !== 'POST') {
// Nếu không phải POST, trả về lỗi Method Not Allowed
return res.status(405).json({ message: 'Method Not Allowed' });
}
// Lấy prompt từ body của request
const { prompt } = req.body;
// Kiểm tra xem prompt có tồn tại không
if (!prompt) {
return res.status(400).json({ message: 'Prompt is required' });
}
try {
// Gọi OpenAI API để tạo completion (phản hồi)
const completion = await openai.chat.completions.create({
model: "gpt-3.5-turbo", // Chọn mô hình AI (có thể thay đổi)
messages: [
{ role: "system", content: "You are a helpful assistant." }, // Vai trò của AI
{ role: "user", content: prompt } // Nội dung yêu cầu từ người dùng
],
max_tokens: 150, // Giới hạn số lượng token trong phản hồi (tùy chọn)
temperature: 0.7, // Độ "sáng tạo" của phản hồi (tùy chọn, 0.0 đến 2.0)
});
// Trích xuất nội dung phản hồi từ AI
const assistantMessage = completion.choices[0]?.message?.content;
// Trả về phản hồi dưới dạng JSON
res.status(200).json({ text: assistantMessage });
} catch (error) {
// Xử lý lỗi nếu có vấn đề khi gọi API
console.error("Error calling OpenAI API:", error);
res.status(500).json({ message: 'Something went wrong', error: error.message });
}
}
Giải thích:
if (req.method !== 'POST') { ... }
: Đảm bảo chỉ xử lý request POST.const { prompt } = req.body;
: Next.js tự động parse body của request POST cóContent-Type: application/json
, cho phép chúng ta dễ dàng truy cập dữ liệu gửi lên (ở đây làprompt
).if (!prompt) { ... }
: Kiểm tra đơn giản xem prompt có rỗng không.try { ... } catch (error) { ... }
: Khốitry...catch
rất quan trọng để bắt lỗi trong quá trình gọi API hoặc bất kỳ vấn đề nào khác.await openai.chat.completions.create({ ... });
: Đây là phần cốt lõi, gọi hàmcreate
của OpenAI client để tạo ra đoạn văn bản dựa trênmessages
.model
: Chỉ định mô hình AI nào sẽ sử dụng.gpt-3.5-turbo
là một lựa chọn phổ biến vì tốc độ và chi phí hiệu quả. Bạn có thể thửgpt-4
cho kết quả chất lượng cao hơn.messages
: Đây là một mảng các đối tượng đại diện cho cuộc trò chuyện.role: "system"
thiết lập ngữ cảnh cho AI,role: "user"
là nội dung yêu cầu của người dùng. Đây là định dạng chuẩn cho API Chat Completions.max_tokens
: Giới hạn độ dài của phản hồi.temperature
: Kiểm soát tính ngẫu nhiên/sáng tạo. Giá trị cao hơn (gần 2.0) tạo ra phản hồi đa dạng và bất ngờ hơn; giá trị thấp hơn (gần 0.0) tạo ra phản hồi tập trung và ít ngẫu nhiên hơn.
const assistantMessage = completion.choices[0]?.message?.content;
: Trích xuất nội dung văn bản từ phản hồi của API. Cấu trúc phản hồi có thể hơi sâu, chúng ta lấy phầncontent
củamessage
đầu tiên trong mảngchoices
. Dấu?.
(optional chaining) giúp tránh lỗi nếu cấu trúc phản hồi không đúng như mong đợi.res.status(200).json({ text: assistantMessage });
: Gửi phản hồi thành công về frontend với status code 200 và dữ liệu là một đối tượng JSON chứa text phản hồi từ AI.- Trong khối
catch
, chúng ta log lỗi ra console server và gửi phản hồi lỗi về frontend với status code 500 (Internal Server Error).
Với API Route này, chúng ta đã có một endpoint backend sẵn sàng nhận prompt và gọi OpenAI API.
Tích hợp Frontend
Bây giờ, chúng ta sẽ tạo một giao diện đơn giản ở phía frontend để người dùng nhập prompt và hiển thị kết quả.
Sửa đổi file pages/index.js
(hoặc tạo một component mới trong thư mục src/app
nếu dùng App Router) để có một form và logic gọi API Route backend.
// pages/index.js
import { useState } from 'react';
function HomePage() {
// State để lưu trữ prompt người dùng nhập
const [prompt, setPrompt] = useState('');
// State để lưu trữ phản hồi từ AI
const [response, setResponse] = useState('');
// State để quản lý trạng thái loading khi đang gọi API
const [loading, setLoading] = useState(false);
// State để lưu trữ thông báo lỗi
const [error, setError] = useState(null);
// Hàm xử lý khi form được submit
const handleSubmit = async (e) => {
e.preventDefault(); // Ngăn chặn refresh trang mặc định của form
// Thiết lập trạng thái loading và xóa kết quả/lỗi cũ
setLoading(true);
setError(null);
setResponse('');
// Kiểm tra prompt rỗng
if (!prompt) {
setError('Please enter a prompt.');
setLoading(false);
return;
}
try {
// Gọi API Route backend chúng ta vừa tạo
const res = await fetch('/api/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ prompt: prompt }), // Gửi prompt trong body dưới dạng JSON
});
const data = await res.json(); // Parse phản hồi JSON
// Kiểm tra nếu phản hồi không thành công (status code khác 2xx)
if (!res.ok) {
// Ném lỗi với thông báo từ backend hoặc thông báo mặc định
throw new Error(data.message || 'Something went wrong.');
}
// Cập nhật state với phản hồi từ AI
setResponse(data.text);
} catch (err) {
// Bắt lỗi và cập nhật state lỗi
console.error("Fetch error:", err);
setError(err.message); // Hiển thị thông báo lỗi cho người dùng
} finally {
// Dù thành công hay thất bại, dừng trạng thái loading
setLoading(false);
}
};
return (
<div style={{ padding: '20px', fontFamily: 'sans-serif' }}>
<h1>✨ Ask OpenAI with Next.js ✨</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
placeholder="Enter your prompt here..."
style={{ marginRight: '10px', padding: '8px', minWidth: '300px' }}
disabled={loading} // Vô hiệu hóa input khi đang loading
/>
<button type="submit" disabled={loading}>
{loading ? 'Generating...' : 'Generate Response'}
</button>
</form>
{/* Hiển thị thông báo lỗi */}
{error && <p style={{ color: 'red', marginTop: '15px' }}>Error: {error}</p>}
{/* Hiển thị phản hồi từ AI */}
{response && (
<div style={{ marginTop: '20px', borderTop: '1px solid #eee', paddingTop: '15px' }}>
<h2>🤖 AI Response:</h2>
<p>{response}</p>
</div>
)}
</div>
);
}
export default HomePage;
Giải thích:
import { useState } from 'react';
: Import hookuseState
để quản lý trạng thái trong component.const [prompt, setPrompt] = useState('');
: State cho input prompt.const [response, setResponse] = useState('');
: State để hiển thị phản hồi từ AI.const [loading, setLoading] = useState(false);
: State boolean để chỉ báo liệu request đang diễn ra hay không. Giúp hiển thị trạng thái cho người dùng (ví dụ: disable button).const [error, setError] = useState(null);
: State để lưu trữ thông báo lỗi nếu có.const handleSubmit = async (e) => { ... }
: Hàm bất đồng bộ được gọi khi form được submit.e.preventDefault();
: Ngăn chặn hành vi submit form mặc định của trình duyệt (gây reload trang).- Thiết lập trạng thái
loading(true)
và xóa các trạng tháiresponse
vàerror
trước đó. await fetch('/api/generate', { ... });
: Sử dụng APIfetch
của trình duyệt để gửi một request POST đến API Route/api/generate
.method: 'POST'
: Chỉ định phương thức HTTP.headers: { 'Content-Type': 'application/json' }
: Cho backend biết body là JSON.body: JSON.stringify({ prompt: prompt })
: Chuyển đổi đối tượng{ prompt: prompt }
thành chuỗi JSON để gửi đi.
const data = await res.json();
: Chờ và parse body của phản hồi thành đối tượng JavaScript.if (!res.ok) { ... }
: Kiểm tra thuộc tínhok
của đối tượngResponse
. Nó làtrue
cho các status code trong khoảng 200-299. Nếu khôngok
, có nghĩa là backend đã trả về lỗi (ví dụ: 400, 405, 500).throw new Error(...)
: Ném ra mộtError
để khốicatch
có thể bắt và xử lý.setResponse(data.text);
: Cập nhật stateresponse
với dữ liệu nhận được từ backend (data.text
).catch (err) { ... }
: Bắt các lỗi xảy ra trong quá trìnhfetch
hoặc lỗi được ném ra từ khốitry
.finally { ... }
: Khối này luôn chạy sautry
hoặccatch
, đảm bảo rằngsetLoading(false)
luôn được gọi để kết thúc trạng thái loading.
- Phần JSX hiển thị form với input và button, sử dụng các state
prompt
,loading
. Hiển thịerror
vàresponse
một cách có điều kiện ({error && <p>...</p>}
).
Chạy thử
- Đảm bảo bạn đã thêm
OPENAI_API_KEY
vào file.env.local
. - Chạy ứng dụng Next.js:
npm run dev # hoặc yarn dev
- Mở trình duyệt và truy cập
http://localhost:3000
. - Nhập một prompt vào ô input, ví dụ: "Viết một đoạn thơ ngắn về mùa thu."
- Nhấn nút "Generate Response".
- Bạn sẽ thấy trạng thái loading, và sau đó là phản hồi được tạo ra bởi OpenAI API hiển thị trên trang.
Mở rộng và Cải tiến
Đây chỉ là điểm khởi đầu. Bạn có thể mở rộng ứng dụng này theo nhiều cách:
- Streaming: Thay vì chờ toàn bộ phản hồi, bạn có thể cập nhật text theo thời gian thực khi OpenAI trả về từng phần (streaming). Điều này mang lại trải nghiệm người dùng tốt hơn cho các phản hồi dài. OpenAI API và Next.js API Routes đều hỗ trợ streaming.
- Parameters: Thử nghiệm với các parameters khác của
openai.chat.completions.create
nhưtemperature
,top_p
,n
(số lượng phản hồi),stop
(ký tự dừng). - Model khác: Sử dụng các model khác như
gpt-4
cho các tác vụ phức tạp hơn. - Xử lý lỗi nâng cao: Hiển thị thông báo lỗi chi tiết hơn dựa trên mã lỗi từ OpenAI.
- Validation: Validate prompt ở cả frontend và backend (ví dụ: giới hạn độ dài).
- Contextual Conversations: Mở rộng mảng
messages
để lưu trữ lịch sử cuộc trò chuyện, cho phép AI nhớ ngữ cảnh của các tương tác trước đó. - UI/UX: Cải thiện giao diện người dùng, thêm hiệu ứng loading đẹp mắt.
- Rate Limiting: Nếu triển khai lên production, hãy xem xét việc áp dụng rate limiting cho API Route của bạn để tránh bị lạm dụng.
Việc tích hợp OpenAI API vào Next.js mở ra cánh cửa cho vô số tính năng AI mạnh mẽ trong ứng dụng web của bạn, từ tạo nội dung, tóm tắt văn bản, dịch thuật, phân tích cảm xúc, đến xây dựng chatbot thông minh và hơn thế nữa.
Comments