快轉到主要內容

LlamaIndex 學習筆記 - 餵資料與 Vector DB

這筆記系列是建構在 LlamaIndex 官方文件的 starter example 的範例節錄/實作/追蹤, 前一篇在這

嘗試的原始碼在 https://github.com/unclefomotw/llamaindex-try/blob/main/src/rag_3.py


此嘗試有兩個重點

  1. 利用 LlamaIndex 做 data ingestion / ETL
  2. 運用外部的 vector database 儲存資料 (以 Qdrant 為例,但其他 DB 應該大同小異)

要安裝 pip install llama-index-vector-stores-qdrant

Simple diagram of the structure of this attempt: data ingestion and use a vector database
此嘗試的大致流程

Offline: 餵資料 #

更新: 請參照 新的這篇,用 IngestionPipeline

首先,不一定需要用 LlamaIndex 做 ETL / Data ingestion,只要有個資料庫,並且已經把文件餵進去就好(可能欄位/設定/細節會需要調整,待查)

不過為了玩一下,我在這是拿空的資料庫 + LlamaIndex 的一些功能,把文本資料爬出來,並轉成文件餵進去

Vector DB - Qdrant #

隨便在本機裝個 Qdrant 試試。Qdrant 有 WebUI http://localhost:6333/ 觀察有哪些資料在裡面,剛裝起來是空的

Qdrant 的 python interface library 是 qdrant-client,不過安裝 llama-index-vector-stores-qdrant 會順便一起裝

利用 LlamaIndex 餵資料 #

def ingest_to_db():
    # Crawl
    documents = SimpleDirectoryReader(
        input_dir=RAG_DATA_DIR, recursive=True, required_exts=[".md"]
    ).load_data()

    # This is from qdrant, not llama-index
    db_client = QdrantClient(host="localhost", port=6333)

    # pass the DB client to the vector store
    vector_store = QdrantVectorStore(
        collection_name="rag_3",
        client=db_client
    )
    storage_context = StorageContext.from_defaults(vector_store=vector_store)

    # The effect: feed data into DB
    VectorStoreIndex.from_documents(
        documents,
        storage_context=storage_context
    )
  • StorageContext: 所謂 context 是用來在函數之間傳遞資訊的,本身沒有作用,而是讓其他人拿得到「東西」。StorageContext 有儲存 vector_store 以及其他跟 store 相關的物件,讓其他人能存取
  • 在 LlamaIndex 裡,“index” 並不是想像中建立反向索引、或有神秘演算法能快速查找資料的
    • 要 look up 需要 .as_query_engine() 變出 RetrieverQueryEngine,用裡面的 retriever
  • VectorStoreIndex.from_documents() 會把 documents 寫入資料庫 – 只要有 documents 以及 vector_store / storage_context (後面解釋)
  • 所以實質的「索引」其實是 Qdrant 做的

乍看之下沒有「餵資料」,但其實看 http://localhost:6333/ Qdrant UI 是有的(後面解釋)

Screenshot of Qdrant WebUI indicating there's now data in the db

Online: 查詢資料庫,然後給 LLM 幻想 #

有了資料庫(無論怎麼餵進去的),可利用 VectorStoreIndex.from_vector_store() 聯繫資料庫變成 (llamaindex 的) index

前面餵完資料後,程式可以終止;現在查詢的程式可以重新跑,查詢是直接查 Qdrant 資料庫,本機沒有文件

    # Declare the Qdrant VectorStore; assuming the data is ingested
    db_client = QdrantClient(host="localhost", port=6333)
    vector_store = QdrantVectorStore(
        collection_name="rag_3",
        client=db_client
    )

    # The index itself does NOT hold any nodes (see below)
    index = VectorStoreIndex.from_vector_store(vector_store)
    query_engine = index.as_query_engine()

    # During inference, the major operations from the query_engine
    # happen in its retriever, where the vector store's `query()` is called
    response = query_engine.query("CFOP 要怎麼學?有建議的順序嗎?")

注意這邊 index 是從 .from_vector_store(vector_store) 來的; vector_store 是 llamaindex 的 QdrantVectorStore 連接 Qdrant 的 client

在 Offline 餵資料的 index 與這裡的 index 是不同的;或者說,不是因為前面用 VectorStoreIndex 餵了資料現在才能查詢,這兩個就是沒關連的

VectorStoreIndex #

詳見 https://github.com/unclefomotw/llamaindex-try/blob/main/src/rag_3.py

以 QdrantVectorStore 為例

  • VectorStoreIndex 的 .from_documents(documents, storage_context) 會呼叫自身的 build_index_from_nodes → 呼叫 _build_index_from_nodes
    • 而 VectorStoreIndex 將其實作成 self._vector_store.add(nodes_batch) (見此), 也就是會呼叫 QdrantVectorStore 的 add, 利用 qdrant_client 插入資料庫 (見此)
  • VectorStoreIndex 的 .from_vector_store(vector_store) 並不會傳文件 / nodes 給 index; 他單純只是把 vector_store (也就是 QdrantVectorStore) 記好
    • 之後其 .as_retriever() 是 VectorIndexRetriever;查詢的主要動作 _retrieve() 是呼叫 vector_store (也就是 QdrantVectorStore) 的 query() ,去打 Qdrant 的 API (見此)
    • 本機不需要儲存所有文件
若您覺得有趣, 請 追蹤我的Facebook 或  Linkedin, 讓你獲得更多資訊!