AI+SQLでSlackなどを操作できるMindsDBのアーキテクチャを読み解く

に公開

MindsDBというSQLでいろんなSaaS(SlackやGithubなど)からデータを抽出できるプロジェクトをご存知でしょうか?

https://github.com/mindsdb/mindsdb

SQL(Structured Query Language)はデータベースに対して「このデータを取得して」「このデータを更新して」と指示を出すための言語ではあるものの、Slack APIを叩くためのものではありません。しかし、MindsDBは、Slackだけでなく、GitHub、OpenAI、PostgreSQLなど200以上のサービスを、すべて同じSQL構文で扱えるというのです。

いったいどうやってこんな芸当を実現しているのでしょうか。私はソースコードを追いかけながら、その仕組みを解き明かしてみることにしました。

この記事を読むと何ができるようになるか

本記事を読むと、読者の皆さんは以下の3つのことができます。

1つ目は、MindsDBの内部構造を理解した上で、自分のプロジェクトに導入するかどうかを判断できることです。「SQLで外部APIを操作できる」という表面的な理解だけでなく、内部でどのような処理が行われているかを知ることで、パフォーマンス特性や制約を予測できます。

2つ目は、MindsDBを使う際にトラブルシューティングがしやすいことです。たとえば「なぜこのクエリが遅いのか」「なぜこのJOINがうまくいかないのか」といった問題に直面したとき、内部アーキテクチャを知っていれば原因を特定しやすくなります。

3つ目は、同様のアーキテクチャを自分で設計する際の参考になることです。複数のデータソースを統一的に扱うシステムを作りたい場合、MindsDBの設計パターンは非常に参考になります。

MindsDBとは何か?

https://www.youtube.com/watch?v=MX3OKpnsoLM&themeRefresh=1

MindsDBは、AIモデルや外部サービスをデータベースのテーブルのように扱えるオープンソースプロジェクトです。たとえば、次のようなSQLでSlackのメッセージを取得できます。

SELECT * FROM mindsdb_slack.messages
WHERE channel_id = 'C123'
LIMIT 10;

このSQLを見ると、まるでSlackのメッセージがどこかのデータベースに保存されているかのように見えます。しかし実際には、このSQLが実行されるたびにMindsDBがSlack APIを呼び出して、リアルタイムでデータを取得しています。いわば「見せかけのテーブル」、つまり仮想テーブルという仕組みです。仮想テーブルとは、実際にはデータを保持していないものの、あたかも通常のテーブルのようにSQLでアクセスできる仕組みを指します。

さらに面白いのは、PostgreSQLのデータとSlackのメッセージをJOIN(結合)したり、AIモデルで感情分析したりすることも、すべてSQLの構文で記述できる点です。データベースとAPIとAIが、同じ言語で操作できる。これはなかなか面白いコンセプトではないでしょうか。

実際の業務でどう使えるか:3つのシナリオ

では、MindsDBの仕組みを知ることで、具体的にどんな業務に活用できるのでしょうか。職種別に3つのシナリオを紹介します。

営業チームがSlackで交わしている顧客対応の会話を、CRMデータベースの顧客情報と組み合わせて分析したいとします。従来であれば、エンジニアに依頼してSlack APIとデータベースそれぞれに接続するコードを書いてもらい、データを取得し、プログラム上で結合する必要がありました。MindsDBを使えば、営業担当者自身が以下のようなSQLを書くだけで分析できます。

-- Slackでの顧客対応履歴とCRM情報を結合
SELECT
    s.text AS 会話内容,
    c.company_name AS 会社名,
    c.deal_size AS 案件規模,
    c.stage AS 商談ステージ
FROM slack.messages s
JOIN crm_db.customers c ON s.mentioned_company = c.company_name
WHERE s.channel_id = 'C営業チャンネル'
AND s.created_at > '2024-01-01';

カスタマーサポートチームが、顧客からの問い合わせ内容をAIで自動分類し、緊急度の高いものを優先対応したいとします。MindsDBを使えば、問い合わせデータに対してAIモデルをJOINするだけで、リアルタイムに分類結果を取得できます。

-- 問い合わせをAIで緊急度分類
SELECT
    t.ticket_id,
    t.content AS 問い合わせ内容,
    p.urgency AS 緊急度,
    p.category AS カテゴリ
FROM zendesk.tickets t
JOIN mindsdb.urgency_classifier p ON t.content = p.input_text
WHERE p.urgency = 'HIGH'
ORDER BY t.created_at DESC;

データエンジニアが、複数のSaaSサービスからデータを収集してデータウェアハウスに集約するETL(Extract, Transform, Load)パイプラインを構築したいとします。従来は各サービスのAPIコネクタを個別に実装する必要がありましたが、MindsDBを使えば、SQLベースで統一的にデータ収集ができます。

-- 複数SaaSからデータを収集してデータウェアハウスに投入
INSERT INTO data_warehouse.daily_metrics
SELECT
    'slack' AS source,
    COUNT(*) AS message_count,
    CURRENT_DATE AS date
FROM slack.messages
WHERE created_at >= CURRENT_DATE
UNION ALL
SELECT
    'github' AS source,
    COUNT(*) AS commit_count,
    CURRENT_DATE AS date
FROM github.commits
WHERE created_at >= CURRENT_DATE;

全体像:料理のレシピのように考える

MindsDBの内部処理は、料理に例えるとわかりやすいかもしれません。

  1. 注文を受け付ける(ユーザーがSQLクエリを送信)
  2. レシピを読み解く(パーサーがSQLを抽象構文木に変換)
  3. 調理手順を決める(クエリプランナーが実行ステップを生成)
  4. 実際に調理する(エグゼキュータが各ステップを実行し、外部APIやデータベースを呼び出す)
  5. 盛り付けて提供(結果をまとめてユーザーに返却)

それでは、実際のコードを追いながら、この流れを一緒に見ていきましょう。

エントリーポイント:main.py

MindsDBはpython -m mindsdbで起動します。__main__.pyがエントリーポイント(プログラムの実行開始地点)となり、複数のAPIプロセスを立ち上げます。

# mindsdb/__main__.py より抜粋
class TrunkProcessEnum(Enum):
    HTTP = "http"
    MYSQL = "mysql"
    JOBS = "jobs"
    TASKS = "tasks"
    ML_TASK_QUEUE = "ml_task_queue"
    LITELLM = "litellm"

ここで注目したいのは、HTTP APIとMySQL APIという2つの入口が用意されている点です。HTTP APIはREST(Representational State Transfer)形式、つまりWebブラウザやcurlコマンドからHTTPリクエストを送る形式でクエリを受け付けます。一方、MySQL APIはMySQLプロトコル(MySQLクライアントとサーバー間の通信規約)を喋るため、既存のMySQLクライアントからそのまま接続できます。

実務で役立つポイント:既存ツールがそのまま使える

この設計が実務で意味するのは、普段使っているMySQLのツールがそのまま使えるということです。たとえば以下のようなツールやライブラリから、MindsDBに接続できます。

  • MySQL Workbench(GUIでSQLを実行)
  • DBeaver(複数データベース対応のGUIツール)
  • Pythonのmysql-connector-pythonライブラリ
  • PHPのmysqli拡張
  • TableauなどのBIツール

実際の業務では、すでにMySQLを使ったBIツールやダッシュボードが稼働していることが多いでしょう。MindsDBはその接続先を変えるだけで、SlackやOpenAIのデータも同じツールから扱えるようになります。つまり、新しいツールの使い方を覚える必要がないのです。

すぐに試せる手順として、まずMindsDBをDockerで起動します。docker run -p 47334:47334 mindsdb/mindsdbを実行してください。次に、普段使っているMySQLクライアントでlocalhost:47334に接続します。最後にSHOW DATABASES;を実行して、MindsDBが応答することを確認してみてください。

SQLクエリの受信

HTTP APIでSQLクエリを受け取る部分を見てみましょう。

# mindsdb/api/http/namespaces/sql.py より
def post(self):
    query = request.json["query"]  # SQLクエリ文字列を取得

    # FakeMysqlProxyを使ってクエリを処理
    mysql_proxy = FakeMysqlProxy()
    result: SQLAnswer = mysql_proxy.process_query(query)
    query_response: dict = result.dump_http_response()

ここで私が「おっ」と思ったのは、FakeMysqlProxyという名前です。HTTP APIなのに、なぜMySQLプロキシ(仲介役)という名前なのでしょうか。

答えは、コードの再利用にあります。MindsDBの開発者は、HTTPでもMySQLでも内部的には同じコードパスを通るように設計しました。つまり、MindsDBの開発チームは「SQLを処理するロジック」を1か所に集約し、そのロジックをHTTP APIからもMySQL APIからも呼び出せるようにしたのです。これは「一度書いたロジックを何度も使い回す」というプログラミングの基本原則が、アーキテクチャレベルで徹底されている良い例だと感じました。

Executorでの実行

mysql_proxy.process_query()の中身を追っていくと、Executorクラスに行き着きます。

# mindsdb/api/executor/mysql_executor.py より
def query_execute(self, sql, params=None):
    self.parse(sql)  # SQL文字列をASTにパース
    if params:
        self.apply_parameters(params)
    self.do_execute()  # 実際の実行

def parse(self, sql):
    self.query = parse_sql(sql)  # mindsdb_sql_parserでパース

def do_execute(self):
    executor_answer: ExecuteAnswer = self.command_executor.execute_command(self.query)

SQLパーサーはmindsdb_sql_parserという別パッケージを使っています。このパーサーがSQLの文字列を受け取り、AST(Abstract Syntax Tree、抽象構文木)に変換します。

ASTとは何か:なぜ文字列を木構造に変換するのか

ASTというのは、SQLの構造を木構造で表現したものです。たとえば SELECT name FROM users WHERE age > 20 というSQLは、以下のような木構造に変換されます。

SELECT文
├── 取得する列: name
├── テーブル: users
└── 条件: age > 20

この変換が重要な理由は、構造化されたデータのほうがプログラムで処理しやすいからです。文字列のままだと「SELECT」という単語がどこにあるか、「FROM」の後に何があるかを毎回文字列検索で探す必要があります。しかしASTに変換すれば「このノードの子ノードを見ればテーブル名がわかる」というように、決まった場所にアクセスするだけで済みます。

ASTの構造を活用した例は以下のとおりです。

  • query.targets → SELECT句の列(name)
  • query.from_table → FROM句のテーブル(users)
  • query.where → WHERE句の条件(age > 20)
  • query.limit → LIMIT句
  • query.order_by → ORDER BY句

MindsDBでは、このように文字列検索ではなく、オブジェクトの属性として直接アクセスできます。

コマンド振り分け

ExecuteCommandsクラスがSQLの種類に応じて処理を振り分けます。

# mindsdb/api/executor/command_executor.py より
def execute_command(self, statement: ASTNode, database_name: str = None) -> ExecuteAnswer:
    statement_type = type(statement)

    if statement_type is CreateDatabase:
        return self.answer_create_database(statement)
    elif statement_type is Select:
        query = SQLQuery(statement, session=self.session, database=database_name)
        return self.answer_select(query)
    # ... その他のコマンドタイプ ...

これは電話の自動音声案内のようなものです。「SELECT文の方は1番を、CREATE文の方は2番を...」という具合に、SQLの種類によって処理を振り分けています。SELECT文の場合はSQLQueryクラスに処理が委譲されます。

この振り分け処理を「ディスパッチャー」と呼びます。MindsDBはこのディスパッチャーパターンを使って、新しいSQLコマンドへの対応を追加しやすくしています。新しいコマンドタイプをサポートしたい場合は、このif文に条件を追加し、対応する処理メソッドを実装するだけです。

クエリプランナー:ルールベースでの実行計画

SQLQueryクラスの中では、QueryPlanner(クエリプランナー)が実行ステップを生成します。

# mindsdb/api/executor/sql_query/sql_query.py より
class SQLQuery:
    def execute_query(self):
        steps = list(self.planner.execute_steps())  # プランナーがステップを生成

        for step in steps:  # 各ステップを順次実行
            step_result = self.execute_step(step)
            self.steps_data[step.step_num] = step_result

        self.fetched_data = step_result  # 最終結果を保存

クエリプランナーは「どのデータソースから何を取得するか」「どう結合するか」「どのAIモデルで予測するか」といった実行計画を立て、その計画を順番に実行していきます。LLMは使わずにルールベースで計画を立てています。

具体例:複数データソースを横断するクエリの実行計画

たとえば「SlackのメッセージをPostgreSQLのユーザー情報とJOINして、AIモデルで感情分析する」というクエリがあったとしましょう。クエリプランナーは以下のような手順を自動的に組み立てます。

ステップ 処理内容 呼び出されるハンドラー
1 Slackからメッセージデータを取得 SlackHandler
2 PostgreSQLからユーザー情報を取得 PostgresHandler
3 両者のデータをメモリ上で結合 内部JOINエンジン
4 結合結果をAIモデルに渡して感情分析 OpenAIHandler
5 最終結果をユーザーに返却 -

この「実行計画を立てる」という概念は、PostgreSQLやMySQLなどの一般的なデータベースにも存在します。EXPLAINコマンドでクエリの実行計画を確認したことがある方なら、イメージしやすいのではないでしょうか。

トラブルシューティングに役立つ知識

クエリプランナーの存在を知っていると、パフォーマンス問題のトラブルシューティングがしやすくなります。たとえば「このクエリが遅い」という問題に直面したとき、以下のような切り分けができます。

  • ステップ1(Slack API呼び出し)が遅い → Slack側のレート制限に引っかかっている可能性
  • ステップ3(JOIN処理)が遅い → データ量が多すぎてメモリ上での結合に時間がかかっている可能性
  • ステップ4(AIモデル呼び出し)が遅い → OpenAI APIの応答が遅い、またはデータ量が多すぎる可能性

外部サービス統合の仕組み:216個のレゴブロック

ここからが私が特に興味を惹かれた部分です。MindsDBは216個ものハンドラー(サービス統合コンポーネント)を持っています。Slack、GitHub、Notion、PostgreSQL、MySQL、OpenAI、Anthropic...。これだけの数をどうやって管理しているのでしょうか。

答えは「共通フレームワーク」にあります。レゴブロックのように、すべてのハンドラーが同じ規格に従っているのです。

# mindsdb/integrations/libs/api_handler.py より
class APIHandler(BaseHandler):
    """
    Base class for handlers associated to the applications APIs
    (e.g. twitter, slack, discord etc.)
    """
    def __init__(self, name: str):
        super().__init__(name)
        self._tables = {}

    def _register_table(self, table_name: str, table_class: Any):
        """APIリソースをテーブルとして登録"""
        self._tables[table_name.lower()] = table_class

    def query(self, query: ASTNode):
        if isinstance(query, Select):
            result = self._get_table(query.from_table).select(query)
        elif isinstance(query, Insert):
            result = self._get_table(query.table).insert(query)
        # ...

各サービスのハンドラーは、このAPIHandlerを継承して、自分専用のテーブルを登録するだけで済みます。新しいサービスを追加したい開発者は、ゼロから作る必要はなく、この「型」に従ってテーブルを定義すればいいわけです。

設計パターンとしての学び

これは「テンプレートメソッドパターン」と呼ばれるデザインパターンの一種です。基底クラス(親クラス)で処理の大枠を定義し、具体的な実装は派生クラス(子クラス)に任せます。この設計には以下のメリットがあります。

メリット 具体的な効果
コントリビューションのハードルが下がる 既存のハンドラーを参考にしながら、決められたインターフェースに従って実装するだけで新しいサービス統合を追加できる
コードの一貫性が保たれる すべてのハンドラーが同じパターンに従うため、1つ理解すれば他も理解しやすい
テストが書きやすい 共通のインターフェースに対してテストを書けるため、新しいハンドラーでも既存のテストを流用できる

自分がライブラリやフレームワークを設計する際にも、この「共通インターフェースを定義して、具体的な実装は派生クラスに任せる」というパターンは非常に参考になります。

OpenAPI仕様からの自動生成

さらに興味深いのは、OpenAPI仕様からハンドラーを自動生成できる機能です。

# mindsdb/integrations/libs/api_handler_generator.py より
class APIResourceGenerator:
    """
    A class to generate API resources based on the OpenAPI specification.
    """
    def __init__(self, url, connection_data, url_base=None, options=None) -> None:
        self.openapi_spec_parser = OpenAPISpecParser(url)
        self.connection_data = connection_data
        self.resources = {}

    def generate_api_resources(self, handler, table_name_format='{url}'):
        paths = self.openapi_spec_parser.get_paths()
        schemas = self.openapi_spec_parser.get_schemas()
        endpoints = self.process_endpoints(paths)

        for endpoint in endpoints:
            table_name = table_name_format.format(url=url, method=endpoint.method)
            self.resources[table_name] = RestApiTable(handler, endpoint=endpoint)

        return self.resources

OpenAPI仕様とは、RESTful APIを記述するための標準フォーマットです。YAMLまたはJSONで記述され、APIのエンドポイント、パラメータ、レスポンスの形式などを定義します。多くのAPIプロバイダーがこの仕様を公開しています。たとえばGitHub(https://api.github.com/openapi.json)やStripe、Twitterなどが該当します。

MindsDBでは、このOpenAPI仕様のURLを指定するだけで、各エンドポイントが自動的にテーブルとして生成されます。たとえば、/users/{username} というエンドポイントは users_x というテーブルになり、パラメータ username はWHERE句で使用できるようになります。

# OpenAPI仕様を解析してエンドポイント情報を抽出
def get_paths(self) -> dict:
    return self.openapi_spec.get('paths', {})

def get_schemas(self) -> dict:
    return self.openapi_spec.get('components', {}).get('schemas', {})

def get_security_schemes(self) -> dict:
    return self.openapi_spec.get('components', {}).get('securitySchemes', {})

これにより、OpenAPI仕様を提供しているAPIであれば、手動でハンドラーを実装しなくても、MindsDBに統合できます。216個以上のサービス統合を実現できている背景には、手動実装だけでなく、このような自動生成の仕組みも貢献しているのです。

Slackハンドラーの例

具体的に、Slackの統合がどのように実装されているか見てみましょう。

# mindsdb/integrations/handlers/slack_handler/slack_handler.py より
class SlackHandler(APIChatHandler):
    def __init__(self, name: Text, connection_data: Dict, **kwargs: Any) -> None:
        super().__init__(name)
        self.connection_data = connection_data

        # テーブルを登録
        self._register_table('conversations', SlackConversationsTable(self))
        self._register_table('messages', SlackMessagesTable(self))
        self._register_table('threads', SlackThreadsTable(self))
        self._register_table('users', SlackUsersTable(self))

    def connect(self) -> WebClient:
        """Slack APIへの接続"""
        self.web_connection = WebClient(token=self.connection_data['token'])
        return self.web_connection

_register_table()メソッドで4つのテーブル(conversations、messages、threads、users)を登録しています。たったこれだけで、SELECT * FROM slack.messagesのようなSQLが書けるようになります。

ここで気づくのは、Slackの概念(チャンネル、メッセージ、スレッド、ユーザー)が、そのままテーブルとして表現されている点です。SQLを知っている人なら、Slack APIのドキュメントを読まなくても、直感的にデータにアクセスできるわけです。

すぐに試せるSlack統合のセットアップ

MindsDBでSlackを使い始める手順は以下の通りです。

まず、Slack APIにアクセスして「Create New App」をクリックします。必要な権限(channels:history, channels:read など)を追加し、「OAuth & Permissions」からBot User OAuth Tokenをコピーしておきます。

次に、MindsDBでデータベースを作成します。

CREATE DATABASE mindsdb_slack
WITH ENGINE = 'slack',
PARAMETERS = {
  'token': 'xoxb-your-token-here'
};

これでセットアップは完了です。あとはデータを取得するだけです。

-- チャンネル一覧を確認
SELECT * FROM mindsdb_slack.conversations;

-- 特定チャンネルのメッセージを取得
SELECT * FROM mindsdb_slack.messages
WHERE channel_id = 'C0123456789'
LIMIT 10;

仮想テーブルの正体

SlackMessagesTableの実装を見ると、SELECT文がどのようにSlack APIの呼び出しに変換されるかがわかります。

# mindsdb/integrations/handlers/slack_handler/slack_tables.py より(簡略化)
def list(self, conditions: List[FilterCondition] = None, limit: int = None, ...):
    client = self.handler.connect()
    params = {}

    # WHERE句の条件をAPIパラメータに変換
    for condition in conditions:
        if condition.column == "channel_id":
            params["channel"] = condition.value
        elif condition.column == "created_at":
            # 日時条件をタイムスタンプに変換
            date = dt.datetime.fromisoformat(condition.value)
            params["oldest"] = date.timestamp()

    # Slack APIを呼び出し
    response = client.conversations_history(**params)
    messages = response["messages"]

    # DataFrameに変換して返却
    return pd.DataFrame(messages)

これが「仮想テーブル」の正体です。データはMindsDBのどこにも保存されていません。SQLクエリが実行されるたびにMindsDBがSlack APIを呼び出して、最新データを取得しているのです。

最後の行で登場するpd.DataFrameは、Pythonのpandasライブラリが提供するデータ構造で、表形式のデータを扱うためのものです。ExcelのシートやSQLのテーブルのように、行と列からなるデータを効率的に処理できます。Slack APIから返ってきたJSONデータをDataFrameに変換することで、MindsDBの内部で統一的にデータを扱えるようになっています。

SQLからAPI呼び出しへの変換フロー

つまり、処理の流れは以下のようになります。

  1. ユーザーが SELECT * FROM slack.messages WHERE channel_id = 'C123' を実行する
  2. MindsDBのパーサーがSQLを解析し、SlackMessagesTableのlist()メソッドを呼び出す
  3. list()メソッド内で、WHERE句の条件(channel_id = 'C123')がSlack APIのパラメータ(channel: 'C123')に変換される
  4. Slack APIのconversations_history(channel='C123')が呼び出される
  5. 結果がDataFrameに変換されてMindsDBに返される
  6. MindsDBがDataFrameをSQLの結果形式に整形してユーザーに返す

SQLという「見慣れた顔」の裏で、実際にはAPI呼び出しが行われている。このギャップこそが、MindsDBが「SQLで何でも操作できる」ように見せている仕組みの核心です。

なぜSQLなのか?

ここまで読んで、「外部APIを呼ぶだけなら、SQLを使う必要はないのでは?」という疑問が浮かんだ方もいるかもしれません。正直なところ、私も最初はそう思いました。

でも、コードを追いかけているうちに、SQLを採用する理由が見えてきました。3つの観点から説明します。

理由1:統一されたインターフェース

Slack、PostgreSQL、OpenAI、すべて同じSQL構文で操作できます。

-- Slack
SELECT * FROM slack.messages WHERE channel_id = 'C123';

-- PostgreSQL
SELECT * FROM postgres_db.users WHERE age > 25;

-- 同じ構文で両方にアクセス

新しいサービスを使うたびに、そのAPIの仕様を覚える必要がありません。学習コストをかなり抑えられます。

たとえば、Slack APIではconversations.historyエンドポイントにパラメータchannelを渡す必要がありますが、GitHub APIでは/repos/{owner}/{repo}/issuesエンドポイントにパスパラメータを渡します。さらにOpenAI APIでは/v1/chat/completionsエンドポイントにJSONボディを送る必要があります。MindsDBを使えば、このような違いを意識せずに済みます。

理由2:複数データソースの結合

これがSQLの真骨頂ではないでしょうか。

-- SlackのメッセージとPostgreSQLのユーザー情報を結合
SELECT s.text, u.name, u.email
FROM slack.messages s
JOIN postgres_db.users u ON s.user = u.slack_user_id
WHERE s.channel_id = 'C123';

Slackのメッセージを送った人の詳細情報を、社内データベースから引っ張ってきて一緒に表示する。こういうことが、たった数行のSQLで書けてしまうわけです。

従来であれば、以下のようなコードを書く必要がありました。

# 従来のアプローチ(イメージ)
slack_messages = slack_client.conversations_history(channel='C123')
user_ids = [m['user'] for m in slack_messages]
users = db.execute("SELECT * FROM users WHERE slack_user_id IN (%s)", user_ids)

# 手動でJOINロジックを実装
result = []
user_dict = {u['slack_user_id']: u for u in users}
for msg in slack_messages:
    user = user_dict.get(msg['user'], {})
    result.append({
        'text': msg['text'],
        'name': user.get('name'),
        'email': user.get('email')
    })

MindsDBを使えば、このような「データを取得して、別のデータを取得して、マッチングする」という面倒なコードを書く必要がなくなります。

理由3:AIモデルとの統合

MindsDBでは、AIモデルもテーブルとして扱えます。ただし、使用する前にまず CREATE MODEL 文でモデルを作成する必要があります。

-- まずモデルを作成(OpenAIを使った感情分析モデルの例)
CREATE MODEL mindsdb.sentiment_model
PREDICT sentiment
USING
    engine = 'openai',
    prompt_template = 'Analyze the sentiment of this text and respond with only one word - positive, negative, or neutral: {{text}}';

モデルを作成したら、JOINでデータを渡すだけで予測結果が得られます。

-- Slackメッセージの感情分析
SELECT m.text, p.sentiment
FROM slack.messages m
JOIN mindsdb.sentiment_model p ON m.text = p.text
WHERE m.channel_id = 'C123';

たとえば、カスタマーサポートチームが使っているダッシュボードがあるとします。そこに「顧客からのメッセージの感情スコア」という列を追加したい場合を考えてみましょう。

従来のアプローチでは、まずPythonで感情分析のコードを書き、次に定期的にバッチ処理を回す仕組みを構築し、分析結果をデータベースに書き込み、最後にダッシュボードからそのテーブルを参照するという手順が必要でした。

MindsDBを使えば、モデルを一度作成しておけば、ダッシュボードのSQLクエリを以下のように書き換えるだけで、リアルタイムに感情分析結果を表示できます。

SELECT
    t.ticket_id,
    t.customer_message,
    t.created_at,
    s.sentiment
FROM support_tickets t
JOIN mindsdb.sentiment_model s ON t.customer_message = s.text
ORDER BY t.created_at DESC;

自然言語エージェント機能

とはいえ、SQLを書くのが面倒という声もあるでしょう。MindsDBは自然言語エージェント機能も提供しています。

# エージェントに自然言語で質問
agent.invoke("Slackのメッセージを10件取得して")
# → エージェントが自動的にSQLを生成して実行

この機能では、内部でLLM(大規模言語モデル)が自然言語をSQLに変換しています。ユーザーは「Slackの最新メッセージを見せて」と言うだけで、MindsDBがそれを SELECT * FROM slack.messages ORDER BY created_at DESC LIMIT 10 のようなSQLに変換して実行してくれます。

SQLと自然言語の使い分け

ただし、SQLには明確性という強みがあります。「最近のメッセージを10件取得して」という自然言語だと、「最近」がどのくらいの期間を指すのか曖昧です。1時間前なのか、1日前なのか、1週間前なのか。SQLなら created_at > '2024-01-01' と明示できます。

用途 推奨する方法 理由
探索的なデータ確認 自然言語 素早く結果を確認したいときに便利
本番運用のクエリ SQL 明確で再現性があり、バージョン管理もしやすい
複雑な結合クエリ SQL JOINの条件を正確に指定できる
非エンジニアの利用 自然言語 SQLを知らなくてもデータにアクセスできる

私の感覚としては、探索的にデータを眺めたいときは自然言語が便利で、本番運用や複雑な結合クエリにはSQLが向いているように思います。両方使えるというのは、状況に応じて使い分けられるという意味で、良い選択肢ではないでしょうか。

SQLという馴染みのある言語で、データベースもAPIもAIモデルも統一的に扱えるというのは、考えていた以上に実用的なコンセプトだと感じました。複数のデータソースを横断して分析したい場合や、既存のBIツールからAIモデルを呼び出したい場合に、このアプローチは役立ちそうです。

参考リンク


About me

現在、市場調査やデスクリサーチの生成AIエージェントを作っています 仲間探し中 / Founder of AI Desk Research Agent @deskrex , https://deskrex.ai

ぜひお気軽にチャットしましょう!
https://x.com/ItaruTomita9779/status/1856471446614356395

お仕事のご相談は以下まで、AIエージェントの開発や研修、調査代行やビジネスコンサルなどの対応も可能です。

生成AIデスクリサーチサービス Deskrex | サービスページ

https://lp.deskrex.ai/

生成AIデスクリサーチエージェント Deskrex App | アプリケーションサイト

https://app.deskrex.ai/

DeskrexAIリサーチ | メディア

https://media.deskrex.ai/

株式会社Deskrex | 会社概要

https://www.deskrex.ai/

Deskrex | Xページ

https://x.com/deskrex

Deskrex テックブログ

Discussion