Jeder Treffer braucht eine Erklärung
Match-Prozentsatz, Citation Gates und der Unterschied zwischen selbstsicher und richtig
Fotografie-KI·Experte·9 Min. Lesezeit · 10. Juli 2026
Das Foto
Zwei Fotos kommen für dieselbe Suche zurück: eines ein exakter Tag-Treffer, der weit über 19 punktet, das andere ein rein semantischer Treffer unter 2. Nebeneinander gezeigt, ohne Kontext, sehen beide einfach wie "ein Ergebnis" aus. Nichts in einer nackten Rangliste sagt einem Besucher, dass eines davon ein nahezu sicherer Treffer und das andere eine lose, recall-getriebene Vermutung ist.
Das Problem
Dieses System hatte zwei getrennte Stellen, an denen "das System sagt X" gegen "ist X tatsächlich wahr" geprüft werden musste, und das war nicht dasselbe Problem. Auf der Suchseite: finalScore ist unbegrenzt und additiv, mischt eine 0–19+-lexikalische Skala mit einer gedeckelten 0–2-semantischen — diese Rohzahl einem Nutzer zu zeigen würde eine Präzision suggerieren, die das System nicht hat. Auf der Assistentenseite: Claude kann in freiem Text fast alles sagen, einschließlich einer plausibel klingenden Beziehung zwischen zwei Fotos, die nichts im Evidenzpaket tatsächlich stützt.
Die Untersuchung
Für die Suche war die Frage, wie man relative Zuversicht kommuniziert, ohne vorzugeben, sie sei absolut. matchPercentage normalisiert jedes Ergebnis gegen das beste Ergebnis in genau dieser Anfrage-Ergebnismenge — max(1, round(finalScore / topFinalScore × 100)) — nicht gegen ein festes oder theoretisches Maximum. Ich habe zunächst erwogen, es als Wahrscheinlichkeit zu rahmen, und die Idee sofort verworfen: Die Skala von finalScore variiert je nach Anfrageform (rein lexikalische Anfragen erzeugen ganz andere Bereiche als hybride), sodass jede feste Skalenabbildung Zahlen erzeugen würde, die präzise aussehen, aber von Anfrage zu Anfrage etwas anderes bedeuten. Das Typsystem erzwingt jetzt die Reihenfolge — rankPhotos() gibt einen Typ zurück, der überhaupt kein matchPercentage-Feld hat, sodass das Lesen davon, bevor computeMatchPercentages() gelaufen ist, ein Kompilierfehler ist, kein Laufzeitfehler, den ich durch Testen hätte finden müssen.
Beim Assistenten ging die Untersuchung tiefer, weil der Fehlermodus adversarial ist, nicht nur kosmetisch. Das Citation Gate (validateCitations()) wurde zuerst gebaut und tut genau eine Sache: Jeder citedPhotoIds-Wert in Claudes Antwort muss im gegebenen Evidenzpaket existieren, oder die gesamte Antwort wird verworfen und durch eine feste Ablehnungsnachricht ersetzt. Das schloss einen Angriff — Claude, das ein Foto referenziert, das ihm nie gezeigt wurde. Es schloss keinen subtileren, gefunden während eines dedizierten Red-Team-Durchgangs (RT-008, 02.07.2026): Claude zitiert nur echte, im Paket enthaltene Fotos, erzählt aber eine Beziehung zwischen ihnen, die der deterministische Evidence-Relationship-Graph nie tatsächlich berechnet hat — "diese wurden mit derselben Kamera aufgenommen", wenn nie eine Kamera-Beziehung abgeleitet wurde. Das Citation Gate prüft, welche Fotos besprochen werden. Es hat keine Meinung dazu, was über sie behauptet wird.
Die Entscheidung
Beim Match-Prozentsatz war die Entscheidung, die Zahl ehrlich zu halten, indem ihr Anspruch eng gefasst wird: Sie beantwortet "wie gut ist das relativ zum besten Treffer in dieser Menge" und nichts weiter. Nach unten gedeckelt bei 1 % für jedes zurückgegebene Ergebnis — nie 0 %, was wie ein Fehler statt eines Treffers mit niedriger Zuversicht wirken würde — und nirgendwo in der UI als Rohwert offengelegt.
Beim Assistenten brauchte das Schließen der engeren Lücke, die das Citation Gate offen ließ, eine zweite, unabhängige strukturelle Prüfung, keine größere Prompt-Anweisung. Claudes Output-Schema bekam ein Feld claims: AssistantClaim[] neben answer/citedPhotoIds, und validateClaims() läuft als reiner Graphvergleich — kein Sprachparsing, kein Regex, kein zweiter Claude-Aufruf — und prüft die photoIds jeder Behauptung gegen genau den Evidence-Relationship-Graph, aus dem das Paket gebaut wurde, bidirektional. Jede ungültige Behauptung verwirft die gesamte Antwort, dasselbe harte Fehlermuster wie beim Citation Gate. Eine dritte, kleinere Korrektur folgte, nachdem das auslieferte: Claude erzählte manchmal dieselbe wahre Beziehung zweimal und gab für jede Erwähnung eine doppelte Behauptung aus — ein Deduplizierungs-Artefakt, kein Grundierungsfehler — also fasst normalizeClaims() exakte Duplikate zusammen, bevor die Validierung läuft, ohne jemals anzutasten, was validateClaims() tatsächlich prüft.
Was ich nicht geschlossen habe, und das sage ich ausdrücklich, statt es zu übertünchen: Eine Beziehung, die nur in freiem Prosatext behauptet wird, ohne passende strukturierte Behauptung, wird immer noch von nichts Deterministischem erfasst. Diese Lücke wird als eigenes Restrisiko geführt (RR-011), statt als gelöst behauptet zu werden — sie vollständig zu schließen würde Sprachparsing brauchen, was diese Phase bewusst ausschließt.
Die Architektur
Suche:
rankPhotos() → RankedPhotoResult (kein matchPercentage-Feld — typdurchgesetzt)
↓
computeMatchPercentages() topFinalScore = Maximum über die Ergebnismenge
↓
matchPercentage = max(1, round(finalScore / topFinalScore × 100))
Assistent:
structuredGenerate() → { answer, citedPhotoIds, claims }
↓
normalizeClaims() dedupliziert exakte Duplikat-Behauptungen
↓
validateClaims() jede Behauptung wird bidirektional gegen
den Evidence-Relationship-Graph geprüft —
jede Abweichung verwirft die gesamte Antwort
↓
validateCitations() jeder citedPhotoIds-Wert muss im
Evidenzpaket existieren, sonst vollständig verwerfen
↓
redactLeakedOutput()
Beide Prüfungen scheitern absichtlich auf dieselbe Weise: vollständige Ablehnung, nie eine stille Teilkorrektur. Eine halb vertrauenswürdige Antwort wird als nicht vertrauenswürdig behandelt.
Was ich gelernt habe
Ich hatte erwartet, dass der Match-Prozentsatz das schwierigere Designproblem sein würde, weil er nutzersichtbar ist und ich befürchtete, jemanden in die Irre zu führen. War er nicht — sobald ich mich weigerte, ihn als Wahrscheinlichkeit lesen zu lassen, folgte der Rest mechanisch. Die Arbeit an Citation Gate/Claim Validator war schwieriger, gerade weil Red-Teaming nach jeder Korrektur eine engere Version desselben Problems fand, und ich musste lernen, mit einer verbliebenen Lücke als offen dokumentiert zu leben, statt das Feature still für fertig zu erklären.
Die tiefere Lektion: "Das System hat die richtigen Quellen zitiert" und "das System hat etwas Wahres gesagt" sind nicht dieselbe Garantie, und ich hatte sie vermischt, bis der Red-Team-Durchgang sie auseinanderzwang. Ein Citation Gate allein hätte mich glauben lassen, der Assistent sei vollständig grundiert, obwohl er es nicht war.
Der Blick nach vorn
Einzelne Behauptungen können jetzt einzeln gegen einen Beziehungsgraphen geprüft werden. Aber dieser Graph selbst — welche Fotos mit welchen zusammenhängen, und in welcher Reihenfolge — ist eine Struktur, die ich noch nicht gebaut hatte, als die Arbeit an diesem Kapitel begann. Behauptungen dagegen zu prüfen bedeutete, dass der Beziehungsgraph zuerst existieren musste, und genau daher kommt die tatsächliche "Erinnerung" in diesem System.