自戒、点検、内省

終わらない反省会をしよう

"知恵袋"をデータエージェントの域に進めた

chiebukuro-mcp は、いろんな SQLite の DB をまとめて Claude に読ませるための個人用 MCP サーバーです。名前の通り「知恵袋」です。手元のデータを溜めておいて、聞かれたら出す。それだけのつもりで書き始めました。

retrospective.hatenadiary.com

config に SQLiteのDB パスを書く。chiebukuro_query_<db> ツールで SELECT を投げる。結果が JSON で返る。おしまい。

これで十分便利でした。ブログの過去記事も、ロケーション履歴も、Things のタスクも、全部同じ API で読める。地味に効きます。

ただ、せっかくここまで作ったのだから、もう一歩進めたくなりました。いわゆるデータエージェントと呼べるものに育てられないか、という方向です。

データエージェントの3要件

そもそも「データエージェント」とは何か。わたしの現時点の整理はこうです。

受動的にデータを返すだけでは足りません。

棚ではなくエージェントと呼べるものには、次の3つが要ると思います。

  1. 道具(Tools) — データを読む手段を持っていること
  2. 知識(Knowledge) — 自分が何者で、どう使えるかを自己記述できること
  3. 対話(Dialogue) — 足りない情報を、能動的に呼び出し側に問い返せること

1 でも結構なツールですが、2 が乗るとデータ側から「こう使って」と言ってきます。3 が乗ると、データ側が会話の主導権を少し取りにきます。この順で厚みが増していく、というのが現時点の見立てです。

使い方の変化

ユーザーとしてのわたし自身から見た使い方は変わりません。何を知りたいかは同じ。

違うのは、そこから先の「どう検索するか、どう推論するか」の段取りを誰が持っているか、という点です。

エージェント化以前は、段取りは全部 Claude 側でした。Claude が schema を読んで、どのテーブルのどのカラムを使うか決めて、source パターンは ruby/ruby:trunk/% だろうと推測して、日付範囲はどのあたりが妥当かをわたしに会話で聞き返して、SELECT を自分で組み立てる。検索と推論の筋道そのものが Claude の自由演技でした。

最近のLLMの性能ならばもちろんそれなり以上に動きますが、ただ、動きの特性から、毎回違う解釈での振る舞いになるので、つまりブレが大きくなります。

そこが、chiebukuro-mcp に備えた段取りで進んでいきますので、解釈違うで逸脱しにくい、ブレにくいというわけです。

実装

以下、chiebukuro-mcp でそれぞれを実際にどう実装したかを書きます。

道具(Tools)

土台です。SELECT を安全に投げる QueryTool、vec0 KNN を叩く SemanticSearchTool、実行計画を返す ExplainQueryTool。読み取り専用原則は死守して、INSERT/UPDATE/DELETE/DROP は ArgumentError で弾きます。

まずここは初期からのものです。

知識(Knowledge)

次に足したのが、DB が自分自身を説明するリソース群です。

リソース URI 中身
SchemaResource schema://<db> テーブル定義と description
RecipesResource recipes://<db> 典型クエリのテンプレ集
HintsResource hints://<db> カラムの enum 候補・サンプル値・関連テーブル

これらは DB 内の _sqlite_mcp_meta という拡張メタテーブルから引き出されます。DB 自身が「わたしはこう聞かれることが多い」「このカラムには大体こういう値が入っている」と自己紹介する仕組みです。

Claude は最初にこのリソースを読んで、その DB の輪郭を掴んでからクエリを組み立てます。

対話(Dialogue)

一番新しく入ったのが、MCPのelicitation を使った対話ツールです。

modelcontextprotocol.io

現時点では GitHub - modelcontextprotocol/ruby-sdk: The official Ruby SDK for the Model Context Protocol. · GitHubにはelicitationはまだ載っていなさそうだったので、forkして手元で追加してみてます。

QueryWithClarificationTool という長い名前のクラスが入口で、曖昧な自然言語要求を受けると、まず IntentAnalyzer で intent を解析して、不足しているフィールドを検出します。

不足があると、ClarificationFormBuilder が _sqlite_mcp_meta の clarification_field 行から入力フォームの JSON Schema を動的に組み立てて、MCP elicitation でユーザーに問い返します。返ってきた値を SqlTemplateEngine が recipe テンプレートに差し込んで SELECT を実行する、という流れです。

エージェント化して何がかわったのか

先ほど書いた通り、ユーザーとしてはうまくいくときには変わらない、ブレにくいから安定するかもという期待ぐらいです。

作り手としては、データ提供から、知識と対話が乗ると、なんというか、データ側がユーザーを迎えにくる感覚が出てきました。ユーザーが全部を組み立てる必要はなく、DB 側が自己紹介して、足りないところは聞き返してくれる。

この「迎えにくる」感じが、わたしにとってのエージェントらしさです。受動的な棚ではなく、多少の自主性を持って対応する何か。知恵袋の概念が少し広がってさらに話せるようになったというところです。