CodeGym /Kurslar /ChatGPT Apps /Qüsurlara davamlılıq: addım rollback‑i, retry‑lar, səhvlə...

Qüsurlara davamlılıq: addım rollback‑i, retry‑lar, səhvlərə nəzarət

ChatGPT Apps
Səviyyə , Dərs
Mövcuddur

1. Niyə ChatGPT App‑də xətalar — istisna deyil, normadır

Ötən mühazirədə tapşırığı addımlara bölməyi və ChatGPT App‑də çoxaddımlı workflow qurmağı danışmışdıq. İndi bu sxeməyə real həyatı əlavə edirik: xətalar, taymautlar və istifadəçi tərəfindən kəsilmələr.

Klassik vebdə məntiq tez‑tez “xoşbəxt yol” ətrafında qurulur, xətalar isə nəsə nadir və fövqəladə bir şey kimi qəbul olunur — qırmızı 500 səhifəsi və s. ChatGPT App‑də mənzərə fərqlidir: siz LLM, xarici API‑lər, MCP, vidcet və üstəlik istənilən an vərəqi bağlaya bilən istifadəçi ilə paylanmış sistemdə işləyirsiniz. Xətalar və kəsilmələr — gündəlik rutindir.

Həyatı çətinləşdirən bir neçə xüsusiyyət var:

  • İlk olaraq, LLM determinizm deyil. Hətta eyni prompt ilə belə o, bir az fərqli qərar verə bilər: başqa alət çağırar, parametrləri dəyişər və ya ümumiyyətlə “yenidən soruşmağın” daha yaxşı olduğunu düşünər.
  • İkincisi, şəbəkə və infrastruktur məhdudiyyətləri. ChatGPT‑dən tool‑call üçün taymautlar var (adətən onlarla saniyə), sizin Next.js/Vercel backend‑inizdə də. Xarici API ləngiyirsə, hər şey ortasında kəsilə bilər.
  • Üçüncüsü, UX faktoru: istifadəçi diqqəti yayınır, söhbəti bağlayır, bir gün sonra qayıdır, amma siz bu müddət ərzində bazada tranzaksiyanı açıq saxlaya bilməzsiniz.

Buradan mühazirənin əsas tezi gəlir:

Qüsurlara davamlı workflow = elə ssenari ki, biz əvvəlcədən hər hansı addımın çökə biləcəyini qəbul edirik və bu halda nə baş verdiyini aydın müəyyənləşdiririk.

Səhv — yalnız istifadəçiyə mesaj göstərmək üçün deyil, həm də model üçün siqnaldır ki, strategiyanı dəyişə, rollback təklif edə, başqa alət sınaya və ya ssenarini səliqəli şəkildə tamamlayacaq.

2. Workflow‑da xəta landşaftı: onlar hansılardır

Sökmələri düzgün idarə etmək üçün əvvəlcə onları ayırd etməyi bacarmaq lazımdır. ChatGPT Apps əsasında LLM tətbiqində tipik olaraq bir neçə sinif səhv olur.

Texniki xətalar. Bunlar paylanmış sistemlərin klassikasıdır: şəbəkə taymautları, sizin və ya xarici API‑lərdən gələn 5xx, MCP serverinin düşməsi, alət handler kodunda bug. Məsələn, GiftGenius‑da sizin MCP‑alətiniz search_products kataloqa müraciət edir, o isə 503 Service Unavailable qaytarır. Bu, avtomatik retry üçün namizəddir.

Məntiqi (model) xətalar. Buraya modelin imtinası (sorğunu siyasəti pozan saydı), halüsinasiyalar və ya məsələn, alət cavabında pozulmuş JSON daxildir. Model tool‑call üçün yalnış arqumentlər generasiya edə bilər və sizin JSON‑validasiya onları buraxmaz. Bu, çox vaxt giriş məlumatı səhvi olur, infrastruktur yox.

Biznes xətaları. Mənaya aiddir: məhsul tükənib, istifadəçinin büdcəsi seçilmiş filtrlər üçün çox azdır, promo‑kod etibarsızdır, bron artıq bitib. GiftGenius‑da bu, “500 namizəddən heç biri qoyulan məhdudiyyətlərə uyğun gəlmir” vəziyyətidir. Burada retry nadir hallarda kömək edir: parametrləri dəyişmək və ya istifadəçiyə məhdudiyyətin qeyri‑real olduğunu izah etmək lazımdır.

UX kəsilmələri. İstifadəçi ssenarini özü yarımçıq qoyur: ChatGPT‑ni bağlayır, vidcetdə “Geri” düyməsini sıxır, əməliyyatı ləğv edir, əvvəlki addımdakı cavabı dəyişir. Bunu da səhv deyil, normal axın hesab etmək lazımdır. Belə hallarda bərpa və rollback edə bilmək vacibdir, bunun barədə az sonra danışacağıq.

Məntiqi və texniki xətaların kəsişməsində ayrı bir problemli hal — agentin sonsuz dövrlərə düşməsidir: model səhv alır, “hmm, yenə sınayım” deyir, yenə səhv, və beləliklə kontekst və ya büdcə bitənə qədər. Belə davranışdan qorunmaq — səhv dizaynının vacib hissəsidir.

3. Baza strategiyalar: retry, fail‑fast, rollback, istifadəçinin cəlb edilməsi

Hər bir səhvi budaqlanma nöqtəsi kimi görmək olar: ya addımı təkrar edirik, ya geri qaytarırıq, ya da istifadəçini cəlb edirik. Və vacibi odur ki, bu strategiyalar kombinə olunur.

Texniki və müvəqqəti nasazlıqlar üçün (şəbəkə “göz vurdu”, API 503 qaytardı) məhdud retry və backoff məntiqlidir. Məntiqi və biznes xətaları üçün (“validator büdcəni qəbul etmədi”, “mallar tükənib”) təkrarın mənası yoxdur, fail‑fast lazımdır və istifadəçidən girişi və ya parametrləri dəyişməyi istəmək.

Artıq xarici dünyada nəsə dəyişən əməliyyatlar üçün (sifariş yaradıldı, bron olundu) rollback lazımdır — UI/kontekstdə “bir addım geri” kimi və ya real kompensasiya tədbirləri (sifarişin ləğvi, vəsaitin qaytarılması) şəklində.

Nəhayət, elə hallar var ki, mütləq istifadəçi iştirakı tələb edir: məsələn, “kart bank tərəfindən rədd edildi” səbəbi ilə ödəniş sistemi imtina edəndə bunu avtomatik düzəldə bilməzsiniz. Model baş verəni düzgün izah etməli və variantlar təklif etməlidir: başqa kart sınamaq, məbləği azaltmaq və ya alqıdan imtina etmək.

Etibarlı workflow üçün hər addım üzrə ayrıca yazmaq faydalıdır: burada hansı tip xətalar mümkündür və hər biri üçün nə edirsiniz — auto‑retry, rollback, istifadəçiyə sorğu və ya sadəcə log və budağın tamamlanması.

4. Retry və backoff: nə vaxt və necə etmək

Gəlin developerin ən təbii reaksiyasından başlayaq: “Gəlin bir də sınayaq.” Fikir düzgündür, amma hər zamanki kimi, şeytan ayrıntılardadır.

Hansıları retry etmək olar

İnteqrasiyalar təcrübəsindən yaxşı bir qayda belə səslənir: şəbəkə xətaları və 5xx — fasilə ilə bir daha sınamaq olar, 4xx — böyük ehtimalla yox.

Yəni əgər 503, 504 aldınızsa və ya ümumiyyətlə xarici API‑dən cavab gözləmədinizsə, qısa gecikmə ilə sorğunu təkrarlamağın mənası var. Əgər server 400 Bad Request və ya 422 Unprocessable Entity qaytardısa, yəqin ki, problem məlumatlardadır və eyni parametrlərlə təkrar heç nəyi dəyişməyəcək.

TypeScript‑də sadə callWithRetry utili

MCP və ya backend qatında alətlərdə istifadə oluna bilən kiçik utili yazaq:

type RetryOptions = {
  maxRetries: number;
  baseDelayMs: number;
};

async function callWithRetry<T>(
  fn: () => Promise<T>,
  { maxRetries, baseDelayMs }: RetryOptions
): Promise<T> {
  let attempt = 0;

  // sonsuz dövrlərə ehtiyac yoxdur
  while (true) {
    try {
      return await fn();
    } catch (err: any) {
      attempt++;
      const status = err?.status ?? err?.response?.status;

      // 4xx üçün retry etmirik
      const isClientError = typeof status === "number" && status >= 400 && status < 500;
      if (attempt > maxRetries || isClientError) {
        throw err;
      }

      const delay = Math.min(baseDelayMs * 2 ** (attempt - 1), 10_000);
      // API-ni “sürü” kimi vurmasın deyə kiçik fasilə
      const jitter = Math.random() * 200;

      await new Promise((r) => setTimeout(r, delay + jitter));
    }
  }
}

Bu funksiya:

  • fn çağırışını məhdud sayda təkrar edir;
  • eyni vaxtda çox retry nəticəsində “sürü” effektindən qaçmaq üçün kiçik təsadüfi səs (jitter) ilə eksponensial backoff istifadə edir;
  • 4xx üzərində retry etmir.

Məsələn, məhsul kataloquna və ya daxili tövsiyə API‑nə gedən MCP alətlərinin içində istifadə etmək yaxşıdır.

Retry‑ı harada etmək

Tez‑tez edilən səhv — nəzarətinizdə olmayan laylarda da daxil olmaqla, bütün sorğuları dalbadal təkrarlamaqdır. ChatGPT ekosistemində retry üçün bir neçə yeriniz var:

  • öz backend/MCP daxilində (bizim callWithRetry kimi);
  • fondakı worker/cuedə (gələcək modullarda job cüeləri və DLQ‑dan daha ətraflı danışacağıq);
  • bəzən — elə vidcetin özündə, söhbət əlavə təsir yaratmayan “siyahını yenilə” kimi yüngül sorğudan gedirsə.

Məntiqi təkrarlamayın: əgər sizin job‑worker artıq backoff ilə üç retry edirsə, vidcetin üzərinə daha beş retry asmağın mənası yoxdur. Və əlbəttə, heç vaxt while(true) { try ... } etməyin — bu, özünüzə DDoS düzəltməyin ən qısa yoludur.

5. Addımların idempotentliyi: dublikatlardan qorunma

Retry ikinci bir problemi yaradır: eyni əməliyyatı iki dəfə etməmək necə mümkün olsun. LLM dünyasında bu xüsusilə kəskindir: model səhvən eyni aləti bir neçə dəfə çağıra bilər, ChatGPT taymautdan sonra tool‑call‑ı təkrarlaya bilər, istifadəçi “Regenerate” basar, sonra isə UI və ya agent ayrıca bir çağırış əlavə edər.

Idempotentlik ideyası sadədir: eyni giriş məlumatları ilə addımın təkrar icrası əlavə yan təsir yaratmırsa, addım idempotent sayılır. Product feed istəmək — ok, tövsiyələri yenidən hesablamaq — ok, amma pulu təkrar çıxmaq və ya eyni məlumatlarla ikinci sifariş yaratmaq — əsla olmaz.

ChatGPT App‑də idempotency key

Klassik naxış: yan təsiri olan hər məntiqi addım üçün idempotency_key (adətən UUID) generasiya edirsiniz, onu model vasitəsilə MCP alətinə ötürür və orada “açar → nəticə” uyğunluğunu saxlayırsınız. Alət eyni açarla ikinci dəfə çağırılarsa, o əməliyyatı təkrarlamır, sadəcə artıq saxlanmış nəticəni qaytarır.

Bizim GiftGenius‑da create_order addımı var. Təsəvvür edin, istifadəçi “Ödə” düyməsini sıxdı, model aləti çağırdı, ödəniş baş tutdu, amma yolda cavab itdi. Model və ya platforma çağırışı təkrarlamağa qərar verir və əgər idempotentlik yoxdursa, dublikat sifariş və ya ikiqat çıxarılma alarıq.

TypeScript‑də idempotent alət üçün sadə nümunə

Idempotency açarı ilə çox sadələşdirilmiş create_order MCP alət handler‑i yazaq. Sadəlik üçün in‑memory Map istifadə edək, real həyatda bu, DB və ya kəş olacaq.

type CreateOrderInput = {
  userId: string;
  items: Array<{ sku: string; qty: number }>;
  idempotencyKey: string;
};

type CreateOrderResult = { orderId: string; status: "created" };

const idempotencyStore = new Map<
  string,
  { paramsHash: string; result: CreateOrderResult }
>();

export async function createOrderTool(input: CreateOrderInput): Promise<CreateOrderResult> {
  const { idempotencyKey, ...rest } = input;
  const paramsHash = JSON.stringify(rest);

  const existing = idempotencyStore.get(idempotencyKey);
  if (existing) {
    // açar artıq varsa, parametrlərin eyni olduğuna əmin oluruq
    if (existing.paramsHash !== paramsHash) {
      throw new Error("Idempotency key reuse with different params");
    }
    return existing.result;
  }

  // burada sifarişin real yaradılması və ödəniş edilir
  const result: CreateOrderResult = {
    orderId: "order_" + Math.random().toString(36).slice(2),
    status: "created",
  };

  idempotencyStore.set(idempotencyKey, { paramsHash, result });
  return result;
}

Burada biz:

  • alətin girişində idempotencyKey tələb edirik;
  • onunla birlikdə parametrlərin haşını saxlayırıq (sadəlik üçün burada JSON.stringify);
  • eyni açarla, amma başqa məlumatlarla təkrar çağırış — bunu xəta hesab edirik;
  • eyni açarla, eyni məlumatlarla təkrar çağırış — əvvəlki nəticəni qaytarırıq.

Real layihədə bunları etmək məsləhətdir:

  • açarları TTL ilə DB‑də saxlamaq (cədvəl hədsiz böyüməsin deyə);
  • idempotency_key‑i loqlamaq və MCP mesajlarının _meta‑sına qoymaq ki, onları Inspector və dashboardlarda rahat izləmək olsun.

6. Addımların rollback‑i və Saga naxışı

Idempotentlik dublikatlardan qoruyur, amma başqa halı həll etmir: ssenarinin ortasında hər hansı addım uğursuz olarsa nə etməli.

E‑commerce‑də bu klassik problemdir: siz artıq sifariş yaratdınız və anbarda malı bronladınız, amma ödəniş mərhələsində nəsə alınmadı. “Bunu unutmaq” olmaz — əvvəlki vəziyyəti nəyəsə qaytarmaq lazımdır.

Loji və texniki rollback

ChatGPT‑workflow‑da rollback‑in iki səviyyəsi var.

Loji rollback — ssenarinin əvvəlki addımına qayıtmaq və konteksti düzəltməkdir. Məsələn, “ödəniş” addımında xəta oldu və siz “ödəmə üsulunun seçimi”nə və ya hətta “hədiyyənin seçimi”nə qayıtmağa qərar verdiniz. Onda vacibdir:

  • backend‑də WorkflowContext‑i yeniləmək (cari addım, seçilmiş parametrlər);
  • modelə addımın dəyişdiyini tool‑call/ToolOutput vasitəsilə bildirmək ki, “köhnə budağı” unutsun və sonrakı davranışı uyğunlaşdırsın;
  • vidcet UI‑ni yeniləmək ki, addımlar və düymələr yeni vəziyyətə uyğun olsun.

Texniki rollback — artıq biznes səviyyəsidir: yaradılmış obyektlərin ləğvi, xarici təsirlərin kompensasiyası. Məsələn: sifarişi ləğv etmək, anbardan rezervi götürmək, ödənişi geri qaytarmaq. Bu, Saga naxışının özüdür: hər “təhlükəli” addım üçün əvvəlcədən kompensasiya əməliyyatı fikirləşirsiniz.

GiftGenius üçün forward/compensate sxemi

Sadələşdirilmiş GiftGenius checkout‑u üçün belə ardıcıllıq çəkə bilərik:

flowchart TD
  A[Addım 1: create_order] --> B[Addım 2: reserve_items]
  B --> C[Addım 3: charge_card]

  C -->|uğur| D[Vəziyyət: completed]

  C -->|xəta| E[Kompensasiya: cancel_reservation]
  E --> F[Kompensasiya: cancel_order]
  F --> G[Vəziyyət: failed + istifadəçiyə mesaj]

Xarici dünyanı dəyişən hər hərəkətə (sifarişin yaradılması, rezervləşdirmə, ödəniş) kompensasiya hərəkəti (sifarişin ləğvi, rezervin götürülməsi, vəsaitin qaytarılması) uyğun gəlir. Onlar həmişə simmetrik olmur və bəzən bir‑birinə tam uyğun deyil, amma ümumi prinsip budur.

Kodda kompensasiya ilə mini‑nümunə

Bu addımları icra edən kiçik kod fraqmentinə baxaq:

async function completeCheckout(ctx: { userId: string }) {
  const order = await createOrderInDb(ctx.userId);

  try {
    await reserveItems(order.id);
    await chargeCard(order.id);
    return { orderId: order.id, status: "paid" as const };
  } catch (err) {
    // kompensasiyaedici əməliyyatlar
    await safeCancelReservation(order.id);
    await safeCancelOrder(order.id);
    throw err;
  }
}

Burada:

  • createOrderInDb, reserveItems, chargeCard — forward addımlarıdır;
  • safeCancelReservationsafeCancelOrder — kompensasiya addımlarıdır və özləri idempotent olmalıdır (əgər artıq ləğv edilmişi yenidən ləğv etməyə çalışsaq, heç nə baş verməməlidir).

Diqqət edin ki, xəta zamanı onu gizlətmirik, irəli ötürürük. Model (ToolOutput vasitəsilə) aydın bir səhv mesajı almalı və artıq insan dilində bunu istifadəçiyə izah edib növbəti addımı təklif etməlidir.

7. Addımların rollback‑i və vəziyyətin sinxronizasiyası: desinxron yaratmamaq üçün

Asanlıqla qiymətdən salınan xüsusi bir “səhv” var: UI, backend və model arasında vəziyyətin desinxronu.

Tipik ssenari:

  1. İstifadəçi 1 → 2 → 3 addımlarından keçir.
  2. 3‑cü addımda nəsə alınmır, istifadəçi vidcetdə “Geri” düyməsini sıxır.
  3. Vidcet öz lokal state‑ini vicdanla 2‑ci addıma qaytarır.
  4. Ancaq model “yadda saxlayır” ki, biz 3‑cü addımda idik və artıq ödənişə cəhd etdik. Növbəti mesajda o, ödənişdən danışmağa davam edir, baxmayaraq ki, istifadəçi hədiyyə seçimi ekranını görür.

Belə olmasın deyə addım rollback hadisəsini açıq şəkildə daxil etmək faydalıdır. Onu vidcet MCP/modelə göndərir — ya alət çağırışı, ya da ToolOutput kimi.

Məsələn, cari addımı və vəziyyəti qeyd edən sadə user_navigated_to_step aləti etmək olar:

type NavigateInput = {
  workflowId: string;
  stepId: string;
};

export async function userNavigatedToStep(input: NavigateInput) {
  await workflowRepo.setCurrentStep(input.workflowId, input.stepId);
  return {
    message: `User moved to step ${input.stepId}`,
  };
}

Vidcet “Geri” basanda bu aləti çağırır; model onun nəticəsini tool‑call tarixçəsində görür və indi dialoqu yeni addımdan davam etdirmək lazım olduğunu anlayır.

UI tərəfində isə bu, təxminən belə handler‑dir:

async function handleBackClick() {
  const { workflowId, prevStepId } = widgetState;

  await window.openai.tools.call("user_navigated_to_step", {
    workflowId,
    stepId: prevStepId,
  });

  setWidgetState((s) => ({ ...s, currentStepId: prevStepId }));
}

Vacib məqam: məhz backend/agent — cari addım üzrə həqiqətin mənbəyidir və model onu alətlər vasitəsilə görür. Beləliklə, sessiya bərpa olunanda da konteksti düzgün sinxronlaşdıra bilərsiniz.

8. Xəta UX‑i: istifadəçi nə görür və model nə görür

Artıq xətaları texniki cəhətdən keçməyi öyrəndik (retry‑lar, rollback‑lər, idempotentlik, vəziyyətin sinxronizasiyası). Qaldı bunu həm istifadəçi, həm də model üçün adekvat göstərmək.

İdeal retry və rollback belə xilas etməyəcək, əgər xəta UX‑i “köhnə Java servletləri” kimidirsə: qırmızı mətn, stack trace və anlaşılmaz “Unexpected error”.

ChatGPT App üçün xəta mesajının iki auditoriyası var:

  • istifadəçi, baş verəni və növbəti nə edə biləcəyini anlamalıdır;
  • model, hansı qərarı verəcəyinə qərar vermək üçün kifayət qədər strukturlaşdırılmış məlumat almalıdır: təkrar, parametrlərin dəyişdirilməsi, alternativ təklif və ya ssenarinin tamamlanması.

Yaxşı praktika:

  • MCP/alətlər səviyyəsində kod, tip, retryable bayrağı və qısa texniki mətnlə strukturlaşdırılmış xəta qaytarmaq;
  • modeə məhz bu strukturu vermək (məsələn, result.structuredContent daxilində), kilometr‑kilometr stack trace yox;
  • UI‑də istifadəçiyə insani, qısa mesaj göstərmək.

Alətin qaytardığı xəta strukturuna mini‑nümunə:

type ToolError = {
  code: string;          // e.g. "PAYMENT_TIMEOUT"
  message: string;       // qısa texniki təsvir
  retryable: boolean;    // yenidən cəhd etmək olarmı
};

throw {
  isError: true,
  error: <ToolError>{
    code: "PAYMENT_TIMEOUT",
    message: "Payment provider did not respond in time",
    retryable: true,
  },
};

Model retryable: true görür və başqa aləti sınaya və ya istifadəçiyə yenidən cəhd etməyi təklif edə bilər.

Vidcet tərəfində isə bu kodları istifadəçiyə anlaşılan mətnlərə map edirsiniz:

function ErrorBanner({ code }: { code: string }) {
  const text =
    code === "PAYMENT_TIMEOUT"
      ? "Ödəniş xidməti vaxtında cavab vermədi. Zəhmət olmasa, bir dəqiqədən sonra yenidən cəhd edin."
      : "Nə isə alınmadı. Zəhmət olmasa, yenidən cəhd edin.";

  return <div className="error-banner">{text}</div>;
}

Və daha bir vacib məqam: istifadəçiyə istisna steklərini, tokenləri, sirləri göstərməyin. Bu həm çirkin, həm də təhlükəlidir. Texniki məlumatı özünüzdə loglayın, istifadəçiyə isə qısa və təhlükəsiz mesaj verin.

Insight

ChatGPT kimi LLM sistemlərində alət çağırışlarının nekorrektliyi — istisnadan çox normadır. Model mütəmadi olaraq validasiyadan keçməyən arqumentlər generasiya edir: qarışmış tiplər, çatmayan sahələr, yanlış dəyərlər, pozulmuş strukturlar. Bu, ənənəvi mühəndislik mənasında səhv deyil — stoxastik modelin təbiətinin bir hissəsidir və bütün xəta interfeysini buna uyğunlaşdırmaq lazımdır.

Açar fikir: xəta mesajı “sındı” siqnalı deyil, növbəti cəhdi düzəltmək üçün təlimatdır. Onun əsas auditoriyası — elə modelin özüdür. Əgər mesaj strukturlaşdırılıb və dəqiq göstərişlər ehtiva edirsə, model parametrləri avtomatik korrektə edə və çağırışı düzgün təkrarlaya bilir. Bu, Tool‑Reflection texnikalarının əsaslandığı prinsiptir: düzgün geribildirim insan iştirakı olmadan agentin növbəti hərəkətini yaxşılaşdırır.

Tövsiyə edirəm ki, xəta formatı üçün belə tələblərə əməl edəsiniz:

  • mesaj validasiyadan keçməyən konkret sahəni göstərməlidir — “Invalid parameters” kimi ümumiləşdirmələr yox;
  • modelin uyğununu seçə bilməsi üçün gözlənilən formatı və ya icazə verilən dəyərləri açıq təsvir etmək vacibdir;
  • mesaj qısa, formal və strukturlaşdırılmış olmalıdır: error_type, field, expected və ya allowed_values kimi sahələr modelə xeyli kömək edir;
  • mümkünsə minimal düzgün giriş nümunəsi verin — bu, modelin bərpasının dəqiqliyini çox vaxt artırır.

Model üçün ideal error feedback iki faktı ehtiva edir: nə səhv getdi və bunu necə düzəltmək olar.

9. Workflow xətalarının loqlanması və metrikaları

Xəta UX‑i hətta səliqəli olsa belə, əslində nə pozulduğunu anlamaq üçün təkcə istifadəçi mesajları yetmir. Strukturlaşdırılmış loglar və addımlar üzrə metrikalar lazımdır.

Workflow‑un hər addımını loqlayanda minimal faydalı dəst:

  • user_id və ya heç olmasa session_id;
  • workflow_idstep_id;
  • addımın statusu (success, failed, retry, rolled_back);
  • error_code (əgər olubsa);
  • addım xarici çağırışlarla bağlıdırsa, idempotency_keycorrelation_id.

MCP və Agents‑də _meta sahələri var; ora idempotency_keycorrelation_id qoymaq rahatdır ki, həm loqlarda, həm də Inspector‑da görünsün.

Node.js/TypeScript‑də ən sadə loqlama nümunəsi (console istifadə edə bilərsiniz, ya da winston/pino):

function logStepFailure(params: {
  userId?: string;
  workflowId: string;
  stepId: string;
  errorCode: string;
  idempotencyKey?: string;
}) {
  console.error(
    JSON.stringify({
      level: "error",
      event: "workflow_step_failed",
      ...params,
      timestamp: new Date().toISOString(),
    })
  );
}

Belə loqları asanlıqla parse etmək, onlar üzrə dashboardlar qurmaq və hesablamaq mümkündür:

  • addımlar arasında konversiyanı;
  • ən çox rast gəlinən xəta tiplərini;
  • retry ilə bitən addımların payını vs tam uğursuzluq.

Hər bir xəta prodakşnda alert olmamalıdır. Kritiki — MCP‑nin düşməsi, sistematik taymautlar, müəyyən addımda kütləvi uğursuzluqlar — bəli, monitorinqə çıxarılmalıdır. “Hədiyyə axtarışında nəticə yoxdur” isə biznes hadisəsidir, insident deyil.

10. GiftGenius‑u inkişaf etdiririk: dayanıqlı checkout addımı

Gəlin indi hamısını bir yerdə toplayaq: retry‑lar, idempotentlik, Saga, vəziyyətin sinxronizasiyası, xəta UX‑i və loqlama — tədris tətbiqimiz GiftGenius‑un bir addımı — sifarişin rəsmiləşdirilməsi (checkout) üzərində.

Nə artıq var

Bu məqamda artıq:

  • çoxaddımlı workflow var: məlumat toplama → ideyaların seçimi → hədiyyənin seçimi → checkout;
  • tool gating qurulub: checkout addımında yalnız commerce alətləri dəsti əlçatandır (create_order, get_payment_methods və s.);
  • seçilmiş hədiyyə, büdcə, userId və cari addım saxlanan WorkflowContext var.

Bu mühazirədə nə əlavə edəcəyik

Checkout addımı üçün tətbiq edirik:

  1. idempotency_keycreate_order aləti üçün;
  2. ödəniş provayderinin müvəqqəti xətalarında retry;
  3. kompensasiya — qismən uğurlu əməliyyatlarda;
  4. düzgün xəta UX‑i — vidcetedə.

“Ödə” düyməsi basılanda vidcetedə idempotency açarının generasiyası:

import { v4 as uuid } from "uuid";

async function handlePayClick() {
  const idempotencyKey = uuid();
  setWidgetState((s) => ({ ...s, idempotencyKey }));

  await window.openai.tools.call("create_order", {
    userId: widgetState.userId,
    items: [/* ... */],
    idempotencyKey,
  });
}

create_order aləti tərəfində — yuxarıda yazdığımız idempotent handler: açarı və nəticəni saxlayır və təkrarda yeni sifariş yaratmır.

Ödəniş API‑si ilə qarşılıqlı əlaqə kodunu callWithRetry ilə əhatə etmək olar ki, şəbəkə qüsurlarında pulu bir neçə dəfə çıxmağa cəhd etsin. Və xəta üçün retryable: true bayrağını əlavə etməyi unutmayın ki, model təkrarı təklif edə bilsin.

Əgər sifarişin uğurla yaradılmasından və vəsaitin çıxılmasından sonra nəsə qırılırsa (məsələn, xarici webhook vaxtında gəlmir), bunu correlation_idworkflow_id ilə loqlayırıq və sonra:

  • fondakı retry‑ı sınayırıq (gələcək modulda cüelər və hadisələr barədə);
  • və ya addımı açıq şəkildə failed kimi işarələyir, kompensasiya addımlarını çağırır və istifadəçiyə baş verəni izah edirik.

11. Qüsurlara davamlı workflow dizaynında tipik səhvlər

Səhv №1: “Hər şeyi retry edirik, ta ki alınana qədər”.
Hər addımı avtomatik qalibiyyətə qədər təkrarlamaq — özünüz üçün lokal cəhənnəm yaratmağın etibarlı yoludur. Şəbəkə və 5xx xətalarını backoff və cəhd limiti ilə bir daha sınamaq olar. Amma 4xx, biznes xətaları və modelin məntiqi uğursuzluqları verilənlərlə düzəldilməli və ya istifadəçiyə izah edilməlidir. Əks halda qeyri‑stabil davranış, qəribə hesablar və çirklənmiş loqlar alacaqsınız.

Səhv №2: Pul və sifariş olan yerdə idempotentliyin olmaması.
Əgər create_order və ya charge_card kimi alət idempotent deyilsə, hər hansı təkrar çağırış (taymaut, Regenerate, agentdə bug) dublikatlara gətirə bilər. LLM ssenarilərində təkrarlar klassik REST frontend‑dən əhəmiyyətli dərəcədə çox rast gəlinir, ona görə də idempotency_key — “xoş bonus” deyil, ödəniş və digər kritik addımlar üçün mütləq şərtdir.

Səhv №3: Kompensasiya əməliyyatlarının olmaması (Saga yoxdur).
Sifarişi yaratdınız, malı bronladınız, amma ödənişdə yıxıldınız və istifadəçiyə sadəcə “nə isə alınmadı” göstərdiniz. Nəticədə sistemdə yarımçıq sifarişlər, rezervlər, maliyyə “quyruqları” asılı qalır. Xarici dünyanı dəyişən hər addım üçün növbəti addımın uğursuzluğu halında nə edəcəyinizi fikirləşin: ləğv etmək, qaytarmaq, “expired” kimi işarələmək və s.

Səhv №4: Agentin sonsuz retry dövrünə buraxılması.
Cəhd sayını məhdudlaşdırmasanız (məsələn, helper‑lərdə maxRetries ilə və ya agent məntiqində max_iterations ilə) və retry faydasız olan yerdə xətaları retryable: false kimi işarələməsəniz, model “Bir də sınayım… bir də…” deyə dövrə düşə bilər. Bu, tokenləri, vaxtı və əsəbləri yandırır.

Səhv №5: Rollback zamanı UI və model arasında vəziyyət desinxronu.
Tez‑tez developerlər “Geri” düyməsini yalnız UI‑də reallaşdırır, amma addımı backend və model ilə sinxronlaşdırmağı unudurlar. Nəticədə istifadəçi 2‑ci addımı görür, model isə 3‑cü addımda “yaşayır” və qəribə təkliflər verir. Həll — user_navigated_to_step kimi açıq hadisələr və hər keçiddə WorkflowContext‑in yenilənməsi.

Səhv №6: İstifadəçi üçün texniki mesajlar və developerlər üçün logların olmaması.
İstifadəçi “Error: ECONNRESET at TcpSocket.onEnd…” alır, siz isə hansı workflow_id və hansı addım üçün nə pozulduğu barədə sıfır məlumat. Düzgün yanaşma: istifadəçi üçün — qısa, anlaşılan mətn və növbəti addım; developer üçün — workflow_id, step_id, error_code, idempotency_keycorrelation_id ilə strukturlaşdırılmış log.

Səhv №7: Alerlər üzrə strategiyanın olmaması.
Ya hər şeyi aler edirlər, o cümlədən “çox dar filtrlərinizə uyğun hədiyyə tapılmadı”, ya da heç nəyi aler etmirlər, MCP‑nin əsl düşməsi də daxil. Kritiki sistem nasazlıqlarını (servisin düşməsi, kütləvi taymautlar, webhook itkiləri) gözlənilən biznes hadisələrindən ayırmağa çalışın. Birincilər monitorinq və on‑call üçündür, ikincilər isə sadəcə analitikada sayılır.

Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION