Skip to content

[Feature] Embedding生成基盤(ローカルLLM / API対応) #61

@Kewton

Description

@Kewton

概要

ドキュメント・コードセクションのEmbedding(ベクトル表現)を生成する基盤を構築する。

背景・動機

Phase 5 (Semantic Extension) の基盤。意味的な類似度検索を実現するために、テキストをベクトルに変換する仕組みが必要。

提案する解決策

対応モデル

モデル 方式 次元数 備考
nomic-embed-text(Ollama) ローカル 768 デフォルト、オフライン対応
OpenAI text-embedding-3-small API 1536 オプション、高速

アーキテクチャ

// trait定義
pub trait EmbeddingProvider: Send + Sync {
    fn embed(&self, texts: &[String]) -> Result<Vec<Vec<f32>>, EmbeddingError>;
    fn dimension(&self) -> usize;
}

// エラー型
pub enum EmbeddingError {
    NetworkError(String),    // 接続失敗
    ApiError(String),        // API応答エラー
    ModelNotFound(String),   // モデル未検出
    InvalidResponse(String), // 不正なレスポンス
    RateLimited,             // レート制限
    Timeout,                 // タイムアウト
    ConfigError(String),     // 設定エラー
    StorageError(String),    // SQLite格納エラー
}

// Ollama実装(reqwest::blocking 使用)
pub struct OllamaProvider { model: String, endpoint: String }

// OpenAI実装(reqwest::blocking 使用)
pub struct OpenAiProvider { api_key: String, model: String }

モジュール構成

src/
├── embedding/
│   ├── mod.rs           # EmbeddingProviderトレイト、EmbeddingError、config読み込み
│   ├── ollama.rs        # OllamaProvider実装
│   ├── openai.rs        # OpenAiProvider実装
│   └── store.rs         # SQLite embeddings.db 操作
├── cli/
│   ├── embed.rs         # embedサブコマンド(新規)
│   ├── index.rs         # --with-embedding オプション追加(変更)
│   └── clean.rs         # --keep-embeddings オプション追加(変更)
├── indexer/
│   └── mod.rs           # embeddings_db_path() 追加(変更)
├── main.rs              # Commands::Embed追加、Index/Update/Clean引数追加(変更)
└── lib.rs               # pub mod embedding; 追加(変更)

Embedding格納

  • SQLite.commandindex/embeddings.db)にベクトルを格納(symbols.dbとは独立)
    • tantivy 0.25 はネイティブベクトル検索に非対応のため、SQLiteを採用
    • テーブル定義: embeddings(section_path TEXT, section_heading TEXT, embedding BLOB, dimension INTEGER, model TEXT, file_hash TEXT)
    • 将来のPhase 5でANN検索ライブラリ(hnswlib等)との連携を想定
  • 格納先を .commandindex/embeddings.db として独立管理(cleanコマンドでの選択的削除に対応)
  • indexer/mod.rsembeddings_db_path() を追加(symbol_db_path() と同様のパターン)

HTTPクライアント方針

  • reqwest::blocking を使用(同期HTTP)
  • 現在のコードベースが完全同期のため、tokio runtime導入は行わない
  • 将来的なasync化は別Issueで対応

Embedding対象

  • tantivy インデックス内の各セクション(見出し単位のテキスト)
  • 1セクション = 1ベクトル
  • コードファイルは現状1ファイル = 1セクション(heading_level = 0)としてembedding生成
    • 将来的にシンボル単位(関数/クラス)のembeddingはPhase 5拡張として検討

バッチ処理

  • commandindex index / update 時にオプションで同時生成(--with-embedding
  • 別コマンド commandindex embed で後から生成も可能
    • 前提: インデックス構築済み(manifest.json + tantivyインデックスが存在)
    • tantivyインデックスからセクションのbodyテキストを読み出してembedding生成
    • インデックス未構築時は適切なエラーメッセージを表示
  • バッチサイズ: 10テキスト/リクエスト(Ollama)、100テキスト/リクエスト(API)

設定

# .commandindex/config.toml
[embedding]
provider = "ollama"           # "ollama" | "openai"
model = "nomic-embed-text"    # モデル名
endpoint = "http://localhost:11434"  # Ollamaのエンドポイント
# api_key = "sk-..."          # OpenAI使用時
  • config.toml基盤はこのIssue内で新規構築する
  • toml クレートを依存に追加
  • デフォルト値生成、型定義、バリデーション、エラーハンドリングを含む
  • config.toml不在時はembedding無効をデフォルト動作とする

エラーハンドリング

  • EmbeddingError を新設し、既存 IndexErrorEmbedding(EmbeddingError) バリアントを追加
  • ConfigErrorEmbeddingError::ConfigError として統合
  • From<EmbeddingError> 実装で既存エラーパターンに統合

キャッシュ戦略

  • ファイルハッシュベースでキャッシュ(既存の manifest::compute_file_hash() を再利用)
  • ファイル未変更ならembedding再生成をスキップ
  • ※ファイルハッシュベースのため、1セクション変更でもファイル内全セクションのembeddingを再生成する(将来最適化可能)
  • clean でembeddingも削除(--keep-embeddings で保持可能)
    • embeddingデータは .commandindex/embeddings.db に独立格納されているため選択的削除が容易
    • --keep-embeddings 時は .commandindex/ 内のファイルを個別削除し、embeddings.dbを残す

依存関係の追加

# Cargo.toml
reqwest = { version = "0.12", features = ["blocking", "json"] }
toml = "0.8"
serde = { version = "1", features = ["derive"] }  # config.tomlのデシリアライズ用(既存なら不要)

受け入れ基準

  • Ollama経由でテキストのembeddingを生成できる
  • OpenAI API経由でembeddingを生成できる
  • 生成されたembeddingが期待される次元数(Ollama: 768, OpenAI: 1536)であること
  • 同一テキストに対するembeddingが一貫していること
  • 空テキストに対する適切なハンドリング
  • --with-embedding オプションでindex/update時に同時生成
  • commandindex embed で後からembedding生成
  • ファイルハッシュベースのキャッシュが機能する
  • Ollamaが起動していない場合に適切なエラーメッセージ
  • 設定ファイル(config.toml)でプロバイダー切り替え
  • EmbeddingProviderトレイトのモック実装によるユニットテスト
  • 既存テスト(21ファイル)が変更なしで通過すること
  • cargo test / clippy / fmt 全パス

テスト戦略

  • EmbeddingProviderトレイトのモック実装によるユニットテスト
  • Ollama/OpenAI統合テストは #[cfg(feature = "integration-test")] で分離
  • CI環境ではモックテストのみ実行
  • 新テストは tests/e2e_embedding.rs として分離
  • --with-embedding のデフォルトは false で既存テストとの後方互換性を維持

影響を受けるファイル

直接変更

ファイル 変更内容
Cargo.toml reqwest, toml 依存追加
src/main.rs Commands::Embed追加、Index/Update/Clean引数追加
src/lib.rs pub mod embedding; 追加
src/cli/mod.rs pub mod embed; 追加
src/cli/index.rs --with-embedding オプション対応
src/cli/clean.rs --keep-embeddings 対応、選択的削除
src/indexer/mod.rs embeddings_db_path() 追加

新規作成

ファイル 内容
src/embedding/mod.rs トレイト、エラー型、config読み込み
src/embedding/ollama.rs OllamaProvider
src/embedding/openai.rs OpenAiProvider
src/embedding/store.rs SQLite操作
src/cli/embed.rs embedサブコマンド

依存 Issue

  • なし(Phase 4完了が前提)

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions