LangGraphHow-to Guides の一つである How to call tools using ToolNode を参考に、Vertex AI Search retriever と紐づく ToolNode を組み込んだ ReAct agent を作成してみます。

LangGraph はステートグラフで表現されるマルチエージェントアプリケーションを構築するためのライブラリです。
LangGraph について学びたい方には LangChain Academy の Introduction to LangGraph がおすすめです。

また、Vertex AI Search retriever は、Vertex AI Search を使用して関連ドキュメントの情報を取得する Langchain retriever の一つで、Vertex AI Search を活用した検索結果を簡単に RAG 活用することができます。

検証環境

ライブラリのバージョン

1
2
3
4
langchain                               0.2.16
langchain-google-community              1.0.8
langgraph                               0.2.39
google-cloud-discoveryengine            0.13.1

Vertex AI Search に使用したデータストア

今回使用したデータストアの構成は以下の通りです。

  • データソース: Cloud Storage
  • データ種別: リンクされた非構造化ドキュメント(JSONL とメタデータ)
  • ドキュメント解析に用いるパーサー: レイアウトパーサー

以下はインポートした JSONL ファイルの中身で、文部科学省のドキュメントを使用しています12(JSONL の形式の詳細については公式ドキュメントを参照ください)。

1
2
{"id": "1", "structData": {"title": "小学校プログラミング教育の手引", "contact": "初等中等教育局学校デジタル化プロジェクトチーム", "issuer": "文部科学省", "url": "https://www.mext.go.jp/content/20200218-mxt_jogai02-100003171_002.pdf"}, "content": {"mimeType": "application/pdf", "uri": "gs://uri_path/to/doc1.pdf"}}
{"id": "2", "structData": {"title": "高等学校学習指導要領(平成30年告示)解説 情報編", "contact": "初等中等教育局学校デジタル化プロジェクトチーム", "issuer": "文部科学省", "url": "https://www.mext.go.jp/content/000166115.pdf"}, "content": {"mimeType": "application/pdf", "uri": "gs://uri_path/to/doc2.pdf"}}

レイアウトパーサーを採用した理由は、PDFデータを RAG として活用する場合レイアウトパーサーを使用した上で適切にチャンクを行うことが推奨されるためです(詳しくは公式ドキュメントを参照ください)。

Vertex AI Search retriever の設定と動作確認

基本的に Langchain — Google Vertex AI Search に従い、まずは VertexAISearchRetriever の設定を済ませます。
データストアの location_iddata_store_id の値は、Vertex AI Agent Builder コンソールのデータストア一覧画面、または個々のデータストアの詳細画面で確認できます。 一覧画面では「場所」列に location_id、「ID」列に data_store_id に設定するべき値が表示されています。

1
2
3
4
5
from langchain_google_community import VertexAISearchRetriever
 
retriever = VertexAISearchRetriever(
    project_id=PROJECT_ID, location_id=LOCATION_ID, data_store_id=DATA_STORE_ID
)

VertexAISearchRetriever の設定ができたので、retriever による検索結果を調べてみます。

1
2
3
4
5
query = "プログラミングの授業の目的は?"
results = retriever.invoke(query)
print(f"検索結果の件数: {len(results)}")
print(f"出力オブジェクトの型: {type(results)} of {type(results[0])}n")
print(results)
1
2
3
4
5
6
7
8
9
検索結果の件数: 2
 
出力オブジェクトの型: <class 'list'> of <class 'langchain_core.documents.base.Document'>
 
[Document(metadata={'title': '小学校プログラミング教育の手引', 'contact': '初等中等教育局学校デジタル化プロジェクトチーム', 'issuer': '文部科学省', 'url': 'https://www.mext.go.jp/content/20200218-mxt_jogai02-100003171_002.pdf', 'id': '1', 'source': 'gs://uri_path/to/doc1.pdf'}, page_content='# 第2章 小学校プログラミング教育で育む力nn(1)プログラミング教育のねらい 小学校におけるプログラミング教育のねらいは、「小学校学習指導要領解 説総則編」においても述べていますが、非常に大まかに言えば、①「プロ グラミング的思考」を育むこと、②プログラムの働きやよさ、情報社会がコ ンピュータ等の情報技術によ
 
(中略)
 
指導計画を作成し、必要な配慮を 記載し,他教科等の担任と共有したり、翌年度の担任等に引き継いだりすることが必要で ある。')]

出力からlangchain_core.documents.base.Document のインスタンスとして2つの検索結果が得られたことが分かります。
ここで、メタデータを詳しく調べてみると以下のように JSONLファイルに記載したメタデータも取得できています。

1
2
3
4
from pprint import pprint
 
for result in results:
    pprint(result.metadata)
01
02
03
04
05
06
07
08
09
10
11
12
{'contact': '初等中等教育局学校デジタル化プロジェクトチーム',
 'id': '1',
 'issuer': '文部科学省',
 'source': 'gs://uri_path/to/doc1.pdf',
 'title': '小学校プログラミング教育の手引',
 'url': 'https://www.mext.go.jp/content/20200218-mxt_jogai02-100003171_002.pdf'}
{'contact': '初等中等教育局学校デジタル化プロジェクトチーム',
 'id': '2',
 'issuer': '文部科学省',
 'source': 'gs://uri_path/to/doc2.pdf',
 'title': '高等学校学習指導要領(平成30年告示)解説 情報編',
 'url': 'https://www.mext.go.jp/content/000166115.pdf'}

捕捉事項: Vertex AI Search retriever 使用時の注意点

今回のように、非構造化データを用いたデータストアを Vertex AI Search retriever で使用する場合は、「Enterprise エディションの機能」を有効にしたアプリと紐づいている必要があります。
アプリとの紐付けを行なっていない、もしくは紐づくアプリの「Enterprise エディションの機能」が有効になっていない場合は以下のようなエラーが発生します。

FailedPrecondition: 400 Cannot use enterprise edition features (website search, multi-modal search, extractive answers/segments, etc.) in a standard edition search engine. Please follow https://cloud.google.com/generative-ai-app-builder/docs/enterprise-edition#toggle-enterprise to enable Enterprise edition. Data store: data_store_name

理由としては、VertexAISearchRetriever_get_content_spec_kwargs メソッドで、 content_search_specExtractiveContentSpec の設定を行なっていることが考えられます。公式ドキュメントに記載がある通り、「Enterprise エディションの機能」を有効にしないと Extractive segments を活用することはできません。

一方、ソースコードを読むと分かるよう engine_data_type=1 つまり構造化データの場合はエラーは発生しません。

ToolNode で使用する tools の定義

LangGraph のステートグラフで Vertex AI Search の検索結果を活用できるようにしていきます。ToolNode はステートグラフ内でツール呼び出しを扱うために活用できるノードです。Vertex AI Search をこのノードを通じて実行し結果を次のノードに渡すことで、検索結果を元にした応答生成が可能になります。
そのための準備として、create_retriever_tool を使用して VertexAISearchRetriever を使用したドキュメント検索ツールを作成します。
回答生成時にメタデータを活用したいので、以下のようなプロンプトテンプレートを定義しておきます。
<meta> タグ内にメタデータの各キーを変数として埋め込んでいます。

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
from textwrap import dedent
 
from langchain_core.prompts import PromptTemplate
 
document_prompt = PromptTemplate.from_template(
    dedent(
        """
        <context>
        {page_content}
 
        <meta>
        title: {title}
        issuer: {issuer}
        contact: {contact}
        url: {url}
        source: {source}
        </meta>
        </context>
        """
    ).strip()
)
1
2
3
4
5
6
7
8
from langchain.tools.retriever import create_retriever_tool
 
retriever_tool = create_retriever_tool(
    retriever=retriever,
    name="search_information_education_guidelines",
    description="プログラミング・情報教育に関する文書を検索する。",
    document_prompt=document_prompt,
)

作成した retriever_toolToolNode に紐付けます。

1
2
3
4
from langgraph.prebuilt import ToolNode
 
tools = [retriever_tool]
tool_node = ToolNode(tools)

ここで一旦、retriever_tool を紐づけた ToolNode の応答を調べてみます。
ToolNode は直前のメッセージが tool 呼び出しを行う AIMessage であることが期待されるので、tool_calls パラメータのある AIMessage を用いて呼び出します。

01
02
03
04
05
06
07
08
09
10
11
12
13
14
from langchain_core.messages import AIMessage
 
message_with_single_tool_call = AIMessage(
    content="",
    tool_calls=[
        {
            "name": retriever_tool.name,
            "args": {"query": "プログラミングの授業の目的は?"},
            "id": "tool_call_id",
            "type": "tool_call",
        }
    ],
)
tool_node.invoke({"messages": [message_with_single_tool_call]})
1
2
3
4
5
{'messages': [ToolMessage(content='n# 第2章 小学校プログラミング教育で育む力nn(1)プログラミング教育のねらい 小学校におけるプログラミング教育のねらいは、「小学校学習指導要領解 説総則編」においても述べていますが、
 
(中略)
 
翌年度の担任等に引き継いだりすることが必要で ある。nn<meta>ntitle: 高等学校学習指導要領(平成30年告示)解説 情報編nissuer: 文部科学省ncontact: 初等中等教育局学校デジタル化プロジェクトチームnurl: https://www.mext.go.jp/content/000166115.pdfnsource: gs://uri_path/to/doc2.pdfn</meta>n</context>', name='search_information_education_guidelines', tool_call_id='tool_call_id')]}

結果を確認すると ToolMessage が返され、content には先ほど定義したプロンプトテンプレートでフォーマットされた出力が設定されていることがわかります。
ここで呼び出しに用いた AIMessage は tools をバインドしたモデルの応答として出力されるので、ToolNode がこの出力を受け取るように状態グラフを組み立てていきます。

ToolNode を組み込んだエージェントの作成

先ほど作成した ToolNode を組み込んだエージェントを作成します。

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
from langgraph.graph import END, START, MessagesState, StateGraph
from langgraph.prebuilt import tools_condition
 
 
def call_model(state: MessagesState):
    messages = state["messages"]
    response = model_with_tools.invoke(messages)
 
    return {"messages": [response]}
 
 
builder = StateGraph(MessagesState)
 
builder.add_node("agent", call_model)
builder.add_node("tools", tool_node)
 
builder.add_edge(START, "agent")
builder.add_conditional_edges("agent", tools_condition)
builder.add_edge("tools", "agent")
 
graph = builder.compile()

これにより以下の図のようなグラフができあがります。

__start__ から開始して、ユーザーからの質問に応じて tool を呼び出すかを agent が判断し tool を呼び出すべきと判断した場合は tools ノードに遷移します。tools ノードでは先ほど確認したように retriever_tool によるドキュメント検索が行われその結果が State に格納され agent に渡されます。それを元に agent はユーザーの質問に回答してくれます。
tool を呼び出さないと判断した場合は __end__ ノードに遷移し一連の会話は終了します。

動作確認

以下のシステムメッセージを定義して使用します。このシステムメッセージは Vertex AI Search で取得できるメタデータを利用して、出典を記載するためのものです3

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
from langchain_core.messages import SystemMessage
 
system_message = SystemMessage(
    dedent(
        """
        Tool Message を用いた回答を行わない、もしくは Tool Message に <meta> タグが含まれない場合、
        以下の指示を無視してください。
 
        <meta> タグ内の情報 (title, issuer, url) を抽出し、
        回答の末尾に1行空けて引用元として表記してください。
        複数引用する場合はそれぞれ明記してください。
 
        引用形式:
        - 「title」(issure)(url)
        """.strip()
    )
)

質問してみます。

1
2
3
4
5
from langchain_core.messages import HumanMessage
 
messages = [system_message, HumanMessage("プログラミングの学校教育の目的を詳しく教えてください")]
for chunk in graph.stream({"messages": messages}, stream_mode="values"):
    chunk["messages"][-1].pretty_print()
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
================================ Human Message =================================
 
プログラミングの学校教育の目的を詳しく教えてください
================================== Ai Message ==================================
Tool Calls:
  search_information_education_guidelines (8a292eff-45a2-43ed-bb86-eec3f388e96e)
 Call ID: 8a292eff-45a2-43ed-bb86-eec3f388e96e
  Args:
    query: プログラミング教育の目的
================================= Tool Message =================================
Name: search_information_education_guidelines
 
<context>
# 第2章 小学校プログラミング教育で育む力
 
(1)プログラミング教育のねらい 小学校におけるプログラミング教育のねらいは、「小学校学習指導要領解 説総則編」においても述べていますが、
 
(中略)
 
段階的な指導を行うなどの配慮を する なお、学校においては,こうした点を踏まえ,個別の指導計画を作成し、必要な配慮を 記載し,他教科等の担任と共有したり、翌年度の担任等に引き継いだりすることが必要で ある。
 
<meta>
title: 高等学校学習指導要領(平成30年告示)解説 情報編
issuer: 文部科学省
contact: 初等中等教育局学校デジタル化プロジェクトチーム
url: https://www.mext.go.jp/content/000166115.pdf
source: gs://m-ueda-test/vertex_test/rag/000166115.pdf:
</meta>
</context>
 
================================== Ai Message ==================================
 
プログラミング教育のねらいは、大きく分けて3つあります。
 
1. **「プログラミング的思考」を育むこと**
- 問題を解決するために、コンピュータにどのような指示を順番に与えれば良いかを論理的に考える力です。
- 複雑な問題を小さな手順に分解したり、手順を組み合わせたり、効率的に処理する方法を考えたりする力を養います。
 
2. **プログラムの働きやよさ、情報社会がコンピュータ等の情報技術によって支えられていることなどに気付くこと**
- コンピュータがどのようにプログラムによって動作しているのか、プログラムがどのように社会に役立っているのかを理解します。
- コンピュータを道具として使いこなすだけでなく、その仕組みや可能性について理解することで、情報社会をより深く理解することができます。
 
3. **各教科等での学びをより確実なものとすること**
- プログラミングを他の教科の学習に活用することで、より深く理解したり、より実践的に学ぶことができます。
- 例えば、算数で正多角形を学ぶ際に、プログラミングで正多角形を描画することで、その性質をより深く理解することができます。
 
これらのねらいを実現するために、プログラミング教育では、コンピュータを用いて実際にプログラムを作成する体験を通して、論理的思考力、問題解決能力、創造性を育みます。
 
-「小学校プログラミング教育の手引」(文部科学省)(https://www.mext.go.jp/content/20200218-mxt_jogai02-100003171_002.pdf)
-「高等学校学習指導要領(平成30年告示)解説 情報編」(文部科学省)(https://www.mext.go.jp/content/000166115.pdf)

ユーザーからの質問(Human Message)に対して、agent が tool 呼び出しを行う判断(Ai Message)を行っていることが分かります。それにより、tool 呼び出しの結果として検索結果(Tool Message)が取得され、それを元にした回答(Ai Message)がされています。

まとめ

質問内容によって Vertex AI Search の検索結果を RAG 活用するエージェントを作成してみました。
LangGraph の例としてはかなり初歩的な内容ではありますが、小さな構造を組み合わせていくことで複雑な処理を行う状態グラフを構成していくことができます。

また、LangChain on Vertex AI を用いると、LangGraph で作成したエージェントを Google Cloud に簡単にデプロイすることもできます。


  1. 「小学校プログラミング教育の手引(第三版) 」(文部科学省)(https://www.mext.go.jp/content/20200218-mxt_jogai02-100003171_002.pdf) ↩
  2. 「情報編 高等学校学習指導要領(平成30年告示)解説」(文部科学省)(https://www.mext.go.jp/content/000166115.pdf) ↩
  3. 文部科学省ウェブサイト利用規約 ↩