Reference węzłów — wszystkie 18 typów¶
Wszystkie typy węzłów dostępnych w wizualnym edytorze. Każdy węzeł opisany w czterech sekcjach: Co robi, Wejście (pola w inspektorze), Wyjście (pola udostępniane następnym krokom), Najczęstsze błędy.
Jeśli czytasz to po raz pierwszy, zacznij od Builder — tutorial. Pełny wykład techniczny (kontrakty, retry, idempotencja, kompilacja do flow Windmill) — windmill/docs/support/nodes.md w repozytorium.
Krótko
- 18 typów w czterech kategoriach: trigger (1), logic (3), AI (1), actions (13).
- Trzy węzły są ukryte z palety —
Source(wstawiany automatycznie),Append puppy name(część szablonu imion psów),Parse personalization cards(j.w.). - Akcje wysyłające (
send_reply,send_template,amazon_template_message) nie są ponawiane w razie błędu, żeby nie zdublować wiadomości. - Akcje BaseLinker +
send_template+create_ticketmają auto-retry: 3 podejścia × 30s. - LLM extract ma retry: 2 × 10s.
Spis treści¶
| Kategoria | Węzły |
|---|---|
| Trigger | Source |
| Logic | Filter · Delay · Parse personalization cards |
| AI | LLM extract |
| Actions — wysyłka i odpowiedzi | Send reply · Send template · Amazon template message |
| Actions — zarządzanie zgłoszeniem | Update status · Escalate · Mark email read · Create ticket |
| Actions — BaseLinker | BaseLinker field · BaseLinker call · BaseLinker find by tag |
| Actions — eBay specials | eBay dispute action · eBay return decide |
| Actions — Pupprint | Append puppy name |
Source¶
Kategoria: trigger · Ukryty z palety (wstawiany automatycznie jako korzeń) · Bez retry
Co robi: sprawdza, że zgłoszenie naprawdę pochodzi z wybranego źródła i (opcjonalnie) z konkretnego konta — to brama wejściowa scenariusza.
Wejście:
| Pole | Typ | Co wpisujesz |
|---|---|---|
| Źródło | select | Jedno z: email, allegro, amazon, ebay, erli, joom, temu, woocommerce, manual. |
| Konto | account-id | Konkretne konto (z listy cs_email_accounts dla email, lub marketplace_accounts dla marketplace) lub puste = dowolne. |
Wyjście: brak wartości udostępnianych następnym krokom (kolejne węzły czytają flow_input.issue / flow_input.event bezpośrednio).
Najczęstsze błędy:
- Event issue_id mismatch — zgłoszenie i zdarzenie nie pasują do siebie. Bug w pobieraczu, zgłoś.
- Source/account validation failed — zgłoszenie pochodzi z innego źródła niż wybrałeś. Sprawdź pasek triggera.
Filter¶
Kategoria: logic · Bez retry · Idempotentny
Co robi: porównuje pole z wartością i zwraca continue lub skip — albo cicho kończy scenariusz (raise_on_skip).
Wejście:
| Pole | Typ | Co wpisujesz |
|---|---|---|
| Pole | text | Ścieżka do pola: issue.subject, event.type, text (cały rendering wątku), results.<id_węzła>.<pole>. |
| Operator | select | is / is_not / contains / not_contains / in / not_in / gt / gte / lt / lte / exists / not_exists / regex. |
| Wartość | text | Pojedyncza wartość do porównania. |
| Lista wartości | string-list | Dla in / not_in. |
| Uwzględniaj wielkość liter | boolean | Domyślnie false (porównanie case-insensitive). |
| Zgłoś sentinel przy skip | boolean | Gdy true, brak dopasowania kończy cały scenariusz cicho (status=filtered_out). |
| Powód skip | text | Komentarz wpisany do logu przebiegu. |
Wyjście:
- Dwa uchwyty na karcie:
continue(gdy warunek pasuje) iskip(gdy nie pasuje). route(string) —"continue"lub"skip". Można czytać przez{{results.<id>.route}}choć zwykle używa się dwóch krawędzi z karty.
Najczęstsze błędy:
- Pole zwraca pustą wartość — operator
existszwrócifalse,is "cokolwiek"też. Użyjnot_existszamiastis "". - Filter nie odpala mimo pasującego tekstu — sprawdź
case_sensitive. Domyślniefalse, ale jeśli ustawisztrue,WIELKIE LITERY≠wielkie litery. - Forward reference —
field: results.X.Ygdzie X jest poniżej tego filtra. Convex odrzuci zapis:references unknown steplubnot upstream.
Delay¶
Kategoria: logic · Bez retry
Co robi: czeka N sekund, zanim odpali kolejny węzeł. Maks. 3600s (1h).
Wejście:
| Pole | Typ | Co wpisujesz |
|---|---|---|
| Sekundy | number (1–3600) | Czas oczekiwania. |
Wyjście: brak.
Najczęstsze błędy: praktycznie żadne — jeśli wartość będzie zła, zostanie zaklamerowana do [0, 3600].
Typowe użycie: czekanie na propagację efektu zewnętrznego (np. po BaseLinker setOrderFields poczekać 30s, aż macro Baselinkera się wykona).
Parse personalization cards¶
Kategoria: logic · Ukryty z palety (część szablonu imion psów) · Bez retry · Idempotentny
Co robi: wycina z HTMLa wiadomości email pola <p id="...name"> i <p id="...sku"> (format używany przez maile-zgłoszenia z Allegro i Erli przy zamówieniach z personalizacją), patrzy do katalogu Convex i policzy, ile produktów w zamówieniu jest tagowanych jako personalizowane. To deterministyczny pre-pass, zanim LLM ruszy.
Wejście: brak (czyta issue.messages[*].html).
Wyjście:
| Klucz | Co znaczy |
|---|---|
structured |
true, jeśli HTML miał rozpoznane <p id> — dane uszczegółowione. |
expected_cards |
liczba kart do wydrukowania (suma per personalizowany SKU); null gdy brak struktury. |
candidate_names |
lista imion-kandydatów wyciągniętych z <p id="...name">. |
personalized_skus |
lista [{sku, count, name}] — produkty katalogowane jako personalizacja. |
non_personalized_skus |
reszta SKU z zamówienia. |
unknown_skus |
SKU, które nie pasują do żadnego produktu w katalogu Convex. |
Najczęstsze błędy: węzeł nigdy nie rzuca wyjątku — zwraca structured=False przy braku struktury. Downstream LLM dostaje wtedy puste expected_cards i pracuje w trybie „jedno imię, brak walidacji ilości".
LLM extract¶
Kategoria: AI · Retry: 2 × 10s · Idempotencja: best-effort (model może zwrócić różne wyniki przy retry)
Co robi: wywołuje lokalny endpoint LLM (Qwen3 na hosth-aio:11434) z promptem i schematem JSON, zwraca pola + confidence (0/1/2).
Wejście:
| Pole | Typ | Co wpisujesz |
|---|---|---|
| Prompt template | textarea (wymagane) | Treść promptu. Wspiera {{text}}, {{subject}}, {{customer_name}}, {{event_type}}, {{issue.subject}}, {{event.type}}, {{results.<id>.<pole>}}. |
| Pola do wyciągnięcia | schema-fields (wymagane) | Schemat JSON: lista pól z typami (string / number / boolean). |
| Jawny tekst wejściowy | textarea | Opcjonalny — jeśli puste, model dostaje subject + snippet + thread. |
| Model | text | Override modelu (puste = qwen3). |
| API base URL | text | Override endpointu LLM. |
| API key override | text | Klucz API (puste = brak / lokalny endpoint). |
| Temperature | number | Domyślnie 0 (deterministyczne). |
| Timeout (s) | number | Domyślnie 60. |
Wyjście:
| Klucz | Co znaczy |
|---|---|
fields |
obiekt z polami z extract_schema (np. {color: "czerwony"}). |
confidence |
0 / 1 / 2 (no_name / vague / certain). |
route |
"no_name" / "vague" / "certain" — 1:1 z confidence, używane do routingu. |
Trzy uchwyty wyjścia: certain, vague, no_name. Możesz wykorzystać do routowania (np. certain → wpisz do BaseLinkera, vague → eskalacja, no_name → odpowiedź z prośbą o uzupełnienie).
Najczęstsze błędy:
- No text is available for extraction — pusty
texti pusty rendering wątku. Sprawdź, czy wiadomość naprawdę dotarła. - LLM response did not contain a JSON object — model zwrócił coś poza JSON-em. Sprawdź prompt — czy jasno mówisz „odpowiedz JSON-em"?
- LLM response field payload must be an object — model zwrócił
fieldsale nie jako obiekt. Doprecyzuj prompt. - Tymczasowe sieciowe błędy — auto-retry 2 × 10s. Po przekroczeniu — przebieg trafia do DLQ.
Send reply¶
Kategoria: action · Bez retry (z premedytacją, żeby nie zdublować wiadomości) · NIEidempotentny
Co robi: wysyła literalny tekst przez kanał źródłowy zgłoszenia (Allegro Message Center, eBay CORE messages, Email SMTP, …). Kanał wybierany automatycznie z issue.source.
Wejście:
| Pole | Typ | Co wpisujesz |
|---|---|---|
| Treść wiadomości | textarea (wymagane) | Tekst odpowiedzi. Wspiera {{customer_name}}, {{subject}}, {{order_external_id}}, {{results.<id>.<pole>}}. |
Wyjście:
| Klucz | Co znaczy |
|---|---|
sent_at |
ISO timestamp wysłania. |
message_id |
Identyfikator wiadomości w kanale (np. SMTP Message-ID, Allegro message id). |
Najczęstsze błędy:
- body is empty — pole
bodypuste lub same białe znaki. - UnsupportedSourceError — kanał nieobsługiwany (np.
manual, lub source nie ma jeszcze wsparcia channel-helpera). - SMTP error — konto email odmówiło. Sprawdź hasło aplikacji w Konfiguracji.
Brak retry — nie ponawia w razie błędu
Jeśli send_reply padnie, przebieg trafia do DLQ — operator musi zadecydować, czy ponowić ręcznie. Powód: retry oznaczałby drugą wiadomość do klienta.
Send template¶
Kategoria: action · Retry: 3 × 30s · NIEidempotentny
Co robi: ładuje szablon wiadomości z Convex (cs_message_templates._id) i wysyła go przez kanał źródłowy. Tak jak send_reply, ale tekst pochodzi z biblioteki szablonów + zmienne.
Wejście:
| Pole | Typ | Co wpisujesz |
|---|---|---|
| Template id | text (wymagane) | Convex id z cs_message_templates. |
| Zmienne template | key-value-list | Dodatkowe zmienne do szablonu. Wartości mogą być placeholderami {{results.<id>.<pole>}}. |
Wyjście:
| Klucz | Co znaczy |
|---|---|
sent_at, message_id |
Jak send_reply (nie zadeklarowane formalnie w schemacie). |
Najczęstsze błędy:
- Template variable '{{X}}' is missing — szablon używa
{{X}}, ale brak go w kontekście. Dodaj wpis doZmienne template. - Template not found — id nie istnieje w
cs_message_templates.
Amazon template message¶
Kategoria: action · Bez retry · NIEidempotentny · Tylko Amazon
Co robi: wysyła wiadomość do kupującego Amazon przez SP-API Messaging v1. Amazon nie ma free-form messagingu — tylko 9 zamkniętych szablonów (patrz Source setup → Amazon).
Wejście:
| Pole | Typ | Co wpisujesz |
|---|---|---|
| Szablon Amazon | select (wymagane) | Jeden z 9 szablonów. Najbliższy free-form: unexpectedProblem. |
| Treść wiadomości | textarea (wymagane) | Tekst — może zawierać placeholdery {{customer_name}}, {{order_external_id}}. |
| Zmienne szablonu (opcjonalne) | key-value-list | Dodatkowe zmienne. |
Wyjście: order_id (z payloadu Amazon).
Najczęstsze błędy:
- amazon_template_message is Amazon-only —
issue.source != "amazon". Dodaj filtr źródła wcześniej. - Amazon SP-API rejected — np. konto sprzedawcy nie ma pozwolenia na messaging dla danego rynku.
Update status¶
Kategoria: action · Bez retry · Idempotentny
Co robi: zmienia cs_issues.status zgłoszenia.
Wejście:
| Pole | Typ | Co wpisujesz |
|---|---|---|
| Nowy status | select (wymagane) | new, classified, action_suggested, in_progress, resolved, escalated. |
Wyjście: previous + current (oba statusy).
Najczęstsze błędy: praktycznie żadne — Convex zaakceptuje każdy z 6 statusów.
Escalate¶
Kategoria: action · Bez retry · NIEidempotentny (każde wywołanie tworzy nową notatkę)
Co robi: dodaje notatkę eskalacji do zgłoszenia + zmienia status na escalated. Notatka jest widoczna w wątku CS jako wpis systemowy.
Wejście:
| Pole | Typ | Co wpisujesz |
|---|---|---|
| Powód eskalacji | textarea (wymagane) | Tekst widoczny operatorowi. |
| Assignee | text | Opcjonalny — kto ma się zająć. |
Wyjście: note_id.
Najczęstsze błędy:
- reason is required — pole
reasonpuste.
Eskalacja jako default w gałęziach
Eskalacja to często „bezpieczny default" — gdy LLM ma niskie confidence, gdy filter zwraca skip, gdy capability eBay nie pozwala działać automatycznie. Operator widzi notatkę w wątku i wie, że scenariusz świadomie odpuścił.
Mark email read¶
Kategoria: action · Bez retry · Idempotentny
Co robi: flaguje wiadomość Gmail jako przeczytaną (\Seen przez IMAP). Tylko dla zgłoszeń z source = email — dla innych źródeł cicho pomija.
Wejście: brak.
Wyjście: marked (liczba flagowanych UID), host, opcjonalnie skipped: true, reason.
Najczęstsze błędy: węzeł połyka błędy IMAP jako skipped zamiast je rzucać — przebieg się nie zatrzyma. Jeśli widzisz w logu imap error: …, sprawdź, czy hasło aplikacji nie wygasło.
Typowe użycie: ostatni krok scenariusza email — operator może traktować skrzynkę Gmail jako kolejkę („wszystko nieprzeczytane = jeszcze nie obsłużone scenariuszem").
Create ticket¶
Kategoria: action · Retry: 3 × 30s · NIEidempotentny (każde wywołanie tworzy nowy issue Forgejo)
Co robi: otwiera issue w Forgejo (git.aiofactory.pl) z tytułem, treścią i listą labelek. Używane, gdy zgłoszenie wymaga interwencji deweloperskiej.
Wejście:
| Pole | Typ | Co wpisujesz |
|---|---|---|
| Tytuł | text (wymagane) | Wspiera placeholdery {{subject}}, {{customer_name}}, {{issue_id}}. |
| Opis | textarea (wymagane) | Pełna treść issue. |
| Labelki | string-list | Lista nazw labelek Forgejo (np. ["bug", "needs-triage"]). |
Wyjście: ticket_url, ticket_id.
Najczęstsze błędy:
- Template variable missing — placeholder
{{X}}w tytule/opisie nie ma wartości. - Forgejo HTTP 401 — token Forgejo wygasł / brak uprawnień.
BaseLinker field¶
Kategoria: action · Retry: 3 × 30s · Idempotentny (overwrite)
Co robi: zapisuje wartość do dodatkowego pola zamówienia BaseLinker (extra_field_1 / extra_field_2 lub custom field id).
Wejście:
| Pole | Typ | Co wpisujesz |
|---|---|---|
| Order external id | text | Puste = issue.order_external_id (z fallbackiem). |
| Field id | text (wymagane) | extra_field_1, extra_field_2, lub custom additional-field id z BaseLinkera. |
| Wartość | text (wymagane) | Co wpisać. Wspiera pełen kontekst placeholderów. |
Wyjście: brak udostępnianych formalnie. Skrypt zwraca bl_response (raw odpowiedź BL).
Najczęstsze błędy:
- No order_external_id could be resolved — zgłoszenie nie ma
order_external_idani podanego override'u. - MissingCredentialError (BaseLinker token) — brak aktywnego konta BaseLinker.
Typowe użycie: zapis koloru / imienia psa do dedykowanego pola BL — patrz szablon kolorów.
BaseLinker call¶
Kategoria: action · Retry: 3 × 30s · Idempotencja: zależy od metody
Co robi: generyczny węzeł — wywołaj dowolną metodę API BaseLinker z parametrami. Używane, gdy specjalizowane węzły (baselinker_field, baselinker_find_order_product_by_tag) nie wystarczają.
Wejście:
| Pole | Typ | Co wpisujesz |
|---|---|---|
| Metoda BL | select (wymagane) | getOrders, setOrderStatus, setOrderFields, runOrderMacroTrigger, deleteOrderProduct, addOrderProduct, addInvoice, getJournalList. |
| Parametry | key-value-list | Klucz → wartość. Wartości mogą być {{results.<id>.<pole>}}. |
Wyjście: result — surowa odpowiedź BaseLinkera. Czytasz przez {{results.<id>.result.<pole>}}.
Najczęstsze błędy:
- BaseLinker method is required — pole
methodpuste. - BaseLinker error — np.
setOrderStatusz nieistniejącymstatus_id.
Idempotencja zależy od metody
getOrders / getJournalList to read-only — bezpieczne. setOrderStatus / setOrderFields to overwrite — ponowienie nadpisze tym samym, OK. addOrderProduct / addInvoice to insert — retry tworzy duplikaty! Dla tych metod ustaw Bez retry w paśmie tytułu (lub po prostu unikaj retry).
BaseLinker find by tag¶
Kategoria: action · Retry: 3 × 30s · Idempotentny (read-only)
Co robi: pobiera zamówienie BaseLinker po external_order_id i znajduje pierwszą linię, której produkt w katalogu Convex ma podany tag (np. ZZ - AI - PodajKolor). Używane przed deleteOrderProduct lub setOrderProductFields.
Wejście:
| Pole | Typ | Co wpisujesz |
|---|---|---|
| Tag produktu | text (wymagane) | Dokładny match (case-sensitive) — np. ZZ - AI - PodajKolor. |
| Order external id | text | Puste = issue.order_external_id. |
| Pomiń scenariusz gdy brak tagu | boolean | Gdy true, brak linii z tagiem kończy scenariusz cicho (status=filtered_out). |
Wyjście:
| Klucz | Co znaczy |
|---|---|
found |
bool — czy znaleziono linię. |
order_product_id |
id linii zamówienia (puste przy found=false). |
bl_order_id |
wewnętrzne BL order_id. |
product_id |
BL catalog product_id. |
sku, name |
SKU i nazwa linii. |
Najczęstsze błędy:
- tag is required — pole
tagpuste. - no_order_external_id — zgłoszenie nie ma
order_external_idani override'u.
eBay dispute action¶
Kategoria: action · Bez retry · NIEidempotentny (eBay odrzuci drugą próbę) · Tylko eBay payment_dispute
Co robi: wykonuje akcję na payment dispute eBay (Sell Payment Dispute API). Trzy akcje: accept (akceptujesz spór, zwracasz pieniądze), contest (kwestionujesz spór), add_evidence (dodajesz dowód do trwającego sporu).
Wejście:
| Pole | Typ | Co wpisujesz |
|---|---|---|
| Akcja | select (wymagane) | accept / contest / add_evidence. |
| Contest reason | select | (Wymagane dla contest) Powód: SELLER_PROVIDES_PROOF_OF_DELIVERY / … |
| Evidence / opis | textarea | Tekst dowodu / opisu. |
| Evidence file URL | text | URL pliku do załączenia (opcjonalny, dla add_evidence). |
| Evidence type | select | Typ dowodu: PROOF_OF_DELIVERY / PROOF_OF_IDENTITY / SIGNATURE_CONFIRMATION / TRACKING / OTHER. |
Wyjście: dispute_id, revision (z odpowiedzi eBay).
Najczęstsze błędy:
- amazon_dispute_action is eBay-only — zgłoszenie nie z eBay.
- capability gate failed — pobieracz nie ustawił
capabilities.can_Xnatrue. Sprawdź czy zgłoszenie ma flagę. - Already accepted/contested — drugie wywołanie tej samej akcji.
eBay return decide¶
Kategoria: action · Bez retry · NIEidempotentny · Tylko eBay return
Co robi: decyduje o zwrocie eBay (Post-Order v2 decide). Cztery decyzje: ACCEPT_RETURN, DECLINE_RETURN, OFFER_PARTIAL_REFUND, PROVIDE_RMA.
Wejście:
| Pole | Typ | Co wpisujesz |
|---|---|---|
| Decyzja | select (wymagane) | Jedna z 4 wymienionych. |
| Wiadomość (opcjonalna) | textarea | Komentarz dla kupującego. |
| Kwota partial refund | number | Wymagana dla OFFER_PARTIAL_REFUND. |
| Waluta partial refund | select | EUR / USD / GBP / PLN. |
Wyjście: return_id + raw response.
Najczęstsze błędy:
- capability gate failed —
capabilities.can_decide = false. - eBay rejected decision — np. zwrot już zamknięty.
Append puppy name¶
Kategoria: action · Ukryty z palety (część szablonu imion psów) · Bez retry · NIEidempotentny
Co robi: dla zamówienia z personalizowanymi naklejkami: rekoncyliuje tablicę imion (z LLM) z liczbą oczekiwanych kart (z parse_personalization_cards) i zapisuje jeden wpis puppy_names per kartę. Cycle-padding gdy klient podał mniej imion niż produktów.
Wejście:
| Pole | Typ | Co wpisujesz |
|---|---|---|
| Tablica imion | text (wymagane) | Zwykle {{results.llm_dog_name.fields.names}}. |
| Oczekiwana liczba kart | text | Zwykle {{results.parse_cards.expected_cards}} (puste = tryb unstructured, jedna karta na imię). |
| Rozmiar karty | text (wymagane) | big lub small. Może być {{results.llm_dog_name.fields.size}}. |
Wyjście: count (ile kart utworzono), created (lista id), expected_cards, reconciled_names.
Najczęstsze błędy:
- zero names — LLM nie wyciągnął żadnego imienia.
- more names than cards — operator widzi błąd; albo prompt LLM zwraca za dużo, albo
expected_cardsjest źle policzony. - invalid size —
sizeto niebiganismall.
Najczęstsze błędy walidacji przy zapisie scenariusza¶
Niezależnie od węzła, te błędy widzisz przy Zapisz:
| Komunikat | Przyczyna | Rozwiązanie |
|---|---|---|
must have exactly one root node |
Graf ma >1 węzeł bez wejścia, albo żaden | Wstaw / usuń, aby był dokładnie jeden korzeń (Source). |
contains a cycle at node '<id>' |
Krawędź wraca do węzła wyżej | Usuń krawędź zwrotną. |
merges are not supported (node '<id>' has multiple inputs) |
Dwie krawędzie zbiegają się w jeden węzeł | Skopiuj końcowy węzeł — jeden per gałąź. |
contains disconnected nodes: <id> |
Węzeł nie jest osiągalny z korzenia | Połącz go krawędzią lub usuń. |
references unknown step '<id>' |
Placeholder odwołuje się do węzła, którego już nie ma | Popraw placeholder — wybierz z panelu Upstream outputs. |
references '<id>' which is not upstream |
Placeholder wskazuje węzeł poniżej tego | Reorganizuj graf — referencje tylko w górę. |
exposes no data output '<key>' |
Klucz nie jest w dataOutputs upstream node-a |
Sprawdź dostępne klucze w panelu Upstream outputs. |
needs labeled outputs to preserve branch routing |
Filter / LLM extract z >1 wyjściem nie ma labelek na krawędziach | Przeciągnij krawędzie z konkretnych uchwytów (np. continue / skip). |
Cross-linki¶
- Tutorial od podstaw — Builder — tutorial.
- Konfiguracja źródeł — Source setup.
- Gotowe szablony — kolory, imiona psów.
- Triage błędów (DLQ) — Monitoring.
Dane techniczne¶
- Katalog typów (źródło prawdy):
convex/convex/support/nodeCatalog.ts(pola, etykiety,dataOutputs). - Implementacje runtime:
windmill/f/support/nodes/<typ>.py— jeden plik per typ. - Kompilator graf → flow Windmill:
convex/convex/support/flow_sync.ts. - Pełna referencja techniczna (z retry-policies, idempotencją, error modes):
windmill/docs/support/nodes.md.