23 β Observability va evals¶
β¬ οΈ Oldingi: 22 β Xavfsizlik Β· π README Β· Keyingi: 24 β Batch API va optimizatsiya β‘οΈ
Bu bobda: ilovangiz haqiqatan ishlayaptimi va sizga necha turyaptimi β buni qanday bilasiz? Oddiy backend'da bu oson: test yozasiz,
assert(jami === 42), tamom. LLM bilan esa chiqish deterministik emas β bir xil prompt har safar biroz boshqa javob beradi. "Mening testimda bir marta ishladi" β bu "ishlab chiqarishda ishlaydi" degani emas. Shuning uchun ikkita ko'nikma kerak: observability (kuzatuvchanlik) β ichkarida nima sodir bo'layotganini ko'rish (har chaqiruvni loglash: kirish, chiqish, model, tokenβnarx, kechikish,stop_reason,_request_id, xatolar) β va evals (baholash) β sifatni tizimli o'lchash (eval to'plami + ball qo'yish). BizloggedCallo'ramini quramiz (PII tozalab, narx-kechikishni loglaydi), agent qadamlarini trace qilamiz, so'ng "promptlar uchun testlar"ni β strukturali tekshiruv + LLM-as-judge (alohida Claude chiqishni rubrika bo'yicha baholaydi) bilan mini eval harness quramiz va pass rate chiqaramiz. Bularsiz siz ko'r holda deploy qilasiz: debug qila olmaysiz, promptni o'zgartirsangiz sifat jimgina pasayadi, hisob esa kutilmaganda oshadi.Halollik eslatmasi: bu bobdagi
msg.usage(token),msg.stop_reason,msg._request_idmaydonlari vaclient.messages.parse()(Zodoutput_formatbilan) chaqiruvlari@anthropic-ai/sdk0.104 ga asoslangan va 06/14-boblardagi bilan bir xil. AI SDK'ningexperimental_telemetry(OpenTelemetry) βaiv6 da mavjud belgi. Kuzatuv platformalari (Langfuse, LangSmith, Helicone, OpenTelemetry) β variant sifatida eslatiladi, biri tavsiya etilmaydi; ularning aniq API'lari tez o'zgaradi, shuning uchun bu bobda kodni o'z loglaringiz atrofida quramiz (har qanday platformaga moslashadi). LLM-as-judge β kuchli, lekin mukammal emas vosita: sudya tarafkash bo'lishi mumkin, shuning uchun uni bittagina haqiqat sifatida emas, signal sifatida ishlating.
Nega bu bob muhim? β non-deterministik tizimni qanday boshqarasiz¶
Tasavvur qiling, siz mahsulot sharhlarini "ijobiy / salbiy" deb tasniflaydigan AI funksiyani yozdingiz. Uch-to'rt sharhda sinab ko'rdingiz β to'g'ri ishladi. Deploy qildingiz. Ikki haftadan keyin mijoz qo'ng'iroq qiladi: "Nega mening salbiy sharhim 'ijobiy' deb belgilandi?" Siz logni ochasiz... va u yo'q. Qaysi prompt ketganini, model nima javob berganini, qancha token sarflanganini β hech narsani bilmaysiz. Tuzatishni qayerdan boshlashni ham bilmaysiz.
Mana bu β odatiy backend bilan AI ilova orasidagi tub farq. Odatiy kodda 2 + 2 doim 4. LLM esa ehtimollik mashinasi (01-bobni eslang): har token oldingilarga qarab tasodifiy element bilan tanlanadi. Ya'ni bir xil prompt har safar biroz boshqacha javob berishi mumkin. Bu β kuchli xususiyat (ijodiy, moslashuvchan), lekin shuni anglatadi: siz aniq matnni assert qila olmaysiz, va "bir marta ishladi" hech narsani kafolatlamaydi.
Demak sizga ikkita yangi ko'nikma kerak:
- Observability (kuzatuvchanlik) β ilova ichida har chaqiruvda nima bo'layotganini ko'rish. Bu β "qora quti"ni shaffof qilish. Kelganda darrov javob bera olishingiz kerak: bu foydalanuvchiga qaysi prompt ketdi? Model nima qaytardi? Necha token, necha sent? Qancha kutdi? Xato bormi?
- Evals (evaluation, baholash) β chiqish sifatini tizimli o'lchash. Bir necha holatdan iborat eval to'plami tuzasiz, ustidan ilovani yurgizib, natijalarga ball qo'yasiz. Bu β "promptlar uchun testlar": promptni yoki modelni o'zgartirganingizda regressiyani (sifat pasayishini) ushlaydi.
Birinchisi β ko'rish, ikkinchisi β o'lchash. Ikkalasi ham bo'lmasa, siz ko'r holda uchasiz. Keling, ko'rishdan boshlaylik.
Observability: har chaqiruvni loglash¶
Eng asosiy odat juda oddiy: har LLM chaqiruvini loglang. Lekin "loglash" deganda console.log(javob) emas β sizga strukturali, qidiriladigan va jamlanadigan ma'lumot kerak. Har chaqiruv uchun quyidagilarni yozing:
- model va asosiy parametrlar (
max_tokens, harorat va h.k.) β qaysi sozlama bilan ishlaganini bilish uchun; usageβinput_tokens,output_tokensβ bulardan narx kelib chiqadi (14-bob);- kechikish (latency, ms) β chaqiruv qancha davom etdi;
stop_reasonβ javob normal tugadimi (end_turn) yokimax_tokensga urilib kesildimi (03-bob);_request_idβ Anthropic qaytaradigan noyob ID; biror so'rov g'alati ketsa, Anthropic qo'llab-quvvatlash xizmatiga shuni berasiz;- xato bo'lsa β xato turi va xabari ham (16-bob);
- kirish/chiqish β lekin PII tozalangan holda (pastda; 22-bobni eslang).
loggedCall o'rami¶
Buni har chaqiruvda qo'lda yozish charchatadi va unutiladi. Yechim β o'ram (wrapper): messages.create ni bir funksiya ichiga oling, u oldin-keyinini o'lchaydi va loglaydi. (14-bobdagi cost() yordamchisini eslang β narxni shu yerda hisoblaymiz.)
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic(); // ANTHROPIC_API_KEY .env dan (02-bob)
// 14-bobdan: narx jadvali va cost()
const NARX = {
"claude-opus-4-8": { in: 5, out: 25 },
"claude-sonnet-4-6": { in: 3, out: 15 },
"claude-haiku-4-5": { in: 1, out: 5 },
};
function cost(usage, model) {
const p = NARX[model];
return (usage.input_tokens / 1e6) * p.in + (usage.output_tokens / 1e6) * p.out;
}
// PII'ni log'dan tozalash (22-bob) β sodda misol
function redact(s) {
return String(s)
.replace(/[\w.+-]+@[\w-]+\.[\w.-]+/g, "[email]") // email
.replace(/\+?\d[\d\s-]{8,}\d/g, "[telefon]"); // telefon raqami
}
/**
* messages.create ni o'rab, har chaqiruvni strukturali loglaydi.
* meta β qo'shimcha kontekst (userId, feature) β jamlash uchun.
*/
async function loggedCall(params, meta = {}) {
const boshi = Date.now();
try {
const msg = await client.messages.create(params);
const kechikish = Date.now() - boshi;
log({
vaqt: new Date().toISOString(),
...meta, // userId, feature, ...
model: params.model,
input_tokens: msg.usage.input_tokens,
output_tokens: msg.usage.output_tokens,
narx: Number(cost(msg.usage, params.model).toFixed(6)),
kechikish_ms: kechikish,
stop_reason: msg.stop_reason, // end_turn | max_tokens | ...
request_id: msg._request_id, // Anthropic qo'llab-quvvatlash uchun
prompt: redact(params.messages.at(-1)?.content), // PII tozalangan
ok: true,
});
return msg;
} catch (err) {
log({
vaqt: new Date().toISOString(),
...meta,
model: params.model,
kechikish_ms: Date.now() - boshi,
ok: false,
xato_turi: err?.constructor?.name, // RateLimitError, APIError... (16-bob)
xato: err?.message,
request_id: err?.request_id, // xatoda ham request_id bo'ladi
});
throw err; // log qildik, lekin xatoni yashirmaymiz
}
}
// "log" β hozircha konsol; ishlab chiqarishda baza/monitoringga yoki platformaga
function log(qator) {
console.log(JSON.stringify(qator));
}
Ishlatish β oddiy messages.create o'rniga:
const msg = await loggedCall(
{
model: "claude-opus-4-8",
max_tokens: 512,
messages: [{ role: "user", content: "Ali (ali@co.uz) buyurtmasi qayerda?" }],
},
{ userId: "user-42", feature: "support-bot" }
);
// log qatori: { ..., narx: 0.0003, kechikish_ms: 820, stop_reason: "end_turn",
// request_id: "req_...", prompt: "Ali ([email]) buyurtmasi qayerda?", ok: true }
Endi har chaqiruv bitta strukturali JSON qatori bo'ladi. Bu nima beradi:
- Debug: mijoz shikoyat qilsa β o'sha
userId/request_idbo'yicha aniq prompt va javobni topasiz. - Narx nazorati:
narxniuserIdyokifeaturebo'yicha jamlab, "kim/nima eng ko'p sarflaydi?" ga javob berasiz (14-bob). - Anomaliyani payqash: kechikish yoki narx to'satdan oshsa, yoki
ok: falsefoizi ko'paysa β ogohlantirish ulaysiz.
Eslatma β loglashdan OLDIN PII'ni tozalang. Foydalanuvchi kiritmasida email, telefon, manzil bo'lishi mumkin. Logingiz β bu yana bir ma'lumot ombori; u ham buzg'unchi nishoni. 22-bobdagi qoidani eslang: log'ga xom PII yozmang. Yuqoridagi
redact()β minimal misol; haqiqiy ilovada o'z domeningizga mos to'liqroq tozalash kerak.
Narx spike'iga ogohlantirish¶
Loglar bor β endi ulardan anomaliyani ushlaymiz. Eng oddiy variant: har chaqiruvdan keyin narxni kuzatib, kutilmagan sakrashda signal bering.
let oynaNarx = 0; // masalan oxirgi daqiqadagi jami narx (soddalashtirilgan)
const SPIKE_CHEGARA = 1.0; // $1/daqiqa β o'z normangizga moslang
function narxniKuzat(narx) {
oynaNarx += narx;
if (oynaNarx > SPIKE_CHEGARA) {
alert(`β οΈ Narx spike: $${oynaNarx.toFixed(2)}/daqiqa β tekshiring!`);
// alert() = Slack/email/PagerDuty'ga xabar (bu yerda joy egasi)
}
}
Erta payqash arzon tuzatish: cheksiz tsikl, prompt injection hujumi (22-bob) yoki shunchaki noto'g'ri model tanlovi hisobingizni yeb qo'yishidan oldin to'xtatasiz.
Agent va ko'p bosqichli oqimni trace qilish¶
Bitta chaqiruvni loglash oddiy. Lekin agent (19-bob) yoki ko'p bosqichli oqim β bu bir nechta chaqiruv ketma-ketligi: model o'ylaydi β tool chaqiradi β natijani oladi β yana o'ylaydi... Agar faqat oxirgi javobni loglasangiz, agent qayerda adashganini tushunmaysiz. Sizga trace kerak β butun trayektoriyaning har qadami.
Trace β bu bir mantiqiy ish (masalan, bir foydalanuvchi so'rovi) bilan bog'liq barcha qadamlarning yozuvi: qaysi tool chaqirildi, qanday kirish bilan, qanday natija qaytdi, har qadamda necha token. Buni bitta traceId bilan bog'lab loglang:
import { randomUUID } from "node:crypto";
async function tracedAgent(savol) {
const traceId = randomUUID(); // butun ish uchun bitta ID
let qadam = 0;
// ... agent loop (19-bobdagidek). Har bosqichda loggedCall ishlatamiz:
const javob = await loggedCall(
{ model: "claude-opus-4-8", max_tokens: 1024, messages: [{ role: "user", content: savol }] },
{ traceId, qadam: qadam++, turi: "reasoning" }
);
// model tool chaqirsa β tool natijasini ham loglang:
log({ traceId, qadam: qadam++, turi: "tool", tool: "qidiruv", kirish: "...", natija: "..." });
return javob;
}
Endi traceId bo'yicha filtrlab, butun agent yo'lini β har qadami bilan β ko'rasiz. Agar agent noto'g'ri tool chaqirgan yoki noto'g'ri argument bergan bo'lsa, aynan qaysi qadamda ekanini topasiz.
Platformalar va AI SDK telemetriyasi¶
Hammasini qo'lda yozish shart emas β buning uchun maxsus kuzatuv platformalari bor: Langfuse, LangSmith, Helicone, va standart OpenTelemetry. Ular trace'larni chiroyli ko'rsatadi, narx/kechikishni jamlaydi, eval'larni ham boshqaradi. Bu yerda birortasini tavsiya qilmaymiz β tanlov ehtiyojingizga bog'liq, va ko'pincha o'z loglaringiz ham yetarli (yuqoridagi loggedCall har qanday platformaga osongina ulanadi: log() ichiga ularning SDK chaqiruvini qo'yasiz).
Agar Vercel AI SDK (11-bob) ishlatsangiz, unda tayyor OpenTelemetry qo'llab-quvvatlash bor β experimental_telemetry:
import { generateText } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
const { text } = await generateText({
model: anthropic("claude-opus-4-8"),
prompt: "Salom!",
experimental_telemetry: { isEnabled: true, functionId: "support-bot" },
});
// OpenTelemetry sozlangan bo'lsa, chaqiruv avtomatik trace bo'ladi
Muhimi β vosita emas, odat: har chaqiruv va har agent qadami ko'rinadigan bo'lsin.
Evals: "promptlar uchun testlar"¶
Observability ichkarini ko'rsatadi. Lekin "sifat yaxshimi?" degan savolga javob bermaydi. Buning uchun eval kerak.
Asosiy g'oya: LLM chiqishini aniq matn bilan tekshira olmaysiz (assert(javob === "...") β bekor, chunki javob har safar boshqacha). Buning o'rniga eval to'plami tuzasiz β har biri { input, criteria } ko'rinishidagi holatlar ro'yxati β ilovani ular ustidan yurgizasiz va har chiqishga ball qo'yasiz. Bu β aynan testlarning LLM versiyasi: promptni yoki modelni o'zgartirganingizda regressiyani ushlaydi.
Diagrammadagi sikl: eval to'plami β quvurni ishlat β ball qo'y β pass rate β promptni o'zgartir β qayta ishlat β solishtir. Eval to'plami misol:
// Har holat: kirish + qanday bo'lsa "to'g'ri" ekanini ta'riflovchi mezon
const evalSet = [
{ input: "Bu mahsulot zo'r, juda mamnunman!", expectedLabel: "ijobiy" },
{ input: "Yetkazib berish kechikdi, hafsalam pir bo'ldi.", expectedLabel: "salbiy" },
{ input: "Narxi o'rtacha, sifati ham shunday.", expectedLabel: "neytral" },
{ input: "Qadoq buzilgan keldi, lekin mahsulot ishlaydi.", expectedLabel: "neytral" },
{ input: "Ajoyib! Hammaga tavsiya qilaman.", expectedLabel: "ijobiy" },
];
Endi bularga ball qo'yish β ikki usul bor.
Ball qo'yish usuli 1 β strukturali / kod tekshiruvi¶
Birinchi va eng arzon usul: kod bilan tekshirib bo'ladigan narsani kod bilan tekshiring. Bu deterministik, tez va bepul β shuning uchun avval shu ishlaydi. Savollar: chiqish to'g'ri JSON bo'lib parse bo'ladimi? Kerakli maydon bormi? Kutilgan yorlig'ga (label) mosmi? Zod (06-bob) bu yerda ideal.
import { z } from "zod";
const Tasnif = z.object({
label: z.enum(["ijobiy", "salbiy", "neytral"]),
ishonch: z.number().min(0).max(1),
});
// Baholanadigan quvur: sharhni tasniflaydi (sizning ilovangiz)
async function tasnifla(matn) {
const res = await client.messages.parse({
model: "claude-haiku-4-5", // tasnif β arzon model yetarli (14-bob)
max_tokens: 128,
messages: [{ role: "user", content: `Sharh kayfiyatini aniqla: "${matn}"` }],
output_format: Tasnif, // Zod sxema (06-bob)
});
return res.parsed_output; // { label, ishonch }
}
// Strukturali tekshiruv: kutilgan label bilan mos keladimi?
function strukturaliBall(chiqish, kutilgan) {
const parsed = Tasnif.safeParse(chiqish); // 1) sxemaga mosmi?
if (!parsed.success) return { passed: false, sabab: "sxema buzuq" };
if (chiqish.label !== kutilgan) // 2) to'g'ri yorliqmi?
return { passed: false, sabab: `kutilgan ${kutilgan}, keldi ${chiqish.label}` };
return { passed: true };
}
Strukturali tekshiruv "to'g'ri javob aniq" bo'lgan vaziyatlar uchun ideal: tasniflash, ajratish, formatga moslik, ha/yo'q. Lekin "javob foydalimi? xushmuomalami? to'g'ri va to'liqmi?" kabi subyektiv savollarga kod javob bera olmaydi. Mana shu yerda ikkinchi usul kerak.
Ball qo'yish usuli 2 β LLM-as-judge (AI bilan AI ni baholash)¶
LLM-as-judge ("sudya sifatida LLM") β bu alohida Claude chaqiruvi, u bir chiqishni rubrika (baholash mezonlari) bo'yicha baholaydi va strukturali { score, passed, reasoning } qaytaradi. Ya'ni AI yordamida AI chiqishiga ball qo'yasiz β odam o'rniga, lekin odam tuzgan aniq mezon bilan.
Nega bu ishlaydi? Chunki "javob xushmuomalami?" degan savol β o'zi LLM yaxshi bajaradigan ish (matnni tushunish va baholash). Hiyla: javobni baholashni boshqa chaqiruvga ajratamiz va undan tuzilgan baho so'raymiz (06-bobdagi messages.parse() bilan), shunda natija kod uchun o'qiladigan bo'ladi:
import { z } from "zod";
// Sudya qaytaradigan struktura
const Baho = z.object({
score: z.number().min(1).max(5), // 1-5 ball
passed: z.boolean(), // o'tdimi (masalan score >= 4)
reasoning: z.string(), // NEGA shu ball β eng muhim maydon
});
/**
* judge: bir chiqishni rubrika bo'yicha baholaydi.
* Sudya β ALOHIDA Claude chaqiruvi (graded model boshqa bo'lishi mumkin).
*/
async function judge(input, output, rubrika) {
const res = await client.messages.parse({
model: "claude-opus-4-8", // sudya kuchli model bo'lgani yaxshi
max_tokens: 512,
messages: [{
role: "user",
content:
`Sen xolis baholovchisan. Quyidagi javobni RUBRIKA bo'yicha 1-5 ball bilan bahola.\n\n` +
`RUBRIKA:\n${rubrika}\n\n` +
`FOYDALANUVCHI SO'ROVI:\n${input}\n\n` +
`BAHOLANADIGAN JAVOB:\n${output}\n\n` +
`Avval reasoning'da sababini yoz, keyin score (1-5) va passed (score>=4) ber.`,
}],
output_format: Baho, // strukturali baho (06-bob)
});
return res.parsed_output; // { score, passed, reasoning }
}
// Ishlatish:
const rubrika =
`- 5: to'liq to'g'ri, aniq va xushmuomala\n` +
`- 3: qisman to'g'ri yoki ohang quruq\n` +
`- 1: noto'g'ri yoki qo'pol`;
const baho = await judge(
"Buyurtmam qachon keladi?",
"Buyurtmangiz 2-3 ish kunida yetkaziladi. Sabr qilganingiz uchun rahmat!",
rubrika
);
console.log(baho); // { score: 5, passed: true, reasoning: "To'g'ri va xushmuomala..." }
Eslatma β sudya mukammal emas. LLM-as-judge β kuchli, lekin uni bittagina haqiqat deb bilmang. Sudya tarafkash (biased) bo'lishi mumkin: uzun javoblarni, o'z uslubidagi javoblarni yoki birinchi ko'rsatilgan variantni asossiz afzal ko'rishi mumkin. Ikki himoya: (1) aniq, o'lchovli rubrika yozing β "yaxshimi?" emas, balki "faktlar to'g'rimi, ohang xushmuomalami, savolga javob berildimi?"; (2) muhim qarorlar uchun bir nechta sudya bahosini o'rtalang (ensemble) yoki namunani qo'lda tekshiring.
reasoningmaydoni shu uchun muhim β sudya nega shu ball berganini ko'rib, unga ishonish/ishonmaslikni o'zingiz hal qilasiz.AI SDK varianti: agar Vercel AI SDK ishlatsangiz, sudyani
generateObjectbilan ham yozasiz (12-bob) βschema: Bahobering,objectqaytadi. Mexanizm bir xil: alohida chaqiruv + strukturali baho.
Eval harness: hammasini birlashtirish¶
Endi ikkala usulni bitta harnessga (eval yurgizgich) yig'amiz: to'plam ustidan ilovani yurgizib, har chiqishga strukturali tekshiruv + sudya qo'llab, pass rate chiqaramiz. Bu β promptni o'zgartirganda qayta yurgizadigan asosiy vositangiz.
async function evalYurgiz(evalSet, rubrika) {
const natijalar = [];
for (const holat of evalSet) {
const chiqish = await tasnifla(holat.input); // ilovani ishga tushir
// 1) Strukturali tekshiruv β arzon, avval (06-bob)
const struktura = strukturaliBall(chiqish, holat.expectedLabel);
// 2) Sudya β faqat struktura o'tsa, sifatni baholaymiz (chaqiruvni tejaymiz)
let baho = { passed: struktura.passed, score: struktura.passed ? null : 1 };
if (struktura.passed) {
baho = await judge(holat.input, JSON.stringify(chiqish), rubrika);
}
const passed = struktura.passed && baho.passed;
natijalar.push({ input: holat.input, struktura, baho, passed });
}
const otgan = natijalar.filter((r) => r.passed).length;
const passRate = ((otgan / natijalar.length) * 100).toFixed(0);
console.log(`\n=== EVAL NATIJASI: ${otgan}/${natijalar.length} (${passRate}%) ===`);
for (const r of natijalar) {
const belgi = r.passed ? "β" : "β";
const sabab = r.passed ? "" : ` β ${r.struktura.sabab ?? r.baho.reasoning}`;
console.log(`${belgi} "${r.input.slice(0, 40)}..."${sabab}`);
}
return { passRate: Number(passRate), natijalar };
}
// Yurgizish:
await evalYurgiz(evalSet, rubrika);
// === EVAL NATIJASI: 4/5 (80%) ===
// β "Bu mahsulot zo'r, juda mamnunman!..."
// β "Qadoq buzilgan keldi, lekin mahsulot ishlaydi...." β kutilgan neytral, keldi salbiy
// ...
Endi ish jarayoni shunday (diagrammani eslang):
- Eval to'plamini yozing (~5-50 holat β ko'paygan sayin ishonch ortadi).
- Yurgizing β boshlang'ich pass rate (masalan 80%).
- Promptni o'zgartiring (05-bob) β masalan, "neytral" ta'rifini aniqlashtiring.
- Qayta yurgizing β yangi pass rate.
- Solishtiring: 80% β 92%? Yaxshilandi, prompt o'zgarishini saqlang. 80% β 60%? Regressiya β orqaga qayting. Mana shu β "test"ning AI versiyasi: o'zgarish sifatni oshirdimi yoki tushirdimi β raqam bilan bilasiz, taxmin bilan emas.
Diqqat β eval to'plamingiz yaxshi bo'lsa, eval ham yaxshi. Agar to'plamingiz faqat oson holatlardan iborat bo'lsa, 100% pass rate hech narsani isbotlamaydi. Real, qiyin va chegaraviy (edge-case) holatlarni qo'shing β ayniqsa ishlab chiqarishda allaqachon xato bo'lgan holatlarni. Eval to'plami β tirik hujjat: yangi xato topilsa, uni darrov to'plamga holat qilib qo'shing, toki u qaytib kelmasin.
Ishlab chiqarishda: sifatni doimiy kuzatish¶
Eval'ni bir marta yozib qo'yib unutish emas β ishlab chiqarishda u doimiy jarayon:
- Real trafikdan namuna oling. Haqiqiy foydalanuvchi so'rovlarining kichik foizini (anonim, PII tozalangan) eval to'plamingizga qo'shing. Sun'iy misollar real foydalanuvchini hech qachon to'liq qoplamaydi.
- Sifat "drift"ini kuzating. Modelni yangilasangiz yoki promptni o'zgartirsangiz, eval'ni qayta yurgizing. Hatto o'zgartirmasangiz ham β vaqti-vaqti bilan yurgizib, sifat asta-sekin pasaymayotganini tekshiring.
- Promptlarni A/B sinang. Ikki prompt variantini bir eval to'plamida solishtiring β qaysi biri yuqori pass rate beradi, shuni tanlang.
- Foydalanuvchi fikrini eval signali sifatida yig'ing. UI'da oddiy π/π tugmasi qo'ying. π olgan javoblar β eng qimmatli eval holatlari: ularni to'plamingizga qo'shing va prompt'ni shularni hal qiladigan qilib yaxshilang.
Shunday qilib aylanma yopiladi: observability sizga nima bo'layotganini ko'rsatadi β real holatlar eval to'plamiga aylanadi β eval prompt o'zgarishlarini tekshiradi β yaxshilangan ilova yana log qilinadi. Bu β AI ilovasini ko'r holda emas, ko'zli boshqarishning poydevori.
Tuzoqlar va ehtiyotkorlik¶
| Muammo | Sabab | Yechim |
|---|---|---|
| Mijoz shikoyatini debug qila olmayapsiz | Chaqiruvlar log qilinmagan | loggedCall o'rami: prompt, javob, request_id ni loglang |
| Log'da foydalanuvchi emaili/telefoni bor | PII tozalanmagan loglandi | Loglashdan oldin redact() (22-bob) |
| Agent qayerda adashganini topa olmayapsiz | Faqat oxirgi javob loglangan | traceId bilan har qadamni trace qiling |
| Promptni o'zgartirib sifat jimgina tushdi | Eval yo'q β regressiya ushlanmadi | Eval harness: o'zgarishdan oldin/keyin pass rate |
| Sudya har doim yuqori ball beradi | Rubrika noaniq ("yaxshimi?") | Aniq, o'lchovli rubrika + reasoning ni o'qing |
| Eval 100% lekin ishlab chiqarishda xato | To'plam faqat oson holatlardan | Real/chegaraviy holatlar, π javoblarni qo'shing |
| Eval juda qimmat/sekin | Har holatda Opus + sudya | Avval arzon strukturali tekshiruv; sudya faqat kerak bo'lganda |
| Token sonini o'zingiz hisoblayapsiz | usage o'qilmagan |
Doim msg.usage (14-bob), taxmin emas |
Diqqat β eval'ni promptni o'zgartirgan HAR safar yurgizing. Eng katta xato β bir prompt'ni "yaxshilab" deploy qilib, bilmasdan boshqa 10 ta holatni buzish. Unit testni har commit'da yurgizganingizdek, eval'ni ham har prompt/model o'zgarishidan keyin yurgizing. Aks holda sifat jimgina, sezilmay pasayadi.
Mashqlar¶
Ba'zi mashqlar haqiqiy API kaliti talab qiladi (jonli chaqiruv/sudya), ba'zilari faqat mantiq/kod. API kaliti kerak bo'lgan joyni belgilab qo'ydik.
Oson¶
loggedCallni yozing. YuqoridagiloggedCallo'ramini (PII'siz, soddalashtirilgan) yozing: umodel,usage,narx,kechikish_ms,stop_reasonni JSON qilib loglasin. Nega_request_idni ham loglash muhim β bir jumlada izohlang.- Nega aniq matnni assert qilib bo'lmaydi? LLM chiqishi non-deterministik bo'lgani uchun
assert(javob === "...")nega ishonchsiz ekanini va uning o'rniga nima qilish kerakligini (eval to'plami + ball) o'z so'zlaringiz bilan tushuntiring. - Strukturali ball.
strukturaliBall(chiqish, kutilgan)funksiyasini yozing: chiqish Zod sxemaga mosligini valabelkutilganga tengligini tekshirsin,{ passed, sabab }qaytarsin.
O'rta¶
- PII tozalash (API kaliti emas).
redact(s)ni kengaytiring: email va telefondan tashqari, oddiy karta raqami namunasini (\d{16})[karta]ga almashtirsin. Uni bir necha test matnida sinab, log'ga xom PII tushmasligini tasdiqlang. judgeni yozing (API kaliti).judge(input, output, rubrika)funksiyasini yozing: alohida Claude chaqiruvimessages.parse()bilan{ score, passed, reasoning }qaytarsin. Bir yaxshi va bir yomon javobni bir xil rubrika bilan baholang β ballar farq qildimi?- Sudya tarafkashligi (API kaliti). Bir xil ma'noli, lekin biri qisqa, biri keraksiz uzun ikkita javobni
judgega bering. Sudya uzunini asossiz yuqori baholadimi?reasoningni o'qib, rubrikani buni oldini oladigan qilib aniqlashtiring.
Qiyin¶
- To'liq eval harness (API kaliti).
evalYurgiz(evalSet, rubrika)ni yozing: 5 ta holat ustidantasniflani yurgizib, har biriga strukturali tekshiruv + sudya qo'llasin va pass rate chiqarsin. Bitta holatni ataylab "qiyin" qiling β qaysi bosqichda (struktura yoki sudya) yiqilishini ko'ring. - Regressiyani ushlang (API kaliti). 7-mashqdagi quvurning prompt'ini ataylab yomonlashtiring (masalan, "neytral" ta'rifini olib tashlang). Eval'ni oldin va keyin yurgizing β pass rate qancha tushdi? Bu nega "test"ga o'xshashligini izohlang.
- Trace (API kaliti). Ikki bosqichli oddiy oqim yozing (masalan: avval savolni tasniflang, keyin shu turga mos javob bering). Har bosqichni bitta
traceIdbilan loglang. Keyin loglarnitraceIdbo'yicha filtrlab, butun trayektoriyani β har qadami bilan β chop eting.
Yechimlar
Quyidagi yechimlarda
clientβnew Anthropic()(02-bob),cost()/NARXβ 14-bobdagidek,Tasnif/Bahoβ bobdagidek. API kaliti kerak bo'lgan yechimlarANTHROPIC_API_KEY(.env) talab qiladi.
1-mashq yechimi¶
async function loggedCall(params, meta = {}) {
const boshi = Date.now();
const msg = await client.messages.create(params);
log({
...meta,
model: params.model,
usage: msg.usage,
narx: Number(cost(msg.usage, params.model).toFixed(6)),
kechikish_ms: Date.now() - boshi,
stop_reason: msg.stop_reason,
request_id: msg._request_id,
});
return msg;
}
const log = (q) => console.log(JSON.stringify(q));
_request_id muhim, chunki biror so'rov g'alati ketsa, uni Anthropic qo'llab-quvvatlash xizmatiga berib, aynan o'sha chaqiruvni tekshirtirasiz.
2-mashq yechimi¶
LLM β ehtimollik mashinasi: bir xil prompt har safar biroz boshqa matn beradi (01-bob). Shuning uchun assert(javob === "...") deyarli doim yiqiladi β sal boshqacha so'z, sal boshqa formatda javob "noto'g'ri" hisoblanadi, garchi ma'no to'g'ri bo'lsa ham. To'g'ri yo'l: aniq matn emas, mezonni tekshirish. Eval to'plami ({ input, criteria }) tuziladi, ilova ustidan yurgiziladi va har chiqishga ball qo'yiladi (strukturali tekshiruv yoki LLM-as-judge), so'ng pass rate o'lchanadi.
3-mashq yechimi¶
function strukturaliBall(chiqish, kutilgan) {
const parsed = Tasnif.safeParse(chiqish);
if (!parsed.success) return { passed: false, sabab: "sxema buzuq" };
if (chiqish.label !== kutilgan)
return { passed: false, sabab: `kutilgan ${kutilgan}, keldi ${chiqish.label}` };
return { passed: true };
}
Avval sxema (to'g'ri shaklmi?), keyin qiymat (to'g'ri yorliqmi?) β ikki bosqichli. safeParse xato tashlamaydi, { success } qaytaradi.
4-mashq yechimi¶
function redact(s) {
return String(s)
.replace(/[\w.+-]+@[\w-]+\.[\w.-]+/g, "[email]")
.replace(/\+?\d[\d\s-]{8,}\d/g, "[telefon]")
.replace(/\b\d{16}\b/g, "[karta]"); // oddiy 16-raqamli karta namunasi
}
console.log(redact("Ali ali@co.uz, +998901234567, 1234567812345678"));
// "Ali [email], [telefon], [karta]"
Bu β minimal misol. Haqiqiy ilovada karta raqami probellar/chiziqlar bilan ham keladi va boshqa PII turlari bor β domeningizga mos to'liqroq tozalash yozing (22-bob).
5-mashq yechimi¶
async function judge(input, output, rubrika) {
const res = await client.messages.parse({
model: "claude-opus-4-8",
max_tokens: 512,
messages: [{
role: "user",
content:
`Sen xolis baholovchisan. RUBRIKA bo'yicha 1-5 ball ber.\n\n` +
`RUBRIKA:\n${rubrika}\n\nSO'ROV:\n${input}\n\nJAVOB:\n${output}\n\n` +
`reasoning'da sababini yoz, keyin score va passed (score>=4).`,
}],
output_format: Baho,
});
return res.parsed_output;
}
const r = `- 5: to'g'ri va xushmuomala\n- 1: noto'g'ri yoki qo'pol`;
console.log(await judge("Buyurtmam qachon?", "2-3 kunda yetkaziladi, rahmat!", r)); // yuqori
console.log(await judge("Buyurtmam qachon?", "Bilmadim, o'zingiz qarang.", r)); // past
Yaxshi javob yuqori, yomoni past ball oladi β chunki sudya rubrikaga qarab baholaydi.
6-mashq yechimi¶
const qisqa = "2-3 ish kunida yetkaziladi.";
const uzun = "Hurmatli mijoz, sizning qimmatli buyurtmangiz haqida... (10 jumla, lekin xuddi shu ma'no)";
console.log(await judge("Buyurtmam qachon?", qisqa, rubrika));
console.log(await judge("Buyurtmam qachon?", uzun, rubrika));
Ko'pincha sudya uzun javobga asossiz yuqoriroq ball beradi (uzunlik = sifat emas!). Rubrikaga aniq band qo'shing: "Qisqalik afzal; ortiqcha so'z ball oshirmaydi. Faqat savolga to'g'ri javob berilganini bahola." reasoning ni o'qib, sudya nimaga e'tibor berganini ko'rasiz va shunga qarab rubrikani tuzatasiz.
7-mashq yechimi¶
async function evalYurgiz(evalSet, rubrika) {
const natijalar = [];
for (const h of evalSet) {
const chiqish = await tasnifla(h.input);
const struktura = strukturaliBall(chiqish, h.expectedLabel);
let baho = { passed: struktura.passed };
if (struktura.passed) baho = await judge(h.input, JSON.stringify(chiqish), rubrika);
natijalar.push({ input: h.input, passed: struktura.passed && baho.passed, struktura, baho });
}
const otgan = natijalar.filter((r) => r.passed).length;
console.log(`${otgan}/${natijalar.length} (${((otgan / natijalar.length) * 100).toFixed(0)}%)`);
natijalar.forEach((r) =>
console.log(`${r.passed ? "β" : "β"} ${r.input.slice(0, 40)}`)
);
return natijalar;
}
await evalYurgiz(evalSet, rubrika);
"Qiyin" holat (masalan, "qadoq buzilgan, lekin ishlaydi") odatda strukturali bosqichda yiqiladi β model "salbiy" desa, kutilgan "neytral" ga teng kelmaydi va sudyagacha yetib bormaydi (chaqiruv tejaladi).
8-mashq yechimi¶
// Yomon prompt: "neytral" ta'rifi yo'q -> model ko'p sharhni ijobiy/salbiyga uradi
async function tasniflaYomon(matn) {
const res = await client.messages.parse({
model: "claude-haiku-4-5", max_tokens: 128,
messages: [{ role: "user", content: `Sharh ijobiymi yoki salbiy: "${matn}"` }],
output_format: Tasnif,
});
return res.parsed_output;
}
// evalYurgiz ichida tasnifla -> tasniflaYomon ga almashtirib, oldin va keyin pass rate'ni solishtiring.
Yomon prompt bilan pass rate tushadi (masalan 80% β 50%), chunki "neytral" holatlar noto'g'ri tasniflanadi. Bu aynan test kabi: o'zgarish (prompt) sifatni oshirdimi yoki buzdimi β pass rate raqam bilan ko'rsatadi, taxmin bilan emas. Shuning uchun eval'ni har prompt o'zgarishida yurgizish kerak.
9-mashq yechimi¶
import { randomUUID } from "node:crypto";
async function ikkiBosqich(savol) {
const traceId = randomUUID();
// 1-qadam: savol turini aniqla
const turRes = await client.messages.create({
model: "claude-haiku-4-5", max_tokens: 16,
messages: [{ role: "user", content: `"${savol}" β texnik yoki umumiy? Bir so'z.` }],
});
const tur = turRes.content[0].text.trim();
log({ traceId, qadam: 1, turi: "tasnif", natija: tur, request_id: turRes._request_id });
// 2-qadam: turga mos javob
const javobRes = await client.messages.create({
model: "claude-opus-4-8", max_tokens: 512,
messages: [{ role: "user", content: `(${tur} savol) ${savol}` }],
});
log({ traceId, qadam: 2, turi: "javob", request_id: javobRes._request_id });
return javobRes.content[0].text;
}
const log = (q) => console.log(JSON.stringify(q));
await ikkiBosqich("Login parolimni unutdim, nima qilay?");
// Loglarni traceId bo'yicha filtrlab, ikkala qadam ketma-ket ko'rinadi
Bitta traceId ikkala qadamni bog'laydi β keyin loglarni shu ID bo'yicha filtrlab, butun trayektoriyani ko'rasiz. Agar javob noto'g'ri bo'lsa, qaysi qadam aybdor ekanini (tasnif xato chiqdimi yoki javob?) aniq topasiz.
Keyingi qadam. Endi siz AI ilovangizni ko'r holda emas boshqarasiz:
loggedCallbilan har chaqiruvni (narx, kechikish,stop_reason,_request_id, PII tozalangan) ko'rasiz, agent qadamlarinitraceIdbilan trace qilasiz, va eval harness β strukturali tekshiruv + LLM-as-judge β bilan sifatni raqam bilan o'lchaysiz hamda regressiyani ushlaysiz. Bu β ishonchli AI ilovasining poydevori. Keyingi bobda esa narx va kechikishni keskin pasaytiruvchi ikki vositaga o'tamiz: 24 β Batch API va optimizatsiya β kechikishga sezgir bo'lmagan ommaviy ishlarni 50% arzon Batch API bilan bajarish va umumiy optimizatsiya. Eval to'plamingizni Batch bilan yurgizsangiz β arzon va tez sifat tekshiruvi.
β¬ οΈ Oldingi: 22 β Xavfsizlik Β· π README Β· Keyingi: 24 β Batch API va optimizatsiya β‘οΈ