الكتابات/tutorial/2026/06
Tutorial27 يونيو 2026·24 دقيقة

بناء وكلاء ذكاء اصطناعي قابلين للمراقبة باستخدام VoltAgent وTypeScript

تعلّم كيفية بناء وكلاء ذكاء اصطناعي جاهزين للإنتاج بلغة TypeScript باستخدام VoltAgent، إطار العمل الذي يضع المراقبة أولاً. يغطي هذا الدرس العملي الوكلاء والأدوات والذاكرة الدائمة والوكلاء الفرعيين تحت مشرف وسير العمل والتتبّع المباشر عبر VoltOps.

معظم أطر عمل وكلاء الذكاء الاصطناعي تجعل الجزء السهل سهلاً، أي ربط نموذج لغوي بعدد من الأدوات، لكنها تترك الجزء الصعب غير مرئي. ففي اللحظة التي يستدعي فيها الوكيل الأداة الخاطئة أو يدخل في حلقة لا تنتهي أو يستهلك الرموز على مطالبة سيئة، تجد نفسك تحدّق في مخرجات console.log محاولاً إعادة تركيب ما حدث.

يتخذ VoltAgent الموقف المعاكس. إنه إطار عمل مفتوح المصدر بلغة TypeScript تكون فيه المراقبة (observability) مواطناً من الدرجة الأولى لا فكرة لاحقة. فكل تشغيل لوكيل، وكل استدعاء أداة، وكل تفويض لوكيل فرعي، وكل خطوة في سير العمل يُسجَّل ويظهر في لوحة تحكم مرئية تُسمّى VoltOps، وهي لوحة على نمط n8n لمراقبة كيفية «تفكير» وكلائك.

في هذا الدرس ستبني وكيل دعم عملاء من الصفر، وتمنحه أدوات وذاكرة دائمة، وتعرضه عبر HTTP، وتنسّق فريقاً من الوكلاء الفرعيين المتخصصين تحت مشرف، وتراقب كل خطوة مباشرةً في لوحة المطوّر.

المتطلبات المسبقة

قبل أن تبدأ، تأكد من توفّر:

  • Node.js 20+ مثبّتاً (node --version)
  • إلمام أساسي بـ TypeScript وasync/await
  • مفتاح API من مزوّد نموذج لغوي (يستخدم هذا الدرس OpenAI، لكن أي مزوّد لـ AI SDK يعمل)
  • محرّر شيفرة، ويُفضّل VS Code

لست بحاجة إلى قاعدة بيانات أو Docker أو أي حساب سحابي. يخزّن VoltAgent الذاكرة والتتبّعات في ملف SQLite محلي افتراضياً.

ما الذي ستبنيه

مساعد دعم:

  1. يجيب عن الأسئلة باستخدام أداة بحث عن الطلبات مخصّصة
  2. يتذكّر المحادثات عبر الطلبات بفضل الذاكرة الدائمة
  3. يعمل كخادم HTTP يمكنك استدعاؤه من أي واجهة أمامية
  4. يفوّض المهام المتخصصة (التلخيص، التنسيق) إلى وكلاء فرعيين
  5. يشغّل سير عمل حتمياً لأتمتة متعددة الخطوات
  6. يبثّ كل أثر تنفيذ إلى لوحة VoltOps

لنبدأ.

الخطوة 1: تهيئة المشروع

يوفّر VoltAgent مولّد مشاريع يجهّز TypeScript وخادم التطوير ووكيلاً مبدئياً. نفّذ:

npm create voltagent-app@latest support-agent

تطلب منك الأداة اسم المشروع ومزوّد الذكاء الاصطناعي ومفتاح API. اختر OpenAI (أو أي مزوّد لديك مفتاحه). عند الانتهاء:

cd support-agent
npm run dev

افتح الرابط الذي يطبعه (لوحة مطوّر VoltOps). لديك الآن وكيل يعمل وقابل للتتبّع. الآن لنفهمه ونعيد بناءه بوعي.

التبعيات الرئيسية التي أضافها المولّد:

# مثبّتة مسبقاً بواسطة المولّد — معروضة للمرجع
npm install @voltagent/core @voltagent/server-hono @voltagent/libsql @voltagent/logger
npm install @ai-sdk/openai          # مزوّد النموذج

ملاحظة حول حقل النموذج: يستخدم VoltAgent Vercel AI SDK مباشرةً. يمكنك تمرير سلسلة نصية بسيطة مثل "openai/gpt-4o-mini" وترك البوابة تحلّها، أو تمرير كائن LanguageModel مكتمل من @ai-sdk/openai. سنستخدم صيغة السلسلة للاختصار.

الخطوة 2: إنشاء أول وكيل

أنشئ src/agents/support.ts. الوكيل ما هو إلا اسم ومجموعة تعليمات (مطالبته النظامية) ونموذج:

import { Agent } from "@voltagent/core";
 
export const supportAgent = new Agent({
  name: "SupportAssistant",
  instructions:
    "You are a friendly customer-support assistant for an online store. " +
    "Answer concisely. If you need order details, use the available tools. " +
    "Never invent order information.",
  model: "openai/gpt-4o-mini",
});

الآن اربطه بنسخة VoltAgent واعرضه عبر HTTP. أنشئ src/index.ts:

import { VoltAgent } from "@voltagent/core";
import { honoServer } from "@voltagent/server-hono";
import { supportAgent } from "./agents/support";
 
new VoltAgent({
  agents: { support: supportAgent },
  server: honoServer(), // يبدأ على المنفذ 3141 افتراضياً
});

أعد تشغيل npm run dev. يُقلع الخادم على المنفذ 3141، ويظهر وكيلك في لوحة VoltOps. يمكنك محادثته مباشرةً من اللوحة، وكل رسالة تُنتج أثراً.

لاستدعاء الوكيل برمجياً بدلاً من ذلك، استخدم generateText:

const response = await supportAgent.generateText(
  "What are your shipping options?"
);
console.log(response.text);

أما لواجهات الزمن الحقيقي، فابثّ الرد رمزاً برمز:

const stream = await supportAgent.streamText("Explain your return policy");
 
for await (const chunk of stream.textStream) {
  process.stdout.write(chunk);
}

الخطوة 3: منح الوكيل أداة

روبوت دردشة لا يستطيع سوى الكلام ليس وكيلاً. تتيح الأدوات للنموذج اتخاذ إجراء، مثل الاستعلام عن قاعدة بيانات أو استدعاء API أو إجراء عملية حسابية. يعرّف VoltAgent الأدوات عبر createTool ويتحقّق من مدخلاتها باستخدام Zod، فتُفحَص الوسائط التي ينتجها النموذج للتأكد من نوعها قبل أن تعمل شيفرتك أصلاً.

أنشئ src/tools/order.ts:

import { createTool } from "@voltagent/core";
import { z } from "zod";
 
// لنفترض أن هذه قاعدة بياناتك
const ORDERS: Record<string, { status: string; eta: string }> = {
  "1001": { status: "shipped", eta: "2026-06-29" },
  "1002": { status: "processing", eta: "2026-07-02" },
};
 
export const lookupOrderTool = createTool({
  name: "lookup_order",
  description: "Look up the status and ETA of a customer order by its ID.",
  parameters: z.object({
    orderId: z.string().describe("The numeric order ID, e.g. 1001"),
  }),
  execute: async ({ orderId }) => {
    const order = ORDERS[orderId];
    if (!order) {
      return { found: false, message: "No order with that ID." };
    }
    return { found: true, ...order };
  },
});

اربطها بالوكيل:

import { Agent } from "@voltagent/core";
import { lookupOrderTool } from "../tools/order";
 
export const supportAgent = new Agent({
  name: "SupportAssistant",
  instructions:
    "You are a friendly customer-support assistant. " +
    "Use the lookup_order tool whenever a customer asks about an order. " +
    "Never invent order information.",
  model: "openai/gpt-4o-mini",
  tools: [lookupOrderTool], // [!code highlight]
});

الآن اسأله: «أين الطلب 1001؟». يقرّر النموذج استدعاء lookup_order، ويمرّر { orderId: "1001" }، ويستلم النتيجة، ثم يجيب بلغة طبيعية. في لوحة VoltOps سترى استدعاء الأداة كقطعة (span) منفصلة، بمدخلاتها ومخرجاتها والوقت الذي استغرقته.

اجعل أوصاف الأدوات محددة وموجّهة نحو الفعل. يختار النموذج الأدوات اعتماداً على name وdescription فقط، فعبارة «ابحث عن حالة الطلب بالمعرّف» أفضل من «مساعد طلبات» غامض. استخدم ‎.describe()‎ على كل حقل في Zod، فهذه التلميحات تذهب مباشرةً إلى مخطّط الأداة الذي يراه النموذج.

الخطوة 4: إضافة ذاكرة دائمة

افتراضياً كل استدعاء عديم الحالة. لتجعل الوكيل يتذكّر محادثةً عبر الطلبات، اربط مزوّد Memory مدعوماً بـ LibSQL (أي SQLite). أنشئ src/memory.ts:

import { Memory } from "@voltagent/core";
import { LibSQLMemoryAdapter } from "@voltagent/libsql";
 
export const sharedMemory = new Memory({
  storage: new LibSQLMemoryAdapter({
    url: "file:./.voltagent/memory.db",
  }),
});

اربطها بالوكيل ومرّر userIdconversationId اختيارياً) عند الاستدعاء، حتى يعرف VoltAgent أي محادثة يحمّلها ويُلحق بها:

import { sharedMemory } from "../memory";
 
export const supportAgent = new Agent({
  name: "SupportAssistant",
  instructions: "You are a friendly customer-support assistant.",
  model: "openai/gpt-4o-mini",
  tools: [lookupOrderTool],
  memory: sharedMemory, // [!code highlight]
});
// الدور الأول
await supportAgent.generateText("My name is Sami and order 1002 is late.", {
  userId: "cust-42",
  conversationId: "ticket-7",
});
 
// دور لاحق — المحادثة نفسها، يستحضر الوكيل السياق
const reply = await supportAgent.generateText("What was my name again?", {
  userId: "cust-42",
  conversationId: "ticket-7",
});
console.log(reply.text); // يشير إلى "Sami"

تبقى المحادثة في ملف SQLite، فتنجو من إعادة تشغيل الخادم. استبدل محوّل LibSQL برابط Turso (‏libsql://your-db.turso.io‏) أو محوّل Postgres عند الانتقال إلى الإنتاج، ولن تتغيّر شيفرة الوكيل.

الخطوة 5: تنسيق الوكلاء الفرعيين عبر مشرف

تصبح الوكلاء المفردة عسيرة الإدارة كلما تراكمت المسؤوليات. وجواب VoltAgent هو الوكلاء المشرفون: منسّق يفوّض إلى وكلاء فرعيين متخصصين، لكلٍّ منهم تعليماته الضيقة وأدواته.

ابنِ متخصصَين ومشرفاً واحداً في src/agents/team.ts:

import { Agent } from "@voltagent/core";
import { lookupOrderTool } from "../tools/order";
 
const orderAgent = new Agent({
  name: "OrderAgent",
  purpose: "Look up and explain order status.",
  instructions: "Use lookup_order to answer questions about orders.",
  model: "openai/gpt-4o-mini",
  tools: [lookupOrderTool],
});
 
const policyAgent = new Agent({
  name: "PolicyAgent",
  purpose: "Answer shipping and returns policy questions.",
  instructions:
    "Answer questions about shipping, returns, and refunds. " +
    "Free returns within 30 days; standard shipping is 3 to 5 days.",
  model: "openai/gpt-4o-mini",
});
 
export const supervisor = new Agent({
  name: "SupportSupervisor",
  instructions:
    "Route each customer question to the right specialist. " +
    "Use OrderAgent for order-specific questions and PolicyAgent for policy questions.",
  model: "openai/gpt-4o-mini",
  subAgents: [orderAgent, policyAgent], // [!code highlight]
});

يتحكّم حقل purpose الاختياري في كيفية إدراك المشرف لكل وكيل فرعي، فهو الملخّص القصير الذي يقرأه المنسّق عند تحديد من يفوّضه، منفصلاً عن instructions الأطول التي يتبعها الوكيل الفرعي نفسه.

الآن استدعاء واحد يتفرّع تلقائياً:

const answer = await supervisor.generateText(
  "Is order 1001 shipped, and can I return it if I don't like it?"
);

يفوّض المشرف شطر الطلب إلى OrderAgent وشطر السياسة إلى PolicyAgent، ثم يدمج ردّيهما. في VoltOps سترى شجرة التفويض: قطعة المشرف، وقطعتَي delegate_task التابعتين، واستدعاء الأداة المتداخل داخل OrderAgent.

إلغاء التشغيل

ينبغي أن تكون الاستدعاءات الطويلة متعددة الوكلاء قابلة للإلغاء. مرّر AbortController فتنتشر الإشارة إلى كل وكيل فرعي وكل أداة:

const controller = new AbortController();
setTimeout(() => controller.abort("Deadline reached"), 10_000);
 
const response = await supervisor.streamText("Research and summarize all open tickets", {
  abortController: controller,
});

الخطوة 6: بناء سير عمل حتمي

الوكلاء رائعون حين تريد للنموذج أن يقرّر ما يفعله. لكنك أحياناً تريد تسلسلاً ثابتاً، تَحقّقْ ثم أَثرِ ثم أَبلِغْ، مع استخدام النموذج اللغوي في خطوات محددة فقط. هنا تأتي سير العمل (workflows). تعمل كسلاسل مكتوبة الأنواع وخطوة بخطوة، ولها سجل تشغيل خاص ودائم.

أنشئ src/workflows/triage.ts:

import { createWorkflowChain } from "@voltagent/core";
import { z } from "zod";
import { supportAgent } from "../agents/support";
 
export const triageWorkflow = createWorkflowChain({
  id: "ticket-triage",
  name: "Ticket Triage",
  input: z.object({ message: z.string() }),
  result: z.object({ category: z.string(), reply: z.string() }),
})
  .andThen({
    id: "classify",
    execute: async ({ data }) => {
      const category = data.message.toLowerCase().includes("order")
        ? "order"
        : "general";
      return { ...data, category };
    },
  })
  .andThen({
    id: "respond",
    execute: async ({ data }) => {
      const res = await supportAgent.generateText(data.message);
      return { category: data.category, reply: res.text };
    },
  });

سجّل سير العمل على نسخة VoltAgent ليظهر في اللوحة ويحصل على سجل تشغيل دائم:

import { VoltAgent, Memory } from "@voltagent/core";
import { honoServer } from "@voltagent/server-hono";
import { LibSQLMemoryAdapter } from "@voltagent/libsql";
import { supervisor } from "./agents/team";
import { triageWorkflow } from "./workflows/triage";
 
new VoltAgent({
  agents: { support: supervisor },
  workflows: { triage: triageWorkflow },
  workflowMemory: new Memory({
    storage: new LibSQLMemoryAdapter({ url: "file:./.voltagent/workflows.db" }),
  }),
  server: honoServer({ port: 3141 }),
});

مدخلات ومخرجات كل خطوة andThen مكتوبة الأنواع من طرف إلى طرف عبر Zod، وكل تشغيل قابل لإعادة التشغيل في اللوحة. هنا تثبت مراقبة VoltAgent جدارتها فعلاً: سير عمل يتعطّل في الخطوة 3 من 5 يريك الخطوة بالضبط مع البيانات التي تدفّقت إليها.

الخطوة 7: تفعيل المراقبة الكاملة

حتى الآن تعيش التتبّعات في الذاكرة لجلسة التطوير. لجعلها دائمة، ولاستخدام لوحة VoltOps المستضافة لمراقبة الإنتاج، أضِف مزوّد VoltAgentObservability ومسجّلاً منظّماً.

import {
  VoltAgent,
  VoltAgentObservability,
} from "@voltagent/core";
import { honoServer } from "@voltagent/server-hono";
import { createPinoLogger } from "@voltagent/logger";
import { LibSQLObservabilityAdapter } from "@voltagent/libsql";
import { supervisor } from "./agents/team";
 
const logger = createPinoLogger({ name: "support-agent", level: "info" });
 
new VoltAgent({
  agents: { support: supervisor },
  server: honoServer(),
  logger,
  observability: new VoltAgentObservability({
    logger,
    storage: new LibSQLObservabilityAdapter({
      // ملف محلي (افتراضي): ./.voltagent/observability.db
      // للإنتاج عبر Turso:
      // url: "libsql://your-db.turso.io",
      // authToken: process.env.TURSO_AUTH_TOKEN,
    }),
  }),
});

لبثّ التتبّعات إلى لوحة VoltOps المستضافة، أنشئ مشروعاً هناك، ثم اضبط المفاتيح في .env:

# .env
VOLTAGENT_PUBLIC_KEY=pk_...
VOLTAGENT_SECRET_KEY=sk_...
OPENAI_API_KEY=sk-...

يلتقط VoltAgent هذه المتغيرات تلقائياً ويعيد توجيه التتبّعات، فتحصل على تتبّع بمستوى الإنتاج وساحات اختبار للمطالبات وإعادة تشغيل للجلسات دون أي تجهيز يدوي.

لا تُودِع ملف .env أبداً في نظام التحكّم بالإصدارات. أضِفه إلى .gitignore. يمنح VOLTAGENT_SECRET_KEY وصولاً كاملاً إلى مشروع المراقبة الخاص بك، فعامله ككلمة مرور وحمّله من مدير أسرار في الإنتاج.

اختبار التطبيق

شغّل الخادم واطرق عليه بـ curl. يعرض خادم Hono في VoltAgent نقطة نهاية للتوليد لكل وكيل:

curl -X POST http://localhost:3141/agents/support/text \
  -H "Content-Type: application/json" \
  -d '{"input": "Where is order 1001?"}'

يجب أن تستلم JSON بالإجابة، وأن يظهر أثر مطابق في اللوحة خلال ثانية. تحقّق من كل قدرة:

  • استخدام الأداة — اسأل «أين الطلب 1001؟» وتأكّد من ظهور قطعة lookup_order
  • الذاكرة — أرسِل رسالتين بنفس userId وتحقّق من أن الثانية تستحضر الأولى
  • التفويض — اطرح سؤالاً يخلط الطلب والسياسة وتأكّد من ظهور قطعتَي وكيل فرعي
  • سير العمل — شغّل سير عمل الفرز وتأكّد من تشغيل الخطوتين بالترتيب

استكشاف الأخطاء وإصلاحها

الوكيل لا يستدعي أداتي أبداً. يقرّر النموذج اعتماداً على name وdescription للأداة. اجعلهما صريحين ومتصدّرين بفعل، وتأكّد من أن طلب المستخدم يتطابق فعلاً مع الأداة. تحقّق من الأثر: إذا أجاب النموذج دون قطعة أداة، فالوصف غالباً كان غامضاً جداً.

الذاكرة لا تبقى عبر إعادة التشغيل. تأكّد من أنك مرّرت url (مسار ملف) إلى LibSQLMemoryAdapter. بدون رابط قد يستخدم مخزناً في الذاكرة يُعاد ضبطه عند إعادة التشغيل. وتأكّد أيضاً من تمرير نفس userId وconversationId في كل دور.

أخطاء المزوّد/المصادقة. تأكّد من تثبيت حزمة مزوّد AI SDK المطابقة (@ai-sdk/openai) ووجود مفتاح API في بيئتك. لا تُحلّ سلسلة "provider/model" إلا إذا كان ذلك المزوّد متاحاً.

التتبّعات لا تصل إلى اللوحة المستضافة. تحقّق مرتين من ضبط كلٍّ من VOLTAGENT_PUBLIC_KEY وVOLTAGENT_SECRET_KEY ومن أن العملية حمّلت فعلاً ملف .env.

ملاحظة لفرق منطقة الشرق الأوسط وشمال إفريقيا

موضع البيانات (data residency) مهم بموجب الهيئة الوطنية لحماية المعطيات الشخصية INPDP في تونس ونظام PDPL في السعودية. يساعد VoltAgent هنا بطريقتين. أولاً، تتخلف الذاكرة والمراقبة إلى ملفات SQLite محلية (LibSQL)، فلا شيء يغادر جهازك ما لم تختر اللوحة المستضافة أو رابط Turso/Postgres بعيداً، أي أنك تتحكّم بمكان بيانات المحادثات والتتبّعات. ثانياً، لأن طبقة النموذج هي AI SDK القياسية، يمكنك توجيه الوكلاء إلى نموذج مستضاف إقليمياً أو ذاتي الاستضافة (عبر نقطة نهاية متوافقة مع OpenAI) دون إعادة كتابة منطق الوكيل، مع إبقاء الاستدلال والذاكرة والتتبّعات داخل نطاقك القضائي عندما يستوجب الامتثال ذلك.

الخطوات التالية

  • أضِف أداة بحث متّجهي (vector search) ليجيب الوكيل من قاعدة معرفتك الخاصة
  • اربط أدوات خارجية عبر بروتوكول سياق النموذج (MCP) بدلاً من كتابة كلٍّ منها يدوياً
  • استبدل LibSQL بـ Postgres حين تتجاوز ملفاً واحداً
  • اربط نقطة نهاية HTTP بواجهة Next.js أمامية ذات بثّ مباشر
  • استكشف ساحة اختبار المطالبات في VoltOps لاختبار التعليمات A/B قبل الإطلاق

دروس ذات صلة على noqta.tn: بناء الوكلاء بإطار Mastra، وClaude Agent SDK، وإضافة ذاكرة دائمة بـ Mem0.

الخلاصة

لقد بنيت نظام وكلاء كاملاً وقابلاً للمراقبة بلغة TypeScript: وكيل دعم يستخدم الأدوات، وذاكرة دائمة، ومشرفاً يفوّض إلى متخصصين، وسير عمل مكتوب الأنواع، وتتبّعاً كاملاً عبر VoltOps. الدرس الذي يرسّخه VoltAgent هو أن الوكلاء الذين لا تراهم وكلاء لا يمكنك الوثوق بهم في الإنتاج، وأن جعل كل خطوة قابلة للتتبّع من أول سطر شيفرة يحوّل تنقيح نظام نموذج لغوي من تخمين إلى قراءة خط زمني. ابنِ صغيراً، وراقب التتبّعات، ونمِّ فريق وكلائك خطوةً قابلةً للمراقبة في كل مرة.