> ## 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>
  Сакланган нәтиҗә — эшләү мохите болай да саклаган нәрсә: сорау идентификаторы белән билгеләнгән ассистент хәбәре һәм шул идентификатор астында түләнгән LLM куллануы. Шуңа күрә кабат уйнату, хәтта беренче омтылыш яртыда өзелгән булса да, беркайчан да модельне чакырмый һәм балансны үзгәртми.
</Note>

## Конфликтлар

| Статус | Код                       | Кайчан                                                                                                                 |
| ------ | ------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `409`  | `idempotency_in_progress` | Беренче омтылыш әле үтәлә. `Retry-After` секунд (килешү буенча 2) көтегез һәм шул ук ачкыч белән кабатлагыз.           |
| `409`  | `idempotency_conflict`    | Шул ук ачкыч башка тән (башка `external_user_id` яки `message`) белән җибәрелгән. Яңа сорау өчен яңа ачкыч кулланыгыз. |

## Агым һәм өзелүләр

Агымлы эндпоинтта кагыйдәләр шул ук. Клиентыгыз агым уртасында өзелсә, җавап ясалуын һәм саклануын дәвам итә. Шул ук ачкыч белән кабатлау `response.created`, аннары турыдан-туры сакланган нәтиҗәле `response.completed` агымлата, дельталар кабат уйнатылмый. [Агымлы җавап](/tt/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>
