CodeGym /Kurslar /ChatGPT Apps /Alət nəticələrinin vidjetdə işlənməsi: ToolOutput → UI

Alət nəticələrinin vidjetdə işlənməsi: ToolOutput → UI

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

1. ToolOutput‑dan React‑komponentinə: ümumi məlumat axını

Ötən mühazirədə server alətinin ToolOutput — model və vidjet üçün strukturlaşdırılmış cavabı — necə formalaşdırdığını araşdırdıq. İndi isə bu yolun ikinci hissəsinə baxaq: həmin ToolOutput vidjetə necə düşür və UI‑a necə çevrilir.

Baş verənləri “sehr” kimi qəbul etməmək üçün gəlin istifadəçidən sizin vidjetinizə qədər olan məlumat yolunu bir daha danışaq. Sadələşdirilmiş şəkildə hər şey belə görünür:

  1. İstifadəçi söhbətdə sual verir.
  2. GPT sorğunu təhlil edir, alətlərin siyahısına baxır və qərar verir: “İndi mənə suggest_gifts kömək edəcək”.
  3. GPT ad və arqumentlərlə (ToolInput) alət çağırışı formalaşdırır və onu sizin serverinizə (MCP və ya backend) göndərir.
  4. Server alətin məntiqini icra edir və nəticəni ToolOutput şəklində qaytarır — məlumatlarla strukturlaşdırılmış JSON, üstəlik model üçün mətn xülasəsi.
  5. ChatGPT ToolOutput‑u alır və onu daha irəli ötürür: modelə (dialoqu davam etdirmək üçün) və Apps SDK vasitəsilə sizin vidjetinizə (window.openai.toolOutput və ya huklar).
  6. Vidjetiniz — adi React‑komponent — toolOutput‑u oxuyur və UI render edir.

Sxematik olaraq bunu belə göstərmək olar:

flowchart TD
  U[Istifadəçi] -->|çat sorğusu| GPT[GPT]
  GPT -->|callTool: suggest_gifts| B[Backend/MCP]
  B -->|"ToolOutput (JSON)"| GPT
  GPT -->|toolOutput-u ötürür| W["Vidjet (React)"]
  W -->|kartlar, siyahılar| U

Vacib məqam: ToolOutput sadəcə “serverin cavabı” deyil. O, həm vidjet üçün çəkmə komandası, həm də eyni zamanda model üçün kontekstdir. Yaxşı App odur ki, bu JSON rahat interfeysə çevrilir, developer tərəfindən DevTools‑da gözdən keçirilməklə kifayətlənmir.

2. ToolOutput‑un anatomiyası: içəridə nə var

Apps SDK‑da alət nəticəsinin formatı üç məntiqi bloka bölünür: structuredContent, content_meta (vidjetdə toolResponseMetadata adı ilə görünür).

Şərti olaraq bunu belə təsəvvür etmək olar:

{
  "structuredContent": { /* UI + model üçün məlumatlar */ },
  "content": "Model və istifadəçi üçün qısa mətn xülasəsi",
  "_meta": { /* yalnız vidjet üçün xidməti məlumatlar */ }
}

Cədvəldə kimin nə gördüyü görünür:

Sahə Kim görür Nə üçün istifadə olunur
structuredContent
Model + vidjet Əsas strukturlaşdırılmış məlumatlar (siyahılar, obyektlər, parametrlər)
content
Model + istifadəçi (mətndə) GPT‑nin cavabına daxil edə biləcəyi qısa xülasə
_meta
Yalnız vidjet Modelə lazım olmayan xidməti məlumatlar (ID‑lər, versiyalar, açarlar və s.)

Apps SDK sənədləri vurğulayır ki, structuredContent / content cütü modelə düşür və onun növbəti cavablarında istifadə oluna bilər. _meta sahəsi isə gizli qalır və yalnız vidjet daxilində toolResponseMetadata vasitəsilə əlçatandır.

GiftGenius üçün ToolOutput nümunəsi

Tutaq ki, bizim suggest_gifts alətimiz serverdə təxminən belə bir bədən qaytarır:

{
  "structuredContent": {
    "items": [
      {
        "id": "boardgame-cozy-strategy",
        "title": "Cozy Strategy Board Game",
        "price": 39.99,
        "currency": "USD",
        "score": 0.92,
        "tags": ["board_game","strategy","2-4_players"]
      }
    ]
  },
  "content": "Bir neçə hədiyyə ideyası tapdım. Aşağıda vidjet onları kartlar şəklində göstərir.",
  "_meta": {
    "giftGenius": {
      "catalogVersion": "2025-10-01",
      "experimentBucket": "A"
    }
  }
}

Burada structuredContent.items — React‑vidjetinizin render edəcəyi hissədir; content model tərəfindən istifadəçiyə nə baş verdiyini izah etmək üçün istifadə oluna bilər; _meta.giftGenius — yalnız UI və ya analitika üçün lazım olan daxili məlumatdır (məsələn, linklər üçün hansı kataloq versiyasından istifadə etmək).

Məhz structuredContent — serverdən ixtiyari JSON‑u əl ilə parçalamaq əvəzinə, JSX‑də baxacağınız obyekt olacaq.

3. Vidjetdə ToolOutput‑u almaq: window.openai və huklar

İndi JSON söhbətlərindən koda keçək. Bu ToolOutput ümumiyyətlə sizin React‑komponentinizə necə düşür?

Apps SDK şablonu bunu iki əsas üsulla edir: ya birbaşa window.openai.toolOutput vasitəsilə, ya da rahat olan hazır React‑hukları (useWidgetProps, useToolOutput və s.) istifadə etməklə. Tövsiyə olunan yanaşma — window.openai‑ya əllə toxunmamaq və daha test edilə bilən, təhlükəsiz kod üçün huklardan istifadə etməkdir.

Ən sadə variant: birbaşa window.openai‑dən

Anlamaq üçün “çılpaq” variantına baxa bilərik:

'use client';

function RawToolOutputDebug() {
  const toolOutput = (window as any).openai?.toolOutput;
  return (
    <pre>{JSON.stringify(toolOutput, null, 2)}</pre>
  );
}

Belə etmək prodakşnda, əlbəttə, məsləhət deyil, amma sazlama və “ilk addımlarda gözlə baxmaq” üçün tam uyğundur.

Praktik variant: React huku ilə

Girişi window.openai‑ya kiçik bir hukuğun içində toplamaq və tipli obyektlə işləmək xeyli rahatdır. Qoy şərti SDK useWidgetProps hukunı versin və o, toolOutputtoolResponseMetadata qaytarsın.

'use client';

import { useWidgetProps } from '@/lib/openai-widget';

export function GiftWidgetRoot() {
  const { toolOutput, toolResponseMetadata } = useWidgetProps();

  // Hələlik sadəcə hədiyyələrin sayını çıxaraq
  const items = toolOutput?.structuredContent?.items ?? [];

  return (
    <div>
      Tapılan hədiyyələr: {items.length}
    </div>
  );
}

Real şablonda hukun adı fərqli ola bilər, amma ideya həmişə eynidir: SDK məlumatları window.openai‑dan götürür və onları komponentinizə props kimi və ya kontekst vasitəsilə verir. Bu, hər dəfə qlobal obyektə əl ilə girməyinizdən qat‑qat asandır və əlavə olaraq, testlərdə məlumat mənbəyini asanlıqla əvəz etməyə imkan verir (məsələn, toolOutput üçün fiktiv məlumat).

4. Hədiyyələri render edirik: structuredContent‑dən JSX‑ə

Keçək əsas hissəyə: structuredContent.items götürək və onlardan kartlar çəkək. Unutmuruq ki, vidjetimiz Next.js‑də adi React‑klient komponentidir (faylın yuxarısında 'use client').

Əvvəlcə bir hədiyyənin tipini təyin edək:

type GiftItem = {
  id: string;
  title: string;
  price: number;
  currency: string;
  tags?: string[];
};

İndi kiçik kart komponenti yazaq:

function GiftCard({ gift }: { gift: GiftItem }) {
  return (
    <div className="gift-card">
      <div className="gift-title">{gift.title}</div>
      <div className="gift-price">
        {gift.price} {gift.currency}
      </div>
    </div>
  );
}

toolOutput‑dan məlumat alan siyahı komponenti:

'use client';

import { useWidgetProps } from '@/lib/openai-widget';

export function GiftList() {
  const { toolOutput } = useWidgetProps();
  const items = (toolOutput?.structuredContent?.items ?? []) as GiftItem[];

  return (
    <div className="gift-list">
      {items.map(gift => (
        <GiftCard key={gift.id} gift={gift} />
      ))}
    </div>
  );
}

Diqqət edin ki, buradakı hər şey adi React koduna çox bənzəyir. Yeganə “sehr” — məlumat mənbəyidir: props və ya fetch əvəzinə toolOutput‑u ChatGPT konteynerindən oxuyuruq.

Və bəli, ilk vaxtlar as GiftItem[] əlavə etsəniz problem deyil. Sonra structuredContent‑i backend ilə ortaq tiplər vasitəsilə dəqiq tipiləşdirmək olar (məsələn, Zod / JSON Schema → TS‑tipləri), amma nümayiş üçün bu da kifayətdir.

5. ToolOutput ətrafında UI vəziyyətləri: yüklənir, boş, xəta

Sadəcə kartları göstərən, digər hallarda isə susan tətbiq çox da rahat deyil. Heç olmasa dörd vəziyyəti aydın emal etmək lazımdır: alət icra olunur, hələ məlumat yoxdur, nəticə var və nəsə səhv getdi.

Apps SDK adətən alət çağırışının statusu barədə müəyyən məlumat verir: alət çağırışlarının siyahısı (useToolInvocations) və ya toolOutput ilə bağlı flaqlar vasitəsilə. Bu mühazirədə sadə model kifayətdir: əgər toolOutput hələ yoxdursa — deməli “yüklənir”; varsa, amma siyahı boşdursa — “boş”; xəta gəlibsə — “xəta”.

Sadəlik üçün güman edək ki, server xəta olduqda structuredContent daxilində error sahəsini doldurur, həmçinin toolOutput kökündəki ok flağı false olur. Bu sxemi server reallaşdırması mövzusunda cavab müqaviləsini layihələndirərkən artıq müzakirə etmişdik.

type ToolOutput = {
  ok: boolean;
  structuredContent?: {
    items?: GiftItem[];
    error?: { code: string; message: string };
  };
};

İndi siyahı komponentimizi yeniləyək:

'use client';

import { useWidgetProps } from '@/lib/openai-widget';

export function GiftListWithStates() {
  const { toolOutput } = useWidgetProps() as { toolOutput?: ToolOutput };

  if (!toolOutput) {
    return <div>Hədiyyələr seçilir…</div>;
  }

  if (!toolOutput.ok) {
    const msg = toolOutput.structuredContent?.error?.message
      ?? 'Tövsiyələri almaq mümkün olmadı.';
    return <div>Xəta: {msg}</div>;
  }

  const items = toolOutput.structuredContent?.items ?? [];

  if (items.length === 0) {
    return <div>Sizin şərtlərinizə uyğun hədiyyə tapılmadı. Parametrləri dəyişməyə çalışın.</div>;
  }

  return (
    <div className="gift-list">
      {items.map(gift => (
        <GiftCard key={gift.id} gift={gift} />
      ))}
    </div>
  );
}

Belə kod artıq istifadəçiyə adekvat təcrübə verir:

  • Alət işlədiyi müddətdə görünür ki, nə isə baş verir.
  • Hər şey çökərsə — boş ekran əvəzinə anlaşılan mesaj görünür.
  • Heç nə tapılmadıqda — bunu norma kimi göstərmirik, baş verəni səmimi şəkildə izah edirik.

Prodakşnda çox güman “Hədiyyələr seçilir…” mətnini kiçik skeleton və ya spinner ilə əvəz edəcəksiniz. Mürəkkəb xətalarda GPT‑yə insan‑oxunaqlı izah formalaşdırmaq imkanı da vermək olar. Amma komponentlərin baza strukturu eyni qalacaq.

6. UI‑də _meta və toolResponseMetadata‑dan istifadə

Artıq structuredContent‑dən əsas məlumatları render etməyi və loading/empty/error kimi baza vəziyyətlərini emal etməyi öyrəndik. Modelin istifadə etmədiyi ToolOutput hissəsi — _meta — qaldı.

_meta sahəsinə qayıdaq. O, modelə görünmür, amma vidjetinizə toolResponseMetadata kimi gəlir (adı fərqli ola bilər, mahiyyət eynidir).

Bu, GPT‑nin məntiqinə təsir etməməli, amma UI üçün vacib olan hər şey üçün əla yerdir:

  • kataloq və ya konfiqurasiya versiyaları;
  • kampaniyanın / A/B eksperimentinin daxili ID‑si;
  • istifadəçiyə hansı “düymələri” göstərmək barədə flaqlar;
  • domen məlumatları ilə qarışdırmaq istəmədiyiniz hər cür texniki şeylər.

Məsələn, server belə _meta qaytara bilər:

"_meta": {
  "giftGenius": {
    "catalogVersion": "2025-10-01",
    "showExperimentalBadges": true
  }
}

Vidjet bunu oxuyub, məsələn, bəzi kartlarda “Yeni ideya” beyci çəkə bilər.

type GiftMeta = {
  giftGenius?: {
    catalogVersion: string;
    showExperimentalBadges?: boolean;
  };
};

export function GiftListWithMeta() {
  const { toolOutput, toolResponseMetadata } = useWidgetProps() as {
    toolOutput?: ToolOutput;
    toolResponseMetadata?: GiftMeta;
  };

  const meta = toolResponseMetadata?.giftGenius;
  const items = toolOutput?.structuredContent?.items ?? [];

  return (
    <div>
      {meta && (
        <div className="catalog-version">
          Kataloq: {meta.catalogVersion}
        </div>
      )}
      <div className="gift-list">
        {items.map(gift => (
          <GiftCard
            key={gift.id}
            gift={gift}
          />
        ))}
      </div>
    </div>
  );
}

Model burada ümumiyyətlə işə qarışmır: o, catalogVersionshowExperimentalBadges barədə heç nə bilmir, ancaq UI onları istədiyi kimi istifadə edə bilər.

Sənədlər məhz bu bölgünü vurğulayır: dialoq və modelin mühakiməsi üçün vacib olan məlumatları structuredContentcontent‑ə qoyuruq; yalnız UI‑texniki olan hər şeyi isə _meta / toolResponseMetadata‑ya.

7. ToolInvocation statusları və “X icra olunur…” haqqında bir az

Alət işləyərkən, ChatGPT özü istifadəçiyə nə baş verdiyini göstərir: söhbətin yuxarısında “GiftGenius icra olunur…” və ya “Xarici tətbiqə müraciət edilir” kimi status görünür. Bunları siz əl ilə çıxarmırsınız, alət çağırışının metadatasına reaksiya verən ChatGPT host mühiti göstərir.

Pərdəarxasında bu, aşağıdakı kimi xidməti açarlarla təsvir olunur: _meta["openai/toolInvocation/invoking"]_meta["openai/toolInvocation/invoked"], yəni əməliyyatın icrada və ya tamamlandığını siqnallaşdırır. Bu sahələr platformanın özü tərəfindən statusu göstərmək üçün istifadə olunur və, adətən, onlara toxunmağa ehtiyac yoxdur: SDK bunu server tərəfində sizin yerinizə edir.

UX baxımından bu xoş bir bonusdur: vidjet hələ skeleton çəkməyə macal tapmasa belə, istifadəçi sistemin nəsə etdiyini görür. Sizin vəzifəniz — bu qlobal statusu “Hədiyyələr seçilir…” və vidjetdə skeleton kimi lokal vəziyyətlərlə tamamlamaqdır, yuxarıda etdiyimiz kimi.

8. Məlumatın həcmi və performans: bütün dünyanı structuredContent‑ə daşımırıq

Ayrıca bir mövzuya da toxunaq: “bəs structuredContent‑ə ümumiyyətlə nə qədər şey qoymaq olar”. İnkişaf etdirici üçün cazibədar görünə bilər: “Axı məndə hədiyyələrin bütün kataloqu var — hamısını qaytaraq, vidjet özü filtrləyər”. Praktikada belə etmək lazım deyil.

Birincisi, structuredContent modelin (LLM) kontekstinə düşür və ümumi token həcmi məhduddur. Sənədlər və praktik bələdçilər israrla həcmi səliqəli saxlamağı məsləhət görür: bu, məlumat anbarı deyil, bir hərəkətin nəticəsidir.

İkincisi, payload nə qədər böyükdürsə, cavab bir o qədər gec gələcək və limitlərə ilişmək, gözlənilməz kəsilmələr/xətalar almaq ehtimalı o qədər artacaq.

Məntiqli yanaşma belədir:

  • Backend məlumatları əvvəlcədən filtrləyib sıralayır və cari addım üçün lazım olanı qaytarır: məsələn, 10–20 ən yaxşı hədiyyə.
  • Növbəti səhifələr lazımdırsa, bu ayrı bir əməliyyatdır (yeni alət çağırışı, yeni ToolOutput).
  • Yalnız UI üçün olan şeylər (məsələn, filtr üçün bütün mümkün teqlər) üçün _meta istifadə oluna bilər, amma ölçünü yenə də nəzarətdə saxlayın.

Vəziyyət modulunda artıq “backend — həqiqətin mənbəyidir, vidjet — keş/təqdimatdır” konsepsiyasını müzakirə etmişdik. Burada da eyni şeydir: alətin nəticəsi çağırış anındakı vəziyyətin səliqəli “kəsiyi”dir, bazanızın tam surəti deyil.

9. Vidjet vəziyyəti və növbəti dialoqla əlaqə

Bu mühazirə rəsmi olaraq ToolOutput → UI haqqındadır, amma yanaşıda daha bir vacib hissə var — widgetState. Məhz o, renderlər arasında istifadəçinin seçimlərini yadda saxlamağa və vidjetinizi sadəcə vitrin deyil, tam funksional usta və ya “hədiyyə konfiquratoru” etməyə imkan verir.

Tipik ssenari belə görünür:

  1. İlk ToolOutput hədiyyələr siyahısını gətirir.
  2. İstifadəçi kartlardan birinə klikləyir.
  3. Vidjet seçilmiş hədiyyəni widgetState‑ə yazır və mümkündürsə, detallara görə follow‑up və ya yeni alət çağırışı göndərir.
  4. Növbəti ToolOutput‑lar bu seçimi nəzərə alır.

Kod baxımından bu, adi React state + setWidgetState çağırışı kimidir; o seçim ChatGPT tərəfində saxlanılır. Fərq yalnız ondadır ki, bu vəziyyət həm model, həm də backend üçün əlçatandır, ona görə də onu yığcam saxlamaq və orada sirrləri saxlamamaq lazımdır.

Bunu çoxaddımlı iş axınları və follow‑up‑lar mövzularında ətraflı müzakirə edəcəyik. Artıq indi belə düşünmək faydalıdır: ToolOutput sizə serverdən “məlumat kəsiyi” verir, widgetState isə bu kəsik ətrafında istifadəçi seçiminin kontekstini.

ToolOutput → UI ilə işləyərkən tipik səhvlər

Səhv №1: “UI istifadəçi üçün adaptasiya etmədən xam JSON ağacını render edir”.
Bəzən sazlama üçün sadəcə <pre>{JSON.stringify(toolOutput)}</pre> etmək və orada dayanmaq istəyirsən. İnkişaf zamanı bu normaldır, amma prodakşnda istifadəçi sizin qürur duyduğunuz, amma anlamadığı strukturu görür. structuredContent‑i mümkün qədər tez mənalı komponentlərə (siyahılar, kartlar, cədvəllər) bükmək, serverin tokenləşdirilmiş cavabını oxutdurmamaq vacibdir.

Səhv №2: Domen məlumatları ilə texniki metadatanın structuredContent‑də qarışdırılması.
“Model və istifadəçi üçün görünməli olan” ilə “yalnız UI və analitika üçün lazım olan”ı ayırdıqda kod daha təmiz olur. Texniki sahələrin — eksperimental flaqlar, kataloq versiyaları, idempotency key və s. — yeri _meta / toolResponseMetadata‑dır. Bütün bunlar structuredContent daxilində qarışanda müqaviləni inkişaf etdirmək və model davranışını test etmək çətinləşir.

Səhv №3: Yüklənmə, boş nəticə və xətalar üçün aydın vəziyyətlərin olmaması.
“Tapılmadı” və ya “Nəsə səhv getdi” əvəzinə boş <div></div> — istifadəçinin “Tətbiq işləmir” deməsinə birbaşa yoldur. Minimal mətn plaseholderləri və sadə skeleton belə UX‑i ciddi şəkildə yaxşılaşdırır. Təkcə ChatGPT‑nin “X icra olunur…” sistem statusuna güvənməyin — vidjet də özündə nə baş verdiyini deməlidir.

Səhv №4: Bir ToolOutput‑a bütün dünyanı sığışdırmaq cəhdi.
Bütün mallar kataloqunu, istifadəçi tarixçəsini və üstəlik server loqlarını bir structuredContent daxilində qaytarmaq pis fikirdir. Bu, model limitlərinə dəyir, cavabı ləngidir və UI‑ı çətinləşdirir. Cari addım üçün lazım olan həcmdə məlumatları qaytarmaq (siyahı səhifəsi, seçilmiş elementin detallar və s.) daha yaxşıdır, növbəti addımları isə ayrı alət çağırışları şəklində tərtib edin.

Səhv №5: Tiplər olmadan qeyri‑sabit cavab formasına sərt bağlama.
Əgər kodun hər yerində sahələrin mövcudluğunu yoxlamadan və tiplər olmadan toolOutput.structuredContent.items[0].whatever kimi yazsanız, serverdə sxemin istənilən təkamülü vidjetin çökməsinə gətirəcək. Ya tipləri JSON Schema ilə sinxronlaşdırın (TS‑tiplərin generasiyası), ya da ən azı əl ilə interfeysləri təsvir edin (GiftItem, ToolOutput) və optional sahələrlə diqqətlə işləyin.

Səhv №6: _meta‑nı görməməzlikdən gəlmək və modeli “artıq” sahələrlə yükləmək.
Bəzən “orada JSON‑dur, artıq olmaz” deyib hər şeyi structuredContent‑ə atmaq cazibədardır. Amma hər sahə modelin kontekstini böyüdür və bir çox şeylər modelə ümumiyyətlə lazım deyil. Məlumat GPT‑nin mühakiməsinə təsir etməməli və mətni cavabda lazım deyilsə, onu _meta‑ya qoyun və yalnız vidjetdə işləyin.

Səhv №7: Onlarla komponentdən birbaşa window.openai‑a müraciətlər.
Bəli, window.openai.toolOutput işləyir, amma tətbiqin yarısı qlobal dəyişənə girməyə başlayanda sazlama və test etmə əzaba çevrilir. Bunu bir dəfə huk/kontekstdə (useWidgetProps/useToolOutput) bükmək və sonra normal props və tipli obyektlərlə istifadə etmək daha yaxşıdır. Bu həm daha təmizdir, həm də Storybook/testlərdə fiktiv məlumatlarla daha asan əvəz olunur.

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