Genkit ให้บริการนามธรรมที่จะช่วยคุณสร้างโฟลว์การสร้างที่เพิ่มการดึงข้อมูล (RAG) รวมถึงปลั๊กอินที่ผสานรวมกับเครื่องมือที่เกี่ยวข้อง
RAG คืออะไร
การสร้างที่เพิ่มการดึงข้อมูลเป็นเทคนิคที่ใช้รวมแหล่งข้อมูลภายนอกไว้ในคำตอบของ LLM คุณควรทำเช่นนั้นเนื่องจากโดยทั่วไป LLM ได้รับการฝึกจากเนื้อหาที่หลากหลาย แต่การใช้งานจริงของ LLM มักต้องใช้ความรู้เฉพาะด้าน (เช่น คุณอาจต้องการใช้ LLM เพื่อตอบคําถามของลูกค้าเกี่ยวกับผลิตภัณฑ์ของบริษัท)
วิธีแก้ปัญหาอย่างหนึ่งคือการปรับแต่งโมเดลโดยใช้ข้อมูลที่เฉพาะเจาะจงมากขึ้น อย่างไรก็ตาม วิธีนี้อาจมีค่าใช้จ่ายสูงทั้งในแง่ของต้นทุนการประมวลผลและในแง่ของความพยายามที่ต้องใช้ในการเตรียมข้อมูลการฝึกที่เพียงพอ
ในทางตรงกันข้าม RAG จะทํางานโดยการรวมแหล่งข้อมูลภายนอกไว้ในพรอมต์เมื่อส่งไปยังโมเดล ตัวอย่างเช่น คุณอาจจินตนาการได้ว่าพรอมต์ "Bart มีความสัมพันธ์อย่างไรกับ Lisa" อาจขยาย ("เพิ่ม") โดยใส่ข้อมูลที่เกี่ยวข้องไว้ข้างหน้า พรอมต์จึงกลายเป็น "เด็กๆ ของ Homer และ Marge มีชื่อว่า Bart, Lisa และ Maggie Bart มีความสัมพันธ์อย่างไรกับ Lisa"
วิธีการนี้มีข้อดีหลายประการ ดังนี้
- ซึ่งอาจประหยัดค่าใช้จ่ายได้มากกว่าเนื่องจากคุณไม่จําเป็นต้องฝึกโมเดลใหม่
- คุณสามารถอัปเดตแหล่งข้อมูลได้อย่างต่อเนื่อง และ LLM จะใช้ข้อมูลที่อัปเดตได้ทันที
- ตอนนี้คุณอ้างอิงแหล่งข้อมูลในการตอบกลับของ LLM ได้แล้ว
ในทางกลับกัน การใช้ RAG หมายความว่าพรอมต์จะยาวขึ้น และบริการ LLM API บางรายการจะเรียกเก็บเงินสำหรับโทเค็นอินพุตแต่ละรายการที่คุณส่ง สุดท้ายแล้ว คุณต้องประเมินการแลกเปลี่ยนค่าใช้จ่ายสำหรับแอปพลิเคชัน
RAG เป็นพื้นที่ที่กว้างมากและมีเทคนิคต่างๆ มากมายที่ใช้เพื่อให้ได้ RAG คุณภาพดีที่สุด เฟรมเวิร์ก Genkit หลักมีนามธรรมหลัก 2 รายการที่จะช่วยคุณทำ RAG
- ตัวจัดทําดัชนี: เพิ่มเอกสารลงใน "ดัชนี"
- เครื่องมือฝัง: เปลี่ยนเอกสารให้เป็นตัวแทนเวกเตอร์
- เครื่องมือดึงข้อมูล: ดึงข้อมูลเอกสารจาก "ดัชนี" ตามการค้นหา
คําจํากัดความเหล่านี้มีความกว้างขวางโดยมีเจตนา เนื่องจาก Genkit ไม่ได้แสดงความคิดเห็นเกี่ยวกับ "ดัชนี" หรือวิธีดึงข้อมูลเอกสารจากดัชนี Genkit มีเฉพาะรูปแบบ Document
ส่วนทุกอย่างอื่นๆ จะกำหนดโดยผู้ให้บริการการดึงข้อมูลหรือผู้ให้บริการติดตั้งใช้งานเครื่องมือจัดทำดัชนี
ผู้จัดทําดัชนี
ดัชนีมีหน้าที่ติดตามเอกสารของคุณในลักษณะที่ช่วยให้คุณสามารถเรียกดูเอกสารที่เกี่ยวข้องได้อย่างรวดเร็วเมื่อมีการค้นหาที่เฉพาะเจาะจง ซึ่งมักทำโดยใช้ฐานข้อมูลเวกเตอร์ ซึ่งจะจัดทำดัชนีเอกสารโดยใช้เวกเตอร์หลายมิติที่เรียกว่าการฝัง การฝังข้อความ (แบบทึบ) จะแสดงถึงแนวคิดที่สื่อผ่านข้อความ ซึ่งสร้างขึ้นโดยใช้โมเดล ML เฉพาะทาง การจัดทําดัชนีข้อความโดยใช้การฝังช่วยให้ฐานข้อมูลเวกเตอร์จัดกลุ่มข้อความที่เกี่ยวข้องตามแนวคิดและดึงข้อมูลเอกสารที่เกี่ยวข้องกับสตริงข้อความใหม่ (ข้อความค้นหา) ได้
คุณต้องส่งผ่านเอกสารลงในดัชนีเอกสารก่อนจึงจะเรียกดูเอกสารเพื่อวัตถุประสงค์ในการสร้างได้ ขั้นตอนการส่งผ่านข้อมูลทั่วไปจะทําสิ่งต่อไปนี้
แยกเอกสารขนาดใหญ่ออกเป็นเอกสารขนาดเล็กเพื่อให้ใช้เฉพาะส่วนที่เกี่ยวข้องเพื่อเสริมพรอมต์เท่านั้น ซึ่งเรียกว่า "การแบ่งกลุ่ม" ซึ่งจำเป็นเนื่องจาก LLM จำนวนมากมีกรอบบริบทที่จำกัด ทำให้การใส่ทั้งเอกสารในพรอมต์ไม่สามารถทำได้
Genkit ไม่มีไลบรารีการแบ่งออกเป็นส่วนๆ ในตัว แต่มีไลบรารีโอเพนซอร์สที่ใช้ร่วมกับ Genkit ได้
สร้างการฝังสําหรับแต่ละกลุ่ม คุณอาจทําเช่นนี้อย่างชัดเจนด้วยรูปแบบการสร้างการฝัง หรืออาจใช้เครื่องมือสร้างการฝังที่ฐานข้อมูลมีให้ ทั้งนี้ขึ้นอยู่กับฐานข้อมูลที่ใช้งาน
เพิ่มข้อมูลโค้ดข้อความและดัชนีลงในฐานข้อมูล
คุณอาจเรียกใช้ขั้นตอนการส่งผ่านข้อมูลเป็นครั้งคราวหรือเพียงครั้งเดียวหากทํางานกับแหล่งข้อมูลที่เสถียร ในทางกลับกัน หากคุณทํางานกับข้อมูลที่เปลี่ยนแปลงบ่อย คุณอาจเรียกใช้ขั้นตอนการนำเข้าอย่างต่อเนื่อง (เช่น ในทริกเกอร์ Cloud Firestore เมื่อใดก็ตามที่มีการอัปเดตเอกสาร)
โปรแกรมฝัง
โปรแกรมฝังคือฟังก์ชันที่ใช้เนื้อหา (ข้อความ รูปภาพ เสียง ฯลฯ) และสร้างเวกเตอร์ตัวเลขที่เข้ารหัสความหมายเชิงอรรถศาสตร์ของเนื้อหาต้นฉบับ ดังที่กล่าวไว้ข้างต้น ระบบจะใช้ประโยชน์จากผู้ฝังเนื้อหาในกระบวนการจัดทำดัชนี แต่คุณก็ใช้แยกต่างหากเพื่อสร้างการฝังได้โดยไม่ต้องใช้ดัชนี
รีทรีฟเวอร์
Retriever คือแนวคิดที่รวมตรรกะที่เกี่ยวข้องกับการดึงข้อมูลเอกสารทุกประเภท กรณีการดึงข้อมูลที่นิยมที่สุดมักจะเป็นการดึงข้อมูลจากที่เก็บเวกเตอร์ อย่างไรก็ตาม ใน Genkit ตัวดึงข้อมูลอาจเป็นฟังก์ชันใดก็ได้ที่แสดงผลข้อมูล
หากต้องการสร้างเครื่องมือดึงข้อมูล คุณสามารถใช้การติดตั้งใช้งานที่ระบุไว้หรือจะสร้างเองก็ได้
ตัวจัดทําดัชนี ตัวดึงข้อมูล และผู้ฝังที่รองรับ
Genkit รองรับเครื่องมือจัดทำดัชนีและเครื่องมือดึงข้อมูลผ่านระบบปลั๊กอิน ระบบรองรับปลั๊กอินต่อไปนี้อย่างเป็นทางการ
- ฐานข้อมูลเวกเตอร์ระบบคลาวด์ของ Pinecone
นอกจากนี้ Genkit ยังรองรับที่เก็บเวกเตอร์ต่อไปนี้ผ่านเทมเพลตโค้ดที่กําหนดไว้ล่วงหน้า ซึ่งคุณสามารถปรับแต่งสําหรับการกําหนดค่าฐานข้อมูลและสคีมา
- PostgreSQL ที่มี
pgvector
การรองรับการฝังโมเดลมีให้ผ่านปลั๊กอินต่อไปนี้
ปลั๊กอิน | โมเดล |
---|---|
Generative AI ของ Google | การฝังข้อความ |
การกําหนดขั้นตอน RAG
ตัวอย่างต่อไปนี้แสดงวิธีนำเข้าคอลเล็กชันเอกสาร PDF ของเมนูร้านอาหารลงในฐานข้อมูลเวกเตอร์และดึงข้อมูลเพื่อใช้ในขั้นตอนที่ระบุรายการอาหารที่มีให้บริการ
ติดตั้งการอ้างอิง
ในตัวอย่างนี้ เราจะใช้ไลบรารี textsplitter
จาก langchaingo
และ ledongthuc/pdf
ไลบรารีการแยกวิเคราะห์ 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()
g, err := genkit.Init(ctx, genkit.WithPlugins(&googlegenai.VertexAI{}))
if err != nil {
log.Fatal(err)
}
if err = localvec.Init(); err != nil {
log.Fatal(err)
}
menuPDFIndexer, _, err := localvec.DefineIndexerAndRetriever(g, "menuQA",
localvec.Config{Embedder: googlegenai.VertexAIEmbedder(g, "text-embedding-004")})
if err != nil {
log.Fatal(err)
}
สร้างการกําหนดค่าการแบ่งกลุ่ม
ตัวอย่างนี้ใช้ไลบรารี textsplitter
ซึ่งมีตัวแยกข้อความแบบง่ายเพื่อแบ่งเอกสารออกเป็นส่วนๆ ที่เปลี่ยนเป็นเวกเตอร์ได้
คําจํากัดความต่อไปนี้กําหนดค่าฟังก์ชันการแบ่งกลุ่มให้แสดงผลกลุ่มเอกสาร 200 อักขระ โดยให้แต่ละกลุ่มซ้อนทับกัน 20 อักขระ
splitter := textsplitter.NewRecursiveCharacter(
textsplitter.WithChunkSize(200),
textsplitter.WithChunkOverlap(20),
)
ดูตัวเลือกการแบ่งกลุ่มเพิ่มเติมสำหรับไลบรารีนี้ได้ในเอกสารประกอบของ langchaingo
กำหนดขั้นตอนของเครื่องมือจัดทำดัชนี
genkit.DefineFlow(
g, "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.WithDocs(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()
g, err := genkit.Init(ctx, genkit.WithPlugins(&googlegenai.VertexAI{}))
if err != nil {
log.Fatal(err)
}
if err = localvec.Init(); err != nil {
log.Fatal(err)
}
model := googlegenai.VertexAIModel(g, "gemini-1.5-flash")
_, menuPdfRetriever, err := localvec.DefineIndexerAndRetriever(
g, "menuQA", localvec.Config{Embedder: googlegenai.VertexAIEmbedder(g, "text-embedding-004")},
)
if err != nil {
log.Fatal(err)
}
genkit.DefineFlow(
g, "menuQA",
func(ctx context.Context, question string) (string, error) {
// Retrieve text relevant to the user's question.
resp, err := ai.Retrieve(ctx, menuPdfRetriever, ai.WithTextDocs(question))
if err != nil {
return "", err
}
// Call Generate, including the menu information in your prompt.
return genkit.GenerateText(ctx, g,
ai.WithModelName("googleai/gemini-2.0-flash"),
ai.WithDocs(resp.Documents),
ai.WithSystem(`
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.`)
ai.WithPrompt(question),
})
เขียนเครื่องมือจัดทำดัชนีและเครื่องมือดึงข้อมูลของคุณเอง
นอกจากนี้ คุณยังสร้างเครื่องมือดึงข้อมูลของคุณเองได้ด้วย ซึ่งมีประโยชน์ในกรณีที่มีการจัดการเอกสารในระบบจัดการเอกสารที่ไม่รองรับใน Genkit (เช่น MySQL, Google ไดรฟ์ ฯลฯ) Genkit SDK มีวิธีการที่ยืดหยุ่นซึ่งช่วยให้คุณระบุโค้ดที่กำหนดเองสำหรับการดึงข้อมูลเอกสารได้
นอกจากนี้ คุณยังกำหนดเครื่องมือดึงข้อมูลที่กำหนดเองซึ่งสร้างขึ้นจากเครื่องมือดึงข้อมูลที่มีอยู่ได้อีกด้วยใน Genkit และใช้เทคนิค RAG ขั้นสูง (เช่น การจัดอันดับใหม่หรือการขยายพรอมต์) เพิ่มเติม
เช่น สมมติว่าคุณมีฟังก์ชันการจัดอันดับใหม่ที่กำหนดเองซึ่งต้องการใช้ ตัวอย่างต่อไปนี้จะกําหนดเครื่องมือดึงข้อมูลที่กำหนดเองซึ่งใช้ฟังก์ชันกับเครื่องมือดึงข้อมูลเมนูที่กําหนดไว้ก่อนหน้านี้
type CustomMenuRetrieverOptions struct {
K int
PreRerankK int
}
advancedMenuRetriever := genkit.DefineRetriever(
g, "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.
resp, err := ai.Retrieve(ctx, menuPDFRetriever,
ai.WithDocs(req.Query),
ai.WithConfig(ocalvec.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
},
)