Texte mit Ollama und Qwen3 Embedding LLM neu rangieren – in Go

RAG umsetzen? Hier sind einige Codeausschnitte in Golang.

Inhaltsverzeichnis

Dieses kleine Reranking Go-Codebeispiel ruft Ollama auf, um Embeddings zu generieren für die Abfrage und für jede Kandidatendokumente, dann Sortieren nach absteigender Kosinusähnlichkeit.

Wir haben bereits eine ähnliche Aktivität durchgeführt - Reranking mit Embedding-Modellen aber das war in Python, mit einem anderen LLM und fast ein Jahr alt.

Ein weiteres ähnliches Codebeispiel, aber mit Qwen3 Reranker:

llamas unterschiedlicher Höhe - Reranking mit Ollama

TL;DR

Das Ergebnis sieht sehr gut aus, die Geschwindigkeit beträgt 0,128 Sekunden pro Dokument. Die Frage wird als Dokument gezählt. Und das Sortieren und Drucken sind auch in diese Statistik einbezogen.

LLM-Speicherbedarf: Obwohl die Modellgröße auf der SSD (ollama ls) kleiner als 3 GB ist

dengcao/Qwen3-Embedding-4B:Q5_K_M           7e8c9ad6885b    2,9 GB

Benötigt es in der GPU VRAM (nicht ein bisschen) mehr: 5,5 GB. (ollama ps)

NAME                                 ID              SIZE
dengcao/Qwen3-Embedding-4B:Q5_K_M    7e8c9ad6885b    5,5 GB 

Wenn Sie eine GPU mit 8 GB haben - sollte das in Ordnung sein.

Reranking mit Embeddings auf Ollama - Beispieloutput

In allen drei Testfällen war das Reranking mit Embedding mithilfe des Ollama-Modells dengcao/Qwen3-Embedding-4B:Q5_K_M großartig! Sehen Sie es selbst.

Wir haben 7 Dateien mit Texten, die beschreiben, was ihr Dateiname sagt:

  • ai_introduction.txt
  • machine_learning.md
  • qwen3-reranking-models.md
  • ollama-parallelism.md
  • ollama-reranking-models.md
  • programming_basics.txt
  • setup.log

Testläufe:

Reranking-Test: Was ist künstliche Intelligenz und wie funktioniert maschinelles Lernen?

./rnk example_query.txt example_docs/

Verwendetes Embedding-Modell: dengcao/Qwen3-Embedding-4B:Q5_K_M
Ollama-Grund-URL: http://localhost:11434
Verarbeite Abfrage-Datei: example_query.txt, Zielverzeichnis: example_docs/
Abfrage: Was ist künstliche Intelligenz und wie funktioniert maschinelles Lernen?
7 Dokumente gefunden
Extrahiere Abfrage-Embedding...
Verarbeite Dokumente...

=== RANGLISTE NACH ÄHNLICHKEIT ===
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)

7 Dokumente verarbeitet in 0,899s (Durchschnitt: 0,128s pro Dokument)

Reranking-Test: Wie verarbeitet Ollama parallele Anfragen?

./rnk example_query2.txt example_docs/

Verwendetes Embedding-Modell: dengcao/Qwen3-Embedding-4B:Q5_K_M
Ollama-Grund-URL: http://localhost:11434
Verarbeite Abfrage-Datei: example_query2.txt, Zielverzeichnis: example_docs/
Abfrage: Wie verarbeitet Ollama parallele Anfragen?
7 Dokumente gefunden
Extrahiere Abfrage-Embedding...
Verarbeite Dokumente...

=== RANGLISTE NACH ÄHNLICHKEIT ===
1. example_docs/ollama-parallelism.md (Score: 0,557)
2. example_docs/qwen3-reranking-models.md (Score: 0,532)
3. example_docs/ollama-reranking-models.md (Score: 0,498)
4. example_docs/ai_introduction.txt (Score: 0,366)
5. example_docs/machine_learning.md (Score: 0,332)
6. example_docs/programming_basics.txt (Score: 0,307)
7. example_docs/setup.log (Score: 0,257)

7 Dokumente verarbeitet in 0,858s (Durchschnitt: 0,123s pro Dokument)

Reranking-Test: Wie können wir das Reranking eines Dokuments mit Ollama durchführen?

./rnk example_query3.txt example_docs/

Verwendetes Embedding-Modell: dengcao/Qwen3-Embedding-4B:Q5_K_M
Ollama-Grund-URL: http://localhost:11434
Verarbeite Abfrage-Datei: example_query3.txt, Zielverzeichnis: example_docs/
Abfrage: Wie können wir das Reranking eines Dokuments mit Ollama durchführen?
7 Dokumente gefunden
Extrahiere Abfrage-Embedding...
Verarbeite Dokumente...

=== RANGLISTE NACH ÄHNLICHKEIT ===
1. example_docs/ollama-reranking-models.md (Score: 0,552)
2. example_docs/ollama-parallelism.md (Score: 0,525)
3. example_docs/qwen3-reranking-models.md (Score: 0,524)
4. example_docs/ai_introduction.txt (Score: 0,369)
5. example_docs/machine_learning.md (Score: 0,346)
6. example_docs/programming_basics.txt (Score: 0,316)
7. example_docs/setup.log (Score: 0,279)

7 Dokumente verarbeitet in 0,882s (Durchschnitt: 0,126s pro Dokument)

Go-Quellcode

Legen Sie alles in einen Ordner und kompilieren Sie es wie folgt

go build -o rnk

Sie können es frei in jedem unterhaltsamen oder kommerziellen Zweck verwenden oder es bei Bedarf auf GitHub hochladen. MIT-Lizenz.

main.go

package main

import (
	"fmt"
	"log"
	"os"
	"sort"
	"time"

	"github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
	Use:   "rnk [query-file] [target-directory]",
	Short: "RAG-System mit Ollama-Embeddings",
	Long:  "Ein einfaches RAG-System, das Embeddings extrahiert und Dokumente mithilfe von Ollama rangiert",
	Args:  cobra.ExactArgs(2),
	Run:   runRnk,
}

var (
	embeddingModel string
	ollamaBaseURL  string
)

func init() {
	rootCmd.Flags().StringVarP(&embeddingModel, "model", "m", "dengcao/Qwen3-Embedding-4B:Q5_K_M", "Embedding-Modell, das verwendet werden soll")
	rootCmd.Flags().StringVarP(&ollamaBaseURL, "url", "u", "http://localhost:11434", "Ollama-Grund-URL")
}

func main() {
	if err := rootCmd.Execute(); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}

func runRnk(cmd *cobra.Command, args []string) {
	queryFile := args[0]
	targetDir := args[1]

	startTime := time.Now()

	fmt.Printf("Verwendetes Embedding-Modell: %s\n", embeddingModel)
	fmt.Printf("Ollama-Grund-URL: %s\n", ollamaBaseURL)
	fmt.Printf("Verarbeite Abfrage-Datei: %s, Zielverzeichnis: %s\n", queryFile, targetDir)

	// Lesen Sie die Abfrage aus der Datei
	query, err := readQueryFromFile(queryFile)
	if err != nil {
		log.Fatalf("Fehler beim Lesen der Abfragedatei: %v", err)
	}
	fmt.Printf("Abfrage: %s\n", query)

	// Finden Sie alle Textdateien im Zielverzeichnis
	documents, err := findTextFiles(targetDir)
	if err != nil {
		log.Fatalf("Fehler beim Finden der Textdateien: %v", err)
	}
	fmt.Printf("Gefunden: %d Dokumente\n", len(documents))

	// Extrahieren Sie Embeddings für die Abfrage
	fmt.Println("Extrahiere Abfrage-Embedding...")
	queryEmbedding, err := getEmbedding(query, embeddingModel, ollamaBaseURL)
	if err != nil {
		log.Fatalf("Fehler beim Extrahieren des Abfrage-Embeddings: %v", err)
	}

	// Verarbeiten Sie die Dokumente
	fmt.Println("Verarbeite Dokumente...")
	validDocs := make([]Document, 0)

	for _, doc := range documents {
		embedding, err := getEmbedding(doc.Content, embeddingModel, ollamaBaseURL)
		if err != nil {
			fmt.Printf("Warnung: Fehlgeschlagen, Embedding für %s zu erhalten: %v\n", doc.Path, err)
			continue
		}

		similarity := cosineSimilarity(queryEmbedding, embedding)
		doc.Score = similarity
		validDocs = append(validDocs, doc)
	}

	if len(validDocs) == 0 {
		log.Fatalf("Keine Dokumente konnten erfolgreich verarbeitet werden")
	}

	// Sortieren Sie nach Ähnlichkeitspunktzahl (absteigend)
	sort.Slice(validDocs, func(i, j int) bool {
		return validDocs[i].Score > validDocs[j].Score
	})

	// Zeigen Sie die Ergebnisse an
	fmt.Println("\n=== RANGLISTE NACH ÄHNLICHKEIT ===")
	for i, doc := range validDocs {
		fmt.Printf("%d. %s (Score: %.3f)\n", i+1, doc.Path, doc.Score)
	}

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

	fmt.Printf("\nVerarbeitet %d Dokumente in %.3fs (Durchschnitt: %.3fs pro Dokument)\n",
		len(validDocs), totalTime.Seconds(), avgTimePerDoc.Seconds())
}

documents.go

package main

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
)

func readQueryFromFile(filename string) (string, error) {
	content, err := os.ReadFile(filename)
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(string(content)), nil
}

func findTextFiles(dir string) ([]Document, error) {
	var documents []Document

	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if !info.IsDir() && isTextFile(path) {
			content, err := os.ReadFile(path)
			if err != nil {
				fmt.Printf("Warnung: Datei %s konnte nicht gelesen werden: %v\n", path, err)
				return nil
			}

			documents = append(documents, Document{
				Path:    path,
				Content: string(content),
			})
		}

		return nil
	})

	return documents, err
}

func isTextFile(filename string) bool {
	ext := strings.ToLower(filepath.Ext(filename))
	textExts := []string{".txt", ".md", ".rst", ".csv", ".json", ".xml", ".html", ".htm", ".log"}
	for _, textExt := range textExts {
		if ext == textExt {
			return true
		}
	}
	return false
}

embeddings.go

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
)

func getEmbedding(text string, model string, ollamaBaseURL string) ([]float64, error) {
	req := OllamaEmbeddingRequest{
		Model:  model,
		Prompt: text,
	}

	jsonData, err := json.Marshal(req)
	if err != nil {
		return nil, err
	}

	resp, err := http.Post(ollamaBaseURL+"/api/embeddings", "application/json", bytes.NewBuffer(jsonData))
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		return nil, fmt.Errorf("Ollama API-Fehler: %s", string(body))
	}

	var embeddingResp OllamaEmbeddingResponse
	if err := json.NewDecoder(resp.Body).Decode(&embeddingResp); err != nil {
		return nil, err
	}

	return embeddingResp.Embedding, nil
}

similarity.go

package main

func cosineSimilarity(a, b []float64) float64 {
	if len(a) != len(b) {
		return 0
	}

	var dotProduct, normA, normB float64

	for i := range a {
		dotProduct += a[i] * b[i]
		normA += a[i] * a[i]
		normB += b[i] * b[i]
	}

	if normA == 0 || normB == 0 {
		return 0
	}

	return dotProduct / (sqrt(normA) * sqrt(normB))
}

func sqrt(x float64) float64 {
	if x == 0 {
		return 0
	}
	z := x
	for i := 0; i < 10; i++ {
		z = (z + x/z) / 2
	}
	return z
}

types.go

package main

// OllamaEmbeddingRequest stellt die Anforderungslast für die Ollama-Embedding-API dar
type OllamaEmbeddingRequest struct {
	Model  string `json:"model"`
	Prompt string `json:"prompt"`
}

// OllamaEmbeddingResponse stellt die Antwort von der Ollama-Embedding-API dar
type OllamaEmbeddingResponse struct {
	Embedding []float64 `json:"embedding"`
}

// Document stellt ein Dokument mit seinen Metadaten dar
type Document struct {
	Path    string
	Content string
	Score   float64
}