Przejdź do treści

Usługi ML

Cztery usługi tej platformy można wywołać samodzielnie, bez rozpoczynania rozmowy i bez uruchamiania agenta: analizę dokumentu, OCR, zamianę mowy na tekst i wykrywanie danych osobowych. To te same implementacje, z których korzystają agenty, tyle że sięga się do nich wprost — jeden zestaw parserów, jeden zestaw detektorów, jeden klient transkrypcji.

Istnieją, bo może ich potrzebować inny komponent. Kolejka, która ma odczytać zeskanowany wniosek, zadanie wsadowe redagujące eksport, usługa chcąca transkrypcji — żadne z nich nie chce okna czatu i żadne nie powinno udawać, że nim jest.

Co jest dostarczone, a co nie

Każda rodzina usług to jeden wiersz. served znaczy, że endpoint tego wdrożenia odpowiada na nią silnikiem wymienionym obok. dependency znaczy, że rodzina jest wymagana i czegoś brakuje, a notatka mówi czego. prepared to przygotowanie architektury: żaden silnik nie jest dostarczany, a szew, którym by przyszedł, jest nazwany.

GET /api/v1/ml/services odpowiada tą samą tabelą, więc integracja może ją odczytać, zamiast wierzyć stronie dokumentacji.

Usługa Wymagania Endpoint Stan Silnik
document_analysis FA-069, FA-070 POST /api/v1/ml/documents/analyze served LiteParse albo PyMuPDF, lokalnie
ocr FA-069, FA-071 POST /api/v1/ml/documents/ocr served OCR LiteParse: wbudowany Tesseract albo zarejestrowany serwer OCR
speech_to_text FA-069, FA-072 POST /api/v1/ml/audio/transcriptions served Własny endpoint transkrypcji organizacji
pii_detection FA-069, FA-073 POST /api/v1/ml/privacy/pii served Detektory wzorców, których używają guardrails
pii_named_entities FA-073, DA-007 dependency Brak na tym wdrożeniu
image_analysis FA-074 prepared Brak na tym wdrożeniu

Dwa wiersze mówią „nie" i oba mówią dlaczego. Nazwane encje — imię i nazwisko, adres pocztowy, numer telefonu — nie mają kształtu wzorca, więc żadne wyrażenie regularne ich nie znajdzie: potrzeba modelu rozpoznawania encji dla każdego języka w zakresie. Endpoint wykrywania poniesie dodatkowe kategorie w dniu, w którym taki model się pojawi, a do tego czasu ich nie deklaruje. Analiza obrazu jest oznaczona jako zakres przyszły w samych wymaganiach.

Jak wywołać

Uwierzytelnianie, nagłówek organizacji i koperta błędu należą do API HTTP. Nie ma osobnego klucza, osobnego hosta ani drugiego wejścia, i jest to świadome: powierzchnia z własnymi drzwiami frontowymi to powierzchnia z własnymi pomyłkami.

Wszystkie cztery bramkuje jedno uprawnienie: ml:invoke. Świadomie nie jest to agents:run — integracja, która parsuje dokumenty, nie powinna przez to móc wydawać budżetu modelowego organizacji. Trzyma je każda rola poza Viewer.

curl -X POST "$BASE/api/v1/ml/documents/ocr" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Organization-Id: $ORG_ID" \
  -F "file=@scan.pdf" \
  -F "language=deu"

Odpowiedź niesie strony w kolejności, przygotowane chunki oraz rozmiar i skrót pliku:

{
  "filename": "scan.pdf",
  "filetype": "pdf",
  "byte_size": 184320,
  "content_hash": "9f2c…",
  "pages": [{"page_num": 1, "content": "Antrag auf …"}],
  "chunks": ["Antrag auf …"]
}

Analiza dokumentu

POST /api/v1/ml/documents/analyze czyta to, co dokument już niesie. Pole parser wybiera między liteparse, który zachowuje układ i czyta formaty biurowe tam, gdzie zainstalowano LibreOffice, a pymupdf, który czyta PDF-y i jest szybszy. chunk_size, chunk_overlap i chunking_strategy kształtują przygotowane chunki; strategie to recursive, fixed i markdown, a czwarta pisownia zostaje odrzucona, zamiast po cichu potraktowana jak recursive.

chunk_overlap musi być mniejszy niż chunk_size. Równy jest odrzucany i nie chodzi o porządek: splitter to przyjmuje, a potem przesuwa się o mniej więcej jeden separator na chunk, zachowując prawie kompletną kopię poprzedniego - więc legalne wysłanie odpowiada dokumentem zwielokrotnionym wiele razy.

Dokument, z którego nic nie da się odczytać, zostaje odrzucony, a nie zwrócony z pustą listą stron, a odmowa mówi, żeby wywołać OCR — czego skan parsowany dla warstwy tekstowej potrzebuje zawsze.

OCR

POST /api/v1/ml/documents/ocr rozpoznaje tekst na każdej stronie, niezależnie od tego, czy strona niesie warstwę tekstową. Tym różni się od ingestii, która wykrywa to automatycznie i pomija rozpoznawanie tam, gdzie tekst już jest: kto poprosił o OCR, poprosił o odczytanie stron jako obrazów.

language to kod Tesseracta, czyli trzy litery — pol, nie pl. Dostarczany obraz instaluje eng i pol i tylko te dwa endpoint przyjmuje: kod bez pakietu językowego za sobą wywraca się wewnątrz parsowania, więc odmowa przychodzi już na granicy. Wdrożenie, które doinstaluje więcej pakietów, poszerza tę listę w tej samej zmianie.

ocr_service_id nazywa serwer OCR zarejestrowany wśród usług lokalnych, więc wdrożenie z własnym sidecarem rozpoznawania wysyła strony tam; pomiń je, a odczyta je silnik wbudowany w parser. Tak czy inaczej strony zostają w sieci samego wdrożenia.

.docx jest tu odrzucany, choć parser go czyta. Ingestia kieruje dokumenty biurowe do czytnika natywnego, zanim sięgnie po parser OCR, więc przyjęcie takiego pliku wyciągnęłoby istniejące akapity, pominęło zeskanowane strony i nic by o tej różnicy nie powiedziało. Przekonwertuj go na PDF.

Jedno wywołanie rozpoznaje najwyżej 200 stron i ma 120 sekund — oba węższe niż w ingestii, bo tutaj ktoś czeka, a na ingestię nie czeka nikt.

Zamiana mowy na tekst

POST /api/v1/ml/audio/transcriptions transkrybuje nagranie na własnych poświadczeniach organizacji. provider i model nazywają, czego użyć; pominięcie obu bierze pierwszą oferowaną parę wdrożenia, a podanie dostawcy bez modelu bierze pierwszy model tego dostawcy. Czego nie robi nigdy, to nie wraca do domyślnego dostawcy, gdy dostawca został nazwany — tak właśnie nagranie przeznaczone dla własnego silnika trafia do vendora.

Nagranie podlega też własnemu limitowi 25 MB klienta transkrypcji, nawet gdy ML_MAX_UPLOAD_SIZE_MB jest wyższy, więc za duże nagranie zostaje odrzucone jako za duże, a nie dociera do silnika i wraca jako 503 o poświadczeniach.

Silnikiem jest ten endpoint, który nazywa model profile organizacji dla danego dostawcy. To jest odpowiedź dla wdrożenia, które nie może wysyłać dźwięku do dostawcy: skieruj profil na własny serwer mówiący tym samym API, a endpoint tutaj się nie zmieni. Organizacja bez użytecznych poświadczeń dostaje odmowę, która to mówi — nic nie jest zakładane jako istniejące, czego operator nie skonfigurował.

Wykrywanie danych osobowych

POST /api/v1/ml/privacy/pii przyjmuje JSON, a nie plik:

curl -X POST "$BASE/api/v1/ml/privacy/pii" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Organization-Id: $ORG_ID" \
  -H "Content-Type: application/json" \
  -d '{"text": "write to ada@example.com", "categories": ["email"]}'
{
  "counts": [{"category": "email", "count": 1}],
  "total": 1,
  "redacted_text": "write to [redacted:email]"
}

Raportowana jest każda zamówiona kategoria, także te, które nic nie dopasowały — „szukano i nie ma" i „nie szukano" to różne odpowiedzi. Kategorie to email, iban, credit_card i us_ssn, a każda jest dopasowywana kształtem, a potem sprawdzana: Luhn dla karty, ISO 7064 dla IBAN-u, więc ciąg cyfr nie zostaje zgłoszony jako rachunek.

Wraca liczność i tekst po redakcji, a nie offsety poszczególnych dopasowań. Detektory odpowiadają przepisanym tekstem, a odzyskanie pozycji oznaczałoby skopiowanie ich tablicy wzorców i sum kontrolnych — kopia, która po cichu przestaje zgadzać się z oryginałem, jest gorsza niż węższy kontrakt.

Co jest zapisywane

Każde wywołanie zostawia wiersz: która usługa, która organizacja, kto poprosił, ile bajtów weszło, ile wyszło, jak długo trwało i jak się skończyło. GET /api/v1/ml/calls odczytuje je z powrotem, od najnowszych, a GET /api/v1/ml/calls/{id} odczytuje jedno.

Odrzucone wywołanie też jest zapisywane, a jego wiersz jest commitowany, zanim odmowa rozwinie żądanie — wiersz tylko dodany do tej transakcji zostałby wycofany przez samą odmowę, którą opisuje, a operator pytający, czemu integracja się psuje, usłyszałby, że tenant nie wykonał żadnych wywołań.

Żadna treść nie jest przechowywana. Wynik wraca w odpowiedzi i nie jest zapisywany, więc dokument tu sparsowany nie staje się dokumentem, który to wdrożenie trzyma, a tekst wysłany do sprawdzenia pod kątem danych osobowych nie zostaje w tabeli, o której nikt nie myślał jak o składnicy dokumentów. Wiersz wywołania innej organizacji jest nie do znalezienia, tak samo jak każdy inny wiersz na tym API.

Zużycie liczy się w jednostce, w której pracuje usługa — strony przy parsowaniu, znaki przy skanowaniu — a nie w pieniądzu. Dostarczone usługi działają albo na maszynach samego operatora, gdzie nie ma ceny dostawcy, albo na własnym koncie dostawcy organizacji, które rozlicza ją wprost. Liczba, której nikt nie pogodzi z fakturą, jest gorsza niż uczciwe zliczenie jednostek.

Limity

Pojedyncze wywołanie przyjmuje do ML_MAX_UPLOAD_SIZE_MB megabajtów, domyślnie 25, i tylko tyle bajtów jest odczytywanych z ciała żądania — za duże wysłanie zostaje odrzucone, zanim w ogóle zostanie skopiowane do pamięci. Jedno skanowanie czyta najwyżej 200000 znaków. Wywołujący może wykonać RATE_LIMIT_ML_PER_MINUTE wywołań na minutę, domyślnie 30, liczonych na wywołującego, a nie na adres.

Limit tempa liczy starty i nie widzi tego, co wciąż trwa, co jest złym kształtem dla pracy mierzonej w minutach. Dlatego worker parsuje naraz najwyżej ML_MAX_CONCURRENT_PARSES dokumentów, domyślnie 4, a wywołanie trafiające na zajęte wszystkie sloty dostaje odmowę z Retry-After, a nie miejsce w kolejce: kto usłyszy „wróć za chwilę", może wrócić, a kto stoi za czterema skanami, już się poddał gdzieś, gdzie nikt tutaj tego nie widzi.

Wykonanie jest synchroniczne: odpowiedź jest wynikiem i nie ma kolejki do odpytywania. To jest uczciwe wobec tego, co tu jest, zamiast aspiracyjne — tryb kolejkowy dodałby własne stany do wiersza wywołania, i taki miałby kształt.

Wdrażanie ich osobno

Usługi skalują się inaczej niż konsola. Przebieg OCR to sekundy pracy procesora na wątku; serwowanie dashboardu nie jest ani jednym, ani drugim. Dlatego deploy/profiles/ml-services/ uruchamia obraz API drugi raz jako replikę, która odpowiada tylko na te ścieżki, z własnymi zasobami i własnym skalowaniem, a ingress przed nią kieruje do niej /api/v1/ml/.

To ten sam obraz i ta sama baza, i właśnie to sprawia, że jedna implementacja obsługuje zarówno agenty, jak i wywołujących wprost. Osobny jest proces, limity i restart — czyli to, o co prosi „wdrażane, aktualizowane i skalowane niezależnie".

Nakładkę i jej oczekiwania opisuje deploy/profiles/ml-services/README.md.