LangChainのエージェントチュートリアルをDatabricksで動かしてみる

いまだに[エージェントシステム](https://docs.databricks.com/ja/generative-ai/agent-framework/ai-agents.html)がよくわかっていないので勉強中です。

いろいろ探していたら、LangChainで良さそうなサンプルを見つけました。

https://python.langchain.com/docs/tutorials/agents/

早速動かしてみます。結論から言うとステップバイステップで動作を確認することができて、とても勉強になりました。

# ライブラリのインストール

```py
%pip install -U langchain-community langgraph langchain-anthropic tavily-python langgraph-checkpoint-sqlite langchain-openai
dbutils.library.restartPython()
```

# APIキーの設定

ここでは、ツールとしてTavily(検索エンジン)を使います。

- [Tavily Search \| 🦜️🔗 LangChain](https://python.langchain.com/docs/integrations/tools/tavily_search/)
- [Tavily AI](https://tavily.com/)

```py
import os

# LangSmithの設定
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = "<LangSmithのAPIキー>"

# TavilyのAPIキー
os.environ["TAVILY_API_KEY"] = "<TavilyのAPIキー>"
```

# ツールの定義

はじめに、使用したいツールを作成する必要があります。ここでメインとなるツールは検索エンジンの[Tavily](https://python.langchain.com/docs/integrations/tools/tavily_search/)です。LangChainでは、Tavily検索エンジンを容易にツールとして使えるビルトインのツールがあります。

```py
from langchain_community.tools.tavily_search import TavilySearchResults

search = TavilySearchResults(max_results=2)
search_results = search.invoke("サンフランシスコの天気はどうですか")
display(search_results)
# 必要に応じて、他のツールを作成できます。
# 必要なツールが揃ったら、後で参照するリストに入れます。
tools = [search]
```

このTavily、初めて使いましたがすごいです。日本語でも普通に動きました。無料で1000コール/月使えます。あとでいろいろ試してみます。

```py
from langchain_community.tools.tavily_search import TavilySearchResults

search = TavilySearchResults(max_results=2)
search_results = search.invoke("サンフランシスコの天気はどうですか")
display(search_results)
# 必要に応じて、他のツールを作成できます。
# 必要なツールが揃ったら、後で参照するリストに入れます。
tools = [search]
```
![Screenshot 2024-12-24 at 12.01.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/bd398b5e-0dac-1ba2-09b3-c47d340d2f8f.png)

上でLangSmithの設定をしているので、トレースを確認することができます。

![Screenshot 2024-12-24 at 12.02.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/149eaa66-9a9f-4d33-135e-81ae84d7705e.png)

Databricksノートブック上でもMLflow Traceでトレースを確認することも可能です。

![Screenshot 2024-12-24 at 12.03.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/42cf8f2c-305d-1edd-ecf7-c8d57258d383.png)

# 言語モデルの活用

次に、ツールを呼び出すためにどのように言語モデルを活用するのかを学びます。LangChainではさまざまな言語モデルをサポートしています。

```py
os.environ["OPENAI_API_KEY"] = "<OpenAI APIキー>"

from langchain_openai import ChatOpenAI

model = ChatOpenAI(model="gpt-4o-mini")
```

メッセージのリストを渡すことで言語モデルを呼び出すことができます。デフォルトではレスポンスは`content`文字列となります。

```py
from langchain_core.messages import HumanMessage

response = model.invoke([HumanMessage(content="こんにちは！")])
response.content
```
```
'こんにちは！今日はどのようにお手伝いできますか？'
```

これで、このモデルがツール呼び出しをできるようにするにはどうすれば良いかが見えてきました。これを実現するには、これらのツールの知識を言語モデルに与えるために、`.bind_tools`を活用します。

```py
model_with_tools = model.bind_tools(tools)
```

これでモデルを呼び出すことができます。はじめに通常のメッセージで呼び出し、レスポンスを見てみましょう。`content`フィールドと`tool_calls`フィールドの両方を確認することができます。

```py
response = model_with_tools.invoke([HumanMessage(content="こんにちは！")])

print(f"ContentString: {response.content}")
print(f"ToolCalls: {response.tool_calls}")
```
```
ContentString: こんにちは！今日はどんなことをお手伝いできますか？
ToolCalls: []
```

それでは、ツールが呼び出されるであろう入力を試してみます。

```py
response = model_with_tools.invoke([HumanMessage(content="サンフランシスコの天気はどうですか")])

print(f"ContentString: {response.content}")
print(f"ToolCalls: {response.tool_calls}")
```
```
ContentString: 
ToolCalls: [{'name': 'tavily_search_results_json', 'args': {'query': 'サンフランシスコ 天気'}, 'id': 'call_pwGfXH5xshEJ1omoVjWbsShc', 'type': 'tool_call'}]
```
![Screenshot 2024-12-24 at 12.06.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/d5402f05-b721-fcf2-841d-bd78a9e2837d.png)

ここでは、テキストコンテンツはありませんがツール呼び出しがされていることを確認できます！これは、Tavily検索ツールを呼び出そうとしています。

これはまだ、ツールを呼び出していません。実際に呼び出すにはエージェントを作成する必要があります。

# エージェントの作成

ここまでで、ツールとLLMを定義したので、エージェントを作成することができます。ここでは、エージェントを構成するために[LangGraph](https://python.langchain.com/docs/concepts/architecture/#langgraph)を使用します。現時点では、エージェントを構成するためにハイレベルのインタフェースを使っていますが、LangGraphの素敵なところはこの高レベルのインタフェースの背後には、あなたがエージェントのロジックを修正したい場合に活用できる低レベルで高度に制御可能なAPIが存在しているということです。

それでは、LLMとツールを用いてエージェントを初期化します。ここでは、`model_with_tools`ではなく`model`を渡していることに注意してください。これは、`create_react_agent`が内部で`.bind_tools`を呼び出すためです。

```py
from langgraph.prebuilt import create_react_agent

agent_executor = create_react_agent(model, tools)
```

# エージェントの実行

これで、いくつかのクエリーでエージェントを実行できるようになりました！現時点では、これらは**ステートレス**のクエリーであることに注意してください(以前のやりとりを記憶しません)。このエージェントはやりとりの**最終的な**状態を返却することに注意してください(すべての入力を含みますが、あとで出力のみを取り出す方法を学びます)。

はじめに、ツール呼び出しを必要としない場合にはどういうレスポンスになるのかを見てみましょう。

```py
response = agent_executor.invoke({"messages": [HumanMessage(content="こんにちは！")]})

response["messages"]
```
```
[HumanMessage(content='こんにちは！', additional_kwargs={}, response_metadata={}, id='6913a752-5cdc-46c1-8189-f299825e13ab'),
 AIMessage(content='こんにちは！どういったことをお手伝いできますか？', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 16, 'prompt_tokens': 81, 'total_tokens': 97, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_d02d531b47', 'finish_reason': 'stop', 'logprobs': None}, id='run-c24bbc55-fb37-4e15-aa25-b4602ec14f46-0', usage_metadata={'input_tokens': 81, 'output_tokens': 16, 'total_tokens': 97, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]
```
![Screenshot 2024-12-24 at 12.07.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/9b6ac534-7232-9903-9ba3-9c171100da9e.png)

内部で何が起きているのかを正確に確認するには、LangSmithのトレースを参照することができます。

![Screenshot 2024-12-24 at 12.08.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/c92200e1-faaf-378d-ac65-a733c37b6b8a.png)


それでは、ツールを呼び出すであろうサンプルを試してみましょう。

```py
response = agent_executor.invoke(
    {"messages": [HumanMessage(content="サンフランシスコの天気はどうですか")]}
)
response["messages"]
```
```
[HumanMessage(content='サンフランシスコの天気はどうですか', additional_kwargs={}, response_metadata={}, id='16ff26ba-6649-42b8-88c7-91ddcfba1d73'),
 AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_jbf02Iv8ogXdcnynky9uxcHD', 'function': {'arguments': '{"query":"サンフランシスコ 天気"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 27, 'prompt_tokens': 93, 'total_tokens': 120, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_d02d531b47', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-a579b60f-80ad-4b67-98b4-7677a76fa37b-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'サンフランシスコ 天気'}, 'id': 'call_jbf02Iv8ogXdcnynky9uxcHD', 'type': 'tool_call'}], usage_metadata={'input_tokens': 93, 'output_tokens': 27, 'total_tokens': 120, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}),
 ToolMessage(content='[{"url": "https://tenki.jp/world/7/92/72494/", "content": "tenki.jp サンフランシスコの天気19日05:00(日本時)発表 予報・実況 過去の天気 この先1週間の天気(現地時間)19日05:00(日本時)発表 晴一時雨 曇時々雨 晴一時雨 ※予報の対象日時は現地時刻になります サンフランシスコと日本の主要都市との天気比較 最高気温｜最低気温｜湿度 集計期間：11月12日(火)～11月18日(月) ※観測データは、不定期な更新が多いため、値は参考値としてご利用ください※湿度はその日に届いた観測データの中での最小値です 周辺の地点の天気(アメリカ) ※サンフランシスコの地点一覧を地図上に表示しています。全地点はこちら。※赤・・・サンフランシスコ\xa0青・・・アメリカのその他の地点 おすすめ情報 世界衛星 空港の天気 PM2.5分布予測 おすすめ記事 気象衛星(北アメリカ) 世界天気のみかた 文化や社会の仕組みも異なる海外、世界の天気予報を見る上でのポイントをまとめました。 周辺の天気19日05:00(日本時)発表 フレスノ 18℃ 3℃ レノ 13℃ -2℃ サンタマリア 15℃ 2℃ ロサンゼルス 16℃ 9℃ ポートランド(オレゴン州... 12℃ 5℃ ラスベガス 14℃ 7℃ シアトル 12℃ 4℃ サンディエゴ 16℃ 8℃ ヴィクトリア 8℃ 1℃ バンクーバー 8℃ 0℃ 最新の記事(気象予報士) tenki.jp tenki.jp 登山天気 全国のコンテンツ tenki.jpトップ 天気予報 観測 防災情報 天気図 指数情報 レジャー天気 季節特集 天気ニュース"}, {"url": "https://weathernews.jp/onebox/tenki/world/7/us/san-francisco/", "content": "サンフランシスコの天気予報【アメリカ】 - ウェザーニュース アメダス 山の天気 アメダス 山の天気 Ch. イルミネーション情報 花火天気 Ch. ほたる情報 マリン天気(海の天気) Labs Ch. マイソリューション サンフランシスコの天気予報現地時刻 11/28(木) 17時 28日(木) 11℃ 11℃ 11℃ 10℃ 10℃ 9℃ 9℃ 29日(金) 9℃ 9℃ 9℃ 8℃ 8℃ 8℃ 8℃ 8℃ 8℃ 10℃ 11℃ 12℃ 13℃ 14℃ 14℃ 14℃ 13℃ 12℃ 11℃ 11℃ 11℃ 10℃ 10℃ 10℃ 30日(土) 9℃ 9℃ 9℃ 9℃ 8℃ 8℃ 8℃ 8℃ 8℃ 9℃ 11℃ 12℃ 13℃ 14℃ 14℃ 14℃ 14℃ 12℃ 12℃ 11℃ 11℃ 11℃ 11℃ 10℃ 1日(日) 10℃ 10℃ 10℃ 10℃ 9℃ 9℃ 9℃ 9℃ 9℃ 11℃ 12℃ 13℃ 14℃ 15℃ 16℃ 16℃ 15℃ 13℃ 13℃ 2日(月) 集計期間： 11月28日(木) ～ 12月4日(水) 28(木) 29(金) 30(土) 1(日) 2(月) 4(水) 5(木) 6(金) 7(土) アメダス(実況天気) ほたる情報 花火天気 山の天気 マリン天気 Labs Ch. マイソリューション ウェザーニュース"}]', name='tavily_search_results_json', id='1cf8034d-df77-4e5d-8f39-937b3100dde4', tool_call_id='call_jbf02Iv8ogXdcnynky9uxcHD', artifact={'query': 'サンフランシスコ 天気', 'follow_up_questions': None, 'answer': None, 'images': [], 'results': [{'title': 'サンフランシスコ(アメリカ)の天気 - 日本気象協会 tenki.jp', 'url': 'https://tenki.jp/world/7/92/72494/', 'content': 'tenki.jp サンフランシスコの天気19日05:00(日本時)発表 予報・実況 過去の天気 この先1週間の天気(現地時間)19日05:00(日本時)発表 晴一時雨 曇時々雨 晴一時雨 ※予報の対象日時は現地時刻になります サンフランシスコと日本の主要都市との天気比較 最高気温｜最低気温｜湿度 集計期間：11月12日(火)～11月18日(月) ※観測データは、不定期な更新が多いため、値は参考値としてご利用ください※湿度はその日に届いた観測データの中での最小値です 周辺の地点の天気(アメリカ) ※サンフランシスコの地点一覧を地図上に表示しています。全地点はこちら。※赤・・・サンフランシスコ\xa0青・・・アメリカのその他の地点 おすすめ情報 世界衛星 空港の天気 PM2.5分布予測 おすすめ記事 気象衛星(北アメリカ) 世界天気のみかた 文化や社会の仕組みも異なる海外、世界の天気予報を見る上でのポイントをまとめました。 周辺の天気19日05:00(日本時)発表 フレスノ 18℃ 3℃ レノ 13℃ -2℃ サンタマリア 15℃ 2℃ ロサンゼルス 16℃ 9℃ ポートランド(オレゴン州... 12℃ 5℃ ラスベガス 14℃ 7℃ シアトル 12℃ 4℃ サンディエゴ 16℃ 8℃ ヴィクトリア 8℃ 1℃ バンクーバー 8℃ 0℃ 最新の記事(気象予報士) tenki.jp tenki.jp 登山天気 全国のコンテンツ tenki.jpトップ 天気予報 観測 防災情報 天気図 指数情報 レジャー天気 季節特集 天気ニュース', 'score': 0.86713725, 'raw_content': None}, {'title': 'サンフランシスコの天気予報【アメリカ】 - ウェザーニュース', 'url': 'https://weathernews.jp/onebox/tenki/world/7/us/san-francisco/', 'content': 'サンフランシスコの天気予報【アメリカ】 - ウェザーニュース アメダス 山の天気 アメダス 山の天気 Ch. イルミネーション情報 花火天気 Ch. ほたる情報 マリン天気(海の天気) Labs Ch. マイソリューション サンフランシスコの天気予報現地時刻 11/28(木) 17時 28日(木) 11℃ 11℃ 11℃ 10℃ 10℃ 9℃ 9℃ 29日(金) 9℃ 9℃ 9℃ 8℃ 8℃ 8℃ 8℃ 8℃ 8℃ 10℃ 11℃ 12℃ 13℃ 14℃ 14℃ 14℃ 13℃ 12℃ 11℃ 11℃ 11℃ 10℃ 10℃ 10℃ 30日(土) 9℃ 9℃ 9℃ 9℃ 8℃ 8℃ 8℃ 8℃ 8℃ 9℃ 11℃ 12℃ 13℃ 14℃ 14℃ 14℃ 14℃ 12℃ 12℃ 11℃ 11℃ 11℃ 11℃ 10℃ 1日(日) 10℃ 10℃ 10℃ 10℃ 9℃ 9℃ 9℃ 9℃ 9℃ 11℃ 12℃ 13℃ 14℃ 15℃ 16℃ 16℃ 15℃ 13℃ 13℃ 2日(月) 集計期間： 11月28日(木) ～ 12月4日(水) 28(木) 29(金) 30(土) 1(日) 2(月) 4(水) 5(木) 6(金) 7(土) アメダス(実況天気) ほたる情報 花火天気 山の天気 マリン天気 Labs Ch. マイソリューション ウェザーニュース', 'score': 0.8184036, 'raw_content': None}], 'response_time': 1.56}),
 AIMessage(content='サンフランシスコの最新の天気情報は以下の通りです：\n\n- **現在の天気**: 晴れ時々曇り\n- **最高気温**: 約11℃（現地時間）\n- **最低気温**: 約9℃（現地時間）\n\nなお、今後の予報では、一時的に雨が降る可能性もあります。詳しい情報は[こちらのリンク](https://tenki.jp/world/7/92/72494/)や[こちら](https://weathernews.jp/onebox/tenki/world/7/us/san-francisco/)でご確認ください。', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 143, 'prompt_tokens': 1111, 'total_tokens': 1254, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_0aa8d3e20b', 'finish_reason': 'stop', 'logprobs': None}, id='run-3a7af433-f5e5-46cb-b713-7a2ef481376f-0', usage_metadata={'input_tokens': 1111, 'output_tokens': 143, 'total_tokens': 1254, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]
```
![Screenshot 2024-12-24 at 12.09.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/8383ae7c-74ef-c829-e933-fcb21189fb7d.png)

# メモリーの追加

上述したように、このエージェントはステートレスです。これは、以前のやりとりを記憶しないことを意味します。メモリーを追加するには、checkpointerを渡す必要があります。checkpointerを渡す際には、(どのスレッド/会話から再開するのかが分かるように)エージェントを呼び出す際に`thread_id`も引き渡す必要があります。

```py
from langgraph.checkpoint.memory import MemorySaver

memory = MemorySaver()
```

```py
agent_executor = create_react_agent(model, tools, checkpointer=memory)

config = {"configurable": {"thread_id": "abc1234"}}
```

```py
for chunk in agent_executor.stream(
    {"messages": [HumanMessage(content="こんにちは！私は隆明です")]}, config
):
    print(chunk)
    print("----")
```
```
2024/12/24 02:48:23 WARNING mlflow.utils.autologging_utils: Encountered unexpected error during autologging: Span for run_id bb424714-c57e-4f82-ad71-aa7d7108891a not found.
{'agent': {'messages': [AIMessage(content='こんにちは、隆明さん！今日はどんなことをお話ししましょうか？', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 20, 'prompt_tokens': 85, 'total_tokens': 105, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_d02d531b47', 'finish_reason': 'stop', 'logprobs': None}, id='run-a8aef6e8-f439-4517-8bc0-f500c920da43-0', usage_metadata={'input_tokens': 85, 'output_tokens': 20, 'total_tokens': 105, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}}
----
```
![Screenshot 2024-12-24 at 12.10.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/59a911a4-206b-b629-6079-c8e2b3ae6406.png)

```py
for chunk in agent_executor.stream(
    {"messages": [HumanMessage(content="私の名前は？")]}, config
):
    print(chunk)
    print("----")
```
```
2024/12/24 02:48:37 WARNING mlflow.utils.autologging_utils: Encountered unexpected error during autologging: Span for run_id 82fd187a-ef2b-4922-a3b6-a1d1e864be8f not found.
{'agent': {'messages': [AIMessage(content='あなたの名前は隆明さんです。何か他にお手伝いできることがありますか？', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 25, 'prompt_tokens': 116, 'total_tokens': 141, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_d02d531b47', 'finish_reason': 'stop', 'logprobs': None}, id='run-9a5cb780-9d94-40ca-9952-82be205da721-0', usage_metadata={'input_tokens': 116, 'output_tokens': 25, 'total_tokens': 141, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}}
----
```

きちんと記憶してくれています！

![Screenshot 2024-12-24 at 12.11.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/e58a8b55-a66c-3f24-8a52-fc5a8c98b852.png)

なお、新規に会話をスタートしたい場合には、使用する`thread_id`を変更します。



### はじめてのDatabricks

[はじめてのDatabricks](https://qiita.com/taka_yayoi/items/8dc72d083edb879a5e5d)

### Databricks無料トライアル

[Databricks無料トライアル](https://databricks.com/jp/try-databricks)