RAG Cho Frontend: Xây Documentation Chatbot Với React Và LangChain
AAdmin
12 tháng 3, 2026
7 phút đọc
0 lượt xem
Hướng dẫn xây dựng documentation chatbot sử dụng RAG pattern. AI trả lời dựa trên docs thực tế của project, không hallucinate.
AI chatbot thường hallucinate khi trả lời về codebase hoặc internal docs. RAG (Retrieval-Augmented Generation) giải quyết vấn đề này bằng cách feed relevant context vào LLM trước khi generate response. Kết quả: chatbot trả lời chính xác dựa trên documentation thật.
RAG Flow Đơn Giản
- Index: Chia docs thành chunks → tạo embeddings → store trong vector DB
- Retrieve: User hỏi → tạo embedding cho query → tìm relevant chunks
- Generate: Gửi question + relevant chunks cho LLM → nhận answer có context
Backend: RAG API Route
// app/api/docs-chat/route.ts
import { openai } from "@ai-sdk/openai";
import { streamText, embed } from "ai";
import { prisma } from "@/lib/prisma";
async function getRelevantDocs(query: string) {
const { embedding } = await embed({
model: openai.embedding("text-embedding-3-small"),
value: query,
});
// Tìm top 5 chunks gần nhất
const docs = await prisma.$queryRaw<Array<{ content: string; title: string }>>`
SELECT content, title
FROM doc_chunks
ORDER BY embedding <=> ${JSON.stringify(embedding)}::vector
LIMIT 5
`;
return docs.map(d => `## ${d.title}
${d.content}`).join("
");
}
export async function POST(req: Request) {
const { messages } = await req.json();
const lastMessage = messages[messages.length - 1].content;
const context = await getRelevantDocs(lastMessage);
const result = streamText({
model: openai("gpt-4o"),
system: `Bạn là documentation assistant. Trả lời DỰA TRÊN context được cung cấp.
Nếu context không chứa thông tin liên quan, nói rõ "Tôi không tìm thấy thông tin này trong docs".
Không bao giờ bịa thông tin.
Context từ documentation:
---
${context}
---`,
messages,
});
return result.toDataStreamResponse();
}
Frontend: Docs Chat Component
// components/docs-chatbot.tsx
"use client";
import { useChat } from "ai/react";
import { Bot, User, Send } from "lucide-react";
export function DocsChatbot() {
const { messages, input, handleInputChange, handleSubmit, isLoading } =
useChat({ api: "/api/docs-chat" });
return (
<div className="fixed bottom-4 right-4 w-96 bg-white border rounded-2xl shadow-2xl">
<div className="p-4 border-b bg-gradient-to-r from-blue-600 to-purple-600 rounded-t-2xl">
<h3 className="text-white font-semibold flex items-center gap-2">
<Bot className="w-5 h-5" /> Docs Assistant
</h3>
</div>
<div className="h-80 overflow-y-auto p-4 space-y-3">
{messages.length === 0 && (
<p className="text-gray-400 text-center mt-8">
Hỏi bất kỳ điều gì về documentation!
</p>
)}
{messages.map((m) => (
<div key={m.id} className="flex gap-2">
{m.role === "assistant" ? (
<Bot className="w-5 h-5 text-blue-600 shrink-0 mt-1" />
) : (
<User className="w-5 h-5 text-gray-600 shrink-0 mt-1" />
)}
<div className="text-sm leading-relaxed">{m.content}</div>
</div>
))}
</div>
<form onSubmit={handleSubmit} className="p-3 border-t flex gap-2">
<input
value={input}
onChange={handleInputChange}
placeholder="Hỏi về docs..."
className="flex-1 px-3 py-2 text-sm border rounded-lg"
/>
<button type="submit" disabled={isLoading}
className="p-2 bg-blue-600 text-white rounded-lg">
<Send className="w-4 h-4" />
</button>
</form>
</div>
);
}
Indexing Documents
Quan trọng nhất là cách bạn chunk documents:
- Chunk size: 500-1000 tokens per chunk — quá nhỏ mất context, quá lớn mất precision
- Overlap: 100-200 tokens overlap giữa chunks để không mất thông tin ở boundary
- Metadata: Lưu title, section heading, file path kèm mỗi chunk để improve retrieval
Tips Production
- Re-index khi docs thay đổi — set up webhook trigger
- Show source references trong response để users verify
- Implement feedback (thumbs up/down) để improve retrieval quality
- Cache frequent queries trong Redis
RAG chatbot là perfect use case cho AI trong frontend. Users get instant, accurate answers thay vì phải search qua hàng trăm pages documentation.
A
Admin
Bình luận (0)
Đăng nhập để bình luận
Chưa có bình luận nào. Hãy là người đầu tiên!