การสร้างแบบเสริมการดึงข้อมูล (RAG)

Firebase Genkit มีแอบสแตรกต์ที่จะช่วยสร้างการสร้างแบบดึงข้อมูลได้ที่เสริม ขั้นตอน (RAG) ตลอดจนปลั๊กอินที่ผสานรวมกับเครื่องมือที่เกี่ยวข้อง

RAG คืออะไร

การสร้างแบบเสริมการดึงข้อมูลเป็นเทคนิคที่ใช้ในการรวม แหล่งข้อมูลลงในคำตอบของ LLM เป็นสิ่งสำคัญที่ต้องทำ ดังนั้น ขณะที่ LLM มักได้รับการฝึกเกี่ยวกับ การใช้งาน LLM ในทางปฏิบัติ ต้องอาศัยความรู้ด้านโดเมนที่เฉพาะเจาะจง (สำหรับ เช่น คุณอาจต้องการใช้ LLM เพื่อตอบคำถามของลูกค้า คำถามเกี่ยวกับ ผลิตภัณฑ์ของบริษัท)

วิธีแก้ไขอย่างหนึ่งคือการปรับแต่งโมเดลโดยใช้ข้อมูลที่เฉพาะเจาะจงมากขึ้น อย่างไรก็ตาม อาจมีค่าใช้จ่ายสูงทั้งในแง่ของต้นทุนการประมวลผลและในแง่ของความพยายามที่จำเป็น เพื่อเตรียมข้อมูลการฝึกอบรมให้เพียงพอ

ในทางตรงกันข้าม RAG ทำงานโดยการรวมแหล่งข้อมูลภายนอกเข้ากับพรอมต์ที่ เวลาที่ส่งต่อไปยังโมเดล ตัวอย่างเช่น คุณอาจจะจินตนาการถึงข้อความแจ้ง "ความสัมพันธ์ของคมสันกับลลิษาคืออะไร" อาจขยาย ("เสริม") โดย ใส่ข้อมูลที่เกี่ยวข้อง ส่งผลให้มีข้อความแจ้ง "Homer และ ลูกของ Marge ชื่อ Bart, Lisa และ Maggie ความสัมพันธ์ของคมสันเป็นอย่างไร ถึงลิซ่า"

วิธีการนี้มีข้อดีหลายประการดังนี้

  • วิธีนี้อาจคุ้มค่ามากกว่าเนื่องจากคุณไม่ต้องฝึกโมเดลอีกครั้ง
  • คุณจะอัปเดตแหล่งข้อมูลได้อย่างต่อเนื่อง และ LLM จะทำสิ่งต่อไปนี้ได้ การใช้ข้อมูลที่อัปเดตแล้ว
  • ตอนนี้คุณจะอ้างอิงข้อมูลอ้างอิงในคำตอบของ LLM ได้

ในทางกลับกัน การใช้ RAG ตามปกติหมายถึงพรอมต์ที่ยาวขึ้นและ LLM API บางรายการ ค่าบริการสำหรับโทเค็นอินพุตแต่ละรายการที่คุณส่ง สุดท้ายนี้ คุณต้องประเมินผล ต้นทุนที่เหมาะสมสำหรับแอปพลิเคชันของคุณ

RAG เป็นพื้นที่ที่กว้างมากและมีเทคนิคต่างๆ มากมายที่ใช้เพื่อให้บรรลุ RAG ที่มีคุณภาพดีที่สุด เฟรมเวิร์ก Genkit หลักมีองค์ประกอบหลัก 2 ประการที่ช่วยให้ ช่วยทำ RAG:

  • ผู้จัดทำดัชนี: เพิ่มเอกสารลงใน "ดัชนี"
  • เครื่องมือฝัง: เปลี่ยนรูปแบบเอกสารเป็นการนำเสนอเวกเตอร์
  • รีทรีฟเวอร์: เรียกเอกสารจาก "ดัชนี" เมื่อมีข้อความค้นหา

คำจำกัดความเหล่านี้มีจุดประสงค์กว้างๆ เนื่องจาก Genkit ไม่มีใครมองว่า "ดัชนี" คืออะไร หรือเอกสารถูกดึงมาจากที่ไหน Genkit เท่านั้น ระบุรูปแบบ Document และอย่างอื่นจะกำหนดโดยรีทรีฟเวอร์หรือ ผู้ให้บริการจัดทำดัชนี

ผู้จัดทำดัชนี

ดัชนีมีหน้าที่รับผิดชอบในการติดตามเอกสารของคุณในลักษณะที่ คุณสามารถเรียกดูเอกสารที่เกี่ยวข้องตามคำค้นหาที่เฉพาะเจาะจงได้อย่างรวดเร็ว มากที่สุด มักจะประสบความสำเร็จโดยใช้ฐานข้อมูลเวกเตอร์ ซึ่งจัดทำดัชนีเอกสารของคุณโดยใช้ เวกเตอร์หลายมิติที่เรียกว่าการฝัง การฝังข้อความ (ไม่ชัดเจน) แสดงถึงแนวคิดที่แสดงออกผ่านข้อความ สร้างขึ้นได้ โดยใช้โมเดล ML ที่มีวัตถุประสงค์พิเศษ การทำดัชนีข้อความโดยใช้การฝังจะทำให้เวกเตอร์ ฐานข้อมูลสามารถจัดกลุ่มข้อความที่มีแนวคิดเกี่ยวข้องกัน และเรียกเอกสาร เกี่ยวข้องกับสตริงข้อความใหม่ (คำค้นหา)

ก่อนที่คุณจะสามารถเรียกเอกสารเพื่อจุดประสงค์ในการสร้าง คุณจะต้อง ส่งผ่านข้อมูลไปยังดัชนีเอกสาร ขั้นตอนการส่งผ่านข้อมูลทั่วไป ดังต่อไปนี้:

  1. แบ่งเอกสารขนาดใหญ่ออกเป็นเอกสารย่อยๆ เพื่อให้เกี่ยวข้องกันเท่านั้น ส่วนขยายบางส่วนจะใช้เพื่อเพิ่มพรอมต์ของคุณ ซึ่งก็คือ "แบ่งส่วน" ขั้นตอนนี้จำเป็น เนื่องจาก LLM จำนวนมากมีหน้าต่างบริบทที่จำกัด ทำให้ไม่สามารถใช้งาน รวมเอกสารทั้งหมด ไว้ในพรอมต์

    Genkit ไม่มีไลบรารีที่แบ่งเป็นกลุ่มๆ ในตัว แต่ก็ยังมีแอตทริบิวต์ ไลบรารีแหล่งที่มาที่พร้อมใช้งานซึ่งเข้ากันได้กับ Genkit

  2. สร้างการฝังสำหรับกลุ่มแต่ละกลุ่ม ขึ้นอยู่กับฐานข้อมูลที่คุณใช้ คุณอาจดำเนินการดังกล่าวอย่างชัดแจ้งด้วยโมเดลการสร้างที่มีการฝัง หรือ อาจใช้เครื่องมือสร้างการฝังที่ฐานข้อมูลมีให้

  3. เพิ่มกลุ่มข้อความและดัชนีลงในฐานข้อมูล

คุณอาจเรียกใช้ขั้นตอนการส่งผ่านข้อมูลไม่บ่อยนักหรือเพียงครั้งเดียวในกรณีที่กำลังทำงาน ด้วยแหล่งข้อมูลที่เสถียร ในทางกลับกัน ถ้าคุณใช้ข้อมูล ที่เปลี่ยนแปลงบ่อยครั้ง คุณอาจเรียกใช้ขั้นตอนการส่งผ่านข้อมูลอย่างต่อเนื่อง (สำหรับ เช่น ในทริกเกอร์ Cloud Firestore ทุกครั้งที่มีการอัปเดตเอกสาร)

เครื่องมือฝัง

เครื่องมือฝังเป็นฟังก์ชันที่นำเนื้อหา (ข้อความ รูปภาพ เสียง ฯลฯ) มาสร้างเวกเตอร์ตัวเลขที่เข้ารหัสความหมายทางอรรถศาสตร์ของเนื้อหาต้นฉบับ ดังที่กล่าวไว้ข้างต้น เครื่องมือฝังวิดีโอจะนำไปใช้โดยเป็นส่วนหนึ่งของกระบวนการจัดทำดัชนี อย่างไรก็ตาม คุณสามารถใช้เครื่องมือเหล่านี้แยกต่างหากเพื่อสร้างการฝังโดยไม่ต้องมีดัชนีได้

รีทรีฟเวอร์

รีทรีฟเวอร์คือแนวคิดสรุปตรรกะที่เกี่ยวข้องกับเอกสารทุกประเภท การดึงข้อมูล กรณีการดึงข้อมูลที่ได้รับความนิยมมากที่สุดมักจะรวมถึงการดึงข้อมูลจาก อย่างไรก็ตาม การจัดเก็บเวกเตอร์ใน Genkit รีทรีฟเวอร์อาจเป็นฟังก์ชันใดก็ได้ที่แสดงผลข้อมูล

หากต้องการสร้างรีทรีฟเวอร์ คุณสามารถใช้การติดตั้งใช้งานที่ระบุไว้หรือ ก็สร้างได้

ตัวจัดทำดัชนี รีทรีฟเวอร์ และการฝังที่รองรับ

Genkit ให้การสนับสนุนเกี่ยวกับตัวจัดทำดัชนีและรีทรีฟเวอร์ผ่านระบบปลั๊กอิน ปลั๊กอินต่อไปนี้ได้รับการสนับสนุนอย่างเป็นทางการแล้ว:

  • ฐานข้อมูลเวกเตอร์ระบบคลาวด์ของ Pinecone

นอกจากนี้ Genkit ยังรองรับเวกเตอร์ Store ต่างๆ ผ่านที่กำหนดไว้ล่วงหน้า ที่คุณสามารถปรับแต่งสำหรับการกำหนดค่าฐานข้อมูลและ สคีมา:

การรองรับโมเดลการฝังมีให้บริการผ่านปลั๊กอินต่อไปนี้

ปลั๊กอิน โมเดล
Generative AI ของ Google การฝังข้อความตุ๊กแก
AI ของ Google Vertex การฝังข้อความตุ๊กแก

การกำหนดโฟลว์ RAG

ตัวอย่างต่อไปนี้แสดงวิธีส่งผ่านคอลเล็กชันเอกสารเมนูอาหารในรูปแบบ PDF ลงในฐานข้อมูลเวกเตอร์และเรียกข้อมูลเพื่อใช้ในโฟลว์กำหนดรายการอาหารที่ใช้ได้

ติดตั้งการอ้างอิง

ในตัวอย่างนี้ เราจะใช้ไลบรารี textsplitter จาก langchaingo และ ไลบรารีการแยกวิเคราะห์ PDF ของ ledongthuc/pdf:

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

กำหนดผู้จัดทำดัชนี

ตัวอย่างต่อไปนี้แสดงวิธีสร้างเครื่องมือจัดทำดัชนีเพื่อนำเข้าคอลเล็กชันเอกสาร 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 ที่แสดงข้อความแบบง่าย ตัวแยกเพื่อแบ่งเอกสารออกเป็นกลุ่มๆ ที่สามารถเวกเตอร์ได้

คำจำกัดความต่อไปนี้จะกำหนดค่าฟังก์ชันแบ่งส่วนเพื่อแสดงผลเอกสาร ส่วนที่มีอักขระ 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 ไดรฟ์ ฯลฯ) 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
	},
)