LangChain は LLM アプリケーションの開発に採用すべきではない
TL;DR
個人の意見ですが LangChain は不必要にコードが複雑で、設計が悪いので production での採用をおすすめしません
LangChain とは
LangChain とは、大規模言語モデル(LLM) に対して簡単なインターフェイスを提供するライブラリです。またその機能は多岐に渡り、Index (ベクタ DB を通して PDF などの外部データを用いるための機能) や Chains (一連の処理を連続実行する機能)などのモジュールを含みます。
LangChain とは - Hakky
長所として、以下が挙げられます。
- OpenAI などの LLM Provider を使ってプログラムから回答を生成するのには良い
- チュートリアルに沿って動かすのは高速に終わる
- 複数の LLM Provider やベクタ DB に対して統一されたインターフェイスを提供するため、差異を吸収できる
- プロトタイプの作成にも用途によっては適切な選択肢となりえる
お気持ち
LangChain の最も大きな問題として設計が悪いということが挙げられます。複数の LLM Provider やの処理を吸収するために過度な抽象化を行っていて、不必要な複雑化を産んでいます。実際にソースコードを見てみると処理を追うのに労力を費やすことになります。
またドキュメントも不十分であり、実際の処理や使い方を把握するためにソースコードを追うと前述の複雑化の問題が顕在化します。
加えて、PR Review があまり機能しておらず、各 LLM Provider やベクタ DB をラップするためのコードが肥大化していくなどの問題もあります。
そもそも OpenAI を使いだけであれば Completion や Embedding API を呼ぶぐらいで済むことが多く、公式ライブラリや直接 API を叩いて困ることはありません。
複数の LLM Provider やベクタ DB を吸収するラッパーとしては有用かもしれませんが、それをすることはあるでしょうか?その考慮は YAGNI 原則に反してはいないでしょうか?
LangChain の設計の悪さは Hackernews や Reddit、Twitter を見ていても痛烈に批判されていて、LangChain を不採用にしたり剥がしたりした例は散見されます。実際筆者のプロジェクトでも LangChain は不採用としました。
(訳) 残念ながら、Langchain は設計が非常に不十分であり、重複する抽象化で満たされており、多くの混乱を引き起こしています。ドキュメントの構成も不十分です
Langchain is unfortunately very poorly designed and is filled with overlapping abstractions which leads to a lot of confusion. The documentation suffers from poor organisation too
LangChain is pointless
(訳) 率直に言って、コードを見ただけで LangChain が garbage software だとわかりました。
それでも、物事がどう動くことになっているのかを理解するために、早く仕事を終わらせるのに役立っている。AI のレシピ本のようなものだ。アプローチが絞れたら、langchain がラップしていると思われるものの上にすべてを書き直すつもりだ。今のところ、個々のライブラリを探し出したり、api を学んだりするよりもその方が早い。基本的にはノートブックに残すつもりだFrankly I could see langchain is garbage software just by looking at the code.
It still helps me get shit done fast to figure out how things are supposed to work. Sort of a cookbook of AI recepies. Once I have an approach narrowed down I'll rewrite everything on top of stuff langchain is supposedly wrapping. For now it's faster than tracking down individual libraries and learning the apis. It will stay in notebooks basically.
The Problem With LangChain - Hacker News
具体例: ChatCompletion
具体例として OpenAI 公式ライブラリと LangChain で ChatCompletion を行う例を見て、LangChain の方を深掘りしていきます。
OpenAI 公式ライブラリで ChatCompletion を呼ぶ例
import os
import openai
openai.api_key = os.getenv("OPENAI_API_KEY")
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{"role": "user", "content": "Hello ChatGPT!"},
],
)
print(response["choices"][0]["message"]["content"]) # Hello! How can I assist you today?
LangChain で ChatCompletion を呼ぶ例
from langchain.chat_models import ChatOpenAI
from langchain.schema import (
AIMessage,
HumanMessage,
SystemMessage,
)
chat = ChatOpenAI()
response = chat([HumanMessage(content="Hello ChatGPT!")])
print(response.content) # Hello! How can I assist you today?
どちらも簡単ですね。
LangChain のリクエストを送る処理
では LangChain 具体的にリクエストを送る部分を追っていきます。
- LangChain のコードを見ると
ChatOpenAI
クラスの__call__
メソッドを参照すると良さそうです。
-
ChatOpenAI
の__call__
メソッドは親クラスのBaseChatModel
で定義されています。
-
generate
メソッドも同様にBaseChatModel
で定義されています。-
_generate_with_cache
が本質的な処理に見えます
-
-
_generate_with_cache
もBaseChatModel
で定義されています。- キャッシュに関する処理を除けば
_generate
で処理してそうです。
- キャッシュに関する処理を除けば
-
_generate
はBaseChatModel
で定義されておらず、継承元のChatOpenAI
で定義されています。- 今回は streaming ではないので
_completion_with_retry
で API を叩いてそうです
- 今回は streaming ではないので
-
completion_with_retry
はChatOpenAI
で定義されています- 本質的な処理は
self.client.create
のようです
- 本質的な処理は
-
ChatOpenAI
の__init__
メソッドは親クラスを見てみても見当たりません。self.client
に代入している部分を探すとChatOpenAI
に以下のような処理が見当たります
@root_validator()
def validate_environment(cls, values: Dict) -> Dict:
# 省略
try:
values["client"] = openai.ChatCompletion
self.client
がopenai.ChatCompletion
らしいので結局openai.ChatCompletion.create
を呼んでいることがわかりました-
root_validator
デコレータがついているので Pydantic のBaseModel
を継承していればvalidate_environment
が初期化時に実行されそうです
`ChatOpenAI` が `BaseModel` を継承していることの確認
-
@root_validator
デコレータのついたメソッドは Pydantic (v1) のBaseModel
を継承していれば初期化時に実行されます- コードを追うと以下のような継承関係がわかります
-
ChatOpenAI
->BaseChatModel
->BaseLanguageModel
->Serializable
->BaseModel
-
- コードを追うと以下のような継承関係がわかります
結論
以下のような流れを追うと reddit などで批判されている理由がわかるのではないでしょうか。これが恐ろしいのはこのコードは最も簡単な例ということです。streaming を使った場合の処理や、他のよりブラックボックス化されたモジュールを使うとより酷いことになりそうです。
LangChain のバグを踏んでエラートラッキングをしたり、LLM のパフォーマンスチューニングや表示の変更で細かなカスタマイズをする際にこのような悲しい体験をすることになります。そういった理由で私はアプリケーション開発時に LangChain を採用しないことに決めました。
一応フォローしておくと、継続的に開発するアプリケーションではない場合や、複数の LLM Provider を比較したい場合は選択肢の一つとして出てくるかと思っています。メリット・デメリットを比較しつつ快適な LLM ライフを送りましょう。
Discussion