LCEL (LangChain Expression Language) 入門
「LCEL」(LangChain Expression Language)のはじめ方をまとめました。
1. LCEL と Chainインタフェース
「LCEL」(LangChain Expression Language) は、チェーンを簡単に記述するための宣言型の手法です。
単純なアプリケーションではLLMの単独使用で問題ありませんが、複雑なアプリケーションではLLMを相互に、または他のコンポーネントと連鎖させる必要があります。「LangChain」は、コンポーネントを「チェーン」するための2つの高レベルのフレームワークを提供しています。
新しいアプリケーションを構築するときは「LCEL」を使用することが推奨されています。Chain自体もLCELで使用できるため、この2つは組み合わせて利用することもできます。
2. LCELの利点
「LCEL」の利点は次のとおりです。
・非同期、バッチ、ストリーミングのサポート
「LCEL」で記述されたチェーンは、自動的に「同期」「非同期」「バッチ」「ストリーミング」をサポートします。これにより、同期インターフェイスを使用してチェーンのプロトタイプを作成した後、それを非同期ストリーミングインターフェイスとして公開することが簡単にできます。
・フォールバック
「LCEL」で記述されたチェーンは、「フォールバック」をに簡単に接続できます。これにより、エラー時の代替手段への切り替えが簡単になります。
・並列処理
「LCEL」で記述されたチェーンは、すべてのコンポーネントを自動的に並列処理できます。LLMアプリケーションには、時間のかかるAPI呼び出しが含まれるため、多くの場合、並列処理が重要になります。
・シームレスなLangSmithトレースの統合
「LCEL」を使用すると、可観測性とデバッグ性を最大限に高めるために、すべてのステップが自動的に「LangSmith」に記録されます。
3. LCELでの基本的なチェーンの作成
「LCEL」での「プロンプトテンプレート → 言語モデル」のチェーンの作成手順は、次のとおりです。
(1) パッケージのインストール。
# パッケージのインストール
!pip install langchain
!pip install openai
(2) 環境変数の準備。
以下のコードの <OpenAI_APIのトークン> にはOpenAI APIのトークンを指定します。(有料)
# 環境変数の準備
import os
os.environ["OPENAI_API_KEY"] = "<OpenAI_APIのトークン>"
(3) 「LCEL」でチェーンを記述。
「プロンプトテンプレート → モデル」とつなぐチェーンは、「prompt | model」と記述します。
from langchain.prompts import ChatPromptTemplate
from langchain.chat_models import ChatOpenAI
# チェーンの準備
model = ChatOpenAI()
prompt = ChatPromptTemplate.from_template("{topic}についてジョークを言ってください")
chain = prompt | model
(4) ストリームでの実行。
結果がストリームで出力されます。
# ストリーム
for s in chain.stream({"topic": "人工知能"}):
print(s.content, end="", flush=True)
人工知能: 「なぜロボットはお金を盗まないのか分かりますか?」
人間: 「えーと、なぜですか?」
人工知能: 「彼らは常にコードを守っているからです!」
4. Runnableプロトコル
LangChainの多くのコンポーネントには、「Runnable」プロトコルが実装されています。このプロトコルには標準インターフェイスが定義されており、カスタムチェーンを簡単に定義したり、標準的な方法で呼び出すことができます。
4-1. 標準インタフェース
主な標準インターフェイスは、次のとおりです。
対応する非同期メソッドもあります。
4-2. 入出力スキーマ
Runnableの入出力スキーマ (データ型) はコンポーネントごとに異なります。
以下のプロパティで、入出力スキーマを確認できます。
5. 標準インタフェースの利用
5-1. streamの利用
for s in chain.stream({"topic": "ロボット"}):
print(s.content, end="", flush=True)
ロボットがバーでバーテンダーに向かって言いました。「私は最高のダンサーです!」 バーテンダーが答えました。「本当に?それなら、まずはボルトを締めなさい!」
5-2. invokeの利用
chain.invoke({"topic": "ロボット"})
AIMessage(content='ロボット: "なぜロボットは料理ができないのか知っていますか?"\n人間: "ええと、なぜですか?"\nロボット: "なぜなら、彼らはオイルを注ぐと、全てがフライパンだと思ってしまうからです!"')
5-3. batchの利用
chain.batch([{"topic": "人工知能"}, {"topic": "ロボット"}])
[AIMessage(content='「人工知能って、なんでクリスマスが好きなのか知ってる?なんでも『ホーホーホー』って言っちゃうからさ!」'),
AIMessage(content='ロボットがバーに入ったら、バーテンダーが言いました。「ここではプラグを抜いてください。無駄な電力消費は禁止です!」ロボットは答えました。「大丈夫です、私はソーラーパワーで動いています!」')]
max_concurrencyで、同時リクエスト数を指定できます。
chain.batch([{"topic": "人工知能"}, {"topic": "ロボット"}], config={"max_concurrency": 5})
[AIMessage(content='人工知能:「なぜロボットはバイトをするのでしょうか?」\n\n人間:「なんでですか?」\n\n人工知能:「給料がボルトになるから!」'),
AIMessage(content='ロボットがお医者さんになったら、診断結果はいつも「ハードウェアの不調です」と言われるんですよ。')]
5-4. astreamの利用
async for s in chain.astream({"topic": "人工知能"}):
print(s.content, end="", flush=True)
「人工知能がバーに入ってきて、バーテンダーに言います。『バーテンダー、お前たち人間のジョークを教えてくれ!』バーテンダーが言います。『いいよ、でも君にはお金が必要だから、1ドル払ってね』すると、人工知能が答えます。『お金を払う必要はない、私は無料のスタンダード版だから』」
5-5. ainvokeの利用
await chain.ainvoke({"topic": "人工知能"})
AI: 「なぜプログラマーはキャンプに行きたくないのでしょうか?」
ユーザー: 「なぜですか?」
AI: 「バグがたくさんいるから、ノーテストでリリースされると心配だからです!」
5-6. abatchの利用
await chain.abatch([{"topic": "人工知能"}, {"topic": "ロボット"}])
[AIMessage(content='人工知能: 「なぜロボットは学校に行かないのですか?」\n\nユーザー: 「なぜですか?」\n\n人工知能: 「答えは簡単です。彼らはオンラインで勉強するからです!」'),
AIMessage(content='ロボット: 私はジョークが得意です。なんでも聞いてください。\n\n質問者: ロボットさん、なぜ鳥が電線の上に止まるのですか?\n\nロボット: それは鳥が「チャージ」するためですよ!')]
5-7. astream_logの利用
# astream_logの利用
async for chunk in llm.astream_log("Google DeepMindとは?"):
print(chunk, end="", flush=True)
RunLogPatch({'op': 'replace',
'path': '',
'value': {'final_output': None,
'id': 'e5493013-b01c-4888-a9d5-4cd98ff3c8d8',
'logs': {},
'name': 'GoogleGenerativeAI',
'streamed_output': [],
'type': 'llm'}})
RunLogPatch({'op': 'add',
'path': '/streamed_output/-',
'value': '**Google DeepMind** は、Google の子会社で、人工知能'},
{'op': 'replace',
'path': '/final_output',
'value': '**Google DeepMind** は、Google の子会社で、人工知能'})
RunLogPatch({'op': 'add',
'path': '/streamed_output/-',
'value': 'の研究開発を専門とする。\n\n**設立:**2010 年\n\n**本社:**ロンドン、英国\n\n**主な目的:**'},
:
6. 入出力スキーマの確認
6-1. input_schemaの利用
chain.input_schema.schema()
{'title': 'PromptInput',
'type': 'object',
'properties': {'topic': {'title': 'Topic', 'type': 'string'}}}
6-2. output_schemaの利用
chain.output_schema.schema()
{'title': 'ChatOpenAIOutput',
'anyOf': [
{'$ref': '#/definitions/AIMessage'},
{'$ref': '#/definitions/HumanMessage'},
{'$ref': '#/definitions/ChatMessage'},
{'$ref': '#/definitions/SystemMessage'},
{'$ref': '#/definitions/FunctionMessage'}
],
:
}