สร้างแอปถามและตอบด้วย RAG แบบหลายโมดัลโดยใช้ Gemini Pro

สร้างแอปถามและตอบด้วย RAG แบบหลายรูปแบบโดยใช้ Gemini Pro

เกี่ยวกับ Codelab นี้

subjectอัปเดตล่าสุดเมื่อ มิ.ย. 27, 2024
account_circleเขียนโดย Bhushan Garware, Aditya Rane, Leonid Kuligin

1 บทนำ

RAG คืออะไร

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

936b7eedba773cac.png

RAG ได้รับความนิยมมากขึ้นเรื่อยๆ เนื่องด้วยเหตุผลหลายประการ ดังนี้

  • เพิ่มความแม่นยำและความเกี่ยวข้อง: RAG ช่วยให้ LLM ให้คำตอบที่แม่นยำและเกี่ยวข้องมากขึ้นโดยอิงตามข้อมูลข้อเท็จจริงที่ดึงมาจากแหล่งข้อมูลภายนอก ซึ่งมีประโยชน์อย่างยิ่งในสถานการณ์ที่ความรู้ล่าสุดมีความสําคัญ เช่น การตอบคําถามเกี่ยวกับเหตุการณ์ปัจจุบัน หรือการให้ข้อมูลในหัวข้อที่เฉพาะเจาะจง
  • อาการหลอนลดลง: บางครั้ง LLM อาจสร้างคำตอบที่ดูเหมือนจะสมเหตุสมผล แต่จริงๆ แล้วไม่ถูกต้องหรือไม่สมเหตุสมผล RAG ช่วยบรรเทาปัญหานี้ด้วยการยืนยันข้อมูลที่สร้างขึ้นกับแหล่งข้อมูลภายนอก
  • ความสามารถในการปรับตัวมากขึ้น: RAG ทําให้ LLM ปรับตัวเข้ากับโดเมนและงานต่างๆ ได้มากขึ้น LLM สามารถปรับแต่งให้แสดงข้อมูลเกี่ยวกับหัวข้อต่างๆ ได้อย่างง่ายดายด้วยการใช้แหล่งความรู้ที่หลากหลาย
  • ประสบการณ์ของผู้ใช้ที่ดีขึ้น: RAG ช่วยปรับปรุงประสบการณ์โดยรวมของผู้ใช้ด้วยการแสดงคำตอบที่มีประโยชน์ เชื่อถือได้ และมีความเกี่ยวข้องมากขึ้น

เหตุผลที่ควรใช้ Multi-Modal

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

2 ตัวเลือกสำหรับ RAG แบบหลายรูปแบบ

  • การฝังข้อมูลแบบหลายโมดัล - โมเดลการฝังข้อมูลแบบหลายโมดัลจะสร้างเวกเตอร์ 1,408 มิติ* โดยอิงตามอินพุตที่คุณให้ ซึ่งอาจรวมข้อมูลรูปภาพ ข้อความ และวิดีโอ เวกเตอร์การฝังรูปภาพและเวกเตอร์การฝังข้อความอยู่ในเชิงพื้นที่เชิงความหมายเดียวกันที่มีมิติข้อมูลเดียวกัน ดังนั้นเวกเตอร์เหล่านี้จึงใช้แทนกันได้สำหรับกรณีการใช้งานต่างๆ เช่น การค้นหารูปภาพด้วยข้อความ หรือการค้นหาวิดีโอด้วยรูปภาพ โปรดดูเดโมนี้
  1. ใช้การฝังแบบหลายรูปแบบเพื่อฝังข้อความและรูปภาพ
  2. ดึงข้อมูลทั้ง 2 รายการโดยใช้การค้นหาแบบคล้ายกัน
  3. ส่งทั้งรูปภาพดิบและข้อความที่ดึงมาไปยัง LLM แบบหลายรูปแบบเพื่อสังเคราะห์คำตอบ
  • การฝังข้อความ -
  1. ใช้ LLM แบบหลายรูปแบบเพื่อสร้างสรุปข้อความของรูปภาพ
  2. ฝังและเรียกข้อมูลข้อความ
  3. ส่งข้อมูลข้อความไปยัง LLM สำหรับการสังเคราะห์คำตอบ

Multi-Vector Retriever คืออะไร

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

สิ่งที่คุณจะสร้าง

กรณีการใช้งาน: การพัฒนาระบบตอบคำถามโดยใช้ Gemini Pro

ลองนึกว่าคุณมีเอกสารที่มีกราฟหรือแผนภาพที่ซับซ้อนซึ่งเต็มไปด้วยข้อมูล คุณต้องการดึงข้อมูลนี้เพื่อตอบคําถามหรือคําค้นหา

ในโค้ดแล็บนี้ คุณจะทำสิ่งต่อไปนี้

  • การโหลดข้อมูลโดยใช้ LangChain document_loaders
  • สร้างสรุปข้อความโดยใช้โมเดล gemini-pro ของ Google
  • สร้างข้อมูลสรุปรูปภาพโดยใช้โมเดล gemini-pro-vision ของ Google
  • สร้างการดึงข้อมูลเวกเตอร์หลายรายการโดยใช้โมเดล textembedding-gecko ของ Google ที่มี Croma Db เป็นพื้นที่เก็บข้อมูลเวกเตอร์
  • พัฒนาเชน RAG แบบหลายสื่อสำหรับการตอบคำถาม

2 ก่อนเริ่มต้น

  1. ในคอนโซล Google Cloud ให้เลือกหรือสร้างโปรเจ็กต์ Google Cloud ในหน้าตัวเลือกโปรเจ็กต์
  2. ตรวจสอบว่าเปิดใช้การเรียกเก็บเงินสำหรับโปรเจ็กต์ Google Cloud แล้ว ดูวิธีตรวจสอบว่าเปิดใช้การเรียกเก็บเงินในโปรเจ็กต์หรือไม่
  3. เปิดใช้ API ที่แนะนําทั้งหมดจากหน้าแดชบอร์ด Vertex AI
  4. เปิด Colab Notebook และเข้าสู่ระบบบัญชีเดียวกับบัญชี Google Cloud ที่ใช้งานอยู่ในปัจจุบัน

3 การสร้าง RAG แบบหลายรูปแบบ

โค้ดแล็บนี้ใช้ Vertex AI SDK สําหรับ Python และ Langchain เพื่อสาธิตวิธีใช้ "ตัวเลือกที่ 2" ที่อธิบายไว้ที่นี่กับ Google Cloud

คุณสามารถดูโค้ดแบบเต็มได้ในไฟล์ Multi-modal RAG with Google Cloud จากที่เก็บที่อ้างอิง

4 ขั้นตอนที่ 1: ติดตั้งและนําเข้าทรัพยากร Dependency

!pip install -U --quiet langchain langchain_community chromadb  langchain-google-vertexai
!pip install --quiet "unstructured[all-docs]" pypdf pillow pydantic lxml pillow matplotlib chromadb tiktoken

ป้อนรหัสโปรเจ็กต์และดำเนินการตรวจสอบสิทธิ์ให้เสร็จสมบูรณ์

#TODO : ENter project and location
PROJECT_ID
= ""
REGION
= "us-central1"

from google.colab import auth
auth
.authenticate_user()

เริ่มต้นใช้งานแพลตฟอร์ม Vertex AI

import vertexai
vertexai
.init(project = PROJECT_ID , location = REGION)

5 ขั้นตอนที่ 2: เตรียมและโหลดข้อมูล

เราใช้ไฟล์ ZIP ที่มีชุดย่อยของรูปภาพและ PDF ที่ดึงมาจากบล็อกโพสต์นี้ หากต้องการทำตามขั้นตอนทั้งหมด โปรดใช้ตัวอย่างต้นฉบับ

ดาวน์โหลดข้อมูลก่อน

import logging
import zipfile
import requests

logging
.basicConfig(level=logging.INFO)

data_url
= "https://storage.googleapis.com/benchmarks-artifacts/langchain-docs-benchmarking/cj.zip"
result
= requests.get(data_url)
filename
= "cj.zip"
with open(filename, "wb") as file:
   file
.write(result.content)

with zipfile.ZipFile(filename, "r") as zip_ref:
   zip_ref
.extractall()

โหลดเนื้อหาข้อความจากเอกสาร

from langchain_community.document_loaders import PyPDFLoader

loader
= PyPDFLoader("./cj/cj.pdf")
docs
= loader.load()
tables
= []
texts
= [d.page_content for d in docs]

ตรวจสอบเนื้อหาจากหน้าแรก

texts[0]

คุณควรเห็นเอาต์พุต

2c5c257779c0f52a.png

จำนวนหน้าทั้งหมดในเอกสาร

len(texts)

ผลลัพธ์ที่คาดหวังคือ

b5700c0c1376abc2.png

6 ขั้นตอนที่ 3: สร้างสรุปข้อความ

นำเข้าไลบรารีที่จำเป็นก่อน

from langchain_google_vertexai import VertexAI , ChatVertexAI , VertexAIEmbeddings
from langchain.prompts import PromptTemplate
from langchain_core.messages import AIMessage
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableLambda

รับสรุปข้อความ

# Generate summaries of text elements
def generate_text_summaries(texts, tables, summarize_texts=False):
   
"""
   Summarize text elements
   texts: List of str
   tables: List of str
   summarize_texts: Bool to summarize texts
   """


   
# Prompt
   prompt_text
= """You are an assistant tasked with summarizing tables and text for retrieval. \
   These summaries will be embedded and used to retrieve the raw text or table elements. \
   Give a concise summary of the table or text that is well optimized for retrieval. Table or text: {element} """

   prompt
= PromptTemplate.from_template(prompt_text)
   empty_response
= RunnableLambda(
       
lambda x: AIMessage(content="Error processing document")
   
)
   
# Text summary chain
   model
= VertexAI(
       temperature
=0, model_name="gemini-pro", max_output_tokens=1024
   
).with_fallbacks([empty_response])
   summarize_chain
= {"element": lambda x: x} | prompt | model | StrOutputParser()

   
# Initialize empty summaries
   text_summaries
= []
   table_summaries
= []

   
# Apply to text if texts are provided and summarization is requested
   
if texts and summarize_texts:
       text_summaries
= summarize_chain.batch(texts, {"max_concurrency": 1})
   
elif texts:
       text_summaries
= texts

   
# Apply to tables if tables are provided
   
if tables:
       table_summaries
= summarize_chain.batch(tables, {"max_concurrency": 1})

   
return text_summaries, table_summaries


# Get text summaries
text_summaries
, table_summaries = generate_text_summaries(
   texts
, tables, summarize_texts=True
)

text_summaries
[0]

ผลลัพธ์ที่คาดหวังคือ

aa76e4b523d8a958.png

7 ขั้นตอนที่ 4: สร้างข้อมูลสรุปรูปภาพ

นำเข้าไลบรารีที่จำเป็นก่อน

import base64
import os

from langchain_core.messages import HumanMessage

สร้างข้อมูลสรุปรูปภาพ

def encode_image(image_path):
   
"""Getting the base64 string"""
   
with open(image_path, "rb") as image_file:
       
return base64.b64encode(image_file.read()).decode("utf-8")


def image_summarize(img_base64, prompt):
   
"""Make image summary"""
   model
= ChatVertexAI(model_name="gemini-pro-vision", max_output_tokens=1024)

   msg
= model(
       
[
           
HumanMessage(
               content
=[
                   
{"type": "text", "text": prompt},
                   
{
                       
"type": "image_url",
                       
"image_url": {"url": f"data:image/jpeg;base64,{img_base64}"},
                   
},
               
]
           
)
       
]
   
)
   
return msg.content


def generate_img_summaries(path):
   
"""
   Generate summaries and base64 encoded strings for images
   path: Path to list of .jpg files extracted by Unstructured
   """


   
# Store base64 encoded images
   img_base64_list
= []

   
# Store image summaries
   image_summaries
= []

   
# Prompt
   prompt
= """You are an assistant tasked with summarizing images for retrieval. \
   These summaries will be embedded and used to retrieve the raw image. \
   Give a concise summary of the image that is well optimized for retrieval."""


   
# Apply to images
   
for img_file in sorted(os.listdir(path)):
       
if img_file.endswith(".jpg"):
           img_path
= os.path.join(path, img_file)
           base64_image
= encode_image(img_path)
           img_base64_list
.append(base64_image)
           image_summaries
.append(image_summarize(base64_image, prompt))

   
return img_base64_list, image_summaries


# Image summaries
img_base64_list
, image_summaries = generate_img_summaries("./cj")

len
(img_base64_list)

len
(image_summaries)

image_summaries
[0]

คุณควรเห็นเอาต์พุตดังต่อไปนี้ fad6d479dd46cb37.png

8 ขั้นตอนที่ 5: สร้างการดึงข้อมูลหลายเวกเตอร์

มาสร้างสรุปข้อความและรูปภาพ แล้วบันทึกลงในที่เก็บเวกเตอร์ ChromaDB

นำเข้าไลบรารีที่จำเป็น

import uuid
from langchain.retrievers.multi_vector import MultiVectorRetriever
from langchain.storage import InMemoryStore
from langchain_community.vectorstores import Chroma
from langchain_core.documents import Document

สร้างการดึงข้อมูลหลายเวกเตอร์

def create_multi_vector_retriever(
   vectorstore
, text_summaries, texts, table_summaries, tables, image_summaries, images
):
   
"""
   Create retriever that indexes summaries, but returns raw images or texts
   """


   
# Initialize the storage layer
   store
= InMemoryStore()
   id_key
= "doc_id"

   
# Create the multi-vector retriever
   retriever
= MultiVectorRetriever(
       vectorstore
=vectorstore,
       docstore
=store,
       id_key
=id_key,
   
)

   
# Helper function to add documents to the vectorstore and docstore
   
def add_documents(retriever, doc_summaries, doc_contents):
       doc_ids
= [str(uuid.uuid4()) for _ in doc_contents]
       summary_docs
= [
           
Document(page_content=s, metadata={id_key: doc_ids[i]})
           
for i, s in enumerate(doc_summaries)
       
]
       retriever
.vectorstore.add_documents(summary_docs)
       retriever
.docstore.mset(list(zip(doc_ids, doc_contents)))

   
# Add texts, tables, and images
   
# Check that text_summaries is not empty before adding
   
if text_summaries:
       add_documents
(retriever, text_summaries, texts)
   
# Check that table_summaries is not empty before adding
   
if table_summaries:
       add_documents
(retriever, table_summaries, tables)
   
# Check that image_summaries is not empty before adding
   
if image_summaries:
       add_documents
(retriever, image_summaries, images)

   
return retriever


# The vectorstore to use to index the summaries
vectorstore
= Chroma(
   collection_name
="mm_rag_cj_blog",
   embedding_function
=VertexAIEmbeddings(model_name="textembedding-gecko@latest"),
)

# Create retriever
retriever_multi_vector_img
= create_multi_vector_retriever(
   vectorstore
,
   text_summaries
,
   texts
,
   table_summaries
,
   tables
,
   image_summaries
,
   img_base64_list
,
)
 

9 ขั้นตอนที่ 6: การสร้าง RAG แบบหลายรูปแบบ

  1. กำหนดฟังก์ชันยูทิลิตี
import io
import re

from IPython.display import HTML, display
from langchain_core.runnables import RunnableLambda, RunnablePassthrough
from PIL import Image


def plt_img_base64(img_base64):
   
"""Disply base64 encoded string as image"""
   
# Create an HTML img tag with the base64 string as the source
   image_html
= f'<img src="data:image/jpeg;base64,{img_base64}" />'
   
# Display the image by rendering the HTML
   display
(HTML(image_html))


def looks_like_base64(sb):
   
"""Check if the string looks like base64"""
   
return re.match("^[A-Za-z0-9+/]+[=]{0,2}$", sb) is not None


def is_image_data(b64data):
   
"""
   Check if the base64 data is an image by looking at the start of the data
   """

   image_signatures
= {
       b
"\xFF\xD8\xFF": "jpg",
       b
"\x89\x50\x4E\x47\x0D\x0A\x1A\x0A": "png",
       b
"\x47\x49\x46\x38": "gif",
       b
"\x52\x49\x46\x46": "webp",
   
}
   
try:
       header
= base64.b64decode(b64data)[:8]  # Decode and get the first 8 bytes
       
for sig, format in image_signatures.items():
           
if header.startswith(sig):
               
return True
       
return False
   
except Exception:
       
return False


def resize_base64_image(base64_string, size=(128, 128)):
   
"""
   Resize an image encoded as a Base64 string
   """

   
# Decode the Base64 string
   img_data
= base64.b64decode(base64_string)
   img
= Image.open(io.BytesIO(img_data))

   
# Resize the image
   resized_img
= img.resize(size, Image.LANCZOS)

   
# Save the resized image to a bytes buffer
   buffered
= io.BytesIO()
   resized_img
.save(buffered, format=img.format)

   
# Encode the resized image to Base64
   
return base64.b64encode(buffered.getvalue()).decode("utf-8")


def split_image_text_types(docs):
   
"""
   Split base64-encoded images and texts
   """

   b64_images
= []
   texts
= []
   
for doc in docs:
       
# Check if the document is of type Document and extract page_content if so
       
if isinstance(doc, Document):
           doc
= doc.page_content
       
if looks_like_base64(doc) and is_image_data(doc):
           doc
= resize_base64_image(doc, size=(1300, 600))
           b64_images
.append(doc)
       
else:
           texts
.append(doc)
   
if len(b64_images) > 0:
       
return {"images": b64_images[:1], "texts": []}
   
return {"images": b64_images, "texts": texts}
  1. กําหนดพรอมต์รูปภาพเฉพาะโดเมน
def img_prompt_func(data_dict):
   
"""
   Join the context into a single string
   """

   formatted_texts
= "\n".join(data_dict["context"]["texts"])
   messages
= []

   
# Adding the text for analysis
   text_message
= {
       
"type": "text",
       
"text": (
           
"You are financial analyst tasking with providing investment advice.\n"
           
"You will be given a mixed of text, tables, and image(s) usually of charts or graphs.\n"
           
"Use this information to provide investment advice related to the user question. \n"
           f
"User-provided question: {data_dict['question']}\n\n"
           
"Text and / or tables:\n"
           f
"{formatted_texts}"
       
),
   
}
   messages
.append(text_message)
   
# Adding image(s) to the messages if present
   
if data_dict["context"]["images"]:
       
for image in data_dict["context"]["images"]:
           image_message
= {
               
"type": "image_url",
               
"image_url": {"url": f"data:image/jpeg;base64,{image}"},
           
}
           messages
.append(image_message)
   
return [HumanMessage(content=messages)]

  1. กําหนดเชน RAG แบบหลายรูปแบบ
def multi_modal_rag_chain(retriever):
   
"""
   Multi-modal RAG chain
   """


   
# Multi-modal LLM
   model
= ChatVertexAI(
       temperature
=0, model_name="gemini-pro-vision", max_output_tokens=1024
   
)

   
# RAG pipeline
   chain
= (
       
{
           
"context": retriever | RunnableLambda(split_image_text_types),
           
"question": RunnablePassthrough(),
       
}
       
| RunnableLambda(img_prompt_func)
       
| model
       
| StrOutputParser()
   
)

   
return chain


# Create RAG chain
chain_multimodal_rag
= multi_modal_rag_chain(retriever_multi_vector_img)

10 ขั้นตอนที่ 7: ทดสอบการค้นหา

  1. เรียกดูเอกสารที่เกี่ยวข้อง
query = "What are the EV / NTM and NTM rev growth for MongoDB, Cloudflare, and Datadog?"
docs
= retriever_multi_vector_img.get_relevant_documents(query, limit=1)

# We get relevant docs
len
(docs)

docs
         You may get similar output 

74ecaca749ae459a.png

plt_img_base64(docs[3])

989ad388127f5d60.png

  1. เรียกใช้ RAG กับข้อความค้นหาเดียวกัน
result = chain_multimodal_rag.invoke(query)

from IPython.display import Markdown as md
md
(result)

เอาต์พุตตัวอย่าง (อาจแตกต่างกันไปเมื่อคุณเรียกใช้โค้ด)

e5e102eaf10289ab.png

11 ล้างข้อมูล

โปรดทำตามขั้นตอนต่อไปนี้เพื่อเลี่ยงไม่ให้เกิดการเรียกเก็บเงินกับบัญชี Google Cloud สำหรับทรัพยากรที่ใช้ในโค้ดแล็บนี้

  1. ในคอนโซล Google Cloud ให้ไปที่หน้าจัดการทรัพยากร
  2. ในรายการโปรเจ็กต์ ให้เลือกโปรเจ็กต์ที่ต้องการลบ แล้วคลิกลบ
  3. ในกล่องโต้ตอบ ให้พิมพ์รหัสโปรเจ็กต์ แล้วคลิกปิดเพื่อลบโปรเจ็กต์

12 ขอแสดงความยินดี

ยินดีด้วย คุณพัฒนา RAG แบบหลายรูปแบบโดยใช้ Gemini เรียบร้อยแล้ว