Ponowne rankowanie tekstów przy użyciu Ollama i Qwen3 Embedding LLM – w Go
Wdrażasz RAG? Oto kilka fragmentów kodu w języku Golang.
Ten niewielki przykład kodu Go do rerankingu wywołuje Ollamę do generowania wektorów dla zapytania oraz dla każdego dokumentu kandydackiego, następnie sortuje wyniki malejąco według podobieństwa kosinusowego.
Już wcześniej wykonaliśmy podobną aktywność – Reranking z modelami embeddingowymi, ale wtedy w Pythonie, z innym LLM i prawie rok temu.
Dla kompletnego przewodnika o budowaniu systemów generacji z wzmocnieniem odzyskiwaniem (RAG), zobacz Tutorial Retrieval-Augmented Generation (RAG): Architektura, Implementacja i Przewodnik Produkcyjny.
Kolejny podobny kod, ale używający modelu Qwen3 Reranker:

TL;DR
Wynik wygląda bardzo dobrze, szybkość wynosi 0,128 s na dokument. Zapytanie jest liczone jako dokument. Do tego statystyki uwzględnione są również sortowanie i wyświetlanie.
Zużycie pamięci LLM:
Mimo że rozmiar modelu na dysku (ollama ls) jest mniejszy niż 3 GB
dengcao/Qwen3-Embedding-4B:Q5_K_M 7e8c9ad6885b 2.9 GB
W pamięci GPU VRAM zajmuje (nieco) więcej: 5,5 GB. (ollama ps)
NAME ID SIZE
dengcao/Qwen3-Embedding-4B:Q5_K_M 7e8c9ad6885b 5.5 GB
Jeśli masz GPU z 8 GB, powinno być OK.
Testowanie rerankingu z embeddingami na Ollamie - Przykładowy wynik
We wszystkich trzech przypadkach testowych reranking z embeddingami przy użyciu modelu ollama dengcao/Qwen3-Embedding-4B:Q5_K_M był świetny! Sprawdźcie to sami.
Mamy 7 plików zawierających teksty opisujące, co sugeruje ich nazwa:
- ai_introduction.txt
- machine_learning.md
- qwen3-reranking-models.md
- ollama-parallelism.md
- ollama-reranking-models.md
- programming_basics.txt
- setup.log
wykonywane testy:
Test rerankingu: Co to jest sztuczna inteligencja i jak działa uczenie maszynowe?
./rnk example_query.txt example_docs/
Używanie modelu embeddingowego: dengcao/Qwen3-Embedding-4B:Q5_K_M
Bazowy URL Ollama: http://localhost:11434
Przetwarzanie pliku zapytania: example_query.txt, katalog docelowy: example_docs/
Zapytanie: Co to jest sztuczna inteligencja i jak działa uczenie maszynowe?
Znaleziono 7 dokumentów
Pobieranie wektora zapytania...
Przetwarzanie dokumentów...
=== RANKING WEDŁUG PODOBIENSTWA ===
1. example_docs/ai_introduction.txt (Wynik: 0.451)
2. example_docs/machine_learning.md (Wynik: 0.388)
3. example_docs/qwen3-reranking-models.md (Wynik: 0.354)
4. example_docs/ollama-parallelism.md (Wynik: 0.338)
5. example_docs/ollama-reranking-models.md (Wynik: 0.318)
6. example_docs/programming_basics.txt (Wynik: 0.296)
7. example_docs/setup.log (Wynik: 0.282)
Przetworzono 7 dokumentów w 0.899s (średnio: 0.128s na dokument)
Test rerankingu: Jak Ollama obsługuje równoległe żądania?
./rnk example_query2.txt example_docs/
Używanie modelu embeddingowego: dengcao/Qwen3-Embedding-4B:Q5_K_M
Bazowy URL Ollama: http://localhost:11434
Przetwarzanie pliku zapytania: example_query2.txt, katalog docelowy: example_docs/
Zapytanie: Jak Ollama obsługuje równoległe żądania?
Znaleziono 7 dokumentów
Pobieranie wektora zapytania...
Przetwarzanie dokumentów...
=== RANKING WEDŁUG PODOBIENSTWA ===
1. example_docs/ollama-parallelism.md (Wynik: 0.557)
2. example_docs/qwen3-reranking-models.md (Wynik: 0.532)
3. example_docs/ollama-reranking-models.md (Wynik: 0.498)
4. example_docs/ai_introduction.txt (Wynik: 0.366)
5. example_docs/machine_learning.md (Wynik: 0.332)
6. example_docs/programming_basics.txt (Wynik: 0.307)
7. example_docs/setup.log (Wynik: 0.257)
Przetworzono 7 dokumentów w 0.858s (średnio: 0.123s na dokument)
Test rerankingu: Jak wykonać reranking dokumentów z Ollamą?
./rnk example_query3.txt example_docs/
Używanie modelu embeddingowego: dengcao/Qwen3-Embedding-4B:Q5_K_M
Bazowy URL Ollama: http://localhost:11434
Przetwarzanie pliku zapytania: example_query3.txt, katalog docelowy: example_docs/
Zapytanie: Jak wykonać reranking dokumentów z Ollamą?
Znaleziono 7 dokumentów
Pobieranie wektora zapytania...
Przetwarzanie dokumentów...
=== RANKING WEDŁUG PODOBIENSTWA ===
1. example_docs/ollama-reranking-models.md (Wynik: 0.552)
2. example_docs/ollama-parallelism.md (Wynik: 0.525)
3. example_docs/qwen3-reranking-models.md (Wynik: 0.524)
4. example_docs/ai_introduction.txt (Wynik: 0.369)
5. example_docs/machine_learning.md (Wynik: 0.346)
6. example_docs/programming_basics.txt (Wynik: 0.316)
7. example_docs/setup.log (Wynik: 0.279)
Przetworzono 7 dokumentów w 0.882s (średnio: 0.126s na dokument)
Kod źródłowy Go
Umieść wszystko w folderze i skompiluj tak:
go build -o rnk
Zachęcam do użycia tego w celach rozrywkowych lub komercyjnych lub wrzucenia na GitHub, jeśli chcesz. Licencja MIT.
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: "System RAG używający embeddingów Ollama",
Long: "Prosty system RAG, który ekstrahuje embeddingi i rankuje dokumenty używając Ollama",
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", "Model embeddingowy do użycia")
rootCmd.Flags().StringVarP(&ollamaBaseURL, "url", "u", "http://localhost:11434", "Bazowy URL Ollama")
}
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("Używanie modelu embeddingowego: %s\n", embeddingModel)
fmt.Printf("Bazowy URL Ollama: %s\n", ollamaBaseURL)
fmt.Printf("Przetwarzanie pliku zapytania: %s, katalog docelowy: %s\n", queryFile, targetDir)
// Odczytanie zapytania z pliku
query, err := readQueryFromFile(queryFile)
if err != nil {
log.Fatalf("Błąd odczytu pliku zapytania: %v", err)
}
fmt.Printf("Zapytanie: %s\n", query)
// Znalezienie wszystkich plików tekstowych w katalogu docelowym
documents, err := findTextFiles(targetDir)
if err != nil {
log.Fatalf("Błąd wyszukiwania plików tekstowych: %v", err)
}
fmt.Printf("Znaleziono %d dokumentów\n", len(documents))
// Ekstrakcja embeddingów dla zapytania
fmt.Println("Pobieranie wektora zapytania...")
queryEmbedding, err := getEmbedding(query, embeddingModel, ollamaBaseURL)
if err != nil {
log.Fatalf("Błąd pobierania wektora zapytania: %v", err)
}
// Przetwarzanie dokumentów
fmt.Println("Przetwarzanie dokumentów...")
validDocs := make([]Document, 0)
for _, doc := range documents {
embedding, err := getEmbedding(doc.Content, embeddingModel, ollamaBaseURL)
if err != nil {
fmt.Printf("Ostrzeżenie: Nie udało się pobrać wektora dla %s: %v\n", doc.Path, err)
continue
}
similarity := cosineSimilarity(queryEmbedding, embedding)
doc.Score = similarity
validDocs = append(validDocs, doc)
}
if len(validDocs) == 0 {
log.Fatalf("Żaden dokument nie mógł zostać pomyślnie przetworzony")
}
// Sortowanie według wyniku podobieństwa (malejąco)
sort.Slice(validDocs, func(i, j int) bool {
return validDocs[i].Score > validDocs[j].Score
})
// Wyświetlenie wyników
fmt.Println("\n=== RANKING WEDŁUG PODOBIENSTWA ===")
for i, doc := range validDocs {
fmt.Printf("%d. %s (Wynik: %.3f)\n", i+1, doc.Path, doc.Score)
}
totalTime := time.Since(startTime)
avgTimePerDoc := totalTime / time.Duration(len(validDocs))
fmt.Printf("\nPrzetworzono %d dokumentów w %.3fs (średnio: %.3fs na 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("Ostrzeżenie: Nie można odczytać pliku %s: %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("błąd API Ollama: %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 reprezentuje obciążenie żądania dla API embeddingowego Ollama
type OllamaEmbeddingRequest struct {
Model string `json:"model"`
Prompt string `json:"prompt"`
}
// OllamaEmbeddingResponse reprezentuje odpowiedź od API embeddingowego Ollama
type OllamaEmbeddingResponse struct {
Embedding []float64 `json:"embedding"`
}
// Document reprezentuje dokument z jego metadanymi
type Document struct {
Path string
Content string
Score float64
}
Przydatne linki
- Karta powtórek Ollama
- Reranking dokumentów tekstowych z Ollamą i modelem Qwen3 Reranker - w Go
- Modele Qwen3 Embedding i Reranker na Ollamie: Wiodąca wydajność
- https://pl.wikipedia.org/wiki/Generowanie_z_wzmocnieniem_odzyskiwaniem
- Zainstaluj i skonfiguruj lokalizację modeli Ollama
- Pisanie skutecznych promptów dla LLM
- Testowanie LLM: gemma2, qwen2 i Mistral Nemo na Ollamie
- Porównanie LLM: Mistral Small, Gemma 2, Qwen 2.5, Mistral Nemo, LLama3 i Phi - Na Ollamie
- Test: Jak Ollama wykorzystuje wydajność procesora Intel i jądra wydajnościowe
- Reranking z modelami embeddingowymi na Ollamie w Pythonie
- Porównanie zdolności podsumowujących LLM
- Dostawcy LLM w chmurze