Blog

Aufbau eines multimodalen RAG-Systems mit Elasticsearch: Die Geschichte von Gotham City

Erfahren Sie, wie Sie ein multimodales Retrieval-Augmented Generation (RAG)-System erstellen, das Text-, Audio-, Video- und Bilddaten integriert, um eine umfassendere, kontextualisierte Informationsbeschaffung zu ermöglichen.

In diesem Blog erfahren Sie, wie Sie mit Elasticsearch eine multimodale RAG-Pipeline (Retrieval-Augmented Generation) erstellen. Wir werden untersuchen, wie Sie ImageBind nutzen können, um Einbettungen für verschiedene Datentypen zu generieren, darunter Text, Bilder, Audio und Tiefenkarten. Sie erfahren auch, wie Sie diese Einbettungen mithilfe von dense_vector und k-NN-Suche effizient in Elasticsearch speichern und abrufen können. Abschließend integrieren wir ein großes Sprachmodell (LLM), um die abgerufenen Beweise zu analysieren und einen umfassenden Abschlussbericht zu erstellen.

Wie funktioniert die multimodale RAG-Pipeline?

  1. Hinweise sammeln → Bilder, Audio, Texte und Tiefenkarten vom Tatort in Gotham.

  2. Einbettungen generieren → Jede Datei wird mithilfe des multimodalen Modells ImageBind in einen Vektor konvertiert.

  3. Indizierung in Elasticsearch → Die Vektoren werden für einen effizienten Abruf gespeichert.

  4. Suche nach Ähnlichkeit → Bei einem neuen Hinweis werden die ähnlichsten Vektoren abgerufen.

  5. Das LLM analysiert die Beweise → Ein GPT-4-Modell synthetisiert die Antwort und identifiziert den Verdächtigen!

Eingesetzte Technologien

  • ImageBind → Generiert einheitliche Einbettungen für verschiedene Modalitäten.

  • Elasticsearch → Ermöglicht eine schnelle und effiziente Vektorsuche.

  • LLM (GPT-4, OpenAI) → Analysiert die Beweise und erstellt einen Abschlussbericht.

Für wen ist dieser Blog?

  • Elastic-Benutzer, die an der multimodalen Vektorsuche interessiert sind.

  • Entwickler, die multimodales RAG in der Praxis verstehen möchten.

  • Jeder, der nach skalierbaren Lösungen zur Analyse von Daten aus mehreren Quellen sucht.

Voraussetzungen für multimodale RAG: Aufbau der Umgebung

Um das Verbrechen in Gotham City aufzuklären, müssen Sie Ihre Technologieumgebung einrichten. Folgen Sie dieser Schritt-für-Schritt-Anleitung:

1. Technische Voraussetzungen

Komponente

Spezifikation

Betriebssystem

Linux, macOS oder Windows

Python

3.10 oder höher

RAM

Mindestens 8 GB (16 GB empfohlen)

Grafikkarte

Optional, aber für ImageBind empfohlen

2. Einrichten des Projekts

Alle Ermittlungsmaterialien sind auf GitHub verfügbar und wir werden für dieses interaktive Erlebnis zur Aufklärung von Verbrechen Jupyter Notebook (Google Colab) verwenden. Befolgen Sie diese Schritte, um zu beginnen:

Einrichten mit Jupyter Notebook (Google Colab)

1. Zugriff auf das Notebook

  • Öffnen Sie unser gebrauchsfertiges Google Colab-Notizbuch: Multimodales RAG mit Elasticsearch.

  • Dieses Notizbuch enthält den gesamten Code und alle Erklärungen, die Sie zum Mitmachen benötigen.

2. Klonen Sie das Repository

# 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. Abhängigkeiten installieren

 # 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. Anmeldeinformationen konfigurieren

# 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

Hinweis: Das ImageBind-Modell (~2 GB) wird beim ersten Ausführen automatisch heruntergeladen.

Nachdem nun alles eingerichtet ist, können wir in die Details eintauchen und das Verbrechen aufklären!

Einleitung: Die Kriminalität in Gotham City

In einer regnerischen Nacht erschüttert ein schockierendes Verbrechen die Stadt Gotham City. Commissioner Gordon braucht Ihre Hilfe, um das Geheimnis zu lüften. Hinweise sind über verschiedene Formate verstreut: verschwommene Bilder, mysteriöse Audiodaten, verschlüsselte Texte und sogar Tiefenkarten. Sind Sie bereit, die fortschrittlichste KI-Technologie zur Lösung des Falls einzusetzen?

In diesem Blog werden Sie Schritt für Schritt durch den Aufbau eines multimodalen RAG-Systems (Retrieval-Augmented Generation) geführt, das verschiedene Datentypen (Bilder, Audio, Texte und Tiefenkarten) in einem einzigen Suchraum vereint. Wir werden ImageBind verwenden, um multimodale Einbettungen zu generieren, Elasticsearch , um diese Einbettungen zu speichern und abzurufen, und ein Large Language Model (LLM), um die Beweise zu analysieren und einen Abschlussbericht zu erstellen.

Grundlagen: Multimodale RAG-Architektur

Was ist ein multimodaler RAG?

Der Aufstieg von Retrieval-Augmented Generation (RAG) Multimodal revolutioniert die Art und Weise, wie wir mit KI-Modellen interagieren. Traditionell arbeiten RAG-Systeme ausschließlich mit Text und rufen relevante Informationen aus Datenbanken ab, bevor sie Antworten generieren. Die Welt beschränkt sich jedoch nicht nur auf Text– auch Bilder, Videos und Audiodateien enthalten wertvolles Wissen. Aus diesem Grund gewinnen multimodale Architekturen an Bedeutung, die es KI-Systemen ermöglichen, Informationen aus verschiedenen Formaten zu kombinieren, um umfassendere und präzisere Antworten zu erhalten.

Drei Hauptansätze für multimodale RAG

Zur Implementierung eines multimodalen RAG werden üblicherweise drei Strategien verwendet. Jeder Ansatz hat seine eigenen Vorteile und Einschränkungen, abhängig vom Anwendungsfall:

1. Gemeinsamer Vektorraum

Daten aus verschiedenen Modalitäten werden mithilfe multimodaler Modelle wie ImageBind in einen gemeinsamen Vektorraum abgebildet. Dadurch können Textabfragen Bilder, Videos und Audiodateien ohne explizite Formatkonvertierung abrufen.

Vorteile:

  • Ermöglicht modalübergreifenden Abruf ohne explizite Formatkonvertierung.

  • Bietet eine reibungslose Integration zwischen verschiedenen Modalitäten und ermöglicht den direkten Abruf von Text, Bild, Audio und Video.

  • Skalierbar für verschiedene Datentypen, daher nützlich für groß angelegte Abrufanwendungen.

Nachteile:

  • Für das Training sind große multimodale Datensätze erforderlich, die möglicherweise nicht immer verfügbar sind.

  • Der gemeinsame Einbettungsraum kann zu einer semantischen Drift führen, bei der die Beziehungen zwischen Modalitäten nicht perfekt erhalten bleiben.

  • Je nach Datensatzverteilung kann eine Verzerrung in multimodalen Modellen die Abrufgenauigkeit beeinträchtigen.

2. Einzelne geerdete Modalität

Alle Modalitäten werden vor dem Abruf in ein einziges Format, normalerweise Text, konvertiert. Beispielsweise werden Bilder durch automatisch generierte Bildunterschriften beschrieben und Audiodaten in Text transkribiert.

Vorteile:

  • Vereinfacht das Auffinden, da alles in eine einheitliche Textdarstellung umgewandelt wird.

  • Funktioniert gut mit vorhandenen textbasierten Suchmaschinen, sodass keine spezielle multimodale Infrastruktur erforderlich ist.

  • Kann die Interpretierbarkeit verbessern, da die abgerufenen Ergebnisse in einem für Menschen lesbaren Format vorliegen.

Nachteile:

  • Informationsverlust: Bestimmte Details (z. B. räumliche Beziehungen in Bildern, Ton in Audio) werden in Textbeschreibungen möglicherweise nicht vollständig erfasst.

  • Abhängig von der Untertitel-/Transkriptionsqualität: Fehler in automatischen Anmerkungen können die Abrufeffektivität verringern.

  • Nicht optimal für rein visuelle oder akustische Abfragen, da beim Konvertierungsprozess möglicherweise wichtiger Kontext entfernt wird.

3. Getrennte Abholung

Behält für jede Modalität unterschiedliche Modelle bei. Das System führt für jeden Datentyp eine separate Suche durch und führt die Ergebnisse später zusammen.

Vorteile:

  • Ermöglicht eine benutzerdefinierte Optimierung pro Modalität und verbessert so die Abrufgenauigkeit für jeden Datentyp.

  • Geringere Abhängigkeit von komplexen multimodalen Modellen, wodurch die Integration vorhandener Abrufsysteme erleichtert wird.

  • Bietet eine detaillierte Kontrolle über die Rangfolge und Neubewertung, da Ergebnisse aus verschiedenen Modalitäten dynamisch kombiniert werden können.

Nachteile:

  • Erfordert eine Zusammenführung der Ergebnisse, wodurch der Abruf- und Rankingprozess komplexer wird.

  • Kann zu inkonsistenten Antworten führen, wenn unterschiedliche Modalitäten widersprüchliche Informationen zurückgeben.

  • Höherer Rechenaufwand , da für jede Modalität unabhängige Suchvorgänge durchgeführt werden, was die Verarbeitungszeit verlängert.

Unsere Wahl: Gemeinsamer Vektorraum mit ImageBind

Unter diesen Ansätzen haben wir uns für den Shared Vector Space entschieden, eine Strategie, die perfekt auf die Anforderungen effizienter multimodaler Suchen abgestimmt ist. Unsere Implementierung basiert auf ImageBind, einem Modell, das mehrere Modalitäten (Text, Bild, Audio und Video) in einem gemeinsamen Vektorraum darstellen kann. Dies ermöglicht uns:

  • Führen Sie modalübergreifende Suchen zwischen verschiedenen Medienformaten durch, ohne alles in Text konvertieren zu müssen.

  • Verwenden Sie ausdrucksstarke Einbettungen , um Beziehungen zwischen verschiedenen Modalitäten zu erfassen.

  • Sorgen Sie für Skalierbarkeit und Effizienz, indem Sie optimierte Einbettungen für einen schnellen Abruf in Elasticsearch speichern.

Durch die Übernahme dieses Ansatzes haben wir eine robuste multimodale Suchpipeline erstellt, bei der eine Textabfrage ohne zusätzliche Vorverarbeitung direkt Bilder oder Audio abrufen kann. Diese Methode erweitert die praktischen Anwendungen von der intelligenten Suche in großen Repositorien bis hin zu fortschrittlichen multimodalen Empfehlungssystemen.

Die folgende Abbildung veranschaulicht den Datenfluss innerhalb der multimodalen RAG-Pipeline und hebt den Indexierungs-, Abruf- und Antwortgenerierungsprozess basierend auf multimodalen Daten hervor:

Multimodaler Rag-Datenfluss

Wie funktioniert der Einbettungsraum?

Traditionell stammen Texteinbettungen aus Sprachmodellen (z. B. BERT, GPT). Mit nativen multimodalen Modellen wie ImageBind von Meta AI verfügen wir nun über ein Grundgerüst, das Vektoren für mehrere Modalitäten generiert:

  • Text: Sätze und Absätze werden in Vektoren derselben Dimension umgewandelt.

  • Bilder (Sehen): Pixel werden in denselben dimensionalen Raum abgebildet, der für Text verwendet wird.

  • Audio: Tonsignale werden in Einbettungen umgewandelt, die mit Bildern und Text vergleichbar sind.

  • Tiefenkarten: Tiefendaten werden verarbeitet und führen ebenfalls zu Vektoren.

Somit kann jeder Hinweis (Text, Bild, Audio, Tiefe) mithilfe von Vektorähnlichkeitsmetriken wie der Kosinusähnlichkeit mit jedem anderen verglichen werden. Wenn eine lachende Audioprobe und ein Bild des Gesichts eines Verdächtigen in diesem Raum „nahe beieinander“ liegen, können wir auf eine gewisse Korrelation schließen (z. B. dieselbe Identität).

Phase 1 – Hinweise am Tatort sammeln

Bevor wir die Beweise analysieren, müssen wir sie sammeln. Das Verbrechen in Gotham hat Spuren hinterlassen, die in Bildern, Audiodateien, Texten und sogar Tiefendaten verborgen sein könnten. Lassen Sie uns diese Hinweise organisieren, um sie in unser System einzuspeisen.

Was haben wir?

Commissioner Gordon hat uns die folgenden Akten mit Beweismaterial geschickt, das in vier verschiedenen Modalitäten am Tatort gesammelt wurde:

Streckenbeschreibung und Modalität

a) Bilder (2 Fotos)

  • crime_scene1.jpg, crime_scene2.jpg → Fotos vom Tatort. Zeigt verdächtige Spuren auf dem Boden.

  • suspect_spotted.jpg → Bild einer Überwachungskamera, das eine Silhouette zeigt, die vom Tatort wegläuft.

b) Audio (1 Aufnahme)

  • joker_laugh.wav → Ein Mikrofon in der Nähe des Tatorts fing ein unheimliches Lachen ein.

c) Text (1 Nachricht)

  • Riddle.txt, note2.txt → Am Tatort wurden einige mysteriöse Notizen gefunden, die möglicherweise vom Verbrecher hinterlassen wurden.

d) Tiefe (1 Tiefenkarte)

  • depth_suspect.png → Eine Überwachungskamera mit Tiefensensor hat einen Verdächtigen in einer nahegelegenen Gasse erfasst.

  • jdancing-depth.png → Eine Überwachungskamera mit Tiefensensor hat einen Verdächtigen aufgenommen, der die U-Bahn-Station entlangging.

Diese Beweisstücke liegen in unterschiedlichen Formaten vor und können nicht direkt auf die gleiche Weise analysiert werden. Wir müssen sie in Einbettungen umwandeln – numerische Vektoren, die einen kreuzmodalen Vergleich ermöglichen.

Dateiorganisation

Bevor wir mit der Verarbeitung beginnen, müssen wir sicherstellen, dass alle Hinweise im Datenverzeichnis ordnungsgemäß organisiert sind, damit die Pipeline reibungslos läuft.

Erwartete Verzeichnisstruktur:

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

Code zur Überprüfung der Hinweisorganisation

Bevor wir fortfahren, stellen wir sicher, dass sich alle erforderlichen Dateien am richtigen Speicherort befinden.

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

Ausführen der Datei

python  stages/01-stage/files_check.py

Erwartete Ausgabe (wenn alle Dateien korrekt sind):

All files are correctly organized!

Erwartete Ausgabe (falls eine Datei fehlt):

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

Dieses Skript hilft, Fehler zu vermeiden, bevor wir mit der Generierung von Einbettungen und deren Indizierung in Elasticsearch beginnen.

Phase 2 – Organisieren der Beweise

Einbettungen mit ImageBind generieren

Um die Hinweise zu vereinheitlichen, müssen wir sie in Einbettungen umwandeln – Vektordarstellungen, die die Bedeutung jeder Modalität erfassen. Wir verwenden ImageBind, ein Modell von Meta AI, das Einbettungen für verschiedene Datentypen (Bilder, Audio, Text und Tiefenkarten) innerhalb eines gemeinsamen Vektorraums generiert.

Einbettungen mit ImageBind generieren

Wie funktioniert ImageBind?

Um verschiedene Arten von Beweisen (Bilder, Audio, Text und Tiefenkarten) zu vergleichen, müssen wir sie mithilfe von ImageBind in numerische Vektoren umwandeln. Dieses Modell ermöglicht die Konvertierung aller Eingabetypen in dasselbe Einbettungsformat und ermöglicht so modalitätsübergreifende Suchen .

Nachfolgend finden Sie einen optimierten Code (src/embedding_generator.py), um Einbettungen für alle Eingabetypen unter Verwendung der entsprechenden Prozessoren für jede Modalität zu generieren:

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

Ein Tensor ist eine grundlegende Datenstruktur im maschinellen Lernen und Deep Learning, insbesondere bei der Arbeit mit Modellen wie ImageBind. In unserem Kontext:

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

Hier stellt der Tensor die Eingabedaten (Bild, Audio oder Text) dar, die in ein mathematisches Format konvertiert werden, das das Modell verarbeiten kann. Speziell:

  • Für Bilder: Der Tensor stellt das Bild als mehrdimensionale Matrix numerischer Werte dar (Pixel, organisiert nach Höhe, Breite und Farbkanälen).

  • Für Audio: Der Tensor stellt Schallwellen als eine Abfolge von Amplituden über die Zeit dar.

  • Für Text: Der Tensor stellt Wörter oder Token als numerische Vektoren dar.

Testen der Einbettungsgenerierung:

Testen wir unsere Einbettungsgenerierung mit dem folgenden Code. Speichern Sie es in 02-stage/test_embedding_generation.py und führen Sie es mit diesem Befehl aus:

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)

Erwartete Ausgabe:

(1024,)

Nun wurde das Bild in einen 1024-dimensionalen Vektor umgewandelt.

Phase 3 – Speicherung und Suche in Elasticsearch

Nachdem wir nun die Einbettungen für die Beweise generiert haben, müssen wir sie in einer Vektordatenbank speichern, um effiziente Suchvorgänge zu ermöglichen. Hierzu verwenden wir Elasticsearch, das dichte Vektoren (dense_vector) unterstützt und Ähnlichkeitssuchen ermöglicht.

Dieser Schritt besteht aus zwei Hauptprozessen:

  • Indizierung der Einbettungen → Speichert die generierten Vektoren in Elasticsearch.

  • Ähnlichkeitssuche → Ruft die ähnlichsten Datensätze zu einem neuen Beweisstück ab.

Indizierung der Beweise in Elasticsearch

Jedes von ImageBind verarbeitete Beweisstück (Bild, Audio, Text oder Tiefe) wird in einen 1024-dimensionalen Vektor umgewandelt. Wir müssen diese Vektoren in Elasticsearch speichern, um zukünftige Suchen zu ermöglichen.

Der folgende Code (src/elastic_manager.py) erstellt einen Index in Elasticsearch und konfiguriert die Zuordnung zum Speichern der Einbettungen.

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"

Ausführen der Indizierung

Lassen Sie uns nun ein Beweisstück indizieren, um den Prozess zu testen.

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

Erwartete Ausgabe in Elasticsearch (Zusammenfassung des indizierten Dokuments):

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

Um alle multimodalen Beweise zu indizieren, führen Sie bitte den folgenden Python-Befehl aus:

python stages/03-stage/index_all_modalities.py

Jetzt werden die Beweise in Elasticsearch gespeichert und können bei Bedarf abgerufen werden.

Überprüfen des Indizierungsprozesses

Nachdem wir das Indexierungsskript ausgeführt haben, überprüfen wir, ob alle unsere Beweise korrekt in Elasticsearch gespeichert wurden. Sie können die Dev Tools von Kibana verwenden, um einige Überprüfungsabfragen auszuführen:

1. Überprüfen Sie zunächst, ob der Index erstellt wurde:

GET _cat/indices/multimodal_content?v

2. Überprüfen Sie dann die Anzahl der Dokumente pro Modalität:

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

3. Untersuchen Sie abschließend die indizierte Dokumentstruktur:

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

Erwartete Ergebnisse:

  • Ein Index mit dem Namen „multimodal_content“ sollte vorhanden sein.

  • Etwa 7 Dokumente, verteilt auf verschiedene Modalitäten (Bild, Audio, Text, Tiefe).

  • Jedes Dokument sollte die Felder „Einbettung“, „Modalität“, „Beschreibung“, „Metadaten“ und „content_path“ enthalten.

Dieser Überprüfungsschritt stellt sicher, dass unsere Beweisdatenbank ordnungsgemäß eingerichtet ist, bevor wir mit der Ähnlichkeitssuche fortfahren.

Suche nach ähnlichen Beweisen in Elasticsearch

Nachdem die Beweise nun indiziert wurden, können wir Suchen durchführen, um die Datensätze zu finden, die einem neuen Hinweis am ähnlichsten sind. Diese Suche verwendet Vektorähnlichkeit , um die ähnlichsten Datensätze im Einbettungsraum zurückzugeben.

Der folgende Code führt diese Suche durch.

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

Testen der Suche – Verwenden von Audio als Abfrage für multimodale Ergebnisse

Testen wir nun die Beweissuche anhand einer verdächtigen Audiodatei. Wir müssen auf die gleiche Weise eine Einbettung für die Datei generieren und nach ähnlichen Einbettungen suchen:

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

Erwartete Ausgabe im 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

Jetzt können wir die gefundenen Beweise analysieren und ihre Relevanz für den Fall bestimmen.

Mehr als Audio – Multimodale Suchen erkunden

Rollentausch: Jede Modalität kann eine „Frage“ sein

In unserem multimodalen RAG- System ist jede Modalität eine potenzielle Suchanfrage. Lassen Sie uns über das Audiobeispiel hinausgehen und untersuchen, wie andere Datentypen Untersuchungen einleiten können.

1. Suche nach Text (Entzifferung der Notiz des Täters)

Szenario: Sie haben eine verschlüsselte Textnachricht gefunden und möchten entsprechende Beweise finden.

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
)

Erwartete Ergebnisse:

🔎 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. Bildersuche (Verfolgung des verdächtigen Tatorts)

Szenario: Ein neuer Tatort (crime_scene2.jpg) muss mit anderen Beweisen verglichen werden.

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
)

Ausgabe:

🔎 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. Tiefenkartensuche (3D-Verfolgung)

Szenario: Eine Tiefenkarte (jdancing-depth.png) deckt Bildfluchtmuster auf.

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
)

Ausgabe

🔎 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

Warum ist das wichtig?

Jede Modalität weist einzigartige Verbindungen auf:

  • Text → Sprachliche Muster des Verdächtigen.

  • Bilder → Erkennung von Orten und Objekten.

  • Tiefe → 3D- Szenenrekonstruktion.

Jetzt verfügen wir in Elasticsearch über eine strukturierte Beweisdatenbank, die es uns ermöglicht,multimodale Beweise effizient zu speichern und abzurufen.

Zusammenfassung unserer Aktivitäten:

  • Gespeicherte multimodale Einbettungen in Elasticsearch.

  • Führte Ähnlichkeitssuchen durch und fand Beweise im Zusammenhang mit neuen Hinweisen.

  • Die Suche wurde mit einer verdächtigen Audiodatei getestet, um sicherzustellen, dass das System ordnungsgemäß funktioniert.

Nächster Schritt: Wir werden ein LLM (Large Language Model) verwenden, um die abgerufenen Beweise zu analysieren und einen Abschlussbericht zu erstellen.

Stufe 4 – Die Punkte mit dem LLM verbinden

Nachdem die Beweise nun in Elasticsearch indiziert wurden und anhand der Ähnlichkeit abgerufen werden können, benötigen wir ein LLM (Large Language Model), um sie zu analysieren und einen Abschlussbericht zu erstellen, den wir an Kommissar Gordon senden können. Der LLM ist dafür verantwortlich , Muster zu erkennen, Hinweise zu verknüpfen und auf Grundlage der gefundenen Beweise einen möglichen Verdächtigen vorzuschlagen .

Für diese Aufgabe verwenden wir GPT-4 Turbo und formulieren eine detaillierte Eingabeaufforderung , damit das Modell die Ergebnisse effizient interpretieren kann.

LLM-Integration

Um das LLM in unser System zu integrieren, haben wir die LLMAnalyzer- Klasse (src/llm_analyzer.py) erstellt, die die abgerufenen Beweise von Elasticsearch empfängt und einen forensischen Bericht erstellt, der diese Beweise als Eingabekontext verwendet.

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

Temperatureinstellung in der LLM-Analyse:

Für unser forensisches Analysesystem verwenden wir eine moderate Temperatur von 0,5. Diese ausgewogene Einstellung wurde gewählt, weil:

  • Es stellt einen Mittelweg zwischen deterministischen (zu starren) und stark zufälligen Ergebnissen dar;

  • Bei 0,5 behält das Modell genügend Struktur bei, um logische und vertretbare forensische Schlussfolgerungen zu liefern;

  • Diese Einstellung ermöglicht es dem Modell, Muster zu erkennen und Verbindungen herzustellen, während es gleichzeitig innerhalb angemessener forensischer Analyseparameter bleibt.

  • Es gleicht den Bedarf an konsistenten, zuverlässigen Ergebnissen mit der Fähigkeit aus, aufschlussreiche Analysen zu erstellen.

Diese moderate Temperatureinstellung trägt dazu bei, dass unsere forensische Analyse sowohl zuverlässig als auch aufschlussreich ist und sowohl zu starre als auch zu spekulative Schlussfolgerungen vermieden werden.

Ausführen der Beweisanalyse

Nachdem wir nun über die LLM-Integration verfügen, benötigen wir ein Skript , das alle Systemkomponenten verbindet. Dieses Skript wird:

  • Suchen Sie in Elasticsearch nach ähnlichen Beweisen .

  • Analysieren Sie die abgerufenen Beweise mithilfe des LLM , um einen Abschlussbericht zu erstellen.

Code: Skript zur Beweisanalyse

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

Erwartete LLM-Ausgabe

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

Fazit: Fall gelöst

Nachdem alle Hinweise gesammelt und analysiert wurden, hat das multimodale RAG-System einen Verdächtigen identifiziert: den Joker.

Durch die Kombination von Bildern, Audio, Text und Tiefenkarten in einem gemeinsamen Vektorraum mithilfe von ImageBind konnte das System Verbindungen erkennen , die manuell nicht hätten identifiziert werden können. Elasticsearch sorgte für schnelle und effiziente Suchvorgänge, während LLM die Beweise in einem klaren und schlüssigen Bericht zusammenfasste.

Die wahre Macht dieses Systems geht jedoch über Gotham City hinaus. Die multimodale RAG-Architektur öffnet Türen zu zahlreichen realen Anwendungen:

  • Urbane Überwachung: Identifizierung von Verdächtigen anhand von Bild-, Audio- und Sensordaten.

  • Forensische Analyse: Korrelieren von Beweisen aus mehreren Quellen zur Aufklärung komplexer Verbrechen.

  • Multimedia-Empfehlung: Erstellen von Empfehlungssystemen , die multimodale Kontexte verstehen (z. B. Musikvorschläge basierend auf Bildern oder Text).

  • Social-Media-Trends: Erkennen von Trendthemen in verschiedenen Datenformaten.

Nachdem Sie nun gelernt haben, wie man ein multimodales RAG-System erstellt, warum testen Sie es nicht mit Ihren eigenen Hinweisen?

Teilen Sie Ihre Entdeckungen mit uns und helfen Sie der Community , im Bereich der multimodalen KI voranzukommen!

Besonderer Dank

Ich möchte Adrian Cole für seinen wertvollen Beitrag und seine Überprüfung während des Prozesses der Definition der Bereitstellungsarchitektur dieses Codes danken.

Referenzen

Häufige Fragen

Was ist ein multimodaler RAG?

Multimodales RAG ist eine Methode, die es KI-Systemen ermöglicht, Informationen aus verschiedenen Formaten (wie Bildern, Videos und Audio) zu kombinieren, um umfassendere und präzisere Antworten zu erhalten.

Wie lässt sich multimodales RAG implementieren?

Zur Implementierung eines multimodalen RAG werden üblicherweise drei Strategien verwendet: gemeinsamer Vektorraum, einzelne geerdete Modalität und separater Abruf

Zugehörige Inhalte

Fortgeschrittene RAG-Techniken Teil 2: Abfragen und Testen

Han Xiang Choong

Entitätsauflösung mit Elasticsearch, Teil 4: Die ultimative Herausforderung

Jessica Moszkowicz

Automatisierung des Log-Parsing in Streams mit ML

Nastia Havriushenko

Entwicklung eines KI-Agenten für die Personalabteilung mit Elastic Agent Builder und GPT-OSS

Tomás Murúa

Erweiterte RAG-Techniken Teil 1: Datenverarbeitung

Han Xiang Choong

Sind Sie bereit, hochmoderne Sucherlebnisse zu schaffen?

Eine ausreichend fortgeschrittene Suche kann nicht durch die Bemühungen einer einzelnen Person erreicht werden. Elasticsearch wird von Datenwissenschaftlern, ML-Ops-Experten, Ingenieuren und vielen anderen unterstützt, die genauso leidenschaftlich an der Suche interessiert sind wie Sie. Lasst uns in Kontakt treten und zusammenarbeiten, um das magische Sucherlebnis zu schaffen, das Ihnen die gewünschten Ergebnisse liefert.

Probieren Sie es selbst aus