Claude Code Plugins

Community-maintained marketplace

Feedback

.claude/skills/vector-search-alternatives/SKILL.md

@mattnigh/skills_collection
12
0

|

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name .claude/skills/vector-search-alternatives/SKILL.md
description SQLiteベースプロジェクト向けのベクトル検索実装の代替戦略スキル。 pgvectorの代わりに、外部ベクトルDB、SQLite VSS拡張、 アプリケーションレベル検索など複数のアプローチを提供します。 ⚠️ 注意: このスキルは元々PostgreSQL pgvector向けでしたが、 SQLiteベースプロジェクト向けに再構成されています。 📚 代替アプローチ: 1. **外部ベクトルデータベース**: Pinecone, Weaviate, Qdrant, Milvus 2. **SQLite VSS拡張**: 実験的なSQLiteベクトル検索サポート 3. **アプリケーションレベル**: メモリ内類似度計算 4. **Turso拡張**: Turso特有のベクトル検索機能(将来対応) 5. **ハイブリッドアプローチ**: SQLite + 外部ベクトルDB 専門分野: - ベクトル検索アーキテクチャ設計 - 外部ベクトルDBとSQLiteの統合 - RAGシステムの実装戦略 - Embedding管理とキャッシング - パフォーマンストレードオフ分析 使用タイミング: - SQLiteプロジェクトでベクトル検索を実装する時 - RAGシステムを構築する時 - ベクトルDB選定を行う時 - セマンティック検索を追加する時 Use proactively when implementing vector search in SQLite projects, building RAG systems, or selecting vector database solutions.
version 2.0.0

Vector Search Alternatives for SQLite Projects

⚠️ 重要な変更

このスキルは元々PostgreSQL pgvector向けに作成されましたが、 本プロジェクトはSQLite(Turso)を使用しているため、 代替アプローチに焦点を当てた内容に再構成されています。

概要

SQLiteネイティブではベクトル検索をサポートしていないため、 複数の代替アプローチを検討する必要があります。 このスキルは、各アプローチの特徴、実装方法、トレードオフを提供します。

主要な価値:

  • SQLiteプロジェクトでのベクトル検索実装ガイダンス
  • 複数の代替ソリューションの比較と選択基準
  • RAGシステム構築のアーキテクチャパターン
  • パフォーマンスとコストのトレードオフ分析

対象ユーザー:

  • .claude/agents/dba-mgr.mdエージェント
  • バックエンド開発者
  • AIエンジニア
  • アーキテクト

代替アプローチ一覧

1. 外部ベクトルデータベース(推奨)

概要: 専用のベクトルデータベースをSQLiteと併用する

選択肢:

サービス 特徴 料金モデル 推奨用途
Pinecone フルマネージド、高速、スケーラブル 使用量課金 プロダクション、高トラフィック
Weaviate OSS、セルフホスト可、GraphQL 無料/マネージド 柔軟性重視、コスト削減
Qdrant OSS、高性能、Rust実装 無料/マネージド パフォーマンス重視
Milvus OSS、大規模対応 無料/マネージド 大規模データセット

実装例(Pinecone):

import { Pinecone } from "@pinecone-database/pinecone";

// 初期化
const pinecone = new Pinecone({
  apiKey: process.env.PINECONE_API_KEY,
});

// インデックス取得
const index = pinecone.index("my-index");

// Embedding追加(SQLiteのIDと関連付け)
await index.upsert([
  {
    id: "doc-1", // SQLiteのドキュメントID
    values: embedding, // 1536次元のベクトル
    metadata: { content: "text", userId: 123 },
  },
]);

// 類似検索
const results = await index.query({
  vector: queryEmbedding,
  topK: 10,
  includeMetadata: true,
});

// SQLiteから詳細情報を取得
const docIds = results.matches.map((m) => m.id);
const docs = await db.query.documents.findMany({
  where: inArray(documents.id, docIds),
});

メリット:

  • 高速で正確なベクトル検索
  • インデックス管理が自動
  • スケーラビリティが高い
  • 豊富な機能(フィルタリング、名前空間など)

デメリット:

  • 追加コスト発生
  • 外部依存が増加
  • データ同期が必要
  • レイテンシの可能性

2. SQLite VSS拡張(実験的)

概要: SQLite用のベクトル類似度検索拡張機能

リポジトリ: https://github.com/asg017/sqlite-vss

実装例:

-- 拡張のロード
.load ./vss0

-- 仮想テーブル作成
CREATE VIRTUAL TABLE documents_vss USING vss0(
  embedding(1536)  -- OpenAI embeddingの次元数
);

-- Embeddingの追加
INSERT INTO documents_vss(rowid, embedding)
VALUES (1, vector_from_blob(?));

-- 類似検索
SELECT
  rowid,
  distance
FROM documents_vss
WHERE vss_search(
  embedding,
  vector_from_blob(?)
)
LIMIT 10;

メリット:

  • SQLite内で完結
  • 外部依存なし
  • 低コスト

デメリット:

  • 実験的な機能(本番環境非推奨)
  • Tursoでサポートされていない
  • パフォーマンスが限定的
  • ドキュメント不足

3. アプリケーションレベル検索

概要: メモリ内でコサイン類似度を計算

実装例:

// Embeddingをメモリにロード(起動時)
const embeddingsCache = await db.query.documents.findMany({
  columns: {
    id: true,
    embedding: true,
  },
});

// コサイン類似度計算
function cosineSimilarity(a: number[], b: number[]): number {
  const dotProduct = a.reduce((sum, ai, i) => sum + ai * b[i], 0);
  const magnitudeA = Math.sqrt(a.reduce((sum, ai) => sum + ai * ai, 0));
  const magnitudeB = Math.sqrt(b.reduce((sum, bi) => sum + bi * bi, 0));
  return dotProduct / (magnitudeA * magnitudeB);
}

// 類似検索
function findSimilar(queryEmbedding: number[], topK: number = 10) {
  const similarities = embeddingsCache.map((doc) => ({
    id: doc.id,
    similarity: cosineSimilarity(queryEmbedding, doc.embedding),
  }));

  return similarities
    .sort((a, b) => b.similarity - a.similarity)
    .slice(0, topK);
}

最適化版(大規模データ対応):

import { Worker } from "worker_threads";

// ワーカープールで並列計算
class VectorSearchWorkerPool {
  private workers: Worker[] = [];

  constructor(poolSize: number = 4) {
    for (let i = 0; i < poolSize; i++) {
      this.workers.push(new Worker("./vector-search-worker.js"));
    }
  }

  async search(
    queryEmbedding: number[],
    embeddings: Array<{ id: string; embedding: number[] }>,
    topK: number,
  ): Promise<Array<{ id: string; similarity: number }>> {
    // データを分割して各ワーカーに配分
    const chunkSize = Math.ceil(embeddings.length / this.workers.length);
    const chunks = [];
    for (let i = 0; i < embeddings.length; i += chunkSize) {
      chunks.push(embeddings.slice(i, i + chunkSize));
    }

    // 並列計算
    const results = await Promise.all(
      chunks.map((chunk, i) =>
        this.workers[i].postMessage({ queryEmbedding, chunk }),
      ),
    );

    // 結果をマージしてソート
    return results
      .flat()
      .sort((a, b) => b.similarity - a.similarity)
      .slice(0, topK);
  }
}

メリット:

  • 完全なコントロール
  • 外部依存なし
  • カスタマイズ可能

デメリット:

  • メモリ制約(大規模データで問題)
  • パフォーマンスが低い
  • スケーラビリティに課題
  • 自前実装のメンテナンスコスト

4. ハイブリッドアプローチ(推奨)

概要: SQLite + 外部ベクトルDB + キャッシング

アーキテクチャ:

┌─────────────────────────────────────────────┐
│          Application Layer                   │
├─────────────────────────────────────────────┤
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  │
│  │ Redis    │  │ Pinecone │  │ SQLite   │  │
│  │ Cache    │←→│ Vector   │←→│ Metadata │  │
│  └──────────┘  └──────────┘  └──────────┘  │
└─────────────────────────────────────────────┘

実装例:

class HybridVectorSearch {
  constructor(
    private db: DrizzleDB,
    private pinecone: PineconeClient,
    private redis: RedisClient,
  ) {}

  async search(query: string, topK: number = 10) {
    // 1. キャッシュチェック
    const cacheKey = `search:${hashQuery(query)}`;
    const cached = await this.redis.get(cacheKey);
    if (cached) return JSON.parse(cached);

    // 2. Embedding生成
    const embedding = await generateEmbedding(query);

    // 3. Pineconeで類似検索
    const vectorResults = await this.pinecone.query({
      vector: embedding,
      topK: topK * 2, // 余分に取得してフィルタリング
    });

    // 4. SQLiteからメタデータ取得
    const docIds = vectorResults.matches.map((m) => m.id);
    const docs = await this.db.query.documents.findMany({
      where: inArray(documents.id, docIds),
      with: {
        user: true, // リレーション
        tags: true,
      },
    });

    // 5. スコアでソートして返却
    const results = docs
      .map((doc) => {
        const vectorScore = vectorResults.matches.find(
          (m) => m.id === doc.id,
        )?.score;
        return { ...doc, score: vectorScore };
      })
      .sort((a, b) => b.score - a.score)
      .slice(0, topK);

    // 6. キャッシュに保存(5分)
    await this.redis.setex(cacheKey, 300, JSON.stringify(results));

    return results;
  }

  async upsert(document: Document) {
    // 1. SQLiteに保存
    const [inserted] = await this.db
      .insert(documents)
      .values(document)
      .returning();

    // 2. Embedding生成
    const embedding = await generateEmbedding(document.content);

    // 3. Pineconeに追加
    await this.pinecone.upsert([
      {
        id: inserted.id,
        values: embedding,
        metadata: {
          userId: document.userId,
          createdAt: inserted.createdAt.toISOString(),
        },
      },
    ]);

    // 4. キャッシュ無効化
    await this.redis.del(`search:*`);

    return inserted;
  }
}

メリット:

  • 最高のパフォーマンス
  • スケーラブル
  • SQLiteの強みを活かせる
  • キャッシングで低レイテンシ

デメリット:

  • 複雑性が高い
  • コストが高い
  • データ同期の管理

推奨アーキテクチャ

小規模プロジェクト(< 10,000ドキュメント)

SQLite (メタデータ) + アプリケーションレベル検索
  • コスト: 最低
  • 実装: シンプル
  • パフォーマンス: 許容範囲

中規模プロジェクト(10,000 - 100,000ドキュメント)

SQLite (メタデータ) + Pinecone (ベクトル) + Redis (キャッシュ)
  • コスト: 中程度
  • 実装: 複雑
  • パフォーマンス: 高速

大規模プロジェクト(> 100,000ドキュメント)

SQLite (メタデータ) + Qdrant/Weaviate (ベクトル) + Redis (キャッシュ) + CDN
  • コスト: 高い
  • 実装: 高度
  • パフォーマンス: 最高速

RAG実装パターン

パターン1: シンプルRAG

async function simpleRAG(question: string) {
  // 1. 質問をEmbedding化
  const queryEmbedding = await generateEmbedding(question);

  // 2. 類似ドキュメント検索
  const relevantDocs = await vectorSearch.search(queryEmbedding, 5);

  // 3. コンテキストを構築
  const context = relevantDocs.map((doc) => doc.content).join("\n\n");

  // 4. LLMに質問
  const response = await llm.chat({
    messages: [
      {
        role: "system",
        content: `以下のコンテキストを使用して質問に答えてください:\n\n${context}`,
      },
      { role: "user", content: question },
    ],
  });

  return response;
}

パターン2: ハイブリッドRAG(ベクトル + キーワード)

async function hybridRAG(question: string) {
  // 1. ベクトル検索
  const vectorResults = await vectorSearch.search(question, 10);

  // 2. SQLiteのFTS5でキーワード検索
  const keywordResults = await db.query.documents.findMany({
    where: sql`${documents.content} MATCH ${question}`,
    limit: 10,
  });

  // 3. 結果をマージ(RRF: Reciprocal Rank Fusion)
  const merged = mergeResults(vectorResults, keywordResults);

  // 4. LLMに質問
  const context = merged
    .slice(0, 5)
    .map((doc) => doc.content)
    .join("\n\n");
  return await llm.chat({ context, question });
}

function mergeResults(
  vectorResults: Document[],
  keywordResults: Document[],
): Document[] {
  const scores = new Map<string, number>();

  // RRFスコア計算
  vectorResults.forEach((doc, rank) => {
    scores.set(doc.id, (scores.get(doc.id) || 0) + 1 / (rank + 60));
  });

  keywordResults.forEach((doc, rank) => {
    scores.set(doc.id, (scores.get(doc.id) || 0) + 1 / (rank + 60));
  });

  // スコアでソート
  return Array.from(scores.entries())
    .sort(([, a], [, b]) => b - a)
    .map(([id]) => vectorResults.find((d) => d.id === id)!)
    .filter(Boolean);
}

ベストプラクティス

すべきこと

  1. Embeddingをキャッシュする
const embeddingCache = new Map<string, number[]>();

async function getCachedEmbedding(text: string): Promise<number[]> {
  const key = hashText(text);
  if (embeddingCache.has(key)) {
    return embeddingCache.get(key)!;
  }

  const embedding = await generateEmbedding(text);
  embeddingCache.set(key, embedding);
  return embedding;
}
  1. バッチ処理を使う
// 一度に複数のEmbeddingを生成
const embeddings = await openai.embeddings.create({
  input: documents.map((d) => d.content),
  model: "text-embedding-3-small",
});
  1. データ同期を自動化する
// Drizzle ORM フック
db.$extends({
  query: {
    documents: {
      async create({ args, query }) {
        const result = await query(args);
        // Pineconeに自動同期
        await syncToPinecone(result);
        return result;
      },
    },
  },
});

避けるべきこと

  1. 同期的なEmbedding生成 - バックグラウンドジョブを使用
  2. 大量データのメモリロード - ページネーションとストリーミング
  3. キャッシュなし - RedisやCDNを活用

トラブルシューティング

問題1: 検索が遅い

原因: アプリケーションレベル検索でのフルスキャン

解決策:

  • Pineconeなど外部ベクトルDBに移行
  • Worker Threadsで並列化
  • Redisでキャッシング

問題2: コストが高い

原因: Embedding生成が頻繁

解決策:

  • Embeddingをキャッシュ
  • バッチ処理でAPI呼び出し削減
  • オープンソースモデル検討(sentence-transformers)

問題3: データ同期ずれ

原因: SQLiteとベクトルDBの不整合

解決策:

  • トランザクション的な更新
  • 定期的な同期ジョブ
  • イベント駆動アーキテクチャ

関連スキル

  • database-design - SQLiteスキーマ設計
  • api-integration - 外部ベクトルDB統合
  • caching-strategies - キャッシング最適化

変更履歴

バージョン 日付 変更内容
2.0.0 2025-12-04 SQLite代替案に再構成
1.0.0 2025-11-27 初版作成(pgvector向け - 非推奨)

参考文献