Практическое занятие: создание мощных мультимодальных поисковых систем с помощью Voyager-3 и LangGraph

Voyage AI's Voyager 3 - это новая современная модель, которая позволяет встраивать текст и изображения в одно и то же пространство. В этом посте я расскажу, как извлечь эти мультимодальные вкрапления из журналов, сохранить их в векторной базе данных (Weaviate) и выполнить поиск по сходству текста и изображений, используя те же векторы вкраплений.

 

Встраивание изображений и текста в одно пространство позволит нам осуществлять высокоточный поиск по мультимодальному контенту, такому как веб-страницы, PDF-файлы, журналы, книги, брошюры и различные документы. Почему эта техника так интересна? Главный интересный аспект встраивания текста и изображений в одно пространство заключается в том, что вы можете искать и извлекать текст, связанный с определенным изображением, и наоборот. Например, если вы ищете кошку, вы найдете изображения, на которых она изображена, но вы также получите текст, который ссылается на эти изображения, даже если в тексте нет прямого слова "кошка".

Позвольте мне показать разницу между традиционным поиском сходства между текстовыми вкраплениями и пространством мультимодальных вкраплений:

ПРИМЕРНЫЙ ВОПРОС: Что в журнале написано о кошках?

实践:使用 Voyager-3 和 LangGraph 构建强大的多模态搜索

Скриншот из фотожурнала - OUTDOOR

 

Ответы на вопросы по регулярному поиску сходства

Результаты поиска не содержат конкретной информации о кошках. В них упоминаются портреты животных и техника фотографирования, но нет прямого упоминания кошек или деталей, связанных с ними.

Как показано выше, слово "кошка" не упоминается; есть только картинка и объяснение, как фотографировать животное. Поскольку слово "кошка" не упоминалось, обычный поиск по сходству не дал никаких результатов.

Мультимодальный поиск ответов

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

Используя мультимодальный поиск, мы найдем изображение кошки, а затем свяжем с ним соответствующий текст. Предоставление этих данных модели позволит ей лучше отвечать и понимать контекст.

 

Как построить конвейер мультимодального встраивания и поиска

Сейчас я в несколько шагов опишу, как работает такой трубопровод:

  1. Мы будем использовать Неструктурированные(мощная библиотека Python для извлечения данных) Извлекает текст и изображения из файлов PDF.
  2. Мы будем использовать Вояджер Мультимодал 3 Модель создает мультимодальные векторы для текста и изображений в одном векторном пространстве.
  3. Мы вставим его в векторное хранилище (Weaviate) в.
  4. Наконец, мы выполним поиск по сходству и сравним результаты для текста и изображений.

Шаг 1: Настройте векторное хранилище и извлеките изображения и текст из документа (PDF)

Здесь нам придется проделать некоторую ручную работу. Обычно Weaviate - это очень простое в использовании хранилище векторов, которое автоматически преобразует данные и добавляет вкрапления при вставке. Однако для Voyager Multimodal v3 нет плагина, поэтому нам придется вычислять вкрапления вручную.В этом случае мы должны создать коллекцию без определения модуля векторизатора.

import weaviate
from weaviate.classes.config import Configure
client = weaviate.connect_to_local()
collection_name = "multimodal_demo"
client.collections.delete(collection_name)
try:
client.collections.create(
name=collection_name,
vectorizer_config=Configure.Vectorizer.none() # 不为此集合设置向量化器
)
collection = client.collections.get(collection_name)
except Exception:
collection = client.collections.get(collection_name)pyt

Здесь я запускаю локальный экземпляр Weaviate в контейнере Docker.

Шаг 2:Извлечение документов и изображений из PDF

Это ключевой шаг в работе процесса. Здесь мы получим PDF, содержащий текст и изображения. Затем мы извлечем содержимое (изображения и текст) и сохраним его в соответствующих блоках. Таким образом, каждый блок будет представлять собой PDF, содержащий строки (собственно текст) и Изображения Python PIL Список элементов

Мы будем использовать Неструктурированные Библиотека делает часть тяжелой работы, но нам все равно нужно написать некоторую логику и настроить параметры библиотеки.

from unstructured.partition.auto import partition
from unstructured.chunking.title import chunk_by_title
elements = partition(
filename="./files/magazine_sample.pdf",
strategy="hi_res",
extract_image_block_types=["Image", "Table"],
extract_image_block_to_payload=True)
chunks = chunk_by_title(elements)

Здесь мы должны использовать hi_res стратегию и использовать извлечь_изображение_блока_к_платежу Экспортируйте изображение в полезную нагрузку, так как эта информация понадобится нам позже для встраивания. После того как мы извлекли все элементы, сгруппируем их в блоки на основе заголовков в документе.

За дополнительной информацией обращайтесь к Неструктурированная документация по кускованию.

В следующем сценарии мы используем эти блоки для вывода двух списков:

  1. Список объектов, которые мы отправим на "Вояджер-3" для создания вектора
  2. Список, содержащий метаданные, извлеченные Unstructured. Эти метаданные необходимы, потому что мы должны добавить их в векторное хранилище. Они дадут нам дополнительные атрибуты для фильтрации и расскажут кое-что о полученных данных.
from unstructured.staging.base import elements_from_base64_gzipped_json
import PIL.Image
import io
import base64
embedding_objects = []
embedding_metadatas = []
for chunk in chunks:
embedding_object = []
metedata_dict = {
"text": chunk.to_dict()["text"],
"filename": chunk.to_dict()["metadata"]["filename"],
"page_number": chunk.to_dict()["metadata"]["page_number"],
"last_modified": chunk.to_dict()["metadata"]["last_modified"],
"languages": chunk.to_dict()["metadata"]["languages"],
"filetype": chunk.to_dict()["metadata"]["filetype"]
}
embedding_object.append(chunk.to_dict()["text"])
# 将图像添加到嵌入对象
if "orig_elements" in chunk.to_dict()["metadata"]:
base64_elements_str = chunk.to_dict()["metadata"]["orig_elements"]
eles = elements_from_base64_gzipped_json(base64_elements_str)
image_data = []
for ele in eles:
if ele.to_dict()["type"] == "Image":
base64_image = ele.to_dict()["metadata"]["image_base64"]
image_data.append(base64_image)
pil_image = PIL.Image.open(io.BytesIO(base64.b64decode(base64_image)))
# 如果图像大于 1000x1000,则在保持纵横比的同时调整图像大小
if pil_image.size[0] > 1000 or pil_image.size[1] > 1000:
ratio = min(1000/pil_image.size[0], 1000/pil_image.size[1])
new_size = (int(pil_image.size[0] * ratio), int(pil_image.size[1] * ratio))
pil_image = pil_image.resize(new_size, PIL.Image.Resampling.LANCZOS)
embedding_object.append(pil_image)
metedata_dict["image_data"] = image_data
embedding_objects.append(embedding_object)
embedding_metadatas.append(metedata_dict)

Результатом работы этого сценария будет список списков, содержимое которых показано ниже:

[['来自\n\n冰岛 KIRKJUFELL 的位置',
<PIL.Image.Image image mode=RGB size=1000x381>,
<PIL.Image.Image image mode=RGB size=526x1000>],
['这座标志性的山峰是我们冰岛拍摄地点的首选,而且在我们去那里之前,我们就看过许多从附近瀑布拍摄的照片。因此,这是我们在日出时前往的第一个地方 - 我们没有失望。这些瀑布为这张照片(顶部)提供了完美的近景趣味,而从这个角度来看,Kirkjufell 是一座完美的尖山。我们花了一两个小时简单地探索这些瀑布,找到了几个不同的角度。']]

Шаг 3: Векторизация извлеченных данных

В этом шаге мы используем блок, созданный в предыдущем шаге, с параметром Пакеты Voyager Python Отправьте их в Voyager, который вернет нам список всех встроенных объектов. Затем мы можем использовать этот результат и в конечном итоге сохранить его в Weaviate.

from dotenv import load_dotenv
import voyageai
load_dotenv()
vo = voyageai.Client()
# 这将自动使用环境变量 VOYAGE_API_KEY。
# 或者,您可以使用 vo = voyageai.Client(api_key="<您的密钥>")
# 包含文本字符串和 PIL 图像对象的示例输入
inputs = embedding_objects
# 向量化输入
result = vo.multimodal_embed(
inputs,
model="voyage-multimodal-3",
truncation=False
)

Если мы обратимся к result.embeddings, то получим список, содержащий перечень всех вычисленных векторов встраивания:

[[-0.052734375, -0.0164794921875, 0.050048828125, 0.01348876953125, -0.048095703125, ...]]

Теперь мы можем использовать batch.add_object метод сохраняет эти встроенные данные в Weaviate в виде одного пакета. Обратите внимание, что мы также добавили метаданные в параметр properties.

with collection.batch.dynamic() as batch:
for i, data_row in enumerate(embedding_objects):
batch.add_object(
properties=embedding_metadatas[i],
vector=result.embeddings[i]
)

Шаг 4: Запрос данных

Теперь мы можем выполнить поиск по сходству и запросить данные. Это легко, поскольку процесс похож на обычный поиск по сходству, выполняемый на текстовом вкраплении. Поскольку в Weaviate нет модуля для Voyager Multimodal, мы должны сами вычислить вектор поискового запроса, прежде чем передать его в Weaviate для выполнения поиска по сходству.

from weaviate.classes.query import MetadataQuery
question = "杂志上关于瀑布说了什么?"
vector = vo.multimodal_embed([[question]], model="voyage-multimodal-3")
vector.embeddings[0]
response = collection.query.near_vector(
near_vector=vector.embeddings[0], # 您的查询向量在此处
limit=2,
return_metadata=MetadataQuery(distance=True)
)
# 显示结果
for o in response.objects:
print(o.properties['text'])
for image_data in o.properties['image_data']:
# 使用 PIL 显示图像
img = PIL.Image.open(io.BytesIO(base64.b64decode(image_data)))
width, height = img.size
if width > 500 or height > 500:
ratio = min(500/width, 500/height)
new_size = (int(width * ratio), int(height * ratio))
img = img.resize(new_size)
display(img)
print(o.metadata.distance)

На изображении ниже показано, что при поиске водопадов будут возвращены текст и изображения, соответствующие этому поисковому запросу. Как видите, на фотографиях изображены водопады, но в самом тексте они не упоминаются. Текст относится к изображению с водопадом, поэтому оно также было найдено. Это невозможно при обычном поиске по вставке текста.

实践:使用 Voyager-3 和 LangGraph 构建强大的多模态搜索

Изображение, показывающее результаты поиска по сходству

 

Шаг 5: Добавьте его ко всему поисковому конвейеру

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

Я абстрагировал часть логики, которую мы обсуждали в предыдущем разделе, в другие модули. Вот как я интегрировал ее в модуль LangGraph Примеры в разработке.

class MultiModalRetrievalState(TypedDict):
messages: Annotated[Sequence[BaseMessage], add_messages]
results: List[Document]
base_64_images: List[str]
class RAGNodes(BaseNodes):
def __init__(self, logger, mode="online", document_handler=None):
super().__init__(logger, mode)
self.weaviate = Weaviate()
self.mode = mode
async def multi_modal_retrieval(self, state: MultiModalRetrievalState, config):
collection_name = config.get("configurable", {}).get("collection_name")
self.weaviate.set_collection(collection_name)
print("正在运行多模态检索")
print(f"正在搜索 {state['messages'][-1].content}")
results = self.weaviate.similarity_search(
query=state["messages"][-1].content, k=3, type="multimodal"
)
return {"results": results}
async def answer_question(self, state: MultiModalRetrievalState, config):
print("正在回答问题")
llm = self.llm_factory.create_llm(mode=self.mode, model_type="default")
include_images = config.get("configurable", {}).get("include_images", False)
chain = self.chain_factory.create_multi_modal_chain(
llm,
state["messages"][-1].content,
state["results"],
include_images=include_images,
)
response = await chain.ainvoke({})
message = AIMessage(content=response)
return {"messages": message}
# 定义配置
class GraphConfig(TypedDict):
mode: str = "online"
collection_name: str
include_images: bool = False
graph_nodes = RAGNodes(logger)
graph = StateGraph(MultiModalRetrievalState, config_schema=GraphConfig)
graph.add_node("multi_modal_retrieval", graph_nodes.multi_modal_retrieval)
graph.add_node("answer_question", graph_nodes.answer_question)
graph.add_edge(START, "multi_modal_retrieval")
graph.add_edge("multi_modal_retrieval", "answer_question")
graph.add_edge("answer_question", END)
multi_modal_graph = graph.compile()
__all__ = ["multi_modal_graph"]

Приведенный выше код сгенерирует следующий график

实践:使用 Voyager-3 和 LangGraph 构建强大的多模态搜索

Визуальное представление созданных графиков

 

实践:使用 Voyager-3 和 LangGraph 构建强大的多模态搜索

В этом следеВы можете видеть контент и изображения, отправляемые в OpenAI для ответов на вопросы.

 

вынести вердикт

Мультимодальное встраивание открывает возможность интегрировать и извлекать информацию из разных типов данных (например, текст и изображения) в одном пространстве встраивания. Объединив такие передовые инструменты, как мультимодальная модель Voyager 3, Weaviate и LangGraph, мы показали, как построить надежный поисковый конвейер, который понимает и связывает контент более интуитивно, чем традиционные подходы, использующие только текст.

Этот подход значительно повышает точность поиска и извлечения информации из различных источников данных, таких как журналы, брошюры и PDF-файлы. Он также демонстрирует, как мультимодальное встраивание может обеспечить более богатую, контекстно-ориентированную информацию, которая связывает изображения с описательным текстом даже в отсутствие явных ключевых слов. Это учебное пособие позволит вам изучить и применить эти методы в своих проектах.

Пример блокнота: https://github.com/vectrix-ai/vectrix-graphs/blob/main/examples/multi-model-embeddings.ipynb

© заявление об авторских правах

Похожие статьи

Нет комментариев

Вы должны войти в систему, чтобы участвовать в комментариях!
Войти сейчас
нет
Нет комментариев...