داشبورد توسعه‌دهنده
پرسش از هوش مصنوعی
پرسش از هوش مصنوعی

پاسخ‌های API جریانی

یاد بگیرید چگونه پاسخ‌های مدل را از API AvalAI با استفاده از رویدادهای ارسال شده توسط سرور (SSE) به صورت جریانی دریافت کنید.

این راهنما با اقتباس از مستندات رسمی OpenAI درباره پاسخ‌های API جریانی، فراخوانی تابع و Structured Outputs، با تغییرات endpoint، کلید API و نکات availability در routeهای AvalAI تهیه شده است.

مقدمه

به طور پیش‌فرض، هنگامی که درخواستی به API AvalAI ارسال می‌کنید، مدل کل خروجی خود را قبل از ارسال آن در یک پاسخ HTTP واحد تولید می‌کند. هنگام تولید خروجی‌های طولانی، انتظار برای پاسخ کامل می‌تواند زمان‌بر باشد. پاسخ‌های جریانی به شما امکان می‌دهد دریافت و پردازش ابتدای خروجی مدل را در حالی که تولید پاسخ کامل ادامه دارد، شروع کنید. این امر به ویژه برای ایجاد برنامه‌های کاربردی تعاملی‌تر و پاسخ‌گوتر مفید است.

این راهنما روی جریان HTTP با stream=true از طریق رویدادهای ارسال‌شده توسط سرور (SSE) تمرکز دارد. OpenAI همچنین انتقال WebSocket پایدار را برای ورودی‌های incremental و state با previous_response_id مستند کرده است، اما در AvalAI آن را یک الگوی جدا بدانید: فقط وقتی از آن استفاده کنید که AvalAI مسیر سازگار با WebSocket را برای برنامه شما ارائه کرده باشد. برای variant اتصال پایدار، حالت WebSocket در Responses را ببینید.

فعال‌سازی جریان

برای شروع دریافت جریانی پاسخ‌ها، پارامتر stream=True (یا معادل آن در SDK زبان برنامه‌نویسی خود) را در درخواست خود به نقطه پایانی مربوطه API AvalAI، مانند Chat Completions API یا Responses API تنظیم کنید.

در اینجا مثالی با استفاده از Responses API آورده شده است:

bash
curl https://api.avalai.ir/v1/responses \
  -H "Authorization: Bearer $AVALAI_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: text/event-stream" \
  -d '{
    "model": "gpt-5.5",
    "input": [
      {"role": "user", "content": "عبارت '\''double bubble bath'\'' را ده بار سریع بگو."}
    ],
    "stream": true
  }' \
  --no-buffer
python
from openai import OpenAI  # از کتابخانه استاندارد OpenAI پایتون استفاده کنید
import os

# کلاینت را برای استفاده از نقطه پایانی و کلید API AvalAI پیکربندی کنید
client = OpenAI(
    api_key=os.environ["AVALAI_API_KEY"],
    base_url="https://api.avalai.ir/v1",
)

try:
    stream = client.responses.create(
        model="gpt-5.5",
        input=[
            {
                "role": "user",
                "content": "عبارت 'double bubble bath' را ده بار سریع بگو.",
            },
        ],
        stream=True,
    )
    print("پاسخ جریانی:")
    for event in stream:
        if event.type == "response.output_text.delta":
            print(event.delta, end="", flush=True)
        elif event.type == "response.refusal.delta":
            print(event.delta, end="", flush=True)
        elif event.type == "response.completed":
            print("\nجریان کامل شد.")
        elif event.type == "response.failed":
            raise RuntimeError(event.response.error)
        elif event.type == "error":
            raise RuntimeError(event)
except Exception as e:
    print(f"خطای API رخ داد: {e}")
javascript
import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.AVALAI_API_KEY,
  baseURL: "https://api.avalai.ir/v1",
});

async function streamResponse() {
  try {
    const stream = await client.responses.create({
      model: "gpt-5.5",
      input: [
        {
          role: "user",
          content: "عبارت 'double bubble bath' را ده بار سریع بگو.",
        },
      ],
      stream: true,
    });

    console.log("پاسخ جریانی:");
    for await (const event of stream) {
      if (event.type === "response.output_text.delta") {
        process.stdout.write(event.delta);
      } else if (event.type === "response.refusal.delta") {
        process.stdout.write(event.delta);
      } else if (event.type === "response.completed") {
        process.stdout.write("\nجریان کامل شد.\n");
      } else if (event.type === "response.failed") {
        throw new Error(JSON.stringify(event.response.error));
      } else if (event.type === "error") {
        throw new Error(JSON.stringify(event));
      }
    }
  } catch (error) {
    console.error("خطای API رخ داد:", error);
  }
}

streamResponse();
go
package main

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
)

func main() {
	apiKey := os.Getenv("AVALAI_API_KEY")
	if apiKey == "" {
		fmt.Println("AVALAI_API_KEY تنظیم نشده است")
		return
	}

	body := `{
		"model": "gpt-5.5",
		"input": "عبارت 'double bubble bath' را ده بار سریع بگو.",
		"stream": true
	}`

	ctx := context.Background()
	req, err := http.NewRequestWithContext(
		ctx,
		http.MethodPost,
		"https://api.avalai.ir/v1/responses",
		strings.NewReader(body),
	)
	if err != nil {
		fmt.Printf("خطا در ایجاد درخواست: %v\n", err)
		return
	}
	req.Header.Set("Authorization", "Bearer "+apiKey)
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "text/event-stream")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		fmt.Printf("خطا در درخواست جریان: %v\n", err)
		return
	}
	defer resp.Body.Close()

	fmt.Println("پاسخ جریانی:")
	if _, err := io.Copy(os.Stdout, resp.Body); err != nil {
		fmt.Printf("\nخطا در خواندن جریان: %v\n", err)
	}
}
php
<?php
require 'vendor/autoload.php';

$apiKey = getenv('AVALAI_API_KEY');

if (!$apiKey) {
    die("کلید API AvalAI پیدا نشد. متغیر محیطی AVALAI_API_KEY را تنظیم کنید.");
}

$customBaseUrl = 'https://api.avalai.ir/v1';

$client = OpenAI::factory()
    ->withApiKey($apiKey)
    ->withBaseUri($customBaseUrl)
    ->make();

try {
 $stream = $client->responses()->createStreamed([
 'model' => 'gpt-5.5',
 'input' => [
 ['role' => 'user', 'content' => "عبارت 'double bubble bath' را ده بار سریع بگو."],
 ],
 // 'stream' => true, // اغلب توسط متد createStreamed مشخص می‌شود
 ]);

 echo "پاسخ جریانی:\n";
 foreach ($stream as $event) {
 // هر رویداد را به محض رسیدن پردازش کنید
 // ساختار $event به کتابخانه کلاینت PHP خاص بستگی دارد
 // مثال: دسترسی به داده‌ها اگر شیئی با متد toArray یا ویژگی‌های عمومی باشد
 if (method_exists($event, 'toArray')) {
 print_r($event->toArray());
 } else {
 var_dump($event);
 }
 echo "\n---\n";
 }

} catch (Exception $e) {
 echo "خطای API رخ داد: " . $e->getMessage() . "\n";
}

تفاوت Streaming در Responses و Chat

اگر یک stream موجود از /v1/chat/completions را مهاجرت می‌دهید، قبل از تغییر endpoint مصرف‌کننده stream را به‌روز کنید:

stream در Chat Completionsstream در Responsesنکته مهاجرت
هر chunk شامل choices[0].delta.content استمتن به شکل رویدادهای response.output_text.delta می‌رسدفقط مقدار event.delta را به بافر متن قابل‌نمایش اضافه کنید.
آرگومان‌های ابزار ممکن است داخل deltaهای chunk برسندآرگومان‌های ابزار با response.function_call_arguments.delta می‌آیند و با response.function_call_arguments.done تمام می‌شوندآرگومان‌ها را بافر کنید و فقط بعد از رویداد done parse/execute کنید.
metadata تکمیل در chunkهای پایانی پخش استresponse.completed آبجکت نهایی پاسخ را حمل می‌کندusage، status و خروجی نهایی را از رویداد completed بخوانید.
خطاها ممکن است به شکل transport error یا chunk stream برسندResponses می‌تواند response.failed یا error emit کندstream را متوقف کنید و سیاست معمول retry/error خود را اجرا کنید.

برای deploymentهای مرورگر یا proxy، هرجا stack اجازه می‌دهد response buffering را هم غیرفعال کنید. هر text delta را به UI flush کنید، اما یک بافر داخلی نگه دارید تا بتوانید آن را با response.output_text.done یا payload نهایی response.completed تطبیق دهید.

خواندن پاسخ‌ها

API AvalAI از رویدادهای معنایی ارسال شده توسط سرور (SSE) برای جریان استفاده می‌کند. هر رویداد با یک اسکیمای از پیش تعریف شده تایپ می‌شود، که به شما امکان می‌دهد به انواع رویدادهای خاص مربوط به برنامه خود گوش داده و آن‌ها را مدیریت کنید.

می‌توانید رویدادهای جداگانه را با استفاده از ویژگی type شی رویداد (یا نوع کلاس در صورت استفاده از SDK مانند کتابخانه‌های پایتون یا Node.js) شناسایی کنید.

برخی از رویدادهای کلیدی چرخه حیات فقط یک بار در هر جریان منتشر می‌شوند، در حالی که برخی دیگر چندین بار با تولید پاسخ منتشر می‌شوند. برای جریان‌های متنی، از این خانواده رویدادها شروع کنید:

رویدادنحوه استفاده
response.created / response.in_progressstate درخواست را مقداردهی کنید و نشان دهید generation شروع شده است.
response.output_item.added / response.output_item.doneآیتم‌های خروجی تایپ‌شده مانند پیام، فراخوانی ابزار یا آیتم‌های reasoning را دنبال کنید.
response.content_part.added / response.content_part.donecontent partهای داخل یک پیام را دنبال کنید.
response.output_text.deltaفقط مقدار delta را به بافر متن قابل نمایش برای کاربر اضافه کنید.
response.output_text.doneمتن بافرشده را با متن نهایی آن content part جایگزین یا تطبیق دهید.
response.output_text.annotation.addedcitationها یا annotationهای دیگر را ذخیره کنید و بعد از پایدار شدن offsetهای متن نمایش دهید.
response.refusal.delta / response.refusal.doneمتن refusal را جدا از متن پاسخ عادی stream کنید و refusal نهایی را یک پاسخ امن و terminal بدانید.
response.function_call_arguments.delta / response.function_call_arguments.doneآرگومان‌های ابزار را بافر کنید و فقط پس از رویداد done parse و اجرا کنید.
response.file_search_call.in_progress / response.file_search_call.searching / response.file_search_call.completedوقتی file search میزبانی‌شده برای route فعال است، وضعیت retrieval را جدا از متن نهایی پاسخ نشان دهید.
response.code_interpreter_call.in_progress / response.code_interpreter_call_code.delta / response.code_interpreter_call.completedپیشرفت interpreter را نمایش دهید، کد تولیدشده را در پنل داخلی stream کنید و خروجی‌ها را فقط بعد از تکمیل call نشان دهید.
response.completedآبجکت پاسخ نهایی، usage، status و خروجی کامل را بخوانید. اگر status نهایی incomplete بود، قبل از استفاده از خروجی جزئی incomplete_details را بررسی کنید.
response.failed / errorدر هر دو حالت stream را متوقف کنید. response.failed به یک آبجکت response وابسته است؛ error می‌تواند خطای سطح stream باشد و response کامل نداشته باشد.

برای لیست کامل انواع رویدادها و اسکیماهای آنها، به ویژه هنگام کار با سناریوهای پیچیده‌تر مانند فراخوانی ابزار، به مرجع Responses API (بخش Streaming) مراجعه کنید.

هنگام ساخت مصرف‌کننده جریان:

  • بر اساس event.type شاخه‌بندی کنید؛ فرض نکنید هر رویداد متن دارد.
  • فقط مقدارهای response.output_text.delta را به بافر قابل نمایش برای کاربر اضافه کنید؛ از response.output_text.done برای تطبیق content part نهایی استفاده کنید.
  • رویدادهای response.output_text.annotation.added را برای citation، ارجاع فایل یا annotationهای جستجو ذخیره کنید و پس از پایدار شدن متن به همان بخش وصل کنید.
  • خروجی refusal را جدا از متن پاسخ عادی نگه دارید تا UI بتواند آن را واضح برچسب‌گذاری کند و آن را به اشتباه به عنوان structured output parse نکند.
  • response.completed را منبع نهایی آمار مصرف و فراداده پایان پاسخ بدانید.
  • پاسخ نهایی با status: "incomplete" را نتیجه جزئی بدانید؛ incomplete_details.reason مثل max_output_tokens یا content_filter را بررسی کنید و در صورت مناسب بودن با prompt محدودتر یا بودجه خروجی بزرگ‌تر retry کنید.
  • هنگام دریافت response.failed یا error جریان را متوقف کنید یا با سیاست retry مشخص دوباره تلاش کنید؛ این دو را جدا log کنید چون فقط response.failed به آبجکت response وابسته است.
  • برای فراخوانی ابزار جریانی، آرگومان‌ها را تا رویداد response.function_call_arguments.done جمع‌آوری کنید و بعد تابع خود را اجرا کنید.
  • برای رویدادهای ابزار میزبانی‌شده مثل file search یا code interpreter، نشانگرهای پیشرفت را به‌روز کنید اما آن payloadها را به متن قابل نمایش دستیار اضافه نکنید. artifactها، snippetهای بازیابی‌شده و کد تولیدشده را در بخش‌های جداگانه UI نگه دارید.
  • فقط وقتی route انتخابی AvalAI از ادامه state با previous_response_id پشتیبانی می‌کند، response.id نهایی را برای turn بعدی نگه دارید؛ در غیر این صورت state مکالمه را در برنامه خودتان نگه دارید.

اتصال مجدد، ادامه stream و اندازه payload

برای پاسخ‌های طولانی، jobهای پس‌زمینه یا کلاینت‌های موبایل، مصرف‌کننده stream را طوری طراحی کنید که قطع اتصال را تحمل کند. streamهای foreground معمولی را یک transport تعاملی و best-effort در نظر بگیرید: اگر اتصال قطع شد، به اندازه کافی state برنامه را نگه دارید تا بتوانید درخواست را دوباره اجرا کنید یا، وقتی route انتخابی پشتیبانی می‌کند، پاسخ کامل‌شده را retrieve کنید.

الگوی background streaming در OpenAI نیاز دارد response را با هر دو مقدار background: true و stream: true بسازید و سپس sequence_number منتشرشده در هر event را به‌عنوان cursor ذخیره کنید. همین الگو را در AvalAI فقط وقتی استفاده کنید که route انتخابی /v1/responses از background response و retrieval streaming پشتیبانی کند:

json
{
  "model": "gpt-5.5",
  "input": "Write a long report and stream progress.",
  "background": true,
  "stream": true
}

اگر این پشتیبانی فعال باشد، آخرین شناسه response و sequence_number رویداد را ذخیره کنید و با GET /v1/responses/{response_id}?stream=true&starting_after=<sequence_number> دوباره وصل شوید تا سرور از بعد از آخرین رویداد پردازش‌شده ادامه دهد. streamهای پس‌زمینه ممکن است رفتار time-to-first-token متفاوتی نسبت به streamهای foreground داشته باشند؛ بنابراین latency را برای مدل و providerی که از طریق AvalAI route می‌کنید اندازه‌گیری کنید.

اگر resume کردن stream برای route در دسترس نیست، یک بافر متن و state درخواست در برنامه خودتان نگه دارید تا بتوانید امن restart کنید، deltaهای تکراری را حذف کنید یا پس از تکمیل، پاسخ نهایی را retrieve کنید. obfuscation جریان را به صورت پیش‌فرض روشن نگه دارید؛ فقط برای مسیرهای شبکه داخلی و قابل اعتماد که کاهش bandwidth از یکنواخت‌سازی اندازه payload مهم‌تر است، include_obfuscation=false بگذارید.

در اینجا یک مثال مفهومی از پردازش دلتاهای متنی در پایتون آورده شده است:

python
# (ادامه مثال پایتون 'فعال‌سازی جریان')

# ... تنظیم کلاینت و شروع جریان ...

try:
    stream = client.responses.create(
        model="gpt-5.5",
        input=[{"role": "user", "content": "یک داستان کوتاه برایم بگو."}],
        stream=True,
    )

    full_text = ""
    annotations = []
    print("داستان جریانی:")
    for event in stream:
        if event.type == "response.output_text.delta":
            text_delta = event.delta
            print(text_delta, end="", flush=True)  # دلتا را فورا چاپ کنید
            full_text += text_delta
        elif event.type == "response.refusal.delta":
            print(event.delta, end="", flush=True)
        elif event.type == "response.output_text.annotation.added":
            annotations.append(event.annotation)
        elif event.type == "response.completed":
            print("\n--- جریان به پایان رسید ---")
            # می‌توانید به دلایل تکمیل، آمار استفاده و غیره از رویداد نهایی دسترسی پیدا کنید
            # print(event.response)
            if event.response.status == "incomplete":
                print(f"\nپاسخ ناقص است: {event.response.incomplete_details}")
        elif event.type == "response.failed":
            print(f"\n--- پاسخ ناموفق بود: {event.response.error} ---")
            break
        elif event.type == "error":
            print(f"\n--- خطایی رخ داد: {event} ---")
            break  # در صورت خطا پردازش را متوقف کنید
    # متغیر 'full_text' اکنون متن کامل تولید شده را در خود دارد
    # print("\n\nداستان کامل:\n", full_text)
except Exception as e:
    print(f"\nخطای API رخ داد: {e}")

موارد استفاده پیشرفته

جریان همچنین برای تعاملات پیشرفته‌تر شامل داده‌های ساختاریافته یا استفاده از ابزار ضروری است:

  • فراخوانی ابزار جریانی: هنگام استفاده از فراخوانی تابع، می‌توانید تصمیم مدل برای فراخوانی یک تابع و آرگومان‌هایی را که قصد استفاده از آنها را دارد، به صورت جریانی دریافت کنید. این به برنامه شما امکان می‌دهد سریعتر واکنش نشان دهد. از رویدادهایی مانند response.output_item.added، response.function_call_arguments.delta و response.function_call_arguments.done استفاده می‌شود.
  • خروجی‌های ساختاریافته جریانی: اگر از خروجی‌های ساختاریافته با text.format استفاده می‌کنید، جریان به شما امکان می‌دهد بخش‌هایی از پاسخ JSON ساختاریافته را به محض تولید دریافت کنید.
  • پیشرفت ابزارهای میزبانی‌شده: وقتی یک route در AvalAI ابزارهای میزبانی‌شده را ارائه کند، streamهای سازگار با OpenAI ممکن است رویدادهای وضعیت file search و رویدادهای کد/پیشرفت code interpreter داشته باشند. این‌ها را وضعیت عملیاتی بدانید، نه متن نهایی پاسخ، و fallbackهای وابسته به route مانند RAG دستی یا اجرای کد سمت برنامه را آماده نگه دارید.

برای مثال‌های دقیق جریان در این سناریوها به راهنماهای مربوطه مراجعه کنید.

ریسک نظارت محتوا

توجه داشته باشید که پخش مستقیم خروجی مدل به کاربران نهایی در یک برنامه کاربردی عملیاتی، نظارت محتوا را چالش‌برانگیزتر می‌کند، زیرا ارزیابی تکمیل‌های جزئی در برابر دستورالعمل‌ها یا خط‌مشی‌های ایمنی ممکن است دشوارتر باشد. اگر گردش‌کار شما برای محتوای تولیدشده امتیازهای moderation درخواست می‌کند، این امتیازها فقط پس از کامل شدن خروجی تولیدشده در دسترس هستند و همراه دلتاهای جزئی ارسال نمی‌شوند. برای سطوح کاربری پرریسک، بافر کردن، بازبینی پس از پایان جریان، یا رویکرد ترکیبی با بررسی سبک محلی را در نظر بگیرید. برای اطلاعات بیشتر به دستورالعمل‌های ایمنی مراجعه کنید.

منابع مرتبط