見出し画像

「LangChain完全入門」を寄り道写経で完全入門④AgentsとCallbacks

書籍の著者 田村悠 先生


この記事は、書籍「LangChain完全入門」の第6章「Agents - 自律的に外部と干渉して言語モデルの限界を超える」と第7章「Callbacks - さまざまなイベント発生時に処理を行う」の通称「寄り道写経」を取り扱います。
寄り道の狙いは「最近のライブラリで動かすこと」です。
したがいまして、記事に掲載する内容は「コード」に焦点を絞ります。

Agents は、ユーザーの要求を解釈して、タスクを分解して、手持ちの道具を用いて納得のいく回答が得られるまで「情報取得~検討」を繰り返す、まさに頼りになるエージェントなのです!
Callbacks は、ログ出力などのイベント発生に伴う処理を取り扱う機能です。

そしていよいよ、チャットbot が成長のピークを迎えます!
Agents と Callbacks でパワーアップしたチャットbot をあなたの手で動かして、もろもろご堪能あれ!!!
さあ書籍を開いて言語モデル活用の旅に出発です🚀

電脳空間に飛び込んだ人のイラスト(女性):「いらすとや」さんより

はじめに


書籍「LangChain完全入門」のご紹介

書籍「LangChain完全入門 生成AIアプリケーション開発がはかどる大規模言語モデルの操り方」(以下、テキストと呼びます)は、2023年10月に発売された LangChain の入門書です。

LangChain は大規模言語モデル(LLM)を活用するアプリケーション開発で一般的に用いられる機能を提供する優れたライブラリであり、テキストによると「執筆時点でデファクトスタンダードとなっています」とのことです。
LLM と API でつながるアプリがサクッと出来てしまう魔法のライブラリです。
LangChain公式サイトはこちら。

テキストを実践して「10数行のコードでLLMを使った簡易なチャットボットを作れる!」という衝撃に何度も出会いました。
そして、自分で書いた(テキストから写経した)チャットGPTのようなChatボットの存在がとても愛おしいのです!

テキストは、LangChain の 6つのモジュールの基礎的な利用方法をコードとコード実行結果を用いて「とてもわかりやすく」解説する優れた書籍です。
プログラミング初学者の私にとって、手入力でいいかげん丹念に写経したコードの実行結果を「テキスト掲載の正解」(テキストのコード実行結果)と照らし合わせられることは、「写経コードが正しく動いたんだ!」という安心感と達成感を確保できる「御守り」です。
安心感が「 LangChain 最高~!楽しい~!」に直結するのです。

ただし最近のPythonライブラリを使うとテキストのコードが動かない場合があります。
そこで寄り道写経。
最近のライブラリで動かせるよう、コードに手を加えます!

バンザイをしている会社員たちのイラスト:「いらすとや」さんより

引用表記

この記事は、出典に記載の書籍に掲載された文章及びコードを引用し、適宜、掲載文章とコードを改変して書いています。
【出典】
「LangChain完全入門 生成AIアプリケーション開発がはかどる大規模言語モデルの操り方」初版、著者 田村悠、インプレス

記事中のイラストは、「かわいいフリー素材集いらすとや」さんのイラストをお借りしています。
ありがとうございます!


おしながき


記事の書き方

  • テキストの「.pyファイル」ごとに書きます。

  • テキストの掲載順に書きます。

  • インポート文のみの変更で動く場合には、インポート文の箇所のみ書きます。

  • インポート文以外の変更が必要な場合には、コード全文を書きます。
    変更箇所は ★印 をつけるなどして明確にします。

  • テキストのコード行番号とは一致しません。

  • 実行結果は書きません。

コードの変更方針

  • 2024年5月6日から10日までの間に変更後コードを実行して動くことを確認しました。

  • ベストな変更方法では無い可能性があります(可能性が高いです)。

  • テキストのオリジナルコードを実行することで発生するエラーの回避策を試行錯誤して考えます。

  • なるべくテキストのコードを尊重して変更します。
    新しいライブラリが期待する使用方法では無い可能性があります。

  • なるべくワーニング発生を回避します。
    ただし、私の技量でワーニング回避できない部分は文章でお知らせします。
    【緩募】ワーニング対策が分かる方、ぜひ対策を教えて下さい。

  • ところどころにコメント文を入れていますが、私的なメモです。
    気にしないでください。

Pythonと主なライブラリの使用バージョン

$$
\begin{array}{l:l}
ライブラリ等 & バージョン \\
\hline
\text{python} & 3.11.9 \\
\text{langchain} & 0.1.17 \\
\text{langchain-core} & 0.1.50 \\
\text{langchain-community} & 0.0.36 \\
\text{langchain-openai} & 0.1.6 \\
\text{langchain-text-splitters} & 0.0.1 \\
\text{langchainhub} & 0.1.15 \\
\text{openai} & 1.25.2 \\
\text{pymupdf} & 1.24.2 \\
\text{spacy} & 3.7.4 \\
\text{tiktoken} & 0.6.0 \\
\text{chromadb} & 0.5.0 \\
\text{chainlit} & 1.0.506 \\
\text{wikipedia} & 1.4.0 \\
\text{redis} & 5.0.4 \\
\text{bs4} & 0.0.2 \\
\text{google-search-results} & 2.4.2 \\
\end{array}
$$

免責事項

私の使用環境で動いたコードをそのまま掲載しており、別の環境で動かない可能性があります。
記事のコードにより生じる直接的または間接的な損害について、私では一切の責任を負いかねます。予めご了承ください。

第6章 Agents 関連


ライブラリのバージョンアップで Agents の機能が大きく変わっていますが、ぜひぜひコードを動かして Agents と仲良しになってくださいね。
言語モデル活用の発展が待っています!

agent_1.py

### 与えられたURLから情報を取得 pythonのrequestsを使用
# ワーニングが2つ出る。
# ・agentsのインポートでGuardrailsOutputParser非推奨が表示
# ・load_toolsでrequestsを指定すると非推奨が表示

# インポート
from langchain_openai import ChatOpenAI
# ★Agent機能変更によるagentsインポートライブラリの変更
from langchain.agents import AgentExecutor, create_react_agent, load_tools 
from langchain import hub

# chatの準備
chat=ChatOpenAI(
    temperature=0,
    model='gpt-3.5-turbo',
)

# toolの準備 
tools = load_tools(
    tool_names=['requests'],            # ★推奨のrequests_allがうまく動作しない
    allow_dangerous_tools=True                 # ★危険対応
)

# create_react_agentのデフォルトプロンプト
prompt = hub.pull('hwchase17/react')           # ★Agent機能変更

# ReActエージェントの準備
agent = create_react_agent(                    # ★Agent機能変更
    tools=tools,
    llm=chat,
    prompt=prompt,
)

# agent実行本体の作成                           # ★Agent機能変更
agent_executer = AgentExecutor(agent=agent, tools=tools, verbose=True)

# エージェントの実行 
input = ('以下のURLにアクセスして東京の天気を調べて日本語で答えてください。\n'
         'https://www.jma.go.jp/bosai/forecast/data/overview_forecast/130000.json')
# ★Agent機能変更
result = agent_executer.invoke({'input': input}, handle_parsing_errors=True)

# 回答の表示
print(f"実行結果: {result['output']}")          # ★output

agent_2.py

### 与えられたURLから情報を取得してファイル出力 pythonのrequestsを使用
# ワーニングが2つ出る。
# ・agentsのインポートでGuardrailsOutputParser非推奨が表示
# ・load_toolsでrequestsを指定すると非推奨が表示

# インポート
from langchain_community.tools.file_management import WriteFileTool # ★_community
from langchain_openai import ChatOpenAI                             # ★_openai
# ★Agent機能変更によるagentsライブラリの変更
from langchain.agents import (
    AgentExecutor, create_structured_chat_agent, load_tools
)
from langchain import hub

# chatの準備
chat=ChatOpenAI(
    temperature=0,
    model='gpt-3.5-turbo',
)

# toolの準備 
tools = load_tools(
    tool_names=[
        'requests_get',
        'serpapi',
    ],
    llm=chat,
    allow_dangerous_tools=True                           # ★危険対応
)
tools.append(WriteFileTool(root_dir='./'))

# create_structured_chat_agentのデフォルトプロンプト
prompt = hub.pull('hwchase17/structured-chat-agent')     # ★Agent機能変更

# structured_chatエージェントの準備
agent = create_structured_chat_agent(                  # ★Agent機能変更
    tools=tools,
    llm=chat,
    prompt=prompt,
)

# agent実行本体の作成                                   # ★Agent機能変更
agent_executer = AgentExecutor(
    agent=agent,
    tools=tools,
    verbose=True,
    handle_parsing_errors=True,                        # ★エラー回避
)

# エージェントの実行 
input = ('北海道の名産品を調べて日本語の文章でresult.txtという'
         'ファイルに保存してください。')
result = agent_executer.invoke({'input': input})       # ★Agent機能変更

# 実行結果の表示
print(f'実行結果: {result}')

agent_3.py

### 独自toolを実行してファイル出力
# ワーニングが出る。
# ・agentsのインポートでGuardrailsOutputParser非推奨が表示

# インポート
import random
from langchain_community.tools.file_management import WriteFileTool # ★_community
from langchain_openai import ChatOpenAI                             # ★_openai
# ★Agent機能変更によるagentsライブラリの変更
from langchain.agents import (
    AgentExecutor, create_structured_chat_agent, Tool
)
from langchain import hub

# chatの準備
chat=ChatOpenAI(
    temperature=0,
    model='gpt-3.5-turbo',
)

## toolの準備 
tools = []
# ファイル保存を追加
tools.append(WriteFileTool(root_dir='./'))
# 乱数生成を追加
def min_limit_random_number(min_number):  # 乱数生成関数
    return random.randint(a=int(min_number), b=100000)
tools.append(
    Tool(
        name='Random',
        description='特定の最小値以上のランダムな数字を生成することができます。',
        func=min_limit_random_number,
    )
)

# create_structured_chat_agentのデフォルトプロンプト
prompt = hub.pull('hwchase17/structured-chat-agent')   # ★Agent機能変更

# structured_chatエージェントの準備
agent = create_structured_chat_agent(                  # ★Agent機能変更
    tools=tools,
    llm=chat,
    prompt=prompt,
)

# agent実行本体の作成                                   # ★Agent機能変更
agent_executer = AgentExecutor(
    agent=agent,
    tools=tools,
    verbose=True,
    handle_parsing_errors=True,                        # ★エラー回避
)

# エージェントの実行 
input = ('10以上のランダムな数字を生成してrandom.txtというファイルに'
         '保存してください。')
result = agent_executer.invoke({'input': input})       # ★Agent機能変更

# 実行結果の表示
print(f'result:\n{result}') # 検証用
print(f"実行結果: {result['output']}")                  # ★output

agent_4.py

### wikipediaで調べてファイル出力
# ワーニングが出る。
# ・agentsのインポートでGuardrailsOutputParser非推奨が表示

# インポート
from langchain.tools.retriever import create_retriever_tool     # ★tools
from langchain_openai import ChatOpenAI                         # ★_openai
from langchain_community.retrievers import WikipediaRetriever   # ★.community
from langchain_community.tools import WriteFileTool             # ★_community
# ★Agent機能変更によるagentsライブラリの変更
from langchain.agents import (
    AgentExecutor, create_structured_chat_agent
)
from langchain import hub

# chatの準備
chat=ChatOpenAI(
    temperature=0,
    model='gpt-3.5-turbo',
)

## toolの準備 
tools = []
# ファイル保存を追加
tools.append(WriteFileTool(root_dir='./'))
# wikipediaリトリバーを追加
retriever = WikipediaRetriever(
    lang='ja',
    doc_content_chars_max=500,
    top_k_results=1,
)
tools.append(
    create_retriever_tool(
        name='WikipediaRetriever',
        description='受け取った単語に関するwikipediaの記事を取得できる。',
        retriever=retriever,
    )
)

# create_structured_chat_agentのデフォルトプロンプト
prompt = hub.pull('hwchase17/structured-chat-agent')   # ★Agent機能変更

# structured_chatエージェントの準備
agent = create_structured_chat_agent(                  # ★Agent機能変更
    tools=tools,
    llm=chat,
    prompt=prompt,
)

# agent実行本体の作成                                   # ★Agent機能変更
agent_executer = AgentExecutor(
    agent=agent,
    tools=tools,
    verbose=True,
    handle_parsing_errors=True,                        # ★エラー回避
)

# エージェントの実行 
input = ('スコッチウイスキーについてwikipediaで調べて概要を日本語で'
         'result_whisky.txtというファイルに保存してください。')
result = agent_executer.invoke({'input': input})       # ★Agent機能変更

# 実行結果の表示
print(f'result:\n{result}')                            # ★検証用
print(f"実行結果: {result['output']}")                 # ★output

agent_5.py

### 会話履歴を保存し、wikipediaで調べてファイル出力を2回繰り返す
# structured_chat_agentを使用することで、ファイル出力に対応できた
# ワーニングが出る。
# ・agentsのインポートでGuardrailsOutputParser非推奨が表示

# インポート
from langchain.tools.retriever import create_retriever_tool     # ★tools
from langchain_openai import ChatOpenAI                         # ★_openai
from langchain.memory import ConversationBufferMemory
from langchain_community.retrievers import WikipediaRetriever   # ★.community
from langchain_community.tools import WriteFileTool             # ★_community
# ★Agent機能変更によるagentsライブラリの変更
from langchain.agents import (
    AgentExecutor, create_structured_chat_agent
)
from langchain import hub

# chatの準備
chat=ChatOpenAI(
    temperature=0,
    model='gpt-3.5-turbo',
)

## toolの準備 
tools = []
# ファイル保存を追加
tools.append(WriteFileTool(root_dir='./'))
# wikipedia取得を追加
retriever = WikipediaRetriever(
    lang='ja',
    doc_content_chars_max=500,
    top_k_results=1,
)
tools.append(
    create_retriever_tool(
        name='WikipediaRetriever',
        description='受け取った単語に関するwikipediaの記事を取得できる。',
        retriever=retriever,
    )
)

# memoryの準備
memory = ConversationBufferMemory(
    memory_key='chat_history',
    return_messages=True,
)

# create_structured_chat_agentのデフォルトプロンプト
prompt = hub.pull('hwchase17/structured-chat-agent')   # ★Agent機能変更

# structured_chatエージェントの準備
agent = create_structured_chat_agent(                  # ★Agent機能変更
    tools=tools,
    llm=chat,
    prompt=prompt,
)


# agent実行本体の作成                                   # ★Agent機能変更
agent_executer = AgentExecutor(
    agent=agent,
    tools=tools,
    # max_iterations=5,  # デフォルト15
    memory=memory,
    verbose=True,
    handle_parsing_errors=True,                         # ★エラー回避
)

# エージェントの実行 1回目
input = ('スコッチウイスキーについてwikipediaで調べて、日本語で概要をまとめて'
         'result_whisky_5.txtというファイルに保存してください。')
result = agent_executer.invoke({'input': input})       # ★Agent機能変更
print(f'result:\n{result}')                            # ★検証用
print(f"1回目の実行結果: {result['output']}")           # ★output

# エージェントの実行 2回目
input2 = ('以前の指示をもう一度実行してください。')
result2 = agent_executer.invoke({'input': input2})     # ★Agent機能変更
print(f'result2:\n{result}')                           # ★検証用
print(f"2回目の実行結果: {result2['output']}")          # ★output

第7章 Callbacks 関連


チャットbot の成長を目の当たりにして、感動あふれる章です。

chainlit_callback.py

### chainlitに言語モデルの処理過程を表示する

# インポート
import chainlit as cl
from langchain.agents import (      # ★Agent機能変更によるagentsライブラリの変更
    AgentExecutor, create_react_agent, load_tools
)
from langchain_openai import ChatOpenAI                # ★_openai
from langchain import hub                              # ★Agent機能変更

## 設定と準備
# chatの準備
chat = ChatOpenAI(temperature=0, model='gpt-3.5-turbo')

# toolsの準備
tools = load_tools(tool_names=['serpapi'])

# create_react_agentのデフォルトプロンプト
prompt = hub.pull('hwchase17/react')                   # ★Agent機能変更

# agentの準備
agent = create_react_agent(                            # ★Agent機能変更
    tools=tools,
    llm=chat,
    prompt=prompt,
)

# agent実行本体の作成                                   # ★Agent機能変更
agent_executer = AgentExecutor(agent=agent, tools=tools, verbose=True)


## チャット画面の処理
# チャット画面起動時の処理
@cl.on_chat_start
async def on_chat_start():
    await cl.Message(content='Agentの初期化が完了しました。').send()

# メッセージ受取時の処理
# chainlit公式のLangchainCallbackHandler情報
#    https://docs.chainlit.io/api-reference/integrations/langchain
@cl.on_message
async def on_message(input_message):
    result = await agent_executer.acall(               # ★Agent機能変更, acall
        {'input': input_message.content},              # ★.content
        callbacks=[
            cl.LangchainCallbackHandler()
        ],
    )
    await cl.Message(content=result['output']).send()  # ★ output

このコードだけは出力結果の掲示をお許しください!
コードを動かして、次のような質問を投げかけてみました。
「Took 7 steps」をクリックして、隠された Agents の秘密をぜひぜひ暴いてみてください!

chainlit_callback.pyを動かして実践した会話例

log_callback.py

### callbacksを利用してログをターミナルに表示する

# インポート
from langchain.callbacks.base import BaseCallbackHandler
from langchain_openai import ChatOpenAI                # ★_openai
from langchain_core.messages import HumanMessage       # ★_core

# logをターミナルに表示するcallbacksクラスの定義
class LogCallbackHandler(BaseCallbackHandler):
    # ChatModelの実行開始時に実行する関数
    def on_chat_model_start(self, serialized, messages, **kwargs):
        print('ChatModelの実行を開始します...')
        print(f'入力: {messages}')
    # Chainsの開始時に実行する関数
    def on_chain_start(self, serialized, inputs, **kwargs):
        print('Chainの実行を開始します...')
        print(f'入力: {inputs}')

# chatの準備
chat = ChatOpenAI(
    model='gpt-3.5-turbo',
    callbacks=[
        LogCallbackHandler()
    ]
)

# chatの実行、結果表示
message = [HumanMessage(content='こんにちは!')]        # ★追加
result = chat.invoke(message)                          # ★invoke
print(result.content)

第6章、第7章の寄り道写経は以上です。

第7章が書籍の最終章です。
この寄り道写経シリーズもこの記事で最終回を迎えます。
ご清聴ありがとうございました!

「感謝」のイラスト文字:「いらすとや」さんより

ほぼほぼ完全に入門できたことに感謝いたします。
私は LangChain complete beginner になれたのでしょうか?
チャットbot に訊いてみようかな。

おわり


シリーズの記事

前の記事

目次

ブログの紹介


note で7つのシリーズ記事を書いています。
ぜひ覗いていってくださいね!

1.のんびり統計

統計検定2級の問題集を手がかりにして、確率・統計をざっくり掘り下げるブログです。
雑談感覚で大丈夫です。ぜひ覗いていってくださいね。
統計検定2級公式問題集CBT対応版に対応しています。
Python、EXCELのサンプルコードの配布もあります。

2.実験!たのしいベイズモデリング1&2をPyMC Ver.5で

書籍「たのしいベイズモデリング」・「たのしいベイズモデリング2」の心理学研究に用いられたベイズモデルを PyMC Ver.5で描いて分析します。
この書籍をはじめ、多くのベイズモデルは R言語+Stanで書かれています。
PyMCの可能性を探り出し、手軽にベイズモデリングを実践できるように努めます。
身近なテーマ、イメージしやすいテーマですので、ぜひぜひPyMCで動かして、一緒に楽しみましょう!

3.実験!岩波データサイエンス1のベイズモデリングをPyMC Ver.5で

書籍「実験!岩波データサイエンスvol.1」の4人のベイジアンによるベイズモデルを PyMC Ver.5で描いて分析します。
この書籍はベイズプログラミングのイロハをざっくりと学ぶことができる良書です。
楽しくPyMCモデルを動かして、ベイズと仲良しになれた気がします。
みなさんもぜひぜひPyMCで動かして、一緒に遊んで学びましょう!

4.楽しい写経 ベイズ・Python等

ベイズ、Python、その他の「書籍の写経活動」の成果をブログにします。
主にPythonへの翻訳に取り組んでいます。
写経に取り組むお仲間さんのサンプルコードになれば幸いです🍀

5.RとStanではじめる心理学のための時系列分析入門 を PythonとPyMC Ver.5 で

書籍「RとStanではじめる心理学のための時系列分析入門」の時系列分析をPythonとPyMC Ver.5 で実践します。
この書籍には時系列分析のテーマが盛りだくさん!
時系列分析の懐の深さを実感いたしました。
大好きなPythonで楽しく時系列分析を学びます。

6.データサイエンスっぽいことを綴る

統計、データ分析、AI、機械学習、Pythonのコラムを不定期に綴っています。
統計・データサイエンス書籍にまつわる記事が多いです。
「統計」「Python」「数学とPython」「R」のシリーズが生まれています。

7.Python機械学習プログラミング実践記

書籍「Python機械学習プログラミング PyTorch & scikit-learn編」を学んだときのさまざまな思いを記事にしました。
この書籍は、scikit-learnとPyTorchの教科書です。
よかったらぜひ、お試しくださいませ。

最後までお読みいただきまして、ありがとうございました。

この記事が参加している募集

新生活をたのしく

私のコレクション

with 國學院大學

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