【０から目指すAIエンジニア】Day8：LCELの基礎

# はじめに
こんにちは、慶應義塾大学理工学部機械工学科3年の金賢佑です！
私は東大発AIベンチャーの株式会社2WINSの元でインターンをしています！

これからの記事では自分がインターンを通して最強のAIエンジニアを目指していきます！
第八回はLCELの基礎を記録しました！

# LCELって？
LCELは、LangChain Expression Languageの略！

前回のday6ではテンプレートを作って、それを実際にLLMに送ったりしましたよね！\
その他出力をPythonオブジェクトにしたりとか。\
それらを連鎖的に行うのがLCELです！

前回も使いましたが、以下のコードでテンプレートを作ってみたください！
``` python
from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate(
    [
        ("system", "ユーザーが入力した料理のレシピを教えてください。"),
        ("human", "{dish}"),
    ]
)
```

<!-- 少し不安でしたらこちらの記事がとてもわかりやすかったのでぜひ参考にしてみてください！
https://qiita.com/niwaka_dev/items/6e3d9ff6d797243c77c3\

また、関数とクラスの違いについてはこちらの記事がわかりやすかったです！
https://qiita.com/free-honda/items/d46c36bec696fe3cd082 -->


続いてChatOpenAIを準備します。
``` python
from langchain_openai import ChatOpenAI

model = ChatOpenAI(model_name='gpt-4o', temperature=0)
```

次に`chain`でこれら二つを繋ぎます！
``` python
chain = prompt | model
```
その後実行します！
``` python
ai_message = chain.invoke({"dish": 'ハンバーグ'})
print(ai_message.content)
```

以下のように、ちゃんとレシピが作成されましたね！

``` text
ハンバーグのレシピをご紹介します。これは基本的なハンバーグの作り方ですが、お好みでアレンジしてみてください。

### 材料（4人分）
- 牛ひき肉：250g
- 豚ひき肉：250g
- 玉ねぎ：1個（みじん切り）
- パン粉：1/2カップ
- 牛乳：1/4カップ
- 卵：1個
- 塩：小さじ1/2
- こしょう：少々
- ナツメグ：少々（お好みで）
- サラダ油：大さじ1

### ソース（お好みで）
- ケチャップ：大さじ3
- ウスターソース：大さじ2

### 作り方
1. **玉ねぎを炒める**  
   フライパンにサラダ油を熱し、みじん切りにした玉ねぎを透明になるまで炒め、冷ましておきます。

2. **パン粉を湿らせる**  
   ボウルにパン粉を入れ、牛乳を加えてしばらく置き、パン粉を湿らせます。

3. **材料を混ぜる**  
   大きなボウルに牛ひき肉、豚ひき肉、冷ました玉ねぎ、湿らせたパン粉、卵、塩、こしょう、ナツメグを入れ、よく混ぜ合わせます。

4. **成形する**  
   手に少量のサラダ油をつけ、肉だねを4等分し、空気を抜くようにして楕円形に成形します。

5. **焼く**  
   フライパンにサラダ油を熱し、中火でハンバーグを焼きます。片面に焼き色がついたら裏返し、蓋をして弱火で中まで火が通るまで焼きます（約10分）。

6. **ソースを作る（お好みで）**  
   ハンバーグを取り出した後のフライパンにケチャップとウスターソースを入れ、軽く煮詰めてソースを作ります。

7. **盛り付ける**  
   焼き上がったハンバーグを皿に盛り付け、ソースをかけて完成です。

お好みで、付け合わせにサラダや温野菜を添えてお楽しみください。
```

# LCELの裏側
ではLCELは裏で何を行なっているのでしょうか？\
`|`って実際にはなんなのでしょうか？

先に結論から言うと、`|`は「演算子オーバーロード」と言うものです。\
私たちが普段使っている`+`や`-`も「演算子オーバーロード」なのです。

例えば、私たちが
``` python
3 + 5         
"Hello" + "!" 
```
と入力したとき、裏では`__add__()`メソッドが呼び出されているのです。
``` python
3.__add__(5)
"Hello".__add__("!")
```
その結果、int同士が足されたり、文字列同士が合わさったりしているのです。\
また、`__add__()`は`+`が使われたときに呼び出される関数なので、もし自分のクラス内で定義すれば、
``` python
class Dog:
    def __init__(self, name):
        self.name = name

    def __add__(self, other):
        return f"{self.name}と{other.name}が仲良しになった！"

a = Dog("ポチ")
b = Dog("ハチ")

print(a + b)  # → ポチとハチが仲良しになった！
```
なんて使い方もできます！

では、演算子`|`を使うとどんなメソッドが呼び出されているのでしょうか。\
正解は、`__or__()`メソッドが呼び出されています。\
つまり、`prompt | model`は`prompt.__or__(model)`を表しています。

実際に`__or__()`メソッドの中身は
``` python
    def __or__(
        self,
        other: Union[
            Runnable[Any, Other],
            Callable[[Any], Other],
            Callable[[Iterator[Any]], Iterator[Other]],
            Mapping[str, Union[Runnable[Any, Other], Callable[[Any], Other], Any]],
        ],
    ) -> RunnableSerializable[Input, Other]:
        """Compose this Runnable with another object to create a RunnableSequence."""
        return RunnableSequence(self, coerce_to_runnable(other))
```
読み解くのが大変なので要点だけ説明すると、\
`prompt.__or__(model)`が呼び出されるので、`self = prompt`, `other = model`になります。\
`Union`からは`other`に渡せるものの種類を表記しています。\
`->`からは返り値の型が入っています。



# まとめ

今回は私たち人間が読みやすいように`.text`を用いましたが、次はさらに`PydanticOutputParser`を使えば後でリストから取り出すこともできるようになりますね！

お疲れ様でした！


# 参考文献
LangChainとLangGraphによるRAG・AIエージェント〈実践〉入門/西見公宏/吉田真吾/大嶋勇樹