1. Czym jest kontekst workflow i po co w ogóle jest potrzebny
W typowej aplikacji webowej dość jasno wiadomo, gdzie „żyje” stan: baza danych, cache oraz coś na froncie, jak Redux lub lokalny stan React. W ChatGPT App jest ciekawiej: stan jest rozłożony na trzy światy – wewnątrz modelu (historia dialogu), wewnątrz widżetu (stan UI) oraz na twoim serwerze/MCP (dane biznesowe).
Przez kontekst workflow będziemy rozumieć cały zestaw danych potrzebnych do odpowiedzi na pytania „na którym jesteśmy kroku” i „co już wiadomo”. Jeśli mówimy o naszym edukacyjnym GiftGenius, w skład kontekstu wchodzą:
- profil odbiorcy prezentu: wiek, płeć, zainteresowania;
- budżet i ewentualnie waluta;
- lista wygenerowanych pomysłów oraz informacja, które z nich użytkownik polubił lub ukrył;
- techniczne rzeczy: identyfikator sesji lub workflow, status („profile_collected”, „ideas_shown”, „checkout_started”).
Ten kontekst jest potrzebny nie tylko tobie jako programiście backendu. Jest potrzebny samej modelowi, aby rozumiała, jakie pytania już padły, jakie narzędzia zostały wywołane i o czym właściwie teraz mowa. Jest też potrzebny użytkownikowi, by po powrocie do czatu nie zaczynać wszystkiego od zera.
Użytkownik intuicyjnie myśli, że „ChatGPT wszystko pamięta”. W rzeczywistości model pamięta tylko tekst dialogu – i to dopóki mieści się on w oknie kontekstu. Strukturalne rzeczy, jak order_id, cart_id czy „lista polubionych pomysłów”, trzeba przechowywać na serwerze, w przeciwnym razie otrzymasz idealną maszynę do generowania pewnych, ale błędnych stwierdzeń.
2. Trzy poziomy stanu: UI, LLM, biznes
Najwygodniej rozumieć zapisywanie kontekstu przez model trzech warstw stanu. To właśnie „State Triad”.
Tabela poziomów
Użyjmy małej tabeli:
| Poziom | Gdzie żyje | Cykl życia | Za co odpowiada | Przykład w GiftGenius |
|---|---|---|---|---|
| UI State | Widżet (React, widgetState) | Dopóki otwarty jest czat / wiadomość z widżetem | Stan wizualny, lokalne dane wejściowe | Które karty są podświetlone, stan formularza |
| LLM Context | Historia czatu w OpenAI | Dopóki wiadomość „mieści się” w kontekście | Rozumienie dialogu i rozumowanie | „Szukamy prezentu dla mamy, budżet $50” |
| Business State | MCP / twój backend (baza danych/Redis) | Tak długo, jak chcesz (trwale) | Prawda: zweryfikowane dane, statusy | { step: "ideas", budget: 50, liked: [42, 51] } |
Warstwa UI jest szybka i responsywna, ale bardzo krucha: ChatGPT może „odmontować” iframe z widżetem, gdy przewiniesz historię do góry, a potem zamontować go ponownie. Właśnie do tego służy widgetState, który żyje nieco dłużej niż komponent React i synchronizuje się z host-klientem ChatGPT.
Warstwa LLM daje modelowi poczucie ciągłego dialogu, lecz przechowuje tylko tekst i wywołania narzędzi. Można tam włożyć JSON z twoim koszykiem, ale to w istocie tylko wstawienie JSON-a do tekstu – model nie potraktuje tego jak bazy danych.
Warstwa biznesowa to coś, co możesz kontrolować jako inżynier: tam znajdują się zwalidowane dane, indeksy, statusy zamówień. Gdy tylko pojawia się poważny scenariusz (prezenty, rezerwacje, nauka), właśnie ta warstwa powinna stać się głównym źródłem prawdy o stanie.
Główny problem inżynierski – by te trzy warstwy nie rozjechały się w różne strony. Użytkownik w widżecie zmienił budżet, model wciąż myśli o starym, a w bazie leży trzecia wartość – to klasyczny przepis na dziwne zachowanie.
3. Co dokładnie zapisujemy: struktura WorkflowContext
Aby mówić konkretnie, opiszmy w TypeScript interfejs kontekstu dla GiftGenius. Załóżmy, że mamy już kilka kroków: zbieranie profilu, wybór budżetu, generowanie pomysłów oraz przeglądanie/polubienia.
Zacznijmy od prostej struktury:
// backend/types/workflow.ts
export type GiftWorkflowStep =
| "profile"
| "budget"
| "ideas"
| "checkout";
export interface GiftWorkflowContext {
id: string; // workflowId - identyfikator scenariusza
userId?: string; // jeśli uwierzytelnianie jest już skonfigurowane
currentStep: GiftWorkflowStep;
profile?: {
age?: number;
gender?: string;
interests?: string[];
};
budget?: {
min?: number;
max?: number;
currency: string;
};
ideas?: {
id: string;
title: string;
}[];
likedIdeaIds: string[];
hiddenIdeaIds: string[];
updatedAt: number; // timestamp do TTL/czyszczenia
}
To nie jest ostateczny schemat, ale kluczowe elementy są już na miejscu. Mamy:
- identyfikator workflow, według którego będziemy wyszukiwać ten kontekst;
- bieżący krok, który pomoże i widżetowi, i modelowi zrozumieć, dokąd doszliśmy;
- zestaw pól, które będą wypełniane na poszczególnych krokach;
- pola serwisowe, jak czas aktualizacji.
Osobno o identyfikatorach. W tej lekcji przez workflowId rozumiemy identyfikator konkretnego scenariusza w naszym backend/MCP. Może on pokrywać się z identyfikatorem sesji dialogu ChatGPT (sessionId), ale nie polegamy na tym. userId to identyfikator użytkownika z twojego systemu uwierzytelniania (jeśli istnieje); jeden użytkownik może mieć kilka aktywnych workflows. W polu id znajduje się właśnie ten workflowId, według którego wyszukujemy i aktualizujemy kontekst.
W kolejnych sekcjach omówimy trzy rzeczy: gdzie przechowywać takie obiekty, jak je tam zapisywać i jak je pobierać z powrotem – zarówno do widżetu, jak i do modelu.
4. Gdzie przechowywać stan: opcje i kompromisy
O przechowywaniu stanu warto myśleć w dwóch płaszczyznach: gdzie jest przechowywany i jak długo żyje. W tym rozdziale skupimy się na miejscu przechowywania, a do czasu życia wrócimy jeszcze w checkliście i bloku o typowych błędach.
Najpierw ustalmy miejsce przechowywania.
Wewnątrz dialogu (w promptcie)
Czasem kusi, by powiedzieć: „Po prostu za każdym razem zwracajmy modelowi JSON z aktualnym stanem i niech sama się połapie”. Działa to dla bardzo prostych scenariuszy i krótkich łańcuchów kroków, ale szybko zderza się z dwoma problemami: ograniczeniem długości kontekstu i brakiem jakichkolwiek gwarancji spójności danych.
Poza tym protokół MCP z natury jest stateless: jak HTTP, domyślnie nie przechowuje żadnego stanu między żądaniami. Aby powiązać wywołanie toola z konkretną sesją, musisz jawnie przekazywać identyfikator – workflow lub session id – albo w argumentach narzędzia, albo przez metadane/nagłówki.
Dlatego przechowywanie stanu biznesowego wyłącznie w dialogu to raczej eksperyment edukacyjny niż architektura.
W widżecie: UI + widgetState
Na poziomie UI korzystamy ze zwykłego stanu React (useState, useReducer itd.), ale – jak już było powiedziane – komponent może zostać odmontowany. W Apps SDK istnieje w tym celu mechanizm widgetState, który żyje poza React i synchronizuje się z hostem ChatGPT. Jeśli przy montowaniu widżetu pobierasz stamtąd zachowaną wartość, a przy zmianach – odkładasz ją z powrotem, otrzymujesz lokalne, ale całkiem wygodne miejsce na stan.
To miejsce świetnie nadaje się do czysto wizualnego stanu: które karty są teraz zwinięte, na której karcie (tabie) jesteś, co użytkownik wpisał w formularzu zanim nacisnął „Dalej”. Nie zastąpi jednak serwera: gdy tylko użytkownik otworzy czat na innym urządzeniu albo po tygodniu, widgetState może już nie pomóc. Budowanie na nim logiki biznesowej to dyskusyjna decyzja.
Na serwerze/MCP: Map, Redis, baza danych
Wreszcie główna opcja produkcyjna: przechowujemy GiftWorkflowContext po stronie serwera MCP lub usługi backendowej. Ponieważ klient i serwer MCP są z definicji stateless, musimy przekazywać workflowId (albo state_token) w każdym wywołaniu narzędzia, aby wiedzieć, który kontekst zaktualizować.
Opcji implementacyjnych jest kilka:
- in-memory Map w Node.js – dobre do demo i środowiska deweloperskiego: szybko, ale znika po restarcie;
- Redis lub inny cache in-memory z TTL – dobry dla krótkich scenariuszy typu wizard (kilka kroków): żyje godzinę–dwie, potem można usunąć;
- zwykła baza SQL/NoSQL – obowiązkowa dla scenariuszy w stylu „wrócił po tygodniu” lub „wersje robocze i koszyki”.
W tym wykładzie nie będziemy wchodzić w konkretną bazę danych, skupimy się na interfejsie i zrozumieniu, co dokładnie powinno tam trafiać.
5. Najprostsze storage w serwerze MCP: Map według workflowId
Zacznijmy od czegoś przyziemnego: zwykła in-memory Map w serwerze MCP, gdzie kluczem jest workflowId. W edukacyjnym demo można go po prostu zrównać z sessionId dialogu, ale w produkcji lepiej trzymać workflowId jako osobny identyfikator scenariusza. Wartością w tej Map będzie GiftWorkflowContext. W realnej produkcji zastąpisz to Redisem lub bazą danych, ale API pozostanie takie samo.
Załóżmy, że mamy serwer MCP w TypeScript. Dodajmy gdzieś obok inicjalizacji:
// mcp/workflowStore.ts
import { GiftWorkflowContext } from "../backend/types/workflow";
const workflows = new Map<string, GiftWorkflowContext>();
export function getWorkflow(id: string): GiftWorkflowContext | undefined {
return workflows.get(id);
}
export function saveWorkflow(ctx: GiftWorkflowContext): void {
workflows.set(ctx.id, { ...ctx, updatedAt: Date.now() });
}
Dalej – narzędzie, które zapisuje profil odbiorcy. Ważne, że przyjmuje workflowId i dane profilu, a wewnątrz aktualizuje/tworzy odpowiedni kontekst:
// mcp/tools/setProfile.ts
import { jsonSchema } from "@modelcontextprotocol/sdk"; // alias
import { getWorkflow, saveWorkflow } from "../workflowStore";
export const setProfileTool = {
name: "gift_set_profile",
description: "Zapisuje profil odbiorcy prezentu",
inputSchema: jsonSchema.object({
workflowId: jsonSchema.string(),
age: jsonSchema.number().optional(),
gender: jsonSchema.string().optional(),
interests: jsonSchema.array(jsonSchema.string()).optional()
}),
async run(input: any) {
const existing = getWorkflow(input.workflowId);
const ctx = existing ?? {
id: input.workflowId,
currentStep: "profile",
likedIdeaIds: [],
hiddenIdeaIds: []
};
ctx.profile = {
age: input.age,
gender: input.gender,
interests: input.interests ?? []
};
ctx.currentStep = "budget";
saveWorkflow(ctx);
return {
structuredContent: {
type: "profileSaved",
workflowId: ctx.id,
profile: ctx.profile,
nextStep: ctx.currentStep
}
};
}
};
To narzędzie rozwiązuje już dwa zadania: zapisuje profil i przesuwa currentStep do następnego kroku. W prawdziwym projekcie być może zechcesz rozdzielić narzędzia „zapisz dane” i „przejdź do kroku”, ale dla zrozumienia koncepcji taki wariant wystarczy.
Zwróć uwagę na workflowId w argumentach: to właśnie ten parametr wiąże wywołanie toola z właściwym kontekstem. Część kliencka (widżet lub agent) musi go gdzieś przechowywać i przekazywać dalej.
6. Powiązanie z Apps SDK: skąd wziąć workflowId i sessionId
Pytanie „skąd wziąć workflowId” w ChatGPT Apps jest nieco filozoficzne. Możliwości zależą od tego, czy używasz uwierzytelniania, MCP bezpośrednio czy Agents SDK. Ogólnie są dwie opcje: generacja po stronie serwera przy pierwszym wywołaniu narzędzia albo generacja w widżecie i przekazanie w dół.
Dla przykładu edukacyjnego przyjmijmy, że pierwszy krok to wywołanie narzędzia MCP, które tworzy workflow, a widżet następnie tylko przejmuje jego id.
Najprostszy wariant:
// mcp/tools/startWorkflow.ts
import { randomUUID } from "crypto";
import { saveWorkflow } from "../workflowStore";
export const startWorkflowTool = {
name: "gift_start_workflow",
description: "Tworzy nowy workflow doboru prezentu",
inputSchema: { type: "object", properties: {} },
async run() {
const id = randomUUID();
saveWorkflow({
id,
currentStep: "profile",
likedIdeaIds: [],
hiddenIdeaIds: [],
updatedAt: Date.now()
});
return {
structuredContent: {
type: "workflowStarted",
workflowId: id,
currentStep: "profile"
}
};
}
};
Następnie model, otrzymawszy workflowId w odpowiedzi narzędzia, może:
- utrzymać go w ukrytej formie w kontekście;
- przekazać go do widżetu przez structuredContent, aby widżet zapisał tę wartość w widgetState i zaczął ją podstawiać przy kolejnych wywołaniach narzędzi.
Po stronie widżetu kod będzie wyglądał mniej więcej tak.
7. Przechowywanie workflowId i lokalnego stanu UI w widżecie
Załóżmy, że mamy widżet listy pomysłów, który chce wiedzieć, jaki workflow wyświetla, i pamiętać lokalne polubienia nawet wtedy, gdy komponent zostanie odmontowany. W uproszczeniu:
// app/widgets/GiftIdeasWidget.tsx
import { useEffect, useState } from "react";
interface Idea {
id: string;
title: string;
}
interface WidgetProps {
widgetId: string;
workflowId: string; // przyszło ze structuredContent
ideas: Idea[];
}
interface UiState {
liked: string[];
}
export function GiftIdeasWidget(props: WidgetProps) {
const [uiState, setUiState] = useState<UiState>({ liked: [] });
useEffect(() => {
window.openai.getWidgetState<UiState>(props.widgetId).then(saved => {
if (saved) setUiState(saved);
});
}, [props.widgetId]);
function toggleLike(id: string) {
const exists = uiState.liked.includes(id);
const next: UiState = {
liked: exists
? uiState.liked.filter(x => x !== id)
: [...uiState.liked, id]
};
setUiState(next);
window.openai.setWidgetState(props.widgetId, next);
// tutaj można wywołać MCP-tool "gift_like_idea"
}
return (
<ul>
{props.ideas.map(idea => (
<li key={idea.id}>
{idea.title}
<button onClick={() => toggleLike(idea.id)}>
{uiState.liked.includes(idea.id) ? "★" : "☆"}
</button>
</li>
))}
</ul>
);
}
Tutaj widgetState pełni rolę warstwy UI: pamiętamy, które pomysły są podświetlone. Dobrą praktyką jest również wysłanie polubień na serwer (przez MCP-tool lub endpoint API w Next.js), aby warstwa biznesowa też wiedziała, co użytkownik wybrał.
Ważne, by nie próbować budować całego workflow na widgetState. To powinien być dodatkowy poziom nad kontekstem biznesowym na serwerze.
8. Wznowienie scenariusza: użytkownik wrócił
Przejdźmy teraz do ciekawszego przypadku: użytkownik zamknął ChatGPT, wrócił po kilku godzinach lub dniach i ponownie otworzył ten sam czat. Co powinno się stać?
Idealny UX wygląda tak: model i App rozumieją, że użytkownik ma już niedokończony workflow, pobierają jego kontekst i mówią coś w stylu: „Podałeś już profil i budżet, przejdźmy do wyboru pomysłów”.
Architektonicznie wygląda to tak:
- Na serwerze przechowujesz GiftWorkflowContext powiązany z jakimś userId lub przynajmniej wewnętrznym workflowId.
- Przy nowym żądaniu (lub pierwszym wywołaniu narzędzia w ramach dialogu) App pyta serwer: „Czy istnieje dla tego użytkownika aktywny workflow?”.
- Jeśli tak, serwer zwraca go i ewentualnie specjalną flagę resume, którą model wykorzystuje w swojej wypowiedzi.
W prostym monolitycznym demo można założyć, że serwer MCP i aplikacja Next.js żyją w jednym repozytorium (lub nawet procesie), dlatego po prostu współdzielimy to samo workflowStore między MCP a endpointami API.
W Next.js może to być prosty endpoint API:
// app/api/gift/workflow/route.ts
import { NextRequest, NextResponse } from "next/server";
import { getWorkflow } from "@/mcp/workflowStore"; // w tym demo MCP i Next.js współdzielą to samo miejsce przechowywania
export async function GET(req: NextRequest) {
const id = req.nextUrl.searchParams.get("workflowId");
if (!id) return NextResponse.json({ error: "Missing workflowId" }, { status: 400 });
const ctx = getWorkflow(id);
if (!ctx) return NextResponse.json({ exists: false });
return NextResponse.json({
exists: true,
context: ctx
});
}
Widżet (lub MCP-tool) może wywołać ten endpoint, gdy potrzebuje zaktualizować stan: na przykład przy pierwszym montowaniu lub przy przełączaniu kroku. W konfiguracji edukacyjnej wystarczy para workflowId + storage w Map; w prawdziwej produkcji dodasz autoryzację i weryfikację przynależności do użytkownika.
Jeśli używasz Agents SDK lub bardziej złożonej orkiestracji, pomysł można rozszerzyć do „checkpointów” – zapisywania stanu na końcach dużych kroków, od których agent może kontynuować po restarcie. To jednak temat następnego modułu.
9. Ruch do przodu i wstecz oraz historia kroków
Nieuchronnie pojawia się pytanie: „Czy można wrócić o krok?”. Dla użytkownika to bardzo naturalne: zmienić budżet, poprawić zainteresowania, usunąć zbędny element z propozycji.
Technicznie oznacza to dwie rzeczy:
- trzeba przechowywać nie tylko bieżący krok, ale też historię podjętych decyzji;
- należy ostrożnie przeliczać dane pochodne po cofnięciu.
Jedna z opcji – dodać do kontekstu pole history, które będzie zawierać migawki kroków. Na przykład:
export interface StepSnapshot {
step: GiftWorkflowStep;
payload: any; // konkretne dane kroku
createdAt: number;
}
export interface GiftWorkflowContext {
// ...wcześniejsze pola
history: StepSnapshot[];
}
Gdy użytkownik uzupełnia profil, dodajesz do historii migawkę step: "profile". Gdy zmienia budżet – kolejną. Przy cofnięciu do profilu:
- aktualizujesz currentStep = "profile";
- opcjonalnie przycinasz historię do potrzebnego indeksu;
- przeliczasz dane pochodne (np. czyścisz pomysły i polubienia, jeśli zależą od budżetu).
Na poziomie modelu ważna jest synchronizacja: jeśli użytkownik nacisnął w widżecie przycisk „Wstecz”, trzeba wysłać wywołanie narzędzia, które zaktualizuje kontekst biznesowy i zwróci w odpowiedzi jawny opis nowego stanu. W przeciwnym razie dostaniesz klasyczny problem rozjazdu: UI pokazuje krok 2, a model jest przekonany, że jesteś na kroku 3.
Na poziomie widżetu cofnięcie może wyglądać jak prosty przycisk:
async function goBackToProfile() {
await fetch("/api/gift/workflow/back", {
method: "POST",
body: JSON.stringify({ workflowId, targetStep: "profile" })
});
// aktualizujemy UI, czyścimy lokalny stan
}
A już serwer zdecyduje, co dokładnie wyczyścić w kontekście i jaką wiadomość wysłać modelowi w odpowiedzi narzędzia.
10. Jak to powiązać z modelem: kontekst dla rozumowania
Wszystko, co robimy ze stanem, jest ostatecznie potrzebne nie tylko użytkownikowi, ale i LLM. Model powinien rozumieć:
- co już wiadomo (np. profil odbiorcy i budżet);
- które kroki zostały już wykonane;
- czy istnieją niedokończone procesy.
Sposób dostarczenia tej informacji do modelu zależy od architektury App: możesz wstrzykiwać ją w system-prompt, zwracać w ToolOutput w formie strukturalnej lub używać specjalnych pól _meta/annotations, jeśli są wspierane przez SDK.
Typowy wzorzec jest taki:
- Narzędzie MCP zwraca w structuredContent krótki zrzut kontekstu: bieżący krok, kluczowe pola i ewentualnie workflowId.
- Apps SDK zamienia to na widżet lub tekst + ukryte dane.
- Model, widząc structuredContent, rozumie, że scenariusz jest kontynuowany, i buduje kolejne działanie w oparciu o to.
W niektórych sytuacjach, jeśli model „zapomniał” ważne parametry lub zaczął halucynować, możesz wymusić odświeżenie kontekstu: wywołać specjalne narzędzie, które zwróci aktualny stan, i model „ponownie wejdzie w kontekst”.
Ważne, by nie próbować wkładać do modelu całego GiftWorkflowContext do ostatniego pola. Wystarczy kilka kluczowych rzeczy: komu szukamy prezentu, jaki budżet, ile pomysłów już pokazano, czy jest niedokończony checkout.
11. Mini-checklista przy projektowaniu WorkflowContext
Zanim przejdziesz do typowych błędów, warto sformułować krótki zestaw pytań, na które powinieneś sobie odpowiedzieć podczas projektowania kontekstu workflow (możesz dosłownie zapisać to obok interfejsu):
- Jakie kroki ma scenariusz i jaki minimalny zestaw danych jest potrzebny na każdym?
To ochroni cię przed potworami JSON „na wszelki wypadek”. - Co trzeba pamiętać tylko w ramach jednego czatu, a co – między sesjami i urządzeniami?
To pierwsze można zostawić w widgetState i promptach, drugie koniecznie wysyłać do bazy danych na serwerze. - Jak będzie wyglądał identyfikator kontekstu?
Może to być para userId + scenario, osobny workflowId lub jedno i drugie. Najważniejsze, byś mógł jednoznacznie znaleźć kontekst w bazie. - Jak będziesz czyścić stare workflows?
W demo dopuszczalne jest „nigdy nie czyścić”, ale w produkcji będziesz potrzebować albo TTL, albo zadań w tle, które usuwają stare workflows. - Czy użytkownik potrzebuje cofania i jak je zrealizujesz?
Czy będziesz przechowywać drzewo gałęzi, czy wystarczy liniowa lista kroków z możliwością cofnięcia.
I na koniec: spróbuj w głowie przeprowadzić scenariusz „użytkownik wrócił po tygodniu w innym czacie”. Jeśli nie potrafisz wytłumaczyć, jak App dowie się o starym workflow i co mu pokaże, trzeba wzmocnić część z trwałym przechowywaniem.
12. Typowe błędy przy pracy z kontekstem między krokami
Błąd nr 1: przechowywanie wszystkiego tylko w historii dialogu.
Czasem kusi: „Skoro model widzi wszystko w tekście, po prostu za każdym razem wylistujmy w promptcie, jaki jest budżet, jakie produkty i co użytkownik wybrał”. Taka metoda szybko rozbija się o limity kontekstu i nie daje żadnych gwarancji spójności: model może spokojnie „zapomnieć” ważny fakt albo pomylić identyfikatory. Rzeczy krytyczne biznesowo (pieniądze, rezerwacje, zamówienia) muszą żyć w twoim backend/MCP jako źródło prawdy.
Błąd nr 2: próba zbudowania całego workflow wyłącznie na widgetState.
widgetState w Apps SDK rozwiązuje problem przetrwania stanu UI między odmontowaniem widżetu a ponownym zamontowaniem, a nie długotrwałego przechowywania workflow. Jeśli spróbujesz trzymać w nim profil, koszyk i historię kroków, skończy się to chaosem przy zmianie urządzenia i niemożnością przywrócenia po dłuższym czasie. Widżet odpowiada za wizualne drobiazgi i lokalny komfort. Cała logika scenariusza powinna żyć na serwerze.
Błąd nr 3: brak jawnego workflowId lub innego klucza.
Zdarza się, że programista polega na niejawnych identyfikatorach, jak conversation_id, ale nie wprowadza własnego pojęcia workflow. W rezultacie nie da się odróżnić jednego scenariusza od innego, rozdzielić kilku równoległych workflows ani przywrócić tego właściwego. Prosty łańcuch znaków workflowId wszędzie tam, gdzie są narzędzia i endpointy API, rozwiązuje masę problemów – zwłaszcza w MCP, który z definicji jest stateless.
Błąd nr 4: mieszanie stanu UI i logiki biznesowej.
Klasyczna sytuacja: do widgetState trafia nie tylko „która zakładka jest otwarta”, ale i „jakie produkty są w koszyku”, a potem próbuje się na podstawie tego stanu podejmować decyzje na serwerze. W efekcie przy najmniejszym rozjeździe (widżet się wyrenderował, ale żądanie jeszcze nie doszło, albo odwrotnie) model widzi jedną rzeczywistość, UI drugą, a baza – trzecią. Granica odpowiedzialności musi być jasna: serwer przechowuje i waliduje dane biznesowe, widżet je wyświetla i daje użytkownikowi wygodny sposób ich zmiany.
Błąd nr 5: brak scenariusza wznowienia i cofania.
Bardzo łatwo narysować piękną „szczęśliwą ścieżkę”, w której użytkownik idealnie idzie po krokach, nic się nie psuje, ChatGPT się nie przeładowuje, a tunel się nie rwie. W rzeczywistości każdy krok może się wyłożyć, użytkownik może wyjść w połowie, a po tygodniu wrócić. Jeśli nie zaprojektowałeś struktury WorkflowContext, nie wymyśliłeś, jak szukać „aktywnego” workflow i nie przewidziałeś przycisków „Wstecz” i „Kontynuuj później”, twój scenariusz będzie kruchy i frustrujący dla użytkowników. Przemyślany kontekst to podstawa odporności na błędy, o której będzie mowa w kolejnym wykładzie.
GO TO FULL VERSION