> ## 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` و`POST /v1/agents/{agent_id}/chat/stream`، **تتطلبان** ترويسة `Idempotency-Key`. الطلب من دونها يُرجع `422 validation_error`.

## اختيار المفتاح

أي قيمة من 1-255 حرفًا تكون فريدة لكل طلب منطقي. أبسط خيار هو UUID يُولَّد لحظة قرارك إرسال الرسالة. المفاتيح محصورة في نطاق مفتاح API الخاص بك، فلا داعي للقلق من التصادم مع مفاتيح أخرى.

```http theme={null}
Idempotency-Key: 7f20a5f0-5dde-48cf-94aa-38acbbd218e0
```

## ما الذي تُرجعه إعادة المحاولة

إعادة المحاولة بنفس `Idempotency-Key` **وبنفس الجسم** تُرجع النتيجة المخزّنة: `request_id` الأصلي، ونفس رسالة المساعد، ونفس `usage`. لا يُولَّد رد ثانٍ ولا يُخصم شيء مرة أخرى.

تبقى السجلات **24 ساعة**. بعد ذلك يبدأ المفتاح نفسه طلبًا جديدًا.

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

## التعارضات

| الحالة | الرمز                     | متى                                                                                                        |
| ------ | ------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `409`  | `idempotency_in_progress` | المحاولة الأولى ما زالت جارية. انتظر عدد ثواني `Retry-After` (2 افتراضيًا) ثم أعد المحاولة بنفس المفتاح.   |
| `409`  | `idempotency_conflict`    | أُرسل المفتاح نفسه بجسم مختلف (`external_user_id` أو `message` مختلف). استخدم مفتاحًا جديدًا للطلب الجديد. |

## الرد المتدفق والانقطاعات

القواعد نفسها تنطبق على نقطة نهاية الرد المتدفق. إذا انقطع اتصال عميلك في منتصف التدفق، يستمر توليد الرد وتخزينه. إعادة المحاولة بنفس المفتاح تبث `response.created` ثم مباشرة `response.completed` حاملًا النتيجة المخزّنة، من دون إعادة الأجزاء. راجع [الرد المتدفق](/ar/guides/streaming).

## حلقة إعادة المحاولة الموصى بها

<CodeGroup>
  ```bash curl theme={null}
  # One Idempotency-Key for the whole logical request; retried on transient codes.
  key=$(uuidgen)
  for attempt in 1 2 3 4 5; do
    status=$(curl -s -o body.json -D headers.txt -w '%{http_code}' \
      -X POST "$CHATTLER_BASE_URL/v1/agents/$CHATTLER_AGENT_ID/chat" \
      -H "Authorization: Bearer $CHATTLER_API_KEY" \
      -H "Idempotency-Key: $key" \
      -H "Content-Type: application/json" \
      -d '{"external_user_id": "customer-123", "message": "I want to know the price"}')
    if [ "$status" -lt 400 ]; then cat body.json; break; fi
    case "$(jq -r '.error.code' body.json)" in
      idempotency_in_progress|conversation_busy|rate_limit_exceeded|api_rate_limit_unavailable|service_unavailable)
        sleep "$(grep -i '^Retry-After:' headers.txt | tr -d '\r' | awk '{print $2}')" ;;
      *) cat body.json; break ;;
    esac
  done
  ```

  ```python Python theme={null}
  import time
  import uuid

  import httpx


  def chat_with_retry(external_user_id: str, message: str, attempts: int = 5) -> dict:
      idempotency_key = str(uuid.uuid4())  # one key for the whole logical request
      for _ in range(attempts):
          response = httpx.post(
              f"{BASE_URL}/v1/agents/{AGENT_ID}/chat",
              headers={**HEADERS, "Idempotency-Key": idempotency_key},
              json={"external_user_id": external_user_id, "message": message},
              timeout=120,
          )
          if response.status_code < 400:
              return response.json()
          code = response.json()["error"]["code"]
          if code in ("idempotency_in_progress", "conversation_busy", "rate_limit_exceeded",
                      "api_rate_limit_unavailable", "service_unavailable"):
              time.sleep(float(response.headers.get("Retry-After", "2")))
              continue
          raise RuntimeError(code)
      raise TimeoutError("gave up")
  ```

  ```javascript Node theme={null}
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
  const RETRYABLE = new Set([
    "idempotency_in_progress",
    "conversation_busy",
    "rate_limit_exceeded",
    "api_rate_limit_unavailable",
    "service_unavailable",
  ]);

  async function chatWithRetry(externalUserId, message, attempts = 5) {
    const idempotencyKey = crypto.randomUUID(); // one key for the whole logical request
    for (let i = 0; i < attempts; i++) {
      const response = await fetch(`${baseUrl}/v1/agents/${agentId}/chat`, {
        method: "POST",
        headers: {
          Authorization: `Bearer ${apiKey}`,
          "Idempotency-Key": idempotencyKey,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ external_user_id: externalUserId, message }),
      });
      const body = await response.json();
      if (response.ok) return body;
      if (!RETRYABLE.has(body.error.code)) throw new Error(body.error.code);
      await sleep(Number(response.headers.get("Retry-After") ?? 2) * 1000);
    }
    throw new Error("gave up");
  }
  ```
</CodeGroup>
