> ## Documentation Index
> Fetch the complete documentation index at: https://docs.chattler.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# الرد المتدفق

> استقبل الرد رمزًا تلو الآخر عبر الأحداث المُرسَلة من الخادم.

`POST /v1/agents/{agent_id}/chat/stream` يأخذ الطلب نفسه تمامًا الذي تأخذه نقطة نهاية الدردشة المتزامنة: نفس الجسم، نفس ترويستي `Authorization` و`Idempotency-Key`، نفس صلاحية `full`، نفس قواعد المحادثة والفوترة ورموز الأخطاء. الفرق الوحيد هو وسيلة النقل: الرد من نوع `text/event-stream`.

## الأحداث

لكل حدث اسم ويحمل سطر `data` واحدًا بصيغة JSON.

| الحدث                        | البيانات                                                                | متى                                                 |
| ---------------------------- | ----------------------------------------------------------------------- | --------------------------------------------------- |
| `response.created`           | `{"request_id", "conversation_id"}`                                     | فور تحديد المحادثة                                  |
| `response.output_text.delta` | `{"delta": "..."}`                                                      | لكل جزء من نص الإجابة أثناء توليد النموذج له؛ يتكرر |
| `response.completed`         | رد الدردشة الكامل (`request_id` و`conversation_id` و`message` و`usage`) | دائمًا آخر حدث في التدفق الناجح                     |
| `error`                      | `{"code", "message", "request_id"}`                                     | دائمًا آخر حدث في التدفق الفاشل                     |

```text theme={null}
event: response.created
data: {"request_id":"7f20a5f0-...","conversation_id":"1c2e..."}

event: response.output_text.delta
data: {"delta":"The basic"}

event: response.output_text.delta
data: {"delta":" plan is $19 per month."}

event: response.completed
data: {"request_id":"7f20a5f0-...","conversation_id":"1c2e...","message":{"id":"66e0...","role":"assistant","content":"The basic plan is $19 per month.","created_at":"2026-09-13T12:00:00Z"},"usage":{"prompt_tokens":120,"completion_tokens":45,"total_tokens":165,"charged_usd":"0.00123400"}}
```

<Note>
  نص الاستدلال ونشاط الأدوات لا يكونان جزءًا من التدفق أبدًا. `response.completed` يحمل دائمًا النص النهائي الكامل، لذا يستطيع العميل الذي فاتته بعض الأجزاء أن يعتمد عليه بدلًا من الأجزاء المتسلسلة.
</Note>

## الأخطاء في التدفق

الإخفاقات **قبل** إنشاء التدفق (مفتاح غير صالح، خطأ تحقق، `idempotency_conflict`، `conversation_busy`، وكيل معطّل، حد معدل) هي [أغلفة أخطاء](/ar/guides/errors) JSON عادية بحالة HTTP الخاصة بها؛ تحقق من `response.ok` قبل أن تبدأ قراءة الأحداث.

بعد إرسال الترويسات تكون الحالة `200` بالفعل. الإخفاق بعد تلك النقطة (مثل `insufficient_balance` أو `internal_error`) يصل كحدث `error`، بنفس حقول الغلاف، ويغلق التدفق.

## الانقطاع وإعادة المحاولة

العميل الذي يقطع الاتصال لا يوقف الرد: يستمر توليده وتخزينه، ويُكمَل سجل التكرار الآمن. إعادة المحاولة بنفس `Idempotency-Key` تبث `response.created` ثم مباشرة `response.completed` بالنتيجة المخزّنة؛ الأجزاء لا تُعاد. راجع [التكرار الآمن](/ar/guides/idempotency).

## أمثلة

<CodeGroup>
  ```bash curl theme={null}
  curl -N -X POST "$CHATTLER_BASE_URL/v1/agents/$CHATTLER_AGENT_ID/chat/stream" \
    -H "Authorization: Bearer $CHATTLER_API_KEY" \
    -H "Idempotency-Key: $(uuidgen)" \
    -H "Content-Type: application/json" \
    -d '{"external_user_id": "customer-123", "message": "Tell me more"}'
  ```

  ```python Python theme={null}
  import json
  import os
  import uuid

  import httpx


  def stream_chat(external_user_id: str, message: str) -> dict:
      url = f"{os.environ['CHATTLER_BASE_URL']}/v1/agents/{os.environ['CHATTLER_AGENT_ID']}/chat/stream"
      headers = {
          "Authorization": f"Bearer {os.environ['CHATTLER_API_KEY']}",
          "Idempotency-Key": str(uuid.uuid4()),
      }
      with httpx.stream("POST", url, headers=headers, timeout=120,
                        json={"external_user_id": external_user_id, "message": message}) as response:
          if response.status_code >= 400:
              response.read()
              raise RuntimeError(response.json()["error"])
          event, completed = None, None
          for line in response.iter_lines():
              if line.startswith("event: "):
                  event = line[len("event: "):]
              elif line.startswith("data: "):
                  data = json.loads(line[len("data: "):])
                  if event == "response.output_text.delta":
                      print(data["delta"], end="", flush=True)
                  elif event == "response.completed":
                      completed = data
                  elif event == "error":
                      raise RuntimeError(f"{data['code']}: {data['message']}")
              elif line == "":
                  event = None
          return completed


  stream_chat("customer-123", "Tell me more")
  ```

  ```javascript Node theme={null}
  const baseUrl = process.env.CHATTLER_BASE_URL;
  const agentId = process.env.CHATTLER_AGENT_ID;
  const apiKey = process.env.CHATTLER_API_KEY;

  async function streamChat(externalUserId, message, onDelta) {
    const response = await fetch(`${baseUrl}/v1/agents/${agentId}/chat/stream`, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${apiKey}`,
        "Idempotency-Key": crypto.randomUUID(),
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ external_user_id: externalUserId, message }),
    });
    if (!response.ok) {
      const body = await response.json();
      throw new Error(`${body.error.code}: ${body.error.message}`);
    }
    const reader = response.body.getReader();
    const decoder = new TextDecoder();
    let buffer = "";
    let completed = null;
    for (;;) {
      const { value, done } = await reader.read();
      if (done) break;
      buffer += decoder.decode(value, { stream: true });
      let boundary;
      while ((boundary = buffer.indexOf("\n\n")) >= 0) {
        const frame = buffer.slice(0, boundary);
        buffer = buffer.slice(boundary + 2);
        const event = frame.match(/^event: (.+)$/m)?.[1];
        const data = JSON.parse(frame.match(/^data: (.+)$/m)?.[1] ?? "{}");
        if (event === "response.output_text.delta") onDelta(data.delta);
        else if (event === "response.completed") completed = data;
        else if (event === "error") throw new Error(`${data.code}: ${data.message}`);
      }
    }
    return completed;
  }

  const result = await streamChat("customer-123", "Tell me more", (t) => process.stdout.write(t));
  console.log("\n", result.usage);
  ```
</CodeGroup>

<Tip>
  استخدم `curl -N` (بلا تخزين مؤقت) لرؤية الأحداث فور وصولها. في المتصفحات لا يستطيع `EventSource` إرسال أجسام `POST` ولا ترويسات مخصصة، والمفتاح يجب ألا يصل إلى المتصفح أصلًا؛ ابثّ من خادمك الخاص ومرّر النص إلى العميل.
</Tip>
