概要
ハイブリッド検索の結果に対して、クエリとドキュメントのペアワイズ類似度を再計算し、より精度の高い順位付けを行うReranking機能を実装する。
背景・動機
初回検索(BM25 + Semantic)は高速だが粗い。Rerankingで上位候補をより精密に再評価することで、最終的な検索精度を向上させる。
提案する解決策
Reranking方式
Cross-Encoder方式を採用:
- クエリとドキュメントのペアを入力し、関連度スコアを直接出力
- Bi-Encoder(embedding類似度)より高精度だがコストが高い
- 上位N件のみにRerankingを適用(デフォルト: top-20 → rerank → top-k出力)
対応モデル・APIエンドポイント
| モデル |
方式 |
エンドポイント |
備考 |
| Ollama(プロンプト方式) |
ローカル |
POST /api/generate |
プライマリ。汎用生成モデル(例: llama3)ベースのプロンプトスコアリング |
| Cohere Rerank API |
API |
POST /v1/rerank |
初期実装ではトレイト(RerankProviderType enum)定義のみ。実装は将来Issueに分離 |
Cohere実装スコープ(M4): 初期実装はOllamaプロバイダーのみ。Cohereは RerankProviderType::Cohere バリアントの定義と、cohere.rs に空のスケルトン(未実装エラー RerankError::ProviderNotImplemented を返す)を配置するに留める。Cohere APIの実際の呼び出し実装は将来Issueとして分離する。
Ollama プロンプト方式の詳細
Ollama の /api/generate エンドポイントを使用し、汎用生成モデル(例: llama3)によるプロンプトベースでクエリとドキュメントの関連度スコアを算出する。bge-reranker-v2-m3 等の専用Rerankモデルも対応モデルの一例として利用可能だが、プライマリは汎用生成モデルとする。
リクエスト形式:
{
"model": "llama3",
"prompt": "Given the query: \"<query>\"\n\nRate the relevance of the following document on a scale of 0 to 10:\n\n<document_text>\n\nRelevance score (0-10):",
"stream": false,
"options": {
"temperature": 0.0,
"num_predict": 10
}
}
レスポンス形式:
{
"model": "llama3",
"response": "8",
"done": true
}
- レスポンスから数値を抽出し、0-10の範囲にクランプしてスコアとする
- パース失敗時はスコア0として扱い、警告をログ出力する
将来対応: Ollama が /api/rerank エンドポイントを正式サポートした場合、ネイティブRerank APIへの切り替えを検討する。その際は RerankProvider トレイトの実装を追加するだけで対応可能な設計とする。
document_text の組み立て規則
Reranking時にモデルへ送信する document_text は、以下の規則で組み立てる:
heading + "\n" + body を連結して構成する
- 最大 4096文字 で truncate する(4096文字を超える場合は先頭4096文字を使用)
- heading が空の場合は body のみを使用する
変更(S4): 最大長を8192文字から4096文字に縮小。Ollamaへの逐次リクエストにおけるパフォーマンスを考慮し、候補あたりの入力サイズを抑制する。
適用対象の検索モード
Rerankingは SearchResult ベースの経路 に適用する。BM25フォールバック時もrerankは適用される(rerank対象はSearchResult型であればモードに関わらず適用)。
| 検索モード |
--rerank 指定時の動作 |
| ハイブリッド検索(デフォルト) |
Reranking適用 |
--no-semantic (BM25単体) |
Reranking適用 |
| BM25フォールバック(embedding未設定時) |
Reranking適用(SearchResult型を返す経路のため) |
--semantic (Semantic Search単体) |
初期実装ではスコープ外。SemanticSearchResult型を使用するため、将来Issueで対応予定 |
--symbol (シンボル検索) |
使用不可(--rerank と排他。異なる結果型を返すため) |
--related (関連ファイル検索) |
使用不可(--rerank と排他。異なる結果型を返すため) |
変更(S2): BM25フォールバック経路もReranking適用対象として明記。SearchResult型を返す経路であれば検索モードに関わらずrerankを適用する。
--rerank は --symbol、--related、および --semantic と conflicts_with に設定する
--rerank はテキスト検索モード(ハイブリッド / --no-semantic / BM25フォールバック)の後段処理として適用される
- Rerankingは検索結果の順序のみを変更し、検索対象の絞り込みロジックには影響しない
--semantic 経路のReranking対応は将来Issueとする(SemanticSearchResult → RerankCandidate への変換処理が必要)
CLIオプション
# Reranking有効(embeddingとrankモデルが利用可能な場合)
commandindex search "認証の流れ" --rerank
# Reranking無効(デフォルト)
commandindex search "認証の流れ"
# リランク候補数を指定
commandindex search "認証の流れ" --rerank --rerank-top 30
# --no-semantic との組み合わせ
commandindex search "認証の流れ" --no-semantic --rerank
# 以下はエラーになる(conflicts_with)
# commandindex search "認証の流れ" --symbol --rerank ← エラー
# commandindex search "認証の流れ" --related --rerank ← エラー
# commandindex search "認証の流れ" --semantic --rerank ← エラー(初期実装ではスコープ外)
引数の優先順位: CLI引数 > config.toml > デフォルト値
--rerank-top 30 が指定された場合、config.toml の top_candidates より優先される
- config.toml に
[rerank] セクションがない場合、デフォルト値を使用する
--rerank-top は requires = "rerank" とし、--rerank なしで --rerank-top のみ指定した場合はclapエラーにする
設定
# .commandindex/config.toml
[rerank]
provider = "ollama" # "ollama" | "cohere"
model = "llama3" # 使用するモデル名(デフォルト: llama3)
top_candidates = 20 # リランク対象の候補数(デフォルト: 20)
endpoint = "http://localhost:11434" # Ollamaエンドポイント(オプション)
Config構造体の変更:
pub struct Config {
// ... 既存フィールド
pub rerank: Option<RerankConfig>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum RerankProviderType {
Ollama,
Cohere, // enum定義のみ。実装は将来Issue
}
pub struct RerankConfig {
pub provider: RerankProviderType, // プロバイダー種別(enum)
pub model: String, // モデル名(デフォルト: "llama3")
pub top_candidates: usize, // リランク対象数(デフォルト: 20)
pub endpoint: Option<String>, // エンドポイントURL(オプション)
pub api_key: Option<String>, // APIキー(Cohere等で使用、オプション)
pub timeout_secs: u64, // 全体タイムアウト秒数(デフォルト: 30)
}
rerank フィールドは Option<RerankConfig> とし、設定がない場合は None
--rerank 指定時に RerankConfig が None の場合、デフォルト値(provider=Ollama, model="llama3", top_candidates=20, timeout_secs=30)を使用する
provider フィールドは String ではなく RerankProviderType enum を使用し、型安全性を確保する
実装ガイド
検索フローの変更箇所
Reranking処理は src/cli/search.rs の検索フロー末尾に挿入される。具体的な変更箇所:
src/cli/search.rs: run() 関数(ハイブリッド検索経路およびBM25フォールバック経路)にRerankingを適用。run_semantic_search() は対象外(初期実装スコープ外)。run() の結果返却前にRerankingを呼び出す分岐を追加する。--rerank フラグが有効な場合、SearchResult のリストを RerankCandidate に変換し、Reranking処理を実行後、スコアを上書きして再ソートする
src/cli/(searchサブコマンド): --rerank / --rerank-top 引数の追加、conflicts_with 設定(--symbol, --related, --semantic)
src/rerank/: Rerankingモジュール(新規作成)
新モジュール構成
src/
├── rerank/ # Rerankingモジュール(新規)
│ ├── mod.rs # RerankProviderトレイト、RerankCandidate/RerankResult型、オーケストレーション
│ ├── ollama.rs # OllamaRerankProvider(/api/generate プロンプト方式)
│ └── cohere.rs # CohereRerankProvider(空スケルトン。RerankError::ProviderNotImplemented を返す)
RerankProvider トレイトを rerank/mod.rs に定義
- 検索モジュールからは
RerankProvider トレイト経由で呼び出し、実装を差し替え可能にする
API呼び出し方式
- Ollamaへのリクエストは候補ごとに 逐次実行(初期実装)
- 各リクエストのタイムアウトは 10秒(個別候補単位)
- 全体タイムアウト: 30秒(デフォルト。
RerankConfig.timeout_secs で設定可能)。全体タイムアウトに達した場合、スコア取得済みの候補のみで再ソートし返す。未処理の候補は元スコアを維持する
- 将来的にバッチ化や
tokio::spawn による並列化を検討する
スコアの扱い
- Reranking後のスコア(0-10の範囲)で
SearchResult.score フィールドを上書きする
- 上書き前のオリジナルスコア(RRFスコアやBM25スコア)は保持しない(初期実装)
- JSON出力時にはrerank後のスコアが
score フィールドに出力される
出力フォーマット
human / json / path 出力形式に対応。
注記(S1): --rerank 指定時のJSON出力における score フィールドの意味が変わる。通常時はRRFスコアやBM25スコアだが、rerank時は 0-10のCross-Encoderスコア になる。スコアの値域とスケールが異なるため、rerankの有無でスコアの比較はできない点に留意。
検索フロー
- ハイブリッド検索(または
--no-semantic のBM25検索、またはBM25フォールバック)でtop-N件を取得。N = max(limit, rerank_top) とする(limit はCLIの --limit 値、rerank_top は --rerank-top 値またはデフォルト20)
- 各候補について
document_text(heading + "\n" + body、最大4096文字で truncate)を組み立てる
- 各候補について (query, document_text) ペアでCross-Encoderスコアを算出
- Cross-Encoderスコアで
SearchResult.score を上書きし、再ソート
- top-k件(
--limit で指定された件数)を出力
変更(M1): 候補取得数を max(limit, rerank_top) とする。例: --limit 30 --rerank --rerank-top 20 の場合、30件を検索エンジンから取得し、上位20件をrerankし、最終的に30件を出力する。--limit 5 --rerank --rerank-top 20 の場合、20件を取得し、20件をrerankし、上位5件を出力する。これにより --limit が --rerank-top より大きい場合でも必要な件数が確保される。
パフォーマンス要件
- Rerankingの追加レイテンシ 運用目安: top-20候補に対して 10秒程度(ローカルOllama環境)。これは受け入れ基準ではなく、実環境での目安値とする
- Ollamaへのリクエストは候補ごとに逐次実行(初期実装)。将来的にバッチ化や並列化を検討する
top_candidates のデフォルト値(20)はレイテンシと精度のバランスを考慮した値
- 全体タイムアウト: デフォルト30秒。タイムアウト超過時はスコア取得済みの候補のみで再ソートし返す。未処理の候補は元スコアを維持する
変更(M3): 「10秒以内」を受け入れ基準から運用目安に変更。途中タイムアウト時はスコア取得済みの候補のみで再ソートし返す方針を明確化。
Graceful Degradation(障害時の振る舞い)
Rerankモデルが利用不可またはエラーが発生した場合の動作:
| 状況 |
動作 |
| Rerankモデルに接続不可 |
Reranking前の検索結果をそのまま返す + stderr に警告出力 |
| 全件のスコア算出に失敗 |
元の検索結果の順序をそのまま返す + stderr に警告出力 |
| 一部の候補のスコア算出に失敗 |
失敗した候補はスコア0として扱い、結果に含める。同スコア時のtie-breakは元の順序を維持する(安定ソート) + stderr に警告出力 |
| レスポンスのパースに失敗 |
スコア0として扱い、結果に含める + stderr に警告出力 |
| 全体タイムアウト超過 |
スコア取得済みの候補のみで再ソートし返す。未処理候補は元スコアを維持 + stderr に警告出力 |
--rerank 指定時にRerankingが完全にスキップされた場合、stderr に Warning: Reranking skipped due to <reason>. Returning original search results. を出力する
- 検索自体は失敗させず、常に結果を返すことを保証する
テスト方針
トレイト設計
pub trait RerankProvider: Send + Sync {
fn rerank(
&self,
query: &str,
documents: Vec<RerankCandidate>,
) -> Result<Vec<RerankResult>, RerankError>;
}
テスト戦略
| テスト種類 |
対象 |
方法 |
| ユニットテスト |
スコアリングロジック |
MockRerankProvider を使用 |
| ユニットテスト |
Graceful Degradation |
MockRerankProvider でエラーを返すケース |
| ユニットテスト |
全件失敗時の元順序維持 |
MockRerankProvider で全件エラー → 元順序で返却を検証 |
| ユニットテスト |
CLI引数パース |
--rerank, --rerank-top のパーステスト |
| ユニットテスト |
CLI引数排他制約 |
--rerank と --symbol / --related / --semantic の conflicts_with テスト |
| ユニットテスト |
--rerank-top requires --rerank |
--rerank-top 単独指定時のclapエラーテスト |
| 統合テスト |
検索フロー全体 |
MockRerankProvider で検索→Rerank→出力の流れを検証 |
既存テストへの影響(M2): 既存テスト(tests/cli_args.rs, tests/output_format.rs)は --rerank なしでは一切影響を受けない。Reranking機能はオプトインであり、--rerank フラグが指定されない限り従来の検索パスがそのまま実行される。--rerank 指定時のみ score の意味がCross-Encoderスコア(0-10)に変わる点に注意。
struct MockRerankProvider {
scores: Vec<f32>, // 返すスコアを事前設定
}
impl RerankProvider for MockRerankProvider {
fn rerank(&self, _query: &str, documents: Vec<RerankCandidate>) -> Result<Vec<RerankResult>, RerankError> {
// テスト用の固定スコアを返す
}
}
CLI引数パーステストの詳細
// 正常系
#[test] fn rerank_flag_is_parsed() { /* --rerank が正しくパースされる */ }
#[test] fn rerank_top_value_is_parsed() { /* --rerank-top 30 が正しくパースされる */ }
#[test] fn rerank_top_requires_rerank() { /* --rerank-top は --rerank なしでclapエラーになる */ }
// 排他制約テスト(conflicts_with)
#[test] fn rerank_conflicts_with_symbol() { /* --rerank --symbol は clap エラーになる */ }
#[test] fn rerank_conflicts_with_related() { /* --rerank --related は clap エラーになる */ }
#[test] fn rerank_conflicts_with_semantic() { /* --rerank --semantic は clap エラーになる(初期実装スコープ外のため) */ }
// 優先順位テスト
#[test] fn cli_rerank_top_overrides_config() { /* CLI引数がconfig.tomlより優先される */ }
受け入れ基準
依存 Issue
将来対応
--semantic 経路のReranking対応(SemanticSearchResult → RerankCandidate 変換)
- Ollama
/api/rerank ネイティブAPIサポート
- バッチ化 / 並列化によるパフォーマンス改善
- Cohere Rerank API 実装(
cohere.rs スケルトンに実装を追加)
概要
ハイブリッド検索の結果に対して、クエリとドキュメントのペアワイズ類似度を再計算し、より精度の高い順位付けを行うReranking機能を実装する。
背景・動機
初回検索(BM25 + Semantic)は高速だが粗い。Rerankingで上位候補をより精密に再評価することで、最終的な検索精度を向上させる。
提案する解決策
Reranking方式
Cross-Encoder方式を採用:
対応モデル・APIエンドポイント
POST /api/generatePOST /v1/rerankRerankProviderTypeenum)定義のみ。実装は将来Issueに分離Ollama プロンプト方式の詳細
Ollama の
/api/generateエンドポイントを使用し、汎用生成モデル(例: llama3)によるプロンプトベースでクエリとドキュメントの関連度スコアを算出する。bge-reranker-v2-m3 等の専用Rerankモデルも対応モデルの一例として利用可能だが、プライマリは汎用生成モデルとする。リクエスト形式:
{ "model": "llama3", "prompt": "Given the query: \"<query>\"\n\nRate the relevance of the following document on a scale of 0 to 10:\n\n<document_text>\n\nRelevance score (0-10):", "stream": false, "options": { "temperature": 0.0, "num_predict": 10 } }レスポンス形式:
{ "model": "llama3", "response": "8", "done": true }document_text の組み立て規則
Reranking時にモデルへ送信する
document_textは、以下の規則で組み立てる:heading + "\n" + bodyを連結して構成する適用対象の検索モード
Rerankingは SearchResult ベースの経路 に適用する。BM25フォールバック時もrerankは適用される(rerank対象はSearchResult型であればモードに関わらず適用)。
--rerank指定時の動作--no-semantic(BM25単体)--semantic(Semantic Search単体)--symbol(シンボル検索)--rerankと排他。異なる結果型を返すため)--related(関連ファイル検索)--rerankと排他。異なる結果型を返すため)--rerankは--symbol、--related、および--semanticとconflicts_withに設定する--rerankはテキスト検索モード(ハイブリッド / --no-semantic / BM25フォールバック)の後段処理として適用される--semantic経路のReranking対応は将来Issueとする(SemanticSearchResult → RerankCandidate への変換処理が必要)CLIオプション
引数の優先順位: CLI引数 > config.toml > デフォルト値
--rerank-top 30が指定された場合、config.toml のtop_candidatesより優先される[rerank]セクションがない場合、デフォルト値を使用する--rerank-topはrequires = "rerank"とし、--rerankなしで--rerank-topのみ指定した場合はclapエラーにする設定
Config構造体の変更:
rerankフィールドはOption<RerankConfig>とし、設定がない場合はNone--rerank指定時にRerankConfigがNoneの場合、デフォルト値(provider=Ollama,model="llama3",top_candidates=20,timeout_secs=30)を使用するproviderフィールドはStringではなくRerankProviderTypeenum を使用し、型安全性を確保する実装ガイド
検索フローの変更箇所
Reranking処理は
src/cli/search.rsの検索フロー末尾に挿入される。具体的な変更箇所:src/cli/search.rs:run()関数(ハイブリッド検索経路およびBM25フォールバック経路)にRerankingを適用。run_semantic_search()は対象外(初期実装スコープ外)。run()の結果返却前にRerankingを呼び出す分岐を追加する。--rerankフラグが有効な場合、SearchResult のリストを RerankCandidate に変換し、Reranking処理を実行後、スコアを上書きして再ソートするsrc/cli/(searchサブコマンド):--rerank/--rerank-top引数の追加、conflicts_with設定(--symbol,--related,--semantic)src/rerank/: Rerankingモジュール(新規作成)新モジュール構成
RerankProviderトレイトをrerank/mod.rsに定義RerankProviderトレイト経由で呼び出し、実装を差し替え可能にするAPI呼び出し方式
RerankConfig.timeout_secsで設定可能)。全体タイムアウトに達した場合、スコア取得済みの候補のみで再ソートし返す。未処理の候補は元スコアを維持するtokio::spawnによる並列化を検討するスコアの扱い
SearchResult.scoreフィールドを上書きするscoreフィールドに出力される出力フォーマット
human / json / path 出力形式に対応。
検索フロー
--no-semanticのBM25検索、またはBM25フォールバック)でtop-N件を取得。N =max(limit, rerank_top)とする(limitはCLIの--limit値、rerank_topは--rerank-top値またはデフォルト20)document_text(heading + "\n" + body、最大4096文字で truncate)を組み立てるSearchResult.scoreを上書きし、再ソート--limitで指定された件数)を出力パフォーマンス要件
top_candidatesのデフォルト値(20)はレイテンシと精度のバランスを考慮した値Graceful Degradation(障害時の振る舞い)
Rerankモデルが利用不可またはエラーが発生した場合の動作:
--rerank指定時にRerankingが完全にスキップされた場合、stderr にWarning: Reranking skipped due to <reason>. Returning original search results.を出力するテスト方針
トレイト設計
テスト戦略
MockRerankProviderを使用MockRerankProviderでエラーを返すケースMockRerankProviderで全件エラー → 元順序で返却を検証--rerank,--rerank-topのパーステスト--rerankと--symbol/--related/--semanticのconflicts_withテスト--rerank-toprequires--rerank--rerank-top単独指定時のclapエラーテストMockRerankProviderで検索→Rerank→出力の流れを検証CLI引数パーステストの詳細
受け入れ基準
--rerankでCross-Encoderによる再順位付けが実行される--rerankなしでは従来の検索結果(Rerankingスキップ)--rerank-topでリランク候補数を指定できる--rerank-topはrequires = "rerank"であり、--rerankなしで単独指定時はclapエラーになる--rerankはハイブリッド検索、--no-semantic、およびBM25フォールバックで動作する(SearchResultベースの経路すべて)--rerankは--symbol、--related、および--semanticと排他(conflicts_with)であり、同時指定時はclapエラーになる--semantic経路のReranking対応は将来Issueとするmax(limit, rerank_top)とし、--limitが--rerank-topより大きい場合でも必要な件数を確保するrerank: Option<RerankConfig>が追加されているRerankConfig.providerはRerankProviderTypeenum を使用しているRerankConfigにendpoint,api_key,timeout_secsフィールドが含まれているRerankConfig追加後も全パスするRerankProviderトレイトが定義され、MockRerankProviderでテスト可能SearchResult.scoreが上書きされるdocument_textは heading + "\n" + body を連結し、最大4096文字で truncate されるsrc/cli/search.rsのrun()関数内RerankProviderTypeenum定義と空スケルトン(RerankError::ProviderNotImplemented)のみcohere.rsは未実装エラーを返すスケルトンとして配置されるtests/cli_args.rs,tests/output_format.rs)は--rerankなしでは一切影響を受けないscoreフィールドは、rerank時は0-10のCross-Encoderスコアとなる依存 Issue
将来対応
--semantic経路のReranking対応(SemanticSearchResult → RerankCandidate 変換)/api/rerankネイティブAPIサポートcohere.rsスケルトンに実装を追加)