見出し画像

LCEL (LangChain Expression Language) 入門

「LCEL」(LangChain Expression Language)のはじめ方をまとめました。


1. LCEL と Chainインタフェース

LCEL」(LangChain Expression Language) は、チェーンを簡単に記述するための宣言型の手法です。

単純なアプリケーションではLLMの単独使用で問題ありませんが、複雑なアプリケーションではLLMを相互に、または他のコンポーネントと連鎖させる必要があります。「LangChain」は、コンポーネントを「チェーン」するための2つの高レベルのフレームワークを提供しています。

・Chainインターフェイス (従来の手法)
・LCEL (LangChain Expression Language)

新しいアプリケーションを構築するときは「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. 標準インタフェース

主な標準インターフェイスは、次のとおりです。

・stream : チェーンを呼び出してストリーミングで返す (同期)
・invoke : チェーンを呼び出す (同期)
・batch : リスト入力でチェーンを呼び出す (同期)

対応する非同期メソッドもあります。

・astream : チェーンを呼び出してストリーミングで返す (非同期)
・ainvoke : チェーンを呼び出す (非同期)
・abatch : リスト入力でチェーンを呼び出す (非同期)
・astream_log : 最終応答に加えて、中間ステップもストリーミングで返す (非同期)

4-2. 入出力スキーマ

Runnableの入出力スキーマ (データ型) はコンポーネントごとに異なります。

・LLM
 ・入力型
: string, [Message], PromptValue
 ・出力型 : string
・ChatModel
 ・入力型
: string, [Message], PromptValue
 ・出力型 : Message
・Prompt
 ・入力型
: dict
 ・出力型 : PromptValue
・Retriever
 ・入力型
: string
 ・出力型 : [Document]
・Tool
 ・入力型
: string, dict
 ・出力型 : ツールに依存
・OutputParser
 ・入力型
: LLMの出力, ChatModelの出力
 ・出力型 : パーサーに依存

以下のプロパティで、入出力スキーマを確認できます。

・input_schema : 入力型のスキーマ (Pydantic)
・output_schema : 出力型のスキーマ (Pydantic)

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.invoke({"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'}
 ],
    :
}

関連



この記事が気に入ったらサポートをしてみませんか?