Ollama와 Qwen3 Reranker 모델을 사용한 문서 재정렬 - Go로

RAG을 구현 중이시다면? 여기 Go 코드 예제가 있습니다 - 2...

Page content

표준 Ollama에는 직접적인 재정렬 API가 없기 때문에, 쿼리-문서 쌍의 임베딩을 생성하고 이를 점수화하여 Qwen3 재정렬기 사용으로 재정렬하기(GO)를 구현해야 합니다.

지난 주에는 Ollama와 Qwen3 임베딩 모델을 사용하여 텍스트 문서 재정렬하기 - Go에 대해 조금 다뤄보았습니다.

오늘은 Qwen3 재정렬기 모델을 시도해 보겠습니다. Ollama에 새롭게 추가된 Qwen3 임베딩 및 재정렬기 모델이 여러 가지 있습니다. 저는 중간 규모의 dengcao/Qwen3-Reranker-4B:Q5_K_M 모델을 사용하고 있습니다.

재정렬된 개들

테스트 실행: TL;DR

동작은 잘 되고, 상당히 빠르며, 표준적인 방법은 아니지만 여전히:

$ ./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개 문서 처리 완료, 소요 시간: 2.023초 (평균: 0.289초/문서)
재정렬기 모델을 사용하여 문서 재정렬...
dengcao/Qwen3-Reranker-4B:Q5_K_M을 사용하여 cross-encoder 접근 방식으로 재정렬 수행

=== 재정렬기로 순위 매기기 ===
1. example_docs/ai_introduction.txt (점수: 0.343)
2. example_docs/machine_learning.md (점수: 0.340)
3. example_docs/programming_basics.txt (점수: 0.320)
4. example_docs/setup.log (점수: 0.313)
5. example_docs/ollama-parallelism.md (점수: 0.313)
6. example_docs/qwen3-reranking-models.md (점수: 0.312)
7. example_docs/ollama-reranking-models.md (점수: 0.306)

7개 문서 처리 완료, 소요 시간: 1.984초 (평균: 0.283초/문서)

Ollama를 호출하는 Go로 작성된 재정렬기 코드

이전 게시물 Ollama를 사용하여 임베딩으로 텍스트 문서 재정렬하기...에서 대부분의 코드를 가져와서 다음과 같은 부분을 추가합니다:

runRnk() 함수의 끝에 추가하세요:

startTime = time.Now()
// 재정렬기 모델을 사용하여 재정렬
fmt.Println("재정렬기 모델을 사용하여 문서 재정렬 중...")

// rerankingModel := "dengcao/Qwen3-Reranker-0.6B:F16"
rerankingModel := "dengcao/Qwen3-Reranker-4B:Q5_K_M"
rerankedDocs, err := rerankDocuments(validDocs, query, rerankingModel, ollamaBaseURL)
if err != nil {
	log.Fatalf("문서 재정렬 중 오류 발생: %v", err)
}

fmt.Println("\n=== 재정렬기로 순위 매기기 ===")
for i, doc := range rerankedDocs {
	fmt.Printf("%d. %s (점수: %.3f)\n", i+1, doc.Path, doc.Score)
}

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

fmt.Printf("\n%d개 문서 처리 완료, 소요 시간: %.3fs (평균: %.3fs/문서)\n",
	len(rerankedDocs), totalTime.Seconds(), avgTimePerDoc.Seconds())

다음과 같은 몇 가지 추가 함수를 추가하세요:

func rerankDocuments(validDocs []Document, query, rerankingModel, ollamaBaseURL string) ([]Document, error) {
	// 표준 Ollama에는 직접적인 재정렬 API가 없기 때문에, 쿼리-문서 쌍의 임베딩을 생성하고 이를 점수화하여
	// 재정렬을 구현해야 합니다

	fmt.Println("cross-encoder 접근 방식으로", rerankingModel, "을 사용하여 재정렬 수행 중")

	rerankedDocs := make([]Document, len(validDocs))
	copy(rerankedDocs, validDocs)

	for i, doc := range validDocs {
		// 쿼리와 문서를 결합하여 재정렬을 위한 프롬프트 생성
		rerankPrompt := fmt.Sprintf("Query: %s\n\nDocument: %s\n\nRelevance:", query, doc.Content)

		// 결합된 프롬프트의 임베딩 가져오기
		embedding, err := getEmbedding(rerankPrompt, rerankingModel, ollamaBaseURL)
		if err != nil {
			fmt.Printf("경고: 문서 %d에 대한 재정렬 임베딩 가져오기 실패: %v\n", i, err)
			// 중립 점수로 대체
			rerankedDocs[i].Score = 0.5
			continue
		}

		// 임베딩의 크기를 사용하여 관련성 점수 계산
		// (이 방법은 간단한 접근 방식이며, 실제로는 훈련된 재정렬기 사용이 필요함)
		score := calculateRelevanceScore(embedding)
		rerankedDocs[i].Score = score
		// fmt.Printf("문서 %d 재정렬 완료, 점수: %.4f\n", i, score)
	}

	// 재정렬 점수(내림차순)에 따라 문서 정렬
	sort.Slice(rerankedDocs, func(i, j int) bool {
		return rerankedDocs[i].Score > rerankedDocs[j].Score
	})

	return rerankedDocs, nil
}

func calculateRelevanceScore(embedding []float64) float64 {
	// 임베딩 크기와 양수 값에 기반한 간단한 점수 계산
	var sumPositive, sumTotal float64
	for _, val := range embedding {
		sumTotal += val * val
		if val > 0 {
			sumPositive += val
		}
	}

	if sumTotal == 0 {
		return 0
	}

	// 크기와 양수 편향을 정규화하여 결합
	magnitude := math.Sqrt(sumTotal) / float64(len(embedding))
	positiveRatio := sumPositive / float64(len(embedding))

	return (magnitude + positiveRatio) / 2
}

math 패키지를 임포트하는 것을 잊지 마세요:

import (
	"math"
)

이제 컴파일해 보세요:

go build -o rnk

그리고 이 간단한 RAG 재정렬기 기술 프로토타입을 실행해 보세요:

./rnk ./example_query.txt ./example_docs

유용한 링크