Files
csbot-prototype/documentation/21-realne-sluzby.md
T
JiriUhlirandClaude Opus 5 aa0a783095 AI zpracovani textu funguje: klic, adresa a tri skripty
Sluzba byla v katalogu bez pristupovych udaju a s appId 'ai-text', tedy
mirila na aplikaci, ktera neexistuje. Konektor nemel co vyplnit a krok nemel
co vykonat.

- sluzba stoji na api.openai.com/v1 stejne jako OpenAI, overeni GET /models
- model se bere z konektoru (ctx.config.model), ne z kazdeho kroku zvlast
- skripty ai-text.classify, ai-text.extract, ai-text.generate

Proc to neni jedna sluzba s OpenAI: tam se posila volny dotaz a vraci volny
text. Tady je uloha pevna a vystup taky - category a confidence, objekt
s popsanymi poli, text s poctem slov. Na to jde ve strome navazat podminkou,
na volny text ne.

Zjisteno u toho a zapsane do 21-realne-sluzby.md: proxy vraci na neznamou
aplikaci 200 s prazdnym telem, takze overeni konektoru u sluzby bez
verifyPath projde i tam, kde nic nebezi. Zatim neopraveno.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 07:32:18 +02:00

19 KiB

21 - Realne sluzby a co k nim potreba

Naprogramovano. Tenhle dokument rika, ktera sluzba v katalogu ma za sebou opravdu bezici aplikaci, jake udaje po firme chce a co s ni umime udelat.

Obecny popis vrstev je v 12-sluzby-a-konektory.md, popis skriptu v 11-skripty-konektoru.md.

Zdroj pravdy

Seznam bezicich aplikaci je na https://services.csbot.cz/apps. Kazda ma /docs se Swaggerem a /openapi.json (u .NET aplikaci /docs/v1/swagger.json) se strojove citelnym popisem.

Katalog v src/data/services.ts z toho vychazi. Neni to totez: jedna aplikace muze nest vic sluzeb katalogu a nektere sluzby katalogu zatim zadnou aplikaci nemaji.

Ktera sluzba stoji na cem

Sluzba katalogu Aplikace (appId) Overeni (verifyPath)
iDoklad idoklad /account/agenda
RAYNET CRM raynet /company?limit=1
CSOB (PSD2) csob /accounts?size=1
SAP Business One sap-bo /api/system/info
PPL CPL pplcplapi /customer
Microsoft 365 microsoft-365-service /status
Google Workspace google-service /google/drive/files
Google Analytics 4 analytics /ga/admin/accountSummaries
Search Console analytics /gsc/sites
Google Ads analytics /googleads/customers:listAccessibleCustomers
Sklik analytics /sklik/limits
Meta Ads meta /ads/me/adaccounts
Prepis hovoru audio-transcription nema, overi se jen dostupnost
E-mail SMTP server firmy prihlaseni na server, nic se neodesila
OpenAI mimo nas, api.openai.com/v1 /models
AI zpracovani textu mimo nas, api.openai.com/v1 /models

Ctyri sluzby na jedne aplikaci. GA4, Search Console, Google Ads a Sklik bezi v analytics, ale kazda ma jine pristupove udaje a jine ceny za pristup. Slucovat je do jedne sluzby by znamenalo, ze firma, ktera ma jen Sklik, musi zaroven vyplnit Google. Proto jsou to ctyri sluzby s jednim appId.

Prepis hovoru nema overeni udaju. Jeho jedine volani je prepis, ktery se uctuje. Overeni proto rekne jen "sluzba odpovida" a nahlas dodá, ze udaje overene nejsou - test, ktery projde i se spatnym klicem, by lhal.

Sluzby, ktere aplikaci zatim nemaji

E-shop, WhatsApp, Facebook Messenger, Instagram, SMS a Slack jsou v katalogu jako popis toho, co chceme umet. Konektor u nich zalozit jde, ale nic za nim neni.

Pozor: overeni u nich projde. Proxy vraci na neznamou aplikaci 200 s prazdnym telem, takze GET /apps/<neco>/health vypada jako zdrava sluzba i u aplikace, ktera neexistuje:

curl -o /dev/null -w "%{http_code}" https://services.csbot.cz/apps/tohle-neexistuje-xyz/health
# 200

Sluzba bez verifyPath se overuje prave pres /health, takze konektor rekne "Sluzba odpovida" i tam, kde nic nebezi. Neni to chyba overovani, je to chyba predpokladu: 2xx od proxy neznamena, ze za ni neco je. Napravit to znamena vyzadovat, aby /health neco odpovedel, ne jen vratil kod.

Meta Ads je neco jineho nez Facebook Messenger a Instagram: aplikace meta je nad Marketing API, tedy reklamy, a je jen pro cteni. Zpravy ze stranky a prime zpravy ta aplikace neumi, proto se na ni ty dve sluzby nenapojily.

Co ktera sluzba chce po firme

Udaje patri konektoru, ne prostredi. Tajne se z API nikdy nevraci.

RAYNET CRM

Pole Hlavicka Kde to vzit
API klic X-Api-Key RAYNET: Nastaveni, Klic k API
E-mail uzivatele X-Raynet-Email prihlasovaci e-mail
Nazev instance X-Instance-Name subdomena uctu

CSOB (PSD2)

Nejvic udaju z celeho katalogu, a je to tak spravne: bankovni rozhrani chce certifikat i token. X-Access-Token navic casem vyprsi a musi se prepsat, jinak konektor prestane fungovat, aniz by se cokoliv jineho zmenilo.

Certifikat QWAC se vklada jako PFX zakodovany do Base64.

SAP Business One

X-SAP-B1-BaseUrl je adresa Service Layer u zakaznika a musi byt dostupna z internetu. X-SAP-B1-Reject-Unauthorized se nastavi na false jen tam, kde ma Service Layer self-signed certifikat.

PPL CPL

Client ID a Client Secret z vyvojarskeho portalu. X-Environment prepne na testovaci prostredi, ktere nevytvari skutecne zasilky - hodi se pri zkousení stromu.

Microsoft 365

Tenant ID, Client ID a Client Secret registrovane aplikace v Entra ID. Prihlasuje se aplikace, ne clovek, takze kazdy krok rika, ktere schranky se tyka.

Google Workspace

Dve cesty, staci jedna:

  • JSON klic service accountu (X-Google-Service-Account-Json) plus opravneni (X-Google-Service-Account-Scopes). Tohle je cesta pro provoz bez cloveka: sluzba si z klice vystavi token sama.
  • Hotovy access token (X-Google-Access-Token). Plati asi hodinu, takze na trvaly provoz to neni.

Overeni konektoru cte Disk, takze service account potrebuje aspon scope https://www.googleapis.com/auth/drive.readonly. Bez nej test skonci chybou, i kdyz je klic v poradku.

Google Analytics 4, Search Console, Google Ads

Vsechny tri pouzivaji tentyz princip prihlaseni pres Google, jen s jinym prefixem hlavicky. Jeden service account staci na vsechny tri, kdyz se mu v kazde sluzbe udeli pristup. Google Ads navic vzdy potrebuje developer token.

Sklik

Jeden token z API Drak. Vygenerovani noveho tokenu zneplatni ten predchozi, takze se u sdileneho uctu vyplati vedet, kdo ho generoval naposled.

Meta Ads

Token systemoveho uzivatele z Business Manageru. Uzivatelsky token prestane platit, kdyz clovek odejde z firmy nebo si zmeni heslo, takze se pro server-to-server nehodi. App secret je povinny tam, kde ma aplikace zapnute appsecret_proof.

Prepis hovoru

Deepgram i OpenAI klic. Obe sluzby bezi paralelne a treti volani jejich vysledky slucuje, takze bez obou klicu to nefunguje.

OpenAI a AI zpracovani textu

Obe stoji na temze API a obe chteji jen API klic. Zadava se holy, slovo Bearer dopise portal - viz nize.

Rozdil je v tom, na co se ptaji. OpenAI posila volny dotaz a vraci volny text; hodi se, kdyz clovek vi, co chce napsat. AI zpracovani textu ma hotove ulohy s pevnym vystupem:

Operace Vraci
Zaradit do kategorie category, confidence a jestli je ze seznamu
Vytahnout udaje objekt s poli, ktera si popsal uzivatel
Vygenerovat text text plus pocet slov

Pevny vystup je ten duvod, proc to neni jedna sluzba: na category a confidence jde ve strome navazat podminkou, na volny text ne.

Model se u teto sluzby bere z konektoru (ctx.config.model), aby se nemusel vyplnovat u kazdeho kroku znovu.

OpenAI: sluzba, ktera nebezi u nas

Zbytek katalogu jsou nase aplikace za services.csbot.cz/apps. OpenAI je cizi domena, se kterou nemuzeme hnout, takze se s ni zachazi jinak na trech mistech.

Adresa je u sluzby, ne z appId

Service ma nove nepovinne pole baseUrl s absolutni adresou. Skladat adresu ze SERVICES_BASE_URL by u ni nedavalo smysl - to je zaklad nasich aplikaci.

Prepsat ji jde dvema zpusoby:

Kudy Pro koho plati K cemu
OPENAI_BASE_URL cela instance brána, napodobenina pri vyvoji
adresa u konektoru jedna firma vlastni Azure OpenAI

Obecne: <SLUZBA>_BASE_URL, kde se z ID sluzby udelaji velka pismena a pomlcka je podtrzitko (sap-bo je SAP_BO_BASE_URL).

Klic se zadava holy

OpenAI chce Authorization: Bearer <klic>. Kdyby si mel uzivatel slovo Bearer psat sam, byl by to prvni zdroj chyb, ktery neni videt ani zpetne - hodnota se z API nevraci, takze preklep v ni uz nikdo nenajde.

Pole udaju proto ma nepovinny prefix a runtime ho doplni az pri sestaveni hlavicky. Redakce v logu se dela na obojí: na cely retezec i na samotny klic, protoze cizi sluzby vraci v chybe jednou jedno a jednou druhé.

Co s OpenAI umime

Operace Endpoint K cemu
Zeptat se modelu POST /chat/completions shrnuti, klasifikace, sepsani odpovedi
Nahrat soubor POST /files vrati fileId pro dalsi krok
Zeptat se na soubor POST /responses vytezeni faktury, smlouvy, fotky
Prepsat zvuk POST /audio/transcriptions jeden pruchod prepisem
Nacist seznam modelu GET /models co ucet umi, nic nestoji

Model je volny text s vychozi hodnotou, ne vyber ze seznamu. Pevny seznam by zestarl pri kazdem vydani noveho modelu a krok stromu by pak odmital hodnotu, kterou ucet umi. Co ucet umi, vrati operace Nacist seznam modelu.

Otazka nad souborem je jiny endpoint nez obycejny dotaz. Soubor jako vstup umi az Responses API; Chat Completions by prijalo jen text, takze by se obsah PDF musel vlepit rucne - a to nejde.

Nahrani a dotaz jsou dva kroky. Jednoho souboru se casto pta vic dotazu a nahravat ho pokazde znovu by stalo cas i penize.

temperature se posila jen kdyz ji uzivatel vyplni. Novejsi modely ji odmitaji uplne, takze poslat vychozi hodnotu by krok rozbilo tam, kde o ni nikdo nestal.

E-mail: sluzba, ktera nejde pres HTTP

Zbytek katalogu se vola pres HTTP a operaci vykona skript. SMTP neni HTTP, a skript umi jen ctx.http - dat mu sit jinudy by zrusilo pravidlo, ze skript nema jak zavolat ven mimo nas klient.

E-mail je proto vnitrni krok (src/runtime/builtinSteps.ts), stejne jako zalozeni ticketu. Rozdil je jen v tom, kam saha: ticket do naseho uloziste, e-mail na posmovni server firmy.

Sluzba to o sobe rika sama, priznakem transport: 'smtp'. Podle nej se rozhodne i overeni konektoru. Neni to vlastnost konektoru: jak se sluzba vola, je vlastnost sluzby.

Co si firma vyplni

Pole Klic Poznamka
SMTP server host napriklad smtp.seznam.cz
Port port 587 pro STARTTLS, 465 pro sifrovane od zacatku
Sifrovani security prazdne se ridi portem, prepsat lze ssl, starttls, zadne
Uzivatel user obvykle cela adresa
Heslo password tajne, z API se nikdy nevraci
Adresa odesilatele from server ji musi povolit
Jmeno odesilatele fromName co uvidi prijemce misto hole adresy
Adresa pro odpovedi replyTo kdyz maji odpovedi chodit jinam

Prazdne sifrovani se ridi portem, protoze to je zvyklost, kterou zna kazdy. Vyslovna hodnota to prebije - jsou servery, ktere to maji jinak.

Adresa serveru se hlida stejne jako u HTTP: nesmi mirit do vnitrni site. Vyplnuje ji firma, takze je to jedina zabrana proti tomu, aby si nechala navazat spojeni dovnitr.

Co se vyplnuje v kroku

Pole Druh Poznamka
Prijemce text adresy oddelene carkou
Kopie, skryta kopie text nepovinne
Predmet text sablona, tedy Ticket {{ticketId}}
Telo zpravy html pise se jako HTML, vice radku
Textova verze longtext bez vyplneni se vyrobi z HTML
Adresa pro odpovedi text prebije hodnotu z konektoru

Krok vraci messageId, accepted a rejected, takze se za nim da vetvit podminkou na to, jestli server nekoho odmitl.

Textova verze neni pridavek. Klient, ktery HTML nezobrazi, by dostal prazdnou zpravu, a filtry nevyzadane posty berou chybejici textovou cast jako priznak spamu.

HTML telo a dosazovani promennych

Druh pole html je novy vedle text a longtext a znamena dve veci: builder ho vykresli jako vysoke pole s neproporcionalnim pismem, a runtime v nem escapuje dosazene hodnoty.

Escapuje se hodnota, ne sablona. Znacky, ktere napsal autor sablony, jsou zamer; ostre zavorky v hodnote od zakaznika ne. Bez toho by text ticketu s <b> prepsal rozvrzeni zpravy a <script> by se dostal prijemci do schranky.

Deje se to v fillTemplates v executoru, protoze jen tam je jeste videt, co byla sablona a co dosazena hodnota. Escapovat az vysledek nejde - v nem uz se to nerozlisi.

Druh pole se urcuje vyslovne v katalogu, ne odhadem podle nazvu. Hadat podle id === 'html' by fungovalo do prvni akce, ktera to pole pojmenuje jinak.

Overeni konektoru

Misto cteciho volani se konektor prihlasi na server (verify v nodemaileru). Nic se neodesila, takze test nikomu nic nedorucí, a bez platneho hesla neprojde.

Hlaska rozlisuje, co se stalo, stejne jako u HTTP:

Co server rekl Co to znamena
EAUTH, kod 535 udaje dostal a neuznal je, jde o uzivatele a heslo
EENVELOPE neprijal odesilatele nebo prijemce
ECONNECTION, ETIMEDOUT nespojilo se, tedy adresa, port nebo sifrovani
kod 4xx docasne odmitnuti, opakovani ma smysl

Odeslani e-mailu je jediny vnitrni krok, ktery smi rict "zkus to znovu". Ostatni selhavaji na spatnem nastaveni, ktere se opakovanim nespravi. Nedostupny posmovni server ale za minutu bezet muze, kdezto spatne heslo bude spatne porad.

U schranek s dvoufazovym overenim musi byt v konektoru heslo pro aplikaci, ne heslo k uctu. Server na to odpovi obycejnym 535 a hlaska to proto rika sama.

Soubory ve skriptech

Soubor prochazi krokem stromu jako Base64 retezec. Duvod: parametr skriptu je vzdy hodnota, kterou jde zapsat do JSONu, protoze se uklada do zaznamu behu. Binarni data by se tam nevesla.

Odesila se pres ctx.http.postForm, ktery slozi multipart/form-data:

await ctx.http.postForm('/files', {
  purpose: 'user_data',
  file: { filename: 'faktura.pdf', base64: inputs.obsah, contentType: 'application/pdf' },
});

Hranici (boundary) dopisuje az fetch. Kdyby si ji skript nastavoval sam, chybela by v hlavicce a sluzba by telo neprecetla.

Strop je SCRIPT_MAX_UPLOAD_BYTES, vychozi 10 MB. Je zamerne nizsi nez u cizich sluzeb: OpenAI zvladne stovky megabajtu, nas zaznam behu ne.

Skripty, ktere k realnym sluzbam existuji

Skript Co dela
raynet.find-company dohleda firmu, nenalezeno neni chyba
raynet.upsert-contact najde podle e-mailu a doplni, jinak zalozi
raynet.create-lead zalozi poptavku, volitelne s vazbou na firmu
csob.list-transactions prelozi cislo uctu na ID banky a stahne pohyby
sap-bo.find-business-partner hleda pres OData filtr, apostrof se zdvojuje
sap-bo.list-orders objednavky partnera nebo za obdobi
ppl.create-shipment zalozi zasilku a pocka na zpracovani davky
ppl.track stav zasilky, nenalezeno neni chyba
microsoft365.send-mail e-mail z konkretni schranky
microsoft365.create-event schuzka vcetne casove zony
google.send-email Gmail: sklada cely RFC 2822 e-mail
google.append-sheet-row pripise radek do tabulky, ID vytahne z odkazu
ga4.run-report srovna GA4 hlavicky a hodnoty na radky
search-console.run-report pojmenuje dimenze zpatky, CTR na procenta
google-ads.campaign-report GAQL, mikrojednotky na koruny
sklik.campaign-report RPC pole argumentu, halere na koruny
meta-ads.insights vykon reklam, cisla z textu na cisla
transcription.transcribe dva enginy a sloucení, s nahradnikem pri vypadku
openai.chat dotaz na model
openai.upload-file nahrani souboru pres multipart
openai.ask-about-file otazka nad souborem pres Responses API
openai.transcribe-audio prepis zvuku
openai.list-models co ucet umi
ai-text.classify zarazeni s jistotou, hlida vycet kategorii
ai-text.extract vystup podle poli, ktera popsal uzivatel
ai-text.generate text s omezenim delky, tonu a jazyka

E-mail v tabulce neni schvalne: neni to skript, ale vnitrni krok, viz vyse.

Skripty pro iDoklad jsou popsane v 11-skripty-konektoru.md.

Co chybi

Chybi Poznamka
Spoustece ze skutecnych sluzeb triggery v katalogu jsou zatim popis, ne kod
Stazeni binarni odpovedi ctx.http cte odpoved jako text, PDF etikety se nevraci
Google: dohledani souboru na Disku wrapper nema v OpenAPI parametry dotazu, neni jiste, co propousti
OAuth toky (Google, CSOB, Meta) token se zadava rucne a po vyprsení se rucne prepisuje
Prilohy u e-mailu krok posila jen telo, soubor zatim neprilozi
Prijem e-mailu (IMAP) spoustec Prijat e-mail je zatim jen popis v katalogu
Overeni udaju u Prepisu hovoru sluzba nema levne cteci volani, ktere by udaje otestovalo