Práctica: Creación de potentes búsquedas multimodales con Voyager-3 y LangGraph
Tutoriales prácticos sobre IAPublicado hace 8 meses Círculo de intercambio de inteligencia artificial 10.7K 00
Voyager 3 de Voyage AI es un nuevo modelo de última generación que permite incrustar texto e imágenes en el mismo espacio. En este post, explicaré cómo extraer estas incrustaciones multimodales de revistas, almacenarlas en una base de datos vectorial (Weaviate) y realizar búsquedas de similitud en texto e imágenes utilizando los mismos vectores de incrustación.
Incrustar imágenes y texto en el mismo espacio nos permitirá realizar búsquedas de gran precisión en contenidos multimodales como páginas web, archivos PDF, revistas, libros, folletos y documentos diversos. ¿Por qué es tan interesante esta técnica? El principal aspecto interesante de incrustar texto e imágenes en el mismo espacio es que se puede buscar y recuperar texto asociado a una imagen concreta y viceversa. Por ejemplo, si buscas gatos, encontrarás imágenes en las que aparezcan gatos, pero también obtendrás texto que haga referencia a esas imágenes, aunque el texto no diga explícitamente la palabra "gato".
Permítanme mostrar la diferencia entre la búsqueda tradicional de similitud de incrustación de texto y el espacio de incrustación multimodal:
EJEMPLO DE PREGUNTA: ¿Qué dice la revista sobre los gatos?

Una captura de pantalla de una revista de fotografía - OUTDOOR
Respuestas a la búsqueda regular de similitudes
Los resultados de búsqueda proporcionados no contienen información específica sobre gatos. Mencionan retratos de animales y técnicas fotográficas, pero no mencionan explícitamente gatos ni detalles relacionados con ellos.
Como se muestra arriba, la palabra "gato" no se menciona; sólo hay una foto y una explicación de cómo hacer fotos del animal. Como no se menciona la palabra "gato", una búsqueda por similitud normal no arrojó ningún resultado.
Búsqueda multimodal de respuestas
Esta revista presenta el retrato de un gato, en el que se destaca la fina captura de sus rasgos faciales y su carácter. El texto subraya cómo los retratos de animales bien hechos llegan al alma del sujeto y crean una conexión emocional con el espectador a través de un convincente contacto visual.
Mediante una búsqueda multimodal, encontraremos la foto de un gato y vincularemos a ella el texto pertinente. Proporcionar estos datos al modelo le permitirá responder mejor y comprender el contexto.
Cómo construir una cadena de incrustación y recuperación multimodal
A continuación describiré cómo funciona este tipo de canalización en unos pocos pasos:
- Utilizaremos el Sin estructurar(una potente biblioteca de Python para la extracción de datos) Extrae texto e imágenes de archivos PDF.
- Utilizaremos el Voyager Multimodal 3 El modelo crea vectores multimodales para texto e imágenes en el mismo espacio vectorial.
- Lo insertaremos en el almacén de vectores (Weaviate) en.
- Por último, realizaremos una búsqueda por similitud y compararemos los resultados del texto y las imágenes.
Paso 1: Configurar el almacenamiento vectorial y extraer las imágenes y el texto del documento (PDF)
Aquí tenemos que hacer algo de trabajo manual. Normalmente, Weaviate es un almacén vectorial muy fácil de usar que transforma automáticamente los datos y añade incrustaciones al insertarlos. Sin embargo, no hay plugin para Voyager Multimodal v3, por lo que tenemos que calcular los embeddings manualmente.En este caso, tenemos que crear una colección sin definir un módulo vectorizador.
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
Aquí, estoy ejecutando una instancia local de Weaviate en un contenedor Docker.
Segundo paso:Extraer documentos e imágenes de PDF
Este es un paso clave en el funcionamiento del proceso. Aquí obtendremos un PDF que contenga texto e imágenes. A continuación, extraeremos el contenido (imágenes y texto) y lo almacenaremos en los bloques pertinentes. Así, cada bloque será un PDF que contendrá cadenas (texto real) y Imágenes Python PIL La lista de elementos de
Utilizaremos el Sin estructurar para hacer parte del trabajo pesado, pero todavía tenemos que escribir algo de lógica y configurar los parámetros de la biblioteca.
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)
En este caso, debemos utilizar el hi_res estrategia y utilizar el extract_image_block_to_payload Exporta la imagen a payload ya que necesitaremos esta información más adelante para la incrustación propiamente dicha. Una vez extraídos todos los elementos, los agruparemos en bloques en función de los títulos del documento.
Para más información, consulte Documentación no estructurada sobre chunking.
En el siguiente script, utilizaremos estos bloques para generar dos listas:
- Una lista con los objetos que enviaremos a la Voyager 3 para crear el vector
- Una lista que contiene los metadatos extraídos por Unstructured. Estos metadatos son necesarios porque tenemos que añadirlos al almacén vectorial. Nos proporcionará atributos adicionales para filtrar y nos dirá algo sobre los datos recuperados.
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)
El resultado de este script será una lista de listas cuyo contenido se muestra a continuación:
[['来自\n\n冰岛 KIRKJUFELL 的位置',
<PIL.Image.Image image mode=RGB size=1000x381>,
<PIL.Image.Image image mode=RGB size=526x1000>],
['这座标志性的山峰是我们冰岛拍摄地点的首选,而且在我们去那里之前,我们就看过许多从附近瀑布拍摄的照片。因此,这是我们在日出时前往的第一个地方 - 我们没有失望。这些瀑布为这张照片(顶部)提供了完美的近景趣味,而从这个角度来看,Kirkjufell 是一座完美的尖山。我们花了一两个小时简单地探索这些瀑布,找到了几个不同的角度。']]
Paso 3: Vectorizar los datos extraídos
En este paso, utilizaremos el bloque creado en el paso anterior con la propiedad Paquetes Voyager Python Envíalos a Voyager, que nos devolverá una lista de todos los objetos incrustados. Podemos entonces utilizar este resultado y eventualmente almacenarlo en 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
)
Si accedemos a result.embeddings, obtendremos una lista que contiene una lista de todos los vectores de incrustación calculados:
[[-0.052734375, -0.0164794921875, 0.050048828125, 0.01348876953125, -0.048095703125, ...]]Ahora podemos utilizar la función batch.add_object
almacena estos datos incrustados en Weaviate como un único lote. Tenga en cuenta que también hemos añadido metadatos al parámetro 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]
)
Paso 4: Consulta de los datos
Ahora podemos realizar una búsqueda por similitud y consultar los datos. Esto es fácil porque el proceso es similar a una búsqueda de similitud normal realizada en una incrustación de texto. Como Weaviate no tiene un módulo para Voyager Multimodal, debemos calcular el vector de consulta de búsqueda nosotros mismos antes de pasarlo a Weaviate para realizar una búsqueda de similitud.
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)
La imagen siguiente muestra que una búsqueda de cascadas devolverá texto e imágenes relevantes para esta consulta de búsqueda. Como puede ver, las fotos reflejan cascadas, pero el texto en sí no las menciona. El texto se refiere a una imagen en la que aparece una cascada, razón por la cual también se recuperó. Esto no es posible con las búsquedas normales de texto incrustado.

Una imagen que muestra los resultados de la búsqueda de similitudes
Paso 5: Añadirlo a toda la cadena de búsqueda
Ahora que hemos extraído el texto y las imágenes de la revista, creado incrustaciones para ellos, añadido a Weaviate, y configurado nuestra búsqueda por similitud, voy a añadirlo a la tubería de recuperación general. En este ejemplo, voy a utilizar LangGraph. el usuario hará una pregunta sobre esta revista, y la tubería responderá a esa pregunta. Ahora que todo el trabajo está hecho, esta parte es tan fácil como configurar una tubería de recuperación típica utilizando texto normal.
He abstraído parte de la lógica que discutimos en la sección anterior en otros módulos. Así es como la he integrado en el módulo LangGraph Ejemplos en proyecto.
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"]
El código anterior generará el siguiente gráfico

Representación visual de los gráficos creados

En este trazo, elPuedes ver el contenido y las imágenes que se envían a OpenAI para responder a las preguntas.
llegar a un veredicto
La incrustación multimodal abre la posibilidad de integrar y recuperar información de distintos tipos de datos (por ejemplo, texto e imágenes) dentro del mismo espacio de incrustación. Combinando herramientas punteras como el modelo Voyager Multimodal 3, Weaviate y LangGraph, mostramos cómo construir un robusto conducto de recuperación que comprende y vincula contenidos de forma más intuitiva que los enfoques tradicionales basados únicamente en texto.
Este enfoque mejora significativamente la precisión de la búsqueda y la recuperación de diversas fuentes de datos, como revistas, folletos y archivos PDF. También demuestra cómo la incrustación multimodal puede proporcionar información más rica y consciente del contexto que vincula las imágenes al texto descriptivo incluso en ausencia de palabras clave explícitas. Este tutorial le permite explorar y aplicar estas técnicas a sus proyectos.
Cuaderno de ejemplo: https://github.com/vectrix-ai/vectrix-graphs/blob/main/examples/multi-model-embeddings.ipynb
© declaración de copyright
Derechos de autor del artículo Círculo de intercambio de inteligencia artificial Todos, por favor no reproducir sin permiso.
Artículos relacionados
Sin comentarios...