الكتابات/tutorial/2026/05
Tutorial21 مايو 2026·28 دقيقة

بناء وكلاء الذكاء الاصطناعي باستخدام E2B Code Interpreter وNext.js

تعلم كيفية بناء وكلاء ذكاء اصطناعي جاهزين للإنتاج ينفذون كود بايثون وجافاسكريبت بأمان داخل صناديق رمل سحابية باستخدام E2B Code Interpreter وVercel AI SDK وNext.js 15. يغطي تحليل البيانات وتوليد المخططات ورفع الملفات وواجهة بث مباشرة.

النماذج اللغوية الكبيرة بارعة في كتابة الكود لكنها كارثية عندما يتعلق الأمر بتشغيله بأمان. إذا سمحت للنموذج بتنفيذ كود بايثون عشوائي عبر eval() على خادمك فأنت عمليا سلمته صلاحية shell كاملة. الحل الذي استقرت عليه الصناعة في عام 2025 كان صندوق الرمل السحابي الآمن، والأشهر بينها هو E2B Code Interpreter — وهو Firecracker microVM يقلع في أقل من 200 ميلي ثانية ويأتي مثبتا مسبقا بـبايثون وNode.js وJupyter وpandas وmatplotlib وأكثر من ألف حزمة إضافية، ويدمر نفسه بمجرد انتهائك.

في هذا الدرس سنقوم بربط E2B بتطبيق Next.js 15 باستخدام Vercel AI SDK وClaude (أو أي نموذج آخر يدعم استدعاء الأدوات) لنطلق تجربة "مفسر كود" تعمل بالفعل: محادثة يستطيع فيها النموذج رسم بيانات CSV وتشغيل تحليلات إحصائية وإرجاع صور وملفات في الوقت الفعلي.

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

تطبيق Next.js 15 يستطيع المستخدمون من خلاله:

  • رفع ملف CSV
  • طرح أسئلة بلغة طبيعية مثل "ما الارتباط بين الإيرادات وعدد الموظفين؟"
  • مشاهدة الوكيل وهو يكتب بايثون ويشغله داخل صندوق رمل معزول ويبث النصوص والمخططات والملفات القابلة للتحميل

في نهاية الدرس ستفهم الحلقة كاملة من البرومبت إلى استدعاء الأداة إلى التنفيذ في الصندوق إلى عرض النتيجة في واجهة المستخدم.

المتطلبات الأساسية

  • Node.js إصدار 20 أو أعلى (متطلب Next.js 15)
  • مدير حزم (يفضل pnpm)
  • مفتاح E2B API من e2b.dev (الباقة المجانية كافية لهذا الدرس)
  • مفتاح Anthropic API (أو OpenAI أو Google أو Groq — أي مزود يدعم استدعاء الأدوات)
  • إلمام أساسي بـReact Server Components وApp Router

الخطوة 1: إعداد المشروع

أنشئ مشروع Next.js 15 جديد مع TypeScript وTailwind.

pnpm create next-app@latest e2b-agent --typescript --tailwind --app --eslint
cd e2b-agent

ثبت AI SDK ومزود Anthropic وE2B Code Interpreter SDK.

pnpm add ai @ai-sdk/anthropic @ai-sdk/react @e2b/code-interpreter zod

ثلاث حزم مهمة هنا:

  • @e2b/code-interpreter — الـSDK الرسمي الذي يطلق الصناديق وينفذ الكود
  • ai و@ai-sdk/anthropic — Vercel AI SDK مع مزود Claude
  • @ai-sdk/react — خطاف useChat الذي يتولى البث في العميل

الخطوة 2: ضبط متغيرات البيئة

أنشئ ملف .env.local في جذر المشروع.

E2B_API_KEY=e2b_xxxxxxxxxxxxxxxx
ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxx

لا تقم بـcommit هذا الملف أبدا. أضفه إلى .gitignore إن لم يكن مضافا أصلا.

الخطوة 3: بناء مساعد الصندوق

أنشئ ملف lib/sandbox.ts. هذه الوحدة تتولى دورة حياة الصندوق ليبقى باقي التطبيق نظيفا.

import { Sandbox } from "@e2b/code-interpreter";
 
const TEMPLATE = "code-interpreter-v1";
const TIMEOUT_MS = 5 * 60 * 1000;
 
export async function createSandbox() {
  const sandbox = await Sandbox.create(TEMPLATE, {
    timeoutMs: TIMEOUT_MS,
  });
  return sandbox;
}
 
export async function runPython(sandboxId: string, code: string) {
  const sandbox = await Sandbox.connect(sandboxId);
  const execution = await sandbox.runCode(code, { language: "python" });
 
  return {
    stdout: execution.logs.stdout.join(""),
    stderr: execution.logs.stderr.join(""),
    results: execution.results.map((r) => ({
      text: r.text,
      png: r.png,
      html: r.html,
      json: r.json,
    })),
    error: execution.error?.value,
  };
}

بعض التفاصيل المهمة. Sandbox.create ترجع microVM ساخن في حوالي 150 ميلي ثانية. runCode تتوقف حتى ينتهي التنفيذ وتعطيك نتائج بأنواع محددة — stdout وstderr ومخرجات غنية (صور matplotlib بصيغة PNG وجداول pandas بـHTML) وأخطاء منظمة. نحن نحتفظ بـsandboxId ليتسنى لاستدعاءات الأدوات اللاحقة في نفس المحادثة إعادة استخدام نفس الجهاز الافتراضي والحفاظ على المتغيرات.

الخطوة 4: تعريف مخطط الأدوات

يستخدم Vercel AI SDK مكتبة Zod لوصف الأدوات. أنشئ ملف lib/tools.ts.

import { tool } from "ai";
import { z } from "zod";
import { runPython, createSandbox } from "./sandbox";
 
export function buildTools(sandboxIdRef: { current: string | null }) {
  return {
    execute_python: tool({
      description:
        "Execute Python code in a secure sandbox. Use this for data analysis, calculations, plotting charts, and file manipulation. Variables persist across calls within the same conversation.",
      parameters: z.object({
        code: z
          .string()
          .describe("Valid Python code. Use matplotlib for plots."),
      }),
      execute: async ({ code }) => {
        if (!sandboxIdRef.current) {
          const sandbox = await createSandbox();
          sandboxIdRef.current = sandbox.sandboxId;
        }
        return runPython(sandboxIdRef.current, code);
      },
    }),
  };
}

sandboxIdRef صندوق مشترك صغير يمرر بالمرجع، بحيث ينشئ أول استدعاء للأداة الصندوق وتعيد كل الاستدعاءات اللاحقة استخدامه. هذه هي الحيلة التي تجعل التفكير متعدد الخطوات ممكنا — يستطيع الوكيل تعريف متغير في الخطوة الأولى وقراءته في الخطوة الثالثة.

الخطوة 5: إنشاء مسار المحادثة

في App Router الخاص بـNext.js 15 يعيش البث من جهة الخادم داخل route handler. أنشئ app/api/chat/route.ts.

import { anthropic } from "@ai-sdk/anthropic";
import { streamText, convertToCoreMessages } from "ai";
import { buildTools } from "@/lib/tools";
 
export const maxDuration = 60;
 
export async function POST(req: Request) {
  const { messages, sandboxId } = await req.json();
  const sandboxIdRef = { current: sandboxId ?? null };
 
  const result = streamText({
    model: anthropic("claude-sonnet-4-6"),
    system:
      "You are a senior data analyst. When the user asks anything that requires computation, plotting, or file inspection, write and run Python in the sandbox rather than guessing. Always show your work briefly before calling the tool.",
    messages: convertToCoreMessages(messages),
    tools: buildTools(sandboxIdRef),
    maxSteps: 5,
    onFinish: ({ response }) => {
      response.headers = {
        ...(response.headers ?? {}),
        "x-sandbox-id": sandboxIdRef.current ?? "",
      };
    },
  });
 
  return result.toDataStreamResponse({
    headers: {
      "x-sandbox-id": sandboxIdRef.current ?? "",
    },
  });
}

ثلاث نقاط مهمة. maxSteps: 5 تسمح للنموذج أن يفكر وينفذ الكود ويقرأ النتيجة ثم ينفذ كودا آخر — بدونها سيستدعي الوكيل الأداة مرة واحدة ويتوقف. برومبت system يخبر النموذج صراحة بتفضيل التنفيذ على التخمين، وهذا هو الهدف الكامل من مفسرات الكود. ترويسة الاستجابة x-sandbox-id هي الطريقة التي نعيد بها معرف الصندوق إلى العميل ليعاد إرفاقه في الدور التالي.

الخطوة 6: بناء واجهة المحادثة

أنشئ ملف app/page.tsx.

"use client";
 
import { useChat } from "@ai-sdk/react";
import { useState } from "react";
 
export default function Home() {
  const [sandboxId, setSandboxId] = useState<string | null>(null);
 
  const { messages, input, handleInputChange, handleSubmit, isLoading } =
    useChat({
      api: "/api/chat",
      body: { sandboxId },
      onResponse: (response) => {
        const id = response.headers.get("x-sandbox-id");
        if (id) setSandboxId(id);
      },
    });
 
  return (
    <div className="mx-auto flex h-screen max-w-3xl flex-col p-6">
      <h1 className="mb-4 text-2xl font-semibold">E2B Code Interpreter</h1>
 
      <div className="flex-1 space-y-4 overflow-y-auto">
        {messages.map((m) => (
          <Message key={m.id} message={m} />
        ))}
      </div>
 
      <form onSubmit={handleSubmit} className="mt-4 flex gap-2">
        <input
          value={input}
          onChange={handleInputChange}
          placeholder="Ask the agent to analyse data..."
          className="flex-1 rounded border border-zinc-300 px-3 py-2"
          disabled={isLoading}
        />
        <button
          type="submit"
          className="rounded bg-black px-4 py-2 text-white disabled:opacity-50"
          disabled={isLoading}
        >
          Send
        </button>
      </form>
    </div>
  );
}

خطاف useChat يقوم بالجزء الثقيل — بث الرموز وإدارة حالة الرسائل ودمج body الذي يرسل sandboxId مع كل طلب. الـcallback المسمى onResponse يلتقط معرف الصندوق الجديد من ترويسات الاستجابة.

الخطوة 7: عرض استدعاءات الأدوات والمخرجات

أضف مكون Message تحت تصدير Home. هذا هو المكان الذي تحيي فيه نتائج الأدوات.

function Message({ message }: { message: any }) {
  return (
    <div className="rounded-lg border border-zinc-200 p-4">
      <div className="mb-2 text-xs font-medium uppercase text-zinc-500">
        {message.role}
      </div>
 
      {message.parts?.map((part: any, i: number) => {
        if (part.type === "text") {
          return (
            <p key={i} className="whitespace-pre-wrap text-sm">
              {part.text}
            </p>
          );
        }
 
        if (part.type === "tool-invocation") {
          const { toolName, state, args, result } = part.toolInvocation;
          return (
            <div key={i} className="my-2 rounded bg-zinc-50 p-3 text-xs">
              <div className="mb-1 font-mono text-zinc-600">
                tool: {toolName} ({state})
              </div>
              <pre className="overflow-x-auto whitespace-pre-wrap text-zinc-800">
                {args?.code}
              </pre>
              {result?.stdout && (
                <pre className="mt-2 border-t border-zinc-200 pt-2 text-zinc-700">
                  {result.stdout}
                </pre>
              )}
              {result?.results?.map((r: any, j: number) =>
                r.png ? (
                  <img
                    key={j}
                    src={`data:image/png;base64,${r.png}`}
                    alt="plot"
                    className="mt-2 rounded border border-zinc-200"
                  />
                ) : null,
              )}
            </div>
          );
        }
        return null;
      })}
    </div>
  );
}

مصفوفة parts هي التمثيل المنظم الذي يقدمه AI SDK لدور المساعد. أجزاء النص تعرض كفقرات، وأجزاء tool-invocation تعرض الكود الذي نفذ وstdout وصور PNG المضمنة من matplotlib. لا توجد خطوة تنزيل ولا خادم ملفات منفصل — البايتات تشحن داخل الاستجابة نفسها.

الخطوة 8: إضافة رفع الملفات

من أجل تحليل حقيقي يحتاج المستخدم إلى رفع بياناته. أنشئ app/api/upload/route.ts.

import { Sandbox } from "@e2b/code-interpreter";
import { createSandbox } from "@/lib/sandbox";
 
export async function POST(req: Request) {
  const formData = await req.formData();
  const file = formData.get("file") as File;
  let sandboxId = formData.get("sandboxId") as string | null;
 
  if (!sandboxId) {
    const sandbox = await createSandbox();
    sandboxId = sandbox.sandboxId;
  }
 
  const sandbox = await Sandbox.connect(sandboxId);
  const buffer = Buffer.from(await file.arrayBuffer());
  const path = `/home/user/${file.name}`;
  await sandbox.files.write(path, buffer);
 
  return Response.json({ sandboxId, path });
}

ثم اربط زر رفع في page.tsx. أرسل المسار الناتج كتلميح في رسالة المستخدم التالية — "رفعت ملف sales.csv في /home/user/sales.csv، هل يمكنك تحميله باستخدام pandas؟" — وسيلتقطه النموذج.

الخطوة 9: التجريب

شغل خادم التطوير.

pnpm dev

افتح http://localhost:3000 وجرب هذه البرومبتات:

  • "ولّد 1000 نقطة عشوائية من توزيع طبيعي وارسم لها هيستوغرام."
  • "احسب أول 20 رقم فيبوناتشي واعرضها كمخطط خطي."
  • "عرّف دالة ترجع رقم العدد الأولي رقم n. استخدمها لإيجاد العدد الأولي رقم 100."

ستشاهد المساعد يكتب بايثون، ويعرض صندوق الأداة الكود في لوحة رمادية، ويبث stdout أسفله، وتظهر أي مخططات matplotlib كصورة مضمنة. المتغيرات تستمر — الدور الثالث يستطيع الإشارة إلى ما عرّف في الدور الأول.

الخطوة 10: تحضير الإنتاج

بعض الأمور التي يجب فعلها قبل نشر هذا خارج بيئة العرض.

عزل صناديق المستخدمين. اليوم يعيش معرف الصندوق في العميل. يستطيع مستخدم متحمس إرسال معرف صندوق شخص آخر. خزن المعرف من جهة الخادم مرتبطا بالجلسة واقرأه من معالج المسار.

المهلات والتنظيف. صناديق E2B تنتهي تلقائيا بعد timeoutMs الذي ضبطته عند الإنشاء، لكن عليك أيضا استدعاء sandbox.kill() من webhook التنظيف عندما يغلق المستخدم تبويبه — الباقات المدفوعة تحاسب على الثانية من زمن تشغيل الصندوق.

حدود الموارد. استخدم إعدادات الموارد في E2B لتحديد سقف CPU والذاكرة لكل صندوق حتى لا يستنزف نموذج جامح رصيدك.

بث استهلاك الرموز. يعرض AI SDK كائن usage في onFinish. سجله في تحليلاتك حتى تستطيع إسناد التكلفة إلى المستخدم.

تعدد اللغات. نفس الصندوق يدعم جافاسكريبت وتايب سكريبت وR وbash. أضف أدوات إضافية (execute_javascript وexecute_bash) بمخططات Zod مطابقة لتترك للوكيل اختيار بيئة التشغيل المناسبة.

معالجة المشاكل

استدعاءات الأدوات لا تحدث أبدا. تحقق من أن maxSteps لا تقل عن 2. عند maxSteps: 1 سيخطط النموذج لكنه لن ينفذ.

البدء البارد للصندوق يبدو بطيئا. أول صندوق في المنطقة يستغرق بين 600 و800 ميلي ثانية. الصناديق اللاحقة في نفس المنطقة تنزل تحت 200 ميلي ثانية. للتطبيقات الحساسة لزمن الاستجابة، سخّن صندوقا مسبقا لكل جلسة نشطة.

مخططات matplotlib تعود فارغة. الخلفية الافتراضية في E2B غير تفاعلية. أضف plt.show() في نهاية كود الرسم — الـSDK يكتشف الشكل ويسلسله إلى PNG تلقائيا.

المتغيرات تختفي بين الأدوار. نسيت تمرير sandboxId. تأكد أن ترويسة الاستجابة تقرأ من العميل وتعاد إلى body الطلب التالي.

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

  • اجمع هذا مع وكلاء Mastra لتدفقات عمل متعددة الوكلاء حيث يخطط وكيل واحد وينفذ آخر
  • اقرنه مع Langfuse لتتبع كل تنفيذ كود من البداية إلى النهاية
  • ابنِ خط أنابيب RAG وكيلي حيث يحلل مفسر الكود المستندات المسترجعة بدلا من حشرها في البرومبت
  • استبدل Claude بـDeepSeek أو GPT-4.1 أو Gemini — السطر الوحيد الذي يتغير هو استيراد النموذج

الخاتمة

E2B Code Interpreter يحول أخطر قدرة في النموذج اللغوي — تنفيذ الكود التعسفي — إلى واحدة من أكثر القدرات أمانا. بأقل من 200 سطر من الكود الرابط تحصل على وكيل يحلل الجداول ويجري اختبارات إحصائية ويرسم مخططات ويعيد ملفات نهائية، كل ذلك داخل Firecracker microVM يمكن التخلص منها وتختفي بانتهاء المحادثة. نفس النمط يمتد إلى هندسة البيانات والحوسبة العلمية والنمذجة المالية وأي مجال يكون فيه "النموذج يكتب الكود والصندوق ينفذه" أفضل من "النموذج يخمن الإجابة."

اشحن هذا، راقبه، وسيتوقف وكلاؤك عن هلوسة الأرقام.