Python GUIの配布地獄からGo製AI搭載TUIへ:ターミナルSQLクライアント「asql」開発記

目次

はじめに

「ちょっとSQLiteの中身を見たいだけなのに、なぜこんなに面倒なんだろう」

そんな素朴な不満から始まったプロジェクトが、Python製GUIツール → Go製TUI → AI搭載ターミナルSQLクライアントへと変貌を遂げました。この記事では、asql の開発ストーリーを振り返りながら、技術選定の判断やAI統合で直面したトラブルについて紹介します。

SQLiteViewの限界とGUIからの脱却

もともとは Python(PyQt6)製のGUIツール「SQLiteView」としてスタートしました。SQLiteファイルの中身をサッと確認できるビューアです。

しかし、使い込むうちに「閲覧だけでなく編集もしたい」という欲求が生まれ、機能追加を検討した段階で根本的な課題に直面しました。

Python製アプリの配布問題です。

ユーザーに uvpip の環境構築を強いることになり、OSごとの依存解決も面倒です。「ちょっとSQLiteを見たいだけ」のツールとしては導入のハードルが高すぎました。

Go言語への転換

この課題を解消するために、Go言語でのフルリライト を決断しました。

判断の決め手は以下の点です。

  • シングルバイナリで配布可能: Windows / macOS / Linux を問わず、バイナリを1つ渡すだけで動く
  • 開発スタイルとの親和性: WSL、Docker、Mac を行き来する環境でもビルドが一貫している
  • CGO不要のSQLiteドライバが存在する: modernc.org/sqlite がPure Go実装を提供しており、クロスコンパイルが容易

配布の問題を技術選定で根本から解消するアプローチです。

あえて「TUI」を選ぶ

Go でリライトするなら GUI フレームワーク(Fyne、Gio など)を使う選択肢もありました。しかし、あえて TUI(Terminal User Interface) を選びました。

理由はシンプルです。

  • ターミナルから出ずに完結する、Vimライクな操作感 が欲しかった
  • SSH越しのサーバー上でもローカルと同じように使えるポータビリティ
  • エンジニアのワークフローに自然に溶け込むツールを目指した

Bubble Tea の採用

TUIフレームワークには Bubble Tea を採用しました。Charm社が開発している Go のTUIフレームワークで、The Elm Architecture に基づいた Model-Update-View パターンで状態管理を行います。

Bubble Tea のエコシステムには、入力フォーム用の Bubbles、スタイリング用の Lip Gloss が揃っており、見た目の良いTUIを効率的に構築できます。

AI Text-to-SQL の統合

「普通のSQLクライアント」で終わらせない

ターミナルSQLクライアントには lazysqlharlequin といった強力な先行者がいます。後発で勝負するには明確な差別化が必要でした。

そこで選んだのが、ローカルLLM(Ollama / LM Studio)と連携した自然言語によるSQL生成 です。

アーキテクチャ

AIクライアントは OpenAI互換API として実装しました。これにより、Ollama と LM Studio の両方に同じインターフェースで対応できます。

# ~/.config/asql/config.yaml
ai:
  ai_endpoint: http://localhost:11434/v1   # Ollama
  ai_model: llama3
  ai_api_key: ""                           # Ollama では不要

ポイントは、DBのスキーマ情報(CREATE TABLE ...)をシステムプロンプトに動的に注入している点です。AIにDB構造を理解させることで、実在するテーブル名・カラム名を使った正確なSQLを生成できます。

You are a SQL assistant for a mysql database.
Generate only the SQL query, no explanation.
Schema:
CREATE TABLE users (
  id INT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(255),
  email VARCHAR(255)
);
CREATE TABLE orders (
  id INT PRIMARY KEY AUTO_INCREMENT,
  user_id INT,
  amount DECIMAL(10,2),
  ...
);

ユーザーが Ctrl+K を押して「先月の売上トップ10のユーザーを教えて」と入力すれば、このスキーマを踏まえたSQLが生成されます。データは外部に送信されず、すべてローカルで完結します。

ハマったポイント

AI統合は華やかに見えますが、実際にはモデル特有の挙動に苦しめられました。ここが開発で最も学びの多かったパートです。

全件結合(直積)の悲劇

軽量モデル(sqlcoder-7b-2 等)に「テーブル一覧を見せて」と頼んだところ、メタデータを返すのではなく、全テーブルを WHERE TRUE で結合する CROSS JOIN クエリ を生成しました。

意図しない直積(Cartesian Product)が発生し、アプリがフリーズ。メモリ爆発の寸前でした。

これは「テーブル一覧」という曖昧な指示を、モデルが「全テーブルのデータを見せる」と解釈した結果です。

対策として、役割分担を明確にしました。

  • メタデータ表示(テーブル一覧、カラム情報)→ Go 側のコードで実装
  • 分析クエリ生成(集計、結合、フィルタ)→ AI に担当させる

プロンプトにも「sqlite_master を参照して構造情報を取得せよ」と明記するようにしました。AIにすべてを任せるのではなく、得意な領域だけを担当させるのが安定運用のコツです。

カラム名の曖昧さ(Ambiguous column name)

departmentsemployees の両方に name カラムがある状態で結合クエリを生成させると、AIが横着して SELECT name ... と出力し、SQLの実行時エラーが発生しました。

-- AI が生成した問題のあるクエリ
SELECT name, department_name
FROM employees JOIN departments ON ...
-- → ERROR: ambiguous column name: name

対策は2つです。

  1. システムプロンプトに「JOIN時は必ず table.column の形式で書くこと」というルールを明記
  2. モデルを指示追従性能の高い Qwen2.5-Coder シリーズに変更

特にモデルの変更は効果が劇的でした。軽量な sqlcoder 系モデルはSQLの生成自体は得意ですが、プロンプトの指示に従う能力(instruction following)が弱い傾向があります。Qwen2.5-Coder に切り替えたことで、テーブル名の修飾を含む正確なクエリが安定して生成されるようになりました。

配布パイプラインの構築

GoReleaser + GitHub Actions

Go で書いたからにはシングルバイナリ配布のメリットを最大限に活かしたいところです。GoReleaser と GitHub Actions を組み合わせ、タグを打つだけで全OS向けのバイナリがリリースされるパイプライン を構築しました。

git tag v0.5.0
git push origin v0.5.0
# → GitHub Actions が起動し、Windows/macOS/Linux 向けバイナリを自動ビルド・リリース

Python 時代に悩まされた「ユーザーに環境構築を強いる」問題は、これで完全に解消されました。

リネーム:sqly → asql

もともとのプロジェクト名「sqly」は、既に同名のツール(nao1215/sqly)が存在していました。CSV/TSV/JSON を SQL で操作できるツールで、Terminal Trove にも掲載されています。

同じ名前では検索で完全に埋もれてしまうため、asql(AI + SQL)にリネームしました。短く、意図が伝わり、タイプしやすい名前です。

Multi-DB 対応(MySQL / PostgreSQL)

SQLite だけでは実用性に限界があるため、MySQL と PostgreSQL への対応も追加しました。

Go の database/sql パッケージの抽象化を活かし、DBAdapter インターフェースでDB実装を分離しています。

# ファイルパス → SQLite
asql ./mydata.db
# DSN → 自動判定
asql "mysql://root:pass@127.0.0.1:3306/testdb"
asql "postgres://user:pass@localhost:5432/mydb"

CLI引数の DSN から DB 種別を自動判定するため、設定ファイルなしで接続できます。AI の Text-to-SQL も DB 種別に応じて MySQL 構文 / PostgreSQL 構文を正しく生成します。

まとめ

asql の開発を振り返ると、以下の判断がプロジェクトの方向性を決定づけました。

  • 配布問題は技術選定で解決する: Python の配布地獄に悩むなら、Go のシングルバイナリという根本解決がある
  • TUI はエンジニア向けツールの正解の一つ: ターミナルから出ない開発フローには、GUI より TUI の方が自然に溶け込む
  • AI には得意な領域だけ任せる: メタデータ取得はコードで、分析クエリ生成はAIで。全部任せると直積の悲劇が起きる
  • モデル選定はプロンプト設計と同じくらい重要: instruction following が弱いモデルでは、いくらプロンプトを工夫しても限界がある
  • 名前の衝突は早期に解消する: 検索での埋没は認知獲得の致命的なボトルネック

asql は GitHub で公開しています。ローカルLLMでSQLを自然言語生成できるターミナルクライアントに興味のある方は、ぜひ試してみてください。

このプロジェクトもバイブコーディングで作成しております。作るのが速いなぁと感じます。

参考リンク

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次