使用Haystack框架構建QA-RAG代理

使用Haystack框架構建QA-RAG代理

想象一下,您正在構建一個需要回答有關產品問題的客戶支援 AI 代理。有時,它需要從您的文件中提取資訊,有時則需要在網上搜尋最新更新。在這類複雜的人工智慧應用中,RAG 代理系統就派上了用場。將它們視為智慧研究助手,它們不僅瞭解您的內部文件,還能決定何時去搜尋網路。在本指南中,我們將介紹使用 Haystack 框架構建 QA RAG 代理系統的過程。

  • 瞭解什麼是 LLM 代理,並理解它與 RAG 系統有何不同。
  • 熟悉用於 LLM 代理應用的 Haystack 框架。
  • 瞭解從模板構建提示的過程,學習如何將不同的提示連線在一起。
  • 學習如何在 Haystack 中使用 ChromaDB 建立嵌入。
  • 學習如何建立從嵌入到生成的完整本地開發系統。

什麼是LLM代理?

LLM 代理是一種人工智慧系統,它可以根據對任務的理解自主做出決策並採取行動。與主要生成文字回復的傳統 LLM 不同,LLM 代理可以做更多事情。它可以思考、計劃和行動,只需極少的人工輸入。它可以評估自己的知識,識別何時需要更多資訊或外部工具。LLM 代理不依賴靜態資料或索引知識,而是決定信任哪些來源,以及如何收集最佳見解。

這類系統還能為工作挑選合適的工具。它可以決定何時需要檢索文件、執行計算或自動執行任務。它們的與眾不同之處在於能夠將複雜問題分解為多個步驟並獨立執行,這使其在研究、分析和工作流程自動化方面具有重要價值。

RAG與RAG代理

傳統的 RAG 系統遵循線性流程。當收到查詢時,系統首先會識別請求中的關鍵要素。然後搜尋知識庫,掃描相關資訊,以幫助設計準確的響應。一旦檢索到相關資訊或資料,系統就會對其進行處理,生成有意義且與上下文相關的回覆。

您可以通過下圖輕鬆瞭解這些流程。

傳統的 RAG 系統遵循線性流程

Source: Author

現在,RAG 代理系統通過以下方式加強了這一過程:

  • 評估查詢要求
  • 在多個知識源之間做出決定
  • 可能將不同來源的資訊結合起來
  • 自主決定響應策略
  • 提供源歸屬響應

關鍵區別在於系統能夠就如何處理查詢做出智慧決策,而不是遵循固定的檢索生成模式。

瞭解Haystack框架元件

Haystack 是一個開源框架,用於構建生產就緒的人工智慧、LLM 應用程式、RAG 管道和搜尋系統。它為構建 LLM 應用程式提供了一個強大而靈活的框架。它允許您整合來自 Huggingface、OpenAI、CoHere、Mistral 和 Local Ollama 等不同平臺的模型。你還可以在 AWS SageMaker、BedRock、Azure 和 GCP 等雲服務上部署模型。

Haystack 為高效資料管理提供了強大的文件儲存。它還配備了一套全面的評估、監控和資料整合工具,可確保應用程式各層效能的平穩執行。它還擁有強大的社羣協作能力,可以定期從不同的服務提供商那裡整合新的服務。

Haystack 框架元件

Source: Haystack

使用Haystack可以構建什麼?

  • 使用強大的檢索和生成技術,在您的資料上簡單推進 RAG。
  • 使用 GPT-4 、Llama3.2、Deepseek-R1 等最新 GenAI 模型的聊天機器人和代理。
  • 在混合型別(影象、文字、音訊和表格)知識庫上生成多模態問題解答系統。
  • 從文件中提取資訊或構建知識圖譜。

Haystack構件

Haystack 有兩個構建全功能 GenAI LLM 系統的主要概念–元件和管道。讓我們通過一個關於日本動漫人物的 RAG 的簡單例子來了解它們。

元件

元件是 Haystack 的核心構件。它們可以執行文件儲存、文件檢索、文字生成和嵌入等任務。Haystack 有許多元件,你可以在安裝後直接使用,它還提供 API,讓你通過編寫 Python 類來製作自己的元件。

它還收集了來自合作伙伴公司和社羣的整合元件。

安裝庫並設定 Ollama

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$ pip install haystack-ai ollama-haystack
# On you system download Ollama and install LLM
ollama pull llama3.2:3b
ollama pull nomic-embed-text
# And then start ollama server
ollama serve
$ pip install haystack-ai ollama-haystack # On you system download Ollama and install LLM ollama pull llama3.2:3b ollama pull nomic-embed-text # And then start ollama server ollama serve
$ pip install haystack-ai ollama-haystack
# On you system download Ollama and install LLM
ollama pull llama3.2:3b
ollama pull nomic-embed-text
# And then start ollama server
ollama serve

匯入一些元件

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
from haystack import Document, Pipeline
from haystack.components.builders.prompt_builder import PromptBuilder
from haystack.components.retrievers.in_memory import InMemoryBM25Retriever
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack_integrations.components.generators.ollama import OllamaGenerator
from haystack import Document, Pipeline from haystack.components.builders.prompt_builder import PromptBuilder from haystack.components.retrievers.in_memory import InMemoryBM25Retriever from haystack.document_stores.in_memory import InMemoryDocumentStore from haystack_integrations.components.generators.ollama import OllamaGenerator
from haystack import Document, Pipeline
from haystack.components.builders.prompt_builder import PromptBuilder
from haystack.components.retrievers.in_memory import InMemoryBM25Retriever
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack_integrations.components.generators.ollama import OllamaGenerator

建立文件和文件儲存

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
document_store = InMemoryDocumentStore()
documents = [
Document(
content="Naruto Uzumaki is a ninja from the Hidden Leaf Village and aspires to become Hokage."
),
Document(
content="Luffy is the captain of the Straw Hat Pirates and dreams of finding the One Piece."
),
Document(
content="Goku, a Saiyan warrior, has defended Earth from numerous powerful enemies like Frieza and Cell."
),
Document(
content="Light Yagami finds a mysterious Death Note, which allows him to eliminate people by writing their names."
),
Document(
content="Levi Ackerman is humanity’s strongest soldier, fighting against the Titans to protect mankind."
),
]
document_store = InMemoryDocumentStore() documents = [ Document( content="Naruto Uzumaki is a ninja from the Hidden Leaf Village and aspires to become Hokage." ), Document( content="Luffy is the captain of the Straw Hat Pirates and dreams of finding the One Piece." ), Document( content="Goku, a Saiyan warrior, has defended Earth from numerous powerful enemies like Frieza and Cell." ), Document( content="Light Yagami finds a mysterious Death Note, which allows him to eliminate people by writing their names." ), Document( content="Levi Ackerman is humanity’s strongest soldier, fighting against the Titans to protect mankind." ), ]
document_store = InMemoryDocumentStore()
documents = [
Document(
content="Naruto Uzumaki is a ninja from the Hidden Leaf Village and aspires to become Hokage."
),
Document(
content="Luffy is the captain of the Straw Hat Pirates and dreams of finding the One Piece."
),
Document(
content="Goku, a Saiyan warrior, has defended Earth from numerous powerful enemies like Frieza and Cell."
),
Document(
content="Light Yagami finds a mysterious Death Note, which allows him to eliminate people by writing their names."
),
Document(
content="Levi Ackerman is humanity’s strongest soldier, fighting against the Titans to protect mankind."
),
]

管道

管道是 Haystack 框架的支柱。它們定義了不同元件之間的資料流。管道本質上是有向無環圖(DAG)。一個具有多個輸出的元件可以連線到另一個具有多個輸入的元件。

您可以通過以下方式定義管道

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
pipe = Pipeline()
pipe.add_component("retriever", InMemoryBM25Retriever(document_store=document_store))
pipe.add_component("prompt_builder", PromptBuilder(template=template))
pipe.add_component(
"llm", OllamaGenerator(model="llama3.2:1b", url="http://localhost:11434")
)
pipe.connect("retriever", "prompt_builder.documents")
pipe.connect("prompt_builder", "llm")
pipe = Pipeline() pipe.add_component("retriever", InMemoryBM25Retriever(document_store=document_store)) pipe.add_component("prompt_builder", PromptBuilder(template=template)) pipe.add_component( "llm", OllamaGenerator(model="llama3.2:1b", url="http://localhost:11434") ) pipe.connect("retriever", "prompt_builder.documents") pipe.connect("prompt_builder", "llm")
pipe = Pipeline()
pipe.add_component("retriever", InMemoryBM25Retriever(document_store=document_store))
pipe.add_component("prompt_builder", PromptBuilder(template=template))
pipe.add_component(
"llm", OllamaGenerator(model="llama3.2:1b", url="http://localhost:11434")
)
pipe.connect("retriever", "prompt_builder.documents")
pipe.connect("prompt_builder", "llm")

您可以將管道視覺化

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
image_param = {
"format": "img",
"type": "png",
"theme": "forest",
"bgColor": "f2f3f4",
}
pipe.show(params=image_param)
image_param = { "format": "img", "type": "png", "theme": "forest", "bgColor": "f2f3f4", } pipe.show(params=image_param)
image_param = {
"format": "img",
"type": "png",
"theme": "forest",
"bgColor": "f2f3f4",
}
pipe.show(params=image_param)

該管道提供

  • 模組化工作流程管理
  • 靈活的元件安排
  • 易於除錯和監控
  • 可擴充套件的處理架構

節點

節點是管道中可連線的基本處理單元,這些節點是執行特定任務的元件。

上述流水線中的節點示例

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
pipe.add_component("retriever", InMemoryBM25Retriever(document_store=document_store))
pipe.add_component("prompt_builder", PromptBuilder(template=template))
pipe.add_component(
"llm", OllamaGenerator(model="llama3.2:1b", url="http://localhost:11434")
)
pipe.add_component("retriever", InMemoryBM25Retriever(document_store=document_store)) pipe.add_component("prompt_builder", PromptBuilder(template=template)) pipe.add_component( "llm", OllamaGenerator(model="llama3.2:1b", url="http://localhost:11434") )
pipe.add_component("retriever", InMemoryBM25Retriever(document_store=document_store))
pipe.add_component("prompt_builder", PromptBuilder(template=template))
pipe.add_component(
"llm", OllamaGenerator(model="llama3.2:1b", url="http://localhost:11434")
)

連線圖

連線圖定義了元件的互動方式。

從上面的管道中可以直觀地看到連線圖。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
image_param = {
"format": "img",
"type": "png",
"theme": "forest",
"bgColor": "f2f3f4",
}
pipe.show(params=image_param)
image_param = { "format": "img", "type": "png", "theme": "forest", "bgColor": "f2f3f4", } pipe.show(params=image_param)
image_param = {
"format": "img",
"type": "png",
"theme": "forest",
"bgColor": "f2f3f4",
}
pipe.show(params=image_param)

動漫管道的連線圖

動漫管道的連線圖

這種圖形結構

  • 定義元件之間的資料流
  • 管理輸入/輸出關係
  • 儘可能實現並行處理
  • 建立靈活的處理路徑。

現在,我們可以使用提示來查詢動漫知識庫。

建立提示模板

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
template = """
Given only the following information, answer the question.
Ignore your own knowledge.
Context:
{% for document in documents %}
{{ document.content }}
{% endfor %}
Question: {{ query }}?
"""
template = """ Given only the following information, answer the question. Ignore your own knowledge. Context: {% for document in documents %} {{ document.content }} {% endfor %} Question: {{ query }}? """
template = """
Given only the following information, answer the question.
Ignore your own knowledge.
Context:
{% for document in documents %}
{{ document.content }}
{% endfor %}
Question: {{ query }}?
"""

該提示將根據文件庫中的資訊提供答案。

使用提示和檢索器進行查詢

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
query = "How Goku eliminate people?"
response = pipe.run({"prompt_builder": {"query": query}, "retriever": {"query": query}})
print(response["llm"]["replies"])
query = "How Goku eliminate people?" response = pipe.run({"prompt_builder": {"query": query}, "retriever": {"query": query}}) print(response["llm"]["replies"])
query = "How Goku eliminate people?"
response = pipe.run({"prompt_builder": {"query": query}, "retriever": {"query": query}})
print(response["llm"]["replies"])

響應:goku res

對於新手來說,這個 RAG 很簡單,但在概念上卻很有價值。現在我們已經瞭解了 Haystack 框架的大部分概念,可以深入研究我們的主要專案了。如果遇到任何新問題,我都會一一解釋。

高中物理問答RAG專案

我們將為高中生建立一個基於 NCERT 物理書的問題解答 RAG。它將通過從 NCERT 書籍中獲取資訊來為問題提供答案,如果沒有相關資訊,它將搜尋網路來獲取資訊:

  • 為此,我將使用:本地 Llama3.2:3b 或 Llama3.2:1b
  • 用於嵌入儲存的 ChromaDB
  • 用於本地嵌入的 Nomic 嵌入文字模型
  • 用於網路搜尋的 DuckDuckGo 搜尋或 Tavily 搜尋(可選)

我使用的是完全本地化的免費系統。

設定開發者環境

我們將設定一個 Python 3.12 的 conda 環境

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$conda create --name agenticlm python=3.12
$conda activate agenticlm
$conda create --name agenticlm python=3.12 $conda activate agenticlm
$conda create --name agenticlm python=3.12
$conda activate agenticlm

安裝必要的軟體包

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$pip install haystack-ai ollama-haystack pypdf
$pip install chroma-haystack duckduckgo-api-haystack
$pip install haystack-ai ollama-haystack pypdf $pip install chroma-haystack duckduckgo-api-haystack
$pip install haystack-ai ollama-haystack pypdf
$pip install chroma-haystack duckduckgo-api-haystack

現在建立一個名為 qagent 的專案目錄。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$md qagent # create dir
$cd qagent # change to dir
$ code . # open folder in vscode
$md qagent # create dir $cd qagent # change to dir $ code . # open folder in vscode
$md qagent # create dir
$cd qagent # change to dir
$ code .   # open folder in vscode

您可以為專案使用純 Python 檔案,也可以為專案使用 Jupyter Notebook,這並不重要。我將使用純 Python 檔案。

在專案根目錄下建立一個main.py檔案。

匯入必要的庫

  • 系統包
  • 核心 haystack 元件
  • 用於嵌入元件的 ChromaDB
  • 用於本地推斷的 Ollama 元件
  • 用於網路搜尋的 Duckduckgo
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# System packages
import os
from pathlib import Path
# System packages import os from pathlib import Path
# System packages
import os
from pathlib import Path
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# Core haystack components
from haystack import Pipeline
from haystack.components.writers import DocumentWriter
from haystack.components.joiners import BranchJoiner
from haystack.document_stores.types import DuplicatePolicy
from haystack.components.converters import PyPDFToDocument
from haystack.components.routers import ConditionalRouter
from haystack.components.builders.prompt_builder import PromptBuilder
from haystack.components.preprocessors import DocumentCleaner, DocumentSplitter
# Core haystack components from haystack import Pipeline from haystack.components.writers import DocumentWriter from haystack.components.joiners import BranchJoiner from haystack.document_stores.types import DuplicatePolicy from haystack.components.converters import PyPDFToDocument from haystack.components.routers import ConditionalRouter from haystack.components.builders.prompt_builder import PromptBuilder from haystack.components.preprocessors import DocumentCleaner, DocumentSplitter
# Core haystack components
from haystack import Pipeline
from haystack.components.writers import DocumentWriter
from haystack.components.joiners import BranchJoiner
from haystack.document_stores.types import DuplicatePolicy
from haystack.components.converters import PyPDFToDocument
from haystack.components.routers import ConditionalRouter
from haystack.components.builders.prompt_builder import PromptBuilder
from haystack.components.preprocessors import DocumentCleaner, DocumentSplitter
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# ChromaDB integration
from haystack_integrations.document_stores.chroma import ChromaDocumentStore
from haystack_integrations.components.retrievers.chroma import (
ChromaEmbeddingRetriever,
)
# ChromaDB integration from haystack_integrations.document_stores.chroma import ChromaDocumentStore from haystack_integrations.components.retrievers.chroma import ( ChromaEmbeddingRetriever, )
# ChromaDB integration
from haystack_integrations.document_stores.chroma import ChromaDocumentStore
from haystack_integrations.components.retrievers.chroma import (
ChromaEmbeddingRetriever,
)
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# Ollama integration
from haystack_integrations.components.embedders.ollama.document_embedder import (
OllamaDocumentEmbedder,
)
from haystack_integrations.components.embedders.ollama.text_embedder import (
OllamaTextEmbedder,
)
from haystack_integrations.components.generators.ollama import OllamaGenerator
# Ollama integration from haystack_integrations.components.embedders.ollama.document_embedder import ( OllamaDocumentEmbedder, ) from haystack_integrations.components.embedders.ollama.text_embedder import ( OllamaTextEmbedder, ) from haystack_integrations.components.generators.ollama import OllamaGenerator
# Ollama integration
from haystack_integrations.components.embedders.ollama.document_embedder import (
OllamaDocumentEmbedder,
)
from haystack_integrations.components.embedders.ollama.text_embedder import (
OllamaTextEmbedder,
)
from haystack_integrations.components.generators.ollama import OllamaGenerator
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# Duckduckgo search integration
from duckduckgo_api_haystack import DuckduckgoApiWebSearch
# Duckduckgo search integration from duckduckgo_api_haystack import DuckduckgoApiWebSearch
# Duckduckgo search integration
from duckduckgo_api_haystack import DuckduckgoApiWebSearch

建立文件儲存

在這裡,文件儲存是最重要的,我們將儲存嵌入內容以便檢索,我們使用 ChromaDB 來儲存嵌入內容,正如你在前面的示例中看到的,我們使用 InMemoryDocumentStore 來快速檢索,因為那時我們的資料很小,但對於一個強大的檢索系統來說,我們不能依賴 InMemoryStore,它會佔用記憶體,而且每次啟動系統時我們都會建立嵌入內容。

解決辦法是使用向量資料庫,如 Pinecode、Weaviate、Postgres Vector DB 或 ChromaDB。我之所以使用 ChromaDB,是因為它免費、開源、易用且強大。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# Chroma DB integration component for document(embedding) store
document_store = ChromaDocumentStore(persist_path="qagent/embeddings")
# Chroma DB integration component for document(embedding) store document_store = ChromaDocumentStore(persist_path="qagent/embeddings")
# Chroma DB integration component for document(embedding) store
document_store = ChromaDocumentStore(persist_path="qagent/embeddings")

persistent_path 是您要儲存嵌入內容的位置。

PDF 檔案路徑

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
HERE = Path(__file__).resolve().parent
file_path = [HERE / "data" / Path(name) for name in os.listdir("QApipeline/data")]
HERE = Path(__file__).resolve().parent file_path = [HERE / "data" / Path(name) for name in os.listdir("QApipeline/data")]
HERE = Path(__file__).resolve().parent
file_path = [HERE / "data" / Path(name) for name in os.listdir("QApipeline/data")]

它將從包含 PDF 檔案的資料資料夾中建立一個檔案列表。

文件預處理元件

我們將使用 Haystack 內建的文件前處理器,如清理器、分割器和檔案轉換器,然後使用寫入器將資料寫入儲存空間。

Cleaner:它會清除文件中的多餘空格、重複行、空行等。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
cleaner = DocumentCleaner()
cleaner = DocumentCleaner()
cleaner = DocumentCleaner()

Splitter它能以單詞、句子、段落、頁面等多種方式分割文件。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
splitter = DocumentSplitter()
splitter = DocumentSplitter()
splitter = DocumentSplitter()

File Converter使用 pypdf 將 pdf 轉換為文件。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
file_converter = PyPDFToDocument()
file_converter = PyPDFToDocument()
file_converter = PyPDFToDocument()

Writer它會將文件儲存到你想要儲存文件的位置,對於重複的文件,它會覆蓋之前的文件。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
writer = DocumentWriter(document_store=document_store, policy=DuplicatePolicy.OVERWRITE)
writer = DocumentWriter(document_store=document_store, policy=DuplicatePolicy.OVERWRITE)
writer = DocumentWriter(document_store=document_store, policy=DuplicatePolicy.OVERWRITE)

現在設定文件索引的嵌入器。

EmbedderNomic 嵌入文字

我們將使用在 Huggingface 和 Ollama 中非常有效且免費的 nomic-embed-text 嵌入器。

在執行索引管道之前,開啟終端並鍵入以下內容,從 Ollama 模型庫中提取 nomic-embed-text 和 llama3.2:3b 模型

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$ ollama pull nomic-embed-text
$ ollama pull llama3.2:3b
$ ollama pull nomic-embed-text $ ollama pull llama3.2:3b
$ ollama pull nomic-embed-text
$ ollama pull llama3.2:3b

在終端中輸入 ollama serve 命令啟動 Ollama

現在嵌入元件

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
embedder = OllamaDocumentEmbedder(
model="nomic-embed-text", url="http://localhost:11434"
)
embedder = OllamaDocumentEmbedder( model="nomic-embed-text", url="http://localhost:11434" )
embedder = OllamaDocumentEmbedder(
model="nomic-embed-text", url="http://localhost:11434"
)

我們使用 OllamaDocumentEmbedder 元件嵌入文件,但如果要嵌入文字字串,則必須使用 OllamaTextEmbedder。

建立索引管道

與之前的玩具 RAG 示例一樣,我們將首先啟動 Pipeline 類。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
indexing_pipeline = Pipeline()
indexing_pipeline = Pipeline()
indexing_pipeline = Pipeline()

現在,我們將把元件逐一新增到管道中

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
indexing_pipeline.add_component("embedder", embedder)
indexing_pipeline.add_component("converter", file_converter)
indexing_pipeline.add_component("cleaner", cleaner)
indexing_pipeline.add_component("splitter", splitter)
indexing_pipeline.add_component("writer", writer)
indexing_pipeline.add_component("embedder", embedder) indexing_pipeline.add_component("converter", file_converter) indexing_pipeline.add_component("cleaner", cleaner) indexing_pipeline.add_component("splitter", splitter) indexing_pipeline.add_component("writer", writer)
indexing_pipeline.add_component("embedder", embedder)
indexing_pipeline.add_component("converter", file_converter)
indexing_pipeline.add_component("cleaner", cleaner)
indexing_pipeline.add_component("splitter", splitter)
indexing_pipeline.add_component("writer", writer)

向管道中新增元件並不考慮順序,因此您可以按任何順序新增元件。

將元件連線到管道圖

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
indexing_pipeline.connect("converter", "cleaner")
indexing_pipeline.connect("cleaner", "splitter")
indexing_pipeline.connect("splitter", "embedder")
indexing_pipeline.connect("embedder", "writer")
indexing_pipeline.connect("converter", "cleaner") indexing_pipeline.connect("cleaner", "splitter") indexing_pipeline.connect("splitter", "embedder") indexing_pipeline.connect("embedder", "writer")
indexing_pipeline.connect("converter", "cleaner")
indexing_pipeline.connect("cleaner", "splitter")
indexing_pipeline.connect("splitter", "embedder")
indexing_pipeline.connect("embedder", "writer")

在這裡,順序很重要,因為如何連線元件會告訴管道資料如何在管道中流動。這就好比,你從哪裡購買水管裝置,順序並不重要,但如何將它們組合在一起,將決定你是否能喝到水。

轉換器轉換 PDF 檔案後,將其傳送給清理器進行清理。然後,清理器將清理後的文件傳送給分割器進行分塊。然後,這些分塊將傳遞給嵌入器進行向量化,最後嵌入器將把這些嵌入交給編寫器進行儲存。

明白了吧!好了,讓我給你一張索引的視覺化圖表,以便你能檢查資料流。

繪製索引流水線

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
image_param = {
"format": "img",
"type": "png",
"theme": "forest",
"bgColor": "f2f3f4",
}
indexing_pipeline.draw("indexing_pipeline.png", params=image_param) # type: ignore
image_param = { "format": "img", "type": "png", "theme": "forest", "bgColor": "f2f3f4", } indexing_pipeline.draw("indexing_pipeline.png", params=image_param) # type: ignore
image_param = {
"format": "img",
"type": "png",
"theme": "forest",
"bgColor": "f2f3f4",
}
indexing_pipeline.draw("indexing_pipeline.png", params=image_param)  # type: ignore

是的,你可以通過 Haystack 管道輕鬆建立一個漂亮的美人魚圖。

索引管道圖

索引管道圖

我想你們現在已經完全理解了 Haystack 管道背後的理念。

實現路由器

現在,我們需要建立一個路由器,通過不同的路徑路由資料。在這種情況下,我們將使用條件路由器,它將根據特定條件完成路由工作。條件路由器將根據元件輸出評估條件。它將引導資料流通過不同的管道分支,從而實現動態決策。它還將具有強大的回退策略。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# Conditions for routing
routes = [
{
"condition": "{{'no_answer' in replies[0]}}",
"output": "{{query}}",
"output_name": "go_to_websearch",
"output_type": str,
},
{
"condition": "{{'no_answer' not in replies[0]}}",
"output": "{{replies[0]}}",
"output_name": "answer",
"output_type": str,
},
]
# router component
router = ConditionalRouter(routes=routes)
# Conditions for routing routes = [ { "condition": "{{'no_answer' in replies[0]}}", "output": "{{query}}", "output_name": "go_to_websearch", "output_type": str, }, { "condition": "{{'no_answer' not in replies[0]}}", "output": "{{replies[0]}}", "output_name": "answer", "output_type": str, }, ] # router component router = ConditionalRouter(routes=routes)
# Conditions for routing
routes = [
{
"condition": "{{'no_answer' in replies[0]}}",
"output": "{{query}}",
"output_name": "go_to_websearch",
"output_type": str,
},
{
"condition": "{{'no_answer' not in replies[0]}}",
"output": "{{replies[0]}}",
"output_name": "answer",
"output_type": str,
},
]
# router component
router = ConditionalRouter(routes=routes)

當系統從嵌入式儲存上下文中獲得 no_answer 回覆時,它將轉而使用網路搜尋工具從網際網路上收集相關資料。

對於網路搜尋,我們將使用 Duckduckgo API 或 Tavily,這裡我使用的是 Duckduckgo。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
websearch = DuckduckgoApiWebSearch(top_k=5)
websearch = DuckduckgoApiWebSearch(top_k=5)
websearch = DuckduckgoApiWebSearch(top_k=5)

好了,大部分繁重的工作已經完成。現在,是時候進行提示工程了

建立提示模板

我們將使用 Haystack PromptBuilder 元件從模板中建立提示資訊

首先,我們將建立一個 qa 提示

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
template_qa = """
Given ONLY the following information, answer the question.
If the answer is not contained within the documents reply with "no_answer.
If the answer is contained within the documents, start the answer with "FROM THE KNOWLEDGE BASE: ".
Context:
{% for document in documents %}
{{ document.content }}
{% endfor %}
Question: {{ query }}?
"""
template_qa = """ Given ONLY the following information, answer the question. If the answer is not contained within the documents reply with "no_answer. If the answer is contained within the documents, start the answer with "FROM THE KNOWLEDGE BASE: ". Context: {% for document in documents %} {{ document.content }} {% endfor %} Question: {{ query }}? """
template_qa = """
Given ONLY the following information, answer the question.
If the answer is not contained within the documents reply with "no_answer.
If the answer is contained within the documents, start the answer with "FROM THE KNOWLEDGE BASE: ".
Context:
{% for document in documents %}
{{ document.content }}
{% endfor %}
Question: {{ query }}?
"""

它會從檔案中獲取上下文,並嘗試回答問題。但如果在文件中找不到相關上下文,系統就會回覆 no_answer。

現在,在從 LLM 得到 no_answer 後的第二次提示中,系統將使用網路搜尋工具從網際網路上收集上下文。

Duckduckgo 提示詞模板

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
template_websearch = """
Answer the following query given the documents retrieved from the web.
Start the answer with "FROM THE WEB: ".
Documents:
{% for document in documents %}
{{ document.content }}
{% endfor %}
Query: {{query}}
"""
template_websearch = """ Answer the following query given the documents retrieved from the web. Start the answer with "FROM THE WEB: ". Documents: {% for document in documents %} {{ document.content }} {% endfor %} Query: {{query}} """
template_websearch = """
Answer the following query given the documents retrieved from the web.
Start the answer with "FROM THE WEB: ".
Documents:
{% for document in documents %}
{{ document.content }}
{% endfor %}
Query: {{query}}
"""

這將有助於系統進入網路搜尋並嘗試回答查詢。

使用 Haystack 的 PromptBuilder 建立提示詞

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
prompt_qa = PromptBuilder(template=template_qa)
prompt_builder_websearch = PromptBuilder(template=template_websearch)
prompt_qa = PromptBuilder(template=template_qa) prompt_builder_websearch = PromptBuilder(template=template_websearch)
prompt_qa = PromptBuilder(template=template_qa)
prompt_builder_websearch = PromptBuilder(template=template_websearch)

我們將使用 Haystack 提示聯結器將提示的各個分支連線到一起。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
prompt_joiner = BranchJoiner(str)
prompt_joiner = BranchJoiner(str)
prompt_joiner = BranchJoiner(str)

實施查詢管道

查詢管道將嵌入查詢,從嵌入中收集上下文資源,並使用 LLM 或網路搜尋工具回答我們的查詢。

它與索引管道類似。

啟動管道

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
query_pipeline = Pipeline()
query_pipeline = Pipeline()
query_pipeline = Pipeline()

為查詢管道新增元件

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
query_pipeline.add_component("text_embedder", OllamaTextEmbedder())
query_pipeline.add_component(
"retriever", ChromaEmbeddingRetriever(document_store=document_store)
)
query_pipeline.add_component("prompt_builder", prompt_qa)
query_pipeline.add_component("prompt_joiner", prompt_joiner)
query_pipeline.add_component(
"llm",
OllamaGenerator(model="llama3.2:3b", timeout=500, url="http://localhost:11434"),
)
query_pipeline.add_component("router", router)
query_pipeline.add_component("websearch", websearch)
query_pipeline.add_component("prompt_builder_websearch", prompt_builder_websearch)
query_pipeline.add_component("text_embedder", OllamaTextEmbedder()) query_pipeline.add_component( "retriever", ChromaEmbeddingRetriever(document_store=document_store) ) query_pipeline.add_component("prompt_builder", prompt_qa) query_pipeline.add_component("prompt_joiner", prompt_joiner) query_pipeline.add_component( "llm", OllamaGenerator(model="llama3.2:3b", timeout=500, url="http://localhost:11434"), ) query_pipeline.add_component("router", router) query_pipeline.add_component("websearch", websearch) query_pipeline.add_component("prompt_builder_websearch", prompt_builder_websearch)
query_pipeline.add_component("text_embedder", OllamaTextEmbedder())
query_pipeline.add_component(
"retriever", ChromaEmbeddingRetriever(document_store=document_store)
)
query_pipeline.add_component("prompt_builder", prompt_qa)
query_pipeline.add_component("prompt_joiner", prompt_joiner)
query_pipeline.add_component(
"llm",
OllamaGenerator(model="llama3.2:3b", timeout=500, url="http://localhost:11434"),
)
query_pipeline.add_component("router", router)
query_pipeline.add_component("websearch", websearch)
query_pipeline.add_component("prompt_builder_websearch", prompt_builder_websearch)

在這裡,我們使用 OllamaGenerator 元件來生成 LLM,該元件可使用 Llama3.2:3b 或 1b 或任何你喜歡的 LLM 工具來生成答案。

將所有元件連線在一起,用於查詢流和答案生成

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
query_pipeline.connect("text_embedder.embedding", "retriever.query_embedding")
query_pipeline.connect("retriever", "prompt_builder.documents")
query_pipeline.connect("prompt_builder", "prompt_joiner")
query_pipeline.connect("prompt_joiner", "llm")
query_pipeline.connect("llm.replies", "router.replies")
query_pipeline.connect("router.go_to_websearch", "websearch.query")
query_pipeline.connect("router.go_to_websearch", "prompt_builder_websearch.query")
query_pipeline.connect("websearch.documents", "prompt_builder_websearch.documents")
query_pipeline.connect("prompt_builder_websearch", "prompt_joiner")
query_pipeline.connect("text_embedder.embedding", "retriever.query_embedding") query_pipeline.connect("retriever", "prompt_builder.documents") query_pipeline.connect("prompt_builder", "prompt_joiner") query_pipeline.connect("prompt_joiner", "llm") query_pipeline.connect("llm.replies", "router.replies") query_pipeline.connect("router.go_to_websearch", "websearch.query") query_pipeline.connect("router.go_to_websearch", "prompt_builder_websearch.query") query_pipeline.connect("websearch.documents", "prompt_builder_websearch.documents") query_pipeline.connect("prompt_builder_websearch", "prompt_joiner")
query_pipeline.connect("text_embedder.embedding", "retriever.query_embedding")
query_pipeline.connect("retriever", "prompt_builder.documents")
query_pipeline.connect("prompt_builder", "prompt_joiner")
query_pipeline.connect("prompt_joiner", "llm")
query_pipeline.connect("llm.replies", "router.replies")
query_pipeline.connect("router.go_to_websearch", "websearch.query")
query_pipeline.connect("router.go_to_websearch", "prompt_builder_websearch.query")
query_pipeline.connect("websearch.documents", "prompt_builder_websearch.documents")
query_pipeline.connect("prompt_builder_websearch", "prompt_joiner")

總結上述連線:

  1. 從 text_embedder 傳送的嵌入到 retriever 的查詢嵌入。
  2. Retriever 向 prompt_builder 的文件傳送資料。
  3. 提示生成器轉到提示聯結器,與其他提示連線。
  4. 提示生成器將資料傳遞給 LLM 進行生成。
  5. LLM 的回覆會轉到路由器,檢查回覆是否有no_answer 。如果沒有 ,則轉到網路搜尋模組。
  6. 網路搜尋將資料作為查詢傳送到網路搜尋提示。
  7. 網路搜尋文件向網路搜尋文件傳送資料。
  8. 網路搜尋提示將資料傳送給提示聯結器。
  9. 提示聯結器將資料傳送到 LLM,以便生成答案。

為什麼不親眼看看呢?

繪製查詢管道圖

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
query_pipeline.draw("agentic_qa_pipeline.png", params=image_param) # type: ignore
query_pipeline.draw("agentic_qa_pipeline.png", params=image_param) # type: ignore
query_pipeline.draw("agentic_qa_pipeline.png", params=image_param)  # type: ignore

查詢圖形

查詢圖形

我知道這張圖很大,但它能讓你清楚地看到野獸腹下發生了什麼。

現在是享受我們辛勤工作成果的時候了。

建立一個便於查詢的函式。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
def get_answer(query: str):
response = query_pipeline.run(
{
"text_embedder": {"text": query},
"prompt_builder": {"query": query},
"router": {"query": query},
}
)
return response["router"]["answer"]
def get_answer(query: str): response = query_pipeline.run( { "text_embedder": {"text": query}, "prompt_builder": {"query": query}, "router": {"query": query}, } ) return response["router"]["answer"]
def get_answer(query: str):
response = query_pipeline.run(
{
"text_embedder": {"text": query},
"prompt_builder": {"query": query},
"router": {"query": query},
}
)
return response["router"]["answer"]

這是一個用於生成答案的簡單函式。

現在執行主指令碼,為 NCERT 物理書編制索引

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
indexing_pipeline.run({"converter": {"sources": file_path}})
indexing_pipeline.run({"converter": {"sources": file_path}})
indexing_pipeline.run({"converter": {"sources": file_path}})

這是一項一次性工作,索引完成後必須註釋這一行,否則它將開始重新索引圖書。

在檔案底部,我們寫入查詢的驅動程式碼

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
if __name__ == "__main__":
query = "Give me 5 MCQ on resistivity?"
print(get_answer(query))
if __name__ == "__main__": query = "Give me 5 MCQ on resistivity?" print(get_answer(query))
if __name__ == "__main__":
query = "Give me 5 MCQ on resistivity?"
print(get_answer(query))

從書本知識中瞭解電阻率的 MCQ

從書本知識中瞭解電阻率的 MCQ

書中沒有提到的另一個問題

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
if __name__ == "__main__":
query = "What is Photosynthesis?"
print(get_answer(query))
if __name__ == "__main__": query = "What is Photosynthesis?" print(get_answer(query))
if __name__ == "__main__":
query = "What is Photosynthesis?"
print(get_answer(query))

輸出

AI回答物理知識

讓我們再試一個問題。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
if __name__ == "__main__":
query = (
"Tell me what is DRIFT OF ELECTRONS AND THE ORIGIN OF RESISTIVITY from the book"
)
print(get_answer(query))
if __name__ == "__main__": query = ( "Tell me what is DRIFT OF ELECTRONS AND THE ORIGIN OF RESISTIVITY from the book" ) print(get_answer(query))
if __name__ == "__main__":
query = (
"Tell me what is DRIFT OF ELECTRONS AND THE ORIGIN OF RESISTIVITY from the book"
)
print(get_answer(query))

AI基於電子書回答問題

因此,它是可行的!我們可以使用更多的資料、書籍或 PDF 檔案進行嵌入,從而生成更多的上下文感知答案。此外,GPT-4o、Anthropic’s Claude 或其他雲 LLM 也能更好地完成工作。

結論

我們的代理 RAG 系統展示了 Haystack 框架的靈活性和健壯性,以及其組合元件和管道的能力。通過部署到網路服務平臺,以及使用 OpenAI 和 nthropic 等更好的付費 LLM,該 RAG 可隨時投入生產。您可以使用 Streamlit 或基於 React 的 Web SPA 構建使用者介面,以獲得更好的使用者體驗。

你可以在這裡找到文章中使用的所有程式碼

  • 與傳統 RAG 相比,RAG 代理系統能提供更智慧、更靈活的響應。
  • Haystack 的管道架構可實現複雜的模組化工作流。
  • 路由器可在生成響應時進行動態決策。
  • 連線圖提供靈活、可維護的元件互動。
  • 整合多個知識源可提高響應質量。

評論留言