RAG(Retrieval-Augmented Generation)やAIエージェントを開発する際、「リレーショナルDB(ユーザーデータ)」 と 「ベクトルDB(埋め込みデータ)」 の二重管理に疲れていませんか?
Edgeで動作するSQLite互換DBである Turso は、ネイティブでベクトル検索機能(Native Vector Search)をサポートしています。これにより、別途PineconeやQdrantを契約・管理することなく、Turso一本でアプリのデータ基盤を完結させることが可能です。
本記事では、TursoをベクトルDBとして使用する方法と、そのメリットについて解説します。
なぜTursoでベクトル検索なのか?
最大のメリットは 「SQLでメタデータフィルタリングができる」 点です。
通常のベクトルDBでは、日付やユーザーIDで検索範囲を絞り込む際に独自のJSON構文などを覚える必要がありますが、Tursoなら慣れ親しんだ WHERE 句や JOIN がそのまま使えます。
- アーキテクチャの簡素化: RDBとVector DBのデータ同期(Consistency)を考える必要がない。
- Edgeでの低レイテンシ: Tursoの強みであるEdge Replicaにより、ユーザーに近い場所で高速に検索が可能。
- コスト効率: 既存のTursoプラン内で利用でき、追加のインフラコストがかからない。
実装手順
Turso(libSQL)でのベクトル検索の実装は非常にシンプルです。今回はOpenAIの埋め込みモデル(text-embedding-3-smallなど)で一般的な 1536次元 のベクトルを扱う想定で進めます。
1. テーブルの作成
ベクトルを格納するカラムには F32_BLOB という型を使用します。引数には次元数を指定します。
CREATE TABLE documents (
id INTEGER PRIMARY KEY AUTOINCREMENT,
content TEXT NOT NULL,
user_id INTEGER NOT NULL,
created_at INTEGER DEFAULT (unixepoch()),
embedding F32_BLOB(1536)
);
2. ベクトルインデックスの作成
検索速度を向上させるため、ベクトルカラムに対してインデックスを作成します。これがないとフルスキャン(全件探索)になり、データ量が増えると遅くなります。
CREATE INDEX documents_idx ON documents (libsql_vector_idx(embedding));
-
libsql_vector_idx: ベクトルインデックスを作成するための内部関数です。
3. データの挿入(Insert)
データ挿入時は、アプリケーション側(TypeScriptやPythonなど)で生成したベクトル配列を、vector() 関数、あるいはバイナリとして渡します。
SQLでの例:
INSERT INTO documents (content, user_id, embedding)
VALUES (
'Tursoはエッジで動くSQLite互換のDBです。',
1,
vector('[0.012, -0.003, 0.45, ...]') -- 実際の1536次元の配列
);
4. ベクトル検索(RAGのRetrieve)
ここがTursoの真骨頂です。vector_top_k 関数を使用して類似検索を行い、その結果を元のテーブルと JOIN します。
SELECT
d.id,
d.content,
d.created_at,
-- 類似度スコア(コサイン類似度ではない場合は距離)
vector_distance_cos(d.embedding, vector('[0.015, ...]')) as distance
FROM vector_top_k('documents_idx', vector('[0.015, ...]'), 5) AS v
JOIN documents AS d ON d.id = v.id
WHERE d.user_id = 1; -- ★ここが重要
ポイント:
-
vector_top_k('インデックス名', クエリベクトル, 取得件数): 上位K件のIDを高速に返します。 -
JOIN documents ...: 取得したIDを元に、テキスト本文を取得します。 -
WHERE d.user_id = 1: SQLのWHERE句で検索範囲を特定のユーザーに限定しています。 これにより、「他人のデータが検索にヒットする」事故をSQLレベルで防げます。
TypeScript (LibSQL Client) での実装例
Node.js / TypeScript環境(@libsql/client)からは以下のように記述できます。
import { createClient } from "@libsql/client";
const client = createClient({
url: "libsql://your-db.turso.io",
authToken: "your-auth-token",
});
async function searchDocuments(queryVector: number[], userId: number) {
const result = await client.execute({
sql: `
SELECT d.content, d.created_at
FROM vector_top_k('documents_idx', vector(?), 5) AS v
JOIN documents AS d ON d.id = v.id
WHERE d.user_id = ?
`,
args: [JSON.stringify(queryVector), userId], // ベクトルは文字列化して渡す
});
return result.rows;
}
注意点
- ハイブリッド検索: キーワード検索(Full Text Search)とベクトル検索を組み合わせる専用の関数はまだありません。SQLを駆使してスコアを計算するか、アプリ側でマージする必要があります。
-
次元数:
F32_BLOBで指定した次元数と、挿入/検索するベクトルの次元数は必ず一致させる必要があります。
まとめ
Tursoを使うことで、「通常のデータ管理」と「AI用のベクトル検索」を単一のSQLiteデータベースで完結できます。
特に、「特定のユーザーのデータ内だけでRAGを行いたい」「最新のデータだけを検索対象にしたい」といったメタデータフィルタリングが必須の要件において、SQLの表現力は圧倒的な武器になります。
これからRAGアプリやAIエージェントを作るなら、構成をシンプルに保てるTursoは非常に有力な選択肢です。
参考: