LiteLLM jako gateway: jedno API dla modeli w chmurze i lokalnych

Piotr Dul

20-10-2026

(aktualizacja: 20-10-2026)

Postaw lokalną bramkę LiteLLM między aplikacją a modelami. Jeden klient openai, konfiguracja w YAML i przełączanie chmura/lokalnie jedną zmienną, bez automatycznego uciekania do chmury.

LiteLLM jako gateway: jedno API dla modeli w chmurze i lokalnych
LiteLLM proxy to lokalny serwer, który przyjmuje zapytania w formacie OpenAI i przekazuje je do wybranego modelu: lokalnego (Ollama) albo chmurowego (OpenAI). Definiujesz aliasy modeli w pliku YAML, a aplikacja zna tylko alias i adres bramki, więc przełączenie z chmury na model lokalny to zmiana jednej wartości w konfiguracji. Bez dodatkowych ustawień LiteLLM nie przechodzi sam na chmurę, gdy model lokalny zawiedzie.

To druga część cyklu o modelach lokalnych. W pierwszej części wywołałeś model w Ollamie klientem openai. Zostały w kodzie „na sztywno” adres, klucz i nazwa modelu. Tu to uporządkujemy.

Czym jest gateway do modeli i po co mi on?

Gateway (bramka) to pośrednik między aplikacją a dostawcami modeli. Aplikacja rozmawia z jednym adresem, a bramka decyduje, dokąd zapytanie trafi.

Bez bramkiZ bramką
Aplikacja zna adres i format każdego dostawcyAplikacja zna jeden adres i jeden format (OpenAI)
Zmiana modelu = zmiana koduZmiana modelu = zmiana konfiguracji
Klucze do chmury są w aplikacjiKlucze są tylko w bramce
Każdy dostawca ma własne różnice w APILiteLLM wyrównuje te różnice

Dla małego projektu to na początku dodatkowy proces, ale zyskujesz miejsce, w którym możesz porównać modele na tym samym kodzie, co zrobimy w trzeciej części.

Jak zainstalować LiteLLM proxy?

Wszystko robimy w jednym środowisku wirtualnym, bez kontenerów:

python -m venv .venv
source .venv/bin/activate
pip install "litellm[proxy]" openai pydantic-settings pyyaml

Nawias kwadratowy [proxy] doinstalowuje serwer. Sama biblioteka litellm pozwala wywoływać modele z kodu, ale bramka wymaga dodatku.

Jak skonfigurować aliasy modeli w YAML?

Bramkę konfiguruje plik litellm.yaml:

;yaml
model_list:
  - model_name: local
    litellm_params:
      model: ollama_chat/llama3.2
      api_base: http://localhost:11434
  - model_name: cloud
    litellm_params:
      model: openai/gpt-4o-mini
      api_key: os.environ/OPENAI_API_KEY

general_settings:
  master_key: sk-lokalny-klucz

Co tu jest ważne:

  • model_name to alias, który zobaczy aplikacja (local, cloud). Nazwa dostawcy nie wycieka do kodu.
  • ollama_chat/ w nazwie modelu mówi LiteLLM, że ma użyć wariantu czatu Ollamy. Po ukośniku wpisujesz nazwę z ollama list.
  • os.environ/OPENAI_API_KEY to składnia LiteLLM do odczytania klucza ze zmiennej środowiskowej, dzięki czemu klucz nie trafia do pliku.
  • master_key to klucz, którym aplikacja uwierzytelnia się w bramce. To nie jest klucz OpenAI. Na lokalnym komputerze możesz wpisać cokolwiek, ale nie używaj tu prawdziwych kluczy dostawców.

Uruchom bramkę w osobnym terminalu:

litellm --config litellm.yaml --port 4000

Sprawdź, jakie aliasy widzi aplikacja:

curl http://localhost:4000/v1/models -H "Authorization: Bearer sk-lokalny-klucz"

W odpowiedzi powinny być identyfikatory local i cloud. Zwróć uwagę, że alias cloud pojawia się na liście nawet bez klucza OpenAI, bo bramka sprawdza go dopiero przy użyciu.

Jak przełączać między chmurą a modelem lokalnym?

Aplikacja używa tego samego klienta openai, ale kieruje go na bramkę:

from openai import OpenAI

client = OpenAI(base_url="http://localhost:4000", api_key="sk-lokalny-klucz")

response = client.chat.completions.create(
    model="local",   # albo "cloud"
    messages=[{"role": "user", "content": "Wyjaśnij, czym jest token."}],
)
print(response.choices[0].message.content)

Jedyna różnica względem chmury to wartość model. Żeby nie wpisywać jej w kodzie, wyciągniemy ustawienia do konfiguracji.

Jak trzymać ustawienia w pliku YAML i zmiennych środowiskowych?

Do tego służy pydantic-settings, rozszerzenie Pydantic do wczytywania konfiguracji (podstawy biblioteki Pydantic opisaliśmy w osobnym artykule). Plik config.yaml:

;yaml
gateway_url: http://localhost:4000
api_key: sk-lokalny-klucz
model: local
timeout_seconds: 300
max_chunk_tokens: 1500

I klasa ustawień w settings.py:

from pydantic_settings import (
    BaseSettings,
    PydanticBaseSettingsSource,
    SettingsConfigDict,
    YamlConfigSettingsSource,
)


class Settings(BaseSettings):
    model_config = SettingsConfigDict(yaml_file="config.yaml", env_prefix="LLM_")

    gateway_url: str
    api_key: str
    model: str = "local"
    timeout_seconds: int = 300
    max_chunk_tokens: int = 1500

    @classmethod
    def settings_customise_sources(
        cls,
        settings_cls: type[BaseSettings],
        init_settings: PydanticBaseSettingsSource,
        env_settings: PydanticBaseSettingsSource,
        dotenv_settings: PydanticBaseSettingsSource,
        file_secret_settings: PydanticBaseSettingsSource,
    ):
        # kolejność = priorytet: argumenty > zmienne środowiskowe > config.yaml
        return (init_settings, env_settings, YamlConfigSettingsSource(settings_cls))

Metoda settings_customise_sources decyduje, skąd i w jakiej kolejności są czytane wartości. U nas: argumenty przekazane do klasy, potem zmienne środowiskowe z prefiksem LLM_, na końcu plik YAML. Efekt:

python analyzer.py raport.txt                  # model z config.yaml (local)
LLM_MODEL=cloud python analyzer.py raport.txt  # tylko ten jeden raz chmura

Typy w klasie (int, str) są walidowane, więc literówka w konfiguracji (np. timeout_seconds: abc) kończy się czytelnym błędem już przy starcie programu, a nie w środku zapytania.

Czy mogę podłączyć kilka płatnych modeli naraz?

Tak. LiteLLM obsługuje ponad sto dostawców, a w model_list możesz mieć dowolnie wiele wpisów od różnych firm. Każdy ma własny alias i własny klucz:

;yaml
model_list:
  - model_name: local
    litellm_params:
      model: ollama_chat/llama3.2
      api_base: http://localhost:11434
  - model_name: tani
    litellm_params:
      model: openai/gpt-4o-mini
      api_key: os.environ/OPENAI_API_KEY
  - model_name: mocny
    litellm_params:
      model: anthropic/claude-sonnet-5-5
      api_key: os.environ/ANTHROPIC_API_KEY
  - model_name: gemini
    litellm_params:
      model: gemini/gemini-2.5-flash
      api_key: os.environ/GEMINI_API_KEY

Prefiks przed ukośnikiem (openai/, anthropic/, gemini/) mówi bramce, do którego dostawcy ma wysłać zapytanie. Aplikacja nadal ma jednego klienta openai i jeden adres, a o modelu decyduje wartość model w konfiguracji. Nazwy modeli dostawcy zmieniają, więc przed użyciem sprawdź aktualne identyfikatory w ich dokumentacji.

Uwaga: jeśli kilka wpisów dostanie ten sam model_name, LiteLLM zacznie rozdzielać ruch między nimi (równoważenie obciążenia). To osobna funkcja. Gdy chcesz wybierać model świadomie albo porównywać modele, każdy wpis musi mieć własny alias.

Czy do każdego zadania potrzebuję najmocniejszego modelu?

Nie, i to jest jeden z najlepszych powodów, żeby mieć kilka aliasów. Najmocniejszy model jest najdroższy i zwykle najwolniejszy, a wiele zadań jest prostych. To jak koszenie trawnika przed domem kombajnem: da się, ale nie ma po co. Podziel zadania według trudności:

Rodzaj zadaniaPrzykładJaki model
Proste, powtarzalneStreszczenie jednego fragmentu, klasyfikacja, wyciągnięcie pola z tekstuTani lub lokalny
ŚrednieOdpowiedź na pytanie z kontekstu, przeredagowanie tekstuŚredni
ZłożoneŁączenie wielu źródeł, rozumowanie krok po kroku, ważna decyzjaMocny

W analizatorze dokumentów widać to wyraźnie. Streszczanie każdego fragmentu osobno to praca, którą wykona tani model, a to ona generuje większość wywołań. Tylko ostatni krok, czyli połączenie streszczeń w jedno, wymaga staranności. Wystarczy dodać do ustawień drugi alias:

class Settings(BaseSettings):
    ...
    model: str = "tani"         # fragmenty: dużo prostych wywołań
    model_final: str = "mocny"  # scalanie: jedno wywołanie, które liczy się najbardziej

W kodzie przekazujesz do ask(...) odpowiedni alias zależnie od kroku. Koszt spada, bo większość tokenów przechodzi przez tańszy model, a jakość tam, gdzie ma znaczenie, zostaje wysoka. Zasada jest prosta: zacznij od najtańszego modelu, który daje akceptowalny wynik na kilku Twoich prawdziwych przykładach, i podnoś poziom tylko tam, gdzie ten wynik jest za słaby. Ile dokładnie zaoszczędzisz, zależy od cenników i od Twoich danych, więc zmierz to w raporcie zużycia tokenów, a nie zakładaj z góry.

Dlaczego nie robię automatycznego przejścia na chmurę (fallbacku)?

LiteLLM potrafi skonfigurować automatyczne przejście na inny model, gdy pierwszy zawiedzie. W tym cyklu świadomie tego nie włączamy, z trzech powodów:

  • Prywatność. Jeśli wybrałeś model lokalny, bo dane nie mogą opuścić komputera, ciche przekierowanie do chmury łamie tę decyzję dokładnie wtedy, gdy nikt nie patrzy.
  • Koszt. Awaria lokalnego modelu zamieniłaby się w płatne wywołania, o których dowiesz się z faktury.
  • Porównywalność. Wyniki testów mają mówić o jednym modelu. Mieszanie ich ukryłoby, który faktycznie odpowiedział.

Zamiast tego aplikacja ma zawieść głośno. Gdy nie ma połączenia z bramką, klient openai zgłasza APIConnectionError, a my zamieniamy go na zrozumiały komunikat:

from openai import APIConnectionError

try:
    response = client.chat.completions.create(model=settings.model, messages=messages)
except APIConnectionError as error:
    raise SystemExit(f"Brak połączenia z bramką {settings.gateway_url}: {error}")

Przełączenie na chmurę ma być Twoją decyzją (LLM_MODEL=cloud), a nie automatem. Dobrze jest też sprawdzić u siebie, co dokładnie zwraca bramka, gdy sama działa, a Ollama jest wyłączona: zobaczysz błąd serwera zamiast odpowiedzi, a o to nam chodzi.

Co robić, gdy bramka zwraca błąd zgodności z modelem?

Zdarza się, że zapytanie, które działa w OpenAI, kończy się błędem po stronie modelu lokalnego, np. o niedozwolonej roli wiadomości (Invalid role). Bramka tłumaczy format, ale nie zmieni możliwości samego modelu. Diagnozuj w tej kolejności:

  1. Zapytaj ten sam model bezpośrednio przez Ollamę (/v1 z poprzedniej części). Jeśli tam też jest błąd, problem leży w modelu lub w treści zapytania, nie w bramce.
  2. Uprość zapytanie do jednej wiadomości user i dodawaj kolejne elementy (rola system, historia), aż błąd wróci.
  3. Zajrzyj do logów bramki, w terminalu, w którym ją uruchomiłeś. Tam jest pełny komunikat dostawcy.

Jak sprawdzić, że wszystko jest podłączone dobrze?

Przed kodem sprawdź trzy rzeczy po kolei: czy Ollama odpowiada (curl localhost:11434/api/version), czy bramka widzi aliasy (/v1/models) i czy Settings() wczytuje to, czego oczekujesz (python -c "from settings import Settings; print(Settings())"). Jeśli któryś krok zawodzi, wiesz dokładnie, w której warstwie jest problem.

Co dalej?

W trzeciej części podepniemy do tej bramki analizator dokumentów z OpenAI i zrobimy porównanie modelu lokalnego z chmurowym: jakości streszczeń i czasu odpowiedzi na tych samych dokumentach.

Jeśli chcesz, żeby ktoś przejrzał Twoją konfigurację i architekturę projektu, zobacz mentoring w ścieżce Python.

Konsultacja i warsztaty devmentor.pl

Masz pytania?