<img height="1" width="1" style="display:none" src="https://www.facebook.com/tr?id=139163818022217&amp;ev=PageView&amp;noscript=1"> <img height="1" width="1" style="display:none" src="https://www.facebook.com/tr?id=271598307802760&amp;ev=PageView&amp;noscript=1">

BoxとGoogle MCP Toolbox for Databasesを使用したAIエージェント

 公開日:2025.04.28  更新日:2026.06.10

AIエージェントの最も興味深い側面の1つは、エージェントが必要なデータを必要なときにどこで取得するかを決定できるように、エージェントに複数のデータソースを指定できることです。世の中で必要とされる主なデータの種類の1つが非構造化データで、Boxでは、機密性の高いデータにセキュリティを提供すると同時に、そのコンテキストを適切と思われる形でAIアプリケーションに提供できるよう支援することができます。

私たちがよく目にしていることの1つに、この非構造化データと構造化データの組み合わせがあります。この組み合わせは、抽出APIを使用してBoxコンテンツからキーと値のペアを抽出するという形で頻繁に見られます。抽出後、このデータはデータベースやアプリケーションへの入力に使用したり、他のデータと組み合わせてメタデータの形式でBox内のファイルに再度追加したりすることができます。

このような傾向から、Google DatabaseチームがGenAI Toolbox for Databases (MCP Toolbox for Databasesに名称が変更されました) をリリースする予定であるというニュースに興味を持ちました。これは、エージェントが任意のデータベースを操作できるようにするための、簡単に構成できる一連のAIツールです。そこで、このToolboxBoxエージェントツールを使用したAIエージェントを利用するアプリケーションを作成することにしました。

エージェントの作成はいつものように簡単です。LangChainlangchain-toolboxライブラリ、Box AI Agent Toolkitを組み合わせて使用し、BoxToolboxOpen AI gpt-4.5-previewとやり取りできるAIエージェントを作成しました。ユースケースとしては、Box内にある住宅ローンの申込書からデータを抽出し、それを使用してPostgresqlデータベース内のデータを基に物件概要をまとめるという考えに落ち着きました。このエージェントのコードはこちらにあります。

このコードを自分で実行するには、多少の設定が必要です。もちろん、PostgreSQLデータベースは必須です。Postgresの手順に従って作業を開始できます。

インストールが完了したら、サンプルデータベースを設定します。

1. psqlコマンドを使用してpostgresに接続します。

psql -h 127.0.0.1 -U postgres

ここで、postgresはデフォルトのpostgresスーパーユーザーを示します。

2. 新しいデータベースと新しいユーザーを作成します。

実際のアプリケーションでは、最小権限の原則に従い、アプリケーションに必要な権限のみを付与することをお勧めします。

CREATE USER property_manager WITH PASSWORD 'property_manager';
CREATE DATABASE property_db;
GRANT ALL PRIVILEGES ON DATABASE property_db TO property_manager;
ALTER DATABASE property_db OWNER TO property_manager;

3. データベースセッションを終了します。

\q

4. 新しいユーザーを使用してデータベースに接続します。

psql -h 127.0.0.1 -U property_manager -d property_db

5. 次のコマンドを使用して6個のテーブルを作成します。

propertiesテーブル:

CREATE TABLE properties(
  id INTEGER NOT NULL PRIMARY KEY,
  address VARCHAR(100) NOT NULL,
  sq_ft INTEGER NOT NULL,
  property_type VARCHAR(20) NOT NULL
);

title_historyテーブル:

CREATE TABLE title_history(
  id INTEGER NOT NULL PRIMARY KEY,
  deed VARCHAR(100) NOT NULL,
  deed_date DATE NOT NULL,
  owner VARCHAR(100) NOT NULL,
  mortgage VARCHAR(100) NOT NULL,
  mortgage_date DATE NOT NULL,
  mortgage_amount INTEGER NOT NULL,
  property_id INTEGER,
  CONSTRAINT property_id_fkey FOREIGN KEY(property_id)
  REFERENCES properties(id)
);

legal_actionsテーブル:

CREATE TABLE legal_actions(
  id INTEGER NOT NULL PRIMARY KEY,
  case_description VARCHAR(100) NOT NULL,
  case_date DATE NOT NULL,
  case_parties VARCHAR(100) NOT NULL,
  property_id INTEGER,
  CONSTRAINT property_id_fkey FOREIGN KEY(property_id)
  REFERENCES properties(id)
);

liensテーブル:

CREATE TABLE liens(
  id INTEGER NOT NULL PRIMARY KEY,
  liens VARCHAR(100) NOT NULL,
  lien_date DATE NOT NULL,
  lien_amount INTEGER NOT NULL,
  property_id INTEGER,
  CONSTRAINT property_id_fkey FOREIGN KEY(property_id)
  REFERENCES properties(id)
);

taxesテーブル:

CREATE TABLE taxes(
  id INTEGER NOT NULL PRIMARY KEY,
  bill VARCHAR(100) NOT NULL,
  bill_date DATE NOT NULL,
  bill_amount INTEGER NOT NULL,
  property_id INTEGER,
  CONSTRAINT property_id_fkey FOREIGN KEY(property_id)
  REFERENCES properties(id)
);

easementsテーブル:

CREATE TABLE easements(
  id INTEGER NOT NULL PRIMARY KEY,
  easement VARCHAR(100) NOT NULL,
  easement_date DATE NOT NULL,
  easement_description VARCHAR(100) NOT NULL,
  property_id INTEGER,
  CONSTRAINT property_id_fkey FOREIGN KEY(property_id)
  REFERENCES properties(id)
);

6. テーブルにデータを挿入します。

propertiesテーブルにデータを入力します。

INSERT INTO properties (address, sq_ft, property_type)
VALUES ('123 Main St, Austin, TX 78701', 1000, 'Residential');

title_historyテーブルにデータを入力します。

INSERT INTO title_history (deed, deed_date, owner, mortgage, mortgage_date, mortgage_amount, property_id)
VALUES ('Deed 123456, County Recorders Office','2010–01–01','John Doe','Mortgage 78901, Bank of America','2015–05–15',100000, 1);

legal_actionsテーブルにデータを入力します。

INSERT INTO legal_actions (case_description, case_date, case_parties, property_id)
VALUES ('Case 901234, Travis County Court','2022–03–15','John Doe vs. Jane Doe',1);

liensテーブルにデータを入力します。

INSERT INTO liens (lien, lien_date, lien_amount, property_id)
VALUES ('Lien 345678, City of Austin','2020–01–01',5000,1);

taxesテーブルにデータを入力します。

INSERT INTO taxes (bill, bill_date, bill_amount, property_id)
VALUES ('Tax Bill 567890, Travis County Tax Office','2023–01–01',1000,1);

easementsテーブルにデータを入力します。

INSERT INTO easements (easement, easement_date, easement_description, property_id)
VALUES ('Easement 112233, Utility Company','2012–01–01','Utility lines',1);

7. データベースセッションを終了します。

\q

エージェントにツールを提供するコードは非常に単純です。最初に、Box AI Agent Toolkitを使用して、作成したエージェントにいくつかのツールを提供しました。

from dataclasses import dataclass
import json
import logging
import dotenv
import os
from typing import Dict, Iterable, List, Union
from box_ai_agents_toolkit import (
  File,
  Folder,
  SearchForContentContentTypes,
  get_ccg_client,
  box_file_get_by_id,
  box_file_text_extract,
  box_file_ai_ask,
  box_file_ai_extract,
  box_file_ai_extract_structured,
  box_folder_text_representation,
  box_folder_ai_ask,
  box_folder_ai_extract,
  box_folder_ai_extract_structured,
  box_search,
  box_locate_folder_by_name,
  box_folder_list_content
)
from box_ai_agents_toolkit.box_api import BoxFileExtendedfrom langchain_core.tools import toollogger = logging.getLogger(__name__)client = get_ccg_client()@tool
def box_tool_file_get_by_id(file_id: str) -> File:
  """
  Get information about a file in Box by its ID.
 
  Args:
    file_id (str): The ID of the file to read.
  return:
    str: The Box File object.
  """
  return box_file_get_by_id(client, file_id=file_id)@tool
def box_tool_file_text_extract(file_id: str) -> str:
  """
  Read the text content of a file in Box.  Args:
    file_id (str): The ID of the file to read.
  return:
  str: The text content of the file.
  """
  return box_file_text_extract(client, file_id=file_id)@tool
def box_tool_file_ai_ask(
  file_id: str, prompt: str
) -> str:
  """
  Ask box ai about a file in Box.  Args:
    file_id (str): The ID of the file to read.
    prompt (str): The prompt to ask the AI.
  return:
    str: The text content of the file.
  """
  return box_file_ai_ask(client=client, file_id=file_id, prompt=prompt)@tool
def box_tool_file_ai_extract(
  file_id: str, prompt: str
) -> dict:
  """
  Extract data from a file in Box from a string promptusing AI.  Args:
    file_id (str): The ID of the file to read.
    fields (str): The fields to extract from the file.
  return:
    str: The extracted data in a json string format.
  """
  return box_file_ai_extract(client=client, file_id=file_id, prompt=prompt)@tool
def box_tool_file_ai_extract_structured(
  file_id: str, fields_json_str: str
) -> str:
  """
  Extract data from a file in Box from a json string prompt using AI.  Args:
    file_id (str): The ID of the file to read.
    fields_json_str (str): The fields to extract from the file.
  return:
    str: The extracted data in a json string format.
  """
  return box_file_ai_extract_structured(client=client, file_id=file_id, fields_json_str=fields_json_str)@tool
def box_tool_folder_text_representation(
  folder_id: str,
  is_recursive: bool = False,
  by_pass_text_extraction: bool = False,
) -> Iterable[BoxFileExtended]:
  """
  Read the text content of a folder in Box.  Args:
    folder_id (str): The ID of the folder to read.
    is_recursive (bool): Whether to read the folder recursively.
    by_pass_text_extraction (bool): Whether to bypass text extraction.
  return:
    Iterable[BoxFileExtended]: The text content of the files.
  """
  yield from box_folder_text_representation(client=client, folder_id=folder_id, is_recursive=is_recursive, by_pass_text_extraction=by_pass_text_extraction)@tool
def box_tool_folder_ai_ask(
  folder_id: str,
  prompt: str,
  is_recursive: bool = False,
  by_pass_text_extraction: bool = False,
) -> Iterable[BoxFileExtended]:
  """
  Ask box ai about a folder in Box.
  
  Args:
    folder_id (str): The ID of the folder to read.
    prompt (str): The prompt to ask the AI.
    is_recursive (bool): Whether to read the folder recursively.
    by_pass_text_extraction (bool): Whether to bypass text extraction.
  return:
    Iterable[BoxFileExtended]: The text content of the files.
  """
  yield from box_folder_ai_ask(client=client, folder_id=folder_id, prompt=prompt, is_recursive=is_recursive, by_pass_text_extraction=by_pass_text_extraction)@tool
def box_tool_folder_ai_extract(
  folder_id: str,
  prompt: str,
  is_recursive: bool = False,
  by_pass_text_extraction: bool = False,
) -> Iterable[BoxFileExtended]:
  """
  Extract data from a folder in Box from a string prompt using AI.  Args:
    folder_id (str): The ID of the folder to read.
    prompt (str): The prompt to ask the AI.
    is_recursive (bool): Whether to read the folder recursively.
    by_pass_text_extraction (bool): Whether to bypass text extraction.
  """
  yield from box_folder_ai_extract(client=client, folder_id=folder_id, prompt=prompt, is_recursive=is_recursive, by_pass_text_extraction=by_pass_text_extraction)@tool
def box_tool_folder_ai_extract_structured(
  folder_id: str,
  fields_json_str: str,
  is_recursive: bool = False,
  by_pass_text_extraction: bool = False,
) -> Iterable[BoxFileExtended]:
  """
  Extract data from a folder in Box from a json string prompt using AI.  Args:
    folder_id (str): The ID of the folder to read.
    fields_json_str (str): The fields to extract from the folder.
    is_recursive (bool): Whether to read the folder recursively.
    by_pass_text_extraction (bool): Whether to bypass text extraction.
  """
  yield from box_folder_ai_extract_structured(client=client, folder_id=folder_id, fields_json_str=fields_json_str, is_recursive=is_recursive, by_pass_text_extraction=by_pass_text_extraction)@tool
def box_tool_search(
  query: str,
  file_extensions: List[str] | None = None,
  content_types: List[SearchForContentContentTypes] | None = None,
  ancestor_folder_ids: List[str] | None = None,
) -> List[File]:
  """
  Search for files in Box with the given query.
  
  Args:
    query (str): The query to search for.
    file_extensions (List[str]): The file extensions to search for, for example *.pdf
    content_types (List[SearchForContentContentTypes]): where to look for the information, possible values are:
      NAME
      DESCRIPTION,
      FILE_CONTENT,
      COMMENTS,
      TAG,
    ancestor_folder_ids (List[str]): The ancestor folder IDs to search in.
  return:
    str: The search results.
  """
  # content_types: List[SearchForContentContentTypes] = [
  # SearchForContentContentTypes.NAME,
  # SearchForContentContentTypes.DESCRIPTION,
  # # SearchForContentContentTypes.FILE_CONTENT,
  # SearchForContentContentTypes.COMMENTS,
  # SearchForContentContentTypes.TAG,
  # ]
  return box_search(client=client, query=query, file_extensions=file_extensions, content_types=content_types, ancestor_folder_ids=ancestor_folder_ids)@tool
def box_tool_locate_folder_by_name(
  folder_name: str, parent_folder_id: str = "0"
) -> List[Folder]:
  """
  Locate a folder in Box by its name.
  
  Args:
    folder_name (str): The name of the folder to locate.
  return:
    str: The folder ID.
  """
  return box_locate_folder_by_name(client=client, folder_name=folder_name, parent_folder_id=parent_folder_id)@tool
def box_tool_folder_list_content(
  folder_id: str, is_recursive: bool = False
) -> List[Union[File, Folder]]:
  """
  List the content of a folder in Box by its ID.
  
  Args:
    folder_id (str): The ID of the folder to list the content of.
    is_recursive (bool): Whether to list the content recursively.
  return:
  str: The content of the folder in a json string format, including the "id", "name", "type", and "description".
  """
  # fields = "id,name,type"
  return box_folder_list_content(client=client, folder_id=folder_id, is_recursive=is_recursive)

次に、ツールをSQLステートメントにマッピングするtools.yamlを作成しました。

sources:
  my-pg-source:
    kind: postgres
    host: 127.0.0.1
    port: 5432
    database: property_db
    user: property_manager
    password: property_manager
tools:
  get-property-by-address:
    kind: postgres-sql
    source: my-pg-source
    description: Search for a property based on the address.
    parameters:
    - name: address
      type: string
      description: The address of the property.
      statement: SELECT * FROM properties WHERE address = $1;
  get-title-history:
    kind: postgres-sql
    source: my-pg-source
    description: get the title history for a property.
    parameters:
    - name: address
      type: string
      description: The address of the property.
      statement: SELECT th.deed, th.deed_date, th.owner, th.mortgage, th.mortgage_date, th.mortgage_amount FROM properties p INNER JOIN title_history th ON p.id = th.property_id WHERE p.address = $1;
  get-liens:
    kind: postgres-sql
    source: my-pg-source
    description: >-
      Get liens for a property.
    parameters:
    - name: address
      type: string
      description: The address of the property.
      statement: SELECT l.lien, l.lien_amount, l.lien_date FROM properties p INNER JOIN liens l ON p.id = l.property_id WHERE p.address = $1;
  get-legal-actions:
    kind: postgres-sql
    source: my-pg-source
    description: >-
      Get legal actions for a property.
    parameters:
    - name: address
      type: string
      description: The address of the property.
      statement: SELECT la.case_description, la.case_date, la.case_parties FROM properties p INNER JOIN legal_actions la ON p.id = la.property_id WHERE p.address = $1;
  get-tax-history:
    kind: postgres-sql
    source: my-pg-source
    description: Get tax history for a property.
    parameters:
    - name: address
      type: string
      description: The address of the property.
      statement: SELECT t.bill, t.bill_date, t.bill_amount FROM properties p INNER JOIN taxes t ON p.id = t.property_id WHERE p.address = $1;
  get-easements:
    kind: postgres-sql
    source: my-pg-source
    description: Get easements for a property.
    parameters:
    - name: address
      type: string
      description: The address of the property.
      statement: SELECT e.easement, e.easement_date, e.easement_description FROM properties p INNER JOIN easements e ON p.id = e.property_id WHERE p.address = $1;

最後に、Toolboxを開始しました。このコマンドは、インストール方法によって異なる場合があります。今回は、curlを使用してインストールしたので、Toolboxはコードと同じディレクトリに存在します。そのため、シェルコマンドを使用してコマンドラインから開始しました。

./toolbox - tools_file "tools.yaml"

まとめると、必要な作業は、エージェントの作成、ツールの取得、プロンプトの収集とエージェントへの提供です。

from typing import List, Sequence, cast

from langgraph.prebuilt import create_react_agent
from langgraph.checkpoint.memory import MemorySaver
from langchain_openai import ChatOpenAI
from langchain_core.tools import BaseTool
from toolbox_langchain import ToolboxClient
from box_tools import (
  box_tool_file_get_by_id,
  box_tool_file_text_extract,
  box_tool_file_ai_ask,
  box_tool_file_ai_extract,
  box_tool_file_ai_extract_structured,
  box_tool_folder_text_representation,
  box_tool_folder_ai_ask,
  box_tool_folder_ai_extract,
  box_tool_folder_ai_extract_structured,
  box_tool_search,
  box_tool_locate_folder_by_name,
  box_tool_folder_list_content
)
prompt = """
  You're a helpful real estate researcher. You research properties and
  provide information about them. You get an address from a mortgage
  application and research the history of the property. You then prepare
  a property abstract report for the mortgage application.
"""
queries = [
  "Can you find the mortgage application in my 'mortgage documents' folder?",
  "Can you extract the name, address, and price of the property from the mortgage application?",
  "Can make sure the address is a property in our database?",
  "What is the title history for this property?",
  "What are the liens for this property?",
  "What are the legal actions for this property?",
  "What is the tax history for this property?",
  "What are the easements for this property?",
  """
    Please generate a property abstract report for this property.
    The report should include the following information:
    - Name of the property
    - Address of the property
    - Price of the property
    - Title history of the property
    - Lien history of the property
    - Legal actions of the property
    - Tax history of the property
    - Easements of the property
    The report should be in a format that is easy to read and understand.
  """,
]
def main():
  model = ChatOpenAI(model="gpt-4.5-preview")
  # Load the tools from the Toolbox server
  client = ToolboxClient("http://127.0.0.1:5000")
  toolbox_tools = client.load_toolset()
  box_tools = [
    box_tool_file_get_by_id,
    box_tool_file_text_extract,
    box_tool_file_ai_ask,
    box_tool_file_ai_extract,
    box_tool_file_ai_extract_structured,
    box_tool_folder_text_representation,
    box_tool_folder_ai_ask,
    box_tool_folder_ai_extract,
    box_tool_folder_ai_extract_structured,
    box_tool_search,
    box_tool_locate_folder_by_name,
    box_tool_folder_list_content
  ]
  tools: Sequence[BaseTool] = cast(List[BaseTool], toolbox_tools) + box_tools
  agent = create_react_agent(model, tools, checkpointer=MemorySaver())
  config = {"configurable": {"thread_id": "thread-1"}}
  for query in queries:
    print(f"{query}\n\n")
    inputs = {"messages": [("user", prompt + query)]}
    response = agent.invoke(inputs, stream_mode="values", config=config)
    print(f"{response["messages"][-1].content}\n\n")
if __name__ == "__main__":
  main()

このコード全体で最も難しかったのは、BoxエージェントツールとToolboxデータベースツールを組み合わせることでした。なぜなら、ToolboxではToolboxToolsの型付きリストが返されるのに対し、BoxツールはBaseToolの型付きリストの形式になっているためです。これらは、型をToolboxToolからBaseToolに変換することで組み合わせることができました。

tools: Sequence[BaseTool] = cast(List[BaseTool], toolbox_tools) + box_tools

以下に、完成した物件概要のサンプルを示します。

## Property Abstract Report

### Property Information
- **Name:** Albert Einstein
- **Address:** 123 Main St, Austin, TX 78701
- **Property Price:** $300,000
- **Property Type:** Residential
- **Square Footage:** 1,000 sq ft
- -
### Title History
- **Owner:** John Doe
- **Deed:** Deed 123456, recorded at the County Recorder's Office
- **Deed Date:** January 1, 2010
- **Mortgage:** Mortgage 789012, Bank of America
- **Mortgage Amount:** $100,000
- **Mortgage Date:** May 15, 2015
- -
### Lien History
- **Lien:** Lien 345678, filed by the City of Austin
- **Lien Amount:** $5,000
- **Lien Date:** January 1, 2020
- -
### Legal Actions
- **Case Number:** Case 901234, Travis County Court
- **Parties Involved:** John Doe vs. Jane Doe
- **Case Date:** March 15, 2022
- -
### Tax History
- **Tax Bill Number:** Tax Bill 567890, Travis County Tax Office
- **Amount:** $1,000
- **Date Issued:** January 1, 2023
- -
### Easements
- **Easement Record:** Easement 112233, Utility Company
- **Easement Date:** January 1, 2012
- **Description:** Utility lines easement for maintenance and infrastructure access

ぜひご意見をお聞かせください。

🦄 Box Platformの他のエキスパートと交流したい場合は、サポートや知識共有のためのBox Developer Community (英語のみ) にご参加ください。


RECENT POST「開発者」の最新記事


開発者

Box、MCPアプリのサポート対象をChatGPT、Microsoft 365 Copilot、Gleanに拡大

開発者

AIエージェントにコンテンツの活用方法を教える: OpenAI Codex向けBox Skillの構築

開発者

Box AIとOpenAI Agents SDKで自律的なドキュメントワークフローを実行

開発者

Box CLI: 開発者とAIエージェントのためのコンテンツCLI