Галоўная / Артыкулы

Адзін Retriever, трыя бэкенда: перанос алгорытму пошуку NumPy у Chroma, FAISS, Pinecone

Перанесіце ручная версія алгоритму пошуку косынуса з NumPy у Chroma, FAISS і Pinecone, а таксама дазнаецеся, які функцыі вносзяць індэксы, фільтры метаданых і можлівасць зберагчыць інфармацыю, а таксама пра падзеяхі, якія паслабляюць релевантнасць.

Выдавец: REACTAPP.TOP11 хв чытання2493 слоў

Крыніцы: Vector Databases, Hands-On — From Your numpy Array to Pinecone

Самым простым сховішчам вектораў є матрыца NumPy з эмбеддінгамі плюс адна лінія арыметычных вырахункаў: chunk_vectors @ q_vec практыкуе оцэнку кожнага фрагмента па вопыту, а сортаванне выбірае найкращыя рэзультаты. Гэта справжняя векторная база дадзеных, проста як можна мінімальная. У цым кераванні мы бераме самэ гэтае засоба для пошуку і переносімо яго на тры рэальныя бэкенды — Chroma, FAISS і Pinecone, ўпрымоўкі, каб вы моглі точна пазнакоміцца з тым, што дадае кожны з іх, які з багатых продактав (Chroma, FAISS, Qdrant, Weaviate, Milvus, pgvector, Pinecone) падходзіць для кожай ситуацыі, і якія памылкі таямна паспрабоўваюць зіпсаваць релевантнасць.

Местныя прыклады не патрабуюць ключаў API і не купуюцца. Pinecone адказвае безкоштовным серверлессам стартовым ўровнем, які выказывае патрэбу ў регистрацыі і ключы, але на момент напісання не патрабуе карточкі платежа, таму можна безкоштовна спробаваць і кераваны варіант. Код прыпускае наявнасць аднаго скрыпта vdb.py, які запускаецца блак за блакам.

Калі масів NumPy вяршыцца

Ручны алгорытм вычысляе косінусную схожасць з кожным зафіксаваным фрагментам пад кожны запит. З каляквітасцю фрагментав выкаонанне вяршыцца мгновэна. Калі ж фрагментав каля мільйона, працэс становіцца повольным, весь масіў патрабуецца ў RAM, а кожны эмбеддінг зникае пасля завершэння працэсу. База дадзеных вектароў усуне тры галінкі:

  1. Скорасць у масштабе. У працэ заместо точнага параболічнага аналізу з кожным векторам, ствараецца апроксиматыўны індэкс найбліжэйшых суседаў, такі як HNSW, який знаходзіць майже найбліжэйшыя векторы за час, прыблізна равны вырастку логарыма з n. Часткова падваўляецца точнасць аднаходжэння даных, але значна прыбывае скорасць.
  2. Стойкасць. Векторы зберагаюцца на дыску або на кіраванам сервере і застаюцца доступнымі пасля перзапуску.
  3. Метаданы і фільтрацыя. Кожны вектор можа мець атрыбуты, такія як {"source": "doc12", "date": "2026-01"}, і запит можа быць абмежаны ўсёма векторамі з такімі атрыбутамі, напрыклад, найбліжэйшымі векторамі, дзе істочнік — doc12.

Разлік между гэтымі двума спосабамі аднаходжэння даных можна выразіць за два рэчы:

your numpy array:   query ──► compare to ALL n vectors ──► sort ──► top-k     (exact, slow at scale)
a vector database:  query ──► walk HNSW graph ──► ~top-k     (approximate, fast, on disk, filterable)

Гэта ўсё сутнае прызначэнне. Усё, што следуе, — це тэхнічныя аспекты.

Словнік, з яким вам давацца сталкаць

  • Умяшчэнне або вектар: спіс цых лякучых числаў, які кодуе значэнне; у прыкладах тут выкарыстоўваецца all-MiniLM-L6-v2.
  • Метрыка адстані: спосаб меры блізкасці. Косай кант (кут межы вектараў) ёсць стандартны выбор для тексту; скалярны добутак і эвклідава (L2) метрыка ўжо як альтэратывы. Выбірайце тую, для якой быў натрэнаваны ваша модель умяшчэння.
  • Індекс: структура, якая спрыяе шырокамасштабным пошукам. HNSW, як навігабельны граф, ёсць стандартам у большасці баз дадзенаў; IVF і PQ разнымі спосабамі паказваюць кампроміс межы памяцю і швальнасцю.
  • Upsert: дадаць вектар па ID або апдэйтаваць яго, якщо ID вялік.
  • Фільтрацыя метаданых: сузэнне пошуку за дапамою збераганых атрыбутаў.
  • Прастор імен або калекцыя: раздзел унутрь адной базы дадзеных, такі ў яком несувязаныя даны ніколі не сумешваюцца.
  • Chroma: натуральны першы апград

    Chroma працюе ўнутрь вашага процэсу Python пасля адной установкі, без сервера і без Docker. Чырвоныя гэтае яго прыводзіць да таго, што ён стае найболей прымітным спосабам пераходу з масівай NumPy. Установіце яго разам з бібліятэкай embedding:

    pip install chromadb sentence-transformers
    

    У наведанам выкладзе скрыпт выкарыстоўвае невялікі корпус, який описвае вымышлены продукт для запісу нотатак пад назвай Nimbus, з тэгам тэмы для кожнага дакумента. Ён стварае кліента ў памяці, ачынае колекцыю, настроеную для вычыслення косай вялічыны на адстань за дапамогою hnsw:space, явна уключае дакументы і дадзяўае ўсім іх ID-ы і метаданы. Потым ён уключае запит і запрашвае два найбліжэйшыя дакументы, выдрукаваючы кожны з іх адстаннёй. Зверніце увагу на каментар да chromadb.Client(): дастатньва прыменіць PersistentClient(path=...), ўсё, што трэба, каб даны засталіся на дыску.

    # vdb.py
    import chromadb
    from sentence_transformers import SentenceTransformer
    
    embedder = SentenceTransformer("all-MiniLM-L6-v2")
    
    # Same Nimbus corpus from the RAG tutorial
    DOCS = [
        "Nimbus Pro costs 8 dollars per month billed annually, with 50 GB storage.",
        "Nimbus offers a 30-day refund policy on all paid plans.",
        "Nimbus Lite (free) includes 1 GB of storage and up to 50 notes.",
        "Nimbus stores notes encrypted at rest with AES-256; E2E encryption is Pro-only.",
        "Live chat support is Pro-only, Mon-Fri 9-6 UTC; free users get 48h email support.",
    ]
    metadatas = [{"topic": t} for t in ["pricing","refund","pricing","security","support"]]
    
    client = chromadb.Client()                       # in-memory; use PersistentClient(path=...) to save to disk
    collection = client.create_collection(
        "nimbus", metadata={"hnsw:space": "cosine"}   # choose the distance metric
    )
    
    # Embed and add. (↔ your chunk_vectors = embedder.encode(...))
    collection.add(
        ids=[f"doc{i}" for i in range(len(DOCS))],
        embeddings=embedder.encode(DOCS).tolist(),
        documents=DOCS,
        metadatas=metadatas,
    )
    
    # Query. (↔ your retrieve(): embed query, find nearest)
    q = "How much does the paid plan cost?"
    res = collection.query(query_embeddings=embedder.encode([q]).tolist(), n_results=2)
    for doc, dist in zip(res["documents"][0], res["distances"][0]):
        print(f"[{dist:.3f}] {doc}")
    

    Эта версія адаптавана да ручнай версіі, і па сутнасці нічога новага няма. collection.add заменяе процэс кодавання дакументаў і зберагчыцьця матрыцы. collection.query заменяе вычысленне матрыцэйнага добутку, функцію argsort і аперэйцію выбору топ-k элементаў. Чым спадзяецца Chroma, такім ёсць індэкс HNSW, можлівасць стойкага зберагчыцьця дакументаў і падтрымка метаданых, якія базуюцца на той жа ідэі.

    Chroma таксама можа аўтоматычна укладзіць текст за дапамогою вбудованага модэлю, якщо паслаць параметр documents= без embeddings=. Якщо ж укладанне текста выкарыстоўваць явна, як паказана вышэй, механізмы застаюцца виднымі, а выбор модэлю залишаецца у вашых руках, што мае значэнне, таму ўсё ж трэба выкарыстоўваць той самы модэль як для дадавання, так і для запытанняў.

    Фільтрацыя па метаданых

    Фільтрацыя — это функция, якой некалендарны ўжоў ніколи не хацелі. Наступны запит шукае „data security“, але обмежвае результаты вектарамі, чыя topic ў значэнні security, за дапамогою аргумента where:

    res = collection.query(
        query_embeddings=embedder.encode(["data security"]).tolist(),
        n_results=3,
        where={"topic": "security"},   # only search vectors tagged security
    )
    print(res["documents"][0])
    

    У прымэтнай експлуатацыі гэта ўтварае адпаведныя запиты, такія як „падобныя заявкі ад гэтага кліента“ чы „дасягнення документацыі ў гэтым годзе“. Фільтрацыя застаўляецца разам з пошукам вектароў, а не як крок пасля обрабаткі, таму вы все раве атрымаеце да n_results паспадобных рэзультатаў. Як вправа, змяніце фільтр на where={"topic": "pricing"} і пераканайцеся, што документы пра вярненне грошаў і безпеку больш не будуць атрымліваны.

    FAISS: быстрая бібліятэка індексаў, а не база дадзенаў

    FAISS, які выкалічваецца компанія Meta, ўжо не являецца базай знакоў. Цэта адчынна оптымізаваная бібліятэка для пошуку найбліжэйшых суседзей, якая не мае метаданоў і сопныяй складовай для зберагачвання, і прызначаная для вбудовывання ў вашу аплікацыю. Багато іншых системаў выкарыстоўваюць яе внутршняя. Зінсталюйте версію для CPU:

    pip install faiss-cpu
    

    Код кодуе дакументы з параметрам normalize_embeddings=True і ператварае іх у формат float32, які трэба FAISS. Ён стварае індекс IndexFlatIP, які ўжо є індексам внутранняго добутку; адтаколькі вектары нормалізуюцца да дужэй дужыны, внутранні добуткі паўтараюць косай схожасць. Пасля дадання вектараў код такім жа спосабам кодуе запит і выклікае функцыю search, каб знайсці два найбліжэйшыя результаты, а пасля — перакладае повернутыя пазіцыі назад у первісныя дакументы.

    import faiss, numpy as np
    
    vectors = embedder.encode(DOCS, normalize_embeddings=True).astype("float32")
    index = faiss.IndexFlatIP(vectors.shape[1])   # IP = inner product = cosine on normalized vectors
    index.add(vectors)                            # ↔ exactly your numpy array, but optimized in C++
    
    q = embedder.encode(["refund window?"], normalize_embeddings=True).astype("float32")
    scores, idx = index.search(q, k=2)            # ↔ your argsort top-k
    for s, i in zip(scores[0], idx[0]):
        print(f"[{s:.3f}] {DOCS[i]}")
    

    Гэта практычна ўсё та ж самая прыемка з NumPy: IndexFlatIP — это пошук за дапамою косай вялічыны методом грубой сілы, рэалізаваны ў оптымізаванам C++. Корыстнае наследжэнне праблічаецца, калі яго заменіць на апрыксиматыўны індэкс, такі як IndexHNSWFlat або IndexIVFFlat, якія зберагаюць той самы інтерфейс, але ведуць пошук за час, меньшы за лінейны. Выберыце FAISS, калі хочаце максимальной швальнасці ўнутрь адного процэса і гатовы самі кераваць перзистэнцыяй і метаданамі, напрыклад, палячы окольны карточнік з пазіцый на записі дакументаў.

    Pinecone: абсалютна кераваныя і без сервера

    Усё, што было раней, працавало ў вашым сабе процэсе. Pinecone — это кераваная база дадзеных вектароў, доступная через сеть: тут няма сервера, які трэба запускаць, і ёна автаматычна масштабуецца. Гэта варыянт для команд, якія не хочуць кераваць інфраструктурой.

    Зважайце, што SDK Pinecone сэрьозна змянілася. Стары стыль pinecone.init(api_key=, environment=) больш не існуе; тепер трэба стварыць об’ект-кліента Pinecone, а пакет называецца pinecone, а не pinecone-client. Адзін раз пераканаўцеся ў актуальной даследовачкай документацыі SDK, якщо ў чым-небудзь з’явіцца проблема.

    Блок пачынаецца з команды інсталяцыі, а пасля — з коду на Python (якщо копіюеце, раздзеліце іх). У ям создаёцца кліент, а таксама безсерверны індэкс пад назвай nimbus, толькі якщо ён яшчэ не існуе; пры гэтым задаюцца параметры dimension=384, каб ён падходзіў да MiniLM, і metric="cosine", пасля чаго выступаеся зяўнерычанне да гэтага індэксу. У ям выкарыстоўваецца операция upsert для дадзення пар (id, vector, metadata), пры чым аўтэнтычны текст зберагаецца ў metadata, адтакуль Pinecone вяртае толькі тое, што вы збераглі. На завершанне выкарыстоўваецца запыт пра два лепшых рэзультата, абмежаны фільтрам у стыле Mongo $eq па параметру topic, і выводзяцца кожны рэйтынг і текст. У рэальным кодзе ключ лепей чытаць з аб’екта сераўісу зменных сяродовыша або сховішча секрэтных дадзеных, а не ставіць яго ў сам код.

    pip install pinecone
    
    from pinecone import Pinecone, ServerlessSpec
    
    pc = Pinecone(api_key="YOUR_API_KEY")          # get a free key at app.pinecone.io
    
    # Create a serverless index (only once). dimension must match your embedder: MiniLM = 384.
    if not pc.has_index("nimbus"):
        pc.create_index(
            name="nimbus",
            dimension=384,
            metric="cosine",
            spec=ServerlessSpec(cloud="aws", region="us-east-1"),
        )
    index = pc.Index("nimbus")
    
    # Upsert: (id, vector, metadata) tuples
    index.upsert(vectors=[
        (f"doc{i}", embedder.encode(d).tolist(), {"text": d, "topic": m["topic"]})
        for i, (d, m) in enumerate(zip(DOCS, metadatas))
    ])
    
    # Query with metadata filtering
    res = index.query(
        vector=embedder.encode("how much is pro?").tolist(),
        top_k=2,
        include_metadata=True,
        filter={"topic": {"$eq": "pricing"}},      # Mongo-style filter operators
    )
    for match in res["matches"]:
        print(f"[{match['score']:.3f}] {match['metadata']['text']}")
    

    Формат адаптаваныя ўсё точна да Chroma: ствараецца індэкс, выкананыя аператыўы upsert для ID, вектараў і метаданых, пасля чаго выконваецца запыт top-k з неабяжным фільтрам. Этап выкарыстоўвання дадзеных аднойчыны такі ж, як і ранейш, але тепер ён выконваецца на інфраструктурэ автаскалявання іншага аператора через API. Pinecone таксама падтрымляе namespaces, якія дзельнуюць адны індэкс, а таксама integrated inference, дзе функцыі upsert_records і search_records самыя включаюць текст, таму не трэба самостайна выканваць процес включэння тексту.

    Абщая насведчанне заключаецца ў тым, што пераход з Chroma на Pinecone змяніў толькі спосаб прыяўлення і вызову сховішча дадзеных, а не логіку RAG, якая яго апрацоўвае. У добра структураваным прыкладзе програмы сховішча вектараў ёсць компонент, які можна заменіць, і самэ гэта ўзгляду на тое, чаму методы фреймворкаў, такія як .as_retriever() з LangChain, застаюцца ідэнтычныя незалежна ад бэкенда.

    Выбір бэкенду па вашыя абмежэння

    Няма ўніверсальнай найкращай базы дадзеных для вектараў; кожная з іх падходзіць для пэўнага набору абмежэнняў. Прыблізна:

    • Масіва NumPy (самастраўленыя): працуюць у межах процесу; падходзяць для прыблізна 10 тысяч вектараў, для навучэння або калі трэба абсолютны контроль.
    • Chroma: вбудованая база дадзеных, працуючая у межах процесу або як локальны сервер; ідеальна для протатэў і малых-середніх дапытак, лёгкая для запуску.
    • FAISS: бібліятэка індексаў, працуючая у межах процесу; даўае максымальную швалерасць, калі вы самі керуеце збераганнем і метаданнымі.
    • pgvector: расшырэнне для Postgres у вашай існуючай базе дадзеных; ідеальна, калі вы вядома выкарыстоўваете Postgres і хочаце зберагваць вектары разам з реляцыйнымі дадзеннямі.
    • Qdrant: база дадзеных, якая можа працаваць самастоятельна за дапамогою Docker або ў хмаре; мае сильныя фільтрацыі, высокую швалерасць завдака техналогій Rust, ёсць адкрытым кодам і падтрымлеў гібранічны пошук.
  • Weaviate: база дадзеных, яка можа быть самастаячай або ў хмаре; уключаныя модулі, гібрыдны пошук, API на базе GraphQL.
  • Milvus: база дадзеных, яка можа быть самастаячай (трэбуе большых зусоў для керавання) або ў хмаре; створаная для обробкі мільярдаў вектараў.
  • Pinecone: толькі кераваная версія, яка знаходзится ў сабеўскай хмаре; без якіх-леба дзействаў керавання і автаскаляроўванне без сервераў.
  • Найчастае пытанне, якое встае, — гэта выкарыстанне адмініструемых служб проты самастаўлення. Адмініструемыя службы, такія як Pinecone чыют іншыя ў версіях для хмары, пазбавляюць ад неабходнасці керування, але ствараюць постоянныя витраты і размешчаюць вашы даны на серверах постачальніка. Самастаўленне Chroma, Qdrant, Weaviate, Milvus чыют pgvector значыць, што вы самі керуеце імі, заўсёды контролюячы витаткі і месца размешчэння даных. Адно з популярных правіла ўзводзіць, што самастаўленне становіцца выгодным, калі месячныя витаткі на адмініструемыя службы досягаюць канікулярных сум у доларах; нижэй гэтага рубежа часы, якія витрачаюцца на керування, зазвычай коштаюць больш, чым заўсёды эканоміцца. Вважайце гэта простым напамінанням, а не законам, адтолькі як спроможнасць вашай команды керування мае такое ж значэнне, як і сума рахунку.

    Для навчання і большасці падзейных проектаў дастатньа Chroma або pgvector. Pinecone або Qdrant Cloud стануць корыстнымі, калі інфраструктуру краща заўважыць команде, якая не хоча ёю кераваць, а Milvus — калі вы справжнім чынам працуеце з мільярдамі вектароў. Якщо вы разважаеце, чы рэальна вам патрэбны спецыяльны хранілнік вектароў, прачытайце чаму продакшн-рэшанцыяў RAG пераходзяць за межы спецыяльных баз дадзеных вектароў.

    Памылкі, якія пасля запуску базы дадзеных ствараюць проблемы

    • Неспадзянанне размера вымераў — гэта найпашчэрэзнейшая памылка. Размер dimension індэкса павінен дапаможыцца з розмерам выходных дадзеных ад модуля эмбеддінгу: 384 для MiniLM, 1536 для багатох модэляў эмбеддінгу OpenAI. Адначаснае викорыстоўванне розных модэляў у аднам індэксе гарантавана прыводзіць да багоў.
  • Несасабліванасць метрык тыхо разрушае релевантнасць, напрыклад, пад час викорыстання евклідовая адзейнасць з модэлю, створаным для косайну. Паспаджавайце метрыку да модэлю.
  • Апроксіматыўны не ёсць тачны. Індэксы ANN часам працягваюць не знаходзіць справжньага найбліжэйшага суседа; гэта цена швальнасці. Большасць баз дадзеных апускае можлівасць налаштавання, такую як ef_search для HNSW або nprobe для IVF, якае падвышае рівень вярнаго аднаходжэння дадзеных за рахунак затрымкі. Чырвоныя дапамогі па гэтым параметрам можна знайсці ў налаштаванні індэксаў HNSW для прыменення ў RAG.
  • Павторны эмбеддынг праз кожнае перзапусканне выходзіць тады, калі нічога не зберагаецца, таму вам даводзіцца платіць за эмбеддынг на кожны запуск. Вярніцеся да PersistentClient ад Chroma або базы дадзеных, якая працюе на серверы.
  • Фільтры маюць свой сабе кошт. Інтэнсіўны фільтрацыя метаданых можа спамляць запыты; індексавайце поля, якія вы фільтруеце, якщо ваша база дадзеных гэта падтрымляе.
  • Хорашы спосаб — узяць існуючы скрыпт RAG на базе NumPy і заменіць толькі функцыю адзыскання на колекцыю Chroma, паказаную раней. Частка генеравання зовсім не зменшваецца, што паказвае, ўсё-такі адзыскання являе сабою чысты, заменяемы слой.

    Карточка швайнароўкі проблем

    • dimension does not match: вашы вектары не маюць таго размеру, якого чакае індекс. Створыце індекс з правильным dimension або выкорыстаце інструмент для уключэння, які быў нарабоўаны для яго.
    • Рэзультаты Chroma выглядаюць аднойчы: па верагоднасці метрыкі змешаны, або для дадзення і запыту былі выкорыстаны разныя інструменты для уключэння. Оба павінны збігатыся.
  • Памяць Pinecone has_index або create_index выклікае памяці: пераканайцеся, што вы викорыстоўваете актуальны SDK (from pinecone import Pinecone), а не застарэлы спосаб pinecone.init.
  • Функцыя FAISS search вяртае ID -1: у індэксе меньша колькасць вектароў, чым k. Дадзіце больш дакументаў або зменшыце значэнне k.
  • У великіх масштабах усё працюе повольна: найбольш верагатна вы викорыстоўваете просты, точны індэкс. Заменіце яго на HNSW або IVF і налаштавьте параметр вярнення дакументаў.
  • Ключовыя выводы

    • База дакументаў на вектарах — это самастраўлены механізм пошуку, які робіцца швыракім, стойкім і здатным да фільтрацыі. Дадаванне вектароў абарачаецца ў кодаванні і збераганні; запытвання выконваюцца за дапамогою методу cosine top-k.
    • Індэкс ANN — ежына новае рашэнне: адхоўваючы можлівасць апрыксиматывных адказаў, пошук застаецца швыракім нават праз зростанне колькасці дакументаў.
  • Фільтрацыя метаданых і ўтримванне іх — гэта функцыі, якія не ў звычным масіве.
  • Калі якосьць выкарыстоўвання дадзеных низкая, спачатку трэба выявіць, чый гэты проблема: у эмбеддінгах (неправильны модель чы сярожэднік) чы ў індексе (занадта низкая кількасць атрыбутаў, якія можна знайсці). Адпаведныя вялічыны, якія вяртаюцца, зазвычай пакажуць, што самэе ў проблеме.
  • Зберагаюце індэкс за адмежаванай інтарфейсной прамежы, так ўбачліва змяна ад Chroma на Pinecone чы будзь-што інша паўтараецца толькі ў кодзе для з’яўлення звязку і выканання запыткаў.