إعادة ترتيب النصوص باستخدام Ollama و Qwen3 Embedding LLM - بلغة Go
هل تخطط لتطبيق RAG؟ إليك بعض أكواد Golang...
هذا المثال الصغير كود Go لترتيب المعاودة يدعو Ollama لإنشاء تضمينات للمستفسر ولكل وثيقة مرشحة، ثم ترتيبها تنازليًا حسب التشابه الكسبي.
لقد قمنا بالفعل بنشاط مشابه - ترتيب المعاودة باستخدام نماذج التضمين ولكن كان ذلك في لغة Python، مع نموذج LLM مختلف تقريبًا، وقبل عام تقريبًا.
كود آخر مشابه، لكن باستخدام Qwen3 Reranker:
TL;DR
النتيجة تبدو جيدة جدًا، السرعة هي 0.128 ثانية لكل وثيقة. السؤال يُعد وثيقة. والتقسيم والطباعة تُحسب أيضًا ضمن هذه الإحصائيات.
استهلاك ذاكرة LLM:
رغم أن حجم النموذج على sdd (ollama ls
) أقل من 3 جيجابايت
dengcao/Qwen3-Embedding-4B:Q5_K_M 7e8c9ad6885b 2.9 GB
يأخذ في ذاكرة VRAM للوحدة المُسرّعة (ليس قليلاً) أكثر: 5.5 جيجابايت. (ollama ps
)
NAME ID SIZE
dengcao/Qwen3-Embedding-4B:Q5_K_M 7e8c9ad6885b 5.5 GB
إذا كانت لديك وحدة مُسرّعة بسعة 8 جيجابايت - يجب أن تكون في порядке جيد.
اختبار ترتيب المعاودة مع التضمينات على Ollama - مخرجات عينة
في جميع حالات الاختبار الثلاثة، كان ترتيب المعاودة مع التضمينات باستخدام نموذج ollama dengcao/Qwen3-Embedding-4B:Q5_K_M مذهلاً! انظر لنفسك.
لدينا 7 ملفات تحتوي على بعض النصوص التي تصف ما يشير إليه اسم الملف:
- ai_introduction.txt
- machine_learning.md
- qwen3-reranking-models.md
- ollama-parallelism.md
- ollama-reranking-models.md
- programming_basics.txt
- setup.log
اختبارات التشغيل:
اختبار ترتيب المعاودة: ما هو الذكاء الاصطناعي وكيف تعمل التعلم الآلي؟
./rnk example_query.txt example_docs/
استخدام نموذج التضمين: dengcao/Qwen3-Embedding-4B:Q5_K_M
عنوان URL الأساسي لـ Ollama: 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 ثانية لكل وثيقة)
اختبار ترتيب المعاودة: كيف يتعامل ollama مع الطلبات المتزامنة؟
./rnk example_query2.txt example_docs/
استخدام نموذج التضمين: dengcao/Qwen3-Embedding-4B:Q5_K_M
عنوان URL الأساسي لـ Ollama: 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 ثانية لكل وثيقة)
اختبار ترتيب المعاودة: كيف يمكننا إجراء ترتيب المعاودة للوثيقة مع ollama؟
./rnk example_query3.txt example_docs/
استخدام نموذج التضمين: dengcao/Qwen3-Embedding-4B:Q5_K_M
عنوان URL الأساسي لـ Ollama: http://localhost:11434
معالجة ملف الاستفسار: example_query3.txt، المجلد المستهدف: example_docs/
الاستفسار: كيف يمكننا إجراء ترتيب المعاودة للوثيقة مع ollama؟
تم العثور على 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: "نظام RAG باستخدام تضمينات Ollama",
Long: "نظام RAG بسيط يستخرج التضمينات ويصنف الوثائق باستخدام 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", "نموذج التضمين الذي سيتم استخدامه")
rootCmd.Flags().StringVarP(&ollamaBaseURL, "url", "u", "http://localhost:11434", "عنوان 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("استخدام نموذج التضمين: %s\n", embeddingModel)
fmt.Printf("عنوان URL الأساسي لـ Ollama: %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("خطأ في واجهة 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 يمثل حمولة الطلب لواجهة API لـ Ollama للتضمين
type OllamaEmbeddingRequest struct {
Model string `json:"model"`
Prompt string `json:"prompt"`
}
// OllamaEmbeddingResponse يمثل الاستجابة من واجهة API لـ Ollama للتضمين
type OllamaEmbeddingResponse struct {
Embedding []float64 `json:"embedding"`
}
// Document يمثل وثيقة مع معلوماتها
type Document struct {
Path string
Content string
Score float64
}
روابط مفيدة
- قائمة مصطلحات Ollama
- ترتيب وثائق النص مع Ollama ونموذج Qwen3 Reranker - في Go
- نماذج Qwen3 للتضمين وترتيب المعاودة على Ollama: الأداء الأفضل
- https://en.wikipedia.org/wiki/Retrieval-augmented_generation
- تثبيت وتكوين موقع نماذج Ollama
- كتابة محفزات فعالة للنماذج الكبيرة
- اختبار النماذج الكبيرة: gemma2، qwen2 و Mistral Nemo على Ollama
- مقارنة النماذج الكبيرة: Mistral Small، Gemma 2، Qwen 2.5، Mistral Nemo، LLama3 و Phi - على Ollama
- اختبار: كيف يستخدم Ollama أداء وحدات المعالجة المركزية من إنتل ووحدات المعالجة الفعالة
- ترتيب المعاودة باستخدام نماذج التضمين على Ollama في Python
- مقارنة قدرات النماذج الكبيرة على تلخيص النصوص
- مزوّدو النماذج الكبيرة في السحابة