0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

DatabricksのSQL MCPサービスを試してみる

Posted at

はじめに

Databricksで公開されているマネージドMCPサービスに、Databricks SQLを実行するサービスが追加されました。
このMCPツールを使うことでエージェントからSQLの実行を可能にできるようになります。

image.png

記載の通り、本サービスは2025年10月時点でまだβ版です

とはいえ、通常はGenie MCPを使うことが推奨されているようです。

image.png

興味を持ちましたので、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だと以下のように記録されます。

image.png

処理としてはDBSQL MCPを使ってSHOW TABLESが実行され、その結果に基づいてDESCRIBE TABLEを実行して最終出力を作成してくれました。Genie MCPでも同様のことはできますが、Genieスペースに登録していないカタログ・スキーマについてはこっちの方が手軽かもしれません。

また、MLflowのTrace情報を見ていくと、現時点ではexecute_sqlexecute_sql_read_onlypoll_sql_resultの3つがツールとして提供されているようです。

image.png

もう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': {}})]}

image.png

SELECT句によるクエリも実行されますね。

今回はクエリなど読み込みのみの処理を実行してみましたが、INSERTなども対応していますので書き込み・更新系の処理に利用するのもよさそうです。(制御は気を付けないといけませんが)

おわりに

DatabricksのDBSQL MCPサービスを使ってみました。
2025年10月19日現在、まだPlaygroundからは利用できないようですが、やがて使えるようになるのではないかなと思います。

クエリ関連はGenieを使う方がほとんどのケースで良さそうですが、更新系はこちらのサービスを利用することで幅広く様々なことができそうです。(Lakebaseへの対応は確認できておらず。まだっぽい?)

こういった使い勝手の良いツールがマネージドで公開されていくのはありがたいですね。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?