CodeWorlds
Powrót do kolekcji
Przewodnik15 min czytaniaZespół CodeWorlds

Outlines, wymuszona struktura wyjścia modelu

Outlines maskuje tokeny niezgodne ze schematem w trakcie generowania. Wersja 1.3.3, podział na modele sterowalne i czarne skrzynki, zmiany z 1.0.

Outlines, wymuszona struktura wyjścia modelu

Outlines nie prosi modelu o poprawny JSON, tylko odbiera mu możliwość wygenerowania czegokolwiek innego. W każdym kroku dekodowania maskuje tokeny, które złamałyby schemat, więc wynik jest zgodny z konstrukcji, a nie z powodu udanej perswazji. Wersja 1.3.3 wyszła 6 sierpnia 2026 roku na licencji Apache 2.0.

Czym różni się wymuszanie od proszenia

Typowe podejście do ustrukturyzowanych odpowiedzi wygląda tak: wysyłasz prompt z opisem schematu, dostajesz tekst, próbujesz go sparsować, a jeśli się nie uda, pytasz jeszcze raz. To jest pętla proszenia i sprawdzania. Model w każdej chwili może wypluć zdanie wprowadzające, potrójny grot z nazwą języka albo przecinek na końcu tablicy, a Ty dowiadujesz się o tym po fakcie.

Outlines wchodzi o poziom niżej. Schemat zamienia na wyrażenie regularne, wyrażenie regularne na automat skończony, a automat na obiekt zwany logits processor. Ten obiekt dostaje przed każdym krokiem dekodowania wektor prawdopodobieństw dla całego słownika i zeruje te pozycje, które w bieżącym stanie automatu są niedozwolone. Jeśli automat jest w stanie po otwarciu klucza "total" i dwukropku, to wszystkie tokeny inne niż cyfra, minus albo spacja mają prawdopodobieństwo zero. Model fizycznie nie ma z czego wybrać.

Konsekwencje są dwie i obie trzeba rozumieć, zanim się zdecydujesz. Pierwsza jest dobra: nie istnieje ścieżka, na której wynik nie parsuje się do schematu, więc znika cała kategoria błędów i cały kod obsługujący ponowienia. Druga jest mniej przyjemna: gwarancja jest wyłącznie składniowa. Model zmuszony do wypełnienia pola currency wpisze tam trzy litery, ale nikt nie obiecuje, że będą to litery właściwej waluty. Wymuszona struktura nie robi z halucynacji prawdy, tylko nadaje jej regularny kształt.

Budowaniem automatu zajmuje się jeden z trzech backendów. Domyślnym dla schematów JSON i dla wyrażeń regularnych jest outlines_core, czyli towarzyszący pakiet w Rust, przypięty dokładnie do wersji 0.2.14. Dla gramatyk bezkontekstowych domyślny jest llguidance. Trzecia opcja to xgrammar. Backend wskazujesz argumentem backend w konstruktorze Generator, a stałe JSON_SCHEMA_DEFAULT_BACKEND, REGEX_DEFAULT_BACKEND i CFG_DEFAULT_BACKEND w module outlines.backends mówią wprost, co jest wybierane, gdy nic nie podasz.

Kompilacja automatu kosztuje czas. Docstring klasy SteerableGenerator mówi o tym wprost: procesor bywa drogi w budowie, dlatego generator go przechowuje. Praktyczny wniosek jest taki, że Generator tworzysz raz na schemat i trzymasz, a nie budujesz w pętli po żądaniach. Dodatkowo działa pamięć podręczna na dysku, domyślnie w katalogu HOMEDIR/.cache/outlines, sterowana zmienną OUTLINES_CACHE_DIR, a w drugiej kolejności XDG_CACHE_HOME. Funkcje outlines.clear_cache, outlines.disable_cache i outlines.get_cache są wystawione na najwyższym poziomie pakietu.

Modele sterowalne i modele w czarnej skrzynce

To jest sedno całej biblioteki i najczęstsze nieporozumienie. Maskowanie tokenów wymaga dostępu do rozkładu prawdopodobieństwa, a więc do wag modelu albo do silnika, który taki dostęp udostępnia. Outlines dzieli więc integracje na dwie grupy i robi to w kodzie, nie w dokumentacji.

W pliku outlines/models/__init__.py typ SteerableModel to suma dokładnie trzech klas: LlamaCpp, MLXLM i Transformers. Tylko dla nich Outlines sam buduje logits processor i sam wymusza schemat. Typ BlackBoxModel obejmuje Anthropic, Dottxt, Gemini, LMStudio, Ollama, OpenAI, Mistral, SGLang, TGI, VLLM oraz VLLMOffline, a AsyncBlackBoxModel dokłada asynchroniczne warianty ośmiu z nich. Dla tej drugiej grupy typ wyjściowy nie jest kompilowany lokalnie, tylko przekazywany dostawcy w takiej formie, jaką dostawca rozumie.

Fabryka Generator sprawdza to isinstance i zwraca inną klasę: SteerableGenerator albo BlackBoxGenerator. Jeśli spróbujesz podać własny procesor argumentem processor do modelu z drugiej grupy, dostaniesz NotImplementedError z komunikatem, że ten model nie obsługuje logits processors. To uczciwe postawienie sprawy, ale trzeba je zauważyć przed wdrożeniem.

Co się dzieje przy dostawcach zamkniętych, widać najlepiej w metodzie format_output_type adaptera OpenAI. Model Pydantic zamienia się na response_format typu json_schema z ustawionym strict: true i z wymuszonym additionalProperties: false. Zwykły dict daje tryb json_object. Natomiast typ Regex kończy się wyjątkiem TypeError z informacją, że ani wyrażenia regularne, ani słowo kluczowe pattern w schemacie nie są dostępne u OpenAI, a typ CFG wyjątkiem o braku gramatyk. Ollama zachowuje się analogicznie i też odrzuca Regex oraz CFG. Model Dottxt, czyli komercyjne API firmy stojącej za biblioteką, odrzuca oba z komunikatem, że będą dostępne wkrótce.

Wniosek jest prosty. Jeżeli Twoim dostawcą jest OpenAI albo Anthropic, Outlines nie daje Ci żadnej gwarancji ponad tę, którą i tak daje dostawca. Jest wtedy warstwą ujednolicającą interfejs, a nie mechanizmem wymuszającym. Gwarancja z maskowania tokenów pojawia się dopiero przy from_transformers, from_llamacpp i from_mlxlm, czyli wtedy, gdy wagi są w Twoim procesie.

Osobna pułapka dotyczy vLLM. Adapter serwerowy wysyła ograniczenia w polu structured_outputs wewnątrz extra_body. Komentarz w kodzie mówi, że to pole wymaga serwera vLLM w wersji co najmniej 0.12, która zastąpiła starsze klucze guided_json, guided_regex i guided_grammar, a starsze serwery po prostu ignorują nowe pole i zwracają wynik bez ograniczeń. Cicha degradacja do braku gwarancji jest gorsza od błędu, bo nie widać jej w logach. Wariant offline VLLMOffline buduje StructuredOutputsParams i wkłada je do SamplingParams, ale nadal liczy się jako czarna skrzynka, bo wymuszaniem zajmuje się silnik vLLM, nie Outlines.

ModelFunkcja ładującaKto wymusza schematRegexGramatyka
Transformersfrom_transformersOutlines lokalnietaktak
llama.cppfrom_llamacppOutlines lokalnietaktak
MLXfrom_mlxlmOutlines lokalnietaktak
OpenAIfrom_openaidostawcanie, TypeErrornie, TypeError
Ollamafrom_ollamadostawcanie, TypeErrornie, TypeError
vLLM serwerfrom_vllmserwer vLLMtak, od wersji 0.12tak, od wersji 0.12
vLLM offlinefrom_vllm_offlinesilnik vLLMtaktak

Wersja 1.0 i zerwanie z interfejsem v0

Wersja 1.0.0 trafiła do PyPI 18 czerwca 2025 roku, a ostatnie wydanie z gałęzi zerowej, 0.2.3, pochodzi z 3 kwietnia 2025 roku. Plik release_note.md, dołączony do opublikowanej paczki, wyjaśnia zamiar: nowa wersja zawęża Outlines do samego generowania sterowanego i oddaje resztę zadań bibliotekom inferencyjnym oraz użytkownikowi.

Cztery zmiany łamią wszystko, co napisano wcześniej. Po pierwsze, funkcje ładujące z modułu models, czyli models.transformers czy models.openai, zastąpiono odpowiednikami z przedrostkiem from_, które przyjmują gotową instancję silnika albo klienta zamiast nazwy modelu i słownika parametrów. Po drugie, moduł generate razem z funkcjami generate.json i generate.choice zniknął na rzecz jednego konstruktora Generator, któremu podaje się dowolny obsługiwany typ wyjściowy. Po trzecie, TransformersVision ustąpił miejsca TransformersMultiModal, ładowanemu przez from_transformers z procesorem zamiast tokenizera. Po czwarte, klasa Function została zastąpiona przez Application, która nie przyjmuje modelu przy tworzeniu, tylko przy wywołaniu, a zmienne szablonu bierze ze słownika, nie z argumentów nazwanych. Integracja z ExLlamaV2 została usunięta bez zamiennika.

Elementy przestarzałe działały z ostrzeżeniem tylko do wersji 1.1.0, która wyszła 10 lipca 2025 roku, czyli niecałe cztery tygodnie po 1.0.0. Każdy poradnik napisany przed połową 2025 roku pokazuje kod, który dziś nie uruchomi się w ogóle. Przy szukaniu przykładów w sieci to jest realny koszt, bo starszych wpisów jest wciąż więcej niż nowych.

Tempo wydań jest nierówne. Po 1.2.0 z 31 lipca 2025 roku przyszła długa seria poprawek, a 1.3.0 pojawiło się dopiero 13 maja 2026 roku. Dalej było gęściej: 1.3.1 trzydziestego czerwca, 1.3.2 dwudziestego lipca i 1.3.3 szóstego sierpnia 2026 roku. Drobna nieścisłość w kanale wydań: pozycja o tytule Outlines v1.2.12 występuje w nim dwa razy, ze znacznikami czasu z 3 marca i 4 maja 2026 roku. Data z PyPI dla 1.3.0 zgadza się z datą z kanału wydań co do dnia, więc same numery są wiarygodne.

Instalacja, zależności i licencja

Rdzeń biblioteki nie ciągnie za sobą żadnego silnika. Zależności podstawowe to jinja2, cloudpickle, diskcache, pydantic od 2.0, jsonschema, pillow, typing_extensions, genson oraz outlines_core przypięty dokładnie do 0.2.14, wydanego 9 stycznia 2026 roku, także na Apache 2.0. Wymagany Python to co najmniej 3.10 i mniej niż 3.14.

Code
Bash
# sam rdzeń, bez silnika
pip install outlines

# model lokalny przez transformers, jedyna droga do pełnej gwarancji na GPU
pip install "outlines[transformers]"

# llama.cpp oraz MLX na Apple Silicon
pip install "outlines[llamacpp]"
pip install "outlines[mlxlm]"

# uwaga: to rozszerzenie instaluje tylko pakiet openai,
# bo Outlines rozmawia z serwerem vLLM po jego API zgodnym z OpenAI
pip install "outlines[vllm]"

# alternatywne backendy budujące automat
pip install "outlines[xgrammar]"
pip install "outlines[llguidance]"

Rozszerzenie outlines[vllm] jest najczęstszym źródłem zdziwienia. W metadanych paczki widnieje pod nim jedna zależność, openai, bo integracja z vLLM idzie przez API zgodne z OpenAI. Samego vLLM musisz zainstalować osobno, a jeśli chcesz trybu offline, to i tak potrzebujesz pakietu vllm w środowisku. Podobnie outlines[sglang] sprowadza tylko openai, a outlines[tgi] tylko huggingface_hub.

Licencja jest przypadkiem wzorcowym i po serii projektów, w których deklaracja rozjeżdża się z zawartością, to przyjemna odmiana. Trzy niezależne źródła mówią to samo. Pole license w PyPI zawiera Apache-2.0, czyli poprawny identyfikator SPDX, a nie wklejony cały tekst licencji. Plik LICENSE w gałęzi głównej repozytorium dottxt-ai/outlines odpowiada kodem 200 i zawiera pełny tekst Apache 2.0 z notą Copyright 2023- The Outlines developers, a wariant LICENSE.md nie istnieje i zwraca 404. Opublikowane koło outlines-1.3.3-py3-none-any.whl ma w katalogu outlines-1.3.3.dist-info/licenses/ jeden plik, LICENSE, również z tekstem Apache 2.0, i zawiera prawdziwy kod, a nie samą metadaną. Jedyne, czego brakuje, to klasyfikator License :: OSI Approved w liście klasyfikatorów PyPI, w której figuruje wyłącznie Programming Language :: Python :: 3. Narzędzia czytające klasyfikatory zamiast pola license mogą więc pokazać puste miejsce.

ŹródłoCo deklarujePlik obecny
Pole license w PyPIApache-2.0nie dotyczy
LICENSE w repozytoriumApache 2.0, Copyright 2023- The Outlines developerstak, kod 200
LICENSE.md w repozytoriumbraknie, kod 404
Koło outlines-1.3.3-py3-none-any.whlpełny tekst Apache 2.0tak, dist-info/licenses/LICENSE
Klasyfikatory PyPIbrak wpisu o licencjinie dotyczy

Ryzyko dostawcy warto rozważyć osobno. Projekt prowadzi firma dottxt, repozytorium ma około 15,7 tysiąca gwiazdek i jest aktywnie rozwijane, ale ta sama firma sprzedaje komercyjne API, dostępne w bibliotece jako from_dottxt. Strona dottxt.ai nie publikuje cennika, adres z podstroną cennika zwraca 404, a jedyną ścieżką jest formularz kontaktowy. Jeśli planujesz oprzeć się na płatnym wariancie, musisz najpierw porozmawiać z handlowcem, bo liczb nie ma publicznie. Sama biblioteka na Apache 2.0 działa oczywiście bez tego.

Poza JSON Schema: wyrażenia regularne i gramatyki

Obsługa schematów JSON to funkcja stołowa i mają ją wszyscy, łącznie z Instructorem i Pydantic AI. Odróżniające jest to, co Outlines potrafi poza nimi.

Moduł outlines.types wystawia funkcje regex, cfg i json_schema oraz klasy Regex, CFG, Choice i JsonSchema. Do tego dochodzi mały język do składania wzorców: either, optional, exactly, at_least, at_most, between, one_or_more i zero_or_more. Podmoduł outlines.types.locale zawiera gotowe wzorce, na razie tylko amerykańskie: locale.us.zip_code to \d{5}(?:-\d{4})?, a locale.us.phone_number to wzorzec numeru w formacie z nawiasami albo z myślnikami. Podmoduły airports i countries istnieją, ale wymagają rozszerzeń outlines[airports] i outlines[countries], a bez nich rzucają ImportError z instrukcją instalacji.

Gramatyki bezkontekstowe zapisuje się w składni Lark. W module outlines.grammars gotowe są dwie: grammars.json i grammars.arithmetic, wczytywane funkcją read_grammar z katalogu z plikami json.lark, arithmetic.lark i common.lark. Własną gramatykę podajesz jako łańcuch znaków przez cfg. To jedyna droga, gdy potrzebujesz wymusić język, którego nie da się opisać schematem JSON: fragment SQL, wyrażenie w Twoim mini języku konfiguracji, komendę robota.

Code
Python
import outlines
from pydantic import BaseModel
from transformers import AutoModelForCausalLM, AutoTokenizer

class Invoice(BaseModel):
    number: str
    total: float
    currency: str

model = outlines.from_transformers(
    AutoModelForCausalLM.from_pretrained("microsoft/Phi-3-mini-4k-instruct"),
    AutoTokenizer.from_pretrained("microsoft/Phi-3-mini-4k-instruct"),
)

# generator buduje automat raz, więc trzymaj go poza pętlą żądań
generator = outlines.Generator(model, Invoice, backend="outlines_core")

raw = generator("Wypisz fakturę numer FV/2026/114 na 1249.90 PLN")
invoice = Invoice.model_validate_json(raw)

Zwróć uwagę na ostatnią linię. Generator zwraca łańcuch znaków, a nie obiekt Pydantic. Metoda SteerableGenerator.__call__ woła self.model.generate(prompt, self.logits_processor, ...) i oddaje to, co zwrócił model. Parsowanie do obiektu jest po Twojej stronie i tym Outlines różni się od Instructora, który oddaje gotową instancję.

Code
Python
from outlines.types import regex, either, cfg, json_schema, locale

# numer faktury w jednym konkretnym formacie
faktura = regex(r"FV/\d{4}/\d{1,4}")

# wybór z zamkniętej listy, bez zgadywania
priorytet = either("niski", "sredni", "wysoki")

# gotowy wzorzec z podmodułu locale
kod_pocztowy = locale.us.zip_code

# gramatyka Lark jako łańcuch znaków
lista = cfg(r"""
start: "[" item ("," item)* "]"
item: /[a-z]+/
""")

# schemat JSON podany wprost, z kontrolą białych znaków
schemat = json_schema({"type": "object", "properties": {"n": {"type": "integer"}}})

generator = outlines.Generator(model, faktura)

Funkcja python_types_to_terms przyjmuje sporo typów wprost: int, float, bool, str, dict bez parametrów (tłumaczony na gramatykę grammars.json), time, date, datetime, klasy Enum, Literal, Union, list, tuple, sparametryzowany dict, modele Pydantic i pokrewne struktury, budowniczego schematów z biblioteki genson, a nawet sygnaturę funkcji. Ma jednak twardy limit zagnieżdżenia: przekroczenie dziesiątego poziomu rekursji kończy się wyjątkiem RecursionError. Głęboko zagnieżdżone albo rekurencyjne modele danych po prostu nie przejdą.

Outlines kontra Instructor, rachunek

Najkrótsze możliwe ujęcie różnicy brzmi tak: Instructor prosi model i ponawia, gdy walidacja nie przejdzie, więc działa z każdym dostawcą, ale płaci za ponowienia i niczego nie gwarantuje. Outlines gwarantuje, ale przy modelach sterowalnych wymaga wag w Twoim procesie. Reszta to szczegóły.

Rachunek dla Instructora policzmy na jawnym założeniu, bo nie mam pomiaru z produkcji: przyjmijmy 1200 tokenów wejścia i 300 tokenów wyjścia na żądanie oraz jedno ponowienie potrzebne w 5 procentach przypadków. Przy tysiącu żądań daje to 1000 wywołań podstawowych plus 50 ponowień, czyli 1050 wywołań i 5 procent narzutu na liczbie zapytań. Wejście rośnie z 1 200 000 do 1 260 000 tokenów, wyjście z 300 000 do 315 000. To założenie, nie pomiar, i przy trudnym schemacie odsetek błędów bywa wielokrotnie wyższy. Domyślne max_retries=3 w metodzie create Instructora oznacza w najgorszym razie cztery płatne wywołania na jedno żądanie użytkownika.

Po stronie Outlines z modelem sterowalnym ponowień nie ma, bo niezgodny wynik nie może powstać. Płacisz za co innego: za czas GPU utrzymywany niezależnie od ruchu, za kompilację automatu przy starcie i za pracę operacyjną wokół własnego silnika, którą przy LiteLLM czy Ollama możesz sobie darować. Próg opłacalności zależy więc od obciążenia, a nie od samej technologii. Przy kilkuset żądaniach dziennie płatny dostawca z ponowieniami wychodzi taniej niż karta w chmurze. Przy stałym, dużym strumieniu proporcje się odwracają.

Jest też przewaga Instructora, o której łatwo zapomnieć przy porównywaniu gwarancji. Walidatory Pydantic potrafią sprawdzać sens, a nie tylko kształt: że data jest z przyszłości, że suma pozycji zgadza się z kwotą całkowitą, że identyfikator istnieje w bazie. Automat ze schematu tego nie wyrazi. Jeśli Twoim problemem jest treść, a nie składnia, maskowanie tokenów go nie rozwiąże i sensowniej sięgnąć po Instructora albo Guardrails AI.

CechaOutlines, model sterowalnyOutlines, czarna skrzynkaInstructor
Gwarancja składniowaz konstrukcjitaka jak u dostawcybrak, walidacja po fakcie
Ponowienianie występujązależnie od dostawcymax_retries, domyślnie 3
Wyrażenia regularnetakzwykle TypeErrornie
Gramatyki Larktaktylko vLLM i SGLangnie
Walidacja semantycznanienietak, walidatory Pydantic
Wymaga wag lokalnietaknienie
Zwracałańcuch znakówłańcuch znakówobiekt Pydantic
LicencjaApache 2.0Apache 2.0MIT

Typowe błędy

Pierwszy i najkosztowniejszy to założenie, że from_openai daje gwarancję z maskowania tokenów. Nie daje. Dostajesz response_format dostawcy, dokładnie to samo, co bez Outlines, tylko przez inny interfejs. Kod, który przechodzi testy lokalnie na from_transformers, po przełączeniu na OpenAI traci własność, na której go oparto.

Drugi to cicha degradacja przy vLLM starszym niż 0.12. Pole structured_outputs jest wtedy ignorowane, żądanie kończy się kodem sukcesu, a wynik nie ma żadnych ograniczeń. Sprawdź wersję serwera, zanim uznasz, że wymuszanie działa, i dodaj po swojej stronie asercję parsowania jako siatkę bezpieczeństwa.

Trzeci to budowanie Generator wewnątrz obsługi żądania. Kompilacja automatu ze złożonego schematu potrafi trwać zauważalnie długo, a klasa jest zaprojektowana tak, żeby procesor zbudować raz i trzymać. W usłudze sieciowej twórz generatory przy starcie procesu, po jednym na schemat.

Czwarty to mylenie poprawności składniowej z prawdziwością. Wymuszony schemat gwarantuje, że pole total będzie liczbą. Nie gwarantuje, że to właściwa liczba z faktury. Warstwa sprawdzająca sens jest nadal potrzebna.

Piąty to kopiowanie kodu z poradników sprzed połowy 2025 roku. Wywołania models.transformers(...) i generate.json(...) zniknęły w 1.1.0. Jeśli przykład nie zawiera from_ w nazwie funkcji ładującej ani konstruktora Generator, jest sprzed przełomu.

Szósty to instalacja outlines[vllm] w przekonaniu, że pojawi się vLLM. Pojawi się openai. Sprawdź listę zależności rozszerzenia, zanim zbudujesz obraz kontenera bez faktycznego silnika. Modele lokalne pobierzesz zwykle z Hugging Face, co jest osobnym krokiem i osobnym kosztem miejsca na dysku.

FAQ

Czy Outlines działa z OpenAI i Anthropic?

Działa, ale w trybie czarnej skrzynki. Ograniczenie jest przekazywane dostawcy jako response_format, a Outlines nie widzi rozkładu prawdopodobieństwa i niczego nie maskuje. Wyrażenia regularne i gramatyki są tam niedostępne i kończą się wyjątkiem TypeError z jasnym komunikatem.

Kiedy wyszła wersja 1.0 i czy stare poradniki działają?

Wersja 1.0.0 pojawiła się w PyPI 18 czerwca 2025 roku. Przestarzały interfejs został usunięty w 1.1.0 z 10 lipca 2025 roku. Kod używający models.transformers czy generate.json nie uruchomi się na obecnej wersji 1.3.3.

Czy gwarancja oznacza, że odpowiedź jest poprawna merytorycznie?

Nie. Gwarancja dotyczy wyłącznie kształtu: wynik zawsze da się sparsować do schematu. Treść pól może być zmyślona i nadal potrzebujesz walidacji semantycznej albo sprawdzenia w źródle prawdy.

Który backend wybrać?

Domyślne ustawienia są sensowne: outlines_core dla schematów JSON i wyrażeń regularnych, llguidance dla gramatyk. Do xgrammar albo llguidance sięgnij, gdy budowa automatu z Twojego schematu jest zbyt wolna, i porównaj czasy na własnym przypadku, bo różnice zależą od kształtu schematu.

Czy Outlines zastępuje Instructora?

Nie w każdym scenariuszu. Zastępuje go tam, gdzie masz wagi lokalnie i zależy Ci na gwarancji bez ponowień. Nie zastępuje tam, gdzie korzystasz z zamkniętego dostawcy albo potrzebujesz walidatorów sprawdzających sens danych, a nie ich kształt.

Jak duży schemat Outlines udźwignie?

Twardym ograniczeniem jest dziesiąty poziom rekursji w python_types_to_terms, powyżej którego dostajesz RecursionError. Miękkim ograniczeniem jest czas kompilacji automatu, rosnący razem ze złożonością schematu. Zmierz go na swoim schemacie przed wdrożeniem.

Dokumentacja projektu leży pod adresem dottxt-ai.github.io/outlines, kod źródłowy w repozytorium dottxt-ai/outlines, a wydania i metadane w PyPI.

Czytaj dalej

Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie