Turso + Drizzle で Vector Search
Turso (libsql) にはベクトル検索がデフォルトで利用できます.Drizzleからこれを利用してみます.
参考リンク
https://docs.turso.tech/sdk/ts/orm/drizzle を参考にDrizzleをセットアップします.Bunを使います.
bun add drizzle-orm @libsql/client
bun add -D drizzle-kit
※ Bunを使っているのでdotenv
は不要です.
package.json
にスクリプトを追加します.
{
"scripts": {
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:studio": "drizzle-kit studio"
}
}
document-search
という名前でデータベースを作成します.
turso db create document-search
データベースのURLを確認します.
turso db show --url document-search
データベースに接続するための認証トークンを取得します.
turso db tokens create document-search
忘れないうちに.env
に書いておきます.
TURSO_DATABASE_URL=
TURSO_AUTH_TOKEN=
データベースに接続します.
import { drizzle } from "drizzle-orm/libsql";
import { createClient } from "@libsql/client";
const turso = createClient({
url: process.env.TURSO_DATABASE_URL!,
authToken: process.env.TURSO_AUTH_TOKEN!,
});
export const db = drizzle(turso);
drizzle.config.ts
を作成します.
require("dotenv").config();
import type { Config } from "drizzle-kit";
export default {
schema: "./db/schema.ts",
out: "./migrations",
dialect: "turso",
dbCredentials: {
url: process.env.TURSO_DATABASE_URL!,
authToken: process.env.TURSO_AUTH_TOKEN,
},
} satisfies Config;
Schemaを作成します.
import { sql } from "drizzle-orm";
import { customType, integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
const float32Array = customType<{
data: number[];
config: { dimensions: number };
configRequired: true;
driverData: Buffer;
}>({
dataType(config) {
return `F32_BLOB(${config.dimensions})`;
},
fromDriver(value: Buffer) {
return Array.from(new Float32Array(value.buffer));
},
toDriver(value: number[]) {
return sql`vector32(${JSON.stringify(value)})`;
},
});
export const documentTable = sqliteTable("document_table", {
id: integer("id").primaryKey(),
document: text("document").notNull(),
embedding: float32Array("embedding", { dimensions: 1536 }) // openai text-embedding-3-small model output
})
db/index.ts
にインデックスを作成するSQLを追加します.
import { drizzle } from "drizzle-orm/libsql";
import { createClient } from "@libsql/client";
+import { sql } from "drizzle-orm";
const turso = createClient({
url: process.env.TURSO_DATABASE_URL!,
authToken: process.env.TURSO_AUTH_TOKEN!,
});
export const db = drizzle(turso);
+await db.run(sql`
+ CREATE INDEX IF NOT EXISTS vector_index
+ ON vector_table(vector)
+ USING vector_cosine(3)
+`);
マイグレーションファイルを生成,適用します.
bun run db:generate
bun run db:migrate
テーブルが作成されているかどうか確認します.Drizzle Studioを起動します.
bun run db:studio
https://local.drizzle.studio/ にアクセスします.document_table
が作成されているはずです.
ちなみにTursoを使っている場合はDrizzle Studioとほぼ同じものがDashboardから利用できます.
OpenAIの埋め込みモデルを使って動作確認用のデータを作成します.OpenAIの埋め込みモデル利用するためにVercel AI SDKを利用します.
bun add ai @ai-sdk/openai
.env
にOPENAIのAPIキーを追加します.
TURSO_DATABASE_URL=
TURSO_AUTH_TOKEN=
+OPENAI_API_KEY=
ハマっったポイント
libsqlのバージョンが古くてVector Searchが使えない.
Full support for vector search in the Turso platform starts from version v0.24.24
(use the turso group show <group-name> command to check the group version).
Indexの作成方法
適当なテキストでベクトル検索を試してみます.
テキストを埋め込み,その結果をデータベースに保存するコード.
import { openai } from '@ai-sdk/openai';
import { embedMany } from 'ai';
import { db } from './db';
import { documentTable } from './db/schema';
import { sql } from 'drizzle-orm';
const documents = [
"今日の仙台の気温は14℃です.",
"今日の仙台の天気は曇りです.",
"私の好きな動物はペンギンです.",
"機能はキムチ鍋を食べました."
]
const { embeddings } = await embedMany({
model: openai.embedding('text-embedding-3-small'),
values: documents,
});
await db.insert(documentTable).values(
embeddings.map((embedding, i) => ({
document: documents[i],
embedding: sql`vector32(${JSON.stringify(embedding)})`,
}))
)
クエリするコード
import { desc, sql } from "drizzle-orm";
import { db } from "./db";
import { documentTable } from "./db/schema";
import { embed } from "ai";
import { openai } from "@ai-sdk/openai";
const query = "今日の仙台市の天気は?";
const { embedding } = await embed({
model: openai.embedding("text-embedding-3-small"),
value: query,
})
const similarity = sql<number>`1 - vector_distance_cos(${documentTable.embedding}, vector(${JSON.stringify(embedding)}))`
const res = await db.select({
document: documentTable.document,
similarity: similarity
}).from(documentTable).orderBy(desc(similarity))
console.log(res)
bun run insert.ts
「今日の仙台市の天気は?」でクエリしてみます.
bun run query.ts
[
{
document: "今日の仙台の天気は曇りです.",
similarity: 0.8126901388168335,
}, {
document: "今日の仙台の気温は14℃です.",
similarity: 0.7196863889694214,
}, {
document: "私の好きな動物はペンギンです.",
similarity: 0.10916465520858765,
}, {
document: "機能はキムチ鍋を食べました.",
similarity: 0.09915316104888916,
}
]
drizzleの書き方に慣れたい...
コード