快轉到主要內容

LangChain 與 LlamaIndex 比較 - Naive RAG

這會是一系列的文章,從不同情境 use case 的實作去比較 LangChain 跟 LlamaIndex 的異同與優缺點,最後再總結

比較版本: LangChain 0.2.0 vs LlamaIndex 0.10.35


所有的實作都在這個 Github,建議邊看邊比對

Naive RAG 是預先把你的文件餵進資料庫。之後在線上接受問題,從資料庫搜出相關片段,再交給 LLM 生成答案。所以有兩個階段: 餵資料 + 搜尋並生成

餵資料 #

LangChainLlamaIndex 在餵資料的部分沒差太多。都能用各種 embedding (OpenAI, HuggingFace, …),都支援不少種資料庫。

差別在 LlamaIndex 針對 RAG 有非常多不同建立索引的模式,讓搜尋生成達到更好的效果(在這篇文章看不出來,我未來會寫另一篇)

另一個差別是,LlamaIndex 抽象了「餵資料」這件事,有內建的 IngestionPipeline : 每個對於文件的切片、處理,都是各別寫成 function,聚集起來變成 transformations pipeline

  # LlamaIndex ETL snippet

  vector_store = QdrantVectorStore(client=db_client, ...)
  node_parser = SentenceSplitter(chunk_size=512, ...)
  embed = HuggingFaceEmbedding(...)

  pipeline = IngestionPipeline(
      transformations=[node_parser, embed_model],  # LOOK AT THIS!
      vector_store=vector_store
  )
  pipeline.run(documents=documents)  # 餵資料

而 LangChain 反而是很平鋪直敘地寫

  # LangChain ETL snippet

  text_splitter = RecursiveCharacterTextSplitter(...)
  embed = HuggingFaceEmbedding(...)

  nodes = text_splitter.split_documents(documents)
  Qdrant.from_documents(  # 對,這行在餵資料
      ...
      embedding=emb
  )

搜尋生成 #

這是兩者差別最大的,務必比對 LangChain (la_rag.py) vs. LlamaIndex (ll_rag.py)

專注在不同的抽象 #

如果只看程式碼的最主要部分:

LangChain

  rag_chain = (
      {'context': retriever | _format_docs, 'query': RunnablePassthrough()}
      | prompt_template
      | llm
      | StrOutputParser()
  )

  response = rag_chain.invoke("要怎麼申請長照2.0?")
  print(response)

LlamaIndex

  response_synthesizer = get_response_synthesizer(
      llm=llm,
      text_qa_template=prompt_template,
  )

  query_engine = RetrieverQueryEngine(
      retriever=retriever,
      response_synthesizer=response_synthesizer
  )

  response = query_engine.query("要怎麼申請長照2.0?")
  print(response)

可以看到 LangChain 有非常特殊的 “pipe” 語法,這是 LCEL (LangChain Expression Language),賣點在把每個「元件」的 input / output 串起來。

而 LlamaIndex 能看到很明顯的 class RetrieverQueryEngine 看起來就是專門做查詢的,但在 LangChain 你只看到一個 “chain”

  • LangChain 著眼在把步驟串起來,也就是抽象化「流程」
  • LlamaIndex 著眼在 RAG 的重要元素,也就是抽象化「搜尋+生成」

就好像樂高:LlamaIndex 是直接一個產品包,但 LangChain 是很多基本的積木。

當然 LangChain 也有 BaseLanguageModel, ChatPromptTemplate 這些 class,但你需要把他們組起來。看似簡單,不過我會在下一篇文章闡述更多

兩者都能達到目標,然而 #

LlamaIndex 在設計上

  • 物件導向
  • 有很多 RAG 領域的 class,一條龍從最基本一路包到最外層,且針對不同實際案例應用,很多都有實作(挑看看選看看唷)
  • 內建就有他們寫好的 prompt (也就是在我的範例程式內,其實我可以不去客製化的)
  • 要客製化流程或 prompt 也不難,不過要懂他們的 class 設計,還有參數可能會傳很深,但剩下就是寫 python

LangChain 在設計上

  • 雖然有基本的 class,但更多是主打 LCEL – 其實是 Runnable,那些 pipe 只是表象
  • LCEL 很有 functional programming language 那一味。只要超出範例可抄的,要客製化就要「多學一種語言」
  • 所以可以看看 LCEL 這一頁,如果沒有耐心讀,那可以不用 LangChain 了
  • 沒有內建的 prompt,但要加不難,還可運用社群的智慧 LangChain Hub
  • 對於 streaming, async, batch 執行有比較好的支援

這篇還沒有討論 serving, instrumentation, agentic flow 等等,是單就 RAG 討論,所以或許看起來會不公平。但也別忘了要做 RAG 的話,兩者都能做到是沒問題的

聽起來很美好?看不出差別?不過這篇只是一問一答的 RAG QA,還不是多次對話的 RAG Chat。 在下一篇你更能看到兩者不同之處


附註:LangChain 原本也有像 LLMChain, ConversationChain 這種比較「高階」的 class,但在 v0.1 的末期要強推 LCEL / Runnable 就逐漸 deprecate,預計 v0.3 被移除

可以看 v0.1 的文件: https://python.langchain.com/v0.1/docs/modules/chains/


若您覺得有趣, 請 追蹤我的Facebook 或  Linkedin, 讓你獲得更多資訊!