這篇是上完 DeepLearning AI 上的課程 Building AI Applications with Haystack 後的心得分享,圖片也都是來自課程影片

Haystack 是什麼?

他是一個可以讓製作 AI 應用程式的流程變得更簡單的一個工具,他可以完成各種文字生成的應用,像是問答、文件檢索、RAG 這些都可以做到。 我覺得他會取這個名字也是因為他就像稻草堆一樣可以把所有 AI 會使用到的套件包在一起,Haystack 裡有兩個最主要的元素 – PipelineComponents 進行整個流程。 其實在實作過程中我覺得他很像小學電腦課會學到的 scratch,把功能包裝成一塊塊積木再堆疊起來。

title

Pipeline、Component 介紹

1. Pipeline

Pipeline 是由多個 Component 串接而成的處理流程,負責定義資料如何在元件間流動。

title

他可以有各種流動的方式,可以設計成直線流程,也可以是複雜的有向無環圖,支援分支與條件路徑。

title

2. Component

Component 是 Haystack 的最小功能單位,每個 Component 都有明確的任務,例如文件轉換(PDFToTextConverter)、文本切割(DocumentSplitter)、檢索(Retriever)、嵌入向量生成(Embedder)、語言模型推理(PromptNode / Generator)等。它們就像積木一樣,可以透過輸入與輸出組合在一起,形成一個完整的工作流程。每個 component 都會各自的 inputs 和 outputs。

title

實作

1. 建立一個 pipeline 架構

在這個例子中會展示如何將文件儲存進向量資料庫,這裡用到了四種 Component,分別是:

  • Converter:將文件檔案轉換成 haystack 內部的格式。
  • Splitter:將文本拆分成模型所需格式。
  • Embedder:將文本轉換為 embedding。
  • Writer:將 embedding 寫進向量資料庫。

我們每次需要做的都是先定義 pipeline 和 component,將定義好的 component 放進 pipeline,再將他們依照順序串接起來。

from haystack import Pipeline

from haystack.components.converters.txt import TextFileToDocument
from haystack.components.preprocessors.document_splitter import DocumentSplitter
from haystack.components.embedders import OpenAIDocumentEmbedder
from haystack.components.writers import DocumentWriter

converter = TextFileToDocument()
splitter = DocumentSplitter()
embedder = OpenAIDocumentEmbedder()
writer = DocumentWriter(document_store=document_store)

indexing_pipeline = Pipeline()

indexing_pipeline.add_component("converter", converter)
indexing_pipeline.add_component("splitter", splitter)
indexing_pipeline.add_component("embedder", embedder)
indexing_pipeline.add_component("writer", writer)

indexing_pipeline.connect("converter", "splitter")
indexing_pipeline.connect("splitter", "embedder")
indexing_pipeline.connect("embedder", "writer")

執行上面的程式後就會看到 pipeline 的資訊,他會跟你說這個 pipeline 中有多少元素、怎麼連接這樣子。 但如果真的要使用這個 pipeline 是需要透過 indexing_pipeline.run() 才能實現把文件放進去向量資料庫的實際功能喔!

indexing_pipeline.run({"converter": {"sources": ["data/davinci.txt"]}})

然後如果等到流程變得更複雜,也可以透過 indexing_pipeline.show() 看到圖像化的 pipeline 長什麼樣

title

2. Conditional router

上面有提到 Haystack 除了直線流程,它也可以做出分支流程,這個章節就來展示他是怎麼做的~這個例子的目標是它一樣有 RAG 資料庫,如果輸入問題是無法從資料庫中取得的,就會希望模型會去網路上尋找答案。

和範例 1 重複的流程我就跳過了,下面直接寫最關鍵的部分。這段定義了什麼樣的情況才需要從網路尋找答案,這個流程在 prompt 裡設定了如果模型回傳 no_answer 那就代表這個流程會導到網路搜尋的分支。

routes = [
  {
    "condition": "{{'no_answer' in replies[0]|lower}}",
    "output": "{{query}}",
    "output_name": "go_to_websearch",
    "output_type": str,
  },
  {
    "condition": "{{'no_answer' not in replies[0]|lower}}",
    "output": "{{replies[0]}}",
    "output_name": "answer",
    "output_type": str,
  },
]

這裡則定義了整個流程使用的工具和流程,ConditionalRouter 就是上一段提到的決定是否使用網路搜尋的邏輯。

rag_or_websearch = Pipeline()
rag_or_websearch.add_component("retriever", InMemoryBM25Retriever(document_store=document_store))
rag_or_websearch.add_component("prompt_builder", PromptBuilder(template=rag_prompt_template))
rag_or_websearch.add_component("llm", OpenAIGenerator())
rag_or_websearch.add_component("router", ConditionalRouter(routes))
rag_or_websearch.add_component("websearch", SerperDevWebSearch())
rag_or_websearch.add_component("prompt_builder_for_websearch", PromptBuilder(template=prompt_for_websearch))
rag_or_websearch.add_component("llm_for_websearch", OpenAIGenerator())

rag_or_websearch.connect("retriever", "prompt_builder.documents")
rag_or_websearch.connect("prompt_builder", "llm")
rag_or_websearch.connect("llm.replies", "router.replies")
rag_or_websearch.connect("router.go_to_websearch", "websearch.query")
rag_or_websearch.connect("router.go_to_websearch", "prompt_builder_for_websearch.query")
rag_or_websearch.connect("websearch.documents", "prompt_builder_for_websearch.documents")
rag_or_websearch.connect("prompt_builder_for_websearch", "llm_for_websearch")

接下來就是實際使用流程了,因為在這個範例中的資料庫放的是有關 Retriever、Embedder、Generator、File Converter 的介紹,所以最後結果會得到 answer 直接顯示答案。

query= "What is a retriever for?"
rag_or_websearch.run({"prompt_builder":{"query": query},
                      "retriever": {"query": query},
                      "router": {"query": query}})

{'llm': {'meta': [{'model': 'gpt-3.5-turbo-0125',
   'index': 0,
   'finish_reason': 'stop',
   'usage': {'completion_tokens': 20,
    'prompt_tokens': 117,
    'total_tokens': 137,
    'completion_tokens_details': {'reasoning_tokens': 0}}}]},
 'router': {'answer': 'Retrievers are used to retrieve relevant documents to a user query using keyword search or semantic search.'}}

感想

其實我覺得 Haystack 如果是對於想要快速地實作小玩具或是對程式設計不熟悉的使用者來說,真的是個非常適合快速建立流程的好工具,但通常越方便的工具就會有個缺點: 就是彈性不高,因為其實可以使用哪些套件都是已經被寫死的,所以我認為如果要開發一個大規模的系統的話,這樣的工具可能不是最佳解。