Reranking teks dengan Ollama dan Qwen3 Embedding LLM - dalam Go

Menerapkan RAG? Berikut adalah beberapa contoh kode dalam Golang...

Konten Halaman

Ini sedikit Contoh kode Go untuk reranking memanggil Ollama untuk menghasilkan embedding untuk query dan setiap dokumen kandidat, kemudian mengurutkan menurun berdasarkan kesamaan kosinus.

Kami sudah melakukan aktivitas serupa - Reranking dengan model embedding tetapi itu dalam python, dengan LLM yang berbeda dan hampir setahun yang lalu.

Kode serupa lainnya, tetapi menggunakan Qwen3 Reranker:

llamas dengan ketinggian berbeda - reranking dengan ollama

TL;DR

Hasilnya terlihat sangat baik, kecepatannya adalah 0,128 detik per dokumen. Pertanyaan dihitung sebagai dokumen. Dan pengurutan dan pencetakan juga termasuk dalam statistik ini.

Konsumsi memori LLM: Meskipun ukuran model di sdd (ollama ls) kurang dari 3 GB

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

Di GPU VRAM membutuhkan (tidak sedikit) lebih: 5,5 GB. (ollama ps)

NAMA                                 ID              UKURAN
dengcao/Qwen3-Embedding-4B:Q5_K_M    7e8c9ad6885b    5,5 GB 

Jika Anda memiliki GPU 8 GB - seharusnya baik-baik saja.

Uji Coba Reranking dengan Embedding di Ollama - Output Contoh

Dalam semua tiga kasus uji reranking dengan embedding menggunakan model ollama dengcao/Qwen3-Embedding-4B:Q5_K_M sangat luar biasa! Lihat sendiri.

Kami memiliki 7 file yang berisi beberapa teks yang menggambarkan apa yang disebutkan oleh nama filenya:

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

jalannya uji:

Uji reranking: Apa itu kecerdasan buatan dan bagaimana mesin belajar bekerja?

./rnk example_query.txt example_docs/

Menggunakan model embedding: dengcao/Qwen3-Embedding-4B:Q5_K_M
URL dasar Ollama: http://localhost:11434
Memproses file query: example_query.txt, direktori target: example_docs/
Query: Apa itu kecerdasan buatan dan bagaimana mesin belajar bekerja?
Ditemukan 7 dokumen
Mengekstrak embedding query...
Memproses dokumen...

=== PEMBAGIAN BERDASARKAN KESAMAAN ===
1. example_docs/ai_introduction.txt (Skor: 0,451)
2. example_docs/machine_learning.md (Skor: 0,388)
3. example_docs/qwen3-reranking-models.md (Skor: 0,354)
4. example_docs/ollama-parallelism.md (Skor: 0,338)
5. example_docs/ollama-reranking-models.md (Skor: 0,318)
6. example_docs/programming_basics.txt (Skor: 0,296)
7. example_docs/setup.log (Skor: 0,282)

Memproses 7 dokumen dalam 0,899s (rata-rata: 0,128s per dokumen)

Uji reranking: Bagaimana ollama menangani permintaan paralel?

./rnk example_query2.txt example_docs/

Menggunakan model embedding: dengcao/Qwen3-Embedding-4B:Q5_K_M
URL dasar Ollama: http://localhost:11434
Memproses file query: example_query2.txt, direktori target: example_docs/
Query: Bagaimana ollama menangani permintaan paralel?
Ditemukan 7 dokumen
Mengekstrak embedding query...
Memproses dokumen...

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

Memproses 7 dokumen dalam 0,858s (rata-rata: 0,123s per dokumen)

Uji reranking: Bagaimana kita dapat melakukan reranking dokumen dengan ollama?

./rnk example_query3.txt example_docs/

Menggunakan model embedding: dengcao/Qwen3-Embedding-4B:Q5_K_M
URL dasar Ollama: http://localhost:11434
Memproses file query: example_query3.txt, direktori target: example_docs/
Query: Bagaimana kita dapat melakukan reranking dokumen dengan ollama?
Ditemukan 7 dokumen
Mengekstrak embedding query...
Memproses dokumen...

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

Memproses 7 dokumen dalam 0,882s (rata-rata: 0,126s per dokumen)

Kode Sumber Go

Masukkan semua ke dalam sebuah folder dan kompilasi seperti

go build -o rnk

Silakan gunakan dalam tujuan hiburan atau komersial atau unggah ke github jika Anda suka. Lisensi 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: "Sistem RAG menggunakan embedding Ollama",
	Long:  "Sistem RAG sederhana yang mengekstrak embedding dan mengurutkan dokumen menggunakan 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 embedding yang akan digunakan")
	rootCmd.Flags().StringVarP(&ollamaBaseURL, "url", "u", "http://localhost:11434", "URL dasar 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("Menggunakan model embedding: %s\n", embeddingModel)
	fmt.Printf("URL dasar Ollama: %s\n", ollamaBaseURL)
	fmt.Printf("Memproses file query: %s, direktori target: %s\n", queryFile, targetDir)

	// Membaca query dari file
	query, err := readQueryFromFile(queryFile)
	if err != nil {
		log.Fatalf("Kesalahan membaca file query: %v", err)
	}
	fmt.Printf("Query: %s\n", query)

	// Mencari semua file teks dalam direktori target
	documents, err := findTextFiles(targetDir)
	if err != nil {
		log.Fatalf("Kesalahan mencari file teks: %v", err)
	}
	fmt.Printf("Ditemukan %d dokumen\n", len(documents))

	// Mengekstrak embedding untuk query
	fmt.Println("Mengekstrak embedding query...")
	queryEmbedding, err := getEmbedding(query, embeddingModel, ollamaBaseURL)
	if err != nil {
		log.Fatalf("Kesalahan mendapatkan embedding query: %v", err)
	}

	// Memproses dokumen
	fmt.Println("Memproses dokumen...")
	validDocs := make([]Document, 0)

	for _, doc := range documents {
		embedding, err := getEmbedding(doc.Content, embeddingModel, ollamaBaseURL)
		if err != nil {
			fmt.Printf("Peringatan: Gagal mendapatkan embedding untuk %s: %v\n", doc.Path, err)
			continue
		}

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

	if len(validDocs) == 0 {
		log.Fatalf("Tidak ada dokumen yang dapat diproses dengan sukses")
	}

	// Mengurutkan berdasarkan skor kesamaan (menurun)
	sort.Slice(validDocs, func(i, j int) bool {
		return validDocs[i].Score > validDocs[j].Score
	})

	// Menampilkan hasil
	fmt.Println("\n=== PEMBAGIAN BERDASARKAN KESAMAAN ===")
	for i, doc := range validDocs {
		fmt.Printf("%d. %s (Skor: %.3f)\n", i+1, doc.Path, doc.Score)
	}

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

	fmt.Printf("\nMemproses %d dokumen dalam %.3fs (rata-rata: %.3fs per dokumen)\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("Peringatan: Tidak dapat membaca file %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("kesalahan 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 merepresentasikan payload permintaan untuk API embedding Ollama
type OllamaEmbeddingRequest struct {
	Model  string `json:"model"`
	Prompt string `json:"prompt"`
}

// OllamaEmbeddingResponse merepresentasikan respons dari API embedding Ollama
type OllamaEmbeddingResponse struct {
	Embedding []float64 `json:"embedding"`
}

// Document merepresentasikan dokumen dengan metadatanya
type Document struct {
	Path    string
	Content string
	Score   float64
}

Tautan yang Berguna