LangChain 與 LlamaIndex 比較 - Naive RAG
目錄
這會是一系列的文章,從不同情境 use case 的實作去比較 LangChain 跟 LlamaIndex 的異同與優缺點,最後再總結
- Naive RAG (這篇)
- Conversational RAG
- Simple agent / tool use
- 人類半規範的 Agentic flow
- 總結 (敬請期待)
比較版本: LangChain 0.2.0 vs LlamaIndex 0.10.35
所有的實作都在這個 Github,建議邊看邊比對
Naive RAG 是預先把你的文件餵進資料庫。之後在線上接受問題,從資料庫搜出相關片段,再交給 LLM 生成答案。所以有兩個階段: 餵資料 + 搜尋並生成
餵資料 #
LangChain 和 LlamaIndex 在餵資料的部分沒差太多。都能用各種 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/