نسل افزوده بازیابی (RAG)، نسل افزوده بازیابی (RAG)، نسل افزوده بازیابی (RAG)

Firebase Genkit انتزاعی‌هایی را ارائه می‌کند که به شما کمک می‌کند جریان‌های تولید تقویت‌شده بازیابی (RAG) بسازید، و همچنین پلاگین‌هایی که با ابزارهای مرتبط ادغام می‌شوند.

RAG چیست؟

تولید افزوده بازیابی تکنیکی است که برای ترکیب منابع خارجی اطلاعات در پاسخ‌های LLM استفاده می‌شود. مهم است که بتوانیم این کار را انجام دهیم، زیرا در حالی که LLM ها معمولاً بر روی مجموعه وسیعی از مطالب آموزش می بینند، استفاده عملی از LLM اغلب به دانش حوزه خاصی نیاز دارد (به عنوان مثال، ممکن است بخواهید از یک LLM برای پاسخ به سوالات مشتریان در مورد شرکت خود استفاده کنید. محصولات).

یک راه حل این است که مدل را با استفاده از داده های خاص تر تنظیم کنید. با این حال، این می تواند هم از نظر هزینه محاسباتی و هم از نظر تلاش مورد نیاز برای تهیه داده های آموزشی کافی گران باشد.

در مقابل، RAG با ترکیب منابع داده خارجی در یک اعلان در زمان ارسال به مدل کار می کند. به عنوان مثال، می توانید این اعلان را تصور کنید: "رابطه بارت با لیزا چیست؟" ممکن است با اضافه کردن برخی اطلاعات مرتبط، گسترش ("افزایش") شود، و در نتیجه این دستور، "فرزندان هومر و مارج بارت، لیزا، و مگی نامیده می شوند. رابطه بارت با لیزا چیست؟"

این رویکرد چندین مزیت دارد:

  • این می تواند مقرون به صرفه تر باشد زیرا نیازی به آموزش مجدد مدل ندارید.
  • شما می توانید به طور مداوم منبع داده خود را به روز کنید و LLM می تواند بلافاصله از اطلاعات به روز شده استفاده کند.
  • شما اکنون این پتانسیل را دارید که در پاسخ های LLM خود به منابع استناد کنید.

از طرف دیگر، استفاده از RAG به طور طبیعی به معنای درخواست های طولانی تر است و برخی از خدمات LLM API برای هر توکن ورودی که ارسال می کنید، هزینه دریافت می کنند. در نهایت، شما باید مبادلات هزینه را برای برنامه های خود ارزیابی کنید.

RAG یک منطقه بسیار گسترده است و تکنیک های مختلفی برای دستیابی به بهترین کیفیت RAG استفاده می شود. چارچوب اصلی Genkit دو انتزاع اصلی را برای کمک به شما در انجام RAG ارائه می دهد:

  • نمایه سازها: اسناد را به یک "نمایه" اضافه کنید.
  • Embedders: اسناد را به یک نمایش برداری تبدیل می کند
  • Retrievers: بازیابی اسناد از یک "شاخص"، با یک پرس و جو.

این تعاریف عمداً گسترده هستند، زیرا Genkit درباره چیستی «نمایه» یا نحوه دقیق بازیابی اسناد از آن نظری ندارد. Genkit فقط یک فرمت Document را ارائه می دهد و بقیه موارد توسط ارائه دهنده پیاده سازی بازیابی یا نمایه ساز تعریف می شود.

نمایه سازها

ایندکس وظیفه ردیابی اسناد شما را دارد به گونه ای که بتوانید به سرعت اسناد مربوطه را با توجه به یک پرس و جو خاص بازیابی کنید. این اغلب با استفاده از یک پایگاه داده برداری انجام می شود، که اسناد شما را با استفاده از بردارهای چند بعدی به نام embeddings فهرست می کند. تعبیه متن (به صورت غیر شفاف) مفاهیمی را نشان می دهد که توسط یک متن بیان شده است. اینها با استفاده از مدل‌های ML با هدف خاص تولید می‌شوند. با نمایه سازی متن با استفاده از جاسازی آن، یک پایگاه داده برداری می تواند متن مرتبط مفهومی را خوشه بندی کند و اسناد مربوط به یک رشته متن جدید (پرس و جو) را بازیابی کند.

قبل از اینکه بتوانید اسناد را به منظور تولید بازیابی کنید، باید آنها را در فهرست سند خود وارد کنید. یک جریان بلع معمولی موارد زیر را انجام می دهد:

  1. اسناد بزرگ را به اسناد کوچکتر تقسیم کنید تا فقط از بخش های مربوطه برای تقویت درخواست های شما استفاده شود - "قطع کردن". این امر ضروری است زیرا بسیاری از LLM ها دارای یک پنجره زمینه محدود هستند که گنجاندن کل اسناد با یک اعلان را غیرعملی می کند.

    Genkit کتابخانه های تکه ای داخلی را ارائه نمی دهد. با این حال، کتابخانه های منبع باز در دسترس هستند که با Genkit سازگار هستند.

  2. برای هر تکه جاسازی ایجاد کنید. بسته به پایگاه داده ای که استفاده می کنید، ممکن است این کار را به صراحت با یک مدل تولید جاسازی انجام دهید، یا ممکن است از ژنراتور جاسازی ارائه شده توسط پایگاه داده استفاده کنید.

  3. تکه متن و فهرست آن را به پایگاه داده اضافه کنید.

اگر با یک منبع داده پایدار کار می کنید، ممکن است جریان دریافت خود را به ندرت یا فقط یک بار اجرا کنید. از طرف دیگر، اگر با داده‌هایی کار می‌کنید که مرتباً تغییر می‌کنند، ممکن است به طور مداوم جریان انتقال را اجرا کنید (به عنوان مثال، در یک راه‌انداز Cloud Firestore، هر زمان که یک سند به‌روزرسانی می‌شود).

تعبیه کننده ها

Embedder تابعی است که محتوا (متن، تصاویر، صدا و غیره) را می گیرد و یک بردار عددی ایجاد می کند که معنای معنایی محتوای اصلی را رمزگذاری می کند. همانطور که در بالا ذکر شد، embedder ها به عنوان بخشی از فرآیند نمایه سازی مورد استفاده قرار می گیرند، با این حال، آنها همچنین می توانند به طور مستقل برای ایجاد جاسازی های بدون شاخص استفاده شوند.

رتریورها

بازیابی مفهومی است که منطق مربوط به هر نوع بازیابی سند را در بر می گیرد. محبوب ترین موارد بازیابی معمولاً شامل بازیابی از فروشگاه های برداری است، با این حال، در Genkit یک بازیابی می تواند هر تابعی باشد که داده را برمی گرداند.

برای ایجاد یک Retriever، می توانید از یکی از پیاده سازی های ارائه شده استفاده کنید یا خود را ایجاد کنید.

نمایه سازها، رتریورها و embedderها پشتیبانی می شود

Genkit از طریق سیستم پلاگین خود، از فهرست‌کننده و بازیابی پشتیبانی می‌کند. پلاگین های زیر به طور رسمی پشتیبانی می شوند:

  • پایگاه داده برداری ابری کاج

علاوه بر این، Genkit از ذخیره‌های برداری زیر از طریق قالب‌های کد از پیش تعریف‌شده پشتیبانی می‌کند، که می‌توانید آن‌ها را برای پیکربندی و طرح پایگاه داده خود سفارشی کنید:

پشتیبانی از مدل جاسازی از طریق پلاگین های زیر ارائه می شود:

پلاگین مدل ها
هوش مصنوعی Google Generative جاسازی متن gecko
Google Vertex AI جاسازی متن gecko

تعریف جریان RAG

مثال‌های زیر نشان می‌دهند که چگونه می‌توانید مجموعه‌ای از اسناد PDF منوی رستوران را در یک پایگاه داده برداری وارد کنید و آنها را برای استفاده در جریانی بازیابی کنید که تعیین می‌کند چه اقلام غذایی در دسترس هستند.

وابستگی ها را نصب کنید

در این مثال، ما از کتابخانه textsplitter از langchaingo و کتابخانه تجزیه PDF ledongthuc/pdf استفاده خواهیم کرد:

go get github.com/tmc/langchaingo/textsplitter
go get github.com/ledongthuc/pdf

یک Indexer را تعریف کنید

مثال زیر نشان می دهد که چگونه می توان یک نمایه ساز ایجاد کرد تا مجموعه ای از اسناد PDF را جذب کند و آنها را در یک پایگاه داده برداری محلی ذخیره کند.

از بازیابی شباهت برداری مبتنی بر فایل محلی استفاده می کند که Genkit خارج از جعبه برای آزمایش و نمونه سازی ساده ارائه می کند ( در تولید استفاده نکنید )

نمایه ساز را ایجاد کنید

// Import Genkit's file-based vector retriever, (Don't use in production.)
import "github.com/firebase/genkit/go/plugins/localvec"

// Vertex AI provides the text-embedding-004 embedder model.
import "github.com/firebase/genkit/go/plugins/vertexai"
ctx := context.Background()

err := vertexai.Init(ctx, &vertexai.Config{})
if err != nil {
	log.Fatal(err)
}
err = localvec.Init()
if err != nil {
	log.Fatal(err)
}

menuPDFIndexer, _, err := localvec.DefineIndexerAndRetriever(
	"menuQA",
	localvec.Config{
		Embedder: vertexai.Embedder("text-embedding-004"),
	},
)
if err != nil {
	log.Fatal(err)
}

ایجاد پیکربندی chunking

این مثال از کتابخانه textsplitter استفاده می‌کند که یک تقسیم‌کننده متن ساده را برای تقسیم اسناد به بخش‌هایی که می‌توانند بردار شوند، فراهم می‌کند.

تعریف زیر تابع chunking را به گونه ای پیکربندی می کند که بخش های سند 200 کاراکتری را با همپوشانی بین تکه های 20 کاراکتری برگرداند.

splitter := textsplitter.NewRecursiveCharacter(
	textsplitter.WithChunkSize(200),
	textsplitter.WithChunkOverlap(20),
)

گزینه های تکه تکه بیشتر برای این کتابخانه را می توان در اسناد langchaingo یافت.

جریان شاخص خود را تعریف کنید

genkit.DefineFlow(
	"indexMenu",
	func(ctx context.Context, path string) (any, error) {
		// Extract plain text from the PDF. Wrap the logic in Run so it
		// appears as a step in your traces.
		pdfText, err := genkit.Run(ctx, "extract", func() (string, error) {
			return readPDF(path)
		})
		if err != nil {
			return nil, err
		}

		// Split the text into chunks. Wrap the logic in Run so it
		// appears as a step in your traces.
		docs, err := genkit.Run(ctx, "chunk", func() ([]*ai.Document, error) {
			chunks, err := splitter.SplitText(pdfText)
			if err != nil {
				return nil, err
			}

			var docs []*ai.Document
			for _, chunk := range chunks {
				docs = append(docs, ai.DocumentFromText(chunk, nil))
			}
			return docs, nil
		})
		if err != nil {
			return nil, err
		}

		// Add chunks to the index.
		err = ai.Index(ctx, menuPDFIndexer, ai.WithIndexerDocs(docs...))
		return nil, err
	},
)
// Helper function to extract plain text from a PDF. Excerpted from
// https://github.com/ledongthuc/pdf
func readPDF(path string) (string, error) {
	f, r, err := pdf.Open(path)
	if f != nil {
		defer f.Close()
	}
	if err != nil {
		return "", err
	}

	reader, err := r.GetPlainText()
	if err != nil {
		return "", err
	}

	bytes, err := io.ReadAll(reader)
	if err != nil {
		return "", err
	}
	return string(bytes), nil
}

جریان نمایه ساز را اجرا کنید

genkit flow:run indexMenu "'menu.pdf'"

پس از اجرای جریان indexMenu ، پایگاه داده برداری با اسناد و مدارک آماده می شود تا در جریان های Genkit با مراحل بازیابی استفاده شود.

یک جریان را با بازیابی تعریف کنید

مثال زیر نشان می دهد که چگونه می توانید از یک رتریور در یک جریان RAG استفاده کنید. مانند مثال نمایه ساز، این مثال از بازیابی برداری مبتنی بر فایل Genkit استفاده می کند که نباید در تولید از آن استفاده کنید.

	ctx := context.Background()

	err := vertexai.Init(ctx, &vertexai.Config{})
	if err != nil {
		log.Fatal(err)
	}
	err = localvec.Init()
	if err != nil {
		log.Fatal(err)
	}

	model := vertexai.Model("gemini-1.5-flash")

	_, menuPdfRetriever, err := localvec.DefineIndexerAndRetriever(
		"menuQA",
		localvec.Config{
			Embedder: vertexai.Embedder("text-embedding-004"),
		},
	)
	if err != nil {
		log.Fatal(err)
	}

	genkit.DefineFlow(
		"menuQA",
		func(ctx context.Context, question string) (string, error) {
			// Retrieve text relevant to the user's question.
			docs, err := menuPdfRetriever.Retrieve(ctx, &ai.RetrieverRequest{
				Document: ai.DocumentFromText(question, nil),
			})
			if err != nil {
				return "", err
			}

			// Construct a system message containing the menu excerpts you just
			// retrieved.
			menuInfo := ai.NewSystemTextMessage("Here's the menu context:")
			for _, doc := range docs.Documents {
				menuInfo.Content = append(menuInfo.Content, doc.Content...)
			}

			// Call Generate, including the menu information in your prompt.
			return ai.GenerateText(ctx, model,
				ai.WithMessages(
					ai.NewSystemTextMessage(`
You are acting as a helpful AI assistant that can answer questions about the
food available on the menu at Genkit Grub Pub.
Use only the context provided to answer the question. If you don't know, do not
make up an answer. Do not add or change items on the menu.`),
					menuInfo,
					ai.NewUserTextMessage(question)))
		})

ایندکسرها و رتریورهای خود را بنویسید

همچنین این امکان وجود دارد که بازیابی خود را ایجاد کنید. اگر اسناد شما در یک فروشگاه اسنادی که در Genkit پشتیبانی نمی شود (به عنوان مثال: MySQL، Google Drive و غیره) مدیریت شوند، مفید است. Genkit SDK روش های انعطاف پذیری را ارائه می دهد که به شما امکان می دهد کد سفارشی برای واکشی اسناد ارائه دهید.

همچنین می‌توانید رتریورهای سفارشی را تعریف کنید که بر روی رتریورهای موجود در Genkit ساخته می‌شوند و تکنیک‌های پیشرفته RAG (مانند رتبه‌بندی مجدد یا توسعه سریع) را در بالا اعمال می‌کنند.

برای مثال، فرض کنید یک تابع رتبه بندی مجدد سفارشی دارید که می خواهید استفاده کنید. مثال زیر یک بازیابی سفارشی را تعریف می کند که عملکرد شما را در بازیابی منو که قبلاً تعریف شده است اعمال می کند:

type CustomMenuRetrieverOptions struct {
	K          int
	PreRerankK int
}
advancedMenuRetriever := ai.DefineRetriever(
	"custom",
	"advancedMenuRetriever",
	func(ctx context.Context, req *ai.RetrieverRequest) (*ai.RetrieverResponse, error) {
		// Handle options passed using our custom type.
		opts, _ := req.Options.(CustomMenuRetrieverOptions)
		// Set fields to default values when either the field was undefined
		// or when req.Options is not a CustomMenuRetrieverOptions.
		if opts.K == 0 {
			opts.K = 3
		}
		if opts.PreRerankK == 0 {
			opts.PreRerankK = 10
		}

		// Call the retriever as in the simple case.
		response, err := menuPDFRetriever.Retrieve(ctx, &ai.RetrieverRequest{
			Document: req.Document,
			Options:  localvec.RetrieverOptions{K: opts.PreRerankK},
		})
		if err != nil {
			return nil, err
		}

		// Re-rank the returned documents using your custom function.
		rerankedDocs := rerank(response.Documents)
		response.Documents = rerankedDocs[:opts.K]

		return response, nil
	},
)