博客

利用 Elasticsearch 构建多模式 RAG 系统:高谭市的故事

了解如何构建多模态检索增强生成(RAG)系统,该系统可整合文本、音频、视频和图像数据,以提供更丰富的上下文化信息检索。

在本博客中,您将学习如何使用 Elasticsearch 构建多模态 RAG(检索-增强生成)管道。我们将探讨如何利用 ImageBind 为文本、图像、音频和深度图等各种数据类型生成嵌入。您还将了解如何使用 dense_vector 和 k-NN 搜索在 Elasticsearch 中高效地存储和检索这些嵌入。最后,我们将整合一个大型语言模型(LLM)来分析检索到的证据,并生成一份全面的最终报告。

多式联运 RAG 管道是如何运作的?

  1. 收集线索→ 哥谭市犯罪现场的图像、音频、文本和深度图。

  2. 生成嵌入→使用 ImageBind 多模态模型将每个文件转换为矢量。

  3. 在 Elasticsearch 中建立索引→ 存储向量以便高效检索。

  4. 通过相似性检索→ 给出一条新线索,检索最相似的向量。

  5. LLM 分析证据→ GPT-4 模型综合反应并确定嫌疑人!

使用的技术

  • ImageBind→ 为各种模式生成统一的嵌入。

  • Elasticsearch→ 实现快速高效的矢量搜索。

  • LLM (GPT-4, OpenAI)→ 分析证据并生成最终报告。

这个博客为谁而设?

  • 对多模式向量搜索感兴趣的弹性用户。

  • 希望在实践中了解多模式 RAG 的开发人员。

  • 任何人都在寻找可扩展的解决方案来分析来自多个来源的数据。

多模式 RAG 的先决条件:设置环境

要在哥谭市破案,您需要建立自己的技术环境。请按照本指南逐步操作:

1.技术要求

组件

规格

系统操作系统

Linux、macOS 或 Windows

Python

3.10 或更高版本

内存

最低 8GB(建议 16GB)。

GPU

可选,但建议用于 ImageBind

2.设立项目

所有调查材料均可在 GitHub 上获取,我们将使用 Jupyter Notebook(Google Colab)进行此次互动式犯罪侦破体验。请按照以下步骤开始操作:

使用 Jupyter Notebook(Google Colab)进行设置

1.访问笔记本

2.克隆版本库

# 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-gotham

3.安装依赖项

 # 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-dotenv

4.配置证书

# 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_URL

注意:第一次运行时将自动下载 ImageBind 模型(~2GB)。

现在,一切准备就绪,让我们深入细节,破案吧!

简介高谭市的犯罪

在哥谭市的一个雨夜,一起令人震惊的犯罪事件震惊了整个城市。戈登局长需要你的帮助来揭开谜底。线索分散在不同的格式中:模糊的图像、神秘的音频、加密的文本,甚至深度图。您准备好使用最先进的人工智能技术破案了吗?

在本博客中,我们将逐步引导您建立一个多模态 RAG(检索-增强生成)系统,将不同类型的数据(图像、音频、文本和深度图)统一到一个搜索空间中。我们将使用ImageBind生成多模态嵌入,使用Elasticsearch存储和检索这些嵌入,并使用大型语言模型 (LLM)分析证据和生成最终报告。

基础知识:多模式 RAG 架构

什么是多模式 RAG?

检索-增强一代(RAG)多模态的兴起正在彻底改变我们与人工智能模型的交互方式。传统上,RAG 系统只处理文本,先从数据库中检索相关信息,然后再生成回复。然而,世界并不局限于文字,图像、视频和音频也蕴含着宝贵的知识。这也是多模态架构日益突出的原因,它允许人工智能系统将来自不同格式的信息结合起来,从而做出更丰富、更精确的反应

多模式 RAG 的三种主要方法

要实施多模式 RAG,通常采用三种策略。每种方法都有自己的优势和局限性,具体取决于使用情况:

1.共享向量空间

利用 ImageBind 等多模态模型将不同模态的数据映射到一个共同的向量空间。这样,文本查询就可以检索图像、视频和音频,而无需进行明确的格式转换。

优势

  • 实现跨模式检索,无需明确的格式转换。

  • 提供不同模式之间的流畅整合,允许直接检索文本、图像、音频和视频。

  • 可对各种数据类型进行扩展,因此适用于大规模检索应用

缺点

  • 训练需要大型多模态数据集,而这些数据集并不总是可用的。

  • 共享的嵌入空间可能会带来语义漂移,即模态之间的关系没有得到完美的保留。

  • 多模态模型的偏差会影响检索的准确性,这取决于数据集的分布情况。

2.单一接地方式

在检索之前,所有模式都会转换为单一格式,通常是文本格式。例如,通过自动生成的标题对图像进行描述,并将音频转录为文本。

优势

  • 由于所有内容都转换为 统一的文本表示 ,因此 简化了检索

  • 现有的文本搜索引擎配合良好,无需专门的多模式基础设施。

  • 由于检索结果采用人类可读格式,因此可提高可解释性

缺点

  • 信息丢失:文本描述可能无法完全捕捉某些细节(如图像中的空间关系、音频中的音调)。

  • 取决于字幕/转录质量:自动注释中的错误会降低检索效率。

  • 对于纯视觉或听觉查询来说并不是最佳选择,因为转换过程可能会删除重要的上下文。

3.单独检索

为每种模式维护不同的模型。系统对每种数据类型分别进行搜索,然后合并 搜索 结果

优势

  • 允许对每种模式进行自定义优化,提高每类数据的检索准确性。

  • 减少对复杂的多模态模型的依赖,从而更容易整合现有的检索系统。

  • 由于可以动态组合不同模式的结果,因此可以对排序和重新排序进行精细控制

缺点

  • 需要对结果进行融合,使检索和排序过程更加复杂。

  • 如果不同模式返回的信息相互冲突,可能会产生不一致的响应

  • 计算成本较高,因为要对每种模式进行独立搜索,增加了处理时间。

我们的选择:使用 ImageBind 共享矢量空间

在这些方法中,我们选择了共享矢量空间,这一策略完全符合高效多模式搜索的需求。我们的实现基于 ImageBind ,它是一种能够在 通用矢量空间 中表示多种模式 (文本、图像、音频和视频 )的模型。这使我们能够

  • 在不同媒体格式之间进行跨模式搜索,无需将所有内容转换为文本。

  • 使用表现力极强的嵌入式技术来捕捉不同模态之间的关系。

  • 确保可扩展性和效率,存储优化嵌入,以便在 Elasticsearch 中快速检索。

通过采用这种方法,我们建立了一个强大的多模态搜索管道,文本查询可以直接检索图像或音频,而无需额外的预处理。这种方法的实际应用范围从大型资料库的智能搜索扩展到先进的多模式推荐系统

下图说明了多模态 RAG 管道中的数据流,突出显示了基于多模态数据的索引、检索和响应生成过程:

多模式抹布数据流

嵌入空间如何运作?

传统上,文本嵌入来自语言模型(如 BERT、GPT)。现在,有了 Meta AI 的ImageBind 等本地多模态模型,我们就有了为多种模态生成向量的骨干:

  • 文本:将句子和段落转换为相同维度的向量。

  • 图像(视觉):像素被映射到与文本相同的维度空间中。

  • 音频声音信号被转换成与图像和文本类似的嵌入信息。

  • 深度地图:深度数据经过处理后也会形成矢量。

因此,任何线索(文本、图像、音频、深度)都可以通过余弦相似度等矢量相似度量进行比较。如果笑声音频样本嫌疑人面部图像在这个空间中 "接近",我们就可以推断出某种相关性(例如,相同的身份)。

第 1 阶段--收集犯罪现场线索

在分析证据之前,我们需要收集证据。高谭市的犯罪留下的痕迹可能隐藏在图像、音频、文本甚至深度数据中。让我们把这些线索组织起来,输入我们的系统。

我们有什么?

戈登局长给我们发来了以下文件,其中包含从犯罪现场收集到的四种不同模式的证据:

轨道说明和模式

a) 图片(2 张照片)

  • crime_scene1.jpg, crime_scene2.jpg → 在犯罪现场拍摄的照片。显示地面上的可疑痕迹。

  • suspect_spotted.jpg → 监控录像显示一个人影逃离现场。

b) 音频(1 次录音)

  • joker_laugh.wav → 犯罪现场附近的麦克风捕捉到了一个阴险的笑声。

c) 文本(1 条信息)

  • Riddle.txt, note2.txt → 在现场发现了一些神秘的纸条,可能是罪犯留下的。

d) 深度(1 幅深度图)

  • depth_suspect.png → 带有深度传感器的监控摄像头捕捉到附近巷子里的一名嫌疑人。

  • jdancing-depth.png → 带有深度感应器的监控摄像头捕捉到一名嫌疑人从地铁站走下来。

这些证据的格式不同,无法以相同的方式直接进行分析。我们需要将它们转化为嵌入式数字向量,以便进行跨模态比较。

文件组织

在开始处理之前,我们需要确保所有线索都在 data/ 目录中组织妥当,以便管道顺利运行。

预期目录结构:

data/
├── images/
│   ├── crime_scene1.jpg
│   ├── suspect_spotted.jpg
│   ...
├── audios/
│   ├── joker_laugh.wav
│   ...
├── texts/
│   ├── riddle.txt
│   ... 
├── depths/
│   ├── depth_suspect.png

验证线索组织的代码

在继续之前,让我们确保所有所需文件都在正确的位置。

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!")

运行文件

python  stages/01-stage/files_check.py

预期输出(如果所有文件都正确):

All files are correctly organized!

预期输出(如果缺少任何文件):

Warning: joker_laugh.wav not found in data/audios/
Warning: depth_suspect.png not found in data/depths/

在我们开始生成嵌入并将其索引到 Elasticsearch 之前,该脚本有助于防止错误的发生。

第 2 阶段--整理证据

使用 ImageBind 生成嵌入

为了统一这些线索,我们需要将它们转化为嵌入式向量表征,以捕捉每种模态的含义。我们将使用ImageBind,它是 Meta AI 的一个模型,可在共享向量空间内生成不同数据类型(图像、音频、文本和深度图)的嵌入。

使用 ImageBind 生成嵌入

ImageBind 如何工作?

要比较不同类型的证据(图像、音频、文本和深度图),我们需要使用ImageBind 将它们转换成数字向量。这种模式允许将任何类型的输入转换为相同的嵌入格式,从而实现不同模态之间的模态搜索。

以下是经过优化的代码 (src/embedding_generator.py) ,可使用适合各种模态的处理器为任何类型的输入生成嵌入:

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)
            raise

张量是机器学习和深度学习中的一种基本数据结构,尤其是在使用 ImageBind 等模型时。就我们而言

input_tensor = processors[modality]([input_data], self.device)

在这里,张量表示将输入数据(图像、音频或文本)转换成模型可以处理的数学格式。具体来说

  • 对于图像:张量以数值的多维矩阵(按高度、宽度和颜色通道排列的像素)来表示图像。

  • 音频张量将声波表示为随时间变化的振幅序列。

  • 对于文本:张量将单词或标记表示为数字向量。

测试嵌入生成:

让我们用下面的代码测试一下嵌入生成。将其保存在 02-stage/test_embedding_generation.py 中,并使用此命令执行:

python stages/02-stage/test_embedding_generation.py
generator = EmbeddingGenerator()
image_embedding = generator.generate_embedding("data/images/crime_scene1.jpg","vision")

print(image_embedding.shape)

预期输出:

(1024,)

现在,图像已转换成1024 维向量

第 3 阶段 - Elasticsearch 中的存储和搜索

既然我们已经生成了证据的嵌入,就需要将它们存储到向量数据库中,以便进行高效搜索。为此,我们将使用Elasticsearch,它支持密集向量 (dense_vector) 并允许进行相似性搜索。

这一步骤包括两个主要过程:

  • 索引嵌入→ 将生成的向量存储在 Elasticsearch 中。

  • 相似性搜索→ 检索与新证据最相似的记录。

在 Elasticsearch 中索引证据

ImageBind处理的每个证据(图像、音频、文本或深度)都会转换成1024 维向量。我们需要在Elasticsearch中存储这些向量,以便将来进行搜索。

下面的代码 (src/elastic_manager.py) 在 Elasticsearch 中创建了一个索引,并配置了映射以存储嵌入。

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"

运行索引

现在,让我们用一个证据来检验这个过程。

# 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))

Elasticsearch 中的预期输出(索引文档的摘要):

{
    "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"  
}

要为所有多模式证据编制索引,请执行以下 Python 命令:

python stages/03-stage/index_all_modalities.py

现在,证据存储在Elasticsearch中,需要时可随时检索。

验证索引编制过程

运行索引脚本后,让我们验证一下 Elasticsearch 中是否正确存储了所有证据。您可以使用Kibana 的开发工具运行一些验证查询:

1.首先,检查是否创建了索引:

GET _cat/indices/multimodal_content?v

2.然后,验证每种模式的文件数量:

GET multimodal_content/_search
{
  "size": 0,
  "aggs": {
    "modalities": {
      "terms": {
        "field": "modality.keyword"
      }
    }
  }
}

3.最后,检查索引文档结构:

GET multimodal_content/_search
{
  "size": 1,
  "query": {
    "match_all": {}
  }
}

预期成果:

  • 应存在名为 `multimodal_content` 的索引。

  • 约 7 份文件分布于不同的模式(视觉、音频、文本、深度)。

  • 每个文档都应包含:嵌入、模式、描述、元数据和内容路径字段。

这一验证步骤可确保我们在进行相似性搜索之前正确建立证据数据库。

在 Elasticsearch 中搜索类似证据

既然证据已经编入索引,我们就可以进行搜索,找出与新线索最相似的记录。这种搜索使用向量相似性来返回嵌入空间中最接近的记录。

以下代码将执行此搜索。

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"]

测试搜索 - 使用音频作为多模态结果的查询方式

现在,让我们用一个可疑的音频文件来测试一下证据搜索功能。我们需要以同样的方式生成文件的嵌入,并搜索类似的嵌入:

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")

终端中的预期输出:

🔎 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

现在,我们可以对检索到的证据进行分析,确定其与案件的相关性。

音频之外--探索多模态搜索

颠倒角色:任何方式都可以是"问题"

在我们的多模态 RAG系统中,每一种模态都是一个潜在的搜索查询。除了音频示例,让我们来探讨一下其他数据类型如何启动调查

1.文本搜索(破译罪犯的纸条)

情景:您发现了一条加密短信,希望找到相关证据。

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
)

预期成果:

🔎 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.txt

2.图像搜索(追踪可疑犯罪现场)

情景:一个新的犯罪现场(crime_scene2.jpg) 需要与其他证据进行比较。

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
)

输出:

🔎 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.png

3.深度图搜索(三维追踪)

场景: 深度图(jdancing-depth.png) 揭示了图像 逃逸模式

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
)

输出

🔎 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

这有什么关系?

每种方式都能揭示独特的联系

  • 文本→ 嫌疑犯的语言模式。

  • 图像→ 识别地点和物体。

  • 深度→ 3D 场景重建。

现在,我们在 Elasticsearch 中拥有了一个 结构化的证据数据库 ,使我们能够 高效地存储和检索多模态证据

工作总结

  • 在 Elasticsearch 中存储多模态嵌入

  • 进行相似性搜索,找到与新线索相关的证据。

  • 使用可疑音频文件测试搜索,确保系统正常运行。

下一步:我们将使用LLM(大型语言模型)分析检索到的证据,并生成最终报告

第 4 阶段--与法律硕士联系起来

现在,证据已经被索引到 Elasticsearch中,并可以通过相似性进行检索,我们需要一个LLM(大型语言模型) 其进行分析,并生成最终报告发送给戈登局长。法学硕士将负责根据检索到的证据确定模式、串联线索并提出可能的嫌疑人

对于这项任务,我们将使用GPT-4 Turbo,制定详细的提示,以便模型能够有效地解释结果。

LLM 整合

为了将 LLM 集成到我们的系统中,我们创建了 LLMAnalyzersrc/llm_analyzer.py (),该类从 Elasticsearch 中接收 检索到的证据 ,并以这些证据为提示语境生成 法证报告

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 None

LLM 分析中的温度设置

对于我们的取证分析系统,我们使用 0.5 的适度温度。选择这种平衡设置的原因是

  • 它是确定性(过于死板)和高度随机输出之间的中间地带;

  • 在 0.5 时,模型保持足够的结构,以提供合乎逻辑和合理的法医结论;

  • 这种设置使模型能够识别模式并建立联系,同时保持在合理的法证分析参数范围内;

  • 它既能提供一致、可靠的输出结果,又能进行深入分析。

这种适度的温度设置有助于确保我们的法证分析既可靠又有洞察力,避免得出过于僵化和过于臆测的结论。

进行证据分析

既然我们已经集成了LLM,就需要一个脚本来连接所有系统组件。该脚本将

  • 在Elasticsearch中搜索类似证据

  • 使用 LLM 分析检索到的证据 ,生成 最终报告。

代码证据分析脚本

python stages/04-stage/rag_crime_analyze.py
import 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)}")

预期学习成果

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

结论案件侦破

通过所有线索的收集和分析,多模式 RAG 系统锁定了一名嫌疑人:小丑

通过使用 ImageBind 图像、音频、文本和深度图 整合到一个 共享的矢量空间 ,该系统能够 检测出 人工无法识别的 连接 。Elasticsearch确保了快速高效的搜索,而LLM则将证据综合成了一份清晰的结论性报告

然而,该系统的真正威力 远不止于高谭市多模式 RAG 架构众多实际应用打开了大门:

  • 城市监控:根据图像、音频和传感器数据识别嫌疑人。

  • 法医分析:多个来源的证据相互关联,以解决复杂的犯罪问题

  • 多媒体推荐: 创建能够理解 多模态语境的 推荐系统 (例如,根据图像或文本推荐 音乐 )。

  • 社交媒体趋势:通过不同的数据格式检测流行话题

既然您已经学会了如何构建多模态 RAG 系统,为什么不用自己的线索来测试它呢

与我们分享您的发现,帮助社区多模态人工智能领域取得进步!

特别感谢

我要感谢 Adrian Cole 在定义本代码的部署架构过程中所做的宝贵贡献和审查。

参考资料

常见问题

什么是多模式 RAG?

多模态 RAG 是一种允许人工智能系统将来自不同格式(如图像、视频和音频)的信息结合起来,以获得更丰富、更精确响应的方法。

如何实施多模式 RAG?

为实现多模态 RAG,通常采用三种策略:共享向量空间、单一基础模态和单独检索

相关内容

高级 RAG 技术第 2 部分:查询和测试

Han Xiang Choong

使用 Elasticsearch 解决实体问题,第 4 部分:终极挑战

Jessica Moszkowicz

在 Streams 中利用机器学习自动化日志解析

Nastia Havriushenko

利用弹性代理生成器和 GPT-OSS 构建人力资源人工智能代理

Tomás Murúa

高级 RAG 技术第 1 部分:数据处理

Han Xiang Choong

准备好打造最先进的搜索体验了吗?

足够先进的搜索不是一个人的努力就能实现的。Elasticsearch 由数据科学家、ML 操作员、工程师以及更多和您一样对搜索充满热情的人提供支持。让我们联系起来,共同打造神奇的搜索体验,让您获得想要的结果。

亲自试用