【AIエージェント入門】LangChain・LangGraphによるエージェント開発　その②LangChain基礎知識

どうも。@TM_AIbuchoことおっさんです。
SES企業の社長が開発経験ゼロからAIを学習しています。
是非とも暖かく、時には厳しく見守っていただけると嬉しいです。

# はじめに

AIエージェントの定義は明確ではないですが、一般的には**特定の目的に応じて自律的に目標を設定し、タスクを実行していくシステム**をAIエージェントといわれています。

LangChainによるAIエージェントを開発してみよう。
開発環境はGoogleColab、言語はPythonを使用しています。

以下書籍を参考にしています。
[LangChainとLangGraphによるRAG・AIエージェント［実践］入門 (エンジニア選書) 単行本（ソフトカバー） – 2024/11/9 西見 公宏 (著), 吉田 真吾 (著), 大嶋 勇樹 (著)](https://amzn.asia/d/2UfTzY9)

# 過去の記事
[【AIエージェント入門】LangChain・LangGraphによるエージェント開発　その①複数のモデルを使ってみる](https://qiita.com/TM_AIbucho/items/33a7b20da151a0b0f016)

# それでは実装していく！

```Python:入力
import os
from google.colab import userdata

os.environ["OPENAI_API_KEY"] = userdata.get("OPENAI_API_KEY")
```

まずはシークレットのAPIキーを読み込みます。

# PromptTemplate

LangChainのPromptTemplateを使ってみる。
いずれも、プロンプトを作成する機能のためLLMの呼び出しはしません。

- PromptTemplate
  - 文字列テンプレートに変数を埋め込んでプロンプトを作成する機能
- ChatpromptTemplate
  - チャット用のSystemMessage、HumanMessage、AIMessageをそれぞれテンプレート化してクラスとして扱う
- MessagePlaceholder
  - チャットのメッセージに会話履歴を含める
- LangSmith / Prompts
  - LangSmith上でコードとは別にプロンプトを管理できる機能

LangSmithのPromptsはワークフロー化していくときに重宝しそうですね！

``` Python:入力
#promptTemplateを使ってみる
from langchain_core.prompts import PromptTemplate

prompt = PromptTemplate.from_template("""こんにちは {name}さん""")

prompt_value = prompt.invoke({"name":"坂本"})
print(prompt_value)
```

``` Python:結果
text='こんにちは 坂本さん'
```

変数に入力データを代入してプロンプトを作成できます。

``` Python:入力
#ChatpromptTemplateを使ってみる
from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_messages(
    [
        ("system","ユーザーの入力に回答してください"),
        ("human","{text}")
    ]
)

prompt_value = prompt.invoke({"text":"亀山社中を設立した人物は？"})
print(prompt_value)
```

``` Python:結果
messages=[SystemMessage(content='ユーザーの入力に回答してください', additional_kwargs={}, response_metadata={}), HumanMessage(content='亀山社中を設立した人物は？', additional_kwargs={}, response_metadata={})]
```

SystemMessageとHumanMessageを分けてプロンプトが作成できます。

``` Python:入力
#MessagesPlaceholderを使ってみる
from langchain_core.messages import AIMessage, HumanMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

prompt = ChatPromptTemplate.from_messages(
    [
        ("system","あなたは優秀なアシスタントです。"),
        MessagesPlaceholder("chat_history",Optional=True),
        ("human","{text}")
    ]
)

prompt_value = prompt.invoke(
    {
        "chat_history":[
            HumanMessage(content="おう、わしが坂本龍馬じゃ！土佐の漁師の息子として生まれ、今じゃ日本の夜明けを目指して奔走しちょる。攘夷から開国へ、新しい時代を切り開くため、薩長同盟の橋渡しもやったぞよ。海援隊も率いちょるが、何よりも日本をええ国にしたいがじゃ！"),
            AIMessage("こんにちは！坂本龍馬さん。お忙しそうですね！"),
            ],
        "text":"わたしはだれ？"
    }
)

print(prompt_value)
```

``` Python:結果
messages=[SystemMessage(content='あなたは優秀なアシスタントです。', additional_kwargs={}, response_metadata={}), HumanMessage(content='おう、わしが坂本龍馬じゃ！土佐の漁師の息子として生まれ、今じゃ日本の夜明けを目指して奔走しちょる。攘夷から開国へ、新しい時代を切り開くため、薩長同盟の橋渡しもやったぞよ。海援隊も率いちょるが、何よりも日本をええ国にしたいがじゃ！', additional_kwargs={}, response_metadata={}), AIMessage(content='こんにちは！坂本龍馬さん。お忙しそうですね！', additional_kwargs={}, response_metadata={}), HumanMessage(content='わたしはだれ？', additional_kwargs={}, response_metadata={})]
```

会話履歴をもとに回答を生成するためのプロンプトを作成。
せっかくなので、gptにプロンプトを入力して結果を見てみます。
prompt_value（上で作成したプロンプト）でのgpt-4o-miniからの回答をみてみます。

``` Python:入力
from langchain_openai import ChatOpenAI
from langchain_core.callbacks import StreamingStdOutCallbackHandler

model = ChatOpenAI(
   model="gpt-4o-mini",
   temperature=0,
   streaming=True,
   callbacks=[StreamingStdOutCallbackHandler()]
)

response = model.invoke(prompt_value)
```

``` Python:結果
あなたは坂本龍馬です。土佐の漁師の息子として生まれ、日本の夜明けを目指して攘夷から開国へと進むために奔走している歴史的な人物です。薩長同盟の橋渡しをし、海援隊を率いて新しい時代を切り開こうとしています。日本をより良い国にするために尽力している姿勢が素晴らしいですね！
```

無事坂本龍馬になれちょる。

# Output Parser

LLMの出力をプログラムで使いやすいようにする機能。
テキストに変換したりJSON形式にしたり。わたしみたいな初心者はJSON？なにそれおいしいの？ってなります。

>JSONは「JavaScript Object Notation」の略で、データを整理して保存
・やり取りするための形式です。
>
>例えば、こんな感じです：

```json
{
    "名前": "田中太郎",
    "年齢": 25,
    "趣味": ["読書", "サッカー", "料理"],
    "住所": {
        "都道府県": "東京都",
        "市区町村": "渋谷区"
    }
}
```

>特徴：
>- 人間が読みやすい
>- コンピュータも扱いやすい
>- {}で囲んで、「"項目名": 値」という形で書く
>- 文字列、数字、配列[]、オブジェクト{}などを表現できる
>
>主にWebサービスやアプリのデータ保存・通信に使われています。 *斜体テキスト*

だそうです。

``` Python:入力
# StrOutputParser　文字列を抽出
from langchain_core.messages import AIMessage
from langchain_core.output_parsers import StrOutputParser

output_parser = StrOutputParser()

ai_message = AIMessage(content="こんにちは。わたしはとてもチャーミングなAIアシスタントです。")
output = output_parser.invoke(ai_message)
print(type(output))
print(output)
```

``` Python:結果
<class 'str'>
こんにちは。わたしはとてもチャーミングなAIアシスタントです。
```

JSONではなく文字列、Strとして抽出しています。
StrOutputParserはこのあと重要になるからね！とのことなのでちゃんと覚えましょう。

# Chain - LangChaiin Expression Language(LCEL)

さて、いよいよ本格的にLangChainの本領を発揮していくようです。
LangChainというだけあって、PromptTemplateやChat model、OutputParserなどの機能を使いながら連鎖的に処理をつなげて（チェーン）いきます。

そこで使われるのがLCELというお作法。
2023年10月から標準化されたとのことで、その歴史は1年ちょっと。
あたらしい！

まずは手始めに、坂本龍馬の自己紹介をChainするがじゃ。

``` Python:入力
from langchain_openai import ChatOpenAI
from langchain_core.messages import AIMessage, HumanMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.output_parsers import StrOutputParser

model = ChatOpenAI(
   model="gpt-4o-mini",
   temperature=0,
   streaming=True,
   callbacks=[StreamingStdOutCallbackHandler()]
)

prompt = ChatPromptTemplate.from_messages(
    [
        ("system","あなたは優秀なアシスタントです。"),
        MessagesPlaceholder("chat_history",optional=True),
        ("human","{text}")
    ]
)

#chainでPrompt作成→LLMで回答生成→文字列に変換
chain = prompt | model | StrOutputParser()
response = chain.invoke({
    "chat_history":[
        HumanMessage(content="おう、わしが坂本龍馬じゃ！土佐の漁師の息子として生まれ、今じゃ日本の夜明けを目指して奔走しちょる。攘夷から開国へ、新しい時代を切り開くため、薩長同盟の橋渡しもやったぞよ。海援隊も率いちょるが、何よりも日本をええ国にしたいがじゃ！"),
        AIMessage("こんにちは！坂本龍馬さん。お忙しそうですね！"),
        ],
       "text":"わたしはだれ？"
    })

print(response)
```

``` Python:結果
あなたは坂本龍馬です。土佐の漁師の息子として生まれ、日本の夜明けを目指して奔走している志士です。攘夷から開国へと時代を切り開くために活動し、薩長同盟の橋渡しを行い、海援隊を率いています。日本をより良い国にするために尽力している偉大な人物ですね！あなたは坂本龍馬です。土佐の漁師の息子として生まれ、日本の夜明けを目指して奔走している志士です。攘夷から開国へと時代を切り開くために活動し、薩長同盟の橋渡しを行い、海援隊を率いています。日本をより良い国にするために尽力している偉大な人物ですね！
```

プロンプト作成、LLMからの回答を取得、文字列にして出力ができました。
JSONで出力して別処理に繋げるとかもできますね。

# まとめ

今回はLangChainの主要な機能として以下の3つを実装してみました：

1. PromptTemplate
- 変数を埋め込んでプロンプトを作成できるPromptTemplate
- チャット用のメッセージをテンプレート化するChatPromptTemplate
- 会話履歴を含められるMessagePlaceholder

2. Output Parser
- LLMの出力を扱いやすい形式に変換
- StrOutputParserを使って文字列として抽出する方法を確認

3. Chain - LangChain Expression Language (LCEL)
- PromptTemplate、Chat model、OutputParserなどの機能を連鎖的に処理
- パイプライン（|）を使って複数の処理を繋げることができる

次回はRAGの実装に取り組んでいきます！！