Mundarija (35)
- Bu darsda
- 1. Nega bu kerak?
- 2. Provayderlar nimasi bilan farq qiladi
- 2.1 So'rov tanasi
- 2.2 Faqat tana emas
- 2.3 Kanon yo'li: provayder shartnomasi
- 3. Vercel AI SDK
- 3.1 Paketlar va model obyekti
- 3.2 Strukturali javob: generateText + Output.object()
- 3.3 Javob sxemaga mos kelmasa
- 3.4 Oqim: streamText + partialOutputStream
- 3.5 generateObject — eskirgan
- 4. Kanon o'lchovi: uch yo'l yonma-yon
- 5. Provayderni almashtirish
- 5.1 Bitta qator
- 5.2 Taxallus, ro'yxat va OpenAI-mos xizmatlar
- 6. Qayta urinish va zaxira model
- 6.1 AI SDK ning maxRetries
- 6.2 Zaxira model nima
- 6.3 Zaxirani modellash
- 6.4 AI SDK da zaxira bormi
- 7. Nega kanonda AI SDK yo'q
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): bo'shliqlarni to'ldiring
- 2-mashq (o'rta): status bo'yicha qaror
- 3-mashq (qiyin): zaxira zanjiri
- 10. Loyiha qadami: zaxira model
- 10.1 src/ai/zaxira.ts
- 10.2 src/ai/ai.module.ts
- 10.3 Test
- 10.4 Tekshirish
- 11. Real ishda
- Xulosa
- Manbalar
Vercel AI SDK va provayderlarni almashtirish: generateText, Output va zaxira model
Qisqacha: Har LLM provayderining API shakli boshqa: Gemini
contentskutadi, OpenAImessages, Anthropicsystemni alohida maydonda. Vercel AI SDK (aipaketi) bularni bitta interfeysga keltiradi:generateText({ model, output })— model qatorini almashtirsangiz, provayder almashadi. AI SDK 7 da strukturali javobgenerateText+Output.object()bilan olinadi;generateObjectendi eskirgan. Kanon o'lchovida uch yo'l (xomfetch,@google/genai, AI SDK) tezlikda farq qilmadi. Shuning uchun loyiha 0 paketli o'z yupqa qatlamida qoldi, unga esa zaxira model qo'shildi.
Bu darsda
- Uch provayder so'rovi qanday farq qilishini ko'rasiz va nega umumiy qatlam kerakligini tushuntirasiz.
- AI SDK 7 da
generateText+Output.object()vastreamTextbilan strukturali javob olasiz. generateObjectnega eskirgani va eski kodda uni qanday tanib olishni bilasiz.maxRetries(bir modelga qayta urinish) va zaxira model (boshqa modelga o'tish) farqini ajratasiz.vazifalar-apigaZaxiraliProvayderqo'shasiz.
Oldin bilishingiz kerak: LLM API asoslari, LLM javoblarini streaming qilish, Strukturali javob va tool calling, Zod 4 serverda.
O'tgan darsda vazifani qadamlarga bo'lish JSON Schema va Zod bilan ishladi. Kod faqat Gemini'ni biladi. Bu darsda savol: ertaga boshqa provayder kerak bo'lsa, qancha kod o'zgaradi?
1. Nega bu kerak?
Tasavvur qiling: dushanba ertalab Gemini 429 qaytara boshladi — limit tugagan. Foydalanuvchilar "Qadamlarga bo'l" tugmasini bosib, xato ko'radi. Ikki savol tug'iladi. Shu so'rovni boshqa model bilan bajarib bo'lmaydimi? Narx oshsa yoki yaxshiroq provayder chiqsa, qancha kodni qayta yozamiz?
O'xshatish: telefoningizda ikki SIM-karta bor. Bitta operator tarmog'i yo'qolsa, telefon ikkinchisiga o'tadi, siz esa raqam terish usulini o'zgartirmaysiz. Ilova ham "qadamlarga bo'l" deydi, qaysi "operator" bajarishini bitta qatlam hal qiladi.
Darsning ikki javobi:
- Provayderni almashtirish — bitta interfeys orqasida bir nechta provayder. Vercel AI SDK buni tayyor beradi, kanon esa o'z yupqa qatlamini yozgan.
- Zaxira model (fallback model) — asosiy model vaqtincha ishlamasa, so'rovni boshqa model bilan bir marta bajarish.
2. Provayderlar nimasi bilan farq qiladi
2.1 So'rov tanasi
Uchta mashhur API bir xil narsani — tizim ko'rsatmasi va foydalanuvchi xabarini — uch xil shaklda kutadi. Quyidagi funksiya faqat so'rov tanasini yasaydi, tarmoqqa chiqmaydi:
function sorovTanasi(provayder, tizim, matn) {
if (provayder === "gemini") {
return {
systemInstruction: { parts: [{ text: tizim }] },
contents: [{ role: "user", parts: [{ text: matn }] }],
};
}
if (provayder === "openai") {
return {
messages: [
{ role: "system", content: tizim },
{ role: "user", content: matn },
],
};
}
if (provayder === "anthropic") {
return {
system: tizim,
messages: [{ role: "user", content: matn }],
};
}
throw new Error(`noma'lum provayder: ${provayder}`);
}
const tizim = "Vazifani qadamlarga bo'l";
const matn = "<vazifa>Non olish</vazifa>";
for (const p of ["gemini", "openai", "anthropic"]) {
console.log(p, JSON.stringify(sorovTanasi(p, tizim, matn)));
}Konsolda:
gemini {"systemInstruction":{"parts":[{"text":"Vazifani qadamlarga bo'l"}]},"contents":[{"role":"user","parts":[{"text":"<vazifa>Non olish</vazifa>"}]}]}
openai {"messages":[{"role":"system","content":"Vazifani qadamlarga bo'l"},{"role":"user","content":"<vazifa>Non olish</vazifa>"}]}
anthropic {"system":"Vazifani qadamlarga bo'l","messages":[{"role":"user","content":"<vazifa>Non olish</vazifa>"}]}Uch qatorni solishtiring. Gemini'da tizim ko'rsatmasi — systemInstruction, xabar matni parts ichida. OpenAI'da tizim ham messages ning bir elementi, faqat role: "system" bilan. Anthropic'da tizim alohida system maydonida turadi.
2.2 Faqat tana emas
Farq so'rov tanasi bilan tugamaydi. Kanon kodidagi uch provayder faylidan (Gemini sinalgan, OpenAI-mos va Anthropic — sinalmagan, kalit yo'q) quyidagi farqlar ko'rinadi:
| Nima | Gemini | OpenAI-mos | Anthropic |
|---|---|---|---|
| Kalit sarlavhasi | x-goog-api-key |
Authorization: Bearer |
x-api-key |
| Strukturali javob | responseJsonSchema |
response_format |
majburiy vosita (kanon yo'li) |
| Embedding | bor | bor | yo'q |
Oxirgi qator muhim: Anthropic'da embedding API yo'q, shuning uchun kanonning Anthropic provayderi embedding() da xato beradi. Javob shakli, token hisobi va xato kodlari ham farqli: Anthropic yuklangan paytda 529 qaytaradi, Gemini esa 503. Ilova kodi bularni bilmasligi kerak.
2.3 Kanon yo'li: provayder shartnomasi
Kanon LLM API asoslari darsida LlmProvayder interfeysini yozgan: yarat, oqim, embedding. Ilova kodi faqat shuni biladi. Har provayder — shu interfeysni amalga oshiruvchi bitta fayl. Config esa qaysi birini yaratishni tanlaydi:
function asosiyProvayder(c: Config): LlmProvayder | null {
switch (c.AI_PROVAYDER) {
case "yoq":
return null;
case "gemini":
return new GeminiProvayder({ /* kalit, model, … */ });
case "openai":
return new OpenAiMosProvayder({ /* … AI_BASE_URL */ });
case "anthropic":
return new AnthropicProvayder({ /* … */ });
}
}Konstruktor argumentlarini /* … */ bilan qisqartirdik. OpenAiMosProvayder — "OpenAI-mos": OpenAI'ning Chat Completions shaklini tushunadigan har qanday xizmat, faqat manzil (AI_BASE_URL) boshqa.
Tekshirib ko'ring: Nega Anthropic provayderini tanlasangiz, semantik qidiruv ishlamaydi?
Javob
Semantik qidiruv matnni vektorga aylantiradigan embedding API'ga tayanadi. Anthropic bunday API bermaydi, shuning uchun kanonning AnthropicProvayder.embedding() xato tashlaydi. Yechim — matn uchun bir provayder, embedding uchun boshqasi. Interfeys buni taqiqlamaydi.
3. Vercel AI SDK
3.1 Paketlar va model obyekti
Vercel AI SDK — TypeScript uchun ochiq kutubxona. U provayderlar farqini o'zi yashiradi. Asosiy paket — ai. Har provayder uchun alohida paket bor: @ai-sdk/google, @ai-sdk/openai, @ai-sdk/anthropic va boshqalar. Kanon tajribasida versiyalar: ai 7.0.153 va @ai-sdk/google 4.0.105.
Ishlatish uch qadam: provayder yaratiladi, undan model olinadi, model funksiyaga beriladi. Kanon tajribasidagi kod:
import { generateText } from "ai";
import { createGoogleGenerativeAI } from "@ai-sdk/google";
const google = createGoogleGenerativeAI({
apiKey: process.env.GEMINI_API_KEY,
});
const { text, usage } = await generateText({
model: google("gemini-3.5-flash-lite"),
system: "Vazifani 2–8 ta qadamga bo'l.",
prompt: "<vazifa>Oilaviy byudjet jadvali</vazifa>",
temperature: 0.2,
abortSignal: AbortSignal.timeout(60_000),
});google("…") — model obyekti: qaysi provayder, qaysi model, qaysi kalit. generateText provayderni bilmaydi — modelning umumiy interfeysini chaqiradi. abortSignal — Bekor qilish: AbortController darsidan tanish.
Kalit faqat muhit o'zgaruvchisida va kod faqat serverda ishlaydi. Brauzerdan LLM chaqirilsa, kalit sahifa kodida oshkor bo'ladi. Shuning uchun saytdagi «Ishga tushir» tugmasi LLM chaqira olmaydi — atayin.
3.2 Strukturali javob: generateText + Output.object()
Strukturali javob darsida kanon JSON Schema'ni Zod sxemasidan yasab, Gemini'ga yuborgan va javobni yana Zod bilan tekshirgan edi. AI SDK 7 da shu ikki qadam bitta parametrda — output:
import { generateText, Output } from "ai";
import { z } from "zod";
const Qadamlar = z.strictObject({
qadamlar: z.array(z.strictObject({
matn: z.string().min(1).max(100),
daqiqa: z.int().min(1).max(480),
})).min(2).max(8),
});
const n = await generateText({
model, // google("…") yoki boshqa model
system: "Vazifani 2–8 ta qadamga bo'l.",
prompt: "<vazifa>Oilaviy byudjet jadvali</vazifa>",
output: Output.object({ schema: Qadamlar }),
});
console.log(n.output);
console.log(n.usage.inputTokens, n.usage.outputTokens);Buni tarmoqsiz ishga tushirdik. model o'rniga AI SDK ning o'z test vositasi — ai/test dagi MockLanguageModelV4 turdi. Soxta model (mock) — haqiqiy LLM o'rniga oldindan berilgan javobni qaytaradigan obyekt. Bizniki shu JSON'ni qaytardi:
const javob = JSON.stringify({ qadamlar: [
{ matn: "Oylik daromadni yozish", daqiqa: 10 },
{ matn: "Xarajat turlarini sanash", daqiqa: 20 },
] });Natija (biz ishga tushirdik: ai 7.0.153, Node 24.21.0, soxta model):
{
qadamlar: [
{ matn: 'Oylik daromadni yozish', daqiqa: 10 },
{ matn: 'Xarajat turlarini sanash', daqiqa: 20 }
]
}
92 41n.output — Zod'dan o'tgan, tayyor obyekt. 92 41 — soxta modelga biz yozgan token soni. Haqiqiy modelda bu yerda provayder sanagan tokenlar bo'ladi.
Modelga nima yuborilganini ham tekshirdik. AI SDK so'rovga responseFormat: { type: "json", schema } qo'ydi. Sxema Zod'dan avtomatik yasalgan — kanondagi z.toJSONSchema() qadami SDK ichida bajariladi.
3.3 Javob sxemaga mos kelmasa
Endi soxta model atigi bitta qadam qaytarsin. Sxemada esa .min(2) bor:
import { NoObjectGeneratedError } from "ai";
try {
await generateText({
model, // {"qadamlar":[{"matn":"Yolg'iz","daqiqa":5}]} qaytaradi
prompt: "x",
output: Output.object({ schema: Qadamlar }),
});
} catch (x) {
console.log(NoObjectGeneratedError.isInstance(x), x.name);
console.log(x.message);
console.log(x.text);
}true AI_NoObjectGeneratedError
No object generated: response did not match schema.
{"qadamlar":[{"matn":"Yolg'iz","daqiqa":5}]}AI_NoObjectGeneratedError — "obyekt yaratilmadi: javob sxemaga mos emas". Xom matn x.text da saqlanadi — logga yozish uchun. AI SDK bu holatda qayta so'ramaydi: soxta model bir marta chaqirilganini sanadik. Kanon esa bitta "tuzatish so'rovi" yuboradi; AI SDK bilan buni catch ichida o'zingiz yozasiz.
3.4 Oqim: streamText + partialOutputStream
Streaming darsida kanon qisman JSON'dan tayyor qadamlarni ajratgan edi. AI SDK da buning uchun streamText va partialOutputStream bor:
import { streamText, Output } from "ai";
const s = streamText({
model,
prompt: "x",
output: Output.object({ schema: Qadamlar }),
});
for await (const q of s.partialOutputStream) {
console.log(JSON.stringify(q));
}Soxta model JSON'ni to'rt bo'lakda yubordi: {"qadamlar":[{"matn":"Daromad, ni yozish","daqiqa":10},, {"matn":"Xarajatlarni sanash",, "daqiqa":20}]}. Natija (biz ishga tushirdik):
{"qadamlar":[{"matn":"Daromad"}]}
{"qadamlar":[{"matn":"Daromadni yozish","daqiqa":10}]}
{"qadamlar":[{"matn":"Daromadni yozish","daqiqa":10},{"matn":"Xarajatlarni sanash"}]}
{"qadamlar":[{"matn":"Daromadni yozish","daqiqa":10},{"matn":"Xarajatlarni sanash","daqiqa":20}]}Birinchi qatorda "Daromad" so'z yarmida uzilgan, daqiqa esa yo'q. Qisman obyekt (partial object) sxemadan o'tmaydi — bu "hozircha kelgan qism". Uni to'g'ridan ko'rsatsangiz, ekranda chala so'zlar chiqadi.
Kanonning toliqQadamlar() funksiyasi esa faqat yopilgan qadam obyektlarini beradi va har birini Zod bilan tekshiradi. AI SDK bilan ham shunday qilsa bo'ladi: yangi qisman holatda oldingisidan keyin kelgan va to'liq yopilgan qadamnigina chiqarish.
3.5 generateObject — eskirgan
Ko'p maqola va eski loyihalarda strukturali javob boshqacha yoziladi:
import { generateObject } from "ai";
const { object } = await generateObject({
model,
schema: Qadamlar,
prompt: "x",
});AI SDK 7 da bu funksiya hali ishlaydi, lekin eskirgan (deprecated). Kanon tajribasida haqiqiy Gemini bilan chaqirilganda Node shunday ogohlantirdi:
(node:24764) [AISDK_DEP_GENERATE_OBJECT] DeprecationWarning: AI SDK Warning: Deprecated: "generateObject". Use generateText with an output setting instead.
(node:24764) [AISDK_DEP_STREAM_OBJECT] DeprecationWarning: AI SDK Warning: Deprecated: "streamObject". Use streamText with an output setting instead.Tarjimasi: "generateObject eskirgan. O'rniga output sozlamali generateText ishlating". streamObject uchun — streamText + output. Soxta modelli sinovimizda ham aynan shu ogohlantirish chiqdi, natija esa to'g'ri bo'ldi (2 qadam). Yangi kodda — generateText/streamText + output; generateObject ni esa eski kodda uchratasiz.
Tekshirib ko'ring:
partialOutputStreamdan kelgan obyektni darhol bazaga saqlash mumkinmi?
Javob
Yo'q. Qisman obyekt sxemadan o'tmagan: matn yarmida uzilgan bo'lishi, majburiy maydon yetishmasligi mumkin (bizning sinovda birinchi holatda daqiqa yo'q edi). Saqlashdan oldin to'liq javobni kuting yoki har yopilgan elementni alohida tekshiring.
4. Kanon o'lchovi: uch yo'l yonma-yon
Kanon bitta vazifani ("Oilaviy byudjet jadvalini oy oxirigacha tuzib chiqish") bitta Zod sxemasi bilan uch yo'l orqali haqiqiy Gemini'ga yubordi. Har yo'l — ikki so'rov. Har qatorda: yo'l, vaqt, Zod natijasi, qadamlar soni va tokenlar (kirish→chiqish). Avval vaqt ustunini yuqoridan pastga solishtiring:
fetch #1 1517 ms Zod: OK qadamlar: 5 token: 92→126
fetch #2 1400 ms Zod: OK qadamlar: 7 token: 92→298
@google/genai #1 1260 ms Zod: OK qadamlar: 5 token: 93→226
@google/genai #2 1443 ms Zod: OK qadamlar: 6 token: 93→256
ai generateObject #1 1562 ms Zod: OK qadamlar: 6 token: 92→180
ai generateObject #2 1264 ms Zod: OK qadamlar: 5 token: 92→206
ai streamObject (qisman obyektlar) 1409 ms Zod: OK qadamlar: 6 token: 92→247Uch yo'l: xom fetch (kanon kodi bilan bir xil yo'l), Google'ning rasmiy @google/genai 2.31.0 SDK si va AI SDK. Kanon tajribasi 2026-11-03 da yozilgan — o'shanda AI SDK uchun generateObject ishlatilgan.
- xom fetch1517 va 1400 ms100 %
- @google/genai1260 va 1443 ms93 %
- AI SDK1562 va 1264 ms97 %
Manba: IlmHamroh kanon o'lchovi, 2026-11-03: gemini-3.5-flash-lite, har yo'l 2 so'rov, bitta mashina (Toshkent → Google)
Grafikda farq 3–7 %. Lekin bitta yo'lning ikki so'rovi orasidagi farq bundan katta: AI SDK 1562 va 1264 ms. Demak farq shovqin ichida — vaqtni tarmoq va model belgilaydi, SDK qatlami emas.
Javob ham har safar boshqa: 5 dan 7 gacha qadam, 126 dan 298 gacha chiqish tokeni. Bir xil so'rov va sozlama — har xil javob; o'quvchida ham boshqacha chiqadi. Noto'g'ri model nomi (gemini-yoq-model) esa AI_APICallError berdi: "models/gemini-yoq-model is not found".
Bog'liqlik hajmini kanon tajriba papkasida o'zimiz o'lchadik (du -sk, hujjat va manba fayllari bilan):
| Paket | Diskda |
|---|---|
ai 7.0.153 (shundan docs/ 3.2 MB) |
9.7 MB |
@ai-sdk/google, provider, provider-utils, gateway |
6.8 MB |
@google/genai 2.31.0 (+ protobufjs 3.1 MB va boshqalar) |
11.7 MB |
| kanonning o'z qatlami | 0 paket |
5. Provayderni almashtirish
5.1 Bitta qator
AI SDK ning asosiy va'dasi: provayder almashsa, faqat model: qatori o'zgaradi:
import { openai } from "@ai-sdk/openai";
import { anthropic } from "@ai-sdk/anthropic";
// model: google("gemini-3.5-flash-lite"),
// model: openai("<model nomi>"),
// model: anthropic("<model nomi>"),Bu qatorlar sinalmagan — kalitlar yo'q edi. Model nomlarini ataylab yozmadik: ular tez o'zgaradi, taxminiy nom esa xato beradi (tajribadagi gemini-yoq-model kabi).
5.2 Taxallus, ro'yxat va OpenAI-mos xizmatlar
Paket ichidagi hujjatga ko'ra (ai 7.0.153, sinalmagan) model nomlari kodda tarqalmasligi uchun ikki vosita bor:
customProvider({ languageModels: { tez: …, aqlli: … } })— o'z taxalluslaringiz; qaysi model ekanini bitta faylda o'zgartirasiz.createProviderRegistry({ google, openai })— model satr bilan tanlanadi:"google:gemini-3.5-flash-lite". Satrni config'dan o'qish mumkin.
Ko'p xizmatlar OpenAI'ning API shaklini takrorlaydi — ular uchun "OpenAI Compatible" provayderi bor. OpenRouter — yuzlab modelga bitta kalit bilan kirish beradigan vositachi. Groq — ochiq modellarni juda tez ishga tushiradigan provayder. Ollama — modelni o'z kompyuteringizda ishga tushirish: ma'lumot tashqariga chiqmaydi, lekin sifat kompyuterga bog'liq.
Kanonda bu vazifani muhit o'zgaruvchilari bajaradi: AI_PROVAYDER, AI_MODEL va OpenAI-mos xizmatlar uchun AI_BASE_URL (Ollama uchun http://127.0.0.1:11434/v1). OpenAI-mos yo'l ham sinalmagan.
6. Qayta urinish va zaxira model
6.1 AI SDK ning maxRetries
AI SDK o'zi ham qayta urinadi. Tekshirdik: soxta model har chaqiruvda 429 xatosini (APICallError, isRetryable: true) tashladi. Sukut sozlamalar bilan:
AI_RetryError | chaqiruvlar: 3 | ~ 6 s
Failed after 3 attempts. Last error: AI_APICallError: Resource exhaustedmaxRetries: 0 bilan esa:
AI_APICallError | chaqiruvlar: 1 | isRetryable: true | status: 429Sukut bo'yicha 1 ta chaqiruv + 2 ta qayta urinish = 3, orasida kutish bilan ~6 soniya (biz ishga tushirdik, ai 7.0.153). Keyin xato AI_RetryError ga o'raldi: "3 urinishdan keyin yiqildi". Qayta urinish o'sha modelga bo'ladi: limit 6 soniyada tugamasa, foydalanuvchi kutib, baribir xato ko'radi.
6.2 Zaxira model nima
Zaxira model — asosiy model vaqtinchalik ishlamay qolganda, o'sha so'rovni boshqa model bilan bajarish. Masalan, asosiy model kattaroq va aqlliroq, zaxirasi — kichikroq, boshqa limitli yoki boshqa avlod.
Qachon o'tish kerak? Faqat vaqtinchalik xatoda:
- 429 — limit tugadi;
- 503 yoki 529 — provayder band;
- timeout — javob vaqtida kelmadi;
- boshqa 5xx — provayder ichki xatosi.
Doimiy xatoda o'tish foydasiz: 400, 401, sxemaga mos bo'lmagan javob boshqa model bilan ham takrorlanadi. Kanonda bu farq LlmXatosi.vaqtinchalik da.
flowchart TD
S["So'rov"] --> A["Asosiy model"]
A -->|"javob"| OK["Foydalanuvchiga"]
A -->|"xato"| V{"Vaqtinchalikmi?"}
V -->|"yo'q: 400, sxema"| X["Xato qaytadi"]
V -->|"ha: 429, 503, timeout"| B{"Oqim boshlanganmi?"}
B -->|"ha"| X
B -->|"yo'q"| Z["Zaxira model"]
Z --> OKDiagrammani yuqoridan o'qing. O'tish uchun ikki shart bor: xato vaqtinchalik bo'lishi va oqimda hali birorta bo'lak ketmagan bo'lishi. Ikkinchi shart nega kerak — keyingi bo'limda.
6.3 Zaxirani modellash
Mantiqni brauzerda soxta provayder bilan ko'ramiz. Provayder model nomiga qarab xato tashlaydi yoki javob qaytaradi:
class LlmXatosi extends Error {
constructor(tur, status) {
super(`${tur} (${status})`);
this.tur = tur;
this.status = status;
}
get vaqtinchalik() {
return ["limit", "band", "timeout"].includes(this.tur)
|| (this.tur === "provayder" && this.status >= 500);
}
}
// soxta provayder: model nomiga qarab xato yoki javob qaytaradi
function soxtaProvayder(xatolar) {
const chaqiruvlar = [];
return {
chaqiruvlar,
async yarat(model) {
chaqiruvlar.push(model);
const x = xatolar[model];
if (x !== undefined) throw x;
return `javob (${model})`;
},
};
}
async function zaxiraBilan(p, asosiy, zaxira) {
try {
return await p.yarat(asosiy);
} catch (x) {
const otsinmi = x instanceof LlmXatosi && x.vaqtinchalik;
if (!otsinmi) throw x;
return p.yarat(zaxira);
}
}
const holatlar = [
["429 limit", new LlmXatosi("limit", 429)],
["503 band", new LlmXatosi("band", 503)],
["400 noto'g'ri so'rov", new LlmXatosi("provayder", 400)],
["sxemaga mos emas", new LlmXatosi("notogri", 200)],
];
for (const [nom, xato] of holatlar) {
const p = soxtaProvayder({ "katta-model": xato });
try {
const j = await zaxiraBilan(p, "katta-model", "kichik-model");
console.log(`${nom}: ${j} | chaqiruvlar: ${p.chaqiruvlar}`);
} catch (x) {
const c = p.chaqiruvlar;
console.log(`${nom}: XATO ${x.message} | chaqiruvlar: ${c}`);
}
}Konsolda:
429 limit: javob (kichik-model) | chaqiruvlar: katta-model,kichik-model
503 band: javob (kichik-model) | chaqiruvlar: katta-model,kichik-model
400 noto'g'ri so'rov: XATO provayder (400) | chaqiruvlar: katta-model
sxemaga mos emas: XATO notogri (200) | chaqiruvlar: katta-modelBirinchi ikki holatda javobni kichik model berdi. Oxirgi ikkitasida xato darhol qaytdi va zaxira model chaqirilmadi — yaroqsiz so'rov uchun ikki marta to'lash ma'nosiz. Model qayerda soddalashgan: haqiqiy kodda foydalanuvchi so'rovni bekor qilishi ham mumkin, bunday so'rov zaxiraga yuborilmaydi.
6.4 AI SDK da zaxira bormi
maxRetries modelni almashtirmaydi. Paket hujjatida modellar orasida o'tish (order) Vercel'ning pullik vositachi xizmati — AI Gateway sozlamasi sifatida tasvirlangan; biz uni sinamadik. O'z serveringizda zaxira — yuqoridagidek kichik o'ram (wrapper). Uni AI SDK ning wrapLanguageModel middleware'i sifatida ham yozish mumkin (sinalmagan). Middleware — modelni o'rab, har so'rov va javobga aralasha oladigan qatlam; Express'dagi middleware bilan bir g'oya.
customProvider dagi fallbackProvider sozlamasini zaxira model deb o'ylamang. Hujjatga ko'ra (2026-11), u xato bo'lganda emas, ro'yxatda yo'q model yoki imkoniyat so'ralganda ishlaydi: "bu taxallus menda yo'q — boshqa provayderdan ol".
Tekshirib ko'ring:
maxRetries: 2va zaxira modelni birga ishlatsak, eng yomon holatda asosiy model necha marta chaqiriladi?
Javob
Uch marta: bitta asosiy chaqiruv va ikki qayta urinish. Keyin zaxira model ham uch martagacha chaqirilishi mumkin. Jami oltitagacha so'rov, orasida kutishlar bilan. Shuning uchun qatlamlarni o'ylab tanlang: qayta urinish bitta joyda bo'lsin. Kanon zaxirani bir martalik qiladi, qayta urinishni esa Resilience naqshlari darsida alohida qatlamga qo'shadi.
7. Nega kanonda AI SDK yo'q
Kanon AI SDK ni tajribada to'liq sinadi, lekin loyihaga qo'shmadi. Sabablar o'lchovga tayanadi:
| Mezon | O'z yupqa qatlami | AI SDK |
|---|---|---|
| Bog'liqlik | 0 paket | ~16 MB (4 paket) |
| Tezlik | farq shovqin ichida | farq shovqin ichida |
| Xato turlari | LlmXatosi: limit, band, timeout… |
APICallError, RetryError… |
| Yangi provayder | o'zimiz yozamiz | tayyor paket |
Kanon tanlovi uchun eng muhim sabablar:
- Xatolar aniq. Ilovaning HTTP javobi (503 +
Retry-After, 504, 502) xato turiga bog'liq. O'z qatlamida tur aniq nomlangan. - Kalit faqat sarlavhada. Gemini kalitni URL'da (
?key=) ham qabul qiladi. URL esa loglarga tushadi. O'z kodimizda bu nazoratda. - Oqim va bekor qilish nazoratda. Mijoz uzilsa, LLM so'rovi ham to'xtaydi.
Narxi ham bor: OpenAI-mos va Anthropic fayllarini kanon o'zi yozdi va ular sinalmagan, AI SDK da esa ular tayyor. Loyihangiz 3–4 provayder bilan ishlasa yoki tool calling'li agentlar kerak bo'lsa, AI SDK oqilona tanlov. Bu "to'g'ri" yoki "noto'g'ri" emas — o'lchov bilan asoslangan tanlov.
8. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
DeprecationWarning: … "generateObject" |
eski API | generateText + Output.object() |
AI_NoObjectGeneratedError |
javob sxemaga mos emas | x.text ni loglang, kerak bo'lsa tuzatish so'rovi |
| Ekranda chala so'zlar | partialOutputStream to'g'ridan ko'rsatildi |
faqat yopilgan elementlarni chiqaring |
| Foydalanuvchi 6 s kutib, baribir xato oldi | maxRetries limitga urilgan modelga urinmoqda |
vaqtinchalik xatoda zaxira model |
| Zaxira model ham xato berdi | doimiy xato (400, sxema) zaxiraga o'tkazilgan | faqat vaqtinchalik xatoda o'tish |
Yana bir tuzoq — embedding modelini zaxira bilan almashtirish: ikki model vektorlarini bir-biri bilan solishtirib bo'lmaydi (keyingi dars).
9. Mashqlar
Mashqlar kurs/mashqlar/31/16-ai-sdk/ da. 2- va 3-mashq yechimlari Node 24.21.0 da tekshirilgan (biz ishga tushirdik).
1-mashq (oson): bo'shliqlarni to'ldiring
- AI SDK 7 da strukturali javob uchun
generateTextga beriladigan parametr — - Eskirgan funksiya —
- Sxemaga mos bo'lmagan javobda chiqadigan xato nomi —
AI_ - Sukut bo'yicha AI SDK bitta so'rovni jami necha marta chaqiradi (429 da)? —
- Kanon o'lchovida uch yo'l orasidagi tezlik farqi qayerda? —
Yechim
- output —
Output.object({ schema }). - generateObject — o'rniga
generateText+output;streamObjecto'rnigastreamText+output. - NoObjectGeneratedError — xom matn
x.textda. - 3 — bitta chaqiruv va
maxRetriessukuti bo'yicha 2 ta qayta urinish. - shovqin ichida — bitta yo'lning ikki so'rovi farqi yo'llar farqidan katta edi.
2-mashq (o'rta): status bo'yicha qaror
qaror(status) funksiyasini yozing: HTTP holat kodi zaxira modelga o'tishga arzisa "zaxira", aks holda "to'xta" qaytarsin. Vaqtinchalik: 429 va hamma 5xx (503, 529 ham shu). Qolgani — doimiy. Ro'yxat: 429, 503, 529, 500, 400, 401, 404.
Yechim
function qaror(status) {
if (status === 429 || status >= 500) return "zaxira";
return "to'xta";
}
for (const s of [429, 503, 529, 500, 400, 401, 404]) {
console.log(s, qaror(s));
}Konsolda:
429 zaxira
503 zaxira
529 zaxira
500 zaxira
400 to'xta
401 to'xta
404 to'xta529 — Anthropic'ning "overloaded" (band) kodi, HTTP standartida yo'q, lekin 5xx oralig'ida. 401 ni zaxiraga o'tkazish ma'nosiz: kalit noto'g'ri bo'lsa, boshqa model ham rad etadi (agar bir xil provayder va kalit bo'lsa).
3-mashq (qiyin): zaxira zanjiri
Bitta zaxira yetmasa, modellar ro'yxati beriladi: ["a", "b", "c"]. zanjir(yarat, modellar) ni yozing: modellarni tartib bilan sinaydi; javob kelsa — { javob, urinishlar }; doimiy xatoda (vaqtinchalik: false) darhol to'xtaydi; hammasi yiqilsa — { xato, urinishlar }. urinishlar — chaqirilgan modellar ro'yxati. Ishora: for…of ichida try/catch (try/catch), xato obyektiga Object.assign bilan maydon qo'shish.
Yechim
async function zanjir(yarat, modellar) {
const urinishlar = [];
let oxirgi;
for (const m of modellar) {
urinishlar.push(m);
try {
return { javob: await yarat(m), urinishlar };
} catch (x) {
oxirgi = x;
if (!x.vaqtinchalik) break;
}
}
return { xato: oxirgi.message, urinishlar };
}
const xato = (message, vaqtinchalik) =>
Object.assign(new Error(message), { vaqtinchalik });
const holat1 = { a: xato("429", true), b: xato("503", true) };
const holat2 = { a: xato("429", true), b: xato("400", false) };
const soxta = (holat) => async (m) => {
if (holat[m] !== undefined) throw holat[m];
return `${m} javobi`;
};
console.log(await zanjir(soxta(holat1), ["a", "b", "c"]));
console.log(await zanjir(soxta(holat2), ["a", "b", "c"]));
console.log(await zanjir(soxta({}), ["a", "b", "c"]));Konsolda:
{ javob: 'c javobi', urinishlar: [ 'a', 'b', 'c' ] }
{ xato: '400', urinishlar: [ 'a', 'b' ] }
{ javob: 'a javobi', urinishlar: [ 'a' ] }Ikkinchi qatorga qarang: b doimiy xato berdi va c umuman chaqirilmadi. Uzun zanjirning narxi — eng yomon holatda foydalanuvchi har model timeout'ini kutadi. Shuning uchun kanonda zaxira bitta.
10. Loyiha qadami: zaxira model
Bu darsda vazifalar-api ga zaxira model qo'shiladi. AI SDK loyihaga qo'shilmaydi — yuqoridagi qaror bo'yicha. Bu qadam keyingi ikki dars (semantik qidiruv va RAG) bilan bitta bosqich. Shuning uchun hali commit qilmang — commit RAG asoslari darsida.
10.1 src/ai/zaxira.ts
Yangi fayl. Kanon qatorlari telefon uchun bo'lingan (ma'no aynan). Formatlangan nusxani loyiha nusxasida tsc --noEmit va eslint bilan tekshirdik — 0 xato.
// zaxira.ts — zaxira model. Asosiy model vaqtinchalik xato bersa
// (429 limit, 503/529 band, timeout, 5xx) — xuddi shu so'rov bir
// marta ZAXIRA model bilan. Doimiy xatolar (400, sxema, rad) —
// zaxiraga o'tkazilmaydi: ular boshqa model bilan ham takrorlanadi.
// Dekorator: LlmProvayder'ni o'raydi — ilova kodi o'zgarmaydi.
// Oqimda — faqat birinchi bo'lakdan OLDINGI xatoda (yarim javob
// ikki marta ketmasin).
import { LlmXatosi } from "./llm.ts";
import type {
EmbeddingTuri, LlmBolak, LlmJavob, LlmProvayder, LlmSorov,
} from "./llm.ts";
export class ZaxiraliProvayder implements LlmProvayder {
readonly nom: string;
readonly model: string;
/** zaxiraga necha marta o'tildi (log/metrika uchun) */
otishlar = 0;
constructor(
private readonly asosiy: LlmProvayder,
private readonly zaxiraModel: string,
) {
this.nom = asosiy.nom;
this.model = asosiy.model;
}
#otsinmi(x: unknown, s: LlmSorov): boolean {
return x instanceof LlmXatosi && x.vaqtinchalik
&& s.signal?.aborted !== true
&& (s.model ?? this.model) !== this.zaxiraModel;
}
async yarat(s: LlmSorov): Promise<LlmJavob> {
try {
return await this.asosiy.yarat(s);
} catch (x) {
if (!this.#otsinmi(x, s)) throw x;
this.otishlar++;
return this.asosiy.yarat({ ...s, model: this.zaxiraModel });
}
}
async *oqim(s: LlmSorov): AsyncIterable<LlmBolak> {
let berildi = false;
try {
for await (const b of this.asosiy.oqim(s)) {
berildi = true;
yield b;
}
} catch (x) {
if (berildi || !this.#otsinmi(x, s)) throw x;
this.otishlar++;
yield* this.asosiy.oqim({ ...s, model: this.zaxiraModel });
}
}
embedding(
matnlar: string[], tur: EmbeddingTuri, signal?: AbortSignal,
): Promise<number[][]> {
// embedding modeli o'zgarsa — vektorlar mos kelmaydi; zaxira YO'Q
return this.asosiy.embedding(matnlar, tur, signal);
}
}Fayldagi to'rt qaror:
- Dekorator naqshi.
ZaxiraliProvaydero'zi hamLlmProvayder. U boshqa provayderni o'raydi. Ilova farqni sezmaydi — xuddi NestJS dagi interceptor kabi, faqat oddiy klass. #otsinmiuch shart. Xato vaqtinchalik; foydalanuvchi so'rovni bekor qilmagan (signal.aborted); so'rov allaqachon zaxira modelda emas — aks holda zaxira o'zini qayta chaqirardi.vaqtinchalik—LlmXatosining hisoblanadigan xususiyati (getter).- Oqimda
berildi. Birinchi bo'lak mijozga ketgan bo'lsa, zaxiraga o'tish ikki xil javobni yopishtirib yuboradi. Shuning uchun bunday paytda xato shundayligicha qaytadi. - Embedding'da zaxira yo'q. Yuqoridagi tuzoq.
Zaxira — o'sha provayderning boshqa modeli ({ ...s, model: this.zaxiraModel }). Boshqa provayderga o'tish uchun ikkita provayder obyekti kerak bo'lardi — kanon buni qilmadi.
10.2 src/ai/ai.module.ts
provayderYarat endi asosiy provayderni yasaydi va AI_ZAXIRA_MODEL berilgan bo'lsa, uni o'raydi. Avvalgi switch nomi asosiyProvayder bo'ldi:
import { ZaxiraliProvayder } from "./zaxira.ts";
export function provayderYarat(c: Config): LlmProvayder | null {
const p = asosiyProvayder(c);
return p === null || c.AI_ZAXIRA_MODEL === undefined
? p
: new ZaxiraliProvayder(p, c.AI_ZAXIRA_MODEL);
}AI_ZAXIRA_MODEL config sxemasida va .env.example da avvaldan bor — u yerda faqat izoh: "429/5xx/timeout'da bitta qayta urinish shu model bilan". Bo'sh qoldirsangiz, zaxira o'chiq.
10.3 Test
test/ai-qidiruv.test.ts — yangi fayl. Uning birinchi to'plami zaxira modelni bazasiz tekshiradi (qolgan testlarini keyingi ikki darsda qo'shamiz). Kanon testi, qatorlari bo'lingan:
describe("#16 zaxira model", () => {
test("vaqtinchalik xato (429) — bir marta zaxira model bilan; "
+ "doimiy xato (sxema) — o'tmaydi; oqim — faqat birinchi "
+ "bo'lakdan oldin", async () => {
const p = new SoxtaProvayder({
javob: () => "{}",
xatolar: [new LlmXatosi("limit", "429", undefined, 429)],
});
const z = new ZaxiraliProvayder(p, "zaxira-model");
const s: LlmSorov = {
tizim: "t", xabarlar: [{ rol: "user", matn: "x" }],
};
assert.equal((await z.yarat(s)).model, "zaxira-model");
assert.deepEqual(p.chaqiruvlar.map((c) => c.model),
[undefined, "zaxira-model"]);
assert.equal(z.otishlar, 1);
p.s.xatolar = [new LlmXatosi("notogri", "sxema")];
await assert.rejects(z.yarat(s), /sxema/);
assert.equal(z.otishlar, 1);
p.s.xatolar = [new LlmXatosi("band", "503", undefined, 503)];
const bolaklar: string[] = [];
for await (const b of z.oqim(s)) {
if (b.tur === "tugadi") bolaklar.push(b.javob.model);
}
assert.deepEqual(bolaklar, ["zaxira-model"]);
});
});SoxtaProvayder — tarmoqsiz test provayderi, xatolar ro'yxatidagi xatoni navbat bilan tashlaydi. Uch holat: 429 — zaxiraga o'tdi; sxema xatosi — o'tmadi (otishlar hamon 1); oqimda 503 birinchi bo'lakdan oldin — zaxira javob berdi.
Biz loyiha nusxasida shu faylni yurgizdik (Node 24.21.0, Vitest 5.0.5, bazasiz):
✓ |asosiy| test/ai-qidiruv.test.ts (2 tests | 1 skipped) 11ms
Test Files 1 passed (1)
Tests 1 passed | 1 skipped (2)1 skipped — bazaga muhtoj testlar bloki (pgvector kerak). Ularni keyingi ikki darsda yozamiz. Kanon o'zi bazali muhitda hammasini yurgizdi.
10.4 Tekshirish
npm run tip
npm run lint
npx vitest run test/ai-qidiruv.test.tsTip va lint 0 xato bilan o'tsa — qadam tayyor. Commit — ikki darsdan keyin.
11. Real ishda
- Qayerda uchraydi: Next.js va Node loyihalarida AI SDK keng tarqalgan; chat interfeyslari (AI chat interfeyslari) uning UI qismi bilan quriladi. Katta kompaniyalarda esa ko'pincha o'z "LLM gateway" qatlami bo'ladi — kanondagi
LlmProvayderga o'xshash. - Zaxira model: prodakshndagi AI xususiyatlarida deyarli majburiy — limit va uzilishlar tez-tez bo'ladi.
- Intervyu savollari: "Provayderni almashtirish qanchalik oson?", "Qaysi xatolarda qayta urinasiz?", "Strukturali javobni qanday tekshirasiz?"
- Kod review'da: "
generateObject— eskirgan", "400 da ham zaxiraga o'tilyapti", "kalit URL'da — loglarga tushadi".
Xulosa
- Provayderlar API shakli, kalit sarlavhasi, strukturali javob usuli va xato kodlarida farq qiladi — ilova bularni bilmasligi kerak.
- AI SDK 7:
generateText/streamText+Output.object({ schema });generateObject/streamObject— eskirgan. - Qisman obyekt (
partialOutputStream) sxemadan o'tmagan — chala so'zlar bo'lishi mumkin. maxRetrieso'sha modelga qayta urinadi; zaxira model — boshqa modelga o'tish, faqat vaqtinchalik xatoda va oqim boshlanmasdan.- Kanon o'lchovi: xom
fetch,@google/genaiva AI SDK tezlikda farq qilmadi; loyiha 0 paketli o'z qatlamida qoldi vaZaxiraliProvayderoldi.
Keyingi dars: Embedding va semantik qidiruv — matnni vektorga aylantirib, so'z emas, ma'no bo'yicha qidiramiz: "doktor" so'rovi "Shifokor qabuli" vazifasini topadi.
Manbalar
- AI SDK hujjati — Generating Structured Data (
generateText+Output.object,partialOutputStream,NoObjectGeneratedError): https://ai-sdk.dev/docs/ai-sdk-core/generating-structured-data (2026-11-03 holati) - AI SDK hujjati — Provider & Model Management (
customProvider,createProviderRegistry,providerId:modelId): https://ai-sdk.dev/docs/ai-sdk-core/provider-management (2026-11-03 holati) - AI SDK ma'lumotnomasi —
customProvider,fallbackProvider("model yoki imkoniyat topilmasa"): https://ai-sdk.dev/docs/reference/ai-sdk-core/custom-provider (2026-11-03 holati) - AI SDK hujjati — Testing (
MockLanguageModelV4,ai/test): https://ai-sdk.dev/docs/ai-sdk-core/testing (2026-11-03 holati) - AI SDK hujjati — Settings (
maxRetries, sukut 2): https://ai-sdk.dev/docs/ai-sdk-core/settings (2026-11-03 holati) - AI SDK hujjati — OpenAI Compatible Providers: https://ai-sdk.dev/providers/openai-compatible-providers (kanonda sinalmagan, 2026-11)
- Google Gen AI SDK (
@google/genai): https://github.com/googleapis/js-genai (2026-11-03 holati) - Kanon o'lchovi (IlmHamroh, 2026-11-03): xom
fetch,@google/genai2.31.0,ai7.0.153 +@ai-sdk/google4.0.105, gemini-3.5-flash-lite, 7 so'rov.generateText+Output,streamText,maxRetries, paket hajmi,zaxira.tstip/lint va test — biz ishga tushirdik (2026-11-03, Node 24.21.0, soxta model, tarmoqsiz).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!