使用 Ollama 和 Qwen3 Reranker 模型对文档进行重排序 - 使用 Go 语言

实现 RAG?这里有一些 Go 代码片段 - 2...

目录

由于标准 Ollama 没有直接的重排序 API,
您需要通过生成查询-文档对的嵌入向量并对其进行评分来实现 使用 Qwen3 重排序器在 GO 中进行重排序

上周我尝试了 使用 Ollama 和 Qwen3 嵌入模型对文本文档进行重排序 - 在 Go 中

今天我将尝试一些 Qwen3 重排序器模型。
目前在 Ollama 上有相当多的 Qwen3 嵌入和重排序器模型 可用,我使用中等规模的 dengcao/Qwen3-Reranker-4B:Q5_K_M

重排序的狗

测试运行:TL;DR

它有效,而且速度相当快,虽然不是标准方法,但仍然:

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

使用嵌入模型: dengcao/Qwen3-Embedding-4B:Q5_K_M
Ollama 基础 URL: http://localhost:11434
处理查询文件: ./example_query.txt, 目标目录: ./example_docs
查询: 人工智能是什么,机器学习是如何工作的?
找到 7 个文档
提取查询嵌入向量...
处理文档...

=== 按相似性排序 ===
1. example_docs/ai_introduction.txt (得分: 0.451)
2. example_docs/machine_learning.md (得分: 0.388)
3. example_docs/qwen3-reranking-models.md (得分: 0.354)
4. example_docs/ollama-parallelism.md (得分: 0.338)
5. example_docs/ollama-reranking-models.md (得分: 0.318)
6. example_docs/programming_basics.txt (得分: 0.296)
7. example_docs/setup.log (得分: 0.282)

处理了 7 个文档,耗时 2.023 秒(平均每个文档 0.289 秒)
使用 dengcao/Qwen3-Reranker-4B:Q5_K_M 模型对文档进行重排序...

=== 使用重排序器排序 ===
1. example_docs/ai_introduction.txt (得分: 0.343)
2. example_docs/machine_learning.md (得分: 0.340)
3. example_docs/programming_basics.txt (得分: 0.320)
4. example_docs/setup.log (得分: 0.313)
5. example_docs/ollama-parallelism.md (得分: 0.313)
6. example_docs/qwen3-reranking-models.md (得分: 0.312)
7. example_docs/ollama-reranking-models.md (得分: 0.306)

处理了 7 个文档,耗时 1.984 秒(平均每个文档 0.283 秒)

用于调用 Ollama 的 Go 重排序器代码

大部分代码来自文章 使用嵌入模型与 Ollama 对文本文档进行重排序...,并添加以下内容:

在 runRnk() 函数末尾添加:

startTime = time.Now()
// 使用重排序模型进行重排序
fmt.Println("使用重排序模型对文档进行重排序...")

// 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("文档重排序出错: %v", err)
}

fmt.Println("\n=== 使用重排序器排序 ===")
for i, doc := range rerankedDocs {
	fmt.Printf("%d. %s (得分: %.3f)\n", i+1, doc.Path, doc.Score)
}

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

fmt.Printf("\n处理了 %d 个文档,耗时 %.3fs (平均每个文档 %.3fs)\n",
	len(rerankedDocs), totalTime.Seconds(), avgTimePerDoc.Seconds())

然后添加几个函数:

func rerankDocuments(validDocs []Document, query, rerankingModel, ollamaBaseURL string) ([]Document, error) {
	// 由于标准 Ollama 没有直接的重排序 API,我们将通过生成查询-文档对的嵌入向量并对其进行评分来实现重排序

	fmt.Println("使用交叉编码器方法实现重排序,模型为", rerankingModel)

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

	for i, doc := range validDocs {
		// 创建一个结合查询和文档的提示,用于重排序
		rerankPrompt := fmt.Sprintf("查询: %s\n\n文档: %s\n\n相关性:", query, doc.Content)

		// 获取结合后的提示的嵌入向量
		embedding, err := getEmbedding(rerankPrompt, rerankingModel, ollamaBaseURL)
		if err != nil {
			fmt.Printf("警告: 无法为文档 %d 获取重排序嵌入向量: %v\n", i, err)
			// 回退到中性评分
			rerankedDocs[i].Score = 0.5
			continue
		}

		// 使用嵌入向量的大小作为相关性评分
		//(这是一个简化的做法 - 实际上,您会使用训练好的重排序器)
		score := calculateRelevanceScore(embedding)
		rerankedDocs[i].Score = score
		// fmt.Printf("文档 %d 重排序后的得分: %.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

有用的链接