Ollama と Qwen3 Reranker モデルを用いたドキュメントの再評価 - Go での実装

RAG を実装中ですか?Go のコードスニペットを紹介 - 2 部目

目次

標準の Ollama には直接的なリランク API がないため、クエリ - ドキュメントペアのエンベッディングを生成してスコアリングを行うことで、Go 言語による Qwen3 Reranker を使ったリランキング を実装する必要があります。

先週は、Ollama と Qwen3 埋め込みモデルを使用したテキストドキュメントのリランキング(Go 言語版) を少し試しました。

今回は Qwen3 Reranker モデルを試してみます。これは、アーキテクチャと実装パターンをカバーする、より広範な Retrieval-Augmented Generation (RAG) チュートリアル の一部です。

Ollama には新しい Qwen3 埋め込みモデルとリランカーモデル がいくつか用意されており、ここでは medium サイズの dengcao/Qwen3-Reranker-4B:Q5_K_M を使用します。

reranking dogs

テスト実行:TL;DR

機能しており、非常に高速です。標準的な方法ではありませんが、それでも:

$ ./rnk ./example_query.txt ./example_docs

Using embedding model: dengcao/Qwen3-Embedding-4B:Q5_K_M
Ollama base URL: http://localhost:11434
Processing query file: ./example_query.txt, target directory: ./example_docs
Query: What is artificial intelligence and how does machine learning work?
Found 7 documents
Extracting query embedding...
Processing documents...

=== RANKING BY SIMILARITY ===
1. example_docs/ai_introduction.txt (Score: 0.451)
2. example_docs/machine_learning.md (Score: 0.388)
3. example_docs/qwen3-reranking-models.md (Score: 0.354)
4. example_docs/ollama-parallelism.md (Score: 0.338)
5. example_docs/ollama-reranking-models.md (Score: 0.318)
6. example_docs/programming_basics.txt (Score: 0.296)
7. example_docs/setup.log (Score: 0.282)

Processed 7 documents in 2.023s (avg: 0.289s per document)
Reranking documents with reranker model...
Implementing reranking using cross-encoder approach with dengcao/Qwen3-Reranker-4B:Q5_K_M

=== RANKING WITH RERANKER ===
1. example_docs/ai_introduction.txt (Score: 0.343)
2. example_docs/machine_learning.md (Score: 0.340)
3. example_docs/programming_basics.txt (Score: 0.320)
4. example_docs/setup.log (Score: 0.313)
5. example_docs/ollama-parallelism.md (Score: 0.313)
6. example_docs/qwen3-reranking-models.md (Score: 0.312)
7. example_docs/ollama-reranking-models.md (Score: 0.306)

Processed 7 documents in 1.984s (avg: 0.283s per document)

Ollama を呼び出すための Go 言語のリランカーコード

Reranking text documents with Ollama using Embedding... という投稿のコードのほとんどを流用し、以下を追加します:

runRnk() 関数の末尾に以下を追加します:

  startTime = time.Now()
	// リランキングモデルを使用してリランク
	fmt.Println("Reranking documents with reranker model...")

	// rerankingModel := "dengcao/Qwen3-Reranker-0.6B:F16"
	rerankingModel := "dengcao/Qwen3-Reranker-4B:Q5_K_M"
	rerankedDocs, err := rerankDocuments(validDocs, query, rerankingModel, ollamaBaseURL)
	if err != nil {
		log.Fatalf("Error reranking documents: %v", err)
	}

	fmt.Println("\n=== RANKING WITH RERANKER ===")
	for i, doc := range rerankedDocs {
		fmt.Printf("%d. %s (Score: %.3f)\n", i+1, doc.Path, doc.Score)
	}

	totalTime = time.Since(startTime)
	avgTimePerDoc = totalTime / time.Duration(len(rerankedDocs))

	fmt.Printf("\nProcessed %d documents in %.3fs (avg: %.3fs per document)\n",
		len(rerankedDocs), totalTime.Seconds(), avgTimePerDoc.Seconds())

次に、いくつか関数を追加します:

func rerankDocuments(validDocs []Document, query, rerankingModel, ollamaBaseURL string) ([]Document, error) {
	// 標準の Ollama には直接的なリランク API がないため、
	// クエリ - ドキュメントペアのエンベッディングを生成してスコアリングすることでリランキングを実装します

	fmt.Println("Implementing reranking using cross-encoder approach with", rerankingModel)

	rerankedDocs := make([]Document, len(validDocs))
	copy(rerankedDocs, validDocs)

	for i, doc := range validDocs {
		// クエリとドキュメントを結合してリランキング用のプロンプトを作成
		rerankPrompt := fmt.Sprintf("Query: %s\n\nDocument: %s\n\nRelevance:", query, doc.Content)

		// 結合したプロンプトのエンベッディングを取得
		embedding, err := getEmbedding(rerankPrompt, rerankingModel, ollamaBaseURL)
		if err != nil {
			fmt.Printf("Warning: Failed to get rerank embedding for document %d: %v\n", i, err)
			// 中立なスコアにフォールバック
			rerankedDocs[i].Score = 0.5
			continue
		}

		// エンベッディングの大きさを関連性スコアとして使用
		// (これは簡易的なアプローチです。実際には、トレーニング済みのリランカーを使用します)
		score := calculateRelevanceScore(embedding)
		rerankedDocs[i].Score = score
		// fmt.Printf("Document %d reranked with score: %.4f\n", i, score)
	}

	// リランキングスコア(降順)でドキュメントをソート
	sort.Slice(rerankedDocs, func(i, j int) bool {
		return rerankedDocs[i].Score > rerankedDocs[j].Score
	})

	return rerankedDocs, nil
}

func calculateRelevanceScore(embedding []float64) float64 {
	// エンベッディングの大きさと正の値に基づく簡易スコアリング
	var sumPositive, sumTotal float64
	for _, val := range embedding {
		sumTotal += val * val
		if val > 0 {
			sumPositive += val
		}
	}

	if sumTotal == 0 {
		return 0
	}

	// 大きさと正のバイアスを正規化して結合
	magnitude := math.Sqrt(sumTotal) / float64(len(embedding))
	positiveRatio := sumPositive / float64(len(embedding))

	return (magnitude + positiveRatio) / 2
}

また、math パッケージをインポートすることを忘れないでください。

import (
	"math"
)

では、コンパイルします。

go build -o rnk

そして、このシンプルな RAG リランカー技術プロトタイプを実行します。

./rnk ./example_query.txt ./example_docs

参考リンク