# Multi-Modal RAG Architecture Patterns

**Last updated:** 2026-02
**Status:** GA / Preview (multimodal search)
**Category:** Multi-Modal AI

---

## Introduksjon

Multi-Modal RAG (Retrieval-Augmented Generation) utvidar tradisjonell tekstbasert RAG til å inkludere bilete, diagram, tabellar og video som datakjelder. I staden for å berre søke i tekstdokument, kan ein multi-modal RAG-pipeline hente relevante bilete, analysere diagram og bruke visuell informasjon saman med tekst for å generere presise og grunnlagda svar.

Azure AI Search introduserte multimodal search som preview i mai 2025, noko som gir native støtte for å indeksere, forstå og hente dokument som inneheld både tekst og bilete. Dette eliminerer behovet for separate system for tekst- og bildeprosessering og reduserer kompleksiteten i RAG-arkitekturen vesentleg.

For norsk offentleg sektor er multi-modal RAG særleg verdifull for scenario som analyse av offentlege dokument med innebygde diagram og tabellar, byggesøknadar med teikningar, vegdokumentasjon med kartutsnitt, og forskingsrapportar med visualiseringar. Informasjon som tidlegare berre var tilgjengeleg som visuelt innhald i PDF-filer kan no søkast i og brukast som grunnlag for AI-assisterte avgjerder.

---

## Multi-Modal Embedding-modellar

### Oversikt over embedding-tilnærmingar

Azure AI Search tilbyr to komplementære tilnærmingar for multimodal embedding:

| Tilnærming | Korleis det fungerer | Beste for | Krav |
|-----------|---------------------|----------|------|
| **Image Verbalization + Text Embeddings** | LLM beskriv bilete i naturleg språk, deretter tekst-embedding | Diagram, flowcharts, forklaringsrikt innhald | LLM + embedding-modell |
| **Direct Multimodal Embeddings** | Enkelt embedding-modell for tekst og bilete i same vektorrom | Visuell likheit, produktbilete, foto | Multimodal embedding-modell |
| **Kombinert** | Begge tilnærmingane saman | Enterprise-løysingar med variert innhald | LLM + multimodal embedding |

### Image Verbalization Pipeline

GenAI Prompt-ferdigheita i Azure AI Search brukar ein LLM under indeksering for å lage natururlege tekstbeskrivelsar av kvart ekstrahert bilete:

```
Ekstrahert bilete → GenAI Prompt Skill → "Five-step HR workflow
                                          starting with manager
                                          approval"
                                              ↓
                                    Text Embedding Skill
                                              ↓
                                    Vektorrepresentasjon
```

**Fordelar:**
- Tolkbar — beskriving kan siterast direkte
- Semantisk djupne — forstår relasjonar i diagram
- Fungerer med standard tekst-embedding-modellar

**Ulemper:**
- LLM-kall per bilete aukar indekseringstid og kostnad
- Kvaliteten avheng av LLM sin evne til å tolke biletet

### Direct Multimodal Embeddings

| Modell | Leverandør | Dimensjonar | Brukstilfelle |
|--------|-----------|------------|--------------|
| **CLIP** | OpenAI | 512 / 768 | Generell tekst-bilete matching |
| **text-embedding-3-large** | Azure OpenAI | 3 072 | Tekst embeddings (etter verbalization) |
| **Azure Vision multimodal** | Azure AI Vision | 1 024 | Direkte bilete + tekst embeddings |
| **Foundry Models (AML)** | Microsoft Foundry | Varierer | Tilpassa multimodale modellar |

```python
# Azure AI Search: Multimodal embedding med Azure Vision
skill_definition = {
    "@odata.type": "#Microsoft.Skills.Vision.VectorizeSkill",
    "name": "multimodal-embedding",
    "description": "Embed bilete og tekst i same vektorrom",
    "context": "/document/pages/*",
    "modelVersion": "2023-04-15",
    "inputs": [
        {"name": "image", "source": "/document/pages/*/normalized_images/*"},
        {"name": "text", "source": "/document/pages/*/content"}
    ],
    "outputs": [
        {"name": "vector", "targetName": "contentVector"}
    ]
}
```

### Kombinert tilnærming for enterprise

Den mest robuste arkitekturen kombinerer begge metodane:

```
Dokument
  ├── Tekst → Text Split Skill → Text Embedding → tekst_vektor
  ├── Diagram/Charts → GenAI Prompt (verbalize) → Text Embedding → diagram_vektor
  └── Foto/Screenshots → Direct Multimodal Embedding → bilete_vektor
```

---

## Chunking-strategiar for bilete og video

### Dokumentekstraksjon

Azure AI Search tilbyr tre innebygde ferdigheiter for innhaldsekstraksjon:

| Ferdigheit | Tekstlokasjon | Biletelokasjon | Tabellar | Cross-page | Format |
|-----------|--------------|---------------|---------|-----------|--------|
| **Document Extraction** | Nei | Ja (PDF) | Nei | Nei | PDF |
| **Document Layout** | Ja (side, polygon) | Ja (side, polygon) | Nei | Nei | PDF, DOCX, XLSX, PPTX |
| **Content Understanding** | Ja (side, polygon) | Ja (side, polygon) | Ja (cross-page) | Ja | PDF, DOCX, XLSX, PPTX |

### Tekst-chunking

Text Split-ferdigheita delar tekst i handterbare delar for embedding:

```json
{
  "@odata.type": "#Microsoft.Skills.Text.SplitSkill",
  "name": "text-splitter",
  "textSplitMode": "pages",
  "maximumPageLength": 2000,
  "pageOverlapLength": 500,
  "maximumPagesToTake": 0,
  "inputs": [
    {"name": "text", "source": "/document/content"}
  ],
  "outputs": [
    {"name": "textItems", "targetName": "chunks"}
  ]
}
```

### Bilete-chunking-strategiar

| Strategi | Implementering | Brukstilfelle |
|----------|---------------|--------------|
| **Side-basert** | Eitt bilete per dokumentside | PDF-analyse med diagram |
| **Objekt-basert** | Utsnitt rundt detekterte objekt | Tekniske teikningar |
| **Grid-basert** | Fast rutenett over stort bilete | Kart, satellittbilete |
| **Semantisk** | Basert på visuell innhaldsanalyse | Blanda dokument |

### Video-chunking

For videoinnhald kombiner Azure Video Indexer med embedding:

```
Video → Video Indexer
  ├── Keyframes → Bilete-embedding
  ├── Scener → Scene-beskriving → Tekst-embedding
  ├── Transkripsjon → Tekst-chunking → Tekst-embedding
  └── OCR-tekst → Tekst-embedding
```

| Chunking-nivå | Granularitet | Token-kostnad | Brukstilfelle |
|--------------|-------------|---------------|--------------|
| **Per keyframe** | Finkornet | Høg | Detaljert visuell søk |
| **Per scene** | Medium | Medium | Narrativ forståing |
| **Per segment (5 min)** | Grovkornet | Låg | Overordna innhaldssøk |

---

## Vector Store Design for Mixed Media

### Azure AI Search indeksdesign

```json
{
  "name": "multimodal-index",
  "fields": [
    {"name": "id", "type": "Edm.String", "key": true},
    {"name": "parent_id", "type": "Edm.String", "filterable": true},
    {"name": "content_type", "type": "Edm.String", "filterable": true, "facetable": true},
    {"name": "text_content", "type": "Edm.String", "searchable": true},
    {"name": "image_description", "type": "Edm.String", "searchable": true},
    {"name": "page_number", "type": "Edm.Int32", "filterable": true, "sortable": true},
    {"name": "source_file", "type": "Edm.String", "filterable": true},
    {
      "name": "text_vector",
      "type": "Collection(Edm.Single)",
      "dimensions": 3072,
      "vectorSearchProfile": "text-profile",
      "searchable": true
    },
    {
      "name": "image_vector",
      "type": "Collection(Edm.Single)",
      "dimensions": 1024,
      "vectorSearchProfile": "image-profile",
      "searchable": true
    },
    {"name": "image_url", "type": "Edm.String"},
    {"name": "bounding_region", "type": "Edm.String"}
  ],
  "vectorSearch": {
    "algorithms": [
      {
        "name": "hnsw-config",
        "kind": "hnsw",
        "hnswParameters": {
          "m": 4,
          "efConstruction": 400,
          "efSearch": 500,
          "metric": "cosine"
        }
      }
    ],
    "profiles": [
      {"name": "text-profile", "algorithm": "hnsw-config", "vectorizer": "text-vectorizer"},
      {"name": "image-profile", "algorithm": "hnsw-config", "vectorizer": "image-vectorizer"}
    ],
    "vectorizers": [
      {
        "name": "text-vectorizer",
        "kind": "azureOpenAI",
        "azureOpenAIParameters": {
          "resourceUri": "https://<resource>.openai.azure.com",
          "deploymentId": "text-embedding-3-large",
          "modelName": "text-embedding-3-large"
        }
      },
      {
        "name": "image-vectorizer",
        "kind": "aml",
        "amlParameters": {
          "uri": "https://<endpoint>.inference.ml.azure.com/score",
          "modelName": "multimodal-embedding"
        }
      }
    ]
  }
}
```

### Knowledge Store for biletebevaring

```json
{
  "knowledgeStore": {
    "storageConnectionString": "<connection-string>",
    "projections": [
      {
        "objects": [
          {
            "storageContainer": "document-insights",
            "generatedKeyName": "insight_id",
            "source": "/document/insights"
          }
        ],
        "files": [
          {
            "storageContainer": "extracted-images",
            "generatedKeyName": "image_id",
            "source": "/document/normalized_images/*"
          }
        ]
      }
    ]
  }
}
```

### Dimensjonalitetsreduksjon

For å optimalisere lagring og ytelse:

| Teknikk | Når bruke | Kommentar |
|---------|----------|-----------|
| **Matryoshka embeddings** | Generelt | text-embedding-3-large støttar reduserte dimensjonar |
| **PCA** | Post-prosessering | Reduser dimensjonar etter embedding |
| **Scalar quantization** | Azure AI Search native | 4x reduksjon i lagring |
| **Binary quantization** | Azure AI Search native | 28x reduksjon, noko kvalitetstap |

---

## Retrieval og Ranking Patterns

### Hybrid søk

Azure AI Search sin hybride søk kombinerer fulltekst, vektorsøk og semantisk ranking:

```python
from azure.search.documents import SearchClient
from azure.search.documents.models import VectorizableTextQuery

search_client = SearchClient(
    endpoint="https://<search-service>.search.windows.net",
    index_name="multimodal-index",
    credential=credential
)

# Hybrid søk: tekst + vektor + semantisk ranking
results = search_client.search(
    search_text="trafikksikkerheit i tunneler",
    vector_queries=[
        VectorizableTextQuery(
            text="trafikksikkerheit i tunneler",
            k_nearest_neighbors=10,
            fields="text_vector,image_vector"
        )
    ],
    query_type="semantic",
    semantic_configuration_name="my-semantic-config",
    select=["text_content", "image_description", "image_url", "page_number", "source_file"],
    filter="content_type eq 'diagram' or content_type eq 'text'",
    top=10
)

for result in results:
    print(f"Score: {result['@search.score']}")
    print(f"Type: {result['content_type']}")
    print(f"Content: {result['text_content'][:200]}")
    if result.get('image_url'):
        print(f"Image: {result['image_url']}")
```

### Multimodal Ranking Pipeline

```
Brukar-query
  ├── Fulltekstsøk → BM25-score
  ├── Vektorsøk (tekst) → Cosine similarity
  ├── Vektorsøk (bilete) → Cosine similarity
  └── Semantisk ranking → Cross-encoder re-ranking
         ↓
  Reciprocal Rank Fusion (RRF)
         ↓
  Top-K resultat (tekst + bilete)
         ↓
  LLM (GPT-4o) med multimodalt kontekst
         ↓
  Grunnlagd svar med kjeldereferansar
```

### Multimodal RAG med GPT-4o

```python
def multimodal_rag_query(query: str, search_client, openai_client):
    """Utfør multimodal RAG-query med tekst og bilete."""

    # Steg 1: Hent relevante chunks (tekst + bilete)
    search_results = search_client.search(
        search_text=query,
        vector_queries=[
            VectorizableTextQuery(text=query, k_nearest_neighbors=5, fields="text_vector")
        ],
        query_type="semantic",
        top=10
    )

    # Steg 2: Bygg multimodalt kontekst
    messages = [
        {"role": "system", "content": "Du er ein AI-assistent for norsk offentleg sektor. Svar basert på konteksten."}
    ]

    user_content = [{"type": "text", "text": f"Spørsmål: {query}\n\nKontekst:"}]

    for result in search_results:
        # Legg til tekst
        user_content.append({
            "type": "text",
            "text": f"\n[Kjelde: {result['source_file']}, side {result['page_number']}]\n{result['text_content']}"
        })

        # Legg til bilete om tilgjengeleg
        if result.get('image_url'):
            user_content.append({
                "type": "image_url",
                "image_url": {"url": result['image_url'], "detail": "high"}
            })

    messages.append({"role": "user", "content": user_content})

    # Steg 3: Generer svar med GPT-4o
    response = openai_client.chat.completions.create(
        model="gpt-4o",
        messages=messages,
        max_tokens=2048
    )

    return response.choices[0].message.content
```

### Filtrering etter innhaldstype

| Filter | Brukstilfelle |
|--------|--------------|
| `content_type eq 'text'` | Berre tekstbaserte resultat |
| `content_type eq 'diagram'` | Berre diagram og charts |
| `content_type eq 'photo'` | Berre foto/screenshots |
| `content_type eq 'table'` | Berre tabellar |
| `page_number ge 5 and page_number le 10` | Spesifikke sider |

---

## End-to-End Pipeline med Azure AI Search

### Fullstendig multimodal indexer-skillset

```json
{
  "name": "multimodal-skillset",
  "skills": [
    {
      "@odata.type": "#Microsoft.Skills.Util.DocumentExtractionSkill",
      "name": "document-extraction",
      "parsingMode": "default",
      "dataToExtract": "contentAndMetadata",
      "configuration": {
        "imageAction": "generateNormalizedImages",
        "normalizedImageMaxWidth": 2000,
        "normalizedImageMaxHeight": 2000
      }
    },
    {
      "@odata.type": "#Microsoft.Skills.Text.SplitSkill",
      "name": "text-chunking",
      "textSplitMode": "pages",
      "maximumPageLength": 2000,
      "pageOverlapLength": 500
    },
    {
      "@odata.type": "#Microsoft.Skills.Custom.GenAIPromptSkill",
      "name": "image-verbalization",
      "description": "Beskriv bilete med LLM",
      "context": "/document/normalized_images/*",
      "inputs": [
        {"name": "image", "source": "/document/normalized_images/*"}
      ],
      "outputs": [
        {"name": "description", "targetName": "imageDescription"}
      ],
      "configuration": {
        "prompt": "Beskriv dette biletet kortfatta. Fokuser på nøkkelinformasjon som er relevant for dokumentet."
      }
    },
    {
      "@odata.type": "#Microsoft.Skills.Text.AzureOpenAIEmbeddingSkill",
      "name": "text-embedding",
      "modelName": "text-embedding-3-large",
      "context": "/document/pages/*",
      "inputs": [
        {"name": "text", "source": "/document/pages/*/content"}
      ],
      "outputs": [
        {"name": "embedding", "targetName": "textVector"}
      ]
    }
  ]
}
```

---

## For Cosmo

- **Image verbalization + text embeddings gir best resultat for dokumenttunge RAG-scenario** i offentleg sektor, fordi diagram og flowcharts i PDF-ar inneheld kritisk informasjon som rein tekst-søk misser.
- **Azure AI Search sin multimodal pipeline (preview mai 2025)** forenklar arkitekturen vesentleg: Document Extraction/Layout → GenAI Prompt → Embedding → Index i ein samla skillset.
- **Kombiner begge embedding-tilnærmingane** for robuste enterprise-løysingar: verbalisering for diagram/charts, direkte embeddings for foto og screenshots.
- **Design indeksen med `content_type`-felt** for filtrert søk. Ikkje bland tekst- og biletevektorar i same felt — bruk separate vektorprofilar med tilpassa dimensjonar.
- **Bruk hybrid søk (fulltekst + vektor + semantisk ranking)** for best recall og presisjon i multimodale scenario. RRF (Reciprocal Rank Fusion) er standard i Azure AI Search.
