Brand Mentions in KI-Antworten messen: Sichtbarkeit mit Python

Wir haben 45 Prompts je dreimal durch Google AI Mode geschickt. Bei 43 von 45 wechselten die genannten Marken zwischen identischen Läufen. Methode, Zahlen und ein Python-Skript.

Um die Sichtbarkeit einer Marke in KI-Antworten zu messen, schickst du einen festen Satz Prompts mehrfach durch die KI-Oberfläche, zählst mit einem Regex mit Wortgrenzen den Anteil der Antworten, die die Marke nennen, und gibst ein Intervall statt einer einzelnen Zahl an. Ein einzelner Lauf ist keine Messung: Am 19. Oktober 2026 haben wir 45 Prompts je dreimal durch Google AI Mode geschickt (135 Antworten), und bei 43 der 45 Prompts unterschied sich die Menge der genannten Marken zwischen identischen Läufen. Das Skript unten erledigt die ganze Arbeit in rund 70 Zeilen Python, für 10 Credits pro Antwort.

Warum ist ein Lauf einer KI-Antwort keine Messung?

Weil derselbe Prompt jedes Mal eine andere Antwort liefert. Über die 135 Antworten hinweg lieferte nur 1 von 45 Prompts drei identische Texte. Bei den Paaren aus Marke und Prompt, in denen eine Marke mindestens einmal auftauchte, fehlte sie in 138 von 302 (46 %) in mindestens einem der drei Läufe. Die Antworten sind lang (Median 396 Wörter) und nennen je etwa fünf der beobachteten Marken, kleine Änderungen im Wortlaut schieben Marken also rein und raus.

Wenn du deine Marke einmal pro Woche prüfst und sie mal auftaucht, mal verschwindet, ist das größtenteils dieses Rauschen und keine Veränderung deiner Sichtbarkeit. Der Beitrag zu AI Mode behandelt das Antwortformat, dieser hier dreht sich darum, was man mit vielen Antworten macht.

Wie wurde gemessen?

Drei Produktkategorien, nach denen Entwickler suchen, je 15 Prompts, so formuliert, wie Leute tippen: „best vector database”, „best vector database for rag”, „best async python web framework”, „web scraping api with a free tier”. Kein Prompt nennt eine Marke. Jeder Prompt ging dreimal an /v1/ai mit country=us und language=en. 135 Aufrufe, 1.350 Credits, durchschnittlich 6,9 Sekunden (zwei Bestätigungsaufrufe an api.fastaf.co am selben Tag brauchten 6,1 und 3,6 Sekunden und meldeten jeweils X-FastAF-Credits-Used: 10). Ein Aufruf lieferte HTTP 200 mit leerer Antwort und wurde damals mit 10 Credits berechnet; nach dieser Messung haben wir die Regel geändert, und eine leere AI-Mode-Antwort wird jetzt erstattet. Als Daten behandelt hätte sie für alle als „keine Marke genannt” gezählt, das Skript wiederholt leere Antworten deshalb weiterhin.

Ich habe pro Kategorie vorab 14 Marken festgelegt. Erwähnungen werden nur im Antworttext gezählt (Absätze, Listeneinträge und Tabellenzellen), nicht in Titeln zitierter Seiten. „Position” ist die Reihenfolge des ersten Auftretens unter den beobachteten Marken. Nichts davon ist Stimmungsanalyse, Empfehlung oder Ranking der Produkte: Es ist, was eine Google-Oberfläche an einem Tag gesagt hat.

Was zeigen die Daten?

Die Erwähnungsrate ist der Anteil der 45 Antworten, die die Marke nennen. Das Intervall ist ein 95-%-Bootstrap-Intervall, das über Prompts neu zieht und damit einschließt, welche 15 Prompts du gewählt hast. Lauf 1 bis 3 sind die Raten, die du aus einem einzelnen Durchgang berichtet hättest.

Kategorie Marke Rate 95-%-Intervall Lauf 1 / 2 / 3
Vektordatenbank Qdrant 96 % 87-100 % 93 / 93 / 100
Pinecone 84 % 71-96 % 80 / 80 / 93
Weaviate 73 % 56-91 % 67 / 73 / 80
Chroma 53 % 31-73 % 60 / 53 / 47
Python-Framework Flask 91 % 82-98 % 80 / 93 / 100
Starlette 49 % 31-67 % 53 / 40 / 53
Litestar 38 % 20-58 % 40 / 27 / 47
Scraping-API Firecrawl 71 % 51-91 % 80 / 67 / 67
ScraperAPI 47 % 29-64 % 53 / 40 / 47
Apify 42 % 24-60 % 33 / 40 / 53

Drei Dinge fallen auf. Erstens schwanken Marken im Mittelfeld einer Kategorie zwischen einzelnen Durchgängen um 10 bis 20 Punkte (Apify 33 % bis 53 %); die mittlere Streuung bei Marken über 20 % lag bei 11 bis 14 Punkten. Zweitens betragen die Intervalle bei 15 Prompts in der Mitte des Wertebereichs plus/minus 20 Punkte, eine Veränderung von „+5 Punkten” von Woche zu Woche ist also unsichtbar. Drittens unterscheiden sich Rankings je nach Durchgang: In der Scraping-Kategorie war ScraperAPI in Durchgang 1 Dritter, und Apify ersetzte Oxylabs in den Top 5 erst in Durchgang 3. Rate und Position sind außerdem verschiedene Dinge: Pinecone wurde früher genannt (durchschnittliche Position 2,2) als Qdrant (2,9), obwohl die Rate niedriger lag.

Zitieren die Antworten die eigenen Seiten der Marken?

Meistens nicht. Von den verschiedenen Hostnamen, die pro Antwort zitiert wurden, gehörte bei Scraping-APIs 44 %, bei Vektordatenbanken 30 % und bei Python-Frameworks 31 % zu einer der beobachteten Marken. Der Rest waren Dritte: reddit.com tauchte in 52 der 135 Antworten auf (39 %), danach github.com, YouTube und Vergleichsblogs. Wer über dich schreibt, zählt genauso viel wie das, was deine eigene Seite sagt.

Auch die Quellen schwanken: Zwei Läufe desselben Prompts teilten im Schnitt 28 bis 34 % ihrer zitierten Quellen (Jaccard-Überlappung), die Frage „welche Seiten zitiert die Antwort” braucht also ebenfalls Wiederholungen.

Wie lasse ich das selbst in Python laufen?

Setz FASTAF_API_KEY, füll PROMPTS mit 15 oder mehr Prompts und BRANDS mit den Namen, die dich interessieren, und starte das Skript. Jeder Aufruf wird an runs.jsonl angehängt, ein Absturz oder erneuter Start überspringt also, was schon erledigt ist.

import json, os, random, re, time
from concurrent.futures import ThreadPoolExecutor

import requests

API = "https://api.fastaf.co/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['FASTAF_API_KEY']}"}
REPEATS = 3
PROMPTS = ["best vector database", "best vector database for rag", "best self hosted vector database"]  # use 15
BRANDS = {  # case-sensitive on purpose: "Falcon" is not "falcon"
    "Qdrant": r"\bQdrant\b", "pgvector": r"\bpgvector\b", "Milvus": r"\bMilvus\b",
    "Pinecone": r"\bPinecone\b", "Weaviate": r"\bWeaviate\b", "Chroma": r"\bChroma(?:DB)?\b",
}

def text_of(node):
    """Every visible string of the answer: paragraphs, list items, table cells."""
    if isinstance(node, str):
        return [node]
    if isinstance(node, list):
        return [s for v in node for s in text_of(v)]
    if isinstance(node, dict):
        return [s for k, v in node.items() if k in ("snippet", "text", "items", "table") for s in text_of(v)]
    return []

def ask(prompt):
    for attempt in range(5):
        r = requests.get(f"{API}/ai", headers=HEADERS, timeout=90,
                         params={"query": prompt, "country": "us", "language": "en"})
        if r.status_code == 200:
            text = "\n".join(text_of(r.json()["text_blocks"]))
            if text:  # an empty 200 is not "no brand mentioned": retry it
                return text
        time.sleep(3 * (attempt + 1))
    raise RuntimeError(f"gave up on {prompt!r}")

def collect(path="runs.jsonl"):
    done = {(r["prompt"], r["run"]) for r in map(json.loads, open(path))} if os.path.exists(path) else set()
    jobs = [(p, n) for p in PROMPTS for n in range(1, REPEATS + 1) if (p, n) not in done]
    def work(job):
        prompt, n = job
        row = {"prompt": prompt, "run": n, "text": ask(prompt)}
        with open(path, "a") as f:  # one line per finished call
            f.write(json.dumps(row) + "\n")
    with ThreadPoolExecutor(max_workers=5) as pool:
        list(pool.map(work, jobs))

def interval(per_prompt, n_boot=4000):
    """95% bootstrap interval resampling PROMPTS, keeping each prompt's runs together."""
    rng, k = random.Random(7), len(per_prompt)
    rates = sorted(sum(rng.choices(per_prompt, k=k)) / k for _ in range(n_boot))
    return rates[int(0.025 * n_boot)], rates[int(0.975 * n_boot)]

def report(path="runs.jsonl"):
    rows = [json.loads(line) for line in open(path)]
    print(f"{'brand':10}{'rate':>6}{'95% CI':>10}{'run1':>6}{'run2':>6}{'run3':>6}")
    table = []
    for brand, rx in BRANDS.items():
        hit = {(r["prompt"], r["run"]): bool(re.search(rx, r["text"])) for r in rows}
        per_prompt = [sum(hit[(p, n)] for n in range(1, REPEATS + 1)) / REPEATS for p in PROMPTS]
        by_run = [sum(hit[(p, n)] for p in PROMPTS) / len(PROMPTS) for n in range(1, REPEATS + 1)]
        table.append((sum(per_prompt) / len(PROMPTS), brand, interval(per_prompt), by_run))
    for rate, brand, (lo, hi), by_run in sorted(table, reverse=True):
        print(f"{brand:10}{rate:>6.0%}{f'{lo:.0%}-{hi:.0%}':>10}" + "".join(f"{x:>6.0%}" for x in by_run))

collect()
report()

Mit der Liste aus drei Prompts oben druckte es am 19. Oktober 2026 pgvector, Weaviate, Qdrant und Milvus mit 100 % in jedem Lauf, Chroma mit 78 % (67 / 67 / 100) und Pinecone mit 67 %.

Welche Zählfehler verändern die Zahlen?

Groß-/Kleinschreibung und Wortgrenzen. Ohne \b passt „Jina” in längere Wörter hinein; ohne Beachtung der Groß-/Kleinschreibung passen „Bottle”, „Falcon” und „Reflex” auf normalen Fließtext, und „scraper API” in Kleinbuchstaben zählt als das Produkt ScraperAPI. In meinem Satz änderte Matching ohne Beachtung der Schreibweise ScraperAPI von 21 auf 22 Antworten und Zyte von 4 auf 5, hier wenig, aber ein kleingeschriebenes „falcon” oder „bottle” trifft man bei einer Marke mit Allerweltsnamen leicht. Füg Aliase hinzu, die du willst („ChromaDB”, „Bright Data” ohne Leerzeichen), und halte sie in einer geprüften Liste. Zähl außerdem nur die Antwort, nicht die zurückgespiegelte Abfrage, sonst misst ein Prompt, der deinen Markennamen enthält, deinen Prompt.

Für AI Overviews funktioniert dieselbe Extraktion am Objekt ai_overview in einer /v1/serp-Antwort, die 5 statt 10 Credits kostet; dieser Beitrag behandelt Präsenz und Quellen pro Keyword.

Ändert GEO, was man messen sollte?

Das GEO-Paper (Aggarwal et al., KDD 2024) berichtete von bis zu 40 % höherer Sichtbarkeit in Antworten generativer Suchmaschinen auf seinem Benchmark GEO-bench, aber das ist ein getesteter Satz von Inhaltsänderungen, keine Garantie für deine Seite. Googles Leitfaden zu generativen KI-Funktionen, zum Zeitpunkt dieser Prüfung zuletzt am 10. Juli 2026 aktualisiert, sagt „The best practices for SEO continue to be relevant” und dass es kein spezielles Markup und keine llms.txt-Datei braucht, um in der Google-Suche zu erscheinen. Die Arbeit ist also weiterhin normale SEO; neu ist nur ein Weg zu erkennen, ob sich etwas bewegt hat, und die Antwort von oben lautet: nur mit Wiederholungen und einem Intervall.

Was kostet das?

45 Prompts x 3 Läufe sind 1.350 Credits, etwa 0,68 $ beim Starter-Paket für 10 $ (20.000 Credits). Eine Kategorie mit 15 Prompts und 3 Wiederholungen sind 450 Credits. Fehlgeschlagene Requests werden nicht berechnet. Siehe Preise und die Docs.

Die Kurzfassung

Zähl Erwähnungen mit einem Regex mit Wortgrenzen unter Beachtung der Schreibweise, lass jeden Prompt mindestens dreimal laufen, und gib die Erwähnungsrate mit einem Intervall an, das über Prompts neu zieht. In unseren 135 Antworten verschwanden 46 % der Marke-Prompt-Auftritte in mindestens einer Wiederholung, und weniger als die Hälfte der zitierten Quellen stammte von den Marken selbst. Starte kostenlos mit 500 Credits und lass das Skript auf deiner eigenen Kategorie laufen.