# ADR-0005: Backend LLM wybierany per platforma, atrapa jako test double - Status: przyjęty - Data: 2026-08-29 ## Kontekst Docelowo pipeline korzysta z vLLM wystawionego na OpenShifcie. Do pracy nad samym pipeline'em potrzebny jest jednak model uruchamialny lokalnie - i tu kończy się jednorodność: **nie istnieje jedna komenda uruchamiająca vLLM na Linuksie i na macOS**. - Linux z GPU NVIDIA: kontener `vllm/vllm-openai`, ścieżka najbliższa produkcji. - macOS na Apple Silicon: Docker nie ma dostępu do Metala, więc kontener odpada. Zostaje natywny plugin społecznościowy `vllm-project/vllm-metal` (backend MLX), instalowany skryptem do własnego venva, wymagający arm64 Pythona 3.12 i Xcode CLT. - Maszyna bez GPU i bez Apple Silicon: żaden z powyższych nie ma sensu. Osobny problem: testy. Tryb `--offline` sprawdza `RuleBrain`, ale nie dotyka tego, co jest w tym projekcie najbardziej kruche - budowy agentów z definicji APM, wywoływania narzędzi przez model i parsowania strukturalnych wyjść. Uzależnienie tego testu od GPU oznaczałoby, że nie uruchomi go nikt poza jedną maszyną. ## Decyzja **Kontrakt jest jeden: endpoint zgodny z API OpenAI pod `CODEMOD_LLM_BASE_URL`.** Backendy różnią się wyłącznie tym, jak ten endpoint powstaje. Konfiguracja mieszka w `llm/models.yml` (jedno źródło prawdy: adresy, modele per rola, argumenty serwera), a `task llm:up` wykrywa platformę i uruchamia właściwy wariant: | Backend | Kiedy | Jak | |---|---|---| | `vllm-gpu` | Linux + NVIDIA + Docker | `docker compose --profile vllm-gpu` | | `vllm-metal` | macOS Apple Silicon | natywny plugin MLX, `vllm serve` | | `ollama` | awaryjnie, obie platformy | natywnie (Metal) lub kontener | | `mock` | testy, CI | `llm/mock_server.py`, stdlib, bez pobierania | Atrapa (`mock`) to **test double, nie symulator modelu**: odpowiada z góry ustalonymi wywołaniami narzędzi i strukturami zestrojonymi z fixture'em `acme-app`, a routing odpowiedzi opiera na nazwie agenta wstrzykiwanej przez agno do komunikatu systemowego. Dzięki niej `tests/test_llm_path.py` przepuszcza pełny przebieg przez `LlmBrain` na każdym MR, bez GPU i bez sieci. ## Konsekwencje **Pozytywne** - Projekt startuje od zera na Linuksie i na macOS jedną komendą; różnice platformowe są schowane za `task llm:up`, a nie rozsypane po README. - Ścieżka agentowa jest testowana automatycznie, nie tylko ręcznie na czyjejś maszynie. - Podmiana modelu lub backendu to zmiana w `llm/models.yml`, nie w kodzie. - Wszystkie backendy wymuszają `--enable-auto-tool-choice` z parserem `hermes`; bez tego vLLM zwraca opis wywołania w treści zamiast `tool_calls` i agent `coder` nie tknąłby żadnego pliku. **Negatywne** - Cztery ścieżki uruchomieniowe do utrzymania. Ograniczamy koszt tym, że wiedza o nich jest w jednym pliku YAML plus jednym skrypcie, a nie w zadaniach. - `vllm-metal` jest pluginem społecznościowym - może się rozjechać z upstreamem vLLM. Dlatego Ollama zostaje jako wariant awaryjny na macOS. - Atrapa jest zestrojona z fixture'em: zmiana fixture'u wymaga aktualizacji scenariusza. To świadomy koszt - alternatywą jest brak testu tej ścieżki. - Model 4B wystarcza do sprawdzenia hydrauliki, ale nie do realnych migracji. Do pracy trzeba większego - stąd tabela rekomendacji per rola w `llm/models.yml`. ## Rozważane alternatywy - **Tylko vLLM, macOS niech używa zdalnego endpointu.** Odpada: uniemożliwia pracę offline i uzależnia każdego developera od środowiska współdzielonego. - **Tylko Ollama.** Prostsze, ale rozjeżdża się z produkcją (inne API tool-callingu, inne kwantyzacje), więc testy przestałyby cokolwiek mówić o zachowaniu na vLLM. - **Brak atrapy, testy tylko na realnym modelu.** Niedeterministyczne i niedostępne w CI; test, który czasem przechodzi, jest gorszy niż brak testu.