بناء روبوت محادثة RAG باستخدام Supabase pgvector و Next.js

نماذج اللغة الكبيرة مثيرة للإعجاب، لكن لديها قيد حاسم: إنها تعرف فقط ما تم تدريبها عليه. ماذا لو كنت تريد مساعداً ذكياً يفهم وثائقك، منتجاتك، أو معرفة شركتك؟
هنا يأتي دور RAG (التوليد المعزز بالاسترجاع). في هذا الدليل، ستبني روبوت محادثة يمكنه الإجابة على الأسئلة باستخدام بياناتك الخاصة من خلال الجمع بين امتداد pgvector من Supabase وواجهات برمجة تطبيقات OpenAI.
ما ستبنيه
بنهاية هذا الدليل، سيكون لديك:
- تطبيق Next.js مع واجهة محادثة
- قاعدة بيانات Supabase تخزن مستنداتك كتضمينات متجهية
- بحث دلالي يجد المحتوى ذي الصلة بناءً على المعنى
- روبوت محادثة مدعوم بـ RAG يجيب على الأسئلة باستخدام بياناتك
المتطلبات الأساسية
قبل البدء، تأكد من توفر:
- Node.js 18+ مثبت
- حساب Supabase (الطبقة المجانية تعمل)
- مفتاح OpenAI API مع وصول إلى التضمينات ونماذج المحادثة
- معرفة أساسية بـ React و TypeScript
- إلمام بـ Next.js App Router
فهم البنية
قبل الغوص في الكود، دعنا نفهم كيف يعمل RAG:
User Question → Embed Question → Search Similar Docs → Augment Prompt → LLM Response
↓ ↓ ↓ ↓ ↓
"What is X?" [0.1, 0.2...] Find top 5 docs Add context Answer!
- المستخدم يطرح سؤالاً باللغة الطبيعية
- تضمين السؤال في متجه (مصفوفة من الأرقام)
- البحث الدلالي يجد المستندات ذات المتجهات المشابهة
- تعزيز الموجه بإضافة المستندات المسترجعة كسياق
- LLM يولد إجابة بناءً على السياق
السحر في الخطوة 3: بدلاً من مطابقة الكلمات المفتاحية، نجد المستندات المشابهة دلالياً - حتى لو لم تشترك في نفس الكلمات.
الخطوة 1: إعداد المشروع
أنشئ مشروع Next.js جديد مع TypeScript:
npx create-next-app@latest rag-chatbot --typescript --tailwind --app --src-dir
cd rag-chatbotثبّت التبعيات المطلوبة:
npm install @supabase/supabase-js openai aiأنشئ ملف .env.local مع بيانات اعتمادك:
NEXT_PUBLIC_SUPABASE_URL=your-supabase-url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
OPENAI_API_KEY=your-openai-api-keyالخطوة 2: تهيئة Supabase مع pgvector
اذهب إلى لوحة تحكم Supabase وافتح SQL Editor. شغّل ما يلي لتمكين امتداد pgvector:
-- Enable the pgvector extension
create extension if not exists vector with schema extensions;الآن أنشئ جدولاً لتخزين مستنداتك مع تضميناتها:
-- Create the documents table
create table documents (
id bigint primary key generated always as identity,
content text not null,
metadata jsonb,
embedding extensions.vector(1536) -- OpenAI text-embedding-3-small dimension
);
-- Enable Row Level Security
alter table documents enable row level security;
-- Create policy for reading (adjust as needed)
create policy "Allow public read access"
on documents for select
using (true);
-- Create an index for faster similarity search
create index on documents
using hnsw (embedding vector_cosine_ops);يتطابق vector(1536) مع أبعاد إخراج نموذج text-embedding-3-small من OpenAI. فهرس HNSW يمكّن البحث السريع عن أقرب الجيران التقريبي.
الخطوة 3: إنشاء دالة التضمين
أنشئ ملفاً جديداً src/lib/embeddings.ts:
import OpenAI from 'openai';
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
export async function generateEmbedding(text: string): Promise<number[]> {
const response = await openai.embeddings.create({
model: 'text-embedding-3-small',
input: text,
});
return response.data[0].embedding;
}
export async function generateEmbeddings(texts: string[]): Promise<number[][]> {
const response = await openai.embeddings.create({
model: 'text-embedding-3-small',
input: texts,
});
return response.data.map((item) => item.embedding);
}الخطوة 4: بناء API لاستيعاب المستندات
أنشئ src/app/api/ingest/route.ts لإضافة مستندات إلى قاعدة معارفك:
import { createClient } from '@supabase/supabase-js';
import { generateEmbeddings } from '@/lib/embeddings';
import { NextResponse } from 'next/server';
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
interface Document {
content: string;
metadata?: Record<string, unknown>;
}
export async function POST(request: Request) {
try {
const { documents }: { documents: Document[] } = await request.json();
if (!documents || documents.length === 0) {
return NextResponse.json(
{ error: 'No documents provided' },
{ status: 400 }
);
}
// Generate embeddings for all documents
const contents = documents.map((doc) => doc.content);
const embeddings = await generateEmbeddings(contents);
// Prepare data for insertion
const rows = documents.map((doc, index) => ({
content: doc.content,
metadata: doc.metadata || {},
embedding: embeddings[index],
}));
// Insert into Supabase
const { data, error } = await supabase
.from('documents')
.insert(rows)
.select('id');
if (error) {
throw error;
}
return NextResponse.json({
success: true,
inserted: data.length,
});
} catch (error) {
console.error('Ingestion error:', error);
return NextResponse.json(
{ error: 'Failed to ingest documents' },
{ status: 500 }
);
}
}الخطوة 5: إنشاء دالة البحث الدلالي
أنشئ src/lib/search.ts لإيجاد المستندات ذات الصلة:
import { createClient } from '@supabase/supabase-js';
import { generateEmbedding } from './embeddings';
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
export interface SearchResult {
id: number;
content: string;
metadata: Record<string, unknown>;
similarity: number;
}
export async function semanticSearch(
query: string,
topK: number = 5,
threshold: number = 0.5
): Promise<SearchResult[]> {
// Generate embedding for the query
const queryEmbedding = await generateEmbedding(query);
// Call the similarity search RPC function
const { data, error } = await supabase.rpc('match_documents', {
query_embedding: queryEmbedding,
match_threshold: threshold,
match_count: topK,
});
if (error) {
throw error;
}
return data;
}الآن أضف دالة المطابقة في SQL Editor الخاص بـ Supabase:
-- Create the similarity search function
create or replace function match_documents(
query_embedding vector(1536),
match_threshold float,
match_count int
)
returns table (
id bigint,
content text,
metadata jsonb,
similarity float
)
language sql stable
as $$
select
documents.id,
documents.content,
documents.metadata,
1 - (documents.embedding <=> query_embedding) as similarity
from documents
where 1 - (documents.embedding <=> query_embedding) > match_threshold
order by documents.embedding <=> query_embedding
limit match_count;
$$;المعامل <=> يحسب مسافة جيب التمام. نحوله إلى تشابه بطرحه من 1.
الخطوة 6: بناء API للمحادثة RAG
أنشئ src/app/api/chat/route.ts:
import OpenAI from 'openai';
import { semanticSearch } from '@/lib/search';
import { NextResponse } from 'next/server';
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
export async function POST(request: Request) {
try {
const { message, conversationHistory = [] } = await request.json();
if (!message) {
return NextResponse.json(
{ error: 'No message provided' },
{ status: 400 }
);
}
// Step 1: Search for relevant documents
const relevantDocs = await semanticSearch(message, 5, 0.5);
// Step 2: Build context from retrieved documents
const context = relevantDocs
.map((doc, i) => `[Document ${i + 1}]\n${doc.content}`)
.join('\n\n');
// Step 3: Create the augmented prompt
const systemPrompt = `You are a helpful assistant that answers questions based on the provided context.
CONTEXT:
${context || 'No relevant documents found.'}
INSTRUCTIONS:
- Answer the user's question based on the context above
- If the context doesn't contain relevant information, say so
- Be concise but thorough
- Cite which document(s) you're referencing when applicable`;
// Step 4: Generate response with GPT-4
const completion = await openai.chat.completions.create({
model: 'gpt-4-turbo-preview',
messages: [
{ role: 'system', content: systemPrompt },
...conversationHistory,
{ role: 'user', content: message },
],
temperature: 0.7,
max_tokens: 1000,
});
const assistantMessage = completion.choices[0].message.content;
return NextResponse.json({
response: assistantMessage,
sources: relevantDocs.map((doc) => ({
id: doc.id,
preview: doc.content.slice(0, 200) + '...',
similarity: doc.similarity,
})),
});
} catch (error) {
console.error('Chat error:', error);
return NextResponse.json(
{ error: 'Failed to process chat' },
{ status: 500 }
);
}
}اختبار التنفيذ
شغّل خادم التطوير:
npm run devافتح http://localhost:3000 وجرب طرح أسئلة مثل:
- "ما هو RAG؟"
- "كيف تعمل تضمينات المتجهات؟"
- "لماذا يُستخدم pgvector؟"
يجب أن يستجيب روبوت المحادثة بمعلومات دقيقة بناءً على المستندات المضافة، موضحاً المصادر التي استخدمها.
الخلاصة
لقد بنيت روبوت محادثة مدعوماً بـ RAG يمكنه الإجابة على الأسئلة باستخدام بياناتك الخاصة. يوفر الجمع بين امتداد pgvector من Supabase وواجهات برمجة تطبيقات OpenAI أساساً قوياً وقابلاً للتوسع لتطبيقات الذكاء الاصطناعي.
النقاط الرئيسية:
- تضمينات المتجهات تمكّن البحث الدلالي بما يتجاوز مطابقة الكلمات المفتاحية
- pgvector يجلب إمكانيات البحث المتجهي إلى PostgreSQL
- RAG يرسخ استجابات LLM في بياناتك الفعلية
- Supabase يوفر خلفية كاملة بأقل إعداد
تتوسع هذه البنية بشكل جيد - أضف المزيد من المستندات، وحسّن استراتيجيات التقسيم، وضبط معلمات الاسترجاع مع نمو قاعدة معارفك.
الموارد:
ناقش مشروعك معنا
نحن هنا للمساعدة في احتياجات تطوير الويب الخاصة بك. حدد موعدًا لمناقشة مشروعك وكيف يمكننا مساعدتك.
دعنا نجد أفضل الحلول لاحتياجاتك.
مقالات ذات صلة

دليل دمج روبوت الدردشة الذكي: بناء واجهات محادثة ذكية
دليل شامل لدمج روبوتات الدردشة الذكية في تطبيقاتك باستخدام OpenAI وAnthropic Claude وElevenLabs. تعلم بناء روبوتات دردشة نصية وصوتية مع Next.js.

مركز دروس AI SDK: دليلك الشامل لبناء تطبيقات الذكاء الاصطناعي
دليلك الشامل لأدوات وSDKs الذكاء الاصطناعي. اعثر على دروس منظمة حسب مستوى الصعوبة تغطي Vercel AI SDK وModelFusion وOpenAI وAnthropic والمزيد.

بناء أداة تحليل SQL مدعومة بالذكاء الاصطناعي
دليل خطوة بخطوة لبناء تطبيق مدعوم بالذكاء الاصطناعي لتحليل SQL باستخدام اللغة الطبيعية.