[翻訳] LangChain Expression Languageを使い始める

[Get started \| 🦜️🔗 Langchain](https://python.langchain.com/docs/expression_language/get_started)の翻訳です。

:::note warn
本書は抄訳であり内容の正確性を保証するものではありません。正確な内容に関しては原文を参照ください。
:::

LCELによって、基本的なコンポーネントによる複雑なチェーンを容易に構築することができ、ストリーミング、並列処理、ロギングのような機能をすぐに活用できます。

# 基本的な例: プロンプト + モデル + 出力パーサー

最も基本的で一般的なユースケースは、プロンプトテンプレートとモデルのチェーンを構成するというものです。これがどのように動作するのかを見るために、トピックを受け取りジョークを生成するチェーンを作成しましょう:

```py
%pip install --upgrade --quiet  langchain-core langchain-community langchain-openai
```
```py
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI

prompt = ChatPromptTemplate.from_template("tell me a short joke about {topic}")
model = ChatOpenAI(model="gpt-4")
output_parser = StrOutputParser()

chain = prompt | model | output_parser

chain.invoke({"topic": "ice cream"})
```
```
"Why don't ice creams ever get invited to parties?\n\nBecause they always drip when things heat up!"
```

LCELを用いて異なるコンポーネントを単一のチェーンにまとめ上げているこのコードの以下の行に注意してください:

```py
chain = prompt | model | output_parser
```

シンボル`|`は、さまざまなコンポーネントの出力を次のコンポーネントの入力として流し込むチェーンである[unixのパイプ演算子](https://en.wikipedia.org/wiki/Pipeline_(Unix))と似ています。

このチェーンでは、ユーザーの入力はプロンプトテンプレートに引き渡され、プロンプトテンプレートの出力はモデルに引き渡され、モデルの出力は出力パーサーに引き渡されます。何が起きているのかをきちんと理解するためにそれぞれのコンポーネントを個別に見ていきましょう。

## 1. プロンプト

`prompt`は`BasePromptTemplate`であり、テンプレート変数のディクショナリーを受け取り、`PromptValue`を生成することを意味します。`PromptValue`は、(入力として文字列を受け取る)`LLM`や(入力としてメッセージのシーケンスを受け取る)`ChatModel`に引き渡されるコンプリートされたプロンプトに対するラッパーです。`BaseMessages`や文字列を生成する両方のロジックを定義しているので、いずれの言語モデルでも動作します。

```py
prompt_value = prompt.invoke({"topic": "ice cream"})
prompt_value
```
```
ChatPromptValue(messages=[HumanMessage(content='tell me a short joke about ice cream')])
```
```py
prompt_value.to_messages()
```
```
[HumanMessage(content='tell me a short joke about ice cream')]
```
```py
prompt_value.to_string()
```
```
'Human: tell me a short joke about ice cream'
```

## 2. モデル

`PromptValue`が`model`に引き渡されます。この場合、`model`は`ChatModel`であり、出力は`BaseMessage`であることを意味します。

```py
message = model.invoke(prompt_value)
message
```
```
AIMessage(content="Why don't ice creams ever get invited to parties?\n\nBecause they always bring a melt down!")
```

`model`が`LLM`の場合、出力は文字列となります。

```py
from langchain_openai.llms import OpenAI

llm = OpenAI(model="gpt-3.5-turbo-instruct")
llm.invoke(prompt_value)
```
```
'\n\nRobot: Why did the ice cream truck break down? Because it had a meltdown!'
```

## 3. 出力パーサー

そして、最後に`model`の出力は`BaseOutputParser`である`output_parser`に引き渡され、入力として文字列や`BaseMessage`を受け取ることを意味します。特に`StrOutputParser`はシンプルにいかなる入力を文字列に変換します。

```py
output_parser.invoke(message)
```
```
"Why did the ice cream go to therapy? \n\nBecause it had too many toppings and couldn't find its cone-fidence!"
```

## 4. 全体のパイプライン

以下のステップを踏みます:

1. `{"topic": "ice cream"}`のように欲しいトピックに関するユーザー入力を引き渡します。
1. プロンプトを構成するために`topic`を用いてPromptValueを構築するために、`prompt`コンポーネントがユーザー入力を受け取ります。
1. 生成されたプロンプトを`model`コンポーネントを受け取り、評価のためにOpenAIのLLMモデルに引き渡します。モデルから生成された出力は`ChatMessage`オブジェクトとなります。
1. 最後に、`output_parser`コンポーネントが`ChatMessage`を受け取り、呼び出されたメソッドから返却されるPython文字列に変換します。

![Screenshot 2024-03-19 at 14.58.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/f7d7baa5-b6d6-461f-fcb0-6adbf9fde07d.png)


任意のコンポーネントの出力に興味があるのであれば、中間結果を確認するために`pronpt`や`prompt | model`のように小さいチェーンをいつでもテストすることができます:

```py
input = {"topic": "ice cream"}

prompt.invoke(input)
# > ChatPromptValue(messages=[HumanMessage(content='tell me a short joke about ice cream')])

(prompt | model).invoke(input)
# > AIMessage(content="Why did the ice cream go to therapy?\nBecause it had too many toppings and couldn't cone-trol itself!")
```

# RAG検索の例

次の例として、質問に反応する際にいくつかのコンテキストを追加するために、retrieval-augmented generationのチェーンを実行したいものとします。

```py
# Requires:
# pip install langchain docarray tiktoken

from langchain_community.vectorstores import DocArrayInMemorySearch
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableParallel, RunnablePassthrough
from langchain_openai.chat_models import ChatOpenAI
from langchain_openai.embeddings import OpenAIEmbeddings

vectorstore = DocArrayInMemorySearch.from_texts(
    ["harrison worked at kensho", "bears like to eat honey"],
    embedding=OpenAIEmbeddings(),
)
retriever = vectorstore.as_retriever()

template = """Answer the question based only on the following context:
{context}

Question: {question}
"""
prompt = ChatPromptTemplate.from_template(template)
model = ChatOpenAI()
output_parser = StrOutputParser()

setup_and_retrieval = RunnableParallel(
    {"context": retriever, "question": RunnablePassthrough()}
)
chain = setup_and_retrieval | prompt | model | output_parser

chain.invoke("where did harrison work?")
```

この場合、コンポーネントのチェーンは以下のようになります:

```py
chain = setup_and_retrieval | prompt | model | output_parser
```

これを説明すると、最初に上のプロンプトテンプレートが、プロンプトで置き換えられる値として`context`と`question`を受け取ることがわかります。プロンプトテンプレートを構築する前に、検索に適した文書を取得し、コンテキストの一部としてそれらを含める必要があります。

準備段階として、クエリーに基づいて文書を取得できるメモリストアを用いたリトリーバーをセットアップしています。これは、実行可能なコンポーネントであり、他のコンポーネントとチェーンすることができますが、これらを個別に実行することもできます:

```py
retriever.invoke("where did harrison work?")
```

そして、文書検索のリトリーバとユーザーの質問を引き渡すためのRunnablePassthroughを用いて取得した文書のエントリーとオリジナルのユーザーの質問を用いて、期待される入力をプロンプトにまとめるために`RunnableParallel`を使用します:

```py
setup_and_retrieval = RunnableParallel(
    {"context": retriever, "question": RunnablePassthrough()}
)
```

完全なチェーンを確認します:

```py
setup_and_retrieval = RunnableParallel(
    {"context": retriever, "question": RunnablePassthrough()}
)
chain = setup_and_retrieval | prompt | model | output_parser
```

フローは以下の通りとなります:

1. 最初のステップでは2つのエントリーを持つ`RunnableParallel`オブジェクトを作成します。最初のエントリー`context`には、リトリーバーによって取得された結果文書が含まれます。2つ目のエントリー`question`には、ユーザーのオリジナルの質問が含まれます。質問を引き渡すために、このエントリーをコピーするために`RunnablePassthrough`を使っています。
1. 上のステップのディクショナリーを`prompt`コンポーネントに入力します。そして、プロンプトを構成し、PromptValueを出力するために、ユーザー入力である`question`と取得した文書である`context`を受け取ります。
1. `model`コンポーネントは生成されたプロンプトを受け取り、評価のためにOpenAIのLLMモデルに入力します。モデルから生成された出力は`ChatMessage`オブジェクトとなります。
1. 最後に、`output_parser`コンポーネントが`ChatMessage`を受け取り、呼び出しメソッドの戻り値となるPython文字列に変換します。

![Screenshot 2024-03-19 at 15.12.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/2a35b81f-14bf-1d4f-0d40-f7158f47642f.png)

# 次のステップ

LCELがある場合とない場合とで、一般的な機能を作成するために必要アコードの隣り合わせの比較をするために、次のセクション[Why use LCEL](https://python.langchain.com/docs/expression_language/why)を読むことをお勧めします。