はじめに
Databricksで公開されているマネージドMCPサービスに、Databricks SQLを実行するサービスが追加されました。
このMCPツールを使うことでエージェントからSQLの実行を可能にできるようになります。
記載の通り、本サービスは2025年10月時点でまだβ版です
とはいえ、通常はGenie MCPを使うことが推奨されているようです。
興味を持ちましたので、LangChainと組み合わせて簡単に動作確認をしてみます。
試験環境はDatabricks on AWS、ノートブッククラスタはサーバレスです。
検証準備
ノートブックを作成し、必要なパッケージをインストールします。
先日LangChainのv1.0が正式リリースされたこともあり、今回はLangChainをインストールして利用します。
%pip install --pre -U langchain==1.0.0 langchain-openai==1.0.0 mlflow
%pip install nest_asyncio databricks-mcp
%restart_python
DatabricksのMCPサービスをLangChainから利用するためのクラス・関数を定義します。
from typing import Any, Callable, List, Generator, TypedDict, cast
from contextlib import asynccontextmanager
import asyncio
from databricks_mcp import DatabricksOAuthClientProvider
from databricks.sdk import WorkspaceClient
from mcp.client.session import ClientSession
from mcp.client.streamable_http import streamablehttp_client
from mcp.types import (
CallToolResult,
EmbeddedResource,
ImageContent,
TextContent,
Tool as MCPTool,
)
from langchain_core.tools import BaseTool, ToolException, StructuredTool
import nest_asyncio
import asyncio
NonTextContent = ImageContent | EmbeddedResource
MAX_ITERATIONS = 1000
nest_asyncio.apply()
class DatabricksConnection(TypedDict):
"""Databricks MCPサーバーへの接続情報を保持する型定義
Attributes:
server_url (str): 接続先のDatabricks MCPエンドポイントURL
workspace_client (WorkspaceClient): Databricksワークスペースクライアント(認証用)
"""
server_url: str
workspace_client: WorkspaceClient
@asynccontextmanager
async def _databricks_mcp_session(connection: DatabricksConnection):
"""Databricks MCPサーバーへの非同期セッションを作成するコンテキストマネージャ
Args:
connection (DatabricksConnection): MCPサーバーへの接続情報
Yields:
ClientSession: 初期化済みのMCPクライアントセッション
"""
async with streamablehttp_client(
url=connection.get("server_url"),
auth=DatabricksOAuthClientProvider(connection.get("workspace_client")),
timeout=60,
) as (reader, writer, _):
async with ClientSession(reader, writer) as session:
await session.initialize()
yield session
async def _list_all_tools(session: ClientSession) -> list[MCPTool]:
"""MCPサーバーから全てのツール情報をページネーションで取得する
Args:
session (ClientSession): MCPクライアントセッション
Returns:
list[MCPTool]: 取得した全ツールのリスト
Raises:
RuntimeError: ページ数が上限を超えた場合
"""
current_cursor: str | None = None
all_tools: list[MCPTool] = []
iterations = 0
while True:
iterations += 1
if iterations > MAX_ITERATIONS:
raise RuntimeError(
f"Reached max of {MAX_ITERATIONS} iterations while listing tools."
)
list_tools_page_result = await session.list_tools(cursor=current_cursor)
if list_tools_page_result.tools:
all_tools.extend(list_tools_page_result.tools)
if list_tools_page_result.nextCursor is None:
break
current_cursor = list_tools_page_result.nextCursor
return all_tools
def _convert_call_tool_result(
call_tool_result: CallToolResult,
) -> tuple[str | list[str], list[NonTextContent] | None]:
"""MCPツール呼び出し結果をテキスト・非テキストに分割して返す
Args:
call_tool_result (CallToolResult): MCPツールの呼び出し結果
Returns:
tuple[str | list[str], list[NonTextContent] | None]: テキスト内容と非テキスト内容
Raises:
ToolException: エラーが発生した場合
"""
text_contents: list[TextContent] = []
non_text_contents = []
for content in call_tool_result.content:
if isinstance(content, TextContent):
text_contents.append(content)
else:
non_text_contents.append(content)
tool_content: str | list[str] = [content.text for content in text_contents]
if not text_contents:
tool_content = ""
elif len(text_contents) == 1:
tool_content = tool_content[0]
if call_tool_result.isError:
raise ToolException(tool_content)
return tool_content, non_text_contents or None
def _convert_mcp_tool_to_langchain_tool(
connection: DatabricksConnection,
tool: MCPTool,
) -> BaseTool:
"""MCPツール情報をLangChainのStructuredToolに変換する
Args:
connection (DatabricksConnection): MCPサーバー接続情報
tool (MCPTool): MCPツール情報
Returns:
BaseTool: LangChain互換のツール
"""
if connection is None:
raise ValueError("a connection config must be provided")
async def call_tool_async(
**arguments: dict[str, Any],
) -> tuple[str | list[str], list[NonTextContent] | None]:
async with _databricks_mcp_session(connection) as tool_session:
call_tool_result = await cast(ClientSession, tool_session).call_tool(
tool.name, arguments
)
return _convert_call_tool_result(call_tool_result)
def call_tool_sync(
**arguments: dict[str, Any]
) -> tuple[str | list[str], list[NonTextContent] | None]:
return asyncio.run(call_tool_async(**arguments))
return StructuredTool(
name=tool.name,
description=tool.description or "",
args_schema=tool.inputSchema,
coroutine=call_tool_async,
func=call_tool_sync,
response_format="content_and_artifact",
metadata=tool.annotations.model_dump() if tool.annotations else None,
)
def list_databricks_mcp_tools(
connections: list[DatabricksConnection],
) -> list[BaseTool]:
"""複数のMCPサーバーから全ツールを取得し、LangChainツールリストとして返す
Args:
connections (list[DatabricksConnection]): MCPサーバー接続情報のリスト
Returns:
list[BaseTool]: LangChain互換の全ツールリスト
"""
if type(connections) != list:
raise ValueError("connections must be a list of DatabricksConnection")
async def _load_databricks_mcp_tools(
connection: DatabricksConnection,
) -> list[BaseTool]:
if connection is None:
raise ValueError("connection config must be provided")
async with _databricks_mcp_session(connection) as tool_session:
tools = await _list_all_tools(tool_session)
converted_tools = [
_convert_mcp_tool_to_langchain_tool(connection, tool) for tool in tools
]
return converted_tools
async def gather():
tasks = [_load_databricks_mcp_tools(con) for con in connections]
return await asyncio.gather(*tasks)
# 結果をflat化してtoolの単一リストとして返す
return sum(asyncio.run(gather()), [])
次にLangChainのエージェントクラスを作成するための準備を行います。
まずは、今回の主題であるDatabricks DBSQL MCPをLangChainのツールとして利用するための準備をします。
# ワークスペース環境の取得
workspace_url = (
dbutils.notebook.entry_point.getDbutils().notebook().getContext().apiUrl().get()
)
w = WorkspaceClient()
# MCPサーバーの接続情報
dbsql_url = f"{workspace_url}/api/2.0/mcp/sql"
mcp_connection_dbsql = DatabricksConnection(server_url=dbsql_url, workspace_client=w)
# LangChain用のツール準備
tools = list_databricks_mcp_tools(connections=[mcp_connection_dbsql])
利用するLLMを準備します。
今回はdatabricks-gpt-oss-20bを利用することにします。
from langchain_openai import ChatOpenAI
import mlflow
mlflow.langchain.autolog()
creds = mlflow.utils.databricks_utils.get_databricks_host_creds()
llm = ChatOpenAI(
model="databricks-gpt-oss-20b",
api_key=creds.token,
base_url=creds.host + "/serving-endpoints",
)
最後に、AIエージェントを作成します。
from langchain.agents import create_agent
agent = create_agent(
model=llm,
tools=tools,
system_prompt="You are a helpful assistant",
)
これで準備完了です。
実行
では、動かしてみましょう。
サンプルデータを使って、テーブルの情報を取得してみます。
from pprint import pprint
response = agent.invoke(
{
"messages": [
{
"role": "user",
"content": "samples.bakehouseスキーマの中にあるテーブル情報を解説して",
},
]
}
)
pprint(response)
{'messages': [HumanMessage(content='samples.bakehouseスキーマの中にあるテーブル情報を解説して', additional_kwargs={}, response_metadata={}, id='41b2509c-41ae-457a-8b5e-216fd020c86f'),
AIMessage(content='', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 1737, 'prompt_tokens': 671, 'total_tokens': 2408, 'completion_tokens_details': None, 'prompt_tokens_details': None}, 'model_provider': 'openai', 'model_name': 'gpt-oss-20b-080525', 'system_fingerprint': None, 'id': 'chatcmpl_a84a5962-7390-43c5-9844-784b2ff100e8', 'finish_reason': 'tool_calls', 'logprobs': None}, id='lc_run--22084117-b89c-4714-bac4-5f671ce776fe-0', tool_calls=[{'name': 'execute_sql_read_only', 'args': {'query': 'SHOW TABLES IN samples.bakehouse'}, 'id': 'call_457b4197-9fe7-47ef-9215-6464de15c06f', 'type': 'tool_call'}], usage_metadata={'input_tokens': 671, 'output_tokens': 1737, 'total_tokens': 2408, 'input_token_details': {}, 'output_token_details': {}}),
ToolMessage(content='{"statement_id":"01f0acc9-bf49-1952-b59b-9249b0bc2983","status":{"state":"SUCCEEDED"},"manifest":{"format":"JSON_ARRAY","schema":{"column_count":3,"columns":[{"name":"database","type_text":"STRING","type_name":"STRING","position":0},{"name":"tableName","type_text":"STRING","type_name":"STRING","position":1},{"name":"isTemporary","type_text":"BOOLEAN","type_name":"BOOLEAN","position":2}]},"total_chunk_count":1,"chunks":[{"chunk_index":0,"row_offset":0,"row_count":6,"byte_count":800}],"total_row_count":6,"total_byte_count":800,"truncated":false},"result":{"chunk_index":0,"row_offset":0,"row_count":6,"data_array":[{"values":[{"string_value":"bakehouse"},{"string_value":"media_customer_reviews"},{"string_value":"false"}]},{"values":[{"string_value":"bakehouse"},{"string_value":"media_gold_reviews_chunked"},{"string_value":"false"}]},{"values":[{"string_value":"bakehouse"},{"string_value":"sales_customers"},{"string_value":"false"}]},{"values":[{"string_value":"bakehouse"},{"string_value":"sales_franchises"},{"string_value":"false"}]},{"values":[{"string_value":"bakehouse"},{"string_value":"sales_suppliers"},{"string_value":"false"}]},{"values":[{"string_value":"bakehouse"},{"string_value":"sales_transactions"},{"string_value":"false"}]}]},"deprecated_session_id":"01f0acc9-bf44-1bba-9d24-227e0dcd2654"}', name='execute_sql_read_only', id='443f3fa6-7766-4c5c-a482-70910661f94f', tool_call_id='call_457b4197-9fe7-47ef-9215-6464de15c06f'),
AIMessage(content='', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 4383, 'prompt_tokens': 1067, 'total_tokens': 5450, 'completion_tokens_details': None, 'prompt_tokens_details': None}, 'model_provider': 'openai', 'model_name': 'gpt-oss-20b-080525', 'system_fingerprint': None, 'id': 'chatcmpl_68aa3b64-3db7-4d7c-b760-7761a77f8e3b', 'finish_reason': 'tool_calls', 'logprobs': None}, id='lc_run--492bf9b2-2247-4ad6-9349-1a1f9a6c68ef-0', tool_calls=[{'name': 'execute_sql_read_only', 'args': {'query': 'DESCRIBE TABLE samples.bakehouse.media_customer_reviews'}, 'id': 'call_4ff62c62-596f-4b11-a55c-8915998b2eb8', 'type': 'tool_call'}], usage_metadata={'input_tokens': 1067, 'output_tokens': 4383, 'total_tokens': 5450, 'input_token_details': {}, 'output_token_details': {}}),
ToolMessage(content='{"statement_id":"01f0acc9-cb1b-105f-b3ab-742d1d09732b","status":{"state":"SUCCEEDED"},"manifest":{"format":"JSON_ARRAY","schema":{"column_count":3,"columns":[{"name":"col_name","type_text":"STRING","type_name":"STRING","position":0},{"name":"data_type","type_text":"STRING","type_name":"STRING","position":1},{"name":"comment","type_text":"STRING","type_name":"STRING","position":2}]},"total_chunk_count":1,"chunks":[{"chunk_index":0,"row_offset":0,"row_count":4,"byte_count":728}],"total_row_count":4,"total_byte_count":728,"truncated":false},"result":{"chunk_index":0,"row_offset":0,"row_count":4,"data_array":[{"values":[{"string_value":"review"},{"string_value":"string"},{"null_value":"NULL_VALUE"}]},{"values":[{"string_value":"franchiseID"},{"string_value":"bigint"},{"null_value":"NULL_VALUE"}]},{"values":[{"string_value":"review_date"},{"string_value":"timestamp"},{"null_value":"NULL_VALUE"}]},{"values":[{"string_value":"new_id"},{"string_value":"int"},{"null_value":"NULL_VALUE"}]}]},"deprecated_session_id":"01f0acc9-cb15-19bc-a754-88dd31e6f9b1"}', name='execute_sql_read_only', id='c7477217-14d5-4bbd-ac17-730a4be0ad09', tool_call_id='call_4ff62c62-596f-4b11-a55c-8915998b2eb8'),
AIMessage(content='', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 3452, 'prompt_tokens': 1409, 'total_tokens': 4861, 'completion_tokens_details': None, 'prompt_tokens_details': None}, 'model_provider': 'openai', 'model_name': 'gpt-oss-20b-080525', 'system_fingerprint': None, 'id': 'chatcmpl_510b9fce-1592-42b4-97cc-350502406114', 'finish_reason': 'tool_calls', 'logprobs': None}, id='lc_run--3fb0b1d8-192f-491c-8a4b-e292cd189562-0', tool_calls=[{'name': 'execute_sql_read_only', 'args': {'query': 'DESCRIBE TABLE samples.bakehouse.media_gold_reviews_chunked'}, 'id': 'call_13d24c9b-91d2-4cec-8ef8-0fac744e0494', 'type': 'tool_call'}], usage_metadata={'input_tokens': 1409, 'output_tokens': 3452, 'total_tokens': 4861, 'input_token_details': {}, 'output_token_details': {}}),
ToolMessage(content='{"statement_id":"01f0acc9-d5c2-1c47-a0fc-4fee533c802d","status":{"state":"SUCCEEDED"},"manifest":{"format":"JSON_ARRAY","schema":{"column_count":3,"columns":[{"name":"col_name","type_text":"STRING","type_name":"STRING","position":0},{"name":"data_type","type_text":"STRING","type_name":"STRING","position":1},{"name":"comment","type_text":"STRING","type_name":"STRING","position":2}]},"total_chunk_count":1,"chunks":[{"chunk_index":0,"row_offset":0,"row_count":5,"byte_count":752}],"total_row_count":5,"total_byte_count":752,"truncated":false},"result":{"chunk_index":0,"row_offset":0,"row_count":5,"data_array":[{"values":[{"string_value":"franchiseID"},{"string_value":"int"},{"null_value":"NULL_VALUE"}]},{"values":[{"string_value":"review_date"},{"string_value":"timestamp"},{"null_value":"NULL_VALUE"}]},{"values":[{"string_value":"chunked_text"},{"string_value":"string"},{"null_value":"NULL_VALUE"}]},{"values":[{"string_value":"chunk_id"},{"string_value":"string"},{"null_value":"NULL_VALUE"}]},{"values":[{"string_value":"review_uri"},{"string_value":"string"},{"null_value":"NULL_VALUE"}]}]},"deprecated_session_id":"01f0acc9-d5ba-1ae2-b3b3-af192b2c4776"}', name='execute_sql_read_only', id='e6bd044a-f78d-421b-88b2-0271b6141e94', tool_call_id='call_13d24c9b-91d2-4cec-8ef8-0fac744e0494'),
AIMessage(content='', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 4281, 'prompt_tokens': 1776, 'total_tokens': 6057, 'completion_tokens_details': None, 'prompt_tokens_details': None}, 'model_provider': 'openai', 'model_name': 'gpt-oss-20b-080525', 'system_fingerprint': None, 'id': 'chatcmpl_9f78e48c-1b70-41a3-a3f2-9c911f546979', 'finish_reason': 'tool_calls', 'logprobs': None}, id='lc_run--be453c97-58ba-40b8-8cd7-519be1e93c49-0', tool_calls=[{'name': 'execute_sql_read_only', 'args': {'query': 'DESCRIBE TABLE samples.bakehouse.sales_customers'}, 'id': 'call_749d93e2-1b3c-476c-90c5-fd8cf85ed624', 'type': 'tool_call'}], usage_metadata={'input_tokens': 1776, 'output_tokens': 4281, 'total_tokens': 6057, 'input_token_details': {}, 'output_token_details': {}}),
ToolMessage(content='{"statement_id":"01f0acc9-e179-1875-82e6-cfa8ade7d853","status":{"state":"SUCCEEDED"},"manifest":{"format":"JSON_ARRAY","schema":{"column_count":3,"columns":[{"name":"col_name","type_text":"STRING","type_name":"STRING","position":0},{"name":"data_type","type_text":"STRING","type_name":"STRING","position":1},{"name":"comment","type_text":"STRING","type_name":"STRING","position":2}]},"total_chunk_count":1,"chunks":[{"chunk_index":0,"row_offset":0,"row_count":12,"byte_count":944}],"total_row_count":12,"total_byte_count":944,"truncated":false},"result":{"chunk_index":0,"row_offset":0,"row_count":12,"data_array":[{"values":[{"string_value":"customerID"},{"string_value":"bigint"},{"null_value":"NULL_VALUE"}]},{"values":[{"string_value":"first_name"},{"string_value":"string"},{"null_value":"NULL_VALUE"}]},{"values":[{"string_value":"last_name"},{"string_value":"string"},{"null_value":"NULL_VALUE"}]},{"values":[{"string_value":"email_address"},{"string_value":"string"},{"null_value":"NULL_VALUE"}]},{"values":[{"string_value":"phone_number"},{"string_value":"string"},{"null_value":"NULL_VALUE"}]},{"values":[{"string_value":"address"},{"string_value":"string"},{"null_value":"NULL_VALUE"}]},{"values":[{"string_value":"city"},{"string_value":"string"},{"null_value":"NULL_VALUE"}]},{"values":[{"string_value":"state"},{"string_value":"string"},{"null_value":"NULL_VALUE"}]},{"values":[{"string_value":"country"},{"string_value":"string"},{"null_value":"NULL_VALUE"}]},{"values":[{"string_value":"continent"},{"string_value":"string"},{"null_value":"NULL_VALUE"}]},{"values":[{"string_value":"postal_zip_code"},{"string_value":"bigint"},{"null_value":"NULL_VALUE"}]},{"values":[{"string_value":"gender"},{"string_value":"string"},{"null_value":"NULL_VALUE"}]}]},"deprecated_session_id":"01f0acc9-e174-1a93-9d4e-58f8afedaf0d"}', name='execute_sql_read_only', id='02e25af9-60e3-4ed8-94f4-d072110b7874', tool_call_id='call_749d93e2-1b3c-476c-90c5-fd8cf85ed624'),
AIMessage(content='', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 1951, 'prompt_tokens': 2290, 'total_tokens': 4241, 'completion_tokens_details': None, 'prompt_tokens_details': None}, 'model_provider': 'openai', 'model_name': 'gpt-oss-20b-080525', 'system_fingerprint': None, 'id': 'chatcmpl_eac46cc1-38e4-4390-a48b-248a7c91d336', 'finish_reason': 'tool_calls', 'logprobs': None}, id='lc_run--b7ca055d-0820-49c1-bac7-1bafed52d6a4-0', tool_calls=[{'name': 'execute_sql_read_only', 'args': {'query': 'DESCRIBE TABLE samples.bakehouse.sales_franchises'}, 'id': 'call_dcf9f86b-8fa0-415e-b8fa-e6ee6456168c', 'type': 'tool_call'}], usage_metadata={'input_tokens': 2290, 'output_tokens': 1951, 'total_tokens': 4241, 'input_token_details': {}, 'output_token_details': {}}),
ToolMessage(content='{"statement_id":"01f0acc9-e6f0-16a7-a25a-2151b0cd14de","status":{"state":"SUCCEEDED"},"manifest":{"format":"JSON_ARRAY","schema":{"column_count":3,"columns":[{"name":"col_name","type_text":"STRING","type_name":"STRING","position":0},{"name":"data_type","type_text":"STRING","type_name":"STRING","position":1},{"name":"comment","type_text":"STRING","type_name":"STRING","position":2}]},"total_chunk_count":1,"chunks":[{"chunk_index":0,"row_offset":0,"row_count":10,"byte_count":872}],"total_row_count":10,"total_byte_count":872,"truncated":false},"result":{"chunk_index":0,"row_offset":0,"row_count":10,"data_array":[{"values":[{"string_value":"franchiseID"},{"string_value":"bigint"},{"null_value":"NULL_VALUE"}]},{"values":[{"string_value":"name"},{"string_value":"string"},{"null_value":"NULL_VALUE"}]},{"values":[{"string_value":"city"},{"string_value":"string"},{"null_value":"NULL_VALUE"}]},{"values":[{"string_value":"district"},{"string_value":"string"},{"null_value":"NULL_VALUE"}]},{"values":[{"string_value":"zipcode"},{"string_value":"string"},{"null_value":"NULL_VALUE"}]},{"values":[{"string_value":"country"},{"string_value":"string"},{"null_value":"NULL_VALUE"}]},{"values":[{"string_value":"size"},{"string_value":"string"},{"null_value":"NULL_VALUE"}]},{"values":[{"string_value":"longitude"},{"string_value":"double"},{"null_value":"NULL_VALUE"}]},{"values":[{"string_value":"latitude"},{"string_value":"double"},{"null_value":"NULL_VALUE"}]},{"values":[{"string_value":"supplierID"},{"string_value":"bigint"},{"null_value":"NULL_VALUE"}]}]},"deprecated_session_id":"01f0acc9-e6ea-174d-a6ee-7cd60b39b5fd"}', name='execute_sql_read_only', id='4d571536-6450-4667-995a-aba68b4d0f62', tool_call_id='call_dcf9f86b-8fa0-415e-b8fa-e6ee6456168c'),
AIMessage(content='## `samples.bakehouse` スキーマにあるテーブル一覧と簡単な説明\n\n| テーブル名 | カラム | データ型 | コメント | 主な用途 |\n|------------|--------|----------|----------|----------|\n| **media_customer_reviews** | review | string | - | 顧客が投稿したレビュー本文。 |\n| | franchiseID | bigint | - | レビューが投稿されたフランチャイズ(店舗)ID。 |\n| | review_date | timestamp | - | レビューが投稿された日時。 |\n| | new_id | int | - | 何らかの内部識別子。保持している情報は不明。 |\n| **media_gold_reviews_chunked** | franchiseID | int | - | レビュー対象フランチャイズ(店舗)ID。 |\n| | review_date | timestamp | - | レビューが投稿された日時。 |\n| | chunked_text | string | - | レビュー本文を分割したチャンク(小分け)本文。 |\n| | chunk_id | string | - | どのチャンクであるかを識別する文字列。 |\n| | review_uri | string | - | もしくは外部に保存されたレビュー本文へのURI。 |\n| **sales_customers** | customerID | bigint | - | 顧客の一意 ID。 |\n| | first_name | string | - | 名。 |\n| | last_name | string | - | 姓。 |\n| | email_address | string | - | メールアドレス。 |\n| | phone_number | string | - | 電話番号。 |\n| | address | string | - | 住所(市区町村含む)。 |\n| | city | string | - | 都市。 |\n| | state | string | - | 州/都道府県。 |\n| | country | string | - | 国名。 |\n| | continent | string | - | 大陸。 |\n| | postal_zip_code | bigint | - | 郵便番号。 |\n| | gender | string | - | 性別。 |\n| **sales_franchises** | franchiseID | bigint | - | フランチャイズ(店舗)ID。 |\n| | name | string | - | 店舗名。 |\n| | city | string | - | 店舗の都市。 |\n| | district | string | - | 郡/区等。 |\n| | zipcode | string | - | 郵便番号。 |\n| | country | string | - | 国。 |\n| | size | string | - | 店舗規模(例:大型/小型)。 |\n| | longitude | double | - | 地理座標(経度)。 |\n| | latitude | double | - | 地理座標(緯度)。 |\n| | supplierID | bigint | - | 設備・原料供給業者の ID。 |\n| **sales_suppliers** | supplierID | bigint | - | 供給業者の一意 ID。 |\n| | supplier_name | string | - | 供給業者の名称。 |\n| | address | string | - | 供給業者の住所。 |\n| | city | string | - | 供給業者の都市。 |\n| | state | string | - | 供給業者の州。 |\n| | country | string | - | 供給業者の国。 |\n| | phone | string | - | 供給業者の電話番号。 |\n| | email | string | - | 供給業者のE‑mail。 |\n| | contact_name | string | - | 連絡担当者名。 |\n| | contact_title | string | - | 連絡担当者役職。 |\n| | fax | string | - | ファックス番号。 |\n| | credit_limit | decimal(10,2) | - | 与信限度額。 |\n| **sales_transactions** | transactionID | bigint | - | 取引一意 ID。 |\n| | customerID | bigint | - | 取引を行った顧客 ID。 |\n| | franchiseID | bigint | - | 取引が発生したフランチャイズ ID。 |\n| | supplierID | bigint | - | 取引に関わった供給業者 ID。 |\n| | transactionDate | timestamp | - | 取引日時。 |\n| | total | decimal(12,2) | - | 取引金額。 |\n| | tax | decimal(12,2) | - | 税額。 |\n| | discount | decimal(12,2) | - | 割引額。 |\n| | shipping | decimal(12,2) | - | 送料。 |\n| | payment_method | string | - | 支払い手段。 |\n| | status | string | - | 取引の状態(例:完了、保留)。 |\n| | notes | string | - | 取引に関する備考。 |\n\n---\n\n### 主な利用イメージ\n\n| テーブル | 想定される分析・用途 |\n|----------|---------------------|\n| **media_customer_reviews** | 顧客レビューを集計し、フランチャイズ別の評価を算出。 |\n| **media_gold_reviews_chunked** | レビュー本文をチャンク化して、テキスト解析(キーワード抽出・感情分析)に使う。 |\n| **sales_customers** | 顧客プロファイル(属性別)を作成し、ターゲットマーケティングに利用。 |\n| **sales_franchises** | 店舗の位置情報を GIS で可視化し、店舗配置最適化や市場調査に活用。 |\n| **sales_suppliers** | 供給業者との取引履歴や信用管理、供給リスク評価に使える。 |\n| **sales_transactions** | 売上・売上構成を時系列で追跡し、売上予測や利益率分析に利用。 |\n\n---\n\n## 使い方のヒント\n\n1. **結合** \n - `sales_transactions` と `sales_customers` を `customerID` で結合すれば、顧客ごとの購入履歴が取得できます。 \n - `sales_transactions` と `sales_franchises` を結合すれば、各店舗ごとの売上集計が可能です。 \n - `media_customer_reviews` の `franchiseID` と `sales_franchises` で結合すると、レビュー数と売上を比較できます。\n\n2. **時系列分析** \n - `review_date` や `transactionDate` を DATE/TIMESTAMP で抽出して週・月単位で集計し、季節性を検証します。\n\n3. **テキスト解析** \n - `chunked_text` を使ってテキストマイニング(TF-IDF、Word2Vecなど)を実施し、顧客満足度や商品改善点を抽出します。\n\n4. **地理情報の閲覧** \n - `sales_franchises` の `longitude/latitude` を使ってマップ可視化(Spotfire, Power BI, Tableau, or Tableau, or Mapbox)で店舗分布を確認します。\n\n---\n\n### まとめ\n\n`samples.bakehouse` スキーマは、**レビュー情報**(顧客の声)と**販売/店舗/供給業者情報**を結合した BI・データサイエンスのサンプルデータセットです。 \n- **レビュー** → 感情・テキスト解析 \n- **顧客** → 1:1顧客プロファイリング \n- **フランチャイズ** → 地理・店舗特性分析 \n- **供給業者** → 供給チェーン監視 \n- **取引** → 売上・利益・傾向把握 \n\nこれらのテーブルを組み合わせれば、実際の業務シナリオを想定した分析が可能です。 \n\nご質問や、具体的なクエリ例、分析手順の詳細などが必要な場合はお知らせください!', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 1821, 'prompt_tokens': 2760, 'total_tokens': 4581, 'completion_tokens_details': None, 'prompt_tokens_details': None}, 'model_provider': 'openai', 'model_name': 'gpt-oss-20b-080525', 'system_fingerprint': None, 'id': 'chatcmpl_b1e9ca6a-a501-4549-a791-bae7ef1f290f', 'finish_reason': 'stop', 'logprobs': None}, id='lc_run--f0f52467-ec0f-49ba-aa1b-428738595e9d-0', usage_metadata={'input_tokens': 2760, 'output_tokens': 1821, 'total_tokens': 4581, 'input_token_details': {}, 'output_token_details': {}})]}
MLflow Tracingだと以下のように記録されます。
処理としてはDBSQL MCPを使ってSHOW TABLESが実行され、その結果に基づいてDESCRIBE TABLEを実行して最終出力を作成してくれました。Genie MCPでも同様のことはできますが、Genieスペースに登録していないカタログ・スキーマについてはこっちの方が手軽かもしれません。
また、MLflowのTrace情報を見ていくと、現時点ではexecute_sql、execute_sql_read_only、poll_sql_resultの3つがツールとして提供されているようです。
もう1件、別の指示を実行させてみます。
response = agent.invoke(
{
"messages": [
{
"role": "user",
"content": "samples.bakehouse.sales_customersのデータ件数を教えて",
},
],
}
)
pprint(response)
{'messages': [HumanMessage(content='samples.bakehouse.sales_customersのデータ件数を教えて', additional_kwargs={}, response_metadata={}, id='3c06f396-5ada-4113-a4b3-291dba3e4779'),
AIMessage(content='', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 315, 'prompt_tokens': 668, 'total_tokens': 983, 'completion_tokens_details': None, 'prompt_tokens_details': None}, 'model_provider': 'openai', 'model_name': 'gpt-oss-20b-080525', 'system_fingerprint': None, 'id': 'chatcmpl_1618bfde-00de-4ceb-b015-27ae8ae906e0', 'finish_reason': 'tool_calls', 'logprobs': None}, id='lc_run--bf7c8a92-9f40-4e25-97a9-3016ab396194-0', tool_calls=[{'name': 'execute_sql_read_only', 'args': {'query': 'SELECT COUNT(*) AS row_count FROM samples.bakehouse.sales_customers'}, 'id': 'call_d572dbc0-0bb4-49b4-b0b9-a461a10c5099', 'type': 'tool_call'}], usage_metadata={'input_tokens': 668, 'output_tokens': 315, 'total_tokens': 983, 'input_token_details': {}, 'output_token_details': {}}),
ToolMessage(content='{"statement_id":"01f0acca-cb22-1e89-8269-7fd5b4e6159b","status":{"state":"SUCCEEDED"},"manifest":{"format":"JSON_ARRAY","schema":{"column_count":1,"columns":[{"name":"row_count","type_text":"BIGINT","type_name":"LONG","position":0}]},"total_chunk_count":1,"chunks":[{"chunk_index":0,"row_offset":0,"row_count":1,"byte_count":328}],"total_row_count":1,"total_byte_count":328,"truncated":false},"result":{"chunk_index":0,"row_offset":0,"row_count":1,"data_array":[{"values":[{"string_value":"300"}]}]},"deprecated_session_id":"01f0acca-cb1b-1d16-8505-a5e4891caf73"}', name='execute_sql_read_only', id='6411aa6b-106b-46a9-8021-86a61192e34f', tool_call_id='call_d572dbc0-0bb4-49b4-b0b9-a461a10c5099'),
AIMessage(content='The `samples.bakehouse.sales_customers` table contains **300 rows**.', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 45, 'prompt_tokens': 898, 'total_tokens': 943, 'completion_tokens_details': None, 'prompt_tokens_details': None}, 'model_provider': 'openai', 'model_name': 'gpt-oss-20b-080525', 'system_fingerprint': None, 'id': 'chatcmpl_26a67952-98db-4979-a8ad-35a83e09ffba', 'finish_reason': 'stop', 'logprobs': None}, id='lc_run--329d4957-a303-48d3-9b36-f3b1af7aa3fa-0', usage_metadata={'input_tokens': 898, 'output_tokens': 45, 'total_tokens': 943, 'input_token_details': {}, 'output_token_details': {}})]}
SELECT句によるクエリも実行されますね。
今回はクエリなど読み込みのみの処理を実行してみましたが、INSERTなども対応していますので書き込み・更新系の処理に利用するのもよさそうです。(制御は気を付けないといけませんが)
おわりに
DatabricksのDBSQL MCPサービスを使ってみました。
2025年10月19日現在、まだPlaygroundからは利用できないようですが、やがて使えるようになるのではないかなと思います。
クエリ関連はGenieを使う方がほとんどのケースで良さそうですが、更新系はこちらのサービスを利用することで幅広く様々なことができそうです。(Lakebaseへの対応は確認できておらず。まだっぽい?)
こういった使い勝手の良いツールがマネージドで公開されていくのはありがたいですね。




