Ollama와 Qwen3 Embedding LLM을 사용한 텍스트 재정렬 - Go로
RAG을 구현 중이시다면, 여기 Golang에서 사용할 수 있는 코드 스니펫 몇 가지가 있습니다.
이 작은
Reranking Go 코드 예제는 Ollama를 호출하여 쿼리와 각 후보 문서에 대한 임베딩을 생성
그런 다음 코사인 유사도에 따라 내림차순으로 정렬합니다.
우리는 이미 유사한 활동을 수행했습니다 - 임베딩 모델을 사용한 Reranking 하지만 이전에는 파이썬을 사용했고, 다른 LLM과 거의 1년 전에 수행했습니다.
또 다른 유사한 코드지만 Qwen3 Reranker를 사용한 예시입니다:
TL;DR
결과는 매우 좋습니다, 문서당 속도는 0.128초입니다. 질문은 문서로 간주됩니다. 정렬 및 출력도 이 통계에 포함됩니다.
LLM 메모리 소비:
모델 크기가 SSD(ollama ls
)에서 3GB 미만이지만
dengcao/Qwen3-Embedding-4B:Q5_K_M 7e8c9ad6885b 2.9 GB
GPU VRAM에서는 (조금도) 더 많이 차지합니다: 5.5GB. (ollama ps
)
NAME ID SIZE
dengcao/Qwen3-Embedding-4B:Q5_K_M 7e8c9ad6885b 5.5 GB
8GB GPU가 있다면 괜찮습니다.
Ollama에서 임베딩을 사용한 Reranking 테스트 - 샘플 출력
3가지 테스트 사례 모두에서 dengcao/Qwen3-Embedding-4B:Q5_K_M Ollama 모델을 사용한 임베딩 기반 Reranking은 매우 훌륭했습니다! 자신의 눈으로 확인해 보세요.
우리는 7개의 파일이 있고, 각 파일의 이름이 무엇을 설명하는지에 대한 텍스트가 포함되어 있습니다:
- ai_introduction.txt
- machine_learning.md
- qwen3-reranking-models.md
- ollama-parallelism.md
- ollama-reranking-models.md
- programming_basics.txt
- setup.log
테스트 실행:
Reranking 테스트: 인공지능이 무엇인지, 머신러닝이 어떻게 작동하는지?
./rnk example_query.txt example_docs/
임베딩 모델 사용: dengcao/Qwen3-Embedding-4B:Q5_K_M
Ollama 기본 URL: http://localhost:11434
쿼리 파일 처리: example_query.txt, 대상 디렉토리: example_docs/
쿼리: 인공지능이 무엇인지, 머신러닝이 어떻게 작동하는지?
7개 문서 발견
쿼리 임베딩 추출...
문서 처리...
=== 유사도에 따른 순위 ===
1. example_docs/ai_introduction.txt (점수: 0.451)
2. example_docs/machine_learning.md (점수: 0.388)
3. example_docs/qwen3-reranking-models.md (점수: 0.354)
4. example_docs/ollama-parallelism.md (점수: 0.338)
5. example_docs/ollama-reranking-models.md (점수: 0.318)
6. example_docs/programming_basics.txt (점수: 0.296)
7. example_docs/setup.log (점수: 0.282)
7개 문서 처리 완료: 0.899초 (평균: 0.128초 문서당)
Reranking 테스트: Ollama가 병렬 요청을 어떻게 처리하는가?
./rnk example_query2.txt example_docs/
임베딩 모델 사용: dengcao/Qwen3-Embedding-4B:Q5_K_M
Ollama 기본 URL: http://localhost:11434
쿼리 파일 처리: example_query2.txt, 대상 디렉토리: example_docs/
쿼리: Ollama가 병렬 요청을 어떻게 처리하는가?
7개 문서 발견
쿼리 임베딩 추출...
문서 처리...
=== 유사도에 따른 순위 ===
1. example_docs/ollama-parallelism.md (점수: 0.557)
2. example_docs/qwen3-reranking-models.md (점수: 0.532)
3. example_docs/ollama-reranking-models.md (점수: 0.498)
4. example_docs/ai_introduction.txt (점수: 0.366)
5. example_docs/machine_learning.md (점수: 0.332)
6. example_docs/programming_basics.txt (점수: 0.307)
7. example_docs/setup.log (점수: 0.257)
7개 문서 처리 완료: 0.858초 (평균: 0.123초 문서당)
Reranking 테스트: Ollama를 사용하여 문서의 Reranking을 어떻게 수행할 수 있는가?
./rnk example_query3.txt example_docs/
임베딩 모델 사용: dengcao/Qwen3-Embedding-4B:Q5_K_M
Ollama 기본 URL: http://localhost:11434
쿼리 파일 처리: example_query3.txt, 대상 디렉토리: example_docs/
쿼리: Ollama를 사용하여 문서의 Reranking을 어떻게 수행할 수 있는가?
7개 문서 발견
쿼리 임베딩 추출...
문서 처리...
=== 유사도에 따른 순위 ===
1. example_docs/ollama-reranking-models.md (점수: 0.552)
2. example_docs/ollama-parallelism.md (점수: 0.525)
3. example_docs/qwen3-reranking-models.md (점수: 0.524)
4. example_docs/ai_introduction.txt (점수: 0.369)
5. example_docs/machine_learning.md (점수: 0.346)
6. example_docs/programming_basics.txt (점수: 0.316)
7. example_docs/setup.log (점수: 0.279)
7개 문서 처리 완료: 0.882초 (평균: 0.126초 문서당)
Go 소스 코드
모든 것을 하나의 폴더에 넣고 다음처럼 컴파일하세요
go build -o rnk
흥미롭거나 상업적인 목적에 자유롭게 사용하거나, 좋아한다면 GitHub에 업로드해도 됩니다. 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: "Ollama 임베딩을 사용한 RAG 시스템",
Long: "Ollama를 사용하여 임베딩을 추출하고 문서를 순위 매기는 간단한 RAG 시스템",
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", "사용할 임베딩 모델")
rootCmd.Flags().StringVarP(&ollamaBaseURL, "url", "u", "http://localhost:11434", "Ollama 기본 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("임베딩 모델 사용: %s\n", embeddingModel)
fmt.Printf("Ollama 기본 URL: %s\n", ollamaBaseURL)
fmt.Printf("쿼리 파일 처리: %s, 대상 디렉토리: %s\n", queryFile, targetDir)
// 파일에서 쿼리 읽기
query, err := readQueryFromFile(queryFile)
if err != nil {
log.Fatalf("쿼리 파일 읽기 오류: %v", err)
}
fmt.Printf("쿼리: %s\n", query)
// 대상 디렉토리에서 모든 텍스트 파일 찾기
documents, err := findTextFiles(targetDir)
if err != nil {
log.Fatalf("텍스트 파일 찾기 오류: %v", err)
}
fmt.Printf("%d개 문서 발견\n", len(documents))
// 쿼리 임베딩 추출
fmt.Println("쿼리 임베딩 추출...")
queryEmbedding, err := getEmbedding(query, embeddingModel, ollamaBaseURL)
if err != nil {
log.Fatalf("쿼리 임베딩 추출 오류: %v", err)
}
// 문서 처리
fmt.Println("문서 처리...")
validDocs := make([]Document, 0)
for _, doc := range documents {
embedding, err := getEmbedding(doc.Content, embeddingModel, ollamaBaseURL)
if err != nil {
fmt.Printf("경고: %s 임베딩 추출 실패: %v\n", doc.Path, err)
continue
}
similarity := cosineSimilarity(queryEmbedding, embedding)
doc.Score = similarity
validDocs = append(validDocs, doc)
}
if len(validDocs) == 0 {
log.Fatalf("처리 가능한 문서가 없습니다")
}
// 유사도 점수에 따라 정렬 (내림차순)
sort.Slice(validDocs, func(i, j int) bool {
return validDocs[i].Score > validDocs[j].Score
})
// 결과 표시
fmt.Println("\n=== 유사도에 따른 순위 ===")
for i, doc := range validDocs {
fmt.Printf("%d. %s (점수: %.3f)\n", i+1, doc.Path, doc.Score)
}
totalTime := time.Since(startTime)
avgTimePerDoc := totalTime / time.Duration(len(validDocs))
fmt.Printf("\n%d개 문서 처리 완료: %.3fs (평균: %.3fs 문서당)\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("경고: 파일 %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("ollama API 오류: %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는 Ollama 임베딩 API 요청 페이로드를 나타냅니다
type OllamaEmbeddingRequest struct {
Model string `json:"model"`
Prompt string `json:"prompt"`
}
// OllamaEmbeddingResponse는 Ollama 임베딩 API 응답을 나타냅니다
type OllamaEmbeddingResponse struct {
Embedding []float64 `json:"embedding"`
}
// Document는 문서의 메타데이터를 나타냅니다
type Document struct {
Path string
Content string
Score float64
}
유용한 링크
- Ollama 간편 가이드
- Ollama와 Qwen3 Reranker 모델을 사용한 텍스트 문서 Reranking - Go로
- Ollama에서 Qwen3 임베딩 및 Reranker 모델: 최첨단 성능
- https://en.wikipedia.org/wiki/Retrieval-augmented_generation
- Ollama 모델 위치 설치 및 구성
- Ollama가 병렬 요청을 어떻게 처리하는가
- LLM에 효과적인 프롬프트 작성
- LLM 테스트: gemma2, qwen2 및 Mistral Nemo Ollama에서
- LLM 비교: Mistral Small, Gemma 2, Qwen 2.5, Mistral Nemo, LLama3 및 Phi - Ollama에서
- 테스트: Ollama가 인텔 CPU 성능과 효율적인 코어를 어떻게 사용하는가
- Ollama에서 임베딩 모델을 사용한 Reranking - 파이썬으로
- LLM 요약 능력 비교
- 클라우드 LLM 제공업체