前回の続きです:

0:準備編
1:Jupyter Notebook 編
2:LangChain 編 (今回はここ)


最終回の今回は前回までに用意した環境や情報を使って、LangChain と呼ばれる LLM アプリケーション開発フレームワークを watsonx.ai の LLM で実装する方法を紹介します。 なお、ここで紹介した内容とその結果は 2024/12/31 時点でのものである点に留意ください。


【LangChain とは? エージェントとは?】
LangChain とは LLM を使った大規模アプリケーションの作成を簡易化するために用意されたオープンソースのオーケストレーション・フレームワークの1つです。

LLM はその名前(Large Language Model)が指し示すように大規模な言語データによってトレーニングされたモデルで、一般的な文脈の問い合わせには慣れていますが、(トレーニングデータに含まれていない)特定分野に関する情報や最新データに関する文脈への問い合わせは苦手としています。

LangChain はそのような LLM が比較的苦手とするコンテキストに対する問い合わせを補完するものです。様々な機能を持つ AI コンポーネントを「チェーン」と呼ばれる形でつなぎ合わせて最終的な答を導きだす機能を有しています。

そして、この LangChain は多くの場合で「エージェント」と呼ばれる機能とセットで使われます。「エージェント」はその名前が示すように「LLM 実行時に渡されたコンポーネントを用いて、『次にどのようなアクションを取ればよいかを判断し、選択し、実行して、目的を完了するまで繰り返す」という役割を担っています。

例えば次のような問い合わせを行いたいとします:
 「千葉県で一番高い山の高さの平方根の値はいくつですか?」

この問い合わせは(単体の)LLM だけではかなり難題に分類されるものだと想像できます。まず「千葉県で一番高い山(の高さ)」は LLM 作成時のトレーニングデータに含まれていれば答えられるかもしれませんが、含まれていなかった場合は答えられないし、場合によってはここでハルシネーションを起こしてしまいかねない質問です。 またその数値が分かったとして「平方根の値」を求める方法もトレーニング時に含まれていないと計算もできないはずです。そこまで都合よく LLM がトレーニングされているとは限らないわけで、LLM だけで正しい答を導くのは難しいはずなのです。

一方、この答を人間的な考え方で導こうとすると、多くの場合は
(1)まず千葉県で一番高い山を特定し(検索する)、 
(2)その山の高さを求め、
(3)最後にその高さの数値の平方根を計算(数値計算)する

という順序で考えて調べていくことで正解にたどり着こうとするのではないかと思います。

LangChain のエージェントはまさにこの「正解にたどり着くための順序を考える」部分を実施するライブラリです。「検索と数値計算を使って『千葉県で1番高い山の高さの平方根の値はいくつですか?』という問題の正解にたどりつくにはどうすればよいか?」を考え、(1)から(3)の順に実行する、というライブラリです。 

今回はこの LangChain のエージェント機能を watsonx.ai で使う例を紹介します。


【Jupyter Notebook で watsonx.ai の LangChain とエージェント機能を利用する】
前回同様に IBM Cloud 環境に用意した Jupyter Notebook 機能にアクセスし、編集状態にします。

まず最初に、これは私自身が色々試した範囲内での体感的な感想ではあるのですが、この LangChain やエージェントを実行する段階においては LLM として llama ベースのものを使うのが期待通りの結果になることが多い印象があります。というわけで、先頭セルの内容を編集し、利用する LLM を "meta-llama/llama-3-2-11b-vision-instruct" と変更します※:
2024123117

MODEL_ID の値を "ibm/granite-8b-japanese" から "meta-llama/llama-3-2-11b-vision-instruct" に変更します


なお、この状態で(MODEL_ID の値を変えた状態で)前回のコードを使ってこの質問を LLM にそのまま問い合わせた時の結果はこちらです。質問の意味を理解できていないかのような回答が最大トークンぶん繰り返されていたような結果になりました。やはり素の LLM には難問のようでした:
2025010300



改めて、次に前回までに作成した内容の下に1つセルを追加し、以下の内容を記載します(ここでは1行だけです):
pip install google-search-results

この1行は、この下のセルで初回に紹介したウェブ検索 API である SerpAPI を使うのですが、そのためのライブラリをインストールするコマンドです。

続けて更にセルを追加し、以下の内容を記載します。まずは最初のセルで初期化した変数 SERPAPI_API_KEY を使って、この値を環境変数化しています。そして前回作成した言語モデル custom_llm に対して、「ウェブ検索と数値演算を併用」してプロンプトに対する答を自律的に考えて求めるよう指示しています(具体的な検索方法や計算アルゴリズムは一切指示していない点に注目してください):
# LangChain Agent で watsonx.ai を使った問い合わせ
from langchain.agents import AgentType, initialize_agent
from langchain_community.agent_toolkits.load_tools import load_tools

import os
os.environ["SERPAPI_API_KEY"] = SERPAPI_API_KEY

# 2つ上で定義した custom_llm を使う
# custom_llm = WatsonxLLM(model=model)

# LLM(watsonx.ai)と serpapi(ウェブ検索)と llm-math(数値演算)を使って問い合わせの答を求める
tools = load_tools( ["serpapi", "llm-math" ], llm = custom_llm )
agent = initialize_agent( tools, custom_llm, agent = AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose = True, handle_parsing_errors = True )

r = agent.invoke( "千葉県で一番高い山の高さの平方根の値はいくつですか?" )
print( r )

2024123118
(↑この2つのセルとその内容を追加します)


ここで追加した内容は前回までに実装した custom_llm というモデルと、ウェブ検索ライブラリ("serpapi")と、数値計算ライブラリ("llm-math")を使って、「千葉県で一番高い山の高さの平方根の値はいくつですか?」という問い合わせを行う内容が記載されています。内容を見て分かるとおり、具体的にどのような思考順序で答えるべきか、といった情報はコード内には一切書かれておらず、そのあたりがエージェントに任されて、ただ問い合わせ内容だけが記載されていることがわかると思います。

では実際にこのコードを実行します。変数定義からやり直す必要があるので前回作った一番上のセルから順にセルを実行していきます(新たに指定した言語モデルで custom_llm 変数がインスタンス化されます)。

前回最後に実行したセルまで実行できたら、次は今回入力した新しい部分を実行します。まずは "pip install google-search-results" だけの行を実行します。以下のような "google-search-results" ライブラリがインストールされていく様子が出力されていきます:
2024123113


では最後のセルも実行します。実行すると元の質問文の内容をどのように解釈して、どのような順序で回答を求めていくかの様子も含めて出力されていくのがわかります:
2024123115


この質問に答えるには、まず千葉県で最も高い山を調べる必要があり、(ここで SerpAPI の検索 API を使って)それが愛宕山の 408m であることがわかり、その上でこの値の平方根を(llm-math という数値演算ライブラリを使って)求めて、、という経過を経ていることがわかりますね。


2024123116


最後は出力が途中で切れてしまいましたが、上図の青枠部分を見ると問い合わせに対する回答である 20.396.. という数字を得ることができています。こういった「どのようなライブラリや API を使う準備ができているのかを理解した上で、最終的に求められている答を求めるためのアプローチ方法から LLM が調べていく、、というより人間的な方法で問い合わせに対する回答を得ることができました。これがLLM の(LangChain の)エージェント機能と呼ばれるものです。

watsonx.ai でも無事に LangChain やエージェント機能を利用できることが確認できました。