Chroma, baza wektorowa, którą uruchomisz w minutę
Większość baz wektorowych zaczyna się od założenia konta, wybrania regionu i skonfigurowania indeksu. Chroma zaczyna się od instalacji pakietu i jednej linii kodu, a baza żyje w pliku obok Twojego projektu.
To podejście przez lata czyniło z niej domyślny wybór na etapie nauki i prototypu. Zmiana ostatnich lat polega na tym, że w wydaniu 1.0 z kwietnia 2025 roku rdzeń przepisano w Rust, co dało czterokrotny wzrost wydajności, a w sierpniu 2025 roku ruszyła wersja hostowana. Narzędzie do zabawy stało się narzędziem, na którym da się postawić produkcję.
Do czego służy baza wektorowa
Wyszukiwanie po słowach kluczowych znajduje dokumenty zawierające dane słowo. Wyszukiwanie wektorowe znajduje dokumenty o zbliżonym znaczeniu, nawet jeśli nie mają wspólnego słowa.
Mechanizm jest prosty w opisie. Tekst zamieniasz na wektor liczb, który reprezentuje jego znaczenie, a pytanie zamieniasz w ten sam sposób. Baza zwraca fragmenty, których wektory leżą najbliżej wektora pytania. Zdanie o wymianie hasła i zdanie o odzyskiwaniu dostępu do konta wypadną blisko siebie, choć nie mają wspólnego słowa.
Najczęstsze zastosowanie to podawanie modelowi kontekstu z własnych dokumentów. Model nie zna Twojej dokumentacji wewnętrznej, więc przed odpowiedzią pobierasz z bazy trzy najbardziej pasujące fragmenty i doklejasz je do promptu. Ta konstrukcja stoi za większością asystentów odpowiadających na pytania o firmowe treści.
Pierwsze uruchomienie
pip install chromadbimport chromadb
klient = chromadb.PersistentClient(path="./baza")
kolekcja = klient.get_or_create_collection("dokumentacja")
kolekcja.add(
documents=[
"Haslo zmieniasz w ustawieniach konta, zakladka Bezpieczenstwo",
"Faktury pobierzesz z panelu rozliczen, sekcja Historia platnosci",
"Konto usuwasz w ustawieniach, operacja jest nieodwracalna",
],
ids=["doc-1", "doc-2", "doc-3"],
)
wynik = kolekcja.query(query_texts=["jak odzyskac dostep"], n_results=2)
print(wynik["documents"][0])Nie ma tu kroku liczenia osadzeń, bo biblioteka robi to sama, domyślnie modelem lokalnym pobieranym przy pierwszym użyciu. To wygodne na start i wystarczające do prototypu, ale przy poważniejszym wdrożeniu warto wybrać model świadomie.
Klient trwały zapisuje dane w podanym katalogu, więc przetrwają restart procesu. Wariant bez ścieżki trzyma wszystko w pamięci i znika razem z programem, co nadaje się do testów i do niczego więcej.
Metadane, czyli miejsce, gdzie wygrywa się jakość
Samo podobieństwo wektorowe rzadko wystarcza. W praktyce potrzebujesz jeszcze filtrów: tylko dokumenty tego użytkownika, tylko z ostatniego roku, tylko w wybranym języku.
kolekcja.add(
documents=[tresc],
metadatas=[{"typ": "faq", "jezyk": "pl", "rok": 2026}],
ids=["faq-118"],
)
wynik = kolekcja.query(
query_texts=["jak zmienic plan"],
n_results=3,
where={"$and": [{"jezyk": "pl"}, {"rok": {"$gte": 2025}}]},
)Filtr działa przed wyszukiwaniem, więc zawęża zbiór, a nie odsiewa wyniki po fakcie. To różnica zasadnicza dla poprawności: bez tego mechanizmu dokument innego klienta może wypaść wyżej niż właściwy, bo semantycznie pasuje lepiej.
Zaplanuj metadane, zanim wypełnisz bazę. Dodanie pola do stu tysięcy wpisów oznacza ponowne wygenerowanie osadzeń albo aktualizację wsadową, a jedno i drugie kosztuje. Cztery pola, o które warto zadbać od początku, to identyfikator właściciela, źródło dokumentu, data i język.
Dzielenie dokumentów na fragmenty
Nie wrzucaj do bazy całych plików. Wektor liczony dla dwudziestostronicowego dokumentu opisuje wszystko naraz i przez to nic konkretnie, a model dostaje potem ścianę tekstu, w której odpowiedź stanowi jedno zdanie.
Rozsądny punkt wyjścia to fragmenty rzędu pięciuset do tysiąca znaków z niewielkim zachodzeniem na siebie. Zachodzenie chroni przed rozcięciem zdania, które niosło odpowiedź, akurat na granicy fragmentów.
Ważniejsza od liczby jest jednak zasada cięcia. Tnij po strukturze dokumentu, czyli po nagłówkach i akapitach, a nie co równą liczbę znaków. Fragment odpowiadający jednej sekcji dokumentacji jest lepszą jednostką wyszukiwania niż wycinek zaczynający się w połowie zdania.
Do każdego fragmentu warto dopisać w metadanych tytuł sekcji i adres źródła. Dzięki temu odpowiedź modelu może zawierać odnośnik, a użytkownik sprawdzi, skąd wzięła się informacja.
Wybór modelu osadzeń
Domyślny model lokalny jest szybki i darmowy, ale słabiej radzi sobie z językiem polskim niż modele wielojęzyczne. To najczęstsza przyczyna sytuacji, w której baza zwraca nietrafione fragmenty, a wina spada na bazę zamiast na osadzenia.
from chromadb.utils import embedding_functions
osadzenia = embedding_functions.SentenceTransformerEmbeddingFunction(
model_name="intfloat/multilingual-e5-base"
)
kolekcja = klient.get_or_create_collection("dokumentacja", embedding_function=osadzenia)Alternatywą jest model dostawcy chmurowego, płatny za tokeny, zwykle trafniejszy i wygodniejszy przy dużym wolumenie. Wybór sprowadza się do trzech pytań: czy dane mogą opuścić Twoją infrastrukturę, ile fragmentów zamierzasz zindeksować i czy zależy Ci na jakości w języku innym niż angielski.
Jedna rzecz jest tu nieodwracalna. Zmiana modelu osadzeń wymaga przeliczenia całej kolekcji, bo wektory z różnych modeli nie są porównywalne. Warto więc przetestować dwa albo trzy modele na próbce stu fragmentów, zanim zindeksujesz sto tysięcy.
Wyszukiwanie pełnotekstowe i podejście mieszane
Samo podobieństwo znaczeniowe zawodzi w jednym powtarzalnym przypadku: gdy użytkownik szuka dokładnego ciągu znaków. Numer zamówienia, nazwa funkcji albo kod błędu to rzeczy, których wektor nie odda, bo semantycznie niczego nie znaczą.
Chroma pozwala odpytać kolekcję również po treści dokumentu, więc obok wyszukiwania wektorowego masz filtr tekstowy.
wynik = kolekcja.query(
query_texts=["blad przy logowaniu"],
n_results=5,
where_document={"$contains": "ERR_AUTH_401"},
)Praktyczne podejście polega na łączeniu obu ścieżek. Uruchamiasz wyszukiwanie wektorowe i tekstowe, a wyniki scalasz, promując fragmenty, które pojawiły się w obu. Ten prosty zabieg podnosi trafność bardziej niż wymiana modelu osadzeń na większy, zwłaszcza w dokumentacji technicznej pełnej nazw własnych.
Warto też wiedzieć, kiedy wyszukiwanie wektorowe jest zwyczajnie złym narzędziem. Pytanie o najnowszy wpis albo o wszystkie zgłoszenia danego klienta to zapytanie do zwykłej bazy, nie do wektorowej. Wektory odpowiadają na pytanie o podobieństwo, nie o fakty.
Ta granica bywa źródłem rozczarowania przy pierwszym wdrożeniu. Użytkownik pyta o liczbę zamówień z zeszłego miesiąca, dostaje fragment dokumentacji o zamówieniach i uznaje, że system nie działa. Rozwiązaniem jest rozpoznanie rodzaju pytania i skierowanie go do właściwego źródła, a nie próba nauczenia bazy wektorowej liczenia.
Aktualizacje i usuwanie danych
Baza wiedzy nie jest statyczna. Dokumenty się zmieniają, a fragmenty usunięte ze źródła muszą zniknąć również z indeksu, inaczej model odpowie treścią, której już nie ma.
kolekcja.upsert(
ids=["faq-118"],
documents=[nowa_tresc],
metadatas=[{"typ": "faq", "jezyk": "pl", "rok": 2026}],
)
kolekcja.delete(where={"zrodlo": "stara-dokumentacja"})Klucz do porządku leży w identyfikatorach. Jeśli powstają losowo przy każdym indeksowaniu, ponowne przetworzenie dokumentu tworzy duplikaty zamiast aktualizować wpisy. Buduj identyfikator z adresu źródła i numeru fragmentu, wtedy ponowne indeksowanie jest bezpieczne.
Drugi mechanizm wart wdrożenia od początku to usuwanie po metadanych. Skoro każdy fragment zna swoje źródło, wycofanie całego dokumentu sprowadza się do jednego wywołania, a nie do wyszukiwania wpisów ręcznie.
Chroma Cloud i skala
Wersja lokalna działa w jednym procesie, więc jej granicą jest pojedyncza maszyna. Sprawdza się do kilkuset tysięcy fragmentów, przy większych zbiorach zaczyna doskwierać pamięć i czas budowania indeksu.
Wersja hostowana używa tego samego interfejsu, ale stoi na architekturze rozproszonej. Rozliczenie jest za faktyczne użycie, a dane leżą w warstwach o różnej temperaturze, gdzie rzadko odpytywane fragmenty schodzą do tańszego magazynu obiektowego. Baza żyje w wybranym regionie i go nie opuszcza, co bywa wymogiem formalnym.
Zgodność interfejsu jest tu najważniejsza w praktyce. Prototyp napisany na kliencie lokalnym przenosi się do wersji chmurowej przez zmianę sposobu tworzenia klienta, bez ruszania logiki zapytań. Niewiele baz daje taką ścieżkę.
Jest jeszcze trzecia droga, sensowna wtedy, gdy nie chcesz utrzymywać żadnego procesu bazy. Upstash udostępnia wyszukiwanie wektorowe po zwykłym HTTP i rozlicza je za żądanie, więc odpytasz je także z funkcji uruchamianych blisko użytkownika, które w ogóle nie pozwalają otworzyć gniazda TCP. Cena za to jest stała: każda operacja niesie narzut zapytania HTTP, więc przy pętli kilkudziesięciu wywołań ten model przestaje się opłacać.
Ceny
| Wariant | Koszt | Dla kogo |
|---|---|---|
| Otwarta, lokalnie | 0 USD | Nauka, prototyp, wdrożenie u siebie |
| Cloud, plan startowy | 0 USD plus zużycie, 5 USD kredytu na start | Mały projekt produkcyjny |
| Cloud, plan zespołowy | 250 USD miesięcznie plus zużycie, w tym 100 USD kredytu | Zespół, wyższe limity, wsparcie |
| Enterprise | wycena indywidualna | Wdrożenie we własnej chmurze, wymogi zgodności |
Obie płatne pozycje w tabeli to abonament powiększony o zużycie liczone osobno: zapis, magazynowanie, odpytywanie i ruch wychodzący mają własne stawki za gigabajt albo terabajt. Kredyty dołożone do planu zespołowego nie przechodzą na kolejny miesiąc, więc niewykorzystana pula po prostu przepada.
Warto też wiedzieć, co dzieje się po przekroczeniu limitu, bo mechanizm jest ostrzejszy, niż wygląda. Limity są dwa, Twój własny i ustawiony przez dostawcę, a przekroczenie któregokolwiek wstrzymuje usługę do czasu podniesienia progu. Rachunek nie rośnie w tle, tylko baza przestaje odpowiadać, co przy wdrożeniu produkcyjnym jest zdarzeniem wartym alertu.
Przy szacowaniu kosztu pamiętaj o pozycji spoza cennika bazy, czyli o osadzeniach. Zindeksowanie miliona fragmentów modelem płatnym potrafi kosztować więcej niż miesiąc utrzymania samej bazy, a jest to koszt jednorazowy przy pierwszym indeksowaniu i powtarzalny przy każdej zmianie modelu.
Jak sprawdzić, czy wyszukiwanie działa
To najbardziej pomijany etap w projektach opartych o wyszukiwanie semantyczne. Trafność ocenia się zwykle na kilku pytaniach zadanych ręcznie, co wystarcza do wrażenia, ale nie do decyzji.
Prosty zestaw testowy rozwiązuje ten problem. Zbierz trzydzieści prawdziwych pytań i przy każdym zapisz identyfikator fragmentu, który powinien się znaleźć w odpowiedzi. Potem policz, w ilu przypadkach ten fragment jest w pierwszej trójce.
trafienia = sum(
1 for pytanie, oczekiwany in zestaw
if oczekiwany in kolekcja.query(query_texts=[pytanie], n_results=3)["ids"][0]
)
print(f"Trafnosc: {trafienia}/{len(zestaw)}")Ta jedna liczba pozwala porównać dwa modele osadzeń, dwa sposoby cięcia dokumentów i wpływ dodania filtrów. Bez niej każda zmiana jest zakładem, a pogorszenie zauważa dopiero użytkownik.
Warto mierzyć osobno dwa przypadki, bo mieszają się ze sobą i mają różne przyczyny. Sytuacja, w której właściwy fragment jest w bazie, ale nie wypada wysoko, wskazuje na osadzenia albo na sposób cięcia. Sytuacja, w której fragmentu w ogóle nie ma, wskazuje na lukę w indeksowaniu i żadna zmiana modelu jej nie naprawi.
Chroma kontra alternatywy
| Narzędzie | Mocna strona | Słabość | Kiedy wybrać |
|---|---|---|---|
| Chroma | Najprostszy start, ta sama ścieżka do chmury | Mniej pokręteł niż w bazach dojrzalszych | Prototyp, który ma dojść do produkcji |
| Pinecone | Skala, dojrzałość operacyjna | Tylko wersja zarządzana, koszt przy dużym zbiorze | Duży ruch produkcyjny bez własnego zespołu |
| Qdrant | Filtrowanie i wydajność, wdrożenie u siebie | Więcej konfiguracji na starcie | Złożone filtry, dane we własnej infrastrukturze |
| pgvector | Wektory obok danych relacyjnych | Wolniejszy przy bardzo dużych zbiorach | Projekt już oparty o PostgreSQL |
Ostatni wiersz zasługuje na komentarz, bo bywa najlepszym wyborem, a najrzadziej rozważanym. Jeśli aplikacja i tak używa PostgreSQL, a fragmentów jest kilkadziesiąt tysięcy, rozszerzenie do istniejącej bazy oszczędza całą osobną usługę do utrzymania.
Praktyczna kolejność decyzji wygląda tak. Najpierw sprawdź, czy w ogóle potrzebujesz osobnej bazy, bo przy małym zbiorze nie potrzebujesz. Potem wybierz między wdrożeniem u siebie a usługą zarządzaną, kierując się tym, kto będzie to utrzymywał. Dopiero na końcu porównuj konkretne narzędzia, bo różnice między nimi są mniejsze niż różnica między dobrym a złym sposobem cięcia dokumentów.
Typowe błędy
Pierwszy to indeksowanie całych dokumentów zamiast fragmentów. Wynik wyszukiwania jest wtedy technicznie poprawny i praktycznie bezużyteczny, bo model dostaje dwadzieścia stron zamiast właściwego akapitu.
Drugi to brak filtrów po właścicielu danych. W aplikacji wielu użytkowników zapytanie bez ograniczenia zwraca fragmenty wszystkich, co jest wyciekiem, a nie usterką trafności.
Trzeci to zmiana modelu osadzeń bez przeliczenia kolekcji. Stare i nowe wektory leżą w różnych przestrzeniach, więc wyniki stają się przypadkowe, a błąd nie zgłasza się w żaden sposób.
Czwarty to zbyt wiele zwracanych fragmentów. Dwadzieścia wyników w prompcie kosztuje tokeny i rozprasza model, podczas gdy trzy trafne wystarczają. Zacznij od trzech i zwiększaj tylko wtedy, gdy widzisz brakujący kontekst.
Piąty to używanie klienta w pamięci na produkcji. Restart procesu kasuje całą bazę, a przy wdrożeniu wielokrotnym każda instancja ma własny, niespójny zbiór danych.
Szósty to pomijanie oceny jakości wyszukiwania. Trzydzieści prawdziwych pytań z zaznaczonym oczekiwanym fragmentem wystarczy, żeby porównać dwa modele osadzeń i dwa sposoby cięcia dokumentów. Bez tego zestawu strojenie sprowadza się do wrażeń.
FAQ
Czy Chroma nadaje się na produkcję?
Tak, po przepisaniu rdzenia w Rust i uruchomieniu wersji hostowanej. Wariant lokalny sprawdza się przy zbiorach do kilkuset tysięcy fragmentów i jednym procesie, a przy większej skali albo wielu instancjach właściwym wyborem jest wersja chmurowa lub inna baza rozproszona.
Chroma czy Pinecone?
Chroma wygrywa prostotą startu i tym, że ten sam kod działa lokalnie i w chmurze. Pinecone wygrywa dojrzałością operacyjną przy dużym ruchu i przewidywalnym opóźnieniem. Na etapie prototypu wybór Chromy nie zamyka drogi do zmiany później.
Czy potrzebuję osobnej bazy wektorowej?
Nie zawsze. Przy kilku tysiącach fragmentów wystarczy wyszukiwanie w pamięci albo rozszerzenie do PostgreSQL. Osobna baza zaczyna się opłacać przy dziesiątkach tysięcy wpisów, filtrach po metadanych i wymaganiu, żeby zapytanie wracało w kilkadziesiąt milisekund.
Czy dane mogą zostać u mnie?
Tak, wersja otwarta działa w całości lokalnie, a osadzenia można liczyć modelem uruchomionym przez Ollamę. W tym układzie żaden fragment dokumentu nie opuszcza Twojej infrastruktury.
Jak Chroma łączy się z frameworkami agentowymi?
Ma gotowe integracje z LangChainem i LlamaIndeksem, gdzie występuje jako magazyn wektorów w standardowym interfejsie. Można też używać jej bezpośrednio, co przy prostym wyszukiwaniu bywa czytelniejsze niż warstwa pośrednia.
Dokumentacja stoi na stronie projektu, a kod źródłowy w repozytorium na GitHubie.