كل وكيل ذكاء اصطناعي جربته من قبل ينساك فور انتهاء المحادثة. اطلب منه توصية بكتاب غداً وسيبدأ من الصفر تماماً، متجاهلاً كل ما أخبرته به سابقاً. هذه الفجوة بين جلسة المحادثة والاستمرارية الحقيقية هي تحديداً المشكلة التي يحلّها Mem0.
في هذا الدرس ستربط طبقة الذاكرة الدائمة من Mem0 بتطبيق Next.js باستخدام Vercel AI SDK v5. في النهاية ستحصل على روبوت محادثة يتذكر المعلومات عبر الجلسات المختلفة، ويُخصّص ردوده بناءً على التفاعلات السابقة، ويدير ذاكرة منفصلة لكل مستخدم.
المتطلبات الأساسية
قبل البدء تأكد من توفّر ما يلي:
- Node.js 18 أو أحدث
- مشروع Next.js 14 أو 15 (أو أنشئ واحداً جديداً كما هو موضح أدناه)
- مفتاح API من Mem0 — سجّل في mem0.ai (تتوفر خطة مجانية)
- مفتاح API من OpenAI (أو أي موفّر آخر تدعمه Vercel AI SDK)
- إلمام بـ React و TypeScript و Next.js App Router
ما الذي ستبنيه
مساعد ذكاء اصطناعي شخصي يستطيع:
- تذكّر التفضيلات المُعبَّر عنها في المحادثة — العادات الغذائية، التقنيات المفضلة، أسلوب التواصل
- استدعاء السياق السابق في كل جلسة جديدة دون أن يضطر المستخدم للتكرار
- عزل الذاكرة لكل مستخدم باستخدام معرّفات المستخدمين، بحيث يحصل كل شخص على مخزنه الخاص
- عرض لوحة ذاكرة تتيح للمستخدمين مراجعة ما يعرفه الوكيل عنهم وإدارته
لماذا تُهمّ الذاكرة الدائمة في وكلاء الذكاء الاصطناعي
نماذج اللغة الكبيرة اليوم عديمة الحالة بطبيعتها. كل طلب API مستقل تماماً عن سابقه. الحل الشائع — حشو سجل المحادثة كاملاً في نافذة السياق — ينهار عند الاستخدام طويل الأمد:
- تمتلئ نوافذ السياق مما يُجبر على الاقتطاع
- تكاليف التوكن ترتفع بشكل خطي مع طول المحادثة
- الاستمرارية بين الجلسات مستحيلة بدون إدارة يدوية للذاكرة
يعالج Mem0 هذا بطبقة ذاكرة دلالية تخزّن الحقائق المستخلصة، وتسترجع الأكثر صلة لكل استعلام، وتبقي حجم الذاكرة محدوداً بصرف النظر عن مدة تفاعل المستخدم.
الخطوة الأولى: إعداد المشروع
أنشئ مشروع Next.js جديداً أو استخدم مشروعاً موجوداً:
npx create-next-app@latest mem0-agent --typescript --tailwind --app
cd mem0-agentثبّت الحزم المطلوبة:
npm install ai @mem0/vercel-ai-provider mem0ai
npm install @ai-sdk/openaiأنشئ ملف .env.local في جذر المشروع:
MEM0_API_KEY=m0-مفتاحك-هنا
OPENAI_API_KEY=sk-مفتاحك-هناالخطوة الثانية: تهيئة موفّر Mem0
يوفّر Mem0 موفّراً رسمياً لـ Vercel AI SDK باسم @mem0/vercel-ai-provider. يُغلّف هذا الموفّر أي نموذج بطبقة ذاكرة تلقائية — تُستخلص الذكريات من كل رسالة وتُحقن كسياق في الدورة التالية.
أنشئ lib/mem0.ts:
import { createMem0 } from "@mem0/vercel-ai-provider";
export const mem0 = createMem0({
provider: "openai",
mem0ApiKey: process.env.MEM0_API_KEY!,
apiKey: process.env.OPENAI_API_KEY!,
});هذا كل الإعداد المطلوب. يتولى الموفّر تخزين الذاكرة واسترجاعها تلقائياً عند استخدامه نموذجاً في أي دالة من دوال Vercel AI SDK.
الخطوة الثالثة: بناء مسار API للمحادثة
أنشئ app/api/chat/route.ts:
import { streamText } from "ai";
import { mem0 } from "@/lib/mem0";
import { NextRequest } from "next/server";
export async function POST(request: NextRequest) {
const { messages, userId } = await request.json();
const result = streamText({
model: mem0("gpt-4o", { user_id: userId }),
system: `أنت مساعد شخصي مفيد. كن موجزاً ومُخصَّصاً.
استخدم ما تعرفه عن المستخدم لتخصيص ردودك.`,
messages,
});
return result.toDataStreamResponse();
}الفرق الجوهري عن مسار Vercel AI SDK القياسي هو خيار user_id المُمرَّر للنموذج. يستخدم Mem0 هذا للتقسيم — كل معرّف مستخدم يُعيَّن له مخزن ذاكرة معزول. يمكنك استخدام أي نص ثابت: UUID من Supabase، معرّف مستخدم من Clerk، أو رمز جلسة.
الخطوة الرابعة: إدارة الذكريات مباشرةً
للتحكم الدقيق، استخدم Node SDK الخاص بـ mem0ai جنباً إلى جنب مع الموفّر. أنشئ lib/memory-client.ts:
import MemoryClient from "mem0ai";
export const memoryClient = new MemoryClient({
apiKey: process.env.MEM0_API_KEY!,
});
export async function addMemories(
messages: Array<{ role: string; content: string }>,
userId: string
) {
return memoryClient.add(messages, { userId });
}
export async function searchMemories(query: string, userId: string) {
return memoryClient.search(query, {
filters: { userId },
limit: 10,
});
}
export async function getAllMemories(userId: string) {
return memoryClient.getAll({ filters: { userId } });
}
export async function deleteMemory(memoryId: string) {
return memoryClient.delete(memoryId);
}أنشئ الآن مسار API لإدارة الذاكرة في app/api/memories/route.ts:
import { NextRequest, NextResponse } from "next/server";
import {
searchMemories,
getAllMemories,
deleteMemory,
} from "@/lib/memory-client";
export async function GET(request: NextRequest) {
const userId = request.nextUrl.searchParams.get("userId");
const query = request.nextUrl.searchParams.get("query");
if (!userId) {
return NextResponse.json({ error: "userId مطلوب" }, { status: 400 });
}
const memories = query
? await searchMemories(query, userId)
: await getAllMemories(userId);
return NextResponse.json({ memories });
}
export async function DELETE(request: NextRequest) {
const { memoryId } = await request.json();
await deleteMemory(memoryId);
return NextResponse.json({ success: true });
}الخطوة الخامسة: بناء واجهة المحادثة
أنشئ components/Chat.tsx:
"use client";
import { useChat } from "ai/react";
import { useState } from "react";
interface ChatProps {
userId: string;
}
export default function Chat({ userId }: ChatProps) {
const [input, setInput] = useState("");
const { messages, append, isLoading } = useChat({
api: "/api/chat",
body: { userId },
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!input.trim()) return;
append({ role: "user", content: input });
setInput("");
};
return (
<div className="flex flex-col h-full">
<div className="flex-1 overflow-y-auto p-4 space-y-4">
{messages.length === 0 && (
<p className="text-gray-400 text-center mt-8">
ابدأ المحادثة. سأتذكرك بين الجلسات.
</p>
)}
{messages.map((m) => (
<div
key={m.id}
className={`flex ${m.role === "user" ? "justify-start" : "justify-end"}`}
>
<div
className={`max-w-[80%] rounded-2xl px-4 py-2 ${
m.role === "user"
? "bg-blue-600 text-white"
: "bg-gray-800 text-gray-100"
}`}
>
{m.content}
</div>
</div>
))}
{isLoading && (
<div className="flex justify-end">
<div className="bg-gray-800 rounded-2xl px-4 py-2 text-gray-400">
جاري التفكير...
</div>
</div>
)}
</div>
<form onSubmit={handleSubmit} className="p-4 border-t border-gray-700">
<div className="flex gap-2">
<button
type="submit"
disabled={isLoading || !input.trim()}
className="bg-blue-600 hover:bg-blue-700 disabled:opacity-50 text-white px-6 py-2 rounded-xl"
>
إرسال
</button>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="اكتب رسالتك..."
className="flex-1 bg-gray-800 rounded-xl px-4 py-2 text-white outline-none text-right"
dir="rtl"
/>
</div>
</form>
</div>
);
}الخطوة السادسة: إضافة لوحة الذكريات
منح المستخدمين رؤية لما يعرفه الوكيل عنهم يبني الثقة. أنشئ components/MemoriesPanel.tsx:
"use client";
import { useEffect, useState } from "react";
interface Memory {
id: string;
memory: string;
created_at: string;
}
export default function MemoriesPanel({ userId }: { userId: string }) {
const [memories, setMemories] = useState<Memory[]>([]);
const [loading, setLoading] = useState(true);
const fetchMemories = async () => {
setLoading(true);
const res = await fetch(`/api/memories?userId=${userId}`);
const data = await res.json();
setMemories(data.memories || []);
setLoading(false);
};
useEffect(() => {
fetchMemories();
}, [userId]);
const handleDelete = async (memoryId: string) => {
await fetch("/api/memories", {
method: "DELETE",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ memoryId }),
});
setMemories((prev) => prev.filter((m) => m.id !== memoryId));
};
if (loading) return <p className="text-gray-400 p-4">جاري تحميل الذكريات...</p>;
return (
<div className="p-4" dir="rtl">
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-semibold">ما أعرفه عنك</h2>
<button onClick={fetchMemories} className="text-sm text-blue-400 hover:underline">
تحديث
</button>
</div>
{memories.length === 0 ? (
<p className="text-gray-400 text-sm">
لا توجد ذكريات بعد. ابدأ المحادثة لبناء ملفك الشخصي.
</p>
) : (
<ul className="space-y-2">
{memories.map((m) => (
<li
key={m.id}
className="bg-gray-800 rounded-lg p-3 flex items-start justify-between gap-2"
>
<button
onClick={() => handleDelete(m.id)}
className="text-red-400 hover:text-red-300 text-xs shrink-0"
>
حذف
</button>
<span className="text-sm text-gray-200 text-right">{m.memory}</span>
</li>
))}
</ul>
)}
</div>
);
}الخطوة السابعة: تجميع كل شيء
حدّث app/page.tsx:
import Chat from "@/components/Chat";
import MemoriesPanel from "@/components/MemoriesPanel";
const USER_ID = "demo-user-001";
export default function Home() {
return (
<div className="flex h-screen bg-gray-900 text-white">
<aside className="w-72 border-r border-gray-700 overflow-y-auto">
<MemoriesPanel userId={USER_ID} />
</aside>
<div className="flex-1 flex flex-col max-w-2xl mx-auto border-x border-gray-700">
<header className="p-4 border-b border-gray-700 text-right" dir="rtl">
<h1 className="text-xl font-bold">المساعد الشخصي</h1>
<p className="text-sm text-gray-400">مدعوم بـ Mem0 و Vercel AI SDK</p>
</header>
<Chat userId={USER_ID} />
</div>
</div>
);
}الخطوة الثامنة: الذاكرة متعددة المستخدمين في الإنتاج
في تطبيق إنتاجي، استبدل USER_ID المُشفَّر بهوية مصادقة حقيقية. مثال باستخدام NextAuth:
// app/api/chat/route.ts
import { auth } from "@/auth";
import { streamText } from "ai";
import { mem0 } from "@/lib/mem0";
export async function POST(request: Request) {
const session = await auth();
const userId = session?.user?.id;
if (!userId) {
return new Response("غير مصرح", { status: 401 });
}
const { messages } = await request.json();
const result = streamText({
model: mem0("gpt-4o", { user_id: userId }),
system: "أنت مساعد شخصي مفيد يمتلك ذاكرة.",
messages,
});
return result.toDataStreamResponse();
}يضمن Mem0 أن ذكريات userId: "alice" لن تتسرب أبداً إلى userId: "bob". مخزن ذاكرة كل مستخدم معزول بالكامل.
الخطوة التاسعة: ضبط سلوك الذاكرة
يمكنك إضافة ذكريات يدوياً في أي وقت — مثلاً عندما يملأ مستخدم نموذج ملفه الشخصي:
await memoryClient.add(
[
{
role: "user",
content: "اسمي سارة. أنا عالمة بيانات في تونس.",
},
],
{ userId: "sara-123" }
);هذه الحقائق متاحة للوكيل في الطلب التالي مباشرةً، قبل أن تقول سارة كلمة واحدة في المحادثة.
اختبار التطبيق
شغّل خادم التطوير:
npm run devافتح http://localhost:3000. أخبر المساعد بعض الحقائق عن نفسك:
- "أنا نباتي وأُفضّل Python في عمل علم البيانات."
- "منطقتي الزمنية UTC+1 وأعمل عادةً في المساء المتأخر."
- "لا أحبّ الأسلوب الرسمي المُفرط."
أعد تحميل الصفحة لبدء جلسة جديدة. اطلب اقتراح وجبة أو نصيحة برمجية — يجب أن يستخدم المساعد الحقائق التي شاركتها في الجلسة السابقة دون أن تحتاج لتكرارها.
حل المشكلات الشائعة
الذكريات لا تظهر بعد المحادثة
- تأكد من صحة
MEM0_API_KEYفي.env.local - تحقق من لوحة تحكم Mem0 على app.mem0.ai للتحقق من وصول الكتابات
- تأكد من تمرير نفس
userIdباستمرار
السياق لا يُحقن في الردود
- موفّر
@mem0/vercel-ai-providerيحقن الذكريات تلقائياً. إذا بدت الردود عامة، جرّب طرح سؤال يرجع مباشرةً إلى حقائق سابقة. - الاسترجاع دلالي وليس مطابقة نصية. أعد صياغة سؤالك إن لزم.
تجاوز حدود الطبقة المجانية
- تسمح الطبقة المجانية من Mem0 بما يصل إلى 500 عملية ذاكرة شهرياً. للتطبيقات الإنتاجية، انتقل لخطة مدفوعة أو استضف النسخة مفتوحة المصدر من مستودع Mem0 على GitHub.
الخطوات التالية
بعد الحصول على وكيل ذكاء اصطناعي ذي حالة يعمل بشكل صحيح، إليك امتدادات طبيعية:
- البحث الدلالي في الذكريات — استخدم
memoryClient.search()مع استعلام لاسترجاع الذكريات الأكثر صلة فقط - الجمع مع RAG — اقرن ذاكرة Mem0 بنظام استرجاع وثائق لوكلاء يعرفون تفضيلات المستخدمين والمحتوى المتخصص معاً
- تصنيف الذكريات — اعلّم الذكريات ببيانات وصفية مثل
category: "work"أوcategory: "personal"وصفّها حسب السياق - ملخصات الجلسات — في نهاية كل جلسة، اطلب من النموذج إنتاج ملخص وخزّنه كذاكرة للاستمرارية طويلة المدى
الخلاصة
الوكلاء عديمو الحالة مفيدون؛ الوكلاء ذوو الحالة لا غنى عنهم. مع Mem0 و Vercel AI SDK v5، يستغرق إضافة طبقة ذاكرة دائمة لتطبيق Next.js أقل من 30 دقيقة من الإعداد وعدد محدود من ملفات TypeScript. النتيجة مساعد يزداد فائدةً مع كل محادثة — يتذكر تفضيلات المستخدم وسياقه وتاريخه دون أن تُدير جدول قاعدة بيانات واحداً يدوياً.
ابدأ صغيراً: انشر مسار المحادثة الأساسي، تحقق من تخزين الذكريات، ثم وسّع بلوحة الذكريات وعزل المستخدمين المتعددين. كل خطوة تُضاعف تجربة المستخدم بشكل ملموس.