1719451916
2024-06-24 10:27:50
Octomindでは、複数のLLMを備えたAIエージェントを使用して、Playwrightでエンドツーエンドのテストを自動的に作成して修正しています。ほんの数か月前までは、 LangChainフレームワーク。
この記事では、LangChain での私たちの苦労と、その厳格な高レベルの抽象化をモジュール式のビルディング ブロックに置き換えることでコード ベースが簡素化され、チームの満足度と生産性が向上した理由について説明します。
背景
私たちは、2023 年初頭から 12 か月以上にわたって LangChain を本番環境で使用し、2024 年に削除しました。
LangChainは2023年に私たちにとって最良の選択であるように思えました。コンポーネントとツールの印象的なリストがあり、その人気は急上昇しました。それは「開発者がアイデアをわずか午後で実用的なコードに仕上げることを可能にします。しかし、要件が高度化するにつれて問題が表面化し始め、LangChain は生産性ではなく摩擦の原因となってしまいました。
柔軟性のなさが明らかになったため、私たちはすぐに LangChain の内部を調べて、システムの低レベルの動作を改善することにしました。しかし、LangChain は多くの詳細を意図的に抽象化しているため、必要な低レベルのコードを書くのは簡単ではなかったり、不可能だったりすることがよくありました。
初期のフレームワークであることの危険性
AI と LLM は急速に変化する分野であり、毎週のように新しい概念やアイデアが登場しています。そのため、LangChain などのフレームワークが複数の新興テクノロジーを中心に作成される場合、時の試練に耐える抽象化を設計することは非常に困難です。
もし私が LangChain のようなフレームワークを彼らが構築しようとしていたときに構築しようとしていたとしても、より良い結果は得られなかっただろうと確信しています。後から見れば間違いを指摘するのは簡単ですし、この投稿の目的は LangChain のコア開発者やその貢献者を不当に批判することではありません。誰もが最善を尽くしています。
適切に設計された抽象化を作成するのは難しい – 要件が十分に理解されている場合でも。ただし、このように流動的な状態のコンポーネント (エージェントなど) をモデル化する場合は、下位レベルのビルディング ブロックに対してのみ抽象化を使用する方が安全です。
LangChainの抽象化の問題
LangChain は、当初は私たちのシンプルな要件が使用法の想定と一致していたため、役立ちました。しかし、その高レベルの抽象化により、すぐにコードが理解しにくくなり、保守が困難になりました。私たちのチームが機能の構築と同じくらい多くの時間を LangChain の理解とデバッグに費やし始めたとき、それは良い兆候ではありませんでした。
LangChain の抽象化へのアプローチに関する問題は、英語の単語をイタリア語に翻訳するというこの単純な例で実証できます。
以下は OpenAI パッケージのみを使用した Python の例です。
from openai import OpenAI
client = OpenAI(api_key="" )
text = "hello!"
language = "Italian"
messages = [
{"role": "system", "content": "You are an expert translator"},
{"role": "user", "content": f"Translate the following from English into {language}"},
{"role": "user", "content": f"{text}"},
]
response = client.chat.completions.create(model="gpt-4o", messages=messages)
result = response.choices[0].message.content
これは、1 つのクラスと 1 つの関数呼び出しを含む、理解しやすいシンプルなコードです。残りは標準の Python です。
これを LangChain のバージョンと比較してみましょう。
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
os.environ["OPENAI_API_KEY"] = ""
text = "hello!"
language = "Italian"
prompt_template = ChatPromptTemplate.from_messages(
[("system", "You are an expert translator"),
("user", "Translate the following from English into {language}"),
("user", "{text}")]
)
parser = StrOutputParser()
chain = prompt_template | model | parser
result = chain.invoke({"language": language, "text": text})
コードはほぼ同じことを行っていますが、類似点はそれだけです。
これで、3 つのクラスと 4 つの関数呼び出しができました。しかし、最も懸念されるのは、3 つの新しい抽象化が導入されたことです。
- プロンプトテンプレート: LLMにプロンプトを提供する
- 出力パーサー: LLMからの出力を処理する
- チェーン: LangChain の「LCEL 構文」が Python の `|` 演算子をオーバーライドする
LangChain が達成したのは、目に見えるメリットもなくコードの複雑さが増しただけです。
このコードは初期段階のプロトタイプには適しているかもしれない。しかし 生産に使用するには、すべてのコンポーネントを適切に理解する必要があります 実際の使用状況で予期せずクラッシュすることがないようにするため、指定されたデータ構造に準拠し、それらの抽象化に基づいてアプリケーションを設計する必要があります。
Python の別の抽象化の比較を見てみましょう。今回は、API から JSON を取得します。
組み込みの http パッケージを使用する:
import http.client
import json
conn = http.client.HTTPSConnection("api.example.com")
conn.request("GET", "/data")
response = conn.getresponse()
data = json.loads(response.read().decode())
conn.close()
リクエスト パッケージの使用:
import requests
response = requests.get("https://api.example.com/data")
data = response.json()
勝者は明らかです。それが優れた抽象化の感覚です。
確かに、これらは些細な例です。しかし、私が言いたいのは、適切な抽象化によってコードが簡素化され、コードを理解するために必要な認知負荷が軽減されるということです。
LangChainは、より少ないコードでより多くのことを実現することで、あなたの生活を楽にしようとしています。 詳細をあなたから隠すしかし、シンプルさと柔軟性が犠牲になると、抽象化の価値は失われます。
LangChainは、 他の抽象化の上に抽象化を重ねるそのため、API を正しく使用する方法を理解するには、ネストされた抽象化の観点から考える必要があることがよくあります。これにより、新しい機能を実装する代わりに、膨大なスタック トレースを理解し、自分で作成していない内部フレームワーク コードをデバッグすることになります。
LangChainが開発チームに与える影響
私たちのアプリケーションは、テストケースの検出、Playwright テストの生成、自動修正など、さまざまな種類のタスクを実行するために AI エージェントを多用します。
単一のシーケンシャルエージェントのアーキテクチャからより複雑なものに移行したい場合、LangChain が制限要因でした。たとえば、サブエージェントを生成して、元のエージェントと対話できるようにします。または、複数の専門エージェントが互いに対話します。
別の例では、ビジネス ロジックと LLM からの出力に基づいて、エージェントがアクセスできるツールの可用性を動的に変更する必要がありました。しかし、LangChain はエージェントの状態を外部から監視する方法を提供していないため、LangChain エージェントで利用できる限られた機能に合わせて実装の範囲を縮小する必要がありました。
これを削除すると、要件を LangChain に適したソリューションに変換する必要がなくなり、コードを書くだけで済むようになりました。
では、LangChain 以外の場合は、どのフレームワークを使用すべきでしょうか? おそらくフレームワークはまったく必要ないかもしれません。
AI アプリケーションを構築するためのフレームワークが必要ですか?
LangChain は、私たちがアプリケーションの構築に集中できるように、LLM 機能を提供することで、早い段階で私たちを支援してくれました。しかし、振り返ってみると、長期的にはフレームワークがなかった方が良かったと思います。
LangChain のコンポーネントの長いリストを見ると、LLM を利用したアプリケーションの構築は複雑であるという印象を受けます。しかし、ほとんどのアプリケーションに必要なコア コンポーネントは通常、次のとおりです。
- LLM通信用クライアント
- 関数呼び出しのための関数/ツール
- RAG 用ベクターデータベース
- 追跡、評価などのための可観測性プラットフォーム。
残りは、それらのコンポーネントに関するヘルパー (例: ベクター データベースのチャンク化と埋め込み)、またはデータの永続化とキャッシュによるファイルとアプリケーションの状態の管理などの通常のアプリケーション タスクです。
フレームワークなしで AI 開発を始めると、確かに、独自のツールボックスをまとめるのに時間がかかり、事前の学習と調査も必要になります。しかし、これから運用する分野の基礎を学習しているので、これは有意義な時間であり、あなたとアプリケーションの将来への価値ある投資です。
ほとんどの場合、 LLMの使い方はシンプルでわかりやすい主に、シーケンシャルなコードを記述し、プロンプトを繰り返し、出力の品質と予測可能性を向上させることになります。ほとんどのタスクは、シンプルなコードと比較的小規模な外部パッケージのコレクションで実現できます。
エージェントを使用する場合でも、エージェントの状態と応答を処理するビジネス ロジックを使用して、事前に決定された連続フローでエージェント間の単純な通信を行う以上のことを行うことはほとんどありません。これを実装するためのフレームワークは必要ありません。
エージェントの分野は刺激的な可能性と興味深いユースケースで急速に進化していますが、エージェントの使用パターンが固まるまでは、今のところはシンプルにしておくことをお勧めします。
ビルディングブロックでスピードと効率を維持
質の悪いコードを本番環境に出荷していないと仮定すると、チームが革新と反復を行える速度が成功の最も重要な指標となります。AI 分野における開発の多くは、実験とプロトタイピングによって推進されています。
しかしフレームワークは通常、 確立された使用パターンに基づいた構造の強化 – LLM を利用したアプリケーションにはまだ備わっていない機能です。新しいアイデアをフレームワーク固有のコードに変換する必要があるため、反復の速度が制限されます。
ビルディング ブロック アプローチでは、慎重に選択された外部パッケージを使用したシンプルな低レベル コードが優先され、アーキテクチャがスリムに保たれるため、開発者は解決しようとしている問題に集中できます。
ビルディングブロックとは、包括的に理解され、変更される可能性が低いと思われる単純なもののことです。たとえば、ベクターデータベースです。これは、 基本的な機能セットを備えたモジュール式コンポーネントなので、簡単に交換できます。 削除して置き換えます。学習速度と各反復サイクルから得られる価値を最大化するには、コードベースをスリムかつ適応性のあるものにする必要があります。
. . .
LangChain に関する私たちの課題と、フレームワークから完全に移行することが私たちのチームにとって非常に有益であった理由を、思慮深く公平に説明できたことを願っています。
抽象化を最小限に抑えたモジュール式のビルディング ブロックを使用するという現在の戦略により、より迅速かつ少ない摩擦で開発できるようになりました。
ファビアン・ボス
Octomind のスタッフ ディープラーニング エンジニア
#AIエージェントの構築にLangChainを使用しなくなった理由