পুনরুদ্ধার-বর্ধিত প্রজন্ম (RAG)

Firebase Genkit বিমূর্ততা প্রদান করে যা আপনাকে পুনরুদ্ধার-অগমেন্টেড জেনারেশন (RAG) ফ্লো তৈরি করতে সাহায্য করে, সেইসাথে প্লাগইনগুলি যা সম্পর্কিত সরঞ্জামগুলির সাথে একীকরণ প্রদান করে।

RAG কি?

পুনরুদ্ধার-বর্ধিত প্রজন্ম হল একটি কৌশল যা LLM-এর প্রতিক্রিয়াগুলিতে তথ্যের বাহ্যিক উত্সগুলিকে অন্তর্ভুক্ত করতে ব্যবহৃত হয়। এটি করতে সক্ষম হওয়া গুরুত্বপূর্ণ কারণ, এলএলএমগুলি সাধারণত বিস্তৃত উপাদানের উপর প্রশিক্ষিত হয়, এলএলএমগুলির ব্যবহারিক ব্যবহারের জন্য প্রায়শই নির্দিষ্ট ডোমেন জ্ঞানের প্রয়োজন হয় (উদাহরণস্বরূপ, আপনি আপনার কোম্পানির সম্পর্কে গ্রাহকদের প্রশ্নের উত্তর দেওয়ার জন্য একটি এলএলএম ব্যবহার করতে চাইতে পারেন পণ্য)।

একটি সমাধান হল আরও নির্দিষ্ট ডেটা ব্যবহার করে মডেলটিকে সূক্ষ্ম-টিউন করা। যাইহোক, এটি গণনা খরচ এবং পর্যাপ্ত প্রশিক্ষণ ডেটা প্রস্তুত করার জন্য প্রয়োজনীয় প্রচেষ্টার পরিপ্রেক্ষিতে উভয়ই ব্যয়বহুল হতে পারে।

বিপরীতে, RAG মডেলে পাস করার সময় একটি প্রম্পটে বাহ্যিক ডেটা উত্সগুলিকে অন্তর্ভুক্ত করে কাজ করে। উদাহরণস্বরূপ, আপনি প্রম্পটটি কল্পনা করতে পারেন, "লিসার সাথে বার্টের সম্পর্ক কী?" কিছু প্রাসঙ্গিক তথ্য প্রিপেন্ড করে প্রসারিত ("বর্ধিত") হতে পারে, যার ফলে প্রম্পট হয়, "হোমার এবং মার্জের সন্তানদের নাম বার্ট, লিসা এবং ম্যাগি। লিসার সাথে বার্টের সম্পর্ক কী?"

এই পদ্ধতির বেশ কয়েকটি সুবিধা রয়েছে:

  • এটি আরও সাশ্রয়ী হতে পারে কারণ আপনাকে মডেলটিকে পুনরায় প্রশিক্ষণ দিতে হবে না।
  • আপনি ক্রমাগত আপনার ডেটা উত্স আপডেট করতে পারেন এবং LLM অবিলম্বে আপডেট করা তথ্য ব্যবহার করতে পারে।
  • আপনার কাছে এখন আপনার এলএলএম-এর প্রতিক্রিয়াগুলিতে রেফারেন্স উদ্ধৃত করার সম্ভাবনা রয়েছে।

অন্যদিকে, RAG ব্যবহার করার অর্থ স্বাভাবিকভাবেই দীর্ঘ প্রম্পট, এবং কিছু LLM API পরিষেবা আপনার পাঠানো প্রতিটি ইনপুট টোকেনের জন্য চার্জ করে। শেষ পর্যন্ত, আপনাকে অবশ্যই আপনার অ্যাপ্লিকেশনগুলির জন্য খরচ ট্রেডঅফ মূল্যায়ন করতে হবে।

RAG একটি খুব বিস্তৃত এলাকা এবং সেরা মানের RAG অর্জনের জন্য বিভিন্ন কৌশল ব্যবহার করা হয়। মূল জেনকিট ফ্রেমওয়ার্ক আপনাকে RAG করতে সাহায্য করার জন্য দুটি প্রধান বিমূর্ততা প্রদান করে:

  • ইনডেক্সার: একটি "সূচীতে" নথি যোগ করুন।
  • এমবেডার: নথিগুলিকে ভেক্টর উপস্থাপনায় রূপান্তরিত করে
  • পুনরুদ্ধারকারী: একটি "সূচী" থেকে নথি পুনরুদ্ধার করুন, একটি প্রশ্ন দেওয়া হয়েছে।

এই সংজ্ঞাগুলি উদ্দেশ্যমূলকভাবে বিস্তৃত কারণ জেনকিট একটি "সূচী" কী বা এটি থেকে ঠিক কীভাবে নথিগুলি পুনরুদ্ধার করা হয় সে সম্পর্কে অ-মতামত। Genkit শুধুমাত্র একটি Document বিন্যাস প্রদান করে এবং বাকি সবকিছু পুনরুদ্ধারকারী বা সূচক বাস্তবায়ন প্রদানকারী দ্বারা সংজ্ঞায়িত করা হয়।

সূচক

সূচী এমনভাবে আপনার নথিগুলির ট্র্যাক রাখার জন্য দায়ী যাতে আপনি একটি নির্দিষ্ট প্রশ্নে প্রাসঙ্গিক নথিগুলি দ্রুত পুনরুদ্ধার করতে পারেন। এটি প্রায়শই একটি ভেক্টর ডাটাবেস ব্যবহার করে সম্পন্ন করা হয়, যা এম্বেডিং নামক বহুমাত্রিক ভেক্টর ব্যবহার করে আপনার নথিগুলিকে সূচী করে। একটি পাঠ্য এমবেডিং (অস্বচ্ছভাবে) পাঠ্যের একটি উত্তরণ দ্বারা প্রকাশিত ধারণাগুলিকে উপস্থাপন করে; এগুলি বিশেষ-উদ্দেশ্য ML মডেল ব্যবহার করে তৈরি করা হয়। টেক্সট এর এমবেডিং ব্যবহার করে ইন্ডেক্স করার মাধ্যমে, একটি ভেক্টর ডাটাবেস ধারণাগতভাবে সম্পর্কিত পাঠ্যকে ক্লাস্টার করতে এবং পাঠ্যের একটি অভিনব স্ট্রিং (কোয়েরি) সম্পর্কিত নথিগুলি পুনরুদ্ধার করতে সক্ষম হয়।

আপনি প্রজন্মের উদ্দেশ্যে নথিগুলি পুনরুদ্ধার করতে পারার আগে, আপনাকে সেগুলিকে আপনার নথির সূচীতে অন্তর্ভুক্ত করতে হবে৷ একটি সাধারণ ইনজেশন প্রবাহ নিম্নলিখিত কাজ করে:

  1. বড় নথিগুলিকে ছোট নথিতে বিভক্ত করুন যাতে শুধুমাত্র প্রাসঙ্গিক অংশগুলি আপনার প্রম্পটগুলিকে বাড়ানোর জন্য ব্যবহার করা হয় - "চঙ্কিং"৷ এটি প্রয়োজনীয় কারণ অনেক এলএলএম-এর একটি সীমিত প্রসঙ্গ উইন্ডো থাকে, যা প্রম্পটের সাথে সম্পূর্ণ নথি অন্তর্ভুক্ত করা অব্যবহারিক করে তোলে।

    জেনকিট বিল্ট-ইন চঙ্কিং লাইব্রেরি প্রদান করে না; যাইহোক, জেনকিটের সাথে সামঞ্জস্যপূর্ণ ওপেন সোর্স লাইব্রেরি পাওয়া যায়।

  2. প্রতিটি খণ্ডের জন্য এম্বেডিং তৈরি করুন। আপনি যে ডাটাবেসটি ব্যবহার করছেন তার উপর নির্ভর করে, আপনি স্পষ্টভাবে একটি এমবেডিং জেনারেশন মডেলের সাথে এটি করতে পারেন, অথবা আপনি ডাটাবেস দ্বারা প্রদত্ত এমবেডিং জেনারেটর ব্যবহার করতে পারেন।

  3. ডাটাবেসে পাঠ্য খণ্ড এবং এর সূচক যুক্ত করুন।

যদি আপনি ডেটার একটি স্থিতিশীল উৎসের সাথে কাজ করেন তবে আপনি আপনার ইনজেশন ফ্লো খুব কম সময়ে বা শুধুমাত্র একবার চালাতে পারেন। অন্যদিকে, আপনি যদি ঘন ঘন পরিবর্তিত ডেটা নিয়ে কাজ করেন, তাহলে আপনি ক্রমাগত ইনজেশন ফ্লো চালাতে পারেন (উদাহরণস্বরূপ, একটি ক্লাউড ফায়ারস্টোর ট্রিগারে, যখনই একটি নথি আপডেট করা হয়)৷

এমবেডার্স

একটি এমবেডার হল একটি ফাংশন যা বিষয়বস্তু (টেক্সট, ছবি, অডিও, ইত্যাদি) নেয় এবং একটি সংখ্যাসূচক ভেক্টর তৈরি করে যা মূল বিষয়বস্তুর শব্দার্থিক অর্থকে এনকোড করে। উপরে উল্লিখিত হিসাবে, সূচীকরণের প্রক্রিয়ার অংশ হিসাবে এমবেডারগুলিকে লিভারেজ করা হয়, তবে, এগুলিকে সূচক ছাড়াই এমবেডিং তৈরি করতে স্বাধীনভাবে ব্যবহার করা যেতে পারে।

উদ্ধারকারী

একটি পুনরুদ্ধারকারী এমন একটি ধারণা যা যেকোনো ধরনের নথি পুনরুদ্ধারের সাথে সম্পর্কিত যুক্তিকে অন্তর্ভুক্ত করে। সর্বাধিক জনপ্রিয় পুনরুদ্ধারের ক্ষেত্রে সাধারণত ভেক্টর স্টোর থেকে পুনরুদ্ধার অন্তর্ভুক্ত থাকে, তবে, জেনকিটে একটি পুনরুদ্ধার এমন যে কোনও ফাংশন হতে পারে যা ডেটা ফেরত দেয়।

একটি পুনরুদ্ধার তৈরি করতে, আপনি প্রদত্ত বাস্তবায়নের একটি ব্যবহার করতে পারেন বা নিজের তৈরি করতে পারেন।

সমর্থিত সূচক, পুনরুদ্ধারকারী এবং এমবেডার

Genkit তার প্লাগইন সিস্টেমের মাধ্যমে সূচক এবং পুনরুদ্ধার সমর্থন প্রদান করে। নিম্নলিখিত প্লাগইনগুলি আনুষ্ঠানিকভাবে সমর্থিত:

এছাড়াও, জেনকিট পূর্বনির্ধারিত কোড টেমপ্লেটগুলির মাধ্যমে নিম্নলিখিত ভেক্টর স্টোরগুলিকে সমর্থন করে, যা আপনি আপনার ডাটাবেস কনফিগারেশন এবং স্কিমার জন্য কাস্টমাইজ করতে পারেন:

এমবেডিং মডেল সমর্থন নিম্নলিখিত প্লাগইনগুলির মাধ্যমে প্রদান করা হয়:

প্লাগইন মডেল
গুগল জেনারেটিভ এআই গেকো টেক্সট এম্বেডিং
গুগল ভার্টেক্স এআই গেকো টেক্সট এম্বেডিং

একটি RAG ফ্লো সংজ্ঞায়িত করা

নিম্নলিখিত উদাহরণগুলি দেখায় যে আপনি কীভাবে একটি ভেক্টর ডাটাবেসে রেস্তোরাঁর মেনু পিডিএফ নথিগুলির একটি সংগ্রহ গ্রহণ করতে পারেন এবং একটি প্রবাহে ব্যবহারের জন্য সেগুলি পুনরুদ্ধার করতে পারেন যা নির্ধারণ করে যে কোন খাবারের আইটেমগুলি উপলব্ধ।

নির্ভরতা ইনস্টল করুন

এই উদাহরণে, আমরা langchaingo থেকে textsplitter লাইব্রেরি এবং ledongthuc/pdf PDF পার্সিং লাইব্রেরি ব্যবহার করব:

go get github.com/tmc/langchaingo/textsplitter
go get github.com/ledongthuc/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)
}

চঙ্কিং কনফিগারেশন তৈরি করুন

এই উদাহরণটি textsplitter লাইব্রেরি ব্যবহার করে যা ভেক্টরাইজ করা যেতে পারে এমন সেগমেন্টে নথিগুলিকে বিভক্ত করার জন্য একটি সাধারণ পাঠ্য স্প্লিটার প্রদান করে।

নিম্নলিখিত সংজ্ঞাটি 20 অক্ষরের খণ্ডগুলির মধ্যে একটি ওভারল্যাপ সহ 200 অক্ষরের নথির অংশগুলি ফেরত দেওয়ার জন্য খণ্ড ফাংশনটি কনফিগার করে৷

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 প্রবাহে একটি পুনরুদ্ধার ব্যবহার করতে পারেন। ইনডেক্সার উদাহরণের মতো, এই উদাহরণটি জেনকিটের ফাইল-ভিত্তিক ভেক্টর পুনরুদ্ধার ব্যবহার করে, যা আপনার উত্পাদনে ব্যবহার করা উচিত নয়।

	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 SDK নমনীয় পদ্ধতি প্রদান করে যা আপনাকে নথি আনার জন্য কাস্টম কোড প্রদান করতে দেয়।

এছাড়াও আপনি কাস্টম পুনরুদ্ধারকারীদের সংজ্ঞায়িত করতে পারেন যা জেনকিটে বিদ্যমান পুনরুদ্ধারগুলির উপরে তৈরি করে এবং উপরে উন্নত 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
	},
)