✅ Optional: 進階技術 - 從零開始打造向量存儲(Vector Store) 在 RAG 系統中,向量存儲庫(Vector Store)是檢索模組的 核心組件,負責存儲文件的向量嵌入,並在查詢時返回相似內容。在這個章節中,我們將: 手寫一個簡易的向量存儲 實作語意檢索(Semantic Search) 加入 Metadata Filtering 測試向量檢索與 Metadata 過濾 思考如何優化檢索性能 這將幫助你深入理解 向量數據庫的底層運作原理,並為你開發自己的向量檢索系統打下堅實基礎。 1️⃣ 環境準備 首先,我們需要安裝一些 必要的 Python 套件: pip install llama-index-readers-file pymupdf pip install llama-index-embeddings-openai 我們也需要下載測試數據: mkdir data wget --user-agent "Mozilla" "https://arxiv.org/pdf/2307.09288.pdf" -O "data/llama2.pdf" 2️⃣ 載入 PDF 文檔 我們使用 llama-index 來讀取 PDF 文檔,並將其解析為文本: from pathlib import Path from llama_index.readers.file import PyMuPDFReader loader = PyMuPDFReader() documents = loader.load(file_path="./data/llama2.pdf") 3️⃣ 將文檔轉換為節點(Nodes) 在 RAG 系統中,為了提高檢索的效果,我們通常會 將文本拆分成小塊,這樣可以讓向量檢索更準確: from llama_index.core.node_parser import SentenceSplitter node_parser = SentenceSplitter(chunk_size=256) nodes = node_parser.get_nodes_from_documents(documents) 4️⃣ 為每個節點生成嵌入向量 現在,我們使用 OpenAI 的 text-embedding-ada-002 來生成向量: from llama_index.embeddings.openai import OpenAIEmbedding embed_model = OpenAIEmbedding() for node in nodes: node_embedding = embed_model.get_text_embedding( node.get_content(metadata_mode="all") ) node.embedding = node_embedding 5️⃣ 建立一個簡單的 In-Memory 向量存儲 我們來 手寫一個 Python 版的 Vector Store,它可以: 新增(add) 節點 獲取(get) 節點 刪除(delete) 節點 查詢(query) 節點 📌 5.1 定義 Vector Store 基本架構 from typing import List, Dict, Any from llama_index.core.schema import BaseNode, TextNode class SimpleVectorStore: """一個簡單的 In-Memory 向量存儲""" def __init__(self): self.node_dict: Dict[str, BaseNode] = {} def add(self, nodes: List[BaseNode]) -> None: """新增節點""" for node in nodes: self.node_dict[node.node_id] = node def get(self, node_id: str) -> BaseNode: """獲取節點""" return self.node_dict.get(node_id, None) def delete(self, node_id: str) -> None: """刪除節點""" if node_id in self.node_dict: del self.node_dict[node_id] 📌 5.2 測試 Vector Store test_node1 = TextNode(id_="1", text="Hello, this is a test node.") test_node2 = TextNode(id_="2", text="This is another node for testing.") vector_store = SimpleVectorStore() vector_store.add([test_node1, test_node2]) print(vector_store.get("1")) # 測試獲取 vector_store.delete("2") print(vector_store.get("2")) # 測試刪除,應該輸出 None 6️⃣ 加入語意檢索(Semantic Search) 現在我們要讓這個向量存儲庫支援 語意檢索,即: 計算查詢向量與所有文件的相似度 取前 K 個最相似的文件 我們用 餘弦相似度(Cosine Similarity) 來計算向量之間的距離: import numpy as np from typing import Tuple def get_top_k_embeddings( query_embedding: List[float], doc_embeddings: List[List[float]], doc_ids: List[str], similarity_top_k: int = 5, ) -> Tuple[List[float], List]: """計算餘弦相似度,返回 Top-K 結果""" qembed_np = np.array(query_embedding) dembed_np = np.array(doc_embeddings) cos_sim = np.dot(dembed_np, qembed_np) / (np.linalg.norm(qembed_np) * np.linalg.norm(dembed_np, axis=1)) sorted_indices = np.argsort(cos_sim)[::-1][:similarity_top_k] sorted_similarities = cos_sim[sorted_indices] sorted_ids = [doc_ids[i] for i in sorted_indices] return sorted_similarities, sorted_ids 7️⃣ 加入 Metadata Filtering 有時候我們需要根據 Metadata(元數據) 來篩選文件: from llama_index.core.vector_stores import MetadataFilters def filter_nodes(nodes: List[BaseNode], filters: MetadataFilters) -> List[BaseNode]: """根據 Metadata 過濾節點""" filtered_nodes = [] for node in nodes: matches = True for f in filters.filters: if f.key not in node.metadata: matches = False continue if f.value != node.metadata[f.key]: matches = False continue if matches: filtered_nodes.append(node) return filtered_nodes 8️⃣ 測試查詢 現在我們來測試 向量檢索 + Metadata 過濾: vector_store = SimpleVectorStore() vector_store.add(nodes) # 加入節點 # 測試語意檢索 query_str = "Can you tell me about the key concepts for safety finetuning" query_embedding = embed_model.get_query_embedding(query_str) query_result = get_top_k_embeddings(query_embedding, [n.embedding for n in nodes], [n.node_id for n in nodes], similarity_top_k=2) for similarity, node_id in zip(query_result[0], query_result[1]): print(f"[Node ID {node_id}] Similarity: {similarity}\n") print(vector_store.get(node_id).get_content(metadata_mode="all")) print("\n----------------\n") 🎯 總結 在這個 Optional 章節中,我們: 從零開始構建了一個簡單的 In-Memory 向量存儲 實作了語意檢索(Semantic Search) 加入了 Metadata Filtering 測試了查詢效果 思考了如何優化檢索性能(FAISS, HNSW, Annoy) 📌 完整教學文件:👉 LlamaIndex 官方文件 從零開始構建回應合成:RAG 教學 簡介 檢索增強生成(RAG)管道是一種強大的方法,透過檢索相關文件來增強 LLM 的表現。其中,回應合成(Response Synthesis)是 RAG 系統的關鍵組件之一,負責將檢索到的節點(nodes)組合成最終答案。然而,當檢索的上下文超出模型的 token 限制時,有效的合成策略變得至關重要。 本教學將探討多種回應合成策略,並使用 LlamaIndex 與 OpenAI LLM 來實作。 你將學到的內容 使用簡單提示詞(prompt)進行回應合成 如何使用 建立與改進(Create and Refine) 策略處理上下文溢出 階層式摘要(Hierarchical Summarization) 的實作方法 使用非同步方式提升合成效率 將所有技術整合到簡單的查詢引擎中 [Optional] 如何優化回應合成效能 前置準備 請確保已安裝所需的 Python 套件: %pip install llama-index-readers-file pymupdf %pip install llama-index-vector-stores-pinecone %pip install llama-index-llms-openai %pip install llama-index 步驟 1:設置環境 我們將建立一個 Pinecone 向量存儲(vector store)並準備 LlamaIndex 組件。 載入數據 我們將下載範例 PDF 文件並使用 PyMuPDFReader 讀取它。 !mkdir data !wget --user-agent "Mozilla" "https://arxiv.org/pdf/2307.09288.pdf" -O "data/llama2.pdf" from pathlib import Path from llama_index.readers.file import PyMuPDFReader loader = PyMuPDFReader() documents = loader.load(file_path="./data/llama2.pdf") 建立 Pinecone 索引與檢索器 初始化 Pinecone 並創建一個向量存儲,設定 chunk 大小為 1024。 import pinecone import os api_key = os.environ["PINECONE_API_KEY"] pinecone.init(api_key=api_key, environment="us-west1-gcp") pinecone.create_index("quickstart", dimension=1536, metric="euclidean", pod_type="p1") pinecone_index = pinecone.Index("quickstart") pinecone_index.delete(deleteAll=True) from llama_index.vector_stores.pinecone import PineconeVectorStore from llama_index.core import VectorStoreIndex from llama_index.core.node_parser import SentenceSplitter from llama_index.core import StorageContext vector_store = PineconeVectorStore(pinecone_index=pinecone_index) splitter = SentenceSplitter(chunk_size=1024) storage_context = StorageContext.from_defaults(vector_store=vector_store) index = VectorStoreIndex.from_documents(documents, transformations=[splitter], storage_context=storage_context) retriever = index.as_retriever() 步驟 2:實作回應合成 基礎回應合成 我們從最基本的方法開始,將檢索到的節點全部放入單一提示詞中。 from llama_index.llms.openai import OpenAI from llama_index.core import PromptTemplate llm = OpenAI(model="text-davinci-003") qa_prompt = PromptTemplate( """ 以下是上下文資訊: --------------------- {context_str} --------------------- 根據上述上下文資訊,請回答問題。 問題: {query_str} 答案: """ ) 處理上下文溢出:建立與改進策略 若檢索的上下文過長,我們可以透過逐步改進來處理。 refine_prompt = PromptTemplate( """ 原始問題如下: {query_str} 我們已提供一個初步答案: {existing_answer} 我們可以利用以下新的上下文來改進這個答案(若需要)。 ------------ {context_str} ------------ 根據新的上下文,請改進原始答案。 若無需修改,請返回原始答案。 改進後的答案: """ ) 階層式摘要策略 透過遞歸方式將檢索的節點進行逐步合併。 def combine_results(texts, query_str, qa_prompt, llm, num_children=5): new_texts = [] for idx in range(0, len(texts), num_children): text_batch = texts[idx : idx + num_children] context_str = "\n\n".join([t for t in text_batch]) fmt_qa_prompt = qa_prompt.format(context_str=context_str, query_str=query_str) combined_response = llm.complete(fmt_qa_prompt) new_texts.append(str(combined_response)) if len(new_texts) == 1: return new_texts[0] else: return combine_results(new_texts, query_str, qa_prompt, llm, num_children=num_children) 步驟 3:整合所有技術 我們將檢索器、回應合成方法整合成一個查詢引擎。 class MyQueryEngine: def __init__(self, retriever, qa_prompt, llm, num_children=5): self.retriever = retriever self.qa_prompt = qa_prompt self.llm = llm self.num_children = num_children def query(self, query_str): retrieved_nodes = self.retriever.retrieve(query_str) return combine_results(retrieved_nodes, query_str, self.qa_prompt, self.llm, self.num_children) [Optional] 優化與改進回應合成效能 使用 非同步處理(Async Processing) 來加快查詢速度。 結合 分批處理(Batch Processing),提高模型推理效能。 微調提示詞(Prompt Engineering)以產生更精確的回應。 總結 本教學介紹了多種回應合成策略,並展示了如何在 RAG 管道中有效地處理大量檢索數據。 簡單合成法 適用於小型上下文 建立與改進策略 可用於逐步擴展答案 階層式摘要 幫助壓縮資訊並提高效率 非同步查詢與分批處理 可提升查詢效能 透過這些技術,您可以構建更強大的 RAG 系統! 從零開始構建回應合成與評估:RAG 教學 簡介 檢索增強生成(RAG)管道是一種強大的方法,透過檢索相關文件來增強 LLM 的表現。其中,回應合成(Response Synthesis)是 RAG 系統的關鍵組件之一,負責將檢索到的節點(nodes)組合成最終答案。此外,評估這些回應的質量也是一個重要步驟,可以確保生成的回應準確且忠於原始資訊。 本教學將探討多種回應合成與評估策略,並使用 LlamaIndex 與 OpenAI LLM 來實作。 你將學到的內容 使用簡單提示詞(prompt)進行回應合成 如何使用 建立與改進(Create and Refine) 策略處理上下文溢出 階層式摘要(Hierarchical Summarization) 的實作方法 正確性(Correctness) 和 忠誠度(Faithfulness) 評估方法 使用非同步方式提升合成效率 檢索評估(Retrieval Evaluation) 與 LlamaIndex 內建的評估模組 問題生成(Question Generation) 技術應用 將所有技術整合到簡單的查詢引擎中 [Optional] 如何優化回應合成效能 評估概念 評估與基準測試是 LLM 開發中的關鍵概念。要提升 LLM 應用(如 RAG 或 Agent)的表現,必須有方法來衡量其效果。 LlamaIndex 提供了多種評估模組,以測量生成結果的質量以及檢索結果的相關性。 回應評估(Response Evaluation) 正確性(Correctness):檢查生成的答案是否與參考答案一致(需要標註資料)。 語義相似性(Semantic Similarity):檢查生成的答案是否與參考答案在語義上相似(需要標註資料)。 忠誠度(Faithfulness):檢查生成的答案是否忠於檢索的上下文(避免幻覺)。 上下文相關性(Context Relevancy):檢查檢索到的內容是否與問題相關。 答案相關性(Answer Relevancy):檢查生成的答案是否與問題相關。 準則遵循(Guideline Adherence):檢查生成的答案是否符合特定準則。 問題生成(Question Generation) LlamaIndex 也可以利用現有的數據集自動生成問題,以進行更全面的評估。 檢索評估(Retrieval Evaluation) 我們也提供模組來獨立評估檢索結果。 核心步驟包括: 數據集生成:從非結構化文本生成 (問題, 上下文) 配對。 檢索評估:使用排名指標(如 MRR、hit-rate、precision)評估檢索效果。 LlamaIndex 提供的評估模組可直接用於上述步驟。 相關工具整合 LlamaIndex 也整合了社群評估工具,如: UpTrain Tonic Validate(包含可視化結果的 Web UI) DeepEval Ragas RAGChecker 使用模式 完整的使用細節請參考以下模式: 查詢評估模式(Query Eval Usage Pattern) 檢索評估模式(Retrieval Eval Usage Pattern) 模組與筆記本 有關如何使用這些評估組件,請參閱對應的模組指南。 RAG 系統的標註評估數據集(LabelledRagDataset) 有關如何使用標註數據集來評估 RAG 系統的詳細說明,請參閱專門的指南。 從零開始構建回應合成與評估:RAG 教學 簡介 檢索增強生成(RAG)管道是一種強大的方法,透過檢索相關文件來增強 LLM 的表現。其中,回應合成(Response Synthesis)是 RAG 系統的關鍵組件之一,負責將檢索到的節點(nodes)組合成最終答案。此外,評估這些回應的質量也是一個重要步驟,可以確保生成的回應準確且忠於原始資訊。 本教學將探討多種回應合成與評估策略,並使用 LlamaIndex 與 OpenAI LLM 來實作。 你將學到的內容 使用簡單提示詞(prompt)進行回應合成 如何使用 建立與改進(Create and Refine) 策略處理上下文溢出 階層式摘要(Hierarchical Summarization) 的實作方法 正確性(Correctness) 和 忠誠度(Faithfulness) 評估方法 使用非同步方式提升合成效率 將所有技術整合到簡單的查詢引擎中 [Optional] 如何優化回應合成效能 前置準備 請確保已安裝所需的 Python 套件: %pip install llama-index-readers-file pymupdf %pip install llama-index-vector-stores-pinecone %pip install llama-index-llms-openai %pip install llama-index 步驟 1:設置環境 我們將建立一個 Pinecone 向量存儲(vector store)並準備 LlamaIndex 組件。 載入數據 我們將下載範例 PDF 文件並使用 PyMuPDFReader 讀取它。 !mkdir data !wget --user-agent "Mozilla" "https://arxiv.org/pdf/2307.09288.pdf" -O "data/llama2.pdf" from pathlib import Path from llama_index.readers.file import PyMuPDFReader loader = PyMuPDFReader() documents = loader.load(file_path="./data/llama2.pdf") 建立 Pinecone 索引與檢索器 初始化 Pinecone 並創建一個向量存儲,設定 chunk 大小為 1024。 import pinecone import os api_key = os.environ["PINECONE_API_KEY"] pinecone.init(api_key=api_key, environment="us-west1-gcp") pinecone.create_index("quickstart", dimension=1536, metric="euclidean", pod_type="p1") pinecone_index = pinecone.Index("quickstart") pinecone_index.delete(deleteAll=True) from llama_index.vector_stores.pinecone import PineconeVectorStore from llama_index.core import VectorStoreIndex from llama_index.core.node_parser import SentenceSplitter from llama_index.core import StorageContext vector_store = PineconeVectorStore(pinecone_index=pinecone_index) splitter = SentenceSplitter(chunk_size=1024) storage_context = StorageContext.from_defaults(vector_store=vector_store) index = VectorStoreIndex.from_documents(documents, transformations=[splitter], storage_context=storage_context) retriever = index.as_retriever() 步驟 2:實作回應合成 基礎回應合成 我們從最基本的方法開始,將檢索到的節點全部放入單一提示詞中。 from llama_index.llms.openai import OpenAI from llama_index.core import PromptTemplate llm = OpenAI(model="text-davinci-003") qa_prompt = PromptTemplate( """ 以下是上下文資訊: --------------------- {context_str} --------------------- 根據上述上下文資訊,請回答問題。 問題: {query_str} 答案: """ ) 處理上下文溢出:建立與改進策略 若檢索的上下文過長,我們可以透過逐步改進來處理。 refine_prompt = PromptTemplate( """ 原始問題如下: {query_str} 我們已提供一個初步答案: {existing_answer} 我們可以利用以下新的上下文來改進這個答案(若需要)。 ------------ {context_str} ------------ 根據新的上下文,請改進原始答案。 若無需修改,請返回原始答案。 改進後的答案: """ ) 步驟 3:回應評估 正確性評估(Correctness Evaluation) 我們比較模型生成的答案與標準答案之間的相似度,並評分 1 到 5 分。 CORRECTNESS_SYS_TMPL = """ 你是一個專業的評估系統,負責評估問答機器人的準確性。 根據以下資訊進行評估: - 使用者問題 - 參考答案 - 生成答案 請評分 1 至 5 分,並解釋你的評分理由。 """ 忠誠度評估(Faithfulness Evaluation) 我們確保生成的答案是否忠於檢索到的內容,而非憑空捏造。 EVAL_TEMPLATE = PromptTemplate( """ 請判斷以下資訊是否受到上下文支持。 如果任何上下文支持此資訊,請回答 YES,否則回答 NO。 資訊: {query_str} 上下文: {context_str} 答案: """ ) 步驟 4:整合所有技術 我們將檢索器、回應合成與評估方法整合成一個查詢引擎。 class MyQueryEngine: def __init__(self, retriever, qa_prompt, llm, num_children=5): self.retriever = retriever self.qa_prompt = qa_prompt self.llm = llm self.num_children = num_children def query(self, query_str): retrieved_nodes = self.retriever.retrieve(query_str) return combine_results(retrieved_nodes, query_str, self.qa_prompt, self.llm, self.num_children) 總結 本教學介紹了回應合成與評估方法,確保 RAG 系統生成的答案既準確又忠於檢索內容。 回應合成:簡單合成、建立與改進、階層式摘要 評估方法:正確性評估、忠誠度評估 非同步處理與批量處理 提升查詢效能 不落格風格教學日誌:從零開始打造 Retrieval 系統 這篇教學日誌紀錄了如何建立一個標準的 Retriever 來對一個向量資料庫(Vector Database)進行檢索,以便透過相似度進行資料查詢。文中使用了 Pinecone 作為向量資料庫,並搭配 LlamaIndex(又稱 GPT Index)相關模組進行文件載入與檢索。 在這篇教學日誌中,你將會學到: 如何生成 Query Embedding 如何使用不同的檢索模式(Dense、Sparse、Hybrid)查詢向量資料庫 如何將查詢結果轉為 Nodes 如何將檢索流程封裝成自訂 Retriever 文末也會提供一些反向思考與進階課題,供同學參考與延伸學習。讓我們開始吧! 目錄 環境設定與安裝 建立 Pinecone Index 建立 PineconeVectorStore 載入文件 文件切割並載入 Vector Store 定義 Vector Retriever Step 1:生成 Query Embedding Step 2:查詢向量資料庫 Step-3:將結果轉成-nodes Step-4:將流程包裝成-retriever-類別 將 Retriever 插入 RetrieverQueryEngine 做回應生成 反向思考:為什麼要這樣做? Optional 進階教學 環境設定與安裝 如果你在 Colab 或其他環境上,請先執行下列指令來安裝所需套件(或確保已安裝): %pip install llama-index-readers-file pymupdf %pip install llama-index-vector-stores-pinecone %pip install llama-index-embeddings-openai !pip install llama-index 如環境中已安裝上述套件,可跳過此步驟。 建立 Pinecone Index 在下方程式碼中,會透過 Pinecone API 建立(或存取)我們想要的 Index。我們使用 text-embedding-ada-002 作為預設的嵌入模型,其維度是 1536。另外,如果之前的 Index 中已有內容,可以透過 pinecone_index.delete(deleteAll=True) 來刪除所有資料重新開始。 from pinecone import Pinecone, Index, ServerlessSpec import os api_key = os.environ["PINECONE_API_KEY"] # 確保你有設定這個環境變數 pc = Pinecone(api_key=api_key) # dimensions are for text-embedding-ada-002 dataset_name = "quickstart" if dataset_name not in pc.list_indexes().names(): pc.create_index( dataset_name, dimension=1536, metric="euclidean", spec=ServerlessSpec(cloud="aws", region="us-east-1"), ) pinecone_index = pc.Index(dataset_name) # [Optional] drop contents in index pinecone_index.delete(deleteAll=True) 建立 PineconeVectorStore 透過 PineconeVectorStore 包裝 pinecone_index,並存放在 StorageContext 中。這樣一來,我們就可以在 LlamaIndex 的高階 API 中直接使用此向量存儲。 from llama_index.vector_stores.pinecone import PineconeVectorStore vector_store = PineconeVectorStore(pinecone_index=pinecone_index) 載入文件 在這個範例中,我們載入了一份 PDF(llama2.pdf)檔案作為測試文件。透過 PyMuPDFReader 讀取 PDF 並轉為 Document 物件。 !mkdir data !wget --user-agent "Mozilla" "https://arxiv.org/pdf/2307.09288.pdf" -O "data/llama2.pdf" from pathlib import Path from llama_index.readers.file import PyMuPDFReader loader = PyMuPDFReader() documents = loader.load(file_path="./data/llama2.pdf") 文件切割並載入 Vector Store 這一步會透過 SentenceSplitter 以一定大小(chunk_size=1024)對文件進行切割,再將分段後的文件載入到向量存儲裡。 由於我們想更直觀地操作,在此直接利用 VectorStoreIndex.from_documents 進行高階封裝,將文件與向量儲存結合。 注意: 本文後半段的檢索過程則是 “從零開始” ,較少使用高階索引的功能,直接操作底層查詢。 from llama_index.core import VectorStoreIndex from llama_index.core.node_parser import SentenceSplitter from llama_index.core import StorageContext splitter = SentenceSplitter(chunk_size=1024) storage_context = StorageContext.from_defaults(vector_store=vector_store) index = VectorStoreIndex.from_documents( documents, transformations=[splitter], storage_context=storage_context ) 定義 Vector Retriever 下面示範如何自行撰寫 Retriever,並一步一步展開整個檢索流程。 預設的查詢字串: query_str = "Can you tell me about the key concepts for safety finetuning" Step 1:生成 Query Embedding 我們使用 OpenAIEmbedding 取得嵌入表徵(embedding),用來與向量資料庫中的內容進行相似度比對。 from llama_index.embeddings.openai import OpenAIEmbedding embed_model = OpenAIEmbedding() query_embedding = embed_model.get_query_embedding(query_str) Step 2:查詢向量資料庫 定義一個 VectorStoreQuery,其中包含我們的 query_embedding、要取回的節點數(similarity_top_k=2),以及檢索模式("default", "sparse", "hybrid" 皆可指定)。 # construct vector store query from llama_index.core.vector_stores import VectorStoreQuery query_mode = "default" # query_mode = "sparse" # query_mode = "hybrid" vector_store_query = VectorStoreQuery( query_embedding=query_embedding, similarity_top_k=2, mode=query_mode ) # returns a VectorStoreQueryResult query_result = vector_store.query(vector_store_query) query_result Step 3:將結果轉成 Nodes VectorStoreQueryResult 會回傳一串 Node 與對應的相似度(similarities)。以下將它們做成一個 NodeWithScore 陣列,方便後續操作或顯示。 from llama_index.core.schema import NodeWithScore from typing import Optional nodes_with_scores = [] for index, node in enumerate(query_result.nodes): score: Optional[float] = None if query_result.similarities is not None: score = query_result.similarities[index] nodes_with_scores.append(NodeWithScore(node=node, score=score)) # [Optional] 顯示看看結果 from llama_index.core.response.notebook_utils import display_source_node for node in nodes_with_scores: display_source_node(node, source_length=1000) Step 4:將流程包裝成 Retriever 類別 以下自訂了一個 PineconeRetriever 類別,繼承自 LlamaIndex 的 BaseRetriever。 只要提供 vector_store、embed_model、query_mode 和 similarity_top_k,就能建立出一個自訂的檢索器。 from llama_index.core import QueryBundle from llama_index.core.retrievers import BaseRetriever from typing import Any, List class PineconeRetriever(BaseRetriever): """Retriever over a pinecone vector store.""" def __init__( self, vector_store: PineconeVectorStore, embed_model: Any, query_mode: str = "default", similarity_top_k: int = 2, ) -> None: """Init params.""" self._vector_store = vector_store self._embed_model = embed_model self._query_mode = query_mode self._similarity_top_k = similarity_top_k super().__init__() def _retrieve(self, query_bundle: QueryBundle) -> List[NodeWithScore]: """Retrieve.""" if query_bundle.embedding is None: query_embedding = self._embed_model.get_query_embedding( query_bundle.query_str ) else: query_embedding = query_bundle.embedding vector_store_query = VectorStoreQuery( query_embedding=query_embedding, similarity_top_k=self._similarity_top_k, mode=self._query_mode, ) query_result = self._vector_store.query(vector_store_query) nodes_with_scores = [] for index, node in enumerate(query_result.nodes): score: Optional[float] = None if query_result.similarities is not None: score = query_result.similarities[index] nodes_with_scores.append(NodeWithScore(node=node, score=score)) return nodes_with_scores 接著,我們透過上面定義的 PineconeRetriever 建立一個檢索器,並嘗試使用它對剛才的 query_str 進行檢索: retriever = PineconeRetriever( vector_store, embed_model, query_mode="default", similarity_top_k=2 ) retrieved_nodes = retriever.retrieve(query_str) for node in retrieved_nodes: display_source_node(node, source_length=1000) 將 Retriever 插入 RetrieverQueryEngine 做回應生成 最後,我們將寫好的 PineconeRetriever 插入到 LlamaIndex 提供的 RetrieverQueryEngine 中,以生成最終回應。 提示:未來還可以替換成不同回應生成器、使用更複雜的 Template 或 Chain 等技術。 from llama_index.core.query_engine import RetrieverQueryEngine query_engine = RetrieverQueryEngine.from_args(retriever) response = query_engine.query(query_str) print(str(response)) 反向思考:為什麼要這樣做? 1. 為什麼要自訂 Retriever? • 直接使用高階 API (如 VectorStoreIndex.query)雖然方便,但有時候我們需要精細掌控查詢細節,或是想要在檢索前後做更多動作(如過濾文件、加入額外的檢索邏輯)。 2. 為什麼要把查詢與回應生成拆開? • 這樣可以輕易替換不同的檢索策略(或 Vector Store)而不影響回應生成邏輯,讓系統更具彈性。 3. 取得相似度有何用? • 相似度可以幫助我們理解檢索到的內容是否與查詢足夠相關;或在後續做融合(Reranking)等進階操作時使用。 Optional 進階教學 1. Sparse 或 Hybrid Search • 如果你的向量儲存工具(例如 Pinecone)支援稀疏向量,可以嘗試使用 "sparse" 或 "hybrid" 模式進行查詢,取得更豐富或多元的檢索結果。 2. 自訂相似度函數或指標 • 如果你想要以餘弦相似度 (cosine similarity) 或其他方式度量向量距離,可以調整 Pinecone Index 的 metric(或自行在程式中計算)。 3. 權重調整 • 在 Hybrid Search 情境下,可以對稀疏向量與 Dense 向量的權重做調整,以便產出更適合特定專案需求的檢索結果。 4. 文件更新與版本控管 • 若文件經常變動,如何有效地更新向量存儲與 Index?是否需要做版本控管或快照(snapshot)保留?這些都是在實際生產環境中常見的議題。 5. 查詢前的意圖識別 • 在一些進階應用中,若系統無法辨別使用者查詢的意圖,可以先經過一層意圖判斷後,選擇合適的 Retriever 或 Query 模式,進一步提升系統的品質。 希望透過上述步驟讓你對整個檢索流程與 Pinecone + LlamaIndex 的運作原理有更直觀的理解。也歡迎大家帶著問題與想法自行實驗並分享心得!