Construindo um sistema RAG multimodal com Elasticsearch: A história de Gotham City
Aprenda a construir um sistema multimodal de Recuperação-Geração Aumentada (RAG) que integra dados de texto, áudio, vídeo e imagem para fornecer recuperação de informações mais rica e contextualizada.
Neste blog, você aprenderá a criar um pipeline RAG (Retrieval-Augmented Generation) multimodal usando o Elasticsearch. Exploraremos como aproveitar o ImageBind para gerar incorporações para vários tipos de dados, incluindo texto, imagens, áudio e mapas de profundidade. Você também descobrirá como armazenar e recuperar esses embeddings de forma eficiente no Elasticsearch usando dense_vector e pesquisa k-NN. Por fim, integraremos um grande modelo de linguagem (LLM) para analisar as evidências recuperadas e gerar um relatório final abrangente.
Como funciona o gasoduto multimodal RAG?
Coletando pistas → Imagens, áudio, textos e mapas de profundidade da cena do crime em Gotham.
Gerando embeddings → Cada arquivo é convertido em um vetor usando o modelo multimodal ImageBind.
Indexação no Elasticsearch → Os vetores são armazenados para recuperação eficiente.
Pesquisando por similaridade → Dada uma nova pista, os vetores mais semelhantes são recuperados.
O LLM analisa as evidências → Um modelo GPT-4 sintetiza a resposta e identifica o suspeito!
Tecnologias utilizadas
ImageBind → Gera embeddings unificados para várias modalidades.
Elasticsearch → Permite pesquisa vetorial rápida e eficiente.
LLM (GPT-4, OpenAI) → Analisa as evidências e gera um relatório final.
Para quem é este blog?
Usuários do Elastic interessados em pesquisa vetorial multimodal.
Desenvolvedores que buscam entender o RAG Multimodal na prática.
Qualquer pessoa em busca de soluções escaláveis para analisar dados de diversas fontes.
Pré-requisitos para RAG multimodal: Configurando o ambiente
Para resolver o crime em Gotham City, você precisa configurar seu ambiente tecnológico. Siga este guia passo a passo:
1. Requisitos técnicos
Componente | Especificação |
|---|---|
Sistema Operacional | Linux, macOS ou Windows |
Python | 3.10 ou posterior |
BATER | Mínimo de 8 GB (16 GB recomendado) |
GPU | Opcional, mas recomendado para ImageBind |
2. Configurando o projeto
Todos os materiais de investigação estão disponíveis no GitHub, e usaremos o Jupyter Notebook (Google Colab) para esta experiência interativa de resolução de crimes. Siga estes passos para começar:
Configurando com o Jupyter Notebook (Google Colab)
1. Acesse o notebook
Abra nosso notebook Google Colab pronto para uso: Multimodal RAG com Elasticsearch.
Este caderno contém todo o código e explicações que você precisa acompanhar.
2. Clone o repositório
# Clone the repository with the multimodal RAG code
!git clone -b https://github.com/elastic/elasticsearch-labs.git
# Navigate to the project directory
cd elasticsearch-labs/supporting-blog-content/building-multimodal-rag-with-elasticsearch-gotham3. Instalar dependências
# Install PyTorch and related libraries
!pip install torch>=2.1.0 torchvision>=0.16.0 torchaudio>=2.1.0
# Install vision processing libraries
!pip install opencv-python-headless pillow numpy
# Install the specific ImageBind fork
!pip install git+https://github.com/hkchengrex/ImageBind.git
# Install Elasticsearch and environment management
!pip install elasticsearch python-dotenv4. Configurar credenciais
# Input your credentials securely
import getpass
ELASTICSEARCH_URL = input("Enter the Elasticsearch endpoint url: ")
ELASTICSEARCH_API_KEY = getpass.getpass("Enter the Elasticsearch API key: ")
OPENAI_API_KEY = getpass.getpass("Enter the OpenAI API key: ")
# Configure environment variables
import os
os.environ["ELASTICSEARCH_API_KEY"] = ELASTICSEARCH_API_KEY
os.environ["OPENAI_API_KEY"] = OPENAI_API_KEY
os.environ["ELASTICSEARCH_URL"] = ELASTICSEARCH_URLObservação: o modelo ImageBind (~2 GB) será baixado automaticamente na primeira execução.
Agora que tudo está pronto, vamos nos aprofundar nos detalhes e solucionar o crime!
Introdução: O crime em Gotham City
Em uma noite chuvosa em Gotham City, um crime chocante abala a cidade. O comissário Gordon precisa da sua ajuda para desvendar o mistério. As pistas estão espalhadas em diferentes formatos: imagens borradas, áudio misterioso, textos criptografados e até mapas de profundidade. Você está pronto para usar a tecnologia de IA mais avançada para resolver o caso?
Neste blog, você será guiado passo a passo pela construção de um sistema RAG (Recuperação-Geração Aumentada) multimodal que unifica diferentes tipos de dados (imagens, áudio, textos e mapas de profundidade) em um único espaço de busca. Usaremos o ImageBind para gerar embeddings multimodais, o Elasticsearch para armazenar e recuperar esses embeddings e um Large Language Model (LLM) para analisar as evidências e gerar um relatório final.
Fundamentos: Arquitetura RAG multimodal
O que é um RAG multimodal?
A ascensão da Geração Aumentada de Recuperação (RAG) Multimodal está revolucionando a maneira como interagimos com modelos de IA. Tradicionalmente, os sistemas RAG trabalham exclusivamente com texto, recuperando informações relevantes de bancos de dados antes de gerar respostas. No entanto, o mundo não se limita ao texto: imagens, vídeos e áudio também contêm conhecimento valioso. É por isso que as arquiteturas multimodais estão ganhando destaque, permitindo que os sistemas de IA combinem informações de diferentes formatos para respostas mais ricas e precisas.
Três abordagens principais para RAG multimodal
Para implementar um RAG multimodal, três estratégias são comumente usadas. Cada abordagem tem suas próprias vantagens e limitações, dependendo do caso de uso:
1. Espaço vetorial compartilhado
Dados de diferentes modalidades são mapeados em um espaço vetorial comum usando modelos multimodais como o ImageBind. Isso permite que consultas de texto recuperem imagens, vídeos e áudio sem conversão de formato explícita.
Vantagens:
Permite recuperação multimodal sem exigir conversão de formato explícita.
Fornece uma integração fluida entre diferentes modalidades, permitindo recuperação direta de texto, imagem, áudio e vídeo.
Escalável para diversos tipos de dados, o que o torna útil para aplicações de recuperação em larga escala.
Desvantagens:
O treinamento requer grandes conjuntos de dados multimodais, que podem nem sempre estar disponíveis.
O espaço de incorporação compartilhado pode introduzir deriva semântica, onde as relações entre modalidades não são perfeitamente preservadas.
O viés em modelos multimodais pode afetar a precisão da recuperação, dependendo da distribuição do conjunto de dados.
2. Modalidade de aterramento único
Todas as modalidades são convertidas em um único formato, geralmente texto, antes da recuperação. Por exemplo, as imagens são descritas por meio de legendas geradas automaticamente e o áudio é transcrito em texto.
Vantagens:
Simplifica a recuperação, pois tudo é convertido em uma representação de texto uniforme.
Funciona bem com mecanismos de busca baseados em texto existentes, eliminando a necessidade de infraestrutura multimodal especializada.
Pode melhorar a interpretabilidade, pois os resultados recuperados estão em um formato legível por humanos.
Desvantagens:
Perda de informações: certos detalhes (por exemplo, relações espaciais em imagens, tom em áudio) podem não ser totalmente capturados em descrições de texto.
Depende da qualidade da legenda/transcrição: erros em anotações automáticas podem reduzir a eficácia da recuperação.
Não é ideal para consultas puramente visuais ou auditivas, pois o processo de conversão pode remover contexto essencial.
3. Recuperação separada
Mantém modelos distintos para cada modalidade. O sistema realiza pesquisas separadas para cada tipo de dado e depois mescla os resultados.
Vantagens:
Permite otimização personalizada por modalidade, melhorando a precisão da recuperação para cada tipo de dado.
Menor dependência de modelos multimodais complexos, facilitando a integração de sistemas de recuperação existentes.
Fornece controle refinado sobre classificação e reclassificação, pois resultados de diferentes modalidades podem ser combinados dinamicamente.
Desvantagens:
Requer fusão de resultados, tornando o processo de recuperação e classificação mais complexo.
Pode gerar respostas inconsistentes se modalidades diferentes retornarem informações conflitantes.
Maior custo computacional , pois são realizadas buscas independentes para cada modalidade, aumentando o tempo de processamento.
Nossa escolha: Espaço vetorial compartilhado com ImageBind
Dentre essas abordagens, escolhemos o espaço vetorial compartilhado, uma estratégia que se alinha perfeitamente com a necessidade de buscas multimodais eficientes. Nossa implementação é baseada no ImageBind, um modelo capaz de representar múltiplas modalidades (texto, imagem, áudio e vídeo) em um espaço vetorial comum. Isso nos permite:
Realize pesquisas multimodais entre diferentes formatos de mídia sem precisar converter tudo em texto.
Use incorporações altamente expressivas para capturar relacionamentos entre diferentes modalidades.
Garanta escalabilidade e eficiência, armazenando embeddings otimizados para recuperação rápida no Elasticsearch.
Ao adotar essa abordagem, construímos um pipeline de pesquisa multimodal robusto, onde uma consulta de texto pode recuperar imagens ou áudio diretamente sem pré-processamento adicional. Este método expande aplicações práticas de busca inteligente em grandes repositórios para sistemas avançados de recomendação multimodal.
A figura a seguir ilustra o fluxo de dados dentro do pipeline RAG multimodal, destacando o processo de indexação, recuperação e geração de resposta com base em dados multimodais:

Como funciona o espaço de incorporação?
Tradicionalmente, as incorporações de texto vêm de modelos de linguagem (por exemplo, BERT, GPT). Agora, com modelos multimodais nativos como o ImageBind da Meta AI, temos uma estrutura que gera vetores para múltiplas modalidades:
Texto: Frases e parágrafos são transformados em vetores da mesma dimensão.
Imagens (visão): Os pixels são mapeados no mesmo espaço dimensional usado para texto.
Áudio: Os sinais sonoros são convertidos em incorporações comparáveis a imagens e texto.
Mapas de profundidade: os dados de profundidade são processados e também resultam em vetores.
Assim, qualquer pista (texto, imagem, áudio, profundidade) pode ser comparada a qualquer outra usando métricas de similaridade vetorial, como similaridade de cosseno. Se uma amostra de áudio de riso e uma imagem do rosto de um suspeito estiverem “próximas” neste espaço, podemos inferir alguma correlação (por exemplo, a mesma identidade).
Etapa 1 - Coletando pistas da cena do crime
Antes de analisar as evidências, precisamos coletá-las. O crime em Gotham deixou rastros que podem estar escondidos em imagens, áudios, textos e até mesmo dados de profundidade. Vamos organizar essas pistas para alimentá-las em nosso sistema.
O que temos?
O Comissário Gordon nos enviou os seguintes arquivos contendo evidências coletadas na cena do crime em quatro modalidades diferentes:
Descrição e modalidade da pista
a) Imagens (2 fotos)
crime_scene1.jpg, crime_scene2.jpg→ Fotos tiradas da cena do crime. Mostra vestígios suspeitos no chão.suspect_spotted.jpg→ Imagem de câmera de segurança mostrando uma silhueta fugindo do local.



b) Áudio (1 gravação)
joker_laugh.wav→ Um microfone perto da cena do crime captou uma risada sinistra.

c) Texto (1 mensagem)
Riddle.txt, note2.txt→ Algumas notas misteriosas foram encontradas no local, possivelmente deixadas pelo criminoso.

d) Profundidade (1 mapa de profundidade)
depth_suspect.png→ Uma câmera de segurança com sensor de profundidade capturou um suspeito em um beco próximo.jdancing-depth.png→ Uma câmera de segurança com sensor de profundidade capturou um suspeito descendo a estação de metrô.


Essas evidências estão em formatos diferentes e não podem ser analisadas diretamente da mesma maneira. Precisamos transformá-los em embeddings — vetores numéricos que permitirão comparação entre modais.
Organização de arquivos
Antes de iniciar o processamento, precisamos garantir que todas as pistas estejam organizadas corretamente no diretório data/ para que o pipeline funcione sem problemas.
Estrutura de diretório esperada:
data/
├── images/
│ ├── crime_scene1.jpg
│ ├── suspect_spotted.jpg
│ ...
├── audios/
│ ├── joker_laugh.wav
│ ...
├── texts/
│ ├── riddle.txt
│ ...
├── depths/
│ ├── depth_suspect.pngCódigo para verificar a organização da pista
Antes de prosseguir, vamos garantir que todos os arquivos necessários estejam no local correto.
import os
# Base directory for clues
data_dir = "data"
# List of expected files
evidences = {
"images": ["crime_scene1.jpg","crime_scene1.jpg", "joker_alley.jpg"],
"audios": ["joker_laugh.wav"],
"texts": ["riddle.txt", "note2.txt”],
"depths": ["depth_suspect.png", "jdancing-depth.png"]
}
# Create directories if they don't exist
for category, files in evidences.items():
category_path = os.path.join(data_dir, category)
os.makedirs(category_path, exist_ok=True)
for file in files:
file_path = os.path.join(category_path, file)
if not os.path.exists(file_path):
print(f"Warning: {file} not found in {category_path}.")
print("All files are correctly organized!")Executando o arquivo
python stages/01-stage/files_check.pySaída esperada (se todos os arquivos estiverem corretos):
All files are correctly organized!Saída esperada (se algum arquivo estiver faltando):
Warning: joker_laugh.wav not found in data/audios/
Warning: depth_suspect.png not found in data/depths/Este script ajuda a evitar erros antes de começarmos a gerar embeddings e indexá-los no Elasticsearch.
Etapa 2 - Organizando as evidências
Gerando embeddings com ImageBind
Para unificar as pistas, precisamos transformá-las em embeddings — representações vetoriais que capturam o significado de cada modalidade. Usaremos o ImageBind, um modelo da Meta AI que gera embeddings para diferentes tipos de dados (imagens, áudio, texto e mapas de profundidade) dentro de um espaço vetorial compartilhado.

Como o ImageBind funciona?
Para comparar diferentes tipos de evidências (imagens, áudio, texto e mapas de profundidade), precisamos transformá-los em vetores numéricos usando o ImageBind. Este modelo permite que qualquer tipo de entrada seja convertido no mesmo formato de incorporação, permitindo pesquisas entre modalidades.
Abaixo está um código otimizado (src/embedding_generator.py) para gerar embeddings para qualquer tipo de entrada usando os processadores apropriados para cada modalidade:
class EmbeddingGenerator:
"""Class for generating multimodal embeddings using ImageBind."""
def __init__(self):
self.device = "cuda" if torch.cuda.is_available() else "cpu"
self.model = self._load_model()
def _load_model(self):
"""Loads the ImageBind model and sets it to inference mode."""
model = imagebind_model.imagebind_huge(pretrained=True)
model.eval()
model.to(self.device)
return model
def generate_embedding(self, input_data, modality):
"""Generates embedding for different modalities"""
processors = {
"vision": lambda x: data.load_and_transform_vision_data(x, self.device),
"audio": lambda x: data.load_and_transform_audio_data(x, self.device),
"text": lambda x: data.load_and_transform_text(x, self.device),
"depth": self.process_depth
}
try:
# Input type verification
if not isinstance(input_data, list):
raise ValueError(f"Input data must be a list. Received: {type(input_data)}")
# Convert input data to a tensor format that the model can process
# For images: [batch_size, channels, height, width]
# For audio: [batch_size, channels, time]
# For text: [batch_size, sequence_length]
inputs = {modality: processors[modality](input_data)}
with torch.no_grad():
embedding = self.model(inputs)[modality]
return embedding.squeeze(0).cpu().numpy()
except Exception as e:
logger.error(f"Error generating {modality} embedding: {str(e)}", exc_info=True)
raiseUm tensor é uma estrutura de dados fundamental em aprendizado de máquina e aprendizado profundo, especialmente ao trabalhar com modelos como o ImageBind. No nosso contexto:
input_tensor = processors[modality]([input_data], self.device)Aqui, o tensor representa os dados de entrada (imagem, áudio ou texto) convertidos em um formato matemático que o modelo pode processar. Especificamente:
Para imagens: O tensor representa a imagem como uma matriz multidimensional de valores numéricos (pixels organizados por altura, largura e canais de cor).
Para áudio: O tensor representa ondas sonoras como uma sequência de amplitudes ao longo do tempo.
Para texto: O tensor representa palavras ou tokens como vetores numéricos.
Testando a geração de incorporação:
Vamos testar nossa geração de incorporação com o código a seguir. Salve-o em 02-stage/test_embedding_generation.py e execute-o com este comando:
python stages/02-stage/test_embedding_generation.pygenerator = EmbeddingGenerator()
image_embedding = generator.generate_embedding("data/images/crime_scene1.jpg","vision")
print(image_embedding.shape)Saída esperada:
(1024,)Agora, a imagem foi transformada em um vetor de 1024 dimensões.
Etapa 3 - Armazenamento e pesquisa no Elasticsearch
Agora que geramos os embeddings para as evidências, precisamos armazená-los em um banco de dados vetorial para permitir pesquisas eficientes. Para isso, usaremos o Elasticsearch, que suporta vetores densos (dense_vector) e permite buscas por similaridade.
Esta etapa consiste em dois processos principais:
Indexando os embeddings → Armazena os vetores gerados no Elasticsearch.
Pesquisa de similaridade → Recupera os registros mais semelhantes a uma nova evidência.
Indexando as evidências no Elasticsearch
Cada evidência processada pelo ImageBind (imagem, áudio, texto ou profundidade) é convertida em um vetor de 1024 dimensões. Precisamos armazenar esses vetores no Elasticsearch para permitir pesquisas futuras.
O código a seguir (src/elastic_manager.py) cria um índice no Elasticsearch e configura o mapeamento para armazenar os embeddings.
from elasticsearch import Elasticsearch, helpers
...
class ElasticsearchManager:
"""Manages multimodal operations in Elasticsearch"""
def __init__(self):
load_dotenv() # Load variables from .env
self.es = self._connect_elastic()
self.index_name = "multimodal_content"
self._setup_index()
def _connect_elastic(self):
"""Connects to Elasticsearch"""
return Elasticsearch(
os.getenv("ELASTICSEARCH_URL"), # Elasticsearch endpoint
api_key=os.getenv("ELASTICSEARCH_API_KEY")
)
def _setup_index(self):
"""Sets up the index if it doesn't exist"""
if not self.es.indices.exists(index=self.index_name):
mapping = {
"mappings": {
"properties": {
"embedding": {
"type": "dense_vector",
"dims": 1024,
"index": True,
"similarity": "cosine"
},
"modality": {"type": "keyword"},
"content": {"type": "binary"},
"description": {"type": "text"},
"metadata": {"type": "object"},
"content_path": {"type": "text"}
}
}
}
self.es.indices.create(index=self.index_name, body=mapping)
def index_content(self, embedding, modality, content=None, description="", metadata=None, content_path=None):
"""Indexes multimodal content"""
doc = {
"embedding": embedding.tolist(),
"modality": modality,
"description": description,
"metadata": metadata or {},
"content_path": content_path
}
if content:
doc["content"] = base64.b64encode(content).decode() if isinstance(content, bytes) else content
return self.es.index(index=self.index_name, document=doc)
def search_similar(self, query_embedding, modality=None, k=5):
"""Searches for similar contents"""
query = {
"knn": {
"field": "embedding",
"query_vector": query_embedding.tolist(),
"k": k,
"num_candidates": 100,
"filter": [{"term": {"modality": modality}}] if modality else []
}
}
try:
response = self.es.search(
index=self.index_name,
query=query,
size=k
)
# Return both source data and score for each hit
return [{
**hit["_source"],
"score": hit["_score"]
} for hit in response["hits"]["hits"]]
except Exception as e:
print(f"Error: processing search_evidence: {str(e)}")
return "Error generating search evidence"Executando a indexação
Agora, vamos indexar uma evidência para testar o processo.
# Example: Indexing an image from the crime scene
generator = EmbeddingGenerator()
es_manager = ElasticsearchManager(cloud_id="YOUR_CLOUD_ID", api_key="YOUR_API_KEY")
image_embedding = generator.generate_embedding("data/images/crime_scene1.jpg", "vision")
response = es_manager.index_content(
embedding=image_embedding,
modality="vision",
description="Photo of the crime scene with suspicious traces",
content_path="data/images/crime_scene1.jpg"
)
print(json.dumps(response, indent=2))Saída esperada no Elasticsearch (resumo do documento indexado):
{
"embedding": [0.12, -0.53, 0.89, ...],
"modality": "vision",
"description": "Photo of the crime scene with suspicious traces",
"content_path": "data/images/crime_scene1.jpg"
}Para indexar todas as evidências multimodais, execute o seguinte comando Python:
python stages/03-stage/index_all_modalities.pyAgora, as evidências são armazenadas no Elasticsearch e estão prontas para serem recuperadas quando necessário.
Verificando o processo de indexação
Depois de executar o script de indexação, vamos verificar se todas as nossas evidências foram armazenadas corretamente no Elasticsearch. Você pode usar as ferramentas de desenvolvimento do Kibana para executar algumas consultas de verificação:
1. Primeiro, verifique se o índice foi criado:
GET _cat/indices/multimodal_content?v2. Em seguida, verifique a contagem de documentos por modalidade:
GET multimodal_content/_search
{
"size": 0,
"aggs": {
"modalities": {
"terms": {
"field": "modality.keyword"
}
}
}
}3. Por fim, examine a estrutura do documento indexado:
GET multimodal_content/_search
{
"size": 1,
"query": {
"match_all": {}
}
}Resultados esperados:
Um índice chamado `multimodal_content` deve existir.
Cerca de 7 documentos distribuídos em diferentes modalidades (visão, áudio, texto, profundidade).
Cada documento deve conter: campos de incorporação, modalidade, descrição, metadados e content_path.
Esta etapa de verificação garante que nosso banco de dados de evidências esteja configurado corretamente antes de prosseguirmos com as pesquisas de similaridade.
Procurando evidências semelhantes no Elasticsearch
Agora que as evidências foram indexadas, podemos realizar pesquisas para encontrar os registros mais semelhantes a uma nova pista. Esta pesquisa usa similaridade vetorial para retornar os registros mais próximos no espaço de incorporação.
O código a seguir realiza essa pesquisa.
def search_similar_evidence(self, query_embedding, k=5, modality=None):
"""Performs a kNN search to find the most similar clues."""
knn_query = {
"field": "embedding",
"query_vector": query_embedding.tolist(),
"k": k,
"num_candidates": 100
}
query_body = {"knn": knn_query}
if modality:
query_body = {
"bool": {
"must": [
query_body,
{"term": {"modality": modality}}
]
}
}
try:
results = self.es.search(
index=self.index_name,
query=query_body,
_source_includes=["description", "modality", "content_path"],
size=k
)
except Exception as e:
print(f"Error processing search_evidence: {str(e)}")
return "Error generating search evidence”
return results["hits"]["hits"]Testando a pesquisa - Usando áudio como consulta para resultados multimodais
Agora, vamos testar a busca por evidências usando um arquivo de áudio suspeito. Precisamos gerar um embedding para o arquivo da mesma maneira e procurar por embeddings semelhantes:
python stages/03-stage/search_by_audio.py# Initialize classes
generator = EmbeddingGenerator()
es_manager = ElasticsearchManager(cloud_id="YOUR_CLOUD_ID", api_key="YOUR_API_KEY")
# Generate embedding for a suspicious audio
audio_embedding = generator.generate_embedding("data/audios/mysterious_laugh.wav", "audio")
# Search for similar evidence in Elasticsearch
similar_evidences = es_manager.search_similar_evidence(audio_embedding, k=3)
# Display the retrieved results
print("\n🔎 Similar evidence found:\n")
for i, evidence in enumerate(similar_evidences, start=1):
description = evidence['_source']['description']
modality = evidence['_source']['modality']
score = evidence['_score']
content_path = evidence['_source'].get('content_path', 'N/A')
print(f"{i}. {description} ({modality})")
print(f" Similarity: {score:.4f}")
print(f" File path: {content_path}\n")Saída esperada no terminal:
🔎 Similar evidence found:
1. A sinister laugh captured near the crime scene (audio)
Similarity: 0.9985
File path: data/audios/joker_laugh.wav
2. The Joker with green hair, white face paint, and a sinister smile in an urban night setting. (vision)
Similarity: 0.6068
File path: data/images/joker_laughing.png
3. Suspect dancing (vision)
Similarity: 0.5591
File path: data/images/jdancing.png
Agora, podemos analisar as evidências recuperadas e determinar sua relevância para o caso.
Além do áudio - Explorando pesquisas multimodais
Invertendo os papéis: Qualquer modalidade pode ser uma "pergunta"
Em nosso sistema RAG multimodal , cada modalidade é uma consulta de pesquisa potencial. Vamos além do exemplo de áudio e explorar como outros tipos de dados podem iniciar investigações.
1. Busca por texto (decifrando a nota do criminoso)
Cenário: Você encontrou uma mensagem de texto criptografada e quer encontrar evidências relacionadas.
python stages/03-stage/search_by_text.py# Generate embedding from text
text = "Why so serious?"
embedding_text = generator.generate_embedding([text], "text")
# Search for related evidence
similar_evidences = es_manager.search_similar(
query_embedding=embedding_text,
k=3
)Resultados esperados:
🔎 Similar evidence found:
1. Mysterious note found at the location (text)
Similarity: 0.7639
File path: data/texts/riddle.txt
2. The Joker with green hair, white face paint, and a sinister smile in an urban night setting. (vision)
Similarity: 0.7161
File path: data/images/joker_laughing.png
3. Why so serious (text)
Similarity: 0.7132
File path: data/texts/note2.txt2. Busca de imagens (rastreamento da cena suspeita do crime)
Cenário: Uma nova cena de crime (crime_scene2.jpg) precisa ser comparada com outras evidências.
python stages/03-stage/search_by_image.py# Generate embedding for a suspicious image
vision_embedding = generator.generate_embedding(["data/images/crime_scene2.jpg"], "vision")
# Search for similar evidence in Elasticsearch
similar_evidences = es_manager.search_similar(
query_embedding=vision_embedding,
k=3
)Saída:
🔎 Similar evidence found:
1. Photo of the crime scene: A dark, rain-soaked alley is filled with playing cards, while a sinister graffiti of the Joker laughing stands out on the brick wall. (vision)
Similarity: 0.8258
File path: data/images/crime_scene1.jpg
2. The Joker with green hair, white face paint, and a sinister smile in an urban night setting. (vision)
Similarity: 0.6897
File path: data/images/joker_laughing.png
3. Suspect dancing (vision)
Similarity: 0.6588
File path: data/images/jdancing.png3. Busca de mapa de profundidade (busca 3D)
Cenário: Um mapa de profundidade (jdancing-depth.png) revela padrões de escape de imagem.
python stages/03-stage/search_by_depth.py# Generate embedding for a suspicious depth map
vision_embedding = generator.generate_embedding(["data/depths/jdancing-depth.png"], "depth")
# Search for similar evidence in Elasticsearch
similar_evidences = es_manager.search_similar(
query_embedding=vision_embedding,
modality="vision",
k=3
)Saída
🔎 Similar evidence found:
1. The Joker with green hair, white face paint, and a sinister smile in an urban night setting. (vision)
Similarity: 0.5329
File path: data/images/joker_laughing.png
2. Photo of the crime scene: A dark, rain-soaked alley is filled with playing cards, while a sinister graffiti of the Joker laughing stands out on the brick wall. (vision)
Similarity: 0.5053
File path: data/images/crime_scene1.jpg
3. Suspect dancing (vision)
Similarity: 0.4859
File path: data/images/jdancing.png
Por que isso importa?
Cada modalidade revela conexões únicas:
Texto → Padrões linguísticos do suspeito.
Imagens → Reconhecimento de locais e objetos.
Profundidade → Reconstrução de cena 3D.
Agora, temos um banco de dados de evidências estruturado no Elasticsearch, o que nos permite armazenar e recuperar evidências multimodais de forma eficiente.
Resumo do que fizemos:
Embeddings multimodais armazenados no Elasticsearch.
Realizou pesquisas de similaridade, encontrando evidências relacionadas a novas pistas.
Testou a pesquisa usando um arquivo de áudio suspeito, garantindo que o sistema funcionasse corretamente.
Próximo passo: Usaremos um LLM (Large Language Model) para analisar as evidências recuperadas e gerar um relatório final.
Etapa 4 - Conectando os pontos com o LLM
Agora que as evidências foram indexadas no Elasticsearch e podem ser recuperadas por similaridade, precisamos de um LLM (Large Language Model) para analisá- las e gerar um relatório final para enviar ao Comissário Gordon. O LLM será responsável por identificar padrões, conectar pistas e sugerir um possível suspeito com base nas evidências recuperadas.
Para esta tarefa, usaremos o GPT-4 Turbo, formulando um prompt detalhado para que o modelo possa interpretar os resultados de forma eficiente.
Integração LLM
Para integrar o LLM em nosso sistema, criamos a classe LLMAnalyzer (src/llm_analyzer.py), que recebe as evidências recuperadas do Elasticsearch e gera um relatório forense usando essas evidências como contexto de prompt.
import os
from openai import OpenAI
import logging
from dotenv import load_dotenv
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class LLMAnalyzer:
"""Evidence analyzer using GPT-4"""
def __init__(self):
load_dotenv()
self.client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
def analyze_evidence(self, evidence_results):
"""
Analyzes multimodal search results and generates a report
Args:
evidence_results: Dict with results by modality
{
'vision': [...],
'audio': [...],
'text': [...],
'depth': [...]
}
"""
# Format evidence for the prompt
evidence_summary = self._format_evidence(evidence_results)
# final prompt
prompt = f"""
You are a highly experienced forensic detective specializing in multimodal evidence analysis. Your task is to analyze the collected evidence (audio, images, text, depth maps) and conclusively determine the **prime suspect** responsible for the Gotham Central Bank case.
---
### **Collected Evidence:**
{evidence_summary}
### **Task:**
1. **Analyze all the evidence** and identify cross-modal connections.
2. **Determine the exact identity of the criminal** based on behavioral patterns, visual/auditory/textual clues, and symbolic markers.
3. **Justify your conclusion** by explaining why this suspect is definitively responsible.
4. **Assign a confidence score (0-100%)** to your conclusion.
---
### **Final Output Format (Strictly Follow This Format):**
- **Prime Suspect:** [Full Name or Alias]
- **Evidence Supporting Conclusion:** [Detailed breakdown of visual, auditory, textual, and behavioral evidence]
- **Behavioral Patterns:** [Key actions, motives, and criminal signature]
- **Confidence Level:** [0-100%]
- **Next Steps (if any):** [What additional evidence would further confirm the identity? If none, state "No further evidence required."]
If there is **insufficient evidence**, specify exactly what is missing and suggest what additional data would be needed for a conclusive identification.
This report must be **direct and definitive**--avoid speculation and provide a final, actionable determination of the suspect's identity.
"""
try:
response = self.client.chat.completions.create(
model="gpt-4-turbo-preview",
messages=[
{
"role": "system",
"content": "You are a forensic detective specialized in multimodal evidence analysis."
},
{"role": "user", "content": prompt_01}
],
temperature=0.5,
max_tokens=1000
)
report = response.choices[0].message.content
logger.info("\n📋 Forensic Report Generated:")
logger.info("=" * 50)
logger.info(report)
logger.info("=" * 50)
return report
except Exception as e:
logger.error(f"Error generating report: {str(e)}")
return NoneConfiguração de temperatura na análise LLM:
Para nosso sistema de análise forense, usamos uma temperatura moderada de 0,5. Este cenário equilibrado foi escolhido porque:
Representa um meio termo entre saídas determinísticas (muito rígidas) e altamente aleatórias;
Em 0,5, o modelo mantém estrutura suficiente para fornecer conclusões forenses lógicas e justificáveis;
Essa configuração permite que o modelo identifique padrões e faça conexões, permanecendo dentro de parâmetros razoáveis de análise forense;
Ele equilibra a necessidade de resultados consistentes e confiáveis com a capacidade de gerar análises criteriosas.
Essa configuração de temperatura moderada ajuda a garantir que nossa análise forense seja confiável e criteriosa, evitando conclusões excessivamente rígidas e especulativas.
Executando a análise de evidências
Agora que temos a integração do LLM, precisamos de um script que conecte todos os componentes do sistema. Este script irá:
Pesquise evidências semelhantes no Elasticsearch.
Analise as evidências recuperadas usando o LLM para gerar um relatório final.
Código: Script de análise de evidências
python stages/04-stage/rag_crime_analyze.pyimport sys
import os
sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'src'))
from embedding_generator import EmbeddingGenerator
from elastic_manager import ElasticsearchManager
from llm_analyzer import LLMAnalyzer
import json
import logging
from dotenv import load_dotenv
# Setup logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Load environment variables
load_dotenv()
# Initialize classes
generator = EmbeddingGenerator()
es_manager = ElasticsearchManager()
llm = LLMAnalyzer()
logger.info("✅ All components initialized successfully")
try:
evidence_data = {}
# Get data for each modality
test_files = {
'vision': 'data/images/crime_scene2.jpg',
'audio': 'data/audios/joker_laugh.wav',
'text': 'Why so serious?',
'depth': 'data/depths/jdancing-depth.png'
}
logger.info("🔍 Collecting evidence...")
for modality, test_input in test_files.items():
try:
if modality == 'text':
embedding = generator.generate_embedding([test_input], modality)
else:
embedding = generator.generate_embedding([str(test_input)], modality)
results = es_manager.search_similar(embedding, k=2)
if results:
evidence_data[modality] = results
logger.info(f"✅ Data retrieved for {modality}: {len(results)} results")
else:
logger.warning(f"⚠️ No results found for {modality}")
except Exception as e:
logger.error(f"❌ Error retrieving {modality} data: {str(e)}")
if not evidence_data:
raise ValueError("No evidence data found in Elasticsearch!")
# Test forensic report generation
logger.info("\n📝 Generating forensic report...")
report = llm.analyze_evidence(evidence_data)
if report:
logger.info("✅ Forensic report generated successfully")
logger.info("\n📊 Report Preview:")
logger.info("+" * 50)
logger.info(report)
logger.info("+" * 50)
else:
raise ValueError("Failed to generate forensic report")
except Exception as e:
logger.error(f"❌ Error in analysis : {str(e)}")Saída esperada do LLM
**Prime Suspect:** The Joker
**Evidence Supporting Conclusion:**
- **Visual Evidence:**
- The photo of the crime scene with playing cards scattered around and the graffiti of the Joker laughing matches the Joker's known calling cards and thematic elements. The similarity score of 0.83 indicates a high likelihood that these elements are directly associated with the Joker.
- The image of the Joker with green hair, white face paint, and a sinister smile in an urban night setting, although with a lower similarity score of 0.69, still supports the presence or recent activity of the Joker in areas consistent with the crime scene's characteristics.
- **Auditory Evidence:**
- The captured sinister laugh with a similarity score of 1.00 perfectly matches known audio profiles of the Joker, making it a direct auditory signature of his presence at or near the crime scene.
- Despite the lower similarity score of 0.61, the second audio piece further corroborates the Joker's involvement through thematic consistency.
- **Textual Evidence:**
- The mysterious note found at the location, with a similarity score of 0.76, likely contains thematic or direct references to the Joker's modus operandi or signature phrases, further implicating him in the crime.
- The similarity score of 0.72 for the Joker's description in textual evidence reinforces the thematic connection to the crime scene.
- **Depth Evidence:**
- Depth sensor capture of the suspect with a similarity score of 0.77 suggests a physical presence matching the Joker's known dimensions or characteristic movements.
- The lower similarity score of 0.53 in the second depth evidence still contributes to the overall pattern of evidence pointing towards the Joker, albeit with less certainty.
**Behavioral Patterns:**
- The Joker is known for his theatrical crimes, often leaving behind a signature trail of chaos, including playing cards, sinister laughter, and thematic graffiti. These elements are not only consistent with his known criminal signature but also directly observed at the crime scene.
- His motives often include creating chaos, drawing attention to his acts, and challenging his arch-nemesis, Batman, making a high-profile bank heist fitting within his behavioral patterns.
**Confidence Level:** 95%
**Next Steps:** No further evidence required.
The combination of visual, auditory, textual, and depth evidence strongly points to the Joker as the prime suspect. The thematic consistency across multiple modes of evidence, combined with known behavioral patterns and criminal signature, leaves little doubt regarding his involvement. While there is always a small margin of uncertainty in forensic analysis, the evidence at hand provides a compelling case against the Joker with a high degree of confidence.
Conclusão: Caso resolvido
Com todas as pistas reunidas e analisadas, o sistema Multimodal RAG identificou um suspeito: o Coringa.
Ao combinar imagens, áudio, texto e mapas de profundidade em um espaço vetorial compartilhado usando o ImageBind, o sistema foi capaz de detectar conexões que seriam impossíveis de identificar manualmente. O Elasticsearch garantiu buscas rápidas e eficientes, enquanto o LLM sintetizou as evidências em um relatório claro e conclusivo.
No entanto, o verdadeiro poder deste sistema vai além de Gotham City. A arquitetura Multimodal RAG abre portas para inúmeras aplicações do mundo real:
Vigilância urbana: Identificação de suspeitos com base em imagens, áudio e dados de sensores.
Análise forense: Correlacionando evidências de múltiplas fontes para solucionar crimes complexos.
Recomendação multimídia: Criação de sistemas de recomendação que entendam contextos multimodais (por exemplo, sugerir músicas com base em imagens ou texto).
Tendências de mídia social: detectando tópicos de tendência em diferentes formatos de dados.
Agora que você aprendeu a construir um sistema RAG multimodal, por que não testá-lo com suas próprias dicas?
Compartilhe suas descobertas conosco e ajude a comunidade a avançar no campo da IA multimodal!
Agradecimentos especiais
Gostaria de agradecer a Adrian Cole por sua valiosa contribuição e revisão durante o processo de definição da arquitetura de implantação deste código.
Referências
Perguntas frequentes
O que é um RAG multimodal?
O RAG multimodal é um método que permite que sistemas de IA combinem informações de diferentes formatos (como imagens, vídeos e áudio) para obter respostas mais ricas e precisas.
Como implementar RAG multimodal?
Para implementar um RAG multimodal, três estratégias são comumente usadas: espaço vetorial compartilhado, modalidade de aterramento único e recuperação separada




