Reranking de textos com Ollama e Qwen3 Embedding LLM - em Go
Implementando RAG? Aqui estão alguns trechos de código em Golang.
Este pequeno exemplo de código Go para reranking está chamando o Ollama para gerar embeddings para a consulta e para cada documento candidato, em seguida, classificando em ordem decrescente pela similaridade do cosseno.
Já fizemos uma atividade semelhante - Reranking com modelos de embedding mas isso foi em Python, com um LLM diferente e quase um ano atrás.
Outro código semelhante, mas usando o Qwen3 Reranker:
TL;DR
O resultado parece muito bom, a velocidade é de 0,128s por documento. A pergunta é contada como um documento. E a classificação e impressão também estão incluídas nessa estatística.
Consumo de memória do LLM:
Mesmo que o tamanho do modelo no SSD (ollama ls
) seja menor que 3GB
dengcao/Qwen3-Embedding-4B:Q5_K_M 7e8c9ad6885b 2,9 GB
Na VRAM da GPU ele ocupa (não um pouco) mais: 5,5GB. (ollama ps
)
NOME ID TAMANHO
dengcao/Qwen3-Embedding-4B:Q5_K_M 7e8c9ad6885b 5,5 GB
Se você tiver uma GPU de 8GB - deve estar tudo bem.
Testando Reranking com Embeddings no Ollama - Saída de Exemplo
Nos três casos de teste, o reranking com embedding usando o modelo Ollama dengcao/Qwen3-Embedding-4B:Q5_K_M foi excelente! Veja por si mesmo.
Temos 7 arquivos contendo alguns textos que descrevem o que o nome do arquivo diz:
- ai_introduction.txt
- machine_learning.md
- qwen3-reranking-models.md
- ollama-parallelism.md
- ollama-reranking-models.md
- programming_basics.txt
- setup.log
execuções de teste:
Teste de reranking: O que é inteligência artificial e como a aprendizagem de máquina funciona?
./rnk example_query.txt example_docs/
Usando modelo de embedding: dengcao/Qwen3-Embedding-4B:Q5_K_M
URL base do Ollama: http://localhost:11434
Processando arquivo de consulta: example_query.txt, diretório-alvo: example_docs/
Consulta: O que é inteligência artificial e como a aprendizagem de máquina funciona?
Encontrado 7 documentos
Extraindo embedding da consulta...
Processando documentos...
=== CLASSIFICAÇÃO POR SIMILARIDADE ===
1. example_docs/ai_introduction.txt (Pontuação: 0,451)
2. example_docs/machine_learning.md (Pontuação: 0,388)
3. example_docs/qwen3-reranking-models.md (Pontuação: 0,354)
4. example_docs/ollama-parallelism.md (Pontuação: 0,338)
5. example_docs/ollama-reranking-models.md (Pontuação: 0,318)
6. example_docs/programming_basics.txt (Pontuação: 0,296)
7. example_docs/setup.log (Pontuação: 0,282)
Processado 7 documentos em 0,899s (média: 0,128s por documento)
Teste de reranking: Como o Ollama lida com solicitações paralelas?
./rnk example_query2.txt example_docs/
Usando modelo de embedding: dengcao/Qwen3-Embedding-4B:Q5_K_M
URL base do Ollama: http://localhost:11434
Processando arquivo de consulta: example_query2.txt, diretório-alvo: example_docs/
Consulta: Como o Ollama lida com solicitações paralelas?
Encontrado 7 documentos
Extraindo embedding da consulta...
Processando documentos...
=== CLASSIFICAÇÃO POR SIMILARIDADE ===
1. example_docs/ollama-parallelism.md (Pontuação: 0,557)
2. example_docs/qwen3-reranking-models.md (Pontuação: 0,532)
3. example_docs/ollama-reranking-models.md (Pontuação: 0,498)
4. example_docs/ai_introduction.txt (Pontuação: 0,366)
5. example_docs/machine_learning.md (Pontuação: 0,332)
6. example_docs/programming_basics.txt (Pontuação: 0,307)
7. example_docs/setup.log (Pontuação: 0,257)
Processado 7 documentos em 0,858s (média: 0,123s por documento)
Teste de reranking: Como podemos fazer o reranking do documento com o Ollama?
./rnk example_query3.txt example_docs/
Usando modelo de embedding: dengcao/Qwen3-Embedding-4B:Q5_K_M
URL base do Ollama: http://localhost:11434
Processando arquivo de consulta: example_query3.txt, diretório-alvo: example_docs/
Consulta: Como podemos fazer o reranking do documento com o Ollama?
Encontrado 7 documentos
Extraindo embedding da consulta...
Processando documentos...
=== CLASSIFICAÇÃO POR SIMILARIDADE ===
1. example_docs/ollama-reranking-models.md (Pontuação: 0,552)
2. example_docs/ollama-parallelism.md (Pontuação: 0,525)
3. example_docs/qwen3-reranking-models.md (Pontuação: 0,524)
4. example_docs/ai_introduction.txt (Pontuação: 0,369)
5. example_docs/machine_learning.md (Pontuação: 0,346)
6. example_docs/programming_basics.txt (Pontuação: 0,316)
7. example_docs/setup.log (Pontuação: 0,279)
Processado 7 documentos em 0,882s (média: 0,126s por documento)
Código-fonte em Go
Coloque tudo em uma pasta e compile-o como
go build -o rnk
Sinta-se à vontade para usá-lo em qualquer propósito recreativo ou comercial ou carregá-lo no GitHub se quiser. Licença 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: "Sistema RAG usando embeddings do Ollama",
Long: "Um sistema RAG simples que extrai embeddings e classifica documentos usando o 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", "Modelo de embedding a ser usado")
rootCmd.Flags().StringVarP(&ollamaBaseURL, "url", "u", "http://localhost:11434", "URL base do 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("Usando modelo de embedding: %s\n", embeddingModel)
fmt.Printf("URL base do Ollama: %s\n", ollamaBaseURL)
fmt.Printf("Processando arquivo de consulta: %s, diretório-alvo: %s\n", queryFile, targetDir)
// Ler consulta do arquivo
query, err := readQueryFromFile(queryFile)
if err != nil {
log.Fatalf("Erro ao ler arquivo de consulta: %v", err)
}
fmt.Printf("Consulta: %s\n", query)
// Encontrar todos os arquivos de texto no diretório-alvo
documents, err := findTextFiles(targetDir)
if err != nil {
log.Fatalf("Erro ao encontrar arquivos de texto: %v", err)
}
fmt.Printf("Encontrado %d documentos\n", len(documents))
// Extrair embeddings para a consulta
fmt.Println("Extraindo embedding da consulta...")
queryEmbedding, err := getEmbedding(query, embeddingModel, ollamaBaseURL)
if err != nil {
log.Fatalf("Erro ao obter embedding da consulta: %v", err)
}
// Processar documentos
fmt.Println("Processando documentos...")
validDocs := make([]Document, 0)
for _, doc := range documents {
embedding, err := getEmbedding(doc.Content, embeddingModel, ollamaBaseURL)
if err != nil {
fmt.Printf("Aviso: Falha ao obter embedding para %s: %v\n", doc.Path, err)
continue
}
similarity := cosineSimilarity(queryEmbedding, embedding)
doc.Score = similarity
validDocs = append(validDocs, doc)
}
if len(validDocs) == 0 {
log.Fatalf("Nenhum documento pôde ser processado com sucesso")
}
// Classificar por pontuação de similaridade (ordem decrescente)
sort.Slice(validDocs, func(i, j int) bool {
return validDocs[i].Score > validDocs[j].Score
})
// Exibir resultados
fmt.Println("\n=== CLASSIFICAÇÃO POR SIMILARIDADE ===")
for i, doc := range validDocs {
fmt.Printf("%d. %s (Pontuação: %.3f)\n", i+1, doc.Path, doc.Score)
}
totalTime := time.Since(startTime)
avgTimePerDoc := totalTime / time.Duration(len(validDocs))
fmt.Printf("\nProcessado %d documentos em %.3fs (média: %.3fs por documento)\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("Aviso: Não foi possível ler o arquivo %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("erro da API do 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 representa a carga útil da requisição para a API de embedding do Ollama
type OllamaEmbeddingRequest struct {
Model string `json:"model"`
Prompt string `json:"prompt"`
}
// OllamaEmbeddingResponse representa a resposta da API de embedding do Ollama
type OllamaEmbeddingResponse struct {
Embedding []float64 `json:"embedding"`
}
// Documento representa um documento com seus metadados
type Document struct {
Path string
Content string
Score float64
}
Links úteis
- Folha de dicas do Ollama
- Reranking de documentos de texto com Ollama e modelo Qwen3 Reranker - em Go
- Modelos Qwen3 de Embedding e Reranker no Ollama: Desempenho de ponta
- https://en.wikipedia.org/wiki/Retrieval-augmented_generation
- Instale e configure a localização dos modelos do Ollama
- Como o Ollama lida com solicitações paralelas
- Escrevendo prompts eficazes para LLMs
- Testando LLMs: gemma2, qwen2 e Mistral Nemo no Ollama
- Comparação de LLMs: Mistral Small, Gemma 2, Qwen 2.5, Mistral Nemo, LLama3 e Phi - no Ollama
- Teste: Como o Ollama está usando o desempenho do processador Intel e núcleos eficientes
- Reranking com modelos de embedding no Ollama em Python
- Comparando as habilidades de resumo de LLMs
- Fornecedores de LLMs em nuvem