IlmHamroh
Data Science va sun'iy intellekt/Katta til modellari6/14-dars45 daqiqa
Mundarija (24)

25.6-dars: LLM API bilan ishlash

25-QISM — KATTA TIL MODELLARI · 6-dars


1. Kirish va motivatsiya

25.5-darsda LLM javobini dastur qanday o'qishini ko'rdik. Endi javobning o'zini qanday olishni — ya'ni API bilan muhandislik darajasida ishlashni o'rganamiz. Birinchi qarashda bu oddiy: bitta funksiya chaqiruvi, client.messages.create(...), javob keldi. Lekin prototip ishlab chiqarishga chiqqan kuni boshqa manzara paydo bo'ladi: bir vaqtda yuzlab so'rov, tezlik chegarasi (429), server band (529), uzilgan ulanish, ikki marta yuborilgan buyurtma, oy oxirida kutilmagan hisob va GitHub ga tushib qolgan kalit.

API atrofidagi kod — qayta urinish, kesh, xarajat hisobi, streaming, batch — model sifatiga bog'liq emas, lekin tizimning ishonchliligi va narxi aynan shunga bog'liq. Yaxshi xabar: bu kod oddiy dasturlash, uni lokal ravishda to'liq sinash mumkin. Buning uchun stub — xatolarni, kechikishni va usage ni haqiqiy API kabi qaytaradigan soxta mijoz yetarli.

Real vaziyat. Kichik jamoa 20 ta ishchi jarayon bilan 100 ta hujjatni tunda qayta ishlaydigan skript yozdi. Birinchi kechada skript 5 soniyada "tugadi": hujjatlarning to'rtdan uch qismidan ko'prog'i 429 xatosi bilan tashlab ketilgan edi. Keyin "xato bo'lsa darhol qayta yubor" qo'shildi — endi server 800 ga yaqin so'rov qabul qildi, ularning deyarli hammasi yana 429, va hujjatlarning ko'pi baribir qayta ishlanmadi. Uchinchi urinishda eksponensial backoff qo'shildi — hammasi ishladi, lekin oyna 50 soniyaga cho'zildi. Oxirida eng oddiy yechim eng yaxshisi bo'lib chiqdi: so'rovlarni serverning tezlik chegarasidan sekinroq tartib bilan yuborish. Bu darsning 2-misoli aynan shu to'rt bosqichni virtual vaqtda o'lchaydi.

Bu darsda Anthropic SDK tuzilishini, xabarlar formati va suhbat tarixini, xatolar turlari va qayta urinish strategiyalarini, kesh va idempotentlikni, streaming, batch va usage dan xarajat hisobini hamda kalitni xavfsiz saqlashni ko'ramiz.

Bu darsda:

  • SDK tuzilishi: mijoz, so'rov, javob obyekti
  • Xabarlar formati va suhbat tarixi
  • Xatolar turlari: qaysi biriga qayta urinish kerak
  • Eksponensial backoff, jitter va mijoz tomonidagi cheklovchi
  • Kesh va idempotentlik
  • Streaming
  • Batch ishlov berish
  • usage dan xarajat hisobi va byudjet
  • Kalitni xavfsiz saqlash
  • Tuzoqlar

ℹ Mashinada LLM API va internet yo'q. Haqiqiy SDK kodi (import anthropic) faqat nazariya va ma'lumotnoma bloklarida. Misollarda STUB — SoxtaLLM, SoxtaServer, SoxtaMijoz deb nomlangan deterministik soxta mijozlar ishlatiladi: ular haqiqiy model emas, xatolar ehtimoli va narxlar — faraz. Lekin qayta urinish, backoff, kesh, idempotentlik va xarajat hisobi kodi haqiqiy — aynan shu kod haqiqiy API bilan ham ishlaydi. Vaqt — virtual hisoblagich (time.sleep yo'q).


2. Nazariya — chuqur tushuntirish

2.1. SDK tuzilishi: mijoz, so'rov, javob

text
MIJOZ:
  client = anthropic.Anthropic()
    kalit: ANTHROPIC_API_KEY muhit o'zgaruvchisidan (kodga YOZILMAYDI)
    timeout (standart 10 daqiqa), max_retries (standart 2) - sozlanadi

SO'ROV (Messages API):
  client.messages.create(
      model="claude-sonnet-5",          # model nomi
      max_tokens=1024,                  # chiqish uchun YUQORI chegara
      system="...",                     # ko'rsatma - alohida parametr
      messages=[{"role": "user", "content": "..."}],
  )

JAVOB OBYEKTI:
  response.id              - so'rov identifikatori (loglash uchun)
  response.model           - qaysi model javob berdi
  response.content         - bloklar ro'yxati: text, tool_use, ...
    response.content[0].text  - eng oddiy holatda matn
  response.stop_reason     - end_turn / max_tokens / stop_sequence / tool_use / refusal
  response.usage           - input_tokens, output_tokens,
                             cache_read_input_tokens, cache_creation_input_tokens

BOSHQA PROVAYDERLAR:
  OpenAI, Google va boshqalarda nomlar farq qiladi, g'oya bir xil:
  model + xabarlar -> matn + stop sababi + token hisobi

Ikki narsaga alohida e'tibor bering. Birinchisi — stop_reason: max_tokens bo'lsa, javob qirqilgan (25.5-dars), refusal bo'lsa — model rad etgan; content ni o'qishdan oldin tekshiring. Ikkinchisi — usage: bu hisob-kitob manbai. Tokenlarni o'zingiz taxmin qilmang (bu darsdagi len(matn) / 4 — faqat stub uchun); haqiqiy hisob usage da yoki count_tokens endpointida.

Javob — matn emas, obyekt: stop_reason va usage ni har doim o'qing va loglang.

2.2. Xabarlar formati va suhbat tarixi

text
ROLLAR:
  system    - alohida parametr (messages ichida emas)
  messages  - user / assistant navbatma-navbat, birinchisi user
  ketma-ket bir xil rol - API ularni bitta navbatga birlashtiradi

API HOLATSIZ (stateless):
  model oldingi so'rovni "eslamaydi"
  har navbatda BUTUN tarix qayta yuboriladi
  1-misol: 1-navbat 374 token, 30-navbat 2794 token (kirish)
  30 navbatli suhbatda jami kirish ~ n^2 ga proporsional o'sadi

TARIX STRATEGIYALARI (1-misol, 30 navbat, jami kirish tokenlari):
  to'liq tarix      47 671    - hamma narsa esda, eng qimmat
  sirpanuvchi oyna  22 812    - arzon, lekin eski faktlar YO'QOLADI
  xulosa + oyna     23 694    - eski qism qisqa xulosa bilan almashtiriladi
  to'liq + kesh     47 671, shundan 46 530 keshdan (prefiks kesh)

PREFIKS KESH (prompt caching):
  so'rov boshi (system + eski tarix) o'zgarmasa, server uni keshdan o'qiydi
  keshdan o'qilgan token arzonroq (aniq koeffitsient - provayder sahifasida)
  shart: prefiks BAYTMA-BAYT bir xil; system ichida sana/vaqt - keshni buzadi

1-misolda faraziy narxlar bilan 1000 ta suhbat: to'liq tarix $143.01, oyna $68.44, xulosa $71.08, to'liq + kesh — $17.38. Muhim kuzatuv: oyna eng arzon bo'lsa ham, 2-navbatda aytilgan ism 30-navbatda yo'qoldi; xulosa uni saqladi (chunki stub xulosasi muhim faktni o'z ichiga oldi — haqiqiy xulosa buni kafolatlamaydi). Keshlangan to'liq tarix esa ham arzon, ham to'liq — shuning uchun amalda birinchi qadam — prefiksni barqaror qilish va keshni yoqish, keyin kerak bo'lsa xulosa.

Model hech narsani eslamaydi — kontekstda nima bo'lsa, faqat shu bor; tarix strategiyasi narx va "xotira" orasidagi murosa.

2.3. Xatolar turlari

text
HTTP     SDK sinfi (Python)            SABAB                   QAROR
400      BadRequestError               so'rov noto'g'ri         TUZATING, qayta emas
401      AuthenticationError           kalit noto'g'ri          TUZATING
403      PermissionDeniedError         ruxsat yo'q              TUZATING
404      NotFoundError                 model nomi xato          TUZATING
429      RateLimitError                tezlik chegarasi         KUTIB qayta urinish
                                       (retry-after sarlavhasi)
500      InternalServerError           server ichki xatosi      qayta urinish
529      InternalServerError           server band (overloaded) qayta urinish
timeout  APITimeoutError               javob kelmadi            qayta urinish (!)
tarmoq   APIConnectionError            ulanish uzildi           qayta urinish

UMUMIY: APIStatusError - barcha HTTP xatolarining asosi (.status_code, .message)

TIMEOUT TUZOG'I:
  javob kelmadi != so'rov bajarilmadi
  server ishni bajargan, faqat javob yo'lda yo'qolgan bo'lishi mumkin
  -> yon ta'sirli amalda idempotentlik kaliti kerak (2.5)

SDK o'zi ham qayta urinadi (standart max_retries=2, 429 va 5xx uchun). Lekin tungi skript yoki katta yuklama uchun bu yetmaydi: o'z siyosatingiz (urinishlar soni, maksimal kutish, mijoz tomonidagi cheklovchi) kerak bo'ladi. Muhimi — xatolarni turi bo'yicha ajratish: bitta except Exception 400 ni ham qayta yuboradi (befoyda) va 401 ni ham (kalit xato bo'lsa, 10 marta yuborish kalitni tuzatmaydi).

400-lar — kodingizdagi xato, 429/5xx/timeout — vaqtinchalik: faqat ikkinchisiga qayta urining.

2.4. Eksponensial backoff, jitter va mijoz tomonidagi cheklovchi

text
EKSPONENSIAL BACKOFF:
  kutish(u) = min(shift, asos * 2^(u-1))       u - urinish raqami
  asos 0.5 s: 0.5, 1, 2, 4, 8, 16, ...

FULL JITTER:
  kutish(u) = uniform(0, min(shift, asos * 2^(u-1)))
  maqsad: bir vaqtda xato olgan mijozlar bir vaqtda qaytmasin
  ("thundering herd" - hammasi bir lahzada qaytib, yana 429)

RETRY-AFTER:
  server "shuncha soniyadan keyin" deydi -> kamida shuncha kuting

MIJOZ TOMONIDAGI CHEKLOVCHI (rate limiter):
  so'rovlarni serverning chegarasidan biroz sekinroq tartib bilan yuborish
  429 ni "davolash" emas, OLDINI OLISH

2-MISOL (20 mijoz x 5 so'rov, server 5 so'rov/s, 5 urug'):
  strategiya             muvaff.  urinish  429 lar  tugash
  retry yo'q              18.9%     100      79      5.0 s
  darhol                  28.7%     799     767      6.8 s
  eksponensial           100.0%     189      84     51.7 s
  full jitter            100.0%     214     108     39.7 s
  jitter + retry-after   100.0%     220     114     38.2 s
  cheklovchi + jitter    100.0%     105       0     30.0 s

Natijani halol o'qiylik. Kutilganidek, qayta urinishsiz va "darhol" qayta urinish bilan so'rovlarning ko'pi yo'qoldi, "darhol" esa serverni 767 ta befoyda so'rov bilan to'ldirdi. Backoff muvaffaqiyatni 100% ga chiqardi. Lekin jitter haqidagi "darslik" da'vosi bu tajribada tasdiqlanmadi: full jitter 429 larni kamaytirmadi, balki ko'paytirdi (+24.6, SE 6.0 — sezilarli), chunki uning o'rtacha kutishi ikki marta qisqa; tugash vaqti esa qisqardi, lekin farq sezilarli emas (-12.0 s, SE 7.0). Retry-after qo'shish ham sezilarli farq bermadi. Jitterning haqiqiy foydasi mijozlar ko'p va sinxron bo'lganda (masalan, hammasi bir vaqtda qayta ulanganda) ko'rinadi — buni o'z yuklamangizda o'lchang. Aniq g'olib esa mijoz tomonidagi cheklovchi: 429 lar 0, urinishlar 105 (100 so'rov + 5 ta 5xx/timeout), tugash 30.0 s.

Avval tezlikni o'zingiz cheklang; qayta urinish — zaxira chora: eksponensial, yuqori chegara bilan, urinishlar soni cheklangan.

2.5. Kesh va idempotentlik

text
JAVOB KESHI (bizning tomonda, prefiks keshdan boshqa):
  bir xil so'rov -> bir xil javob (T = 0 da) -> API ni qayta chaqirmaslik
  KALIT = javobga ta'sir qiluvchi HAMMA narsa:
    model, system, messages, max_tokens, temperature, tools, ...
  kanonik JSON: sort_keys=True, bir xil ajratuvchilar -> sha256

3-MISOL (3000 so'rov, 300 savol, Zipf, 2 do'kon):
  kalit                      API chaqiruv  hit ulushi  noto'g'ri
  kesh yo'q                     3000          0.0%         0
  naiv: faqat savol matni        411         86.3%      1283   <- boshqa do'kon javobi!
  to'liq so'rov                  613         79.6%         0
  to'liq + normallash            434         85.5%         0
  to'liq + norm. + LRU 100       989         67.0%         0

NORMALLASHTIRISH:
  "  Yetkazish  haqida..." va "yetkazish haqida..." - bir xil savol
  ehtiyot: faqat MA'NONI o'zgartirmaydigan normallashtirish

QACHON KESHLAMASLIK:
  T > 0 va xilma-xillik kerak bo'lsa; shaxsiy ma'lumotli javoblar;
  vaqtga bog'liq savollar ("bugun kurs qancha?") - TTL bilan

IDEMPOTENTLIK:
  yon ta'sirli amal (buyurtma yaratish, SMS yuborish, to'lov)
  timeout dan keyin qayta urinish -> amal IKKI marta bajarilishi mumkin
  yechim: mijoz amal uchun BIR MARTA kalit yaratadi, server kalitni eslaydi
  3-misol: 300 amal, 10% timeout -> kalitsiz 35 ta ortiqcha bajarilish, kalit bilan 0

Naiv kalit eng yuqori hit ulushini berdi — va eng xavfli bo'ldi: 1283 ta javob boshqa do'kon uchun yaratilgan edi, chunki kalitda system yo'q. Bu "tez va arzon" ko'rinadigan, lekin jim xato beradigan klassik tuzoq. LRU 100 — xotira cheklangan kesh: Zipf taqsimotida eng mashhur savollar keshda qoladi, hit ulushi 67.0% ga tushdi, lekin narx baribir kesh yo'q holatdan uch barobar kam ($3.41 va $10.35, faraziy narxlar).

Kesh kaliti — javobga ta'sir qiluvchi hamma narsaning kanonik xeshi; yon ta'sirli amalga — idempotentlik kaliti.

2.6. Streaming

text
ODDIY SO'ROV: foydalanuvchi butun javob tayyor bo'lguncha kutadi
STREAMING:    javob bo'laklari (delta) kelishi bilan ko'rsatiladi

HODISALAR KETMA-KETLIGI:
  message_start          - usage.input_tokens
  content_block_delta    - matn bo'lagi (ko'p marta)
  message_delta          - stop_reason, usage.output_tokens
  message_stop           - tugadi

4-MISOL (stub, virtual vaqt):
  birinchi so'z 0.83 s da, oxirgisi 4.43 s da
  streamsiz foydalanuvchi 4.43 s bo'sh ekranga qaraydi

UZILISH:
  message_stop kelmadi -> javob TO'LIQ EMAS
  output_tokens ham yo'q -> xarajatni taxmin qilish kerak
  qisman javobni "tayyor" deb saqlamang

QACHON KERAK:
  chat interfeysi (sezilgan kechikish), uzun javoblar (HTTP timeout dan saqlanish)
  fon ishlarida (batch) - shart emas

Streaming haqiqiy kechikishni emas, sezilgan kechikishni kamaytiradi; message_stop siz oqim — to'liq emas.

2.7. Batch ishlov berish

text
G'OYA:
  ko'p so'rovni bitta paketda yuborish -> provayder o'z vaqtida bajaradi
  afzallik: arzonroq (odatda chegirma bilan), tezlik chegarasiga urilmaydi
  kamchilik: natija darhol emas (daqiqalar - soatlar)

QACHON:
  tungi hisobotlar, katta arxivni tasniflash, baholash to'plamlari (LLM ni baholash darsi)
  foydalanuvchi kutmaydigan har qanday ish

NATIJALAR TARTIBI:
  natijalar IXTIYORIY tartibda keladi
  har so'rovga custom_id -> natijani custom_id bo'yicha bog'lash
  4-misol: pozitsiya bo'yicha bog'lash 998 / 1000 xato, custom_id bo'yicha 0

Kutish mumkin bo'lgan ish — batch ga; natijalarni doim custom_id bo'yicha bog'lang.

2.8. usage dan xarajat hisobi va byudjet

text
FORMULA (har so'rov):
  narx = input_tokens      * narx_kirish
       + cache_read_tokens * narx_kirish * kesh_koeffitsienti
       + output_tokens     * narx_chiqish
  narxlar $/1M token; chiqish odatda kirishdan bir necha barobar qimmat
  ANIQ NARXLAR O'ZGARADI - provayderning rasmiy sahifasidan oling

HISOBOT KESIMLARI:
  vazifa (chat, xulosa, tasnif), model, foydalanuvchi, kun
  4-misol (faraziy narxlar): xarajatning 86% i 'xulosa' da
  -> optimallashtirishni eng katta ulushdan boshlang (kesh, kichikroq model, qisqa kontekst)

BYUDJET QO'RIQCHISI:
  so'rovdan OLDIN: joriy sarf + taxminiy yuqori chegara > byudjet -> to'xtash
  taxmin: kirish tokenlari (count_tokens) + max_tokens * narx_chiqish
  4-misol: $0.50 byudjetda 9 so'rov, sarf $0.457 - oshmadi

Har so'rovning usage ini loglang va vazifa bo'yicha yig'ing — "bizning LLM hisobimiz qayerga ketyapti?" savoliga javob faqat shu loglardan.

2.9. Kalitni xavfsiz saqlash

text
QOIDALAR:
  kalit - muhit o'zgaruvchisida (ANTHROPIC_API_KEY) yoki sirlar menejerida
  kodda, notebook da, git da, loglarda - HECH QACHON
  .env fayli - .gitignore ga; namuna uchun .env.example (kalitsiz)
  logda - niqoblangan: sk-ant-...Ab12

KALIT OSHKOR BO'LSA:
  o'chirish yetmaydi - git tarixida qoladi
  darhol kalitni bekor qilish va yangisini yaratish (rotate)

AVTOMATIK TEKSHIRUV:
  commit dan oldin kalit naqshini qidirish (4-misol: sk-ant-... regex)
  CI da secret scanning

BRAUZER / MOBIL ILOVA:
  kalit mijoz qurilmasiga HECH QACHON bermang
  so'rov o'z serveringiz orqali (proksi) - u yerda kalit, cheklov, log

Kalit — muhitda, koddan tashqarida; oshkor bo'lgan kalit — bekor qilingan kalit.

2.10. Tuzoqlar

Asosiy tuzoqlar: stop_reason ni tekshirmasdan content[0].text ni o'qish; tokenlarni usage o'rniga taxmin bilan hisoblash; system ni messages ichiga qo'yish; API "eslaydi" deb tarixni yubormaslik yoki aksincha — cheksiz tarix yuborish; system prompt ichiga sana/vaqt qo'yib prefiks keshni buzish; bitta except Exception bilan 400 va 401 ni ham qayta yuborish; kutmasdan darhol qayta urinish; urinishlar soni va maksimal kutish chegarasiz; tezlik chegarasini faqat 429 dan keyin "bilib olish"; timeout ni "bajarilmadi" deb hisoblab yon ta'sirli amalni qayta yuborish; kesh kalitida system yoki model yo'qligi; json.dumps ni sort_keys siz kalit uchun ishlatish; qisman stream javobini to'liq deb saqlash; batch natijalarini pozitsiya bo'yicha bog'lash; kalitni kodga yozish, logga chiqarish yoki brauzerga berish; aniq narxlarni kodga "haqiqiy" deb qotirib qo'yish.


3. Tez ma'lumotnoma

python
import os

import anthropic

# ---- mijoz: kalit muhitdan, timeout va SDK ning o'z qayta urinishlari ----
assert "ANTHROPIC_API_KEY" in os.environ, "kalitni muhit o'zgaruvchisiga qo'ying"
client = anthropic.Anthropic(timeout=60.0, max_retries=3)

# ---- oddiy so'rov ----
javob = client.messages.create(
    model="claude-sonnet-5",
    max_tokens=1024,
    system="Siz do'kon yordamchisisiz. Qisqa javob bering.",
    messages=[
        {"role": "user", "content": "Yetkazish qancha vaqt oladi?"},
        {"role": "assistant", "content": "Odatda 2 kun."},
        {"role": "user", "content": "Samarqandga ham shundaymi?"},
    ],
)
if javob.stop_reason == "max_tokens":
    print("javob qirqilgan")
matn = "".join(b.text for b in javob.content if b.type == "text")
print(javob.id, javob.usage.input_tokens, javob.usage.output_tokens)

# ---- xatolarni turi bo'yicha ushlash ----
try:
    javob = client.messages.create(model="claude-sonnet-5", max_tokens=256,
                                   messages=[{"role": "user", "content": "Salom"}])
except anthropic.BadRequestError as e:          # 400 - so'rovni tuzating
    raise
except anthropic.RateLimitError as e:           # 429 - kutib qayta urinish
    kutish = e.response.headers.get("retry-after")
except anthropic.APITimeoutError:               # javob kelmadi - bajarilgan bo'lishi mumkin
    pass
except anthropic.APIStatusError as e:           # boshqa HTTP xatolar (5xx, 529 ...)
    print(e.status_code, e.message)
except anthropic.APIConnectionError:            # tarmoq
    pass

# ---- prefiks kesh: barqaror system ni keshlash ----
javob = client.messages.create(
    model="claude-sonnet-5", max_tokens=1024,
    system=[{"type": "text", "text": "UZUN VA O'ZGARMAS KO'RSATMA ...",
             "cache_control": {"type": "ephemeral"}}],
    messages=[{"role": "user", "content": "Savol"}],
)
print(javob.usage.cache_read_input_tokens, javob.usage.cache_creation_input_tokens)

# ---- streaming ----
with client.messages.stream(model="claude-sonnet-5", max_tokens=2048,
                            messages=[{"role": "user", "content": "Uzun javob yozing"}]) as oqim:
    for bolak in oqim.text_stream:
        print(bolak, end="", flush=True)
    yakuniy = oqim.get_final_message()          # to'liq javob va usage

# ---- tokenlarni oldindan sanash ----
sanoq = client.messages.count_tokens(model="claude-sonnet-5",
                                     messages=[{"role": "user", "content": "Salom"}])
print(sanoq.input_tokens)

# ---- batch ----
from anthropic.types.message_create_params import MessageCreateParamsNonStreaming
from anthropic.types.messages.batch_create_params import Request

paket = client.messages.batches.create(requests=[
    Request(custom_id=f"hujjat-{i}",
            params=MessageCreateParamsNonStreaming(
                model="claude-sonnet-5", max_tokens=512,
                messages=[{"role": "user", "content": f"{i}-hujjatni tasniflang"}]))
    for i in range(1000)
])
# keyinroq: client.messages.batches.retrieve(paket.id).processing_status == "ended"
for natija in client.messages.batches.results(paket.id):
    if natija.result.type == "succeeded":
        saqla(natija.custom_id, natija.result.message)      # custom_id bo'yicha!

LLM API xulosasi

javob = obyekt: content + stop_reason + usage; ikkalasini ham tekshiring
API holatsiz: tarix har safar yuboriladi; prefiks kesh + barqaror system
400/401/403/404 - tuzating; 429/5xx/529/timeout - qayta urining
avval mijoz tomonida cheklovchi, keyin eksponensial backoff (+ jitter), chegara bilan
kesh kaliti = kanonik JSON xeshi (model, system, messages, parametrlar)
yon ta'sir + timeout -> idempotentlik kaliti
streaming - sezilgan kechikish; message_stop siz - to'liq emas
batch - arzon, sekin; custom_id bo'yicha bog'lash
narx = usage x narx jadvali (rasmiy sahifadan); byudjetni so'rovdan oldin tekshirish
kalit - muhitda; oshkor bo'lsa - rotate

4. Batafsil misollar

Misollar Python 3.14 standart kutubxonasi va numpy bilan. API o'rnida — STUB (haqiqiy model emas); retry, backoff, kesh, idempotentlik va xarajat kodi haqiqiy. Vaqt — virtual.

Misol 1 — Xabarlar formati va suhbat tarixi strategiyalari

python
"""Xabarlar formati va suhbat tarixi: tekshiruv, tarix strategiyalari va kirish tokenlari narxi."""

import math

SYSTEM = ("Siz 'Wisar' onlayn do'konining yordamchisisiz. Faqat do'kon mahsulotlari, "
          "yetkazib berish va to'lov haqida javob bering. ") * 12      # ~ uzun ko'rsatma


def token(matn):
    return math.ceil(len(matn) / 4)          # taxminiy (25.2-dars); haqiqiysi: count_tokens


def xabarlarni_tekshir(system, xabarlar):
    """API qoidalariga o'xshash tekshiruv: rollar, birinchi xabar, bo'sh matn."""
    xato = []
    if not isinstance(system, str):
        xato.append("system - alohida parametr, satr bo'lishi kerak")
    for i, x in enumerate(xabarlar):
        if x["role"] not in ("user", "assistant"):
            xato.append(f"[{i}] noma'lum rol {x['role']!r} (system - messages ichida emas)")
        if not str(x.get("content", "")).strip():
            xato.append(f"[{i}] bo'sh content")
        if i and x["role"] == xabarlar[i - 1]["role"]:
            xato.append(f"[{i}] ketma-ket ikki {x['role']!r} - bitta navbatga birlashadi")
    if not xabarlar or xabarlar[0]["role"] != "user":
        xato.append("birinchi xabar 'user' bo'lishi kerak")
    return xato


class SoxtaLLM:
    """STUB - haqiqiy model emas. Faqat kontekstda bor ma'lumotga 'javob beradi':
    'ismim nima' savoliga ism kontekstda bo'lsa - to'g'ri, bo'lmasa - 'bilmayman'."""

    def yarat(self, system, xabarlar):
        kontekst = system + " ".join(x["content"] for x in xabarlar)
        savol = xabarlar[-1]["content"]
        if "ismim nima" in savol:
            javob = "Ismingiz Dilnoza." if "Dilnoza" in kontekst else "Kechirasiz, bilmayman."
        else:
            javob = "Tushundim. " + "Buyurtmangiz bo'yicha ma'lumot: ... " * 6
        kirish = token(system) + sum(token(x["content"]) + 4 for x in xabarlar)
        return javob, {"input_tokens": kirish, "output_tokens": token(javob)}


def ha_yoq(b):
    return "ha" if b else "yo'q"


def tarix_tayyorla(strategiya, tarix, xulosa, oyna=6):
    if strategiya == "to'liq":
        return tarix
    if strategiya == "oyna":
        return tarix[-(2 * oyna - 1):]                       # oxirgi navbatlar, user dan boshlanadi
    bosh = [{"role": "user", "content": "Oldingi suhbat xulosasi: " + xulosa},
            {"role": "assistant", "content": "Tushundim, davom etamiz."}]
    return bosh + tarix[-(2 * oyna - 1):]


def suhbat(strategiya, navbatlar=30, kesh=False):
    llm = SoxtaLLM()
    tarix, xulosa, jami_kirish, jami_kesh, eslab = [], "", 0, 0, None
    oldingi_prefiks = 0
    for n in range(1, navbatlar + 1):
        if n == 2:
            matn = "Ismim Dilnoza, buyurtmam Samarqandga boradi."
        elif n == navbatlar:
            matn = "Aytgancha, ismim nima edi?"
        else:
            matn = f"{n}-savol: yetkazib berish muddati va to'lov usullari haqida aytib bering."
        tarix.append({"role": "user", "content": matn})
        yuborish = tarix_tayyorla(strategiya, tarix, xulosa)
        assert not xabarlarni_tekshir(SYSTEM, yuborish)
        javob, usage = llm.yarat(SYSTEM, yuborish)
        if kesh:                               # prefiks kesh: oldingi so'rovning boshi qayta o'qiladi
            jami_kesh += min(oldingi_prefiks, usage["input_tokens"])
            oldingi_prefiks = usage["input_tokens"] + usage["output_tokens"]
        jami_kirish += usage["input_tokens"]
        tarix.append({"role": "assistant", "content": javob})
        if strategiya == "xulosa" and "Ismim" in matn:
            xulosa += "Mijoz ismi Dilnoza, manzil Samarqand. "     # stub xulosa: muhim faktlar
        if n == navbatlar:
            eslab = javob.startswith("Ismingiz")
    return jami_kirish, jami_kesh, eslab, usage["input_tokens"]


def main() -> None:
    print("=== 1. Xabarlar formatini tekshirish ===")
    holatlar = {
        "to'g'ri": [{"role": "user", "content": "Salom"},
                    {"role": "assistant", "content": "Assalomu alaykum!"},
                    {"role": "user", "content": "Yetkazish qancha?"}],
        "system ichida": [{"role": "system", "content": "Siz yordamchisiz"},
                          {"role": "user", "content": "Salom"}],
        "assistant boshida": [{"role": "assistant", "content": "Salom"}],
        "ketma-ket user": [{"role": "user", "content": "Salom"},
                           {"role": "user", "content": "Yetkazish qancha?"}],
    }
    for nom, x in holatlar.items():
        xato = xabarlarni_tekshir(SYSTEM, x)
        print(f"  {nom:<18} {'OK' if not xato else xato[0]}")

    print("\n=== 2. API holatsiz: har navbatda butun tarix qayta yuboriladi ===")
    print(f"  system ~{token(SYSTEM)} token; 30 navbatli suhbat")
    for n in (1, 10, 20, 30):
        kirish = suhbat("to'liq", navbatlar=n)[3]
        print(f"  {n:>2}-navbat so'rovi: kirish {kirish:>5} token")

    print("\n=== 3. Tarix strategiyalari (30 navbat) ===")
    print("  strategiya     jami kirish  shundan keshdan  2-navbatdagi ism esda")
    natija = {}
    for strategiya, kesh in [("to'liq", False), ("to'liq", True), ("oyna", False), ("xulosa", False)]:
        jami, keshda, eslab, _ = suhbat(strategiya, kesh=kesh)
        nom = strategiya + (" + kesh" if kesh else "")
        natija[nom] = (jami, keshda, eslab)
        print(f"  {nom:<14} {jami:>11} {keshda:>16} {ha_yoq(eslab):>22}")

    print("\n=== 4. Faraziy narx bilan (kirish tokenlari uchun) ===")
    NARX_KIRISH, KESH_KOEF = 3.0, 0.1       # FARAZIY: $ / 1M token; kesh o'qish - 10%
    for nom, (jami, keshda, _) in natija.items():
        dollar = ((jami - keshda) + keshda * KESH_KOEF) * NARX_KIRISH / 1e6
        print(f"  {nom:<14} ${dollar * 1000:.2f} / 1000 suhbat")
    print("  (haqiqiy narxni provayderning rasmiy sahifasidan oling)")

    print("\n=== 5. Xulosa (natijadan) ===")
    t, o, x = natija["to'liq"], natija["oyna"], natija["xulosa"]
    print(f"  to'liq tarix oynadan {t[0] / o[0]:.1f} barobar qimmat, lekin fakt esda: "
          f"{ha_yoq(t[2])} / {ha_yoq(o[2])}")
    if x[2] and not o[2]:
        print("  xulosa: oyna narxiga yaqin va faktni saqladi (agar xulosa uni o'z ichiga olsa)")
    print("  \u2b50 Model hech narsani 'eslamaydi' - kontekstda nima bo'lsa, faqat shu bor")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Xabarlar formatini tekshirish ===
  to'g'ri            OK
  system ichida      [0] noma'lum rol 'system' (system - messages ichida emas)
  assistant boshida  birinchi xabar 'user' bo'lishi kerak
  ketma-ket user     [1] ketma-ket ikki 'user' - bitta navbatga birlashadi

=== 2. API holatsiz: har navbatda butun tarix qayta yuboriladi ===
  system ~363 token; 30 navbatli suhbat
   1-navbat so'rovi: kirish   374 token
  10-navbat so'rovi: kirish  1114 token
  20-navbat so'rovi: kirish  1954 token
  30-navbat so'rovi: kirish  2794 token

=== 3. Tarix strategiyalari (30 navbat) ===
  strategiya     jami kirish  shundan keshdan  2-navbatdagi ism esda
  to'liq               47671                0                     ha
  to'liq + kesh        47671            46530                     ha
  oyna                 22812                0                   yo'q
  xulosa               23694                0                     ha

=== 4. Faraziy narx bilan (kirish tokenlari uchun) ===
  to'liq         $143.01 / 1000 suhbat
  to'liq + kesh  $17.38 / 1000 suhbat
  oyna           $68.44 / 1000 suhbat
  xulosa         $71.08 / 1000 suhbat
  (haqiqiy narxni provayderning rasmiy sahifasidan oling)

=== 5. Xulosa (natijadan) ===
  to'liq tarix oynadan 2.1 barobar qimmat, lekin fakt esda: ha / yo'q
  xulosa: oyna narxiga yaqin va faktni saqladi (agar xulosa uni o'z ichiga olsa)
  ⭐ Model hech narsani 'eslamaydi' - kontekstda nima bo'lsa, faqat shu bor

Nima ko'rsatdi: 1-bo'limda tekshiruvchi uchta tipik xatoni topdi: system ni messages ichiga qo'yish, suhbatni assistant bilan boshlash va ketma-ket ikki user (API ularni birlashtiradi — xato emas, lekin ko'pincha kodda tarixni noto'g'ri yig'ish belgisi). 2-bo'lim API holatsizligining narxini ko'rsatadi: system prompt ~363 token, 1-navbatda kirish 374 token, 30-navbatda esa 2794 — har navbatda butun tarix qayta yuboriladi. 3-bo'limda 30 navbatli suhbat uchun jami kirish tokenlari: to'liq tarix 47 671, sirpanuvchi oyna (oxirgi 6 navbat) 22 812, xulosa + oyna 23 694. To'liq tarix oynadan 2.1 barobar qimmat — lekin faqat to'liq tarix va xulosa 2-navbatda aytilgan ismni 30-navbatda "esladi"; oyna uni yo'qotdi. Stub xulosasi muhim faktni saqlashi — biz yozgan qoida; haqiqiy modelning xulosasi nimani tashlab yuborishini oldindan bilmaysiz. 4-bo'limda faraziy narxlar (kirish $3 / 1M token, keshdan o'qish 10%) bilan: to'liq tarix $143.01 / 1000 suhbat, prefiks kesh bilan $17.38 — bu stubda har so'rov oldingi so'rovning butun prefiksini keshdan o'qiydi deb soddalashtirilgan (haqiqiy keshda minimal prefiks uzunligi va yashash muddati bor). Xulosa: tarixni qisqartirishdan oldin prefiksni barqaror qilib keshni yoqing — u "xotira" ni saqlagan holda narxni eng ko'p kamaytirdi. Bog'liq bo'limlar: 2.1, 2.2.

Misol 2 — Xatolar, eksponensial backoff, jitter va cheklovchi (virtual vaqt)

python
"""Xatolar turlari va qayta urinish: eksponensial backoff, jitter va retry-after (virtual vaqtda)."""

import heapq
import math
import random

import numpy as np

QAYTA_URINILADI = {429, 500, 529, "timeout"}           # 400 - so'rovning o'zi xato, qayta urinilmaydi


class SoxtaServer:
    """STUB server - haqiqiy API emas. Token-bucket tezlik chegarasi (sekundiga TEZLIK so'rov),
    tasodifiy 529 (band), 500 va timeout, buzuq so'rovga doim 400. Ehtimollar - FARAZ."""

    TEZLIK, SIGIM = 5.0, 5.0

    def __init__(self, rng):
        self.rng = rng
        self.tokenlar, self.oxirgi = self.SIGIM, 0.0

    def sorov(self, t, buzuq):
        self.tokenlar = min(self.SIGIM, self.tokenlar + (t - self.oxirgi) * self.TEZLIK)
        self.oxirgi = t
        if self.tokenlar < 1:
            return 429, (1 - self.tokenlar) / self.TEZLIK, 0.05     # retry-after, javob vaqti
        self.tokenlar -= 1
        if buzuq:
            return 400, None, 0.05
        r = self.rng.random()
        if r < 0.03:
            return 529, None, 0.3
        if r < 0.04:
            return 500, None, 0.3
        if r < 0.05:
            return "timeout", None, 10.0          # mijoz 10 s kutib, o'zi uzadi
        return 200, None, 1.0


def kutish(strategiya, urinish, retry_after, rng, asos=0.5, shift=60.0):
    """urinish - nechanchi muvaffaqiyatsiz urinishdan keyin (1, 2, ...)."""
    if strategiya == "darhol":
        return 0.05
    chegara = min(shift, asos * 2 ** (urinish - 1))
    if strategiya == "eksponensial":
        return chegara
    kut = rng.uniform(0, chegara)                   # full jitter
    if strategiya == "jitter + retry-after" and retry_after is not None:
        kut = max(kut, retry_after)
    return kut


def simulyatsiya(strategiya, urug, mijozlar=20, har_biri=5, maks_urinish=10):
    rng_srv, rng_kl = random.Random(urug), random.Random(10_000 + urug)
    server = SoxtaServer(rng_srv)
    buzuq = {(m, k): rng_kl.random() < 0.02 for m in range(mijozlar) for k in range(har_biri)}
    slot = [0.0]                                   # mijoz tomonidagi umumiy cheklovchi

    def rejalashtir(t):
        """Cheklovchi bo'lsa: keyingi bo'sh slotni band qiladi (TEZLIK dan 5% sekinroq)."""
        if strategiya != "cheklovchi + jitter":
            return t
        s = max(t, slot[0])
        slot[0] = s + 1.05 / SoxtaServer.TEZLIK
        return s

    navbat = [(rejalashtir(0.0), m, m, 0, 1) for m in range(mijozlar)]   # (vaqt, tartib, mijoz, so'rov, urinish)
    heapq.heapify(navbat)
    tartib = mijozlar
    oldin_400 = set()
    st = {"urinish": 0, "429": 0, "ok": 0, "400 qayta": 0, "yiqildi": 0, "tugash": 0.0}
    while navbat:
        t, _, m, k, u = heapq.heappop(navbat)
        st["urinish"] += 1
        kod, retry_after, dt = server.sorov(t, buzuq[m, k])
        t_javob = t + dt
        keyingi = None
        if kod == 200:
            st["ok"] += 1
            keyingi = (t_javob, k + 1, 1)
        else:
            st["429"] += kod == 429
            qayta = kod in QAYTA_URINILADI or strategiya == "darhol"   # 'darhol' - 400 ni ham
            if kod == 400:
                st["400 qayta"] += (m, k) in oldin_400       # aynan shu so'rov yana 400 oldi
                oldin_400.add((m, k))
            if strategiya != "retry yo'q" and qayta and u < maks_urinish:
                keyingi = (t_javob + kutish(strategiya, u, retry_after, rng_kl), k, u + 1)
            else:
                st["yiqildi"] += kod != 400
                keyingi = (t_javob, k + 1, 1)
        st["tugash"] = max(st["tugash"], t_javob)
        if keyingi[1] < har_biri:
            tartib += 1
            heapq.heappush(navbat, (rejalashtir(keyingi[0]), tartib, m, keyingi[1], keyingi[2]))
    st["togri"] = sum(not b for b in buzuq.values())
    return st


def main() -> None:
    print("=== 1. Xatolar turlari va qaror ===")
    for kod, izoh in [(400, "so'rov xato (validatsiya)"), (429, "tezlik chegarasi"),
                      (500, "server ichki xatosi"), (529, "server band (overloaded)"),
                      ("timeout", "javob kelmadi")]:
        qaror = "qayta urinish" if kod in QAYTA_URINILADI else "TUZATISH kerak, qayta emas"
        print(f"  {str(kod):<8} {izoh:<28} -> {qaror}")

    print("\n=== 2. 20 mijoz x 5 so'rov bir vaqtda (server: 5 so'rov/s), 5 urug' ===")
    print("  strategiya             muvaff.  urinish   429 lar  400 qayta  tugash (s)")
    strategiyalar = ["retry yo'q", "darhol", "eksponensial", "full jitter", "jitter + retry-after",
                     "cheklovchi + jitter"]
    natija = {}
    for s in strategiyalar:
        r = [simulyatsiya(s, u) for u in range(5)]
        natija[s] = r
        ok = np.mean([x["ok"] / x["togri"] for x in r])
        print(f"  {s:<22} {ok:>7.1%} {np.mean([x['urinish'] for x in r]):>8.0f} "
              f"{np.mean([x['429'] for x in r]):>9.0f} {np.mean([x['400 qayta'] for x in r]):>10.0f} "
              f"{np.mean([x['tugash'] for x in r]):>11.1f}")
    print("  (tugash - oxirgi javob kelgan virtual vaqt; time.sleep ishlatilmadi)")

    print("\n=== 3. Jitter ta'siri: juftlashgan farq (bir xil urug'lar) ===")
    for a, b, kalit in [("full jitter", "eksponensial", "429"),
                        ("full jitter", "eksponensial", "tugash"),
                        ("jitter + retry-after", "full jitter", "429")]:
        f = np.array([x[kalit] - y[kalit] for x, y in zip(natija[a], natija[b])])
        se = f.std(ddof=1) / math.sqrt(len(f))
        print(f"  {kalit:<6} {a} - {b}: {f.mean():+.1f}, SE {se:.1f} -> "
              f"{'sezilarli' if abs(f.mean()) > 2 * se else 'sezilarli emas'}")

    print("\n=== 4. Kutish vaqtlari (urinish 1..6, asos 0.5 s, shift 60 s) ===")
    rng = random.Random(0)
    print("  eksponensial: " + ", ".join(f"{kutish('eksponensial', u, None, rng):.1f}"
                                         for u in range(1, 7)))
    print("  full jitter:  " + ", ".join(f"{kutish('full jitter', u, None, rng):.1f}"
                                         for u in range(1, 7)))

    print("\n=== 5. Xulosa (natijadan) ===")
    yoq, jit = natija["retry yo'q"], natija["full jitter"]
    print(f"  qayta urinishsiz muvaffaqiyat {np.mean([x['ok'] / x['togri'] for x in yoq]):.1%} - "
          f"429 lar birinchi soniyalarda to'planadi")
    d = np.mean([x["400 qayta"] for x in natija["darhol"]])
    if d > 0:
        print(f"  'darhol' 400 ni ham qayta yubordi: {d:.0f} ta befoyda so'rov")
    c = natija["cheklovchi + jitter"]
    print(f"  mijoz tomonida cheklovchi: 429 lar {np.mean([x['429'] for x in c]):.0f}, "
          f"urinishlar {np.mean([x['urinish'] for x in c]):.0f} (so'rovlar 100)")
    print("  \u2b50 Avval tezlikni o'zingiz cheklang; qayta urinish - 429/5xx/timeout, backoff + jitter")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Xatolar turlari va qaror ===
  400      so'rov xato (validatsiya)    -> TUZATISH kerak, qayta emas
  429      tezlik chegarasi             -> qayta urinish
  500      server ichki xatosi          -> qayta urinish
  529      server band (overloaded)     -> qayta urinish
  timeout  javob kelmadi                -> qayta urinish

=== 2. 20 mijoz x 5 so'rov bir vaqtda (server: 5 so'rov/s), 5 urug' ===
  strategiya             muvaff.  urinish   429 lar  400 qayta  tugash (s)
  retry yo'q               18.9%      100        79          0         5.0
  darhol                   28.7%      799       767          1         6.8
  eksponensial            100.0%      189        84          0        51.7
  full jitter             100.0%      214       108          0        39.7
  jitter + retry-after    100.0%      220       114          0        38.2
  cheklovchi + jitter     100.0%      105         0          0        30.0
  (tugash - oxirgi javob kelgan virtual vaqt; time.sleep ishlatilmadi)

=== 3. Jitter ta'siri: juftlashgan farq (bir xil urug'lar) ===
  429    full jitter - eksponensial: +24.6, SE 6.0 -> sezilarli
  tugash full jitter - eksponensial: -12.0, SE 7.0 -> sezilarli emas
  429    jitter + retry-after - full jitter: +5.8, SE 7.4 -> sezilarli emas

=== 4. Kutish vaqtlari (urinish 1..6, asos 0.5 s, shift 60 s) ===
  eksponensial: 0.5, 1.0, 2.0, 4.0, 8.0, 16.0
  full jitter:  0.4, 0.8, 0.8, 1.0, 4.1, 6.5

=== 5. Xulosa (natijadan) ===
  qayta urinishsiz muvaffaqiyat 18.9% - 429 lar birinchi soniyalarda to'planadi
  'darhol' 400 ni ham qayta yubordi: 1 ta befoyda so'rov
  mijoz tomonida cheklovchi: 429 lar 0, urinishlar 105 (so'rovlar 100)
  ⭐ Avval tezlikni o'zingiz cheklang; qayta urinish - 429/5xx/timeout, backoff + jitter

Nima ko'rsatdi: stub server sekundiga 5 so'rov o'tkazadi (token-bucket), qolganiga 429 va retry-after qaytaradi; o'tgan so'rovlarning 3% i 529, 1% i 500, 1% i timeout (ehtimollar — faraz), 2% so'rov buzuq (doim 400). 20 mijoz 5 tadan so'rovni bir vaqtda boshlaydi. Qayta urinishsiz faqat 18.9% muvaffaqiyat — 79 ta so'rov birinchi soniyalarda 429 olib tashlab ketildi. "Darhol" qayta urinish (va 400 ni ham qayta yuborish) serverni 767 ta 429 bilan to'ldirdi va baribir 28.7% da qoldi: 10 urinish yarim soniyada tugaydi, tezlik chegarasi esa tiklanishga ulgurmaydi. Eksponensial backoff 100% ga chiqardi (189 urinish, 51.7 s). Kutilmagan natija: full jitter 429 larni ko'paytirdi (214 urinish, 108 ta 429; juftlashgan farq +24.6, SE 6.0 — sezilarli), chunki uning o'rtacha kutishi ikki barobar qisqa; tugash vaqti qisqardi (39.7 s), lekin farq -12.0, SE 7.0 — sezilarli emas. retry-after ni hisobga olish ham sezilarli farq bermadi (+5.8, SE 7.4). Bu jitterni "foydasiz" demaydi — bu yuklamada mijozlar backoff tufayli allaqachon yetarli darajada tarqalib ketadi; jitter mijozlar bir lahzada sinxron qaytganda (masalan, server qayta ishga tushgandan keyin) muhim. Aniq g'olib — mijoz tomonidagi cheklovchi: 0 ta 429, 105 urinish (100 so'rov + 5 ta 5xx/timeout qayta urinishi) va eng qisqa tugash 30.0 s. 100 so'rovni 5 so'rov/s da yuborishning nazariy minimumi ~21 s — qolgani qayta urinishlar va oxirgi javoblar. Bog'liq bo'limlar: 2.3, 2.4.

Misol 3 — Javoblar keshi va idempotentlik

python
"""Javoblar keshi va idempotentlik: kesh kaliti, normallashtirish, LRU va takroriy yon ta'sirlar."""

import hashlib
import json
import random
import re
from collections import OrderedDict

import numpy as np

DOKONLAR = {"A": "Siz 'Bozor24' do'konining yordamchisisiz. Yetkazish 2 kun.",
            "B": "Siz 'TezSavdo' do'konining yordamchisisiz. Yetkazish 1 kun."}
MAVZULAR = ["yetkazish", "to'lov", "qaytarish", "kafolat", "chegirma", "buyurtma holati"]


def savollar_havzasi():
    return [f"{m} haqida {i}-savol: qanday ishlaydi?" for i in range(50) for m in MAVZULAR]


class SoxtaLLM:
    """STUB - haqiqiy model emas: javob system va savolga deterministik bog'liq."""

    def __init__(self):
        self.chaqiruv = 0

    def yarat(self, sorov):
        self.chaqiruv += 1
        dokon = re.search(r"'(\w+)'", sorov["system"]).group(1)
        savol = " ".join(sorov["messages"][-1]["content"].split()).lower()
        return f"[{dokon}] {savol} -> javob"


def kalit_naiv(sorov):
    return sorov["messages"][-1]["content"]                       # faqat savol matni


def kanonik(sorov, normallash):
    s = json.loads(json.dumps(sorov))
    if normallash:
        for x in s["messages"]:
            x["content"] = " ".join(x["content"].split()).lower()
    return json.dumps(s, sort_keys=True, ensure_ascii=False, separators=(",", ":"))


def kalit_togri(sorov, normallash=False):
    """Javobga ta'sir qiladigan HAMMA narsa: model, system, messages, parametrlar."""
    return hashlib.sha256(kanonik(sorov, normallash).encode()).hexdigest()


class LRUKesh:
    def __init__(self, sigim=None):
        self.d, self.sigim = OrderedDict(), sigim

    def ol(self, k):
        if k in self.d:
            self.d.move_to_end(k)
            return self.d[k]
        return None

    def qoy(self, k, v):
        self.d[k] = v
        self.d.move_to_end(k)
        if self.sigim is not None and len(self.d) > self.sigim:
            self.d.popitem(last=False)                                # eng eski ishlatilganni chiqarish


def yuklama(n, urug):
    rng = np.random.default_rng(urug)
    havza = savollar_havzasi()
    vazn = 1.0 / np.arange(1, len(havza) + 1) ** 1.1                  # Zipf: bir necha savol juda ko'p
    idx = rng.choice(len(havza), n, p=vazn / vazn.sum())
    sorovlar = []
    for i in idx:
        savol = havza[i]
        if rng.random() < 0.25:                                       # foydalanuvchi yozish uslubi
            savol = "  " + savol.capitalize().replace(" ", "  ", 1) + " "
        dokon = "A" if rng.random() < 0.5 else "B"
        sorovlar.append({"model": "claude-sonnet-5", "max_tokens": 300, "temperature": 0.0,
                         "system": DOKONLAR[dokon], "messages": [{"role": "user", "content": savol}]})
    return sorovlar


def ishga_tushir(sorovlar, kalit_fn, sigim=None):
    llm, kesh, notogri = SoxtaLLM(), LRUKesh(sigim), 0
    for s in sorovlar:
        haqiqiy = SoxtaLLM().yarat(s)                                 # tekshiruv uchun "to'g'ri javob"
        if kalit_fn is None:
            javob = llm.yarat(s)
        else:
            k = kalit_fn(s)
            javob = kesh.ol(k)
            if javob is None:
                javob = llm.yarat(s)
                kesh.qoy(k, javob)
        notogri += javob != haqiqiy
    return llm.chaqiruv, notogri


def main() -> None:
    print("=== 1. Kesh kaliti: tartib va bo'shliqlar ===")
    a = {"model": "m", "system": "s", "messages": [{"role": "user", "content": "Salom"}]}
    b = {"messages": [{"content": "Salom", "role": "user"}], "system": "s", "model": "m"}
    print(f"  json.dumps (sort_keys yo'q) teng: {json.dumps(a) == json.dumps(b)}")
    print(f"  kanonik (sort_keys=True) teng:    {kanonik(a, False) == kanonik(b, False)}")
    c = {**a, "messages": [{"role": "user", "content": "  salom "}]}
    print(f"  '  salom ' va 'Salom': normallashsiz {kalit_togri(a) == kalit_togri(c)}, "
          f"normallash bilan {kalit_togri(a, True) == kalit_togri(c, True)}")

    print("\n=== 2. 3000 so'rov (300 savol, Zipf, 2 do'kon), 5 urug' ===")
    print("  kalit                        API chaqiruv   hit ulushi  noto'g'ri javob")
    variantlar = [("kesh yo'q", None, None), ("naiv: faqat savol", kalit_naiv, None),
                  ("to'liq so'rov", kalit_togri, None),
                  ("to'liq + normallash", lambda s: kalit_togri(s, True), None),
                  ("to'liq + norm. + LRU 100", lambda s: kalit_togri(s, True), 100)]
    natija = {}
    for nom, fn, sigim in variantlar:
        r = np.array([ishga_tushir(yuklama(3000, u), fn, sigim) for u in range(5)])
        natija[nom] = r
        print(f"  {nom:<27} {r[:, 0].mean():>12.0f} {1 - r[:, 0].mean() / 3000:>12.1%} "
              f"{r[:, 1].mean():>16.0f}")

    print("\n=== 3. Faraziy narx (1 chaqiruv ~ 400 kirish + 150 chiqish token) ===")
    NARX_KIR, NARX_CHIQ = 3.0, 15.0                    # FARAZIY $ / 1M token - rasmiy sahifadan oling
    bir = (400 * NARX_KIR + 150 * NARX_CHIQ) / 1e6
    for nom in ["kesh yo'q", "to'liq + normallash", "to'liq + norm. + LRU 100"]:
        print(f"  {nom:<27} ${natija[nom][:, 0].mean() * bir:.2f} / 3000 so'rov")

    print("\n=== 4. Idempotentlik: yon ta'sirli amal va timeout dan keyin qayta urinish ===")
    for kalit_bilan in (False, True):
        rng, bajarilgan, kalitlar = random.Random(7), [], set()
        for op in range(300):                            # 300 ta "buyurtma yaratish" amali
            ikalit = f"buyurtma-{op}"                     # mijoz amal uchun BIR MARTA yaratadi
            for _ in range(4):
                if not (kalit_bilan and ikalit in kalitlar):
                    bajarilgan.append(op)                 # server amalni bajardi
                    kalitlar.add(ikalit)
                if rng.random() >= 0.10:                  # 10% - javob yo'lda yo'qoldi (timeout)
                    break
        takror = len(bajarilgan) - len(set(bajarilgan))
        holat = "bor" if kalit_bilan else "yo'q"
        print(f"  idempotentlik kaliti {holat:<4}: 300 amal -> bajarildi {len(bajarilgan)}, "
              f"ortiqcha (takroriy) bajarilish {takror}")

    print("\n=== 5. Xulosa (natijadan) ===")
    nv = natija["naiv: faqat savol"]
    if nv[:, 1].mean() > 0:
        print(f"  naiv kalit: hit ko'p, lekin {nv[:, 1].mean():.0f} ta javob BOSHQA do'kon uchun edi")
    t, n = natija["to'liq so'rov"], natija["to'liq + normallash"]
    print(f"  normallash hit ulushini {1 - t[:, 0].mean() / 3000:.1%} -> "
          f"{1 - n[:, 0].mean() / 3000:.1%} ga oshirdi, xato 0")
    print("  \u2b50 Kalit = javobga ta'sir qiluvchi hamma narsa; yon ta'sirli amalga idempotentlik kaliti")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Kesh kaliti: tartib va bo'shliqlar ===
  json.dumps (sort_keys yo'q) teng: False
  kanonik (sort_keys=True) teng:    True
  '  salom ' va 'Salom': normallashsiz False, normallash bilan True

=== 2. 3000 so'rov (300 savol, Zipf, 2 do'kon), 5 urug' ===
  kalit                        API chaqiruv   hit ulushi  noto'g'ri javob
  kesh yo'q                           3000         0.0%                0
  naiv: faqat savol                    411        86.3%             1283
  to'liq so'rov                        613        79.6%                0
  to'liq + normallash                  434        85.5%                0
  to'liq + norm. + LRU 100             989        67.0%                0

=== 3. Faraziy narx (1 chaqiruv ~ 400 kirish + 150 chiqish token) ===
  kesh yo'q                   $10.35 / 3000 so'rov
  to'liq + normallash         $1.50 / 3000 so'rov
  to'liq + norm. + LRU 100    $3.41 / 3000 so'rov

=== 4. Idempotentlik: yon ta'sirli amal va timeout dan keyin qayta urinish ===
  idempotentlik kaliti yo'q: 300 amal -> bajarildi 335, ortiqcha (takroriy) bajarilish 35
  idempotentlik kaliti bor : 300 amal -> bajarildi 300, ortiqcha (takroriy) bajarilish 0

=== 5. Xulosa (natijadan) ===
  naiv kalit: hit ko'p, lekin 1283 ta javob BOSHQA do'kon uchun edi
  normallash hit ulushini 79.6% -> 85.5% ga oshirdi, xato 0
  ⭐ Kalit = javobga ta'sir qiluvchi hamma narsa; yon ta'sirli amalga idempotentlik kaliti

Nima ko'rsatdi: 1-bo'lim kalit qurishning ikki tuzog'ini ko'rsatadi: kalitlari boshqa tartibda yozilgan bir xil lug'atlar json.dumps da farq qiladi (False), sort_keys=True bilan — teng (True); bo'sh joy va katta harf farqi normallashtirishsiz alohida kalit beradi. 2-bo'limda 3000 so'rov (300 savol, Zipf taqsimoti — bir necha savol juda ko'p so'raladi; 25% so'rov ortiqcha bo'sh joy va katta harf bilan; ikki do'kon — ikki xil system). Naiv kalit (faqat savol matni) eng yuqori hit ulushini berdi (86.3%), lekin 1283 ta javob boshqa do'kon uchun edi — jim xato. To'liq so'rov kaliti 79.6% hit va 0 xato; ma'noni o'zgartirmaydigan normallashtirish hit ni 85.5% ga ko'tardi, xato baribir 0. LRU 100 — xotira 100 javob bilan cheklangan: hit 67.0%, lekin faraziy narx kesh yo'q holatdagi $10.35 o'rniga $3.41 (cheksiz keshda $1.50). 4-bo'lim idempotentlik: 300 ta yon ta'sirli amal, har urinishda 10% ehtimol bilan javob yo'lda yo'qoladi (server esa amalni bajargan). Kalitsiz qayta urinishlar 35 ta ortiqcha bajarilish berdi — real hayotda bu 35 ta ikki marta yaratilgan buyurtma yoki ikki marta yechilgan to'lov. Mijoz har amal uchun bir marta yaratgan kalit bilan server takrorni tanidi — 0. Bog'liq bo'limlar: 2.5.

Misol 4 — usage dan xarajat, byudjet, streaming, batch va kalit xavfsizligi

python
"""Xarajat usage dan, byudjet qo'riqchisi, streaming hodisalari, batch va kalitni xavfsiz saqlash."""

import heapq
import math
import random
import re

# FARAZIY narx jadvali ($ / 1M token): haqiqiy narxni provayderning rasmiy sahifasidan oling
NARX = {"katta": {"kirish": 6.0, "chiqish": 30.0},
        "orta": {"kirish": 2.0, "chiqish": 10.0},
        "kichik": {"kirish": 0.5, "chiqish": 2.5}}
KESH_OQISH_KOEF = 0.1                       # faraz: keshdan o'qilgan kirish tokeni 10% narxda
BATCH_CHEGIRMA = 0.5                        # faraz: batch so'rovlar yarim narxda


def narx(model, usage):
    n = NARX[model]
    return (usage["input_tokens"] * n["kirish"]
            + usage.get("cache_read_input_tokens", 0) * n["kirish"] * KESH_OQISH_KOEF
            + usage["output_tokens"] * n["chiqish"]) / 1e6


class SoxtaMijoz:
    """STUB - haqiqiy API emas. Javobda haqiqiy API dagi kabi usage maydonlari bor."""

    def __init__(self, urug):
        self.rng = random.Random(urug)

    def create(self, model, vazifa):
        kir = {"chat": 1200, "xulosa": 6000, "tasnif": 300}[vazifa]
        chiq = {"chat": 250, "xulosa": 400, "tasnif": 5}[vazifa]
        kesh = int(kir * 0.8) if vazifa == "chat" and self.rng.random() < 0.7 else 0
        return {"usage": {"input_tokens": int(kir * self.rng.uniform(0.8, 1.2)) - kesh,
                          "cache_read_input_tokens": kesh,
                          "output_tokens": int(chiq * self.rng.uniform(0.5, 1.5))}}

    def stream(self, matn, uzilish=None):
        """Hodisalar ketma-ketligi (virtual vaqt bilan), haqiqiy streaming API ga o'xshash."""
        t = 0.8                                                 # birinchi tokengacha
        yield t, {"type": "message_start", "usage": {"input_tokens": 900}}
        sozlar = matn.split(" ")
        for i, s in enumerate(sozlar):
            if uzilish is not None and i == uzilish:
                return                                          # ulanish uzildi
            t += 0.03
            yield t, {"type": "content_block_delta", "text": s + " "}
        yield t, {"type": "message_delta", "stop_reason": "end_turn",
                  "usage": {"output_tokens": math.ceil(len(matn) / 4)}}
        yield t, {"type": "message_stop"}


def stream_yigish(hodisalar):
    matn, usage, tugadi, birinchi = [], {}, False, None
    for t, h in hodisalar:
        if h["type"] == "message_start":
            usage.update(h["usage"])
        elif h["type"] == "content_block_delta":
            birinchi = t if birinchi is None else birinchi
            matn.append(h["text"])
        elif h["type"] == "message_delta":
            usage.update(h["usage"])
        elif h["type"] == "message_stop":
            tugadi = True
        oxirgi = t
    return "".join(matn), usage, tugadi, birinchi, oxirgi


def niqobla(kalit):
    return kalit[:7] + "..." + kalit[-4:] if kalit and len(kalit) > 12 else "***"


def main() -> None:
    mijoz = SoxtaMijoz(0)

    print("=== 1. usage dan xarajat: vazifa va model bo'yicha (600 so'rov) ===")
    marshrut = {"chat": "orta", "xulosa": "katta", "tasnif": "kichik"}
    jami = {}
    for i in range(600):
        vazifa = ["chat", "chat", "xulosa", "tasnif", "tasnif", "tasnif"][i % 6]
        u = mijoz.create(marshrut[vazifa], vazifa)["usage"]
        j = jami.setdefault(vazifa, [0, 0, 0, 0.0])
        j[0] += u["input_tokens"]
        j[1] += u["cache_read_input_tokens"]
        j[2] += u["output_tokens"]
        j[3] += narx(marshrut[vazifa], u)
    print("  vazifa  model    kirish   keshdan   chiqish    narx ($)")
    for v, (a, b, c, d) in jami.items():
        print(f"  {v:<7} {marshrut[v]:<7} {a:>8} {b:>9} {c:>9} {d:>10.3f}")
    eng = max(jami, key=lambda v: jami[v][3])
    print(f"  eng qimmat: {eng} ({jami[eng][3] / sum(x[3] for x in jami.values()):.0%})")

    print("\n=== 2. Byudjet qo'riqchisi: 'xulosa' vazifasi, byudjet $0.50 ===")
    sarf, bajarildi = 0.0, 0
    taxmin = narx("katta", {"input_tokens": 7200, "output_tokens": 600})  # yuqori chegara
    for _ in range(100):
        if sarf + taxmin > 0.50:
            break                                        # so'rovdan OLDIN tekshirish
        sarf += narx("katta", mijoz.create("katta", "xulosa")["usage"])
        bajarildi += 1
    print(f"  bajarildi {bajarildi} / 100, sarflandi ${sarf:.3f}, byudjetdan oshmadi: {sarf <= 0.50}")

    print("\n=== 3. Streaming: birinchi so'z va to'liq javob (virtual vaqt) ===")
    javob = " ".join(["Buyurtmangiz"] + ["ertaga yetkaziladi"] * 60)
    matn, usage, tugadi, bir, oxir = stream_yigish(mijoz.stream(javob))
    print(f"  birinchi so'z {bir:.2f} s da, oxirgisi {oxir:.2f} s da; "
          f"so'z {len(matn.split())}, usage {usage}, to'liq: {tugadi}")
    matn2, usage2, tugadi2, _, _ = stream_yigish(mijoz.stream(javob, uzilish=40))
    print(f"  uzilgan oqim: so'z {len(matn2.split())}, output_tokens bormi: "
          f"{'output_tokens' in usage2}, to'liq: {tugadi2} -> qayta so'rash kerak")

    print("\n=== 4. 1000 so'rov: parallel (8 oqim) va batch ===")
    rng = random.Random(1)
    kechikish = [rng.uniform(2, 8) for _ in range(1000)]
    oqimlar = [0.0] * 8
    for k in kechikish:                                   # har so'rov eng tez bo'shagan oqimga
        heapq.heapreplace(oqimlar, oqimlar[0] + k)
    print(f"  parallel 8 oqim: tugash {max(oqimlar) / 60:.1f} daqiqa, narx 1.00 x")
    print(f"  batch: tugash - soatlargacha (provayder kafolati ichida), narx {BATCH_CHEGIRMA:.2f} x (faraz)")
    natijalar = [(f"sorov-{i}", f"javob-{i}") for i in range(1000)]
    rng.shuffle(natijalar)                                # batch natijalari ixtiyoriy tartibda
    pozitsiya = sum(n[1] != f"javob-{i}" for i, n in enumerate(natijalar))
    lugat = dict(natijalar)
    custom = sum(lugat[f"sorov-{i}"] != f"javob-{i}" for i in range(1000))
    print(f"  natijani pozitsiya bo'yicha bog'lash: {pozitsiya} xato; custom_id bo'yicha: {custom} xato")

    print("\n=== 5. Kalit: muhitdan olish, niqoblash va koddan izlash ===")
    muhit = {"ANTHROPIC_API_KEY": "sk-ant-api03-" + "x" * 40 + "Ab12"}     # soxta kalit
    kalit = muhit.get("ANTHROPIC_API_KEY")
    print(f"  kalit topildi: {kalit is not None}, logda: {niqobla(kalit)}")
    fayllar = {"app.py": 'client = anthropic.Anthropic()',
               "eski.py": 'client = anthropic.Anthropic(api_key="sk-ant-api03-' + "y" * 40 + '")',
               "README.md": "Kalitni ANTHROPIC_API_KEY ga yozing"}
    naqsh = re.compile(r"sk-ant-[A-Za-z0-9_\-]{20,}")
    for nom, matn in fayllar.items():
        if naqsh.search(matn):
            print(f"  DIQQAT: {nom} ichida kalit bor -> o'chiring va kalitni almashtiring (rotate)")

    print("\n=== 6. Xulosa (natijadan) ===")
    print(f"  xarajatning {jami[eng][3] / sum(x[3] for x in jami.values()):.0%} i '{eng}' da - "
          f"optimallashtirishni shundan boshlang")
    print("  \u2b50 Har so'rovning usage ini loglang; natijani custom_id bilan bog'lang; kalit - muhitda")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. usage dan xarajat: vazifa va model bo'yicha (600 so'rov) ===
  vazifa  model    kirish   keshdan   chiqish    narx ($)
  chat    orta      114863    123840     49175      0.746
  xulosa  katta     600894         0     40694      4.826
  tasnif  kichik     90413         0      1304      0.048
  eng qimmat: xulosa (86%)

=== 2. Byudjet qo'riqchisi: 'xulosa' vazifasi, byudjet $0.50 ===
  bajarildi 9 / 100, sarflandi $0.457, byudjetdan oshmadi: True

=== 3. Streaming: birinchi so'z va to'liq javob (virtual vaqt) ===
  birinchi so'z 0.83 s da, oxirgisi 4.43 s da; so'z 121, usage {'input_tokens': 900, 'output_tokens': 288}, to'liq: True
  uzilgan oqim: so'z 40, output_tokens bormi: False, to'liq: False -> qayta so'rash kerak

=== 4. 1000 so'rov: parallel (8 oqim) va batch ===
  parallel 8 oqim: tugash 10.7 daqiqa, narx 1.00 x
  batch: tugash - soatlargacha (provayder kafolati ichida), narx 0.50 x (faraz)
  natijani pozitsiya bo'yicha bog'lash: 998 xato; custom_id bo'yicha: 0 xato

=== 5. Kalit: muhitdan olish, niqoblash va koddan izlash ===
  kalit topildi: True, logda: sk-ant-...Ab12
  DIQQAT: eski.py ichida kalit bor -> o'chiring va kalitni almashtiring (rotate)

=== 6. Xulosa (natijadan) ===
  xarajatning 86% i 'xulosa' da - optimallashtirishni shundan boshlang
  ⭐ Har so'rovning usage ini loglang; natijani custom_id bilan bog'lang; kalit - muhitda

Nima ko'rsatdi: narx jadvali va kesh/batch koeffitsientlari — faraziy (model nomlari o'rniga "katta/o'rta/kichik" darajalari); haqiqiy narxlarni provayderning rasmiy sahifasidan oling. 1-bo'limda 600 so'rovning usage i vazifa bo'yicha yig'ildi: so'rovlarning faqat oltidan biri bo'lgan xulosa (uzun kirish, katta model) xarajatning 86% ini tashkil qildi, tasnif esa so'rovlarning yarmi bo'lsa ham $0.048 — kichik model va 5 tokenli javob. chat da kirish tokenlarining katta qismi keshdan o'qildi (123 840 va 114 863). Bunday jadval optimallashtirishni qayerdan boshlashni aytadi. 2-bo'lim: byudjet qo'riqchisi har so'rovdan oldin "joriy sarf + yuqori chegara" ni tekshirdi — $0.50 byudjetda 9 so'rov, sarf $0.457, oshib ketish yo'q. 3-bo'lim streaming hodisalarini yig'di: birinchi so'z 0.83 s da, oxirgisi 4.43 s da — streamsiz foydalanuvchi 4.43 s bo'sh ekranga qarardi; usage message_start va message_delta dan yig'ildi. Uzilgan oqimda 40 so'z keldi, message_stop va output_tokens yo'q — javob to'liq emas deb belgilanadi. 4-bo'lim: 1000 so'rov 8 parallel oqimda 10.7 daqiqa; batch arzonroq, lekin soatlab kutish mumkin. Batch natijalari aralash tartibda keldi: pozitsiya bo'yicha bog'lash 998 xato, custom_id bo'yicha — 0. 5-bo'lim: kalit muhitdan olindi va logga niqoblangan holda (sk-ant-...Ab12) yozildi; oddiy regex skaner eski.py ichidagi yozib qo'yilgan kalitni topdi. Bog'liq bo'limlar: 2.6, 2.7, 2.8, 2.9.


5. To'g'ri va noto'g'ri tushunishlar

Noto'g'ri fikr To'g'risi
"Model suhbatni eslaydi" API holatsiz: har navbatda butun tarix yuboriladi (1-misol: 374 → 2794 token)
"Tarixni qisqartirish — eng yaxshi tejash" Oyna faktni yo'qotdi; prefiks kesh to'liq tarixni $143.01 dan $17.38 ga tushirdi (faraziy narx)
"Har qanday xatoga qayta urinish kerak" 400/401/404 — kodingizdagi xato; qayta yuborish befoyda
"Xato bo'lsa darhol qayta yuborish tezroq" 2-misol: 767 ta 429, muvaffaqiyat 28.7%
"Jitter har doim 429 ni kamaytiradi" 2-misolda ko'paytirdi (+24.6, SE 6.0); foydasi yuklamaga bog'liq
"429 — qayta urinish bilan hal bo'ladi" Eng yaxshisi — oldini olish: cheklovchi bilan 0 ta 429
"Timeout — so'rov bajarilmadi" Bajarilgan bo'lishi mumkin: 35 ta ortiqcha bajarilish
"Kesh kaliti uchun savol matni yetarli" 1283 ta boshqa do'kon javobi; kalit — butun so'rov
"Streaming javobni tezlashtiradi" Sezilgan kechikishni: birinchi so'z 0.83 s da, oxirgisi baribir 4.43 s
"Batch natijalari yuborilgan tartibda" Ixtiyoriy tartib; custom_id bo'yicha bog'lang
"Kalitni private repoga qo'ysa bo'ladi" Kalit — muhitda; oshkor bo'lsa — rotate

6. Keng tarqalgan xatolar va yechimlari

1. stop_reason tekshirilmagan

python
matn = javob.content[0].text                                        # ⚠️
if javob.stop_reason == "max_tokens":                               # ✅
    raise RuntimeError("javob qirqilgan")
matn = "".join(b.text for b in javob.content if b.type == "text")

2. system messages ichida

python
messages = [{"role": "system", "content": "..."}, {"role": "user", "content": q}]   # ⚠️
client.messages.create(model=m, max_tokens=512, system="...",
                       messages=[{"role": "user", "content": q}])   # ✅

3. Hamma xatoga bir xil qayta urinish

python
except Exception:
    continue                                                        # ⚠️
except (anthropic.RateLimitError, anthropic.InternalServerError,
        anthropic.APITimeoutError, anthropic.APIConnectionError):
    time_kut(kutish("full jitter", urinish, None, rng))             # ✅ 400 - tuzatiladi

4. Backoff siz, chegarasiz

python
while True:
    try: return yubor(s)
    except RateLimitError: pass                                     # ⚠️
for urinish in range(1, 7):
    try: return yubor(s)
    except RateLimitError: kut(min(60, 0.5 * 2 ** (urinish - 1)))   # ✅

5. Kesh kaliti to'liq emas

python
kalit = savol                                                       # ⚠️
kalit = hashlib.sha256(json.dumps(sorov, sort_keys=True).encode()).hexdigest()   # ✅

6. Yon ta'sirli amalni kalitsiz qayta yuborish

python
for _ in range(3): buyurtma_yarat(d)                                # ⚠️
ikalit = f"buyurtma-{d['id']}"                                      # ✅ bir marta
for _ in range(3): buyurtma_yarat(d, idempotency_key=ikalit)

7. Kalit kodda

python
client = anthropic.Anthropic(api_key="sk-ant-...")                  # ⚠️
client = anthropic.Anthropic()          # ANTHROPIC_API_KEY muhitdan  # ✅

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 25.2-dars (o'tilgan): tokenlar, kontekst oynasi va xarajat — bu yerda usage bilan haqiqiy hisob
  • 25.5-dars (o'tilgan): strukturali chiqish — qayta so'rash sikli aynan shu API xatolari siyosati ichida ishlaydi
  • 18-qism (o'tilgan): juftlashgan taqqoslash va SE — 2-misoldagi strategiyalar
  • Keyingi darslar: Embeddinglar — embedding API ham xuddi shu retry/batch/kesh qoidalari bilan; RAG — kontekstni prompt ga sig'dirish va prefiks kesh; LLM ni baholash — batch bilan katta baholash to'plamlari; Agentlar va tool calling — ko'p chaqiruvli sikllarda byudjet va idempotentlik; LLM xavfsizligi — kalitlar, proksi server; MLOps va deploy qismida — monitoring, loglar, xarajat panellari

8. Eng yaxshi amaliyotlar

  1. Har so'rovda stop_reason va usage ni o'qing va response.id bilan birga loglang.

  2. Xatolarni turi bo'yicha ajrating: 400-larni tuzating, 429/5xx/timeout ga qayta urining.

  3. Yuklamani mijoz tomonida cheklang; qayta urinish — eksponensial, jitter bilan, urinishlar va kutish chegaralangan.

  4. System prompt va eski tarixni barqaror saqlang — prefiks kesh ishlasin; unga sana va tasodifiy ID qo'ymang.

  5. Javob keshi kalitini butun so'rovning kanonik xeshidan quring.

  6. Yon ta'sirli har amalga idempotentlik kaliti bering.

  7. Foydalanuvchi kutmaydigan ishni batch ga yuboring; natijani custom_id bo'yicha bog'lang.

  8. Xarajatni vazifa va model bo'yicha yig'ing; byudjetni so'rovdan oldin tekshiring.

  9. Kalitni muhitda saqlang, commit dan oldin skanerlang, oshkor bo'lsa — darhol almashtiring.


9. Amaliy topshiriq

Vazifa 1: Bashorat qiling

python
1.  # 10-navbatda model 2-navbatdagi faktni qayerdan "biladi"?
2.  # system prompt ichiga joriy vaqt qo'yilsa, prefiks keshga nima bo'ladi?
3.  # BadRequestError (400) ga qayta urinish kerakmi?
4.  # 529 nimani anglatadi va qayta urinish kerakmi?
5.  # asos 0.5 s, shift 60 s: 8-urinishdan oldingi eksponensial kutish?
6.  # full jitter da 3-urinishdan oldingi kutish oralig'i?
7.  # timeout bo'ldi - buyurtma yaratildimi?
8.  # json.dumps({"b": 1, "a": 2}) == json.dumps({"a": 2, "b": 1}) ?
9.  # kesh kalitida system bo'lmasa, ikki do'kon bitta keshni ishlatsa?
10. # stream da message_stop kelmadi - javobni saqlaymizmi?
11. # batch natijalari qaysi tartibda keladi?
12. # kalit git ga tushdi va faylni o'chirdik - yetarlimi?
Javoblar
  1. Faqat biz yuborgan tarixdan — API holatsiz; tarix yuborilmasa, fakt yo'q
  2. Har so'rovda prefiks o'zgaradi — kesh hech qachon ishlamaydi
  3. Yo'q — so'rovni tuzatish kerak
  4. Server band (overloaded) — vaqtinchalik, backoff bilan qayta urinish
  5. min(60, 0.5 * 2^6) = 32 s
  6. uniform(0, 0.5 * 2^2) = uniform(0, 2) s
  7. Noma'lum — bajarilgan bo'lishi mumkin; idempotentlik kaliti bilan qayta yuboring
  8. False — sort_keys=True kerak
  9. Bir do'kon mijoziga boshqa do'kon javobi (3-misol: 1283 ta)
  10. Yo'q — to'liq emas; qayta so'rang yoki "qisman" deb belgilang
  11. Ixtiyoriy — custom_id bo'yicha bog'lanadi
  12. Yo'q — git tarixida qoladi; kalitni bekor qilib yangisini yarating

Vazifa 2: Xatolarni tuzating

python
1.  client = anthropic.Anthropic(api_key="sk-ant-api03-...")
    print("kalit:", client.api_key)

2.  for urinish in range(100):
        try:
            return client.messages.create(**sorov)
        except Exception:
            pass

3.  kesh_kaliti = sorov["messages"][-1]["content"]

4.  for i, natija in enumerate(client.messages.batches.results(paket.id)):
        saqla(hujjatlar[i], natija)

5.  def tolov_yech(summa):
        for _ in range(3):
            try:
                return tolov_api.yech(summa)
            except TimeoutError:
                continue
Javoblar
python
1.  client = anthropic.Anthropic()                       # ANTHROPIC_API_KEY muhitdan
    print("kalit:", niqobla(os.environ["ANTHROPIC_API_KEY"]))

2.  rng = random.Random(sorov_id)
    for urinish in range(1, 7):                          # chegarali
        try:
            return client.messages.create(**sorov)
        except anthropic.BadRequestError:
            raise                                        # tuzatiladi, qayta emas
        except (anthropic.RateLimitError, anthropic.InternalServerError,
                anthropic.APITimeoutError, anthropic.APIConnectionError):
            kut(rng.uniform(0, min(60, 0.5 * 2 ** (urinish - 1))))
    raise RuntimeError("urinishlar tugadi")

3.  kesh_kaliti = hashlib.sha256(
        json.dumps(sorov, sort_keys=True, ensure_ascii=False).encode()).hexdigest()

4.  for natija in client.messages.batches.results(paket.id):
        saqla(hujjat_idsi[natija.custom_id], natija)     # custom_id bo'yicha

5.  def tolov_yech(summa, amal_id):
        ikalit = f"tolov-{amal_id}"                      # amal uchun bir marta
        for urinish in range(1, 4):
            try:
                return tolov_api.yech(summa, idempotency_key=ikalit)
            except TimeoutError:
                kut(min(10, 0.5 * 2 ** (urinish - 1)))

Vazifa 3: Suhbat tarixi

Modellang:

  1. Xabarlar tekshiruvchisi (rollar, birinchi xabar, bo'sh matn)
  2. To'liq / oyna / xulosa + oyna strategiyalari
  3. Navbat bo'yicha kirish tokenlari va jami
  4. Prefiks kesh bilan faraziy narx; eski faktni "eslash" testi

Vazifa 4: Qayta urinish simulyatsiyasi

Modellang:

  1. Token-bucket server va xatolar turlari (virtual vaqt)
  2. Strategiyalar: yo'q, darhol, eksponensial, jitter, retry-after, cheklovchi
  3. Muvaffaqiyat, urinishlar, 429 lar, tugash vaqti — 5 urug'
  4. Juftlashgan farqlar va SE; mijozlar sonini 5 va 50 ga o'zgartirib jitter ta'sirini qayta o'lchang

Vazifa 5: Kesh va idempotentlik

Modellang:

  1. Kanonik kalit va normallashtirish
  2. Zipf yuklama, ikki system
  3. Hit ulushi va noto'g'ri javoblar, LRU sig'imi bo'yicha egri chiziq (50, 100, 200, 400)
  4. Idempotentlik kaliti bilan va kalitsiz

Vazifa 6: Xarajat va batch

Modellang:

  1. usage dan narx (faraziy jadval), vazifa kesimida
  2. Byudjet qo'riqchisi
  3. Streaming hodisalarini yig'ish va uzilishni aniqlash
  4. Batch natijalarini custom_id bo'yicha bog'lash

Vazifa 7: O'ylash

Hamkasbingiz aytdi: "SDK o'zi 2 marta qayta urinadi, demak xatolar haqida o'ylash shart emas. Bundan tashqari, jitter — ortiqcha murakkablik: 2-misolda u 429 larni ko'paytirdi. Oddiy eksponensial backoff qoldiramiz va tamom."

Javob

Qisqa javob: SDK ning o'rnatilgan qayta urinishlari — yaxshi boshlang'ich himoya, lekin katta yuklama uchun siyosat emas. Jitter haqidagi xulosa esa o'lchovdan kengroq.

1. SDK qayta urinishlari nimani qamraydi. Bitta so'rovning vaqtinchalik xatosini — ha. Lekin 20 ta jarayon bir vaqtda ishlasa, 2 ta qayta urinish tezlik chegarasini "yengib o'tmaydi": 2-misolda qayta urinishsiz 18.9%, backoff bilan ham 10 urinish kerak bo'ldi. Tungi skript uchun mijoz tomonidagi cheklovchi hal qiluvchi: 0 ta 429, eng qisqa tugash vaqti (30.0 s).

2. Xatolar turi baribir muhim. 400 va 401 ni SDK ham qayta yubormaydi — lekin sizning kodingiz ularni qanday qayta ishlaydi? Loglanadimi, navbatga qaytariladimi, ogohlantirish chiqadimi? Timeout dan keyin yon ta'sirli amal — idempotentlik kalitisiz ikki marta bajarilishi mumkin (3-misol: 35 ta).

3. Jitter haqida. 2-misolda full jitter 429 larni ko'paytirdi (+24.6, SE 6.0), chunki o'rtacha kutish qisqaroq; tugash vaqtidagi farq esa sezilarli emas. Bu — shu yuklama uchun natija. Jitterning maqsadi — sinxron qaytishning oldini olish; mijozlar ko'p va bir lahzada xato olganda (server qayta ishga tushishi) sinxron to'lqinlar paydo bo'ladi. Uni olib tashlashdan oldin o'sha stsenariyni ham o'lchang. Jitter bitta qator kod — murakkablik emas.

Tavsiya:

python
# 1. mijoz tomonida cheklovchi (so'rov/s va parallel oqimlar soni)
# 2. SDK max_retries + o'z siyosatingiz: 429/5xx/timeout, eksponensial + jitter, chegara
# 3. 400/401 - alohida qayta ishlash va ogohlantirish
# 4. yon ta'sirli amallar - idempotentlik kaliti
# 5. loglar: urinishlar soni, 429 ulushi, tugash vaqti - haftalik ko'rib chiqish

Hamkasbga javob: "SDK qayta urinishlari qolsin, lekin tungi yuklama uchun asosiy himoya — so'rovlarni tezlik chegarasidan sekinroq yuborish; 2-misolda bu 429 larni nolga tushirdi. Jitterni esa qoldiraylik — u arzon, va uning foydasi aynan biz o'lchamagan holatda (sinxron qayta ulanish) ko'rinadi."

Nimani mustahkamlaydi: 2.2, 2.3, 2.4, 2.5, 2.8-bo'limlar.


Xulosa

Bu darsda LLM API atrofidagi muhandislik kodini qurdik va stub yordamida lokal sinadik: xabarlar formati va suhbat tarixi, xatolar turlari va qayta urinish, kesh va idempotentlik, streaming, batch, usage dan xarajat va kalit xavfsizligi. Stub ehtimollari va narxlari faraziy, lekin kodning o'zi haqiqiy API bilan ham xuddi shunday ishlaydi.

Eng muhim uch fikr:

  1. API holatsiz: tarix, kesh va xarajat bir-biriga bog'liq. 1-misolda 30 navbatli suhbatda kirish 374 dan 2794 tokenga o'sdi, jami 47 671 token. Sirpanuvchi oyna narxni 2.1 barobar kamaytirdi, lekin eski faktni yo'qotdi; prefiks kesh esa to'liq tarixni saqlagan holda faraziy narxni $143.01 dan $17.38 ga tushirdi.

  2. Xatolar turi bo'yicha, yuklama esa mijoz tomonida boshqariladi. 2-misolda qayta urinishsiz 18.9%, "darhol" qayta urinish bilan 28.7% va 767 ta 429; eksponensial backoff 100%, lekin 51.7 s. Full jitter bu yuklamada 429 larni kamaytirmadi (+24.6, SE 6.0), tugash vaqtidagi farq sezilarli emas. Aniq g'olib — mijoz tomonidagi cheklovchi: 0 ta 429, 105 urinish, 30.0 s.

  3. Kesh kaliti to'liq bo'lsin, yon ta'sirli amal idempotent bo'lsin, xarajat usage dan hisoblansin. 3-misolda faqat savol matnidan qurilgan kalit 1283 ta boshqa do'kon javobini qaytardi; to'liq kanonik kalit + normallashtirish 85.5% hit va 0 xato berdi. Idempotentlik kalitisiz 300 amalda 35 ta ortiqcha bajarilish. 4-misolda xarajatning 86% i bitta vazifada edi — usage loglari optimallashtirishni qayerdan boshlashni ko'rsatadi.

Keyingi darsda embeddinglar va semantik qidiruv: matnni vektorga aylantirish, kosinus o'xshashligi va normallashtirish, semantik va kalit so'z qidiruvini halol taqqoslash, embedding o'lchami va narxi, hamda taxminiy yaqin qo'shni qidiruvi (IVF) ni noldan yozamiz.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
25.6-dars: LLM API bilan ishlash — IlmHamroh