Создайте приложение вопросов и ответов с помощью Multi-Modal RAG с помощью Gemini Pro.

1. Введение

Что такое RAG?

Метод расширенной генерации с учетом поиска (Retrieval Augmented Generation, RAG) — это техника, которая сочетает в себе возможности больших языковых моделей (LLM) со способностью извлекать релевантную информацию из внешних источников знаний. Это означает, что LLM не только полагается на свои внутренние обучающие данные, но и может получать доступ к актуальной, специфической информации и включать ее в свои ответы.

936b7eedba773cac.png

Популярность RAG растет по нескольким причинам:

  • Повышенная точность и релевантность: RAG позволяет магистрам права давать более точные и релевантные ответы, основываясь на фактической информации, полученной из внешних источников. Это особенно полезно в ситуациях, когда актуальные знания имеют решающее значение, например, при ответе на вопросы о текущих событиях или предоставлении информации по конкретным темам.
  • Снижение галлюцинаций: LLM-ы иногда могут генерировать ответы, которые кажутся правдоподобными, но на самом деле неверны или бессмысленны. RAG помогает смягчить эту проблему, проверяя полученную информацию по внешним источникам.
  • Повышенная адаптивность: RAG делает программы магистратуры по прикладным наукам (LLM) более адаптируемыми к различным областям и задачам. Благодаря использованию различных источников знаний, программу LLM можно легко настроить для предоставления информации по широкому кругу тем.
  • Улучшение пользовательского опыта: RAG может улучшить общий пользовательский опыт, предоставляя более информативные, надежные и релевантные ответы.

Почему мультимодальный

В современном мире, насыщенном данными, документы часто сочетают текст и изображения для всесторонней передачи информации. Однако большинство систем поиска с дополненной реальностью (RAG) упускают из виду ценные сведения, скрытые в изображениях. По мере роста популярности многомодальных больших языковых моделей (LLM) крайне важно изучить, как мы можем использовать визуальный контент наряду с текстом в системах RAG, чтобы глубже понять информационный ландшафт.

Два варианта для многомодальной RAG

  • Мультимодальные встраивания — модель мультимодальных встраиваний генерирует 1408-мерные векторы* на основе предоставленных вами входных данных, которые могут включать комбинацию изображений, текста и видео. Вектор встраивания изображения и вектор встраивания текста находятся в одном семантическом пространстве и имеют одинаковую размерность. Следовательно, эти векторы можно использовать взаимозаменяемо для таких задач, как поиск изображения по тексту или поиск видео по изображению. Посмотрите эту демонстрацию .
  1. Используйте мультимодальное встраивание для встраивания текста и изображений.
  2. Найдите оба варианта, используя поиск по сходству.
  3. Передайте как полученные исходные изображения, так и фрагменты текста в многомодальную LLM-систему для синтеза ответа.
  • Встраивание текста -
  1. Используйте многомодальный LLM для создания текстовых резюме изображений.
  2. Встраивание и извлечение текста
  3. Передайте фрагменты текста в LLM для синтеза ответов.

Что такое многовекторный ретривер?

Многовекторный поиск использует резюме разделов документа для извлечения исходного контента с целью синтеза ответов. Он повышает качество RAG, особенно для задач, требующих большого количества таблиц, графиков, диаграмм и т. д. Подробнее можно узнать в блоге Langchain .

Что вы построите

Пример использования: Разработка системы ответов на вопросы с использованием Gemini Pro.

Представьте, что у вас есть документы, содержащие сложные графики или диаграммы, насыщенные информацией. Вы хотите извлечь эти данные, чтобы ответить на вопросы или запросы.

В этом практическом задании вы выполните следующие действия:

  • Загрузка данных с использованием document_loaders из LangChain.
  • Создавайте текстовые резюме, используя модель 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 и войдите в ту же учетную запись, что и ваша текущая активная учетная запись Google Cloud.

3. Создание многомодальной RAG

В этом практическом занятии используется Vertex AI SDK для Python и Langchain , чтобы продемонстрировать, как реализовать описанный здесь «Вариант 2» с помощью Google Cloud.

Полный код можно найти в файле Multi-modal RAG with Google Cloud в указанном репозитории .

4. Шаг 1: Установка и импорт зависимостей

!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.