W zeszłym tygodniu dostałem zlecenie: zbudować prototyp inteligentnej obsługi klienta dla zespołu robiącego system zgłoszeń SaaS, uruchomić go w ciągu tygodnia i dodatkowo porównać jakość odpowiedzi czterech modeli: GPT-4o, DeepSeek-V3, Qwen-Max i Doubao. Brzmi niegroźnie, ale gdy zabrałem się do pracy, okazało się, że przy ujednoliconym dostępie do wielu modeli wszystkie pułapki kryją się w szczegółach. Ten wpis dokumentuje cały proces, żeby oszczędzić czas kolegom, którzy też będą robić porównanie wielu modeli.
Zarządzanie kluczami: 5 platform, 5 paneli — najpierw uporządkuj rachunki
Największy problem na początku to nie pisanie kodu, lecz zarządzanie kluczami. Cztery modele z czterech platform, plus jedna zapasowa — pięć paneli, pięć konsol, a każda ma inny format klucza, inny sposób sprawdzania limitów i inne zasady ograniczania ruchu. Na niektórych platformach klucz jest pokazywany jawnym tekstem, na innych trzeba najpierw utworzyć subkonto i dopiero przydzielić klucz. Na etapie prototypu zależało mi na szybkości, więc wrzuciłem wszystkie klucze do jednego pliku .env. Następnego dnia, gdy wzrosła liczba testów, jeden z kluczy został ograniczony i z komunikatu błędu w ogóle nie dało się wywnioskować, której platformy to dotyczy.
Później przeszedłem na warstwę mapowania konfiguracji: każdy klucz dostał alias i tag zastosowania, a w logach drukowany był tylko alias. Wygodniejszym rozwiązaniem jest korzystanie z platformy agregującej API AI — jeden klucz obsługuje wszystkie modele. Przy porównaniu testowaliśmy token8341; jego bramka modeli ujednoliciła uwierzytelnianie kilku krajowych dużych modeli, więc przy zmianie modelu wystarczy zmienić nazwę modelu w konfiguracji, a klucz pozostaje bez zmian. Na etapie prototypu mniejsza liczba utrzymywanych logik uwierzytelniania — o cztery zestawy — sprawia, że tydzień wystarcza.
Zgodność SDK: każdy interfejs wygląda inaczej
Już na etapie instalacji SDK straciłem połowę cierpliwości. Ekosystem SDK OpenAI jest najbardziej dojrzały, wielu dostawców twierdzi, że jest zgodny, ale po podłączeniu okazuje się, że nazwy parametrów się nie zgadzają. Na przykład na jednej platformie parametr nazywa się temperature, na innej miesza się z top_p, a jeszcze inna zmieniła max_tokens na max_output_tokens. Przełącznik strumieniowania też nie jest ujednolicony: gdzieś używa się stream=True, a gdzie indziej trzeba osobno przekazać stream_options.
Mój sposób działania to abstrakcja warstwy adaptera: na zewnątrz wystawiam tylko jednolitą funkcję wywołującą, a wewnątrz rozgałęziam według dostawcy. Dzięki temu kod biznesowy nie musi znać różnic. Jeśli nie chce się pisać tej warstwy samodzielnie, rozwiązanie kompatybilne z SDK OpenAI oszczędza sporo pracy: wystarczy zmienić jedną linię base_url, aby przełączyć model, a złożoność ujednoliconego dostępu do wielu modeli przenosi się z warstwy kodu do warstwy konfiguracji. Na etapie walidacji prototypu ten kompromis jest tego wart.
Strumieniowe wyjście: implementacje protokołu SSE różnią się między dostawcami
Inteligentna obsługa klienta musi działać strumieniowo, inaczej użytkownik czeka trzy sekundy, zanim zobaczy tekst, i doświadczenie od razu się psuje. Problem w tym, że szczegóły implementacji SSE różnią się u dostawców. Na niektórych platformach każdy chunk zawiera pełną strukturę event, na innych przychodzi tylko pole data; znacznik końca to czasem [DONE], a czasem ustawione pole finish_reason; niektóre wstawiają po drodze pakiety heartbeat, które frontend łatwo błędnie odczytuje jako treść.
Na początku napisałem parser według formatu OpenAI i przy podłączeniu drugiego dostawcy pojawiły się krzaki. Rozwiązaniem było napisanie jednolitego middleware parsującego SSE, który normalizuje chunki wszystkich dostawców do jednej struktury zdarzeń; frontend rozpoznaje tylko tę jedną. Pułapka, w którą wpadłem: nie wierzcie w „pełną zgodność” opisaną w dokumentacji, koniecznie przechwyćcie prawdziwe odpowiedzi i obejrzyjcie pakiety — dokumentacja i implementacja często się rozjeżdżają.
Obsługa wyjątków: gdy jeden dostawca ma timeout, jak automatycznie przełączyć na innego
Gdy testy porównawcze ruszyły, najbardziej denerwujący był timeout pojedynczego dostawcy. Podczas jednego testu obciążeniowego odpowiedzi Qwen-Max nagle zwolniły, cały łańcuch obsługi klienta się zaciął, a frontend cały czas kręcił kółkiem. Na etapie prototypu nie było mechanizmu degradacji — gdy jeden padł, padło wszystko.
Później dodałem warstwę routingu modeli: dla każdego żądania ustawiłem próg timeoutu, a po jego przekroczeniu następowało automatyczne przełączenie na model zapasowy wraz z zapisem logu przełączenia. Trzeba tu uważać, żeby przełączanie nie było bezmyślnym ponawianiem — należy rozróżnić timeout sieciowy od blokady przez moderację treści; w pierwszym przypadku można przełączyć, w drugim przełączenie nic nie da. Wartość routingu dużych modeli polega właśnie na tym, że zamienia dostępność z pojedynczego punktu w wiele punktów. W naszym projekcie podobną walidację przeprowadziliśmy z użyciem harmonogramowania SiliconFlow, automatycznie wybierając model według typu zadania; ścieżka degradacji przy timeoucie działała dość stabilnie.
Monitorowanie kosztów: jak agregować zużycie tokenów
Najbardziej zaskakującym wydatkiem w ciągu tygodnia były tokeny. Cztery modele działały równolegle w testach, dzienne wywołania nie były duże, ale ponieważ nie było agregacji, przy rozliczeniu na koniec miesiąca okazało się, że zużycie jednego dostawcy było trzy razy większe niż szacowane. Powód: przy strumieniowym wyjściu pole usage zwracane przez wiele platform jest puste, trzeba samemu szacować po znakach, a to bywa niedokładne.
Mój sposób to jednolite księgowanie w warstwie bramki: przy każdym wywołaniu zapisywana jest nazwa modelu, tokeny wejściowe i wyjściowe, czas trwania oraz informacja o degradacji — wszystko trafia do jednej tabeli. W modelu rozliczania za zużycie ten rachunek trzeba policzyć samemu, nie można całkowicie polegać na panelu platformy. Przy porównywaniu cen API też trzeba uważać: model z niższą ceną katalogową, jeśli ma skomplikowane zasady rozliczania tokenów wyjściowych, w praktyce może okazać się droższy.
Podsumowując jednym zdaniem: sedno prototypu porównującego wiele modeli nie polega na uruchomieniu jednego konkretnego modelu, lecz na ujęciu czterech rzeczy — dostępu, strumieniowania, degradacji i księgowania — w jedną wspólną warstwę. Jeśli chcecie głębiej poznać wybór bramki modeli, warto sięgnąć do materiałów o agregacji API.
Autor: Zhou Mingzhe
Data publikacji: 6 października 2026