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

واجهة Responses API من OpenAI: بناء وكلاء ذكاء اصطناعي بالأدوات في TypeScript

أتقن واجهة Responses API من OpenAI — البديل الجاهز للإنتاج عن واجهة Assistants API المهملة. ابنِ وكلاء ذكاء اصطناعي بالبحث على الويب ومفسر الكود والأدوات المخصصة والبث المباشر في TypeScript وNext.js 15.

واجهة Assistants API من OpenAI ستُغلق في 26 أغسطس 2026. إذا كان تطبيقك يعتمد عليها — خيوط المحادثة، والتنفيذ، وحلقات الاستطلاع، وكائنات المساعد — فأنت بحاجة إلى الهجرة الآن. البديل هو Responses API، واجهة أنظف وأسرع وأكثر قدرة تُلخّص كامل سير عمل Assistants في استدعاء دالة واحد.

يأخذك هذا الدرس التفصيلي عبر كل شيء: الاستخدام الأساسي، والأدوات المدمجة (البحث على الويب ومفسر الكود)، واستدعاء الدوال المخصصة، والبث المباشر، وإدارة المحادثات متعددة الأدوار، ودليل هجرة كامل. في النهاية، سيكون لتطبيق Next.js 15 الخاص بك خلفية وكيل ذكاء اصطناعي جاهزة للإنتاج.

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

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

  • Node.js 20 أو أحدث
  • حساب OpenAI مع مفتاح API (من platform.openai.com/api-keys)
  • مشروع Next.js 15 (npx create-next-app@latest my-agent --typescript)
  • إلمام بـ TypeScript وأنماط async/await

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

مساعد بحث ذكي متعدد اللغات في Next.js 15 يستطيع:

  • الإجابة على الأسئلة مستنداً إلى بحث ويب في الوقت الفعلي
  • تنفيذ كود Python عند الطلب باستخدام مفسر الكود
  • استدعاء دوال مخصصة لجلب بيانات منظمة
  • بث الردود كلمة بكلمة إلى المتصفح
  • الحفاظ على سياق المحادثة متعددة الأدوار دون إعادة إرسال التاريخ الكامل

ما هي Responses API؟

Responses API هي واجهة OpenAI الموحدة للذكاء الاصطناعي الوكيل، أُطلقت في مطلع 2025 وأصبحت المسار الموصى به لجميع المشاريع الجديدة. النموذج الذهني بسيط:

  • ترسل عناصر الإدخال (رسائل، نتائج الأدوات)
  • تستقبل عناصر الإخراج (نص، استدعاءات دوال، نتائج أدوات، آثار التفكير)
  • تتولى الواجهة البرمجية الحلقة الوكيلية — تستدعي الأدوات المدمجة، وتُنفّذ استدعاءات الدوال، وتُسلسلها تلقائياً ضمن طلب واحد

لماذا هي أفضل من Assistants API؟

كانت Assistants API تتطلب تنسيق أربعة كائنات منفصلة — المساعدون والخيوط والرسائل والتنفيذات — إضافة إلى حلقة استطلاع لانتظار الاكتمال. كان تطبيق الدردشة البسيط يحتاج ستة استدعاءات API. تُلخّص Responses API كل ذلك في استدعاء responses.create() واحد. يتحسن استخدام ذاكرة التخزين المؤقت بنسبة 40–80%، مما يُخفّض التكاليف بشكل ملحوظ. كما تحصل على إمكانات أحدث: البحث العميق، وخوادم MCP البعيدة، واستخدام الكمبيوتر.

الخطوة 1: تثبيت SDK وإعداده

ثبّت أحدث إصدار من SDK لـ Node.js:

npm install openai

أضف مفتاح API إلى .env.local:

OPENAI_API_KEY=sk-proj-مفتاحك-هنا

أنشئ عميل OpenAI مشترك في lib/openai.ts:

import OpenAI from "openai";
 
export const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
  maxRetries: 3,
});

إعداد maxRetries: 3 يمنحك تراجعاً أسياً تلقائياً للأخطاء العابرة — وهو أمر مهم للإنتاج.

الخطوة 2: أول استجابة لك

أنشئ مسار API أساسي في app/api/chat/route.ts:

import { openai } from "@/lib/openai";
import { NextResponse } from "next/server";
 
export async function POST(req: Request) {
  const { message } = await req.json();
 
  const response = await openai.responses.create({
    model: "gpt-4.5",
    instructions: "أنت مساعد بحث مفيد.",
    input: message,
  });
 
  return NextResponse.json({ text: response.output_text });
}

ثلاثة معاملات رئيسية:

  • model — النموذج المراد استخدامه (gpt-4.5، gpt-4.5-mini، o3، إلخ)
  • instructions — يحل محل رسالة النظام؛ يضبط شخصية الوكيل وسلوكه
  • input — رسالة المستخدم (نص أو مصفوفة من عناصر الإدخال)

response.output_text هي خاصية مختصرة تستخرج أول محتوى نصي من الاستجابة.

الخطوة 3: عناصر الإدخال والإخراج

للمحادثات متعددة الأدوار، مرر مصفوفة من العناصر إلى input:

const response = await openai.responses.create({
  model: "gpt-4.5",
  input: [
    { role: "user", content: "ما هي عاصمة تونس؟" },
    { role: "assistant", content: "عاصمة تونس هي مدينة تونس." },
    { role: "user", content: "ما هو عدد سكانها؟" },
  ],
});

تحتوي مصفوفة response.output على عناصر إخراج مصنّفة. تحقق دائماً من item.type قبل الوصول إلى الحقول الخاصة بالنوع:

for (const item of response.output) {
  if (item.type === "message") {
    for (const part of item.content) {
      if (part.type === "output_text") {
        console.log(part.text);
      }
    }
  }
}

أنواع عناصر الإخراج الممكنة: message، وfunction_call، وfunction_call_output، وreasoning، وweb_search_call.

الخطوة 4: أداة مدمجة — البحث على الويب

فعّل البحث على الويب في الوقت الفعلي بإضافة { type: "web_search_preview" } إلى مصفوفة tools. يقرر النموذج باستقلالية متى يكون البحث ضرورياً:

const response = await openai.responses.create({
  model: "gpt-4.5",
  instructions: "أنت مساعد بحث. اذكر مصادرك دائماً.",
  input: "ما هي أبرز مميزات Responses API من OpenAI الصادرة في 2026؟",
  tools: [{ type: "web_search_preview" }],
});
 
console.log(response.output_text);

تتضمن نتائج البحث تعليقات توضيحية — استشهادات URL مضمّنة. استخرجها من الاستجابة:

for (const item of response.output) {
  if (item.type === "message") {
    for (const part of item.content) {
      if (part.type === "output_text" && part.annotations) {
        for (const ann of part.annotations) {
          if (ann.type === "url_citation") {
            console.log(`المصدر: ${ann.title} — ${ann.url}`);
          }
        }
      }
    }
  }
}

تحصل على إجابات موثوقة ومستشهد بها دون أي إعداد لواجهة بحث خارجية.

الخطوة 5: أداة مدمجة — مفسر الكود

يُشغّل مفسر الكود Python في حاوية معزولة. استخدمه للرياضيات وتحليل البيانات ومعالجة الملفات وإنشاء المخططات البيانية:

const response = await openai.responses.create({
  model: "gpt-4.5",
  instructions: "أنت محلل بيانات. اعرض دائماً عملك بالكود.",
  input: "احسب الفائدة المركبة على 10,000 دولار بمعدل 7% سنوياً لمدة 10 سنوات. اعرض كل سنة.",
  tools: [
    {
      type: "code_interpreter",
      container: { type: "auto" },
    },
  ],
});
 
console.log(response.output_text);

يكتب النموذج كود Python، يُنفّذه داخل الحاوية، ويُرجع النتائج كجزء من الاستجابة. الملفات المنتجة (مخططات بيانية، ملفات CSV) تُشار إليها بـ file_id في الإخراج ويمكن تنزيلها عبر Files API.

ملاحظة: يتطلب مفسر الكود بيئة Node.js الكاملة على Vercel — وليس بيئة Edge.

الخطوة 6: أدوات الدوال المخصصة

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

تعريف الأداة:

const getWeatherTool = {
  type: "function" as const,
  name: "get_weather",
  description: "الحصول على أحوال الطقس الحالية لمدينة ما.",
  parameters: {
    type: "object",
    properties: {
      city: { type: "string", description: "اسم المدينة، مثل تونس" },
      unit: {
        type: "string",
        enum: ["celsius", "fahrenheit"],
        description: "وحدة درجة الحرارة",
      },
    },
    required: ["city"],
    additionalProperties: false,
  },
  strict: true,
};

ضبط strict: true يُفعّل الإخراج المنظم لمعطيات الدوال — يُرجع النموذج دائماً JSON صحيح يطابق مخططك.

تشغيل الحلقة الوكيلية:

import type OpenAI from "openai";
 
async function runAgent(userMessage: string): Promise<string> {
  let input: string | OpenAI.Responses.InputItem[] = userMessage;
 
  while (true) {
    const response = await openai.responses.create({
      model: "gpt-4.5",
      input,
      tools: [getWeatherTool],
    });
 
    const functionCall = response.output.find(
      (item): item is OpenAI.Responses.FunctionCallItem =>
        item.type === "function_call"
    );
 
    if (!functionCall) {
      return response.output_text;
    }
 
    const args = JSON.parse(functionCall.arguments) as {
      city: string;
      unit?: string;
    };
    const weather = await fetchWeather(args.city, args.unit ?? "celsius");
 
    // أضف نتيجة الدالة وأعد الحلقة
    input = [
      { role: "user", content: userMessage },
      ...response.output,
      {
        type: "function_call_output" as const,
        call_id: functionCall.call_id,
        output: JSON.stringify(weather),
      },
    ];
  }
}
 
async function fetchWeather(city: string, unit: string) {
  // استدع واجهة طقس حقيقية هنا في الإنتاج
  return { city, temperature: 24, unit, condition: "مشمس" };
}

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

الخطوة 7: بث الاستجابات

البث يوفر تجربة مستخدم أفضل بكثير للردود الطويلة. استخدم openai.responses.stream():

// app/api/chat/stream/route.ts
import { openai } from "@/lib/openai";
 
export const runtime = "nodejs";
 
export async function POST(req: Request) {
  const { message } = await req.json();
 
  const encoder = new TextEncoder();
 
  const readable = new ReadableStream({
    async start(controller) {
      const stream = openai.responses.stream({
        model: "gpt-4.5",
        instructions: "أنت مساعد مفيد.",
        input: message,
        tools: [{ type: "web_search_preview" }],
      });
 
      for await (const event of stream) {
        if (
          event.type === "response.output_text.delta" &&
          event.delta
        ) {
          controller.enqueue(encoder.encode(event.delta));
        }
      }
 
      controller.close();
    },
  });
 
  return new Response(readable, {
    headers: {
      "Content-Type": "text/plain; charset=utf-8",
      "Cache-Control": "no-cache",
    },
  });
}

قراءة البث على جانب العميل:

"use client";
import { useState } from "react";
 
export function ChatBox() {
  const [output, setOutput] = useState("");
 
  async function ask(message: string) {
    setOutput("");
    const res = await fetch("/api/chat/stream", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ message }),
    });
 
    const reader = res.body!.getReader();
    const decoder = new TextDecoder();
 
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      setOutput((prev) => prev + decoder.decode(value));
    }
  }
 
  return (
    <div className="p-4">
      <button
        className="px-4 py-2 bg-blue-600 text-white rounded"
        onClick={() => ask("ما آخر المستجدات في الذكاء الاصطناعي؟")}
      >
        اسأل
      </button>
      <p className="mt-4 whitespace-pre-wrap">{output}</p>
    </div>
  );
}

الخطوة 8: المحادثات متعددة الأدوار باستخدام previous_response_id

بدلاً من إعادة إرسال تاريخ المحادثة الكامل في كل دور، استخدم previous_response_id. يُخزّن OpenAI السياق السابق من جهته:

export async function POST(req: Request) {
  const { message, previousResponseId } = await req.json();
 
  const response = await openai.responses.create({
    model: "gpt-4.5",
    input: message,
    previous_response_id: previousResponseId ?? undefined,
    tools: [{ type: "web_search_preview" }],
  });
 
  return NextResponse.json({
    text: response.output_text,
    responseId: response.id,
  });
}

يُخزّن عميلك responseId من كل استجابة ويُمرّره كـ previousResponseId في الطلب التالي. هذا أكثر كفاءة من مصفوفات التاريخ اليدوية — وتدفع مقابل الرموز المخزّنة مؤقتاً بجزء بسيط من سعر الرمز المُدخَل.

الخطوة 9: مسار Next.js 15 الكامل للإنتاج

نضع معاً كل الأنماط السابقة في مسار API كامل:

// app/api/agent/route.ts
import { openai } from "@/lib/openai";
import { NextResponse } from "next/server";
import type OpenAI from "openai";
 
export const runtime = "nodejs";
 
const searchTool: OpenAI.Responses.Tool = { type: "web_search_preview" };
const codeTool: OpenAI.Responses.Tool = {
  type: "code_interpreter",
  container: { type: "auto" },
};
 
export async function POST(req: Request) {
  const {
    message,
    previousResponseId,
    enableSearch = true,
    enableCode = false,
  } = await req.json();
 
  const tools: OpenAI.Responses.Tool[] = [
    ...(enableSearch ? [searchTool] : []),
    ...(enableCode ? [codeTool] : []),
  ];
 
  try {
    const response = await openai.responses.create({
      model: "gpt-4.5",
      instructions:
        "أنت مساعد بحث متخصص للمطورين في منطقة الشرق الأوسط وشمال أفريقيا. " +
        "استشهد بالمصادر عند استخدام البحث على الويب. اعرض الكود عند تحليل البيانات.",
      input: message,
      tools: tools.length > 0 ? tools : undefined,
      previous_response_id: previousResponseId ?? undefined,
    });
 
    return NextResponse.json({
      text: response.output_text,
      responseId: response.id,
      usage: {
        inputTokens: response.usage?.input_tokens,
        outputTokens: response.usage?.output_tokens,
        cachedTokens: response.usage?.input_tokens_details?.cached_tokens,
      },
    });
  } catch (error) {
    if (error instanceof OpenAI.APIError) {
      return NextResponse.json(
        { error: error.message },
        { status: error.status ?? 500 }
      );
    }
    throw error;
  }
}

الخطوة 10: الهجرة من Assistants API

إليك الخريطة الكاملة للمفاهيم لتوجيه هجرتك:

Assistants APIمكافئ Responses API
assistants.create()معامل instructions في responses.create()
threads.create()previous_response_id (خفيف) أو Conversations API (دائم)
threads.messages.create()معامل input
threads.runs.create()responses.create()
حلقة استطلاع التنفيذغير مطلوبة — متزامنة افتراضياً
threads.messages.list()مصفوفة response.output
أداة بحث الملفات{ type: "file_search", vector_store_ids: [...] }
أداة مفسر الكود{ type: "code_interpreter", container: { type: "auto" } }
أدوات الدوال{ type: "function", name, parameters } (نفس تنسيق المخطط)

قبل (Assistants API — 6 استدعاءات API + استطلاع):

const assistant = await openai.beta.assistants.create({
  name: "Research Bot",
  instructions: "أنت مساعد بحث.",
  tools: [{ type: "web_search" }],
  model: "gpt-4-turbo",
});
 
const thread = await openai.beta.threads.create();
await openai.beta.threads.messages.create(thread.id, {
  role: "user",
  content: userMessage,
});
 
let run = await openai.beta.threads.runs.create(thread.id, {
  assistant_id: assistant.id,
});
 
while (run.status !== "completed") {
  await new Promise((r) => setTimeout(r, 1000));
  run = await openai.beta.threads.runs.retrieve(thread.id, run.id);
}
 
const messages = await openai.beta.threads.messages.list(thread.id);
const text = messages.data[0].content[0].text.value;

بعد (Responses API — استدعاء API واحد):

const response = await openai.responses.create({
  model: "gpt-4.5",
  instructions: "أنت مساعد بحث.",
  input: userMessage,
  tools: [{ type: "web_search_preview" }],
});
 
const text = response.output_text;

الفرق واضح — ما كان يحتاج 6 استدعاءات API وحلقة استطلاع أصبح الآن await واحداً.

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

خطأ ERR_STREAM_WRITE_AFTER_END على Edge runtime: يتطلب مفسر الكود بيئة Node.js كاملة. أضف export const runtime = "nodejs" إلى أي مسار يستخدمه.

حلقات استدعاء أدوات لا نهاية لها: أضف حارس maxTurns إلى حلقة while وأرجع خطأ إذا تجاوزها. حد معقول هو 10 أدوار.

انقطاع البث على Vercel: زد maxDuration للدالة في next.config.ts للمسارات التي تستخدم البحث العميق أو مفسر الكود — قد تعمل حتى 30 ثانية.

أخطاء تجاوز الحد (429): يتعامل maxRetries: 3 في عميل OpenAI مع الحدود العابرة تلقائياً. للحجم العالي المستمر، اطلب زيادة حد المعدل من لوحة منصة OpenAI.

output_text فارغ: يحدث هذا عندما يكون الإخراج النهائي للنموذج استدعاء دالة وليس رسالة. تحقق من response.output للعناصر من نوع function_call وأكمل الحلقة الوكيلية.

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

  • بحث الملفات: ارفع ملفات PDF إلى مخزن متجهات مع openai.vectorStores.create()، ثم أضف { type: "file_search", vector_store_ids: ["vs_..."] } لتفعيل توليد الاسترداد المعزز.
  • البحث العميق: استخدم { type: "deep_research" } للتقارير البحثية متعددة الخطوات. هذه عملية غير متزامنة وقد تستغرق 5–30 دقيقة.
  • MCP البعيد: اتصل بأي خادم Model Context Protocol مع { type: "mcp", server_url: "...", headers: { Authorization: "Bearer ..." } }.
  • استخدام الكمبيوتر: ابنِ وكلاء أتمتة المتصفح مع { type: "computer_use_preview" } للمهام التي تتطلب التنقل في الواجهات الحقيقية.
  • أضف قابلية الملاحظة: ادمج Langfuse أو LangSmith لتتبع كل استدعاء أداة، وقياس الكمون، وإجراء التقييمات على مخرجات وكيلك.

الخاتمة

تحوّل Responses API من OpenAI سير عمل Assistants الذي يتطلب ست استدعاءات واستطلاعاً إلى استدعاء دالة واحد معبّر. مع البحث على الويب ومفسر الكود وأدوات الدوال والبث وprevious_response_id لتسلسل المحادثة — كل ذلك جاهز مباشرةً — لديك كل ما تحتاجه لبناء وكلاء ذكاء اصطناعي على مستوى الإنتاج في TypeScript. إغلاق Assistants API في 26 أغسطس 2026 يجعل الهجرة عاجلة — والخبر الجيد أن الواجهة الجديدة أفضل في كل الأبعاد: أبسط وأسرع وأرخص وأكثر قدرة.