Riordinare i documenti con Ollama e modello Qwen3 Reranker - in Go

L'implementazione di RAG? Ecco alcuni frammenti di codice in Go - 2...

Indice

Poiché l’Ollama standard non dispone di un’API di reranking diretta, sarà necessario implementare reranking utilizzando Qwen3 Reranker in GO generando gli embedding per le coppie query-documento e valutandoli.

La settimana scorsa ho fatto un po’ di Reranking di documenti testuali con Ollama e Qwen3 Embedding model - in Go.

Oggi proverò alcuni modelli Qwen3 Reranker. Esiste un insieme abbastanza ampio di nuovi Qwen3 Embedding & Reranker Models su Ollama disponibili, ne uso uno medio - dengcao/Qwen3-Reranker-4B:Q5_K_M

reranking dogs

Esecuzione del test: TL;DR

Funziona, e piuttosto veloce, non è un modo molto standard, ma comunque:

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

Utilizzo del modello di embedding: dengcao/Qwen3-Embedding-4B:Q5_K_M
URL base di Ollama: http://localhost:11434
Elaborazione del file query: ./example_query.txt, cartella target: ./example_docs
Query: Cosa è l'intelligenza artificiale e come funziona l'apprendimento automatico?
Trovati 7 documenti
Estrazione dell'embedding della query...
Elaborazione dei documenti...

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

Elaborati 7 documenti in 2.023s (media: 0.289s per documento)
Reranking dei documenti con modello reranker...
Implementazione del reranking utilizzando un approccio cross-encoder con dengcao/Qwen3-Reranker-4B:Q5_K_M

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

Elaborati 7 documenti in 1.984s (media: 0.283s per documento)

Codice in Go per chiamare Ollama

Prendere gran parte del codice dal post Reranking di documenti testuali con Ollama utilizzando Embedding... e aggiungere questi elementi:

Alla fine della funzione runRnk():

  startTime = time.Now()
	// rerank utilizzando il modello reranker
	fmt.Println("Reranking dei documenti con modello reranker...")

	// 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("Errore nel reranking dei documenti: %v", err)
	}

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

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

	fmt.Printf("\nElaborati %d documenti in %.3fs (media: %.3fs per documento)\n",
		len(rerankedDocs), totalTime.Seconds(), avgTimePerDoc.Seconds())

Poi aggiungere un paio di funzioni aggiuntive:

func rerankDocuments(validDocs []Document, query, rerankingModel, ollamaBaseURL string) ([]Document, error) {
	// Poiché l'Ollama standard non dispone di un'API di reranking diretta, implementeremo
	// il reranking generando gli embedding per le coppie query-documento e valutandoli

	fmt.Println("Implementazione del reranking utilizzando un approccio cross-encoder con", rerankingModel)

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

	for i, doc := range validDocs {
		// Creare un prompt per il reranking combinando query e documento
		rerankPrompt := fmt.Sprintf("Query: %s\n\nDocumento: %s\n\nRilevanza:", query, doc.Content)

		// Ottenere l'embedding per il prompt combinato
		embedding, err := getEmbedding(rerankPrompt, rerankingModel, ollamaBaseURL)
		if err != nil {
			fmt.Printf("Avviso: Impossibile ottenere l'embedding per il reranking del documento %d: %v\n", i, err)
			// Valore di fallback per un punteggio neutrale
			rerankedDocs[i].Score = 0.5
			continue
		}

		// Utilizzare la magnitudo dell'embedding come punteggio di rilevanza
		// (Questo è un approccio semplificato - in pratica si utilizzerebbe un reranker addestrato)
		score := calculateRelevanceScore(embedding)
		rerankedDocs[i].Score = score
		// fmt.Printf("Documento %d reranked con punteggio: %.4f\n", i, score)
	}

	// Ordinare i documenti per punteggio di reranking (decrescente)
	sort.Slice(rerankedDocs, func(i, j int) bool {
		return rerankedDocs[i].Score > rerankedDocs[j].Score
	})

	return rerankedDocs, nil
}

func calculateRelevanceScore(embedding []float64) float64 {
	// Punteggio semplice basato sulla magnitudo dell'embedding e sui valori positivi
	var sumPositive, sumTotal float64
	for _, val := range embedding {
		sumTotal += val * val
		if val > 0 {
			sumPositive += val
		}
	}

	if sumTotal == 0 {
		return 0
	}

	// Normalizzazione e combinazione della magnitudo con un bias positivo
	magnitude := math.Sqrt(sumTotal) / float64(len(embedding))
	positiveRatio := sumPositive / float64(len(embedding))

	return (magnitude + positiveRatio) / 2
}

Non dimenticare di importare un po’ di math

import (
	"math"
)

Ora compiliamo

go build -o rnk

e ora eseguiamo questo semplice prototipo di RAG reranker

./rnk ./example_query.txt ./example_docs