RAG mit Azure AI Search: Tutorial mit Python und Hybridsuche

Andreas Nigg
Andreas Nigg · Mitgründer, Pondhouse Data
Veröffentlicht Aktualisiert 15 Min. Lesezeit
RAG mit Azure AI Search: Tutorial mit Python und Hybridsuche
Inhalt

Azure AI Search ist der verwaltete Suchdienst von Microsoft und auf Azure der übliche Baustein für den Retrieval-Teil von RAG (Retrieval-Augmented Generation). Der Dienst liest den Text aus Ihren Dateien, teilt ihn in Abschnitte (Chunks), erzeugt dafür Embeddings mit einem Azure-OpenAI-Modell und beantwortet Suchanfragen mit einer Kombination aus Stichwortsuche, Vektorsuche und Reranking. Ein Chatmodell in Microsoft Foundry formuliert daraus die Antwort.

Dieses Tutorial baut die Pipeline vollständig mit dem Python-SDK (azure-search-documents 12.0.0) auf und testet sie an einem echten Dokument, dem 144 Seiten langen EU AI Act. Am Ende beantwortete die Pipeline alle fünf Testfragen richtig, mit Artikelangaben, in rund 1,2 Sekunden pro Antwort. Dafür waren zwei Korrekturen nötig, und beide Probleme treffen Sie mit eigenen Dokumenten genauso: Der Basic-Tarif schnitt die letzten 15 % des PDFs ab, und mit der Standard-Chunkgröße scheiterte eine Frage nach Anwendungsfristen. Getestet haben wir am 6. November 2026; Preise und Regionen haben denselben Stand.

RAG (auf Englisch) läuft in zwei Phasen. Beim Indexieren werden Dokumente in Chunks zerlegt und jeder Chunk mit einem Embedding gespeichert. Bei einer Anfrage sucht das System die passendsten Chunks und gibt sie einem Sprachmodell als Kontext mit. Azure AI Search deckt die erste Phase vollständig ab und von der zweiten den Suchteil.

RAG-Pipeline mit Azure AI Search: Indexer und Skillset füllen den Index, eine hybride Anfrage mit Semantic Ranker liefert gpt-5.4-mini die Quellen

Diese Objekte legen Sie in Azure AI Search an:

ObjektAufgabe
Data SourceVerbindung zu Blob Storage, SharePoint, OneLake, Azure SQL, Cosmos DB und anderen
SkillsetVerarbeitungsschritte beim Indexieren, hier der Text Split Skill (Chunking) und der Azure OpenAI Embedding Skill
IndexDer durchsuchbare Speicher: Textfelder für die Stichwortsuche, Vektorfelder für die Ähnlichkeitssuche
Index ProjectionSchreibt jeden Chunk als eigenes Suchdokument statt eines Dokuments pro Datei
IndexerFührt die Pipeline bei Bedarf oder nach Zeitplan aus und verarbeitet nur neue oder geänderte Dateien
VectorizerWandelt die Frage zur Laufzeit mit demselben Modell in einen Vektor um, Ihr Code schickt nur Text

Microsoft nennt das Zusammenspiel aus Skillset, Index Projection und Vectorizer "Integrated Vectorization". Ihre Anwendung berechnet selbst kein einziges Embedding.

Bei einer Anfrage startet eine hybride Suche parallel eine BM25-Stichwortsuche und eine Vektorsuche (HNSW) und führt beide Ergebnislisten per Reciprocal Rank Fusion (RRF) zusammen. Der Semantic Ranker sortiert danach die besten 50 Treffer mit einem mehrsprachigen Modell neu, das von Bing abgeleitet ist. Die besten fünf Chunks gehen an das Chatmodell.

Klassisches RAG, Agentic Retrieval und Foundry IQ

Die RAG-Übersicht von Microsoft beschreibt inzwischen zwei Wege. "Classic RAG" ist die Pipeline oben: Ihr Code schickt eine hybride Anfrage und ruft das Modell auf. Bei "Agentic Retrieval" plant ein LLM mehrere Teilanfragen, der Dienst führt sie parallel aus und liefert die zusammengeführten Ergebnisse. Die zugehörige Ressource heißt Knowledge Base; Foundry IQ ist der Name für Knowledge Bases, die Agenten im Microsoft-Foundry-Portal nutzen.

Der Status ist gemischt. Knowledge Bases mit minimalem, extraktivem Retrieval sind seit der REST-API 2026-04-01 allgemein verfügbar (GA). Die Anfrageplanung per LLM, die Antwortsynthese und die Reasoning-Stufen low und medium sind noch Preview (2026-08-01-preview), und das Portal zeigt alle Agentic-Retrieval-Funktionen nur im Preview-Modus. Weiter unten testen wir beides.

Falls Sie "Azure OpenAI On Your Data" genutzt haben (den Parameter data_sources der Chat-Completions-API oder "Add your data" im Playground): Microsoft hat die Funktion am 14. Oktober 2026 eingestellt. Bestehende Suchindizes funktionieren weiter. Sie rufen den Suchdienst jetzt selbst auf, wie unten gezeigt, oder steigen auf eine Knowledge Base um.

Tarif und Region wählen

Für den Datenstandort im DACH-Raum zählt die Region so viel wie der Tarif. Die Regionsliste (Stand 3. November 2026) führt Sweden Central, West Europe, Germany West Central und North Europe als "high demand". Das "prevents the creation of new search services", also lassen sich dort keine neuen Suchdienste anlegen. Bestehende Dienste laufen weiter. France Central und Switzerland North nehmen neue Dienste an, und alle sechs Regionen unterstützen Semantic Ranker und Agentic Retrieval.

Chat- und Embedding-Modell dürfen in einer anderen Region laufen als der Suchdienst. Die Verfügbarkeit der Modelle haben wir am 6. November 2026 mit az cognitiveservices model list geprüft:

RegionSuchdienstgpt-5.4-minitext-embedding-3-small
France Centralneue Dienste möglichDataZoneStandard, GlobalStandardDataZoneStandard, GlobalStandard
Switzerland Northneue Dienste möglichDataZoneStandard, GlobalStandardStandard (regional), GlobalStandard
Germany West Centralhigh demand, keine neuen DiensteDataZoneStandard, GlobalStandardDataZoneStandard, GlobalStandard
Sweden Centralhigh demand, keine neuen DiensteDataZoneStandard, GlobalStandardDataZoneStandard, GlobalStandard

Wir haben alles in France Central angelegt und beide Modelle als DataZoneStandard bereitgestellt. Laut der Seite zu den Deployment-Typen verarbeitet der Dienst die Daten dann nur innerhalb der EU Data Boundary (dazu können EFTA-Staaten wie die Schweiz und Norwegen gehören), und gespeicherte Daten bleiben in der Geografie der Ressource. Ein GlobalStandard-Deployment ist etwa 9 % günstiger, die Daten "may be processed in any Azure region", also in jeder Azure-Region.

Free reicht für einen ersten Blick. Allerdings gibt es pro Subscription nur einen kostenlosen Dienst, mit 50 MB Speicher, drei Indizes und Indexer-Läufen von höchstens drei Minuten. Basic ist der kleinste Tarif für echte Dokumente. Der Import-Quickstart setzt Basic oder höher auch für Managed Identities voraus, die wir für jede Verbindung nutzen.

Azure-Ressourcen anlegen

Sie brauchen eine Azure-Subscription, die Azure CLI und Python 3.10 oder neuer. Diese Befehle haben wir ausgeführt; ersetzen Sie die Platzhalter durch Ihre Werte:

1SUB="<subscription-id>"
2RG="rg-rag-demo"
3LOCATION="francecentral"
4SUFFIX="<unique-suffix>"
5
6az group create --subscription $SUB -n $RG -l $LOCATION
7
8az search service create --subscription $SUB -g $RG -n srch-rag-$SUFFIX -l $LOCATION \
9 --sku basic --partition-count 1 --replica-count 1 \
10 --auth-options aadOrApiKey --aad-auth-failure-mode http401WithBearerChallenge \
11 --identity-type SystemAssigned --semantic-search free
12
13az cognitiveservices account create --subscription $SUB -g $RG -n aif-rag-$SUFFIX -l $LOCATION \
14 --kind AIServices --sku S0 --custom-domain aif-rag-$SUFFIX --yes
15
16az storage account create --subscription $SUB -g $RG -n strag$SUFFIX -l $LOCATION \
17 --sku Standard_LRS --kind StorageV2 --allow-blob-public-access false --min-tls-version TLS1_2

--auth-options aadOrApiKey aktiviert Microsoft Entra ID (RBAC) zusätzlich zu den API-Keys, --identity-type SystemAssigned gibt dem Suchdienst eine eigene Managed Identity, und --semantic-search free schaltet den kostenlosen Plan des Semantic Rankers ein. Der AIServices-Account ist eine Microsoft-Foundry-Ressource. Sie braucht eine eigene Subdomain, damit Azure AI Search sie aufrufen kann. Danach stellen Sie die beiden Modelle bereit:

1az cognitiveservices account deployment create --subscription $SUB -g $RG -n aif-rag-$SUFFIX \
2 --deployment-name gpt-5.4-mini --model-name gpt-5.4-mini --model-version 2026-03-17 \
3 --model-format OpenAI --sku-name DataZoneStandard --sku-capacity 100
4
5az cognitiveservices account deployment create --subscription $SUB -g $RG -n aif-rag-$SUFFIX \
6 --deployment-name text-embedding-3-small --model-name text-embedding-3-small --model-version 1 \
7 --model-format OpenAI --sku-name DataZoneStandard --sku-capacity 150

Wir haben gpt-5.4-mini gewählt, weil Azure gpt-4.1-mini bereits als "Legacy" führt (Abschaltung am 14. April 2027). Die Kapazität ist in tausend Token pro Minute angegeben.

Dann vergeben Sie die Rollen. Die Managed Identity des Suchdienstes liest die Blobs und ruft das Embedding-Modell auf; Ihr eigenes Benutzerkonto verwaltet den Index und ruft das Chatmodell auf. So landet kein Key im Code oder in der Indexdefinition.

1SRCH_ID=$(az search service show --subscription $SUB -g $RG -n srch-rag-$SUFFIX --query id -o tsv)
2SRCH_MI=$(az search service show --subscription $SUB -g $RG -n srch-rag-$SUFFIX --query identity.principalId -o tsv)
3AIF_ID=$(az cognitiveservices account show --subscription $SUB -g $RG -n aif-rag-$SUFFIX --query id -o tsv)
4ST_ID=$(az storage account show --subscription $SUB -g $RG -n strag$SUFFIX --query id -o tsv)
5ME="<your Entra object ID>"
6
7az role assignment create --subscription $SUB --assignee-object-id $SRCH_MI --assignee-principal-type ServicePrincipal --role "Cognitive Services OpenAI User" --scope $AIF_ID
8az role assignment create --subscription $SUB --assignee-object-id $SRCH_MI --assignee-principal-type ServicePrincipal --role "Storage Blob Data Reader" --scope $ST_ID
9az role assignment create --subscription $SUB --assignee-object-id $ME --assignee-principal-type User --role "Search Service Contributor" --scope $SRCH_ID
10az role assignment create --subscription $SUB --assignee-object-id $ME --assignee-principal-type User --role "Search Index Data Contributor" --scope $SRCH_ID
11az role assignment create --subscription $SUB --assignee-object-id $ME --assignee-principal-type User --role "Cognitive Services OpenAI User" --scope $AIF_ID
12az role assignment create --subscription $SUB --assignee-object-id $ME --assignee-principal-type User --role "Storage Blob Data Contributor" --scope $ST_ID

Dokument hochladen

Als Testdokument dient der EU AI Act, Verordnung (EU) 2024/1689, als englisches PDF von EUR-Lex (144 Seiten, 2,6 MB). Die EU erlaubt die Weiterverwendung von EUR-Lex-Dokumenten mit Quellenangabe. Mit langen Artikeln, Querverweisen und Anhängen ist die Verordnung ein guter Ersatz für Verträge, Normen und Handbücher.

Der Basic-Tarif extrahiert nur die ersten 512.000 Zeichen jeder Datei (mehr dazu bei den Stolpersteinen). Deshalb haben wir das PDF vor dem Hochladen in drei Teile zu je 48 Seiten geteilt:

1qpdf ai-act-en.pdf --pages . 1-48 -- ai-act-en-part1.pdf
2qpdf ai-act-en.pdf --pages . 49-96 -- ai-act-en-part2.pdf
3qpdf ai-act-en.pdf --pages . 97-144 -- ai-act-en-part3.pdf
4
5az storage container create --subscription $SUB --account-name strag$SUFFIX -n docs --auth-mode login
6az storage blob upload-batch --subscription $SUB --account-name strag$SUFFIX -d docs -s ./parts --auth-mode login

Python-Pakete installieren

1uv add azure-search-documents==12.0.0 azure-identity openai

Wir haben azure-search-documents 12.0.0 (erschienen am 1. Mai 2026, nutzt die REST-API 2026-04-01), azure-identity 1.27.0 und openai 3.46.0 verwendet. Die Skripte lesen drei Umgebungsvariablen:

1export AZURE_SEARCH_ENDPOINT=https://srch-rag-<suffix>.search.windows.net
2export AZURE_OPENAI_ENDPOINT=https://aif-rag-<suffix>.openai.azure.com
3export STORAGE_RESOURCE_ID=$ST_ID

DefaultAzureCredential meldet sich mit Ihrem Azure-CLI-Login an. Wenn Ihr Konto mehrere Tenants sieht, führen Sie vorher az account set --subscription <id> aus. Die CLI fordert Tokens für den Tenant der Standard-Subscription an, und ein Token aus dem falschen Tenant scheitert mit einem Authentifizierungsfehler.

Schritt 1: Index und Indexing-Pipeline anlegen

Dieses Skript legt Data Source, Index, Skillset und Indexer an. Der Indexer startet, sobald er angelegt ist; das Skript wartet, bis der Lauf fertig ist.

1import os
2import time
3
4from azure.identity import DefaultAzureCredential
5from azure.search.documents.indexes import SearchIndexClient, SearchIndexerClient
6from azure.search.documents.indexes.models import (
7 AzureOpenAIEmbeddingSkill,
8 AzureOpenAIVectorizer,
9 AzureOpenAIVectorizerParameters,
10 HnswAlgorithmConfiguration,
11 IndexProjectionMode,
12 InputFieldMappingEntry,
13 OutputFieldMappingEntry,
14 SearchField,
15 SearchFieldDataType,
16 SearchIndex,
17 SearchIndexer,
18 SearchIndexerDataContainer,
19 SearchIndexerDataSourceConnection,
20 SearchIndexerIndexProjection,
21 SearchIndexerIndexProjectionSelector,
22 SearchIndexerIndexProjectionsParameters,
23 SearchIndexerSkillset,
24 SemanticConfiguration,
25 SemanticField,
26 SemanticPrioritizedFields,
27 SemanticSearch,
28 SplitSkill,
29 VectorSearch,
30 VectorSearchProfile,
31)
32
33search_endpoint = os.environ["AZURE_SEARCH_ENDPOINT"] # https://<service>.search.windows.net
34openai_endpoint = os.environ["AZURE_OPENAI_ENDPOINT"] # https://<resource>.openai.azure.com
35storage_id = os.environ["STORAGE_RESOURCE_ID"] # /subscriptions/.../storageAccounts/<name>
36name = "ai-act"
37
38credential = DefaultAzureCredential()
39index_client = SearchIndexClient(search_endpoint, credential)
40indexer_client = SearchIndexerClient(search_endpoint, credential)
41
42# 1. Data source: a blob container, accessed with the search service's managed identity
43data_source = SearchIndexerDataSourceConnection(
44 name=f"{name}-blob",
45 type="azureblob",
46 connection_string=f"ResourceId={storage_id};",
47 container=SearchIndexerDataContainer(name="docs"),
48)
49indexer_client.create_or_update_data_source_connection(data_source)
50
51# 2. Index: one search document per chunk, with text and vector fields
52embedding_params = AzureOpenAIVectorizerParameters(
53 resource_url=openai_endpoint,
54 deployment_name="text-embedding-3-small",
55 model_name="text-embedding-3-small",
56)
57index = SearchIndex(
58 name=name,
59 fields=[
60 SearchField(name="chunk_id", type=SearchFieldDataType.STRING, key=True,
61 searchable=True, analyzer_name="keyword", sortable=True),
62 SearchField(name="parent_id", type=SearchFieldDataType.STRING, filterable=True),
63 SearchField(name="title", type=SearchFieldDataType.STRING, searchable=True),
64 SearchField(name="chunk", type=SearchFieldDataType.STRING, searchable=True,
65 analyzer_name="en.microsoft"),
66 SearchField(name="text_vector", type="Collection(Edm.Single)", searchable=True,
67 vector_search_dimensions=1536, vector_search_profile_name="hnsw-profile"),
68 ],
69 vector_search=VectorSearch(
70 algorithms=[HnswAlgorithmConfiguration(name="hnsw")],
71 vectorizers=[AzureOpenAIVectorizer(vectorizer_name="openai", parameters=embedding_params)],
72 profiles=[VectorSearchProfile(name="hnsw-profile", algorithm_configuration_name="hnsw",
73 vectorizer_name="openai")],
74 ),
75 semantic_search=SemanticSearch(
76 default_configuration_name="default",
77 configurations=[SemanticConfiguration(
78 name="default",
79 prioritized_fields=SemanticPrioritizedFields(
80 title_field=SemanticField(field_name="title"),
81 content_fields=[SemanticField(field_name="chunk")],
82 ),
83 )],
84 ),
85)
86index_client.create_or_update_index(index)
87
88# 3. Skillset: split the extracted text into chunks, embed every chunk
89split = SplitSkill(
90 name="split",
91 context="/document",
92 text_split_mode="pages",
93 maximum_page_length=1000,
94 page_overlap_length=200,
95 inputs=[InputFieldMappingEntry(name="text", source="/document/content")],
96 outputs=[OutputFieldMappingEntry(name="textItems", target_name="pages")],
97)
98embed = AzureOpenAIEmbeddingSkill(
99 name="embed",
100 context="/document/pages/*",
101 resource_url=openai_endpoint,
102 deployment_name="text-embedding-3-small",
103 model_name="text-embedding-3-small",
104 dimensions=1536,
105 inputs=[InputFieldMappingEntry(name="text", source="/document/pages/*")],
106 outputs=[OutputFieldMappingEntry(name="embedding", target_name="text_vector")],
107)
108skillset = SearchIndexerSkillset(
109 name=f"{name}-skillset",
110 skills=[split, embed],
111 # index projections write each chunk as its own search document
112 index_projection=SearchIndexerIndexProjection(
113 selectors=[SearchIndexerIndexProjectionSelector(
114 target_index_name=name,
115 parent_key_field_name="parent_id",
116 source_context="/document/pages/*",
117 mappings=[
118 InputFieldMappingEntry(name="chunk", source="/document/pages/*"),
119 InputFieldMappingEntry(name="text_vector", source="/document/pages/*/text_vector"),
120 InputFieldMappingEntry(name="title", source="/document/metadata_storage_name"),
121 ],
122 )],
123 parameters=SearchIndexerIndexProjectionsParameters(
124 projection_mode=IndexProjectionMode.SKIP_INDEXING_PARENT_DOCUMENTS,
125 ),
126 ),
127)
128indexer_client.create_or_update_skillset(skillset)
129
130# 4. Indexer: connects data source, skillset and index, runs on creation
131indexer = SearchIndexer(
132 name=f"{name}-indexer",
133 data_source_name=data_source.name,
134 skillset_name=skillset.name,
135 target_index_name=name,
136)
137indexer_client.create_or_update_indexer(indexer)
138
139start = time.time()
140while True:
141 time.sleep(10)
142 status = indexer_client.get_indexer_status(indexer.name)
143 run = status.last_result
144 if run and run.status not in ("inProgress", "reset"):
145 break
146print(f"status={run.status} items={run.item_count} failed={run.failed_item_count} "
147 f"errors={[e.error_message for e in run.errors]} took~{time.time() - start:.0f}s")

Ein paar Details gehen leicht schief:

  • Das Schlüsselfeld chunk_id braucht den Analyzer keyword, sonst können die Index Projections die erzeugten Chunk-IDs nicht schreiben.
  • Vectorizer im Index und Embedding Skill müssen auf dasselbe Modell mit denselben Dimensionen zeigen. Sonst liegen Anfragen und Chunks in verschiedenen Vektorräumen.
  • en.microsoft ist der Sprach-Analyzer für die Stichwortsuche. Für deutsche Dokumente nehmen Sie de.microsoft.
  • Kein Connection String enthält einen Key. Die Data Source nutzt ResourceId=..., Skill und Vectorizer melden sich mit der Managed Identity des Suchdienstes an.

Der Lauf über die drei Teile dauerte 73 Sekunden und ergab 809 Chunks. Der Index belegt 18,7 MB, davon 5,0 MB Vektorindex. Eine Basic-Partition fasst 15 GB Speicher und 5 GB Vektoren. Der Speicher ist also zuerst voll, bei etwas über 800 Dokumenten dieser Größe.

Wenn Sie lieber im Portal arbeiten: Der Import data Wizard (Szenario "RAG") legt dieselben vier Objekte an. Laut Dokumentation teilt er immer in Chunks von 2.000 Zeichen mit 500 Zeichen Überlappung, und diese Einstellung lässt sich nicht ändern. Das ist nicht egal, wie der Test unten zeigt.

Schritt 2: Hybride Suche und Antwort mit Quellenangaben

Der Abfragecode passt in ein Modul. retrieve schickt eine hybride Anfrage: die Frage als Suchtext für BM25 und eine VectorizableTextQuery, die der Suchdienst selbst in einen Vektor umwandelt. Mit query_type="semantic" sortiert der Semantic Ranker die Treffer neu. answer nummeriert die besten fünf Chunks und weist gpt-5.4-mini an, sie zu zitieren.

1import os
2
3from azure.identity import DefaultAzureCredential, get_bearer_token_provider
4from azure.search.documents import SearchClient
5from azure.search.documents.models import VectorizableTextQuery
6from openai import OpenAI
7
8credential = DefaultAzureCredential()
9search = SearchClient(os.environ["AZURE_SEARCH_ENDPOINT"], "ai-act", credential)
10llm = OpenAI(
11 base_url=f"{os.environ['AZURE_OPENAI_ENDPOINT']}/openai/v1/",
12 api_key=get_bearer_token_provider(credential, "https://cognitiveservices.azure.com/.default"),
13)
14
15
16def retrieve(question: str, mode: str = "hybrid-semantic", top: int = 5) -> list[dict]:
17 vector = VectorizableTextQuery(text=question, k_nearest_neighbors=50, fields="text_vector")
18 args = {"select": ["chunk_id", "title", "chunk"], "top": top}
19 if mode in ("keyword", "hybrid", "hybrid-semantic"):
20 args["search_text"] = question
21 if mode in ("vector", "hybrid", "hybrid-semantic"):
22 args["vector_queries"] = [vector]
23 if mode == "hybrid-semantic":
24 args["query_type"] = "semantic"
25 args["semantic_configuration_name"] = "default"
26 return list(search.search(**args))
27
28
29SYSTEM = """You answer questions about the EU AI Act using only the numbered sources.
30Cite every statement with its source number in square brackets, e.g. [2], and name the
31Article or Annex when the source shows it. If the sources do not contain the answer,
32say so instead of guessing. Answer in the language of the question."""
33
34
35def answer(question: str) -> tuple[str, list[dict]]:
36 chunks = retrieve(question)
37 sources = "\n\n".join(f"[{i}] {c['chunk']}" for i, c in enumerate(chunks, start=1))
38 response = llm.responses.create(
39 model="gpt-5.4-mini", # deployment name
40 instructions=SYSTEM,
41 input=f"Sources:\n{sources}\n\nQuestion: {question}",
42 )
43 return response.output_text, chunks
44
45
46if __name__ == "__main__":
47 import sys
48
49 text, chunks = answer(sys.argv[1])
50 print(text)
51 for i, c in enumerate(chunks, start=1):
52 print(f"[{i}] {c['title']} {c['chunk'][:80]!r}")

k_nearest_neighbors=50 gibt dem Semantic Ranker die vollen 50 Kandidaten, die er neu sortieren kann. Der OpenAI-Client nutzt den v1-Endpunkt der Foundry-Ressource und ein Token aus Entra ID statt eines API-Keys; model ist der Name des Deployments. Auf die Frage "From when do the obligations for general-purpose AI models apply?" antwortete die Pipeline:

1The obligations for providers of general-purpose AI models apply from **2 August 2025**. [1]
2
3The sources also say these obligations apply **once the general-purpose AI models are placed on the market**. [2]

Quelle [1] ist der Chunk mit Erwägungsgrund 179, der genau dieses Datum nennt. Ihre Anwendung ordnet die Nummern den Chunks zu (title, chunk_id) und kann sie als Links anzeigen.

Testergebnis: fünf Fragen zum EU AI Act

Wir haben fünf Fragen gestellt, deren Antworten eindeutig im Text stehen. Pro Frage haben wir geprüft, ob der Chunk mit der Antwort in jedem Suchmodus unter den ersten fünf Treffern war und ob die Antwort stimmte. Eine Frage war auf Deutsch, das Dokument auf Englisch.

FrageStichwortVektorHybridHybrid + SemanticAntwort
Höchststrafe für eine verbotene KI-Praxisnicht gefundenRang 5nicht gefundenRang 1richtig: 35 Mio. Euro oder 7 % (Art. 99 Abs. 3)
Was Chatbot-Anbieter Nutzern sagen müssennicht gefundenRang 1Rang 4Rang 1richtig (Art. 50 Abs. 1)
Ab wann gelten die Pflichten für GPAI-Modellenicht gefundennicht gefundennicht gefundenRang 1richtig: 2. August 2025
Pflichten der Betreiber zur menschlichen Aufsicht (deutsch)nicht gefundenRang 3Rang 3Rang 1richtig, auf Deutsch (Art. 26 Abs. 2)
Ist KI zum Filtern von Bewerbungen hochriskant?Rang 1Rang 1Rang 1Rang 1richtig (Anhang III)

Hybride Suche mit Semantic Ranker brachte bei jeder Frage den richtigen Chunk auf Platz 1. Die Stichwortsuche allein fand nur einen davon, und die deutsche Frage fand per Stichwort gar nichts, weil das Dokument englisch ist. Die Vektorsuche hat die Sprachgrenze überbrückt, und bei den Fragen nach Strafe und Frist hat erst der Ranker den richtigen Chunk nach vorn geholt.

Antwortzeiten der Suche (Median und 90. Perzentil über 15 Anfragen pro Modus, Client in Österreich, Dienst in France Central):

ModusMedianp90
Stichwort40 ms68 ms
Vektor108 ms143 ms
Hybrid217 ms263 ms
Hybrid + Semantic269 ms340 ms

Die vollständige Antwort mit Modellaufruf dauerte zwischen 0,9 und 4,2 Sekunden, im Median 1,2 Sekunden. Pro Frage las gpt-5.4-mini rund 1.050 Eingabe-Token und schrieb 50 bis 270 Ausgabe-Token.

Was die ersten beiden Versuche falsch machten

Die Tabelle zeigt die endgültige Konfiguration. Dorthin kamen wir in drei Läufen:

AufbauRichtige Antworten
Ein PDF mit 144 Seiten, Chunks mit 2.000/500 Zeichen3 von 5 (Strafe: "nicht in den Quellen"; GPAI-Frist: falsch)
Drei PDF-Teile, 2.000/500 Zeichen4 von 5 (GPAI-Frist: falsch)
Drei PDF-Teile, 1.000/200 Zeichen5 von 5

Im ersten Lauf meldete der Indexer Erfolg, mit einer einzigen Warnung: Truncated extracted text to '524288' characters. Der Index endete mitten in Artikel 99. Alles danach fehlte: die Geldbußen, Artikel 113 mit den Fristen und sämtliche Anhänge. Das Modell sagte korrekt, dass die Quellen die Strafhöhe nicht enthalten, und beantwortete die Hochrisiko-Frage aus den Erwägungsgründen statt aus Anhang III.

Die zweite Korrektur betrifft die Chunkgröße. Bei 2.000 Zeichen lag die Passage zu den Fristen in einem Chunk zusammen mit Artikel 1, und Artikel 113 selbst nennt nur "Chapter V", ohne KI-Modelle mit allgemeinem Verwendungszweck zu erwähnen. Keiner der beiden Chunks schaffte es unter die ersten 50. Bei 1.000 Zeichen stand der Chunk mit Erwägungsgrund 179 auf Platz 1. Kleinere Chunks sind nicht immer besser. Bei Gesetzestexten und anderen Dokumenten mit vielen kurzen, in sich geschlossenen Regelungen haben sie hier aber klar geholfen. Testen Sie das mit Ihren eigenen Dokumenten und Fragen, bevor Sie den Standard des Wizards übernehmen.

Knowledge Bases und Agentic Retrieval

Eine Knowledge Base setzt auf einen bestehenden Index auf. In der GA-Version (2026-04-01) plant sie keine Anfragen: Sie übergeben eine oder mehrere Suchabsichten (Intents), der Dienst führt sie parallel mit semantischem Reranking aus und liefert die zusammengeführten Chunks mit Referenzen. Das SDK 12.0.0 unterstützt das:

1import os
2
3from azure.identity import DefaultAzureCredential
4from azure.search.documents.indexes import SearchIndexClient
5from azure.search.documents.indexes.models import (
6 KnowledgeBase,
7 KnowledgeSourceReference,
8 SearchIndexKnowledgeSource,
9 SearchIndexKnowledgeSourceParameters,
10)
11from azure.search.documents.knowledgebases import KnowledgeBaseRetrievalClient
12from azure.search.documents.knowledgebases.models import (
13 KnowledgeBaseRetrievalRequest,
14 KnowledgeRetrievalSemanticIntent,
15 SearchIndexKnowledgeSourceParams,
16)
17
18endpoint = os.environ["AZURE_SEARCH_ENDPOINT"]
19credential = DefaultAzureCredential()
20index_client = SearchIndexClient(endpoint, credential)
21
22# A knowledge source wraps the existing index, a knowledge base groups knowledge sources
23index_client.create_or_update_knowledge_source(SearchIndexKnowledgeSource(
24 name="ai-act-source",
25 search_index_parameters=SearchIndexKnowledgeSourceParameters(search_index_name="ai-act"),
26))
27index_client.create_or_update_knowledge_base(KnowledgeBase(
28 name="ai-act-kb",
29 knowledge_sources=[KnowledgeSourceReference(name="ai-act-source")],
30))
31
32kb = KnowledgeBaseRetrievalClient(endpoint, credential, knowledge_base_name="ai-act-kb")
33result = kb.retrieve(KnowledgeBaseRetrievalRequest(
34 # GA (2026-04-01): you pass the queries yourself, no LLM query planning
35 intents=[
36 KnowledgeRetrievalSemanticIntent(search="maximum fine for prohibited AI practices"),
37 KnowledgeRetrievalSemanticIntent(search="date of application of general-purpose AI obligations"),
38 ],
39 knowledge_source_params=[SearchIndexKnowledgeSourceParams(
40 knowledge_source_name="ai-act-source", include_references=True,
41 )],
42))
43print(result.response[0].content[0].text[:600])
44for ref in result.references[:6]:
45 print(ref.doc_key[-12:], round(ref.reranker_score or 0, 2))

Die Antwort enthält die Chunks als JSON-Array (ref_id, title, content), fertig für die Übergabe an ein Modell. In unserem Lauf stand der Chunk mit Erwägungsgrund 179 an erster Stelle. Die GA-Version leistet etwa dasselbe wie die klassische hybride Anfrage, bringt aber zwei Dinge mit: Sie kann mehrere Knowledge Sources (Indizes, Blob, OneLake, Web) in einem Aufruf abfragen, und Agenten in Foundry können sie direkt als Werkzeug nutzen.

Spannender ist der Teil, der noch Preview ist. Mit 2026-08-01-preview, einer Modellreferenz und der Reasoning-Stufe low haben wir die ursprüngliche Fristen-Frage an eine Knowledge Base auf dem Index mit 2.000-Zeichen-Chunks geschickt, also genau dort, wo klassisches RAG gescheitert war. gpt-5.4-mini zerlegte die Frage in drei Teilanfragen, und die Passage aus Erwägungsgrund 179 war im Ergebnis. Der Aufruf dauerte 2,0 Sekunden: 0,86 Sekunden für die Planung und rund 0,3 Sekunden pro Teilanfrage, parallel ausgeführt. Die Anfrageplanung gleicht eine ungünstige Chunkgröße also aus, kostet aber einen zusätzlichen Modellaufruf pro Frage. Als Preview-Funktion gehört sie vorerst nicht in die Produktion.

Stolpersteine

Große Dateien werden still abgeschnitten

Jeder Tarif extrahiert nur eine begrenzte Zahl von Zeichen pro Datei: 256.000 bei Free, 512.000 bei Basic, 4 Millionen bei S1 (siehe die Limits-Seite). Der Indexer meldet trotzdem Erfolg und hinterlässt nur eine Warnung im Verlauf. Prüfen Sie nach jedem Lauf die Warnungen, teilen Sie große Dateien oder nehmen Sie S1. Die maximale Dateigröße liegt bei Free und Basic bei 16 MB.

Die Chunkgröße des Wizards ist fest

Der Wizard im Portal teilt immer in 2.000/500 Zeichen. Wenn Sie andere Werte brauchen, definieren Sie das Skillset wie oben im Code oder passen es nach dem Wizard an.

Beliebte EU-Regionen nehmen keine neuen Dienste an

In vier der gängigsten europäischen Regionen können Sie derzeit keinen neuen Suchdienst anlegen. Prüfen Sie die Regionsliste vor der Planung. Suchdienst und Modelle dürfen in verschiedenen Regionen liegen.

Der kostenlose Plan des Semantic Rankers ist begrenzt

Neue Dienste starten im kostenlosen Plan mit einem monatlichen Kontingent; die Preisseite nennt 1.000 kostenlose Anfragen. Danach liefern Anfragen einen Abrechnungsfehler, bis Sie den Dienst auf den Standard-Plan umstellen (az search service update --semantic-search standard). Preisseite und Dokumentation zum Semantic Ranker widersprechen sich übrigens bei der Frage, ob der Free-Tarif den Ranker überhaupt bekommt. Planen Sie mit Basic.

DefaultAzureCredential erwischt den falschen Tenant

DefaultAzureCredential nutzt den Azure-CLI-Login. Gehört die Standard-Subscription der CLI zu einem anderen Tenant, scheitert jede Anfrage mit einem Authentifizierungsfehler. az account set --subscription <id> behebt das.

Chunks haben keine Seitenzahlen

Die Standard-Textextraktion merkt sich nicht, von welcher Seite ein Chunk stammt. Beim AI Act reichten die Artikelnummern im Text für die Zitate. Wenn Sie Seitenzahlen oder Koordinaten brauchen, nehmen Sie den Document Layout Skill mit locationMetadata. Ab 20 Dokumenten pro Indexer und Tag wird er über Document Intelligence abgerechnet.

Die Indexstatistik hinkt hinterher

Direkt nach dem Indexieren zeigte die Indexstatistik mehrere Minuten lang 0 Dokumente und 0 Byte, obwohl Anfragen schon Treffer lieferten. Was durchsuchbar ist, prüfen Sie mit get_document_count().

Was Azure AI Search kostet

Preise aus der Azure Retail Prices API für France Central, abgerufen am 6. November 2026. Die Monatswerte rechnen mit 730 Stunden, einem Replikat und einer Partition.

TarifPreis pro StundePro MonatSpeicher pro PartitionVektorindex pro Partition
Free0050 MB gesamtk. A.
Basic0,0907 Eurorund 66 Euro15 GB5 GB
Standard S10,3016 Eurorund 220 Euro160 GB35 GB
Standard S21,2065 Eurorund 881 Euro512 GB150 GB

Dazu kommen nutzungsabhängige Kosten:

PostenPreis
Semantic Ranker1.000 Anfragen pro Monat frei, danach 0,8977 Euro pro 1.000
Agentic Retrieval50 Millionen Token pro Monat frei, danach 0,022 US-Dollar pro Million Token
gpt-5.4-mini, DataZoneStandard0,825 US-Dollar pro Million Eingabe-Token, 4,95 US-Dollar pro Million Ausgabe-Token
text-embedding-3-small, DataZoneStandard0,022 US-Dollar pro Million Token

Bei rund 1.050 Eingabe- und 120 Ausgabe-Token kostet eine Antwort etwa 0,0015 US-Dollar für das Modell. Den ganzen AI Act einmal in Embeddings umzuwandeln, kostet deutlich unter einem Cent. In der Praxis besteht die Monatsrechnung also vor allem aus dem Suchtarif: Für einige tausend Fragen im Monat auf Basic zahlen Sie rund 66 Euro plus ein paar Euro für Modell und Semantic Ranker. Jedes zusätzliche Replikat und jede Partition kostet noch einmal den Stundenpreis, ein Basic-Dienst mit zwei Replikaten also rund 132 Euro. Unser gesamter Testlauf kostete weniger als einen Euro.

Microsoft bietet außerdem einen Serverless-Tarif als Preview an, abgerechnet nach Compute Units pro Stunde und gespeicherten GB, mit Skalierung auf null und 1 GB pro Index. Er ist nur in einigen Regionen verfügbar, hat kein SLA und lässt sich nicht in die dedizierten Tarife migrieren. Für einen Proof of Concept mit wenig Last kann er die günstigere Wahl werden.

Unsere Einschätzung

Für Unternehmen, die ohnehin auf Azure arbeiten, ist Azure AI Search der naheliegende Startpunkt für RAG. Die integrierte Pipeline erspart Ihnen eigenen Code für Chunking, Embeddings und Synchronisation, der Indexer erkennt geänderte Dateien von selbst, und hybride Suche mit Semantic Ranker lieferte im Test gute Treffer, ohne dass wir außer der Chunkgröße etwas einstellen mussten. Mit Managed Identities und Entra-ID-Rollen steht kein Key im Code, und mit France Central oder Switzerland North plus DataZoneStandard-Deployments bleiben die Daten in der EU oder in der Schweiz.

Die Schwachstellen übersieht man leicht. Das Extraktionslimit pro Datei fällt kaum auf, die Chunkgröße des Wizards ist fest, und die beliebtesten EU-Regionen nehmen derzeit keine neuen Dienste an. Agentic Retrieval mit Anfrageplanung bringt eine echte Verbesserung, ist aber zum größten Teil noch Preview. Testen Sie das Retrieval vor dem Livegang mit Ihren eigenen Dokumenten und einer Reihe echter Fragen, so wie hier. Wie Sie das automatisieren, zeigt unser Beitrag zur RAG-Evaluierung mit Ragas. Reicht die Qualität des Rerankings nicht, ist ein Late-Interaction-Reranker wie ColBERT (auf Englisch) eine Alternative zum Semantic Ranker.

Wenn Ihr Wissen in SharePoint und Microsoft 365 liegt, lohnt auch ein Blick auf SharePoint-Listen als Wissensquelle in Copilot Studio. Für Agenten, die den Index als Werkzeug nutzen, gibt es unseren Leitfaden zum Microsoft Agent Framework.

Wir bauen RAG-Systeme auf Azure für Unternehmen in Österreich, Deutschland und der Schweiz, von der Aufbereitung der Dokumente bis zur Evaluierung. Wenn Sie so ein Projekt planen, finden Sie bei unserer KI-Beratung den Einstieg, oder schreiben Sie uns direkt.

Sie möchten zuverlässige KI-Agenten-Systeme bauen?

Wir haben auf Basis aktueller Forschung einen ausführlichen Leitfaden (auf Englisch) erstellt, wie Sie robuste KI-Agenten-Systeme bauen, die auch im produktiven Betrieb funktionieren. Der Leitfaden behandelt:

  • die 14 systematischen Fehlerquellen in Multi-Agenten-Systemen
  • belegte Best Practices für das Design von Agenten
  • strukturierte Kommunikationsprotokolle und Prüfmechanismen
Andreas Nigg

Über den Autor

Andreas Nigg

Mitgründer, Pondhouse Data

Andreas entwickelt seit mehr als acht Jahren Datenanwendungen. Bei Pondhouse Data konzipiert und baut er KI-Systeme und integriert sie in komplexe, gewachsene Unternehmensumgebungen.