[{"content":"","date":null,"permalink":"https://constantinsblog.eu/de/tags/algorithms/","section":"Tags","summary":"","title":"Algorithms"},{"content":"","date":null,"permalink":"https://constantinsblog.eu/de/tags/computational-geometry/","section":"Tags","summary":"","title":"Computational Geometry"},{"content":"Hi, ich bin Constantin, Softwareentwickler aus Hamburg. In diesem Blog dokumentiere ich, was ich über Softwareentwicklung, Cloud-Entwicklung und KI-unterstützte Entwicklung lerne. Gelegentlich geht es auch um Themen abseits von Code. Ich schreibe diese Notizen für mein zukünftiges Ich und für alle, die sich mit ähnlichen Fragen beschäftigen.\n","date":null,"permalink":"https://constantinsblog.eu/de/","section":"Notizen über Softwareentwicklung, Cloud und KI","summary":"","title":"Notizen über Softwareentwicklung, Cloud und KI"},{"content":"","date":null,"permalink":"https://constantinsblog.eu/de/posts/","section":"Posts","summary":"","title":"Posts"},{"content":"","date":null,"permalink":"https://constantinsblog.eu/de/tags/software-engineering/","section":"Tags","summary":"","title":"Software Engineering"},{"content":"","date":null,"permalink":"https://constantinsblog.eu/de/tags/","section":"Tags","summary":"","title":"Tags"},{"content":" Dieser deutschsprachige Beitrag ist eine KI-unterstützte Übersetzung des englischen Originals. Zwei Strecken schneiden sich entweder oder sie tun es nicht. Zumindest dachte ich, dass ich diese Frage stelle.\nIn Komp-Geom, meiner Kotlin-Multiplatform-Bibliothek für Geometrie, kann ein paarweiser Streckentest einen Kontakt melden, den ein Bentley-Ottmann-Sweep über dieselbe endliche Eingabe übersieht. Das geschieht nahe einer Präzisionsgrenze, wenn Koordinaten um winzige Beträge verändert wurden. Zuerst fiel mir das Problem beim Sweep auf, aber inzwischen sehe ich es nicht mehr als reines Sweep-Line-Problem. Der Sweep macht lediglich die Folgen sichtbar: Wenn zwei numerische Entscheidungen einander widersprechen, kann eine ganze Kette späterer Entscheidungen schiefgehen.\nDie Frage, die ich beantworten möchte, ist allgemeiner. Was bedeutet es für eine Geometriebibliothek, konsistente Entscheidungen über ihre Eingabe zu treffen, und was sollte sie versprechen, wenn ein Ergebnis nahe einer Präzisionsgrenze liegt?\nEine Präzisionsgrenze ist eine Entscheidungsgrenze #Ein Double speichert eine Zahl mit endlicher Genauigkeit. Bei der Rechnung mit solchen Zahlen werden Zwischenergebnisse gerundet. Ein Epsilon ist etwas anderes: eine Regel, nach der eine Anwendung zwei Werte als hinreichend nah beieinander betrachtet. Ein Epsilon misst nicht den Rundungsfehler jedes Ausdrucks. Es kann auch bewusst den Unterschied zwischen verschiedenen Eingabekoordinaten verwischen.\nKomp-Geoms EpsilonDoubleEquivalence betrachtet endliche Werte a und b als näherungsweise gleich, wenn\n|a − b| ≤ ε · max(1, |a|, |b|)\nDas voreingestellte geometrische Epsilon ist 10⁻¹⁰. Nahe null hat die Schwelle einen absoluten Anteil, bei größeren Beträgen skaliert sie mit den Operanden. Operationen können eine andere DoubleEquivalence erhalten. Das ist nützlich, wenn ein lokaler Vergleich die Vorstellung des Aufrufers von Nähe berücksichtigen soll.\nAber Nähe ist keine Identität. Setzen wir ε = 10⁻⁸, a = 0, b = 0,75 × 10⁻⁸ und c = 1,5 × 10⁻⁸. Die ersten beiden Werte liegen nah beieinander, ebenso die letzten beiden, aber der erste und der letzte nicht. Näherungsweise Gleichheit ist nicht transitiv. Sie darf nicht als Identität oder Vergleichsordnung einer sortierten Sammlung dienen. Auch Apache Commons Geometry unterscheidet zwischen näherungsweisem eq() und strengem equals() samt Hashing. Sein Präzisionskontext ist für lokale Operationen mit Toleranzen nützlich. eq() aus der Objektidentität herauszuhalten garantiert allein jedoch noch nicht, dass die Prädikate und konstruierten Koordinaten aller Algorithmen nahe einer Grenze miteinander übereinstimmen.\nNoch wichtiger ist, dass ein Vergleich von Zahlen nicht dasselbe ist wie eine Entscheidung über Geometrie. Ein Orientierungstest fragt, auf welcher Seite einer gerichteten Geraden ein Punkt liegt. Ein Inzidenztest fragt, ob ein Punkt zu einer Kante gehört. Ein Schnitttest fragt, ob zwei Kanten einander treffen. Eine Konstruktion berechnet einen neuen Punkt. Wenn diese Operationen einander widersprechen, kann sich die Zusammenhangsstruktur des Ergebnisses ändern, nicht nur seine letzte Nachkommastelle.\nDas betrifft weit mehr als einen Sweep #Betrachten wir einen Algorithmus zur Berechnung der konvexen Hülle, der drei beinahe kollineare Punkte untersucht. Wechselt das Vorzeichen seines Orientierungstests, kann er einen Punkt behalten, der entfernt werden müsste, oder einen Punkt entfernen, der ein Eckpunkt der Hülle sein müsste. Ein Polygonalgorithmus, der entscheidet, ob ein Punkt innen, auf dem Rand oder außen liegt, muss bei Randfällen miteinander vereinbare Antworten liefern. Bei einer Triangulierung kann ein falsches Vorzeichen im Orientierungs- oder Umkreistest die kombinatorische Struktur verändern. Eine Überlagerung oder ein Arrangement von Polygonen muss übereinstimmend entscheiden, ob zwei Kanten sich an einem Eckpunkt treffen, bevor Flächen zusammengesetzt werden können. Das sind Beispiele für dieselbe allgemeine Abhängigkeit, keine Behauptung, dass all diese Algorithmen in Komp-Geom derzeit fehlschlagen.\nIn anderer Form begegnete mir das bei der Arbeit an meiner Bachelorarbeit über Minkowski-Summen. Wenn nahezu identische berechnete Koordinaten in einer DCEL zu getrennten Eckpunkten wurden, konnten Kanten unverbunden bleiben und Flächen sich nicht schließen. Der daraus entstehende Fehler war topologisch. Koordinaten stärker zu runden half dieser Implementierung in manchen Fällen. Es war aber eine Strategie zur Fehlerbehebung und keine allgemeine Definition geometrischer Korrektheit.\nNicht jede numerische Operation braucht eine exakte Topologie. Für die Anzeige kann ein näherungsweiser Abstand genügen. Strengere Anforderungen entstehen, wenn eine Zahl darüber entscheidet, welche Objekte existieren oder verbunden sind.\nWarum der Sweep die Lücke sichtbar machte #Ein paarweiser Test klassifiziert einen Kontakt mit der konfigurierten Toleranz und berechnet seinen Punkt mit Double-Arithmetik. Der Sweep muss diesen Punkt anschließend in eine Ereigniswarteschlange einordnen. Komp-Geom schert die Koordinaten für die Ereignisreihenfolge und prüft die Inzidenz im ursprünglichen Koordinatensystem. Sein Statusbaum verwendet inzwischen eine stabile, positionsbasierte Ordnung statt einer epsilonabhängigen Vergleichsfunktion. Ereigniskoordinaten und Streckenhöhen werden aber weiterhin mit gerundeten Doubles berechnet, während die Inzidenzprüfungen Toleranzen berücksichtigen.\nDer differenzielle Test zeigt einen Widerspruch entlang dieses Wegs. Ein konstruierter Ereignispunkt könnte eine spätere Prüfung nicht mehr bestehen, die der paarweise Test bestanden hat. Die Abbildung zeigt einen möglichen Mechanismus, keine nachgewiesene Diagnose des beobachteten Falls.\nDie Ansätze definieren unterschiedliche Arten von Geometrie #Es gibt keinen einzelnen Schalter für „mehr Präzision“. Die ersten beiden Ansätze befassen sich mit unterschiedlichen Teilen einer Berechnung und können zusammenwirken. Der dritte verändert die Geometrie bewusst auf eine gewählte Auflösung.\nGefilterte robuste Prädikate: zuverlässig entscheiden #Ein Prädikat beantwortet eine kleine Frage wie „Liegt dieser Punkt links oder rechts von dieser Geraden?“. Wenn drei Punkte fast auf einer Geraden liegen, ist die Antwort schwierig, weil eine Rundung verändern könnte, auf welcher Seite der dritte Punkt zu liegen scheint. Ein gefiltertes Prädikat versucht zuerst die schnelle Berechnung. Ist der mögliche Rundungsfehler zu klein, um die Antwort zu verändern, übernimmt es das Ergebnis. Andernfalls rechnet es genauer, bevor es entscheidet.\nShewchuks adaptive Prädikate nutzen diesen Ansatz unter anderem für Orientierungsfragen. Sie ermitteln die richtige Antwort für die als Eingabe übergebenen Gleitkommazahlen. Sie können uns nicht sagen, ob diese Zahlen ein vermessenes Objekt in der realen Welt vollkommen korrekt beschreiben, und sie berechnen für sich genommen auch keinen exakten Schnittpunkt. Anders als ein Epsilonvergleich erklärt der Filter ein kleines Ergebnis nicht einfach zu null.\nPrädikate und Konstruktionen: erst entscheiden, dann lokalisieren #Zu wissen, dass zwei Strecken sich schneiden, ist das eine. Zu berechnen, wo sie sich schneiden, ist etwas anderes. Das Erste ist eine Entscheidung, also ein Prädikat. Das Zweite ist eine Konstruktion: Sie erzeugt eine neue Koordinate. Die Hinweise von CGAL zur Robustheit behandeln beides als getrennte Aufgaben. Sein Kernel-Design fragt danach, welche Operationen ein Algorithmus benötigt.\nAuch wenn die Eingabeendpunkte Double-Werte sind, kann ihr Schnittpunkt zwischen den Zahlen liegen, die ein Double darstellen kann. Ein näherungsweiser Vec2 reicht oft aus, um einen Punkt auf dem Bildschirm zu zeichnen. Als Identität eines gemeinsamen Eckpunkts oder als Schlüssel für die Reihenfolge späterer Ereignisse ist er nicht unbedingt sicher. Wenn ein Algorithmus weitere Entscheidungen über diesen Punkt trifft, muss er eine Darstellung oder Vergleichsmöglichkeit beibehalten, die mit der früheren Entscheidung konsistent ist.\nGeometrie auf einem festen Raster: eine Auflösung wählen #Nehmen wir an, eine Karte erfasst Orte auf einem Raster mit einem Meter Abstand. Zwei verschiedene Punkte könnten beide auf dieselbe Rasterposition verschoben werden. In der reduzierten Geometrie sind sie nun ein Punkt. Eine schmale Lücke kann sich schließen und eine kurze Linie zusammenfallen. Das ist kein Rundungsfehler, den man verstecken sollte. Es ist eine bewusste Regel dafür, welche Details die Karte bewahrt.\nKorrektes Snap Rounding braucht auch Regeln für die Linien und Formen, die mit diesen Punkten verbunden sind. Der Präzisionsreduzierer von JTS dokumentiert einen Modus, der eine gültige reduzierte Topologie erhält, warnt aber auch davor, dass nahe Polygone verschmelzen können. Ein Raster ist nützlich, wenn eine Anwendung eine sinnvolle Auflösung hat. Es ist jedoch kein direkter Ersatz dafür, zwei Werte als näherungsweise gleich zu betrachten.\nDie Entscheidung für Komp-Geom #Für Komp-Geom würde ich zuerst eine Topologie prüfen, die die endlichen Double-Eingabekoordinaten als exakt betrachtet. Dabei kämen für gewöhnliche Fälle gefilterte Prädikate und bei ungewissem Ergebnis genauere Berechnungen zum Einsatz. Das würde die geometrische Semantik der API verändern, nicht nur ihre numerische Genauigkeit: Ein Beinahe-Treffer, der derzeit aufgrund der Toleranz als Kontakt akzeptiert wird, würde nicht mehr als Schnitt gelten. Ein als Vec2 zurückgegebener Schnittpunkt könnte weiterhin näherungsweise sein. Dieser gerundete Punkt dürfte aber nicht stillschweigend die Ereignisreihenfolge oder Eckpunktidentität bestimmen.\nIch würde mit dem Streckenschnitt und dem Sweep beginnen, um den gesamten Vertrag zu testen. Danach würde ich dieselben Regeln für Prädikate und Konstruktionen auf andere Algorithmen anwenden, bei denen die Topologie wichtig ist. EpsilonDoubleEquivalence würde ich für lokale näherungsweise Vergleiche beibehalten und eine Geometrie auf festem Raster als separates Modell betrachten.\nDer Sweep hat die Lücke sichtbar gemacht, aber das Ziel ist größer: Jede Entscheidung, die die geometrische Topologie verändert, muss einem konsistenten Modell folgen.\n","date":"26 September 2026","permalink":"https://constantinsblog.eu/de/posts/precision-boundaries-komp-geom/","section":"Posts","summary":"Eine Abweichung bei einem Sweep-Line-Algorithmus macht ein grundlegenderes Problem sichtbar: Numerische Entscheidungen bestimmen die Topologie geometrischer Algorithmen. Hier beschreibe ich die Grundlage für präzise Entscheidungen, die ich mir für Komp-Geom wünsche.","title":"Wenn Geometriealgorithmen einander widersprechen: Präzisionsgrenzen in Komp-Geom"},{"content":"","date":null,"permalink":"https://constantinsblog.eu/de/tags/artificial-intelligence/","section":"Tags","summary":"","title":"Artificial Intelligence"},{"content":" Dieser deutschsprachige Beitrag ist eine KI-unterstützte Übersetzung des englischen Originals. Gib /graphify in deinen KI-Coding-Assistenten ein, und er bildet dein gesamtes Projekt (Code, Dokumentation, PDFs, Bilder und Videos) als Wissensgraph ab, den du anstelle einer Dateisuche abfragen kannst.\nDiese Behauptung steht prominent im Graphify-Repository. Sie hat sofort meine Aufmerksamkeit geweckt. grep und Werkzeuge wie rg sind alt, einfach und zuverlässig. Ein Wissensgraph klingt deutlich ausgefeilter, aber Ausgefeiltheit allein macht ein Werkzeug noch nicht nützlich.\nHinter dem Marketingvergleich steckt außerdem eine interessantere Frage. KI-Coding-Agenten erkunden Repositories nicht genau so wie Menschen. Sie führen viele Suchen aus, lesen Dateiausschnitte, verfolgen Symbole über Module hinweg und fügen Ergebnisse wiederholt ihrem Kontext hinzu. Vielleicht kann ein Graph einem Agenten einen besseren Ausgangspunkt geben und einen Teil dieser Arbeit vermeiden.\nIch habe diese Idee am FastAPI-Repository getestet. Ich stellte demselben Coding-Agenten zwölf Fragen, einmal mit Zugriff auf Graphify und einmal ohne. Gemessen habe ich Zeit, Werkzeugnutzung, untersuchte Dateien, Tokens und Kosten.\nWas Graphify tatsächlich erstellt #Graphify ist keine Vektordatenbank. Laut seiner Beschreibung des Extraktionsprozesses erstellt es den Graphen in mehreren Durchläufen:\nTree-sitter extrahiert die Code-Struktur, einschließlich Klassen, Funktionen, Imports und Aufrufen. Audio und Video können lokal transkribiert werden. Dokumentation, wissenschaftliche Arbeiten, Bilder und Transkripte können von einem LLM semantisch extrahiert werden. Die entstehenden Knoten stehen für Dinge wie Dateien, Symbole und Konzepte. Kanten beschreiben Beziehungen wie calls, imports und implements. Graphify kennzeichnet Beziehungen als EXTRACTED, INFERRED oder AMBIGUOUS. Das ist wichtig, weil nicht jede Beziehung im Graphen denselben Beweiswert hat. Anschließend gruppiert der Leiden-Algorithmus dicht verbundene Knoten in Communities.\nDas Projekt erzeugt einen maschinenlesbaren Graphen, einen Bericht und eine interaktive HTML-Visualisierung. Dies ist der Graph aus meinem FastAPI-Checkout:\nVon Graphify aus dem FastAPI-Repository erzeugter Graph Interaktiven Graphen öffnen Die Visualisierung ist beeindruckend, aber in meinem Experiment geht es nicht darum, ob Graphify einen Graphen zeichnen kann. Es geht darum, ob dieser Graph einem Agenten hilft, realistische Fragen zu einem Repository mit weniger Arbeit zu beantworten.\nDie Warnung für große Korpora ist wichtig #Graphifys aktueller Skill warnt, wenn ein Korpus mehr als 500 unterstützte Dateien oder 2.000.000 Wörter enthält, und bittet den Nutzer, den Umfang einzugrenzen. FastAPI überschritt die Dateigrenze, aber ich habe absichtlich mit dem gesamten Repository weitergemacht, um Implementierungscode, Tests und Dokumentation einzubeziehen.\nDas gab Graphify viele Informationen, erhöhte aber auch die Erstellungskosten und bezog Material ein, das für manche Fragen irrelevant war. Die Warnung ist sinnvolles Produktverhalten. Sie schränkt jedoch das Versprechen ein, ein „gesamtes Projekt“ abzubilden. Einen einzigen riesigen Graphen zu bauen ist nicht automatisch besser, als den für die Arbeit relevanten Teil eines Repositories zu indexieren.\nAufbau des Experiments #Graphifys README verwendet einen FastAPI-Graphen als Hauptbeispiel, daher habe ich dasselbe Projekt genutzt. Der Checkout enthielt FastAPI Version 0.141.1 im Commit 50113da. Die exportierten Sitzungen bewahren außerdem die vom Agenten verwendeten Dateien und Zeilenreferenzen.\nIch verwendete:\nGraphify 0.9.55 OpenAIs gpt-5.6-luna für die Coding-Agent-Sitzungen den pi coding agent als Agenten-Harness eine neue Sitzung pro Frage Ich prüfte die Produkt- und Benchmarkbehauptungen des Repositories im Commit c7ec108, der sich als Version 0.9.62 ausweist. Das Experiment verwendete 0.9.55, daher können sich aktuelles Verhalten oder aktuelle Behauptungen unterscheiden.\nIch erzeugte den Graphen mit:\nuv tool install \u0026#39;graphifyy[sql,openai]\u0026#39; graphify extract . --openai graphify cluster-only . --openai Die Graph-Erzeugung verbrauchte 3,8 Millionen Eingabe-Tokens und 349.000 Ausgabe-Tokens. Das gemischte Korpus enthält umfangreiche Dokumentation und andere Assets. Das Ergebnis beschreibt daher die getesteten Optionen und diesen Korpus, nicht das günstigste mögliche code-only Setup.\nDanach verwendete ich zwei Agentenkonfigurationen:\nOhne Graphify: normale Repository-Werkzeuge wie Suche und Dateilesen Mit Graphify: dieselben Werkzeuge plus Graphify-Skill und erzeugter Graph Beide Konfigurationen erhielten dasselbe Modell, dieselbe Frage, dasselbe Antwortformat und denselben Repository-Zustand. Die Behandlung war gewöhnliche Repository-Erkundung im Vergleich zu Repository-Erkundung mit Graphify-Skill, erzeugtem Graphen und Graphify-Abfragewerkzeugen. Der Vergleich misst daher den praktischen Effekt, Graphify zum Agenten hinzuzufügen, statt den Graphen von Skill-Anweisungen und Werkzeugen zu isolieren.\nFragen #Ich teilte die Fragen in drei Gruppen ein. Graphify-spezifische Begriffe vermied ich, damit die Prompts Fragen ähneln, die beim Einstieg in ein unbekanntes Projekt gestellt werden.\nEinfache Navigation #Diese Fragen testen, ob Graphify bei Aufgaben, die eine Quellsuche bereits gut erledigt, unnötigen Aufwand erzeugt.\nQ1: Wo ist die Klasse FastAPI definiert? Q2: Wo befindet sich die Logik für den Anwendungsstart? Q3: Welche Datei definiert die relevante Konfigurationsoption? Q4: Wo werden HTTP-Exceptions in Antworten umgewandelt? Q3 ist nicht präzise genug. Ich behielt die Frage als Fallstudie für Mehrdeutigkeit bei, schloss sie aber aus den Gesamtsummen und den Mittelwerten nach Fragetyp aus. Ihr Ergebnis bleibt zur Kontrolle in den Rohdaten. In einem künftigen Durchlauf würde ich sie durch eine konkrete Konfigurationsoption ersetzen.\nBeziehungsfragen #Diese Fragen erfordern, Beziehungen über die Codebasis hinweg zu verfolgen.\nQ5: Zeichne den Weg von einer eingehenden Anfrage bis zu einem Route-Handler nach. Q6: Wie werden Abhängigkeiten aufgelöst, bevor eine Route aufgerufen wird? Q7: Wie arbeitet Middleware mit der Exception-Behandlung zusammen? Q8: Wie ist eine registrierte Route mit der OpenAPI-Schemaerzeugung verbunden? Fragen zu Änderungsfolgen #Hier soll der Agent abschätzen, welche Teile des Repositories von einer Änderung betroffen wären.\nQ9: Welche Dateien müssten geändert werden, um eine neue Middleware-Konfigurationsoption hinzuzufügen? Q10: Wo würdest du den Code ändern, um die Fehlerbehandlung von Abhängigkeiten zu ändern? Q11: Welche Teile des Projekts wären von einer Änderung der Routenregistrierung betroffen? Q12: Welche Tests sollten bei einer Änderung der OpenAPI-Erzeugung aktualisiert werden? Für diese Fragen gibt es keine einzige definitive Dateiliste. Verschiedene Implementierungen können gültig sein. Daher behandle ich sie als Erkundungsaufgaben und nicht als Fragen mit einer einfachen Musterlösung.\nPrompt #Jede Sitzung erhielt diesen Prompt:\nI am new to the FastAPI project and have a Question: [Question] Provide: 1. A concise answer to the question. 2. The reasoning path you followed. 3. Relevant files, symbols, and line numbers. 4. Any uncertainty or assumptions. Was ich gemessen habe #Für jede Sitzung erfasste ich:\nZeit: verstrichene Zeit bis zur ersten brauchbaren Antwort. Ich zählte die erste Antwort, die die Frage beantwortete und mindestens eine überprüfbare Datei- oder Symbolreferenz enthielt. Vollständige Korrektheit war keine Voraussetzung, da ich die Korrektheit nicht unabhängig bewertet habe. Dateien: eindeutige untersuchte Repository-Dateien Werkzeugaufrufe: alle Werkzeugaufrufe Graph-Aufrufe: Aufrufe, die Graphify abfragten Gesamteingabe: nicht gecachte Eingabe sowie Prompt-Cache-Lese- und Schreibvorgänge über alle Modellaufrufe Ausgabe: erzeugte Ausgabe-Tokens Beobachtete Kosten: vom Provider geschätzte Kosten in US-Dollar einschließlich des während der Sitzung aufgezeichneten Cache-Verhaltens Gesamteingabe ist nicht die Zahl der von der Festplatte gelesenen Quellcode-Tokens. Es handelt sich um die verarbeitete Eingabe der gesamten Agentensitzung. Wiederholter Konversationskontext, Werkzeugergebnisse und Graphify-Ausgabe tragen alle dazu bei. Die exportierten Sitzungsdaten trennen Skill-Anweisungen, Graph-Abfrageausgabe, Quellwerkzeugausgabe und wiederholten Kontext nicht. Daher kann ich nicht feststellen, welche Komponente den Unterschied zwischen den Konfigurationen verursacht hat.\nErgebnisse #Gesamtergebnis #Über die elf einbezogenen Fragen hinweg ergaben sich folgende Summen:\nMetrik Ohne Mit Änderung Zeit 468 s 511 s +9,2 % Dateien 89 76 -14,6 % Tools 175 160 -8,6 % Input 1.974.282 2.951.731 +49,5 % Output 27.638 27.204 -1,6 % Kosten $0,1687 $0,1847 +9,5 % Graphify verringerte die Zahl der untersuchten Dateien in acht einbezogenen Fragen, mit einem Gleichstand und zwei Fällen mit mehr Dateien. Die Zahl der Werkzeugaufrufe sank in sieben Fragen und stieg in vier. Graphify war in vier Fragen schneller und in sieben langsamer, verbrauchte in keiner Frage weniger Eingabe und hatte in zwei Fällen niedrigere beobachtete Kosten.\nDas ist das zentrale Ergebnis. Graphify gab dem Agenten meist eine kleinere Menge von Quelldateien, aber Abfragen und Verarbeitung des Graphen erzeugten genug zusätzlichen Kontext und Aufwand, um die erwartete Tokenersparnis aufzuheben. Bei den gemessenen Effizienzergebnissen schnitt Graphify insgesamt schlechter ab. Da ich die Antwortqualität nicht bewertet habe, kann das Experiment nicht zeigen, ob die zusätzlichen Kosten bessere oder vollständigere Antworten erkauft haben.\nDateien und Werkzeugaufrufe sind außerdem nur Proxy-Metriken, keine Nutzerergebnisse. Eine Graph-Abfrage kann mehr Text zurückgeben als mehrere gezielte Suchen, und weniger untersuchte Dateien helfen nicht, wenn die Antwort langsamer, teurer oder weniger vollständig ist. Dieses Experiment misst Erkundungsaufwand, nicht Antwortkorrektheit. Bei Zeit, Eingabe und beobachteten Kosten schnitt Graphify insgesamt schlechter ab. Ob der zusätzliche Aufwand bessere Antworten erzeugte, kann ich nicht sagen.\nDie Ausgabe-Totale waren nahezu identisch. Der große Unterschied liegt auf der Eingabeseite, wo die Graphify-Konfiguration über die elf einbezogenen Sitzungen fast eine Million zusätzliche Tokens verarbeitete.\nErgebnisse nach Fragetyp #Mittelwerte zeigen ein nützlicheres Muster als die Gesamtsumme:\nTyp Setup Zeit Dateien Tools Input Output Kosten Navigation Ohne 26,0 s 5,3 11,7 97.772 1.910 $0,0103 Navigation Mit 39,0 s 4,0 11,7 195.548 1.550 $0,0122 Beziehungen Ohne 35,0 s 5,3 13,8 181.467 2.647 $0,0156 Beziehungen Mit 52,8 s 4,5 18,3 295.205 2.885 $0,0180 Änderungen Ohne 62,5 s 13,0 21,3 238.775 2.830 $0,0189 Änderungen Mit 45,8 s 11,5 13,0 296.067 2.754 $0,0191 Einfache Navigation brauchte keinen Graphen #Bei den drei einbezogenen Fragen zur einfachen Navigation untersuchte Graphify durchschnittlich 1,3 Dateien weniger, brauchte aber 13 Sekunden länger und verarbeitete 100 % mehr Eingabe. class FastAPI zu finden ist genau die Art von Aufgabe, bei der eine exakte Quellsuche schwer zu schlagen ist.\nQ1 ist repräsentativ. Der Graphify-Agent fragte den Graphen ab, suchte nach der exakten Deklaration, las fastapi/applications.py und prüfte den öffentlichen Re-Export in fastapi/__init__.py. Der Graph zeigte den relevanten Bereich, aber der Agent benötigte weiterhin gezielte Quellsuchen und Dateilesen, um Antwort und Zeilennummern zu überprüfen.\nDas ist kein Versagen des Graphen, sondern eine fehlende Passung zwischen Werkzeug und Aufgabe. Eine Symbollsuche hat bereits ein ausgezeichnetes Signal-Rausch-Verhältnis.\nBeziehungsfragen waren die schwächste Kategorie #Ich hatte erwartet, dass Graphify bei Beziehungsfragen am besten abschneidet. Stattdessen zeigte diese Kategorie die größte Verlangsamung. Die durchschnittliche Zeit stieg von 35,0 auf 52,8 Sekunden, die Werkzeugaufrufe von 13,8 auf 18,3 und die Eingabe um 62,7 %.\nBeide Konfigurationen fanden im Allgemeinen dieselben zentralen Implementierungspfade. Zum Beispiel verfolgten beide Antworten auf Q5 die Anfrage durch Anwendung, Middleware, Router, Abhängigkeitsauflösung, Endpunktaufruf und Antwortserialisierung. Beide verwiesen auf fastapi/applications.py, fastapi/routing.py und fastapi/dependencies/utils.py.\nDer Graph machte die Quellinspektion nicht überflüssig. In jeder Graphify-Sitzung fragte der Agent zuerst Graphify ab und nutzte anschließend Suchen und Dateilesen, um die zurückgegebenen Beziehungen zu bestätigen. Diese Überprüfung ist besonders bei abgeleiteten Kanten sinnvoll, bedeutet aber, dass der Graph eine zusätzliche Navigationsebene und keinen Ersatz für die Quellsuche darstellt.\nBreite Fragen zu Änderungsfolgen waren vielversprechender #Die Kategorie der Änderungsfolgen zeigt ein anderes Bild. Graphify senkte die durchschnittliche Zeit von 62,5 auf 45,8 Sekunden und die Werkzeugaufrufe von 21,3 auf 13,0. Auch die durchschnittliche Zahl untersuchter Dateien sank von 13,0 auf 11,5.\nQ12 zeigte die deutlichste Verbesserung. Die Sitzung ohne Graphify dauerte bei der Suche in der Testsuite 101 Sekunden und 29 Werkzeugaufrufe. Die Graphify-Sitzung dauerte 47 Sekunden und 15 Werkzeugaufrufe. Beide untersuchten weiterhin viele Dateien, aber der Graph scheint eine nützliche erste Karte der OpenAPI-bezogenen Tests geliefert zu haben.\nDiese Kategorie enthält nur vier Einzelmessungen, und Q12 ist ein auffällig langsamer Lauf ohne Graphify. Ohne Wiederholungen oder eine Bewertung der Antwortqualität kann ich nicht feststellen, wie viel des scheinbaren Gewinns vom Graphen und wie viel von der normalen Agentenvarianz stammt. Der Zielkonflikt bleibt außerdem bestehen. Fragen zu Änderungsfolgen verbrauchten mit Graphify 24,0 % mehr Eingabe, während die durchschnittlichen Kosten nahezu identisch waren. Ich sehe dies als Ansatzpunkt für ein besseres Experiment, nicht als Beleg dafür, dass Graphify Änderungsanalysen verbessert.\nVergleich mit Graphifys Benchmarks #Graphify veröffentlicht zwei relevante Benchmarks.\nTokenreduktion #Das How-it-works-Dokument berichtet:\nKorpus Dateien Reduktion Karpathy-Repositories, Papers und Bilder 52 71,5-fach Graphify-Quellcode und Transformer-Paper 4 5,4-fach Synthetische httpx-Bibliothek 6 ca. 1-fach Auf den ersten Blick scheinen 71,5-mal weniger Tokens nicht zu meinem Anstieg von 49,5 % zu passen. Die Messungen verwenden unterschiedliche Ausgangswerte. Graphifys benchmark.py vergleicht eine Schätzung des gesamten Korpus mit einer Schätzung einer begrenzten Graph-Abfrage. Meine Baseline verwendete rg, find und gezieltes Lesen, während meine Messung auch Konversationsverlauf, Werkzeugergebnisse und jeden Modellaufruf umfasst.\nGraphkompression kann also real sein, während die Eingabe eines Agenten von Anfang bis Ende steigt. Ein Retrieval-Werkzeug sollte mit der gezielten Suche und dem gezielten Lesen verglichen werden, die ein fähiger Coding-Agent bereits beherrscht, nicht nur damit, ein ganzes Repository in den Kontext zu legen.\nCode Intelligence #Graphifys veröffentlichter Benchmarkbericht enthält einen näheren Vergleich. Bei sechs bewerteten Fragen über das ERPNext-Repository mit ungefähr einer Million Zeilen verbesserte ein fester Coding-Agent laut Bericht die Abdeckung zentraler Fakten von 70,8 % mit grep- und Lesewerkzeugen auf 82,0 %, wenn ihm ein Graphify-Werkzeug gegeben wurde. Der Bericht nennt ungefähr 140.000 Tokens pro Abfrage.\nDie Bewertungsmethode ist besser als meine, weil Antworten gegen atomare Schlüsselfakten geprüft werden. Trotzdem handelt es sich um ein selbst veröffentlichtes Ergebnis aus Graphifys eigener Harness mit nur sechs Fragen und aggregierten Ergebnissen. Verwendet werden ein anderes Modell, Repository und eine andere Graphify-Implementierung, während mein Experiment den Aufwand von Anfang bis Ende statt die Abdeckung von Schlüsselfakten misst.\nDie Ergebnisse widersprechen sich nicht, aber keines von beiden zeigt eine breite Nützlichkeit. Zusammen legen sie eine engere Hypothese nahe, die sich zu testen lohnt: Graphifys Wert könnte mit der Größe des Repositories und der Breite der Fragen steigen, während sein Aufwand bei einfacher Navigation, die gewöhnliche Suche bereits erledigt, dominiert.\nWas ich schlussfolgern kann und was nicht #Durch die Messungen gestützt #Für die elf einbezogenen FastAPI-Fragen und diese Konfiguration gilt:\nGraphify verringerte die Zahl der untersuchten Dateien in acht Fragen, mit einem Gleichstand. Graphify verringerte die Zahl der Werkzeugaufrufe in sieben Fragen. Die Gesamteingabe sank in keiner Frage. Insgesamt war Graphify langsamer. Die beobachteten Kosten waren insgesamt 9,5 % höher, noch ohne die Graph-Erstellung. Am meisten half es bei der breiten Erkundung von Tests und Änderungsfolgen. Der Agent verwendete nach der Graph-Abfrage weiterhin Quellsuche und Dateilesen. Durch die Messungen nicht gestützt #Ich kann nicht schlussfolgern, dass:\nGraphify korrektere Antworten erzeugte. die Antworten ohne Graphify korrekter waren. ein Durchlauf pro Frage die normale Modellvarianz abbildet. sich das Ergebnis auf jedes Repository, Modell oder jede Graphify-Version übertragen lässt. Der fehlende Korrektheits-Benchmark ist die größte Einschränkung. Ein künftiges Experiment sollte atomare erwartete Fakten definieren, bevor die Sitzungen ausgeführt werden. Für Aufgaben zu Änderungsfolgen könnten historische Pull Requests als Referenz dienen, indem vorhergesagte Dateien mit der tatsächlich gemergten Änderung verglichen werden.\nWeitere Einschränkungen #Ein Durchlauf pro Frage und Konfiguration #Das Modellverhalten ist stochastisch. Jede Frage sollte mehrmals ausgeführt und die Reihenfolge der Läufe randomisiert werden, um Werkzeugeffekte von gewöhnlicher Schwankung zu trennen.\nCache-Verhalten #Die Sitzungsexporte enthalten Prompt-Cache-Lese- und Schreibvorgänge. Gecachte Eingabe kann anders berechnet werden als nicht gecachte Eingabe. Daher sollten sich Gesamteingabe und Geldkosten nicht proportional verändern. Der zusätzliche Graphify-Skill verändert außerdem den Prompt-Präfix und kann die Cache-Effektivität beeinflussen.\nDie beobachteten Kosten verwenden die während jeder Sitzung aufgezeichneten Provider-Schätzungen einschließlich des aufgetretenen Cache-Verhaltens. Da ich Ausführungsreihenfolge, Aufwärmen und Provider-Cache-Zustand nicht kontrolliert habe, beschreibt der Kostenvergleich diese Läufe und keinen stabilen Preisvergleich ohne Cache. Ich habe alle Eingabekategorien zu Gesamteingabe zusammengefasst, anstatt einen kontrollierten Cache-Vergleich zu behaupten.\nDie Fragen unterscheiden sich in ihrer Präzision #Q3 ist zu vage, und die Fragen zu Änderungsfolgen erlauben mehrere gültige Interpretationen. Eine künftige Version sollte konkrete Optionen und erwartetes Verhalten benennen.\nDer Aufwand für die Erstellung wurde nicht amortisiert #Die Tabellen pro Frage schließen die 3,8 Millionen Eingabe- und 349.000 Ausgabe-Tokens aus, die für die Graph-Erzeugung verwendet wurden. Selbst ohne den Kosten der Erstellung einen Geldwert zuzuweisen, hatten die Graphify-Sitzungen insgesamt höhere beobachtete Kosten. Wiederverwendung könnte die Erstellungskosten amortisieren, würde aber die in diesen Läufen beobachtete Prämie pro Abfrage nicht beseitigen.\nAuch die Aktualität des Graphen verursacht operativen Aufwand. Aktualisierungen nach Pulls und Merges, Verantwortung in CI und erzeugte Artefakte könnten in einem großen Team wichtig sein, aber ich habe diese Belastung nicht gemessen. Sie bleibt ein weiterer unbewiesener Teil des Wertversprechens für große Repositories.\nWann ich Graphify verwenden würde #Auf Grundlage dieses Experiments und des veröffentlichten Designs von Graphify würde ich es nur dann weiter evaluieren, wenn:\ndas Repository sehr groß und unbekannt ist, Fragen viele Pakete oder Schichten überschreiten, ich wiederholt Änderungsfolgen analysieren muss, Code, Architektur-Dokumente, wissenschaftliche Arbeiten und andere Medien gemeinsam durchsucht werden sollen, derselbe Graph oft genug wiederverwendet wird, um die Erstellung zu amortisieren, ich Beziehungen verfolgen muss, die sich nur schwer als lexikalische Suche ausdrücken lassen. Ich würde wahrscheinlich darauf verzichten, wenn:\nich eine bekannte Klasse, Funktion oder einen bekannten Konfigurationsnamen finden muss, das Repository klein genug für gezielte Suche und Dateilesen ist, ich nur eine oder zwei Fragen habe, der Graph schnell veralten würde, ich nicht rechtfertigen kann, Dokumentation oder andere Assets an ein semantisches Extraktions-Backend zu senden. Ein praktischer hybrider Workflow #Das Experiment legt nahe, dass „Graphify statt grep“ das falsche mentale Modell ist. Ein sicherer Workflow sieht so aus:\nFrage den Graphen nach möglichen Symbolen, Dateien und Pfaden ab. Bevorzuge nach Möglichkeit EXTRACTED-Beziehungen gegenüber abgeleiteten Beziehungen. Überprüfe wichtige Graphbeziehungen im Quellcode. Verwende eine exakte Suche für Deklarationen und Referenzen. Begrenze den Graphen für fokussierte Arbeit auf relevante Verzeichnisse. Behandle den Graphen als Index, nicht als letzte Instanz. So verhielt sich auch der Graphify-Agent. Der Graph gab Orientierung, während Quelltextlektüre Belege lieferte.\nFazit: eine nützliche Karte, aber kein grep-Ersatz #Graphify ist mehr als ein schicker HTML-Graphgenerator. Es erstellt einen abfragbaren strukturellen Index und zeigt Beziehungen, die sich lexikalisch nur schwer entdecken lassen. Diese Fähigkeit ist real. Ihre Nützlichkeit für einen Coding-Agenten folgt jedoch nicht allein aus ihrer Existenz.\nIn meinem Experiment ersetzte Graphify grep nicht. In jeder Graphify-Sitzung fragte der Agent den Graphen ab und verwendete danach Quellsuchen oder Dateilesen zur Überprüfung. Bei einfacher Navigation und sogar bei mehreren Fragen über mehrere Dateien hinweg war der Graph meist ein zusätzlicher Retrieval-Schritt vor der Quelltextprüfung. Das gemessene Ergebnis waren mehr Eingabe und höhere Latenz. Ob sich die Antwortqualität genug verbesserte, um beides zu rechtfertigen, habe ich nicht gemessen.\nAuch die Vorlaufkosten sind wichtig. Ich verbrauchte 3,8 Millionen Eingabe-Tokens und 349.000 Ausgabe-Tokens für die Erstellung des Graphen. Danach verarbeiteten die Graphify-Sitzungen 49,5 % mehr Eingabe als die Baseline und hatten in neun der elf einbezogenen Fragen höhere beobachtete Kosten. Bei den gemessenen Effizienzergebnissen zahlte sich diese Investition nicht aus. Mehr Wiederverwendung würde die Erstellungskosten verteilen, aber die beobachteten Kosten pro Abfrage waren in diesen Läufen ebenfalls höher. Wiederverwendung allein behebt die gezeigte Wirtschaftlichkeit daher nicht. Eine unabhängige Qualitätsbewertung könnte dieses Urteil ändern, wenn sie zeigte, dass die Prämie zuverlässig bessere Antworten erkauft.\nMein aktuelles Urteil ist skeptischer als „nützlich für große Repositories“. Auf Grundlage der Effizienzbelege aus diesem Experiment würde ich Graphify nicht übernehmen. Ich würde eine stärkere Bewertung an einer großen, langlebigen Codebasis mit wiederholter Arbeit über mehrere Bereiche hinweg, unabhängig bewerteten Antworten und historischen Änderungen als Ground Truth durchführen. Solange ein solcher Test keine besseren Antworten oder verlässliche Zeitersparnis zeigt, bleibt gezielte Quellsuche die einfachere und besser belegte Standardeinstellung.\nAm wichtigsten ist, dass dieses Experiment die Frage verändert hat, die ich an jedes KI-Werkzeug für Code-Intelligenz stellen würde. „Komprimiert es das Repository?“ reicht nicht. Die nützliche Frage lautet: Hilft es einem Agenten, schneller und günstiger zu einer gut belegten Antwort zu gelangen als eine gezielte Suche im Quellcode?\nFür dieses FastAPI-Experiment lautet die Antwort noch nicht überzeugend ja.\nRohdaten #Die einzelnen Messungen und vollständigen exportierten Sitzungen sind unten enthalten, damit der Vergleich geprüft und reproduziert werden kann.\nOhne Graphify # Q Zeit Dateien Tools Graph Input Output Kosten Rohdaten Q1 23 2 8 0 50.483 881 $0,0059 Sitzung Q2 28 8 12 0 86.642 2.610 $0,0117 Sitzung Q3 (ausgeschlossen) 42 6 14 0 104.745 1.921 $0,0107 Sitzung Q4 27 6 15 0 156.190 2.239 $0,0132 Sitzung Q5 32 4 10 0 125.938 2.987 $0,0154 Sitzung Q6 37 4 14 0 197.980 2.289 $0,0163 Sitzung Q7 36 8 19 0 218.382 2.650 $0,0163 Sitzung Q8 35 5 12 0 183.568 2.662 $0,0144 Sitzung Q9 60 17 17 0 218.802 3.013 $0,0167 Sitzung Q10 46 7 21 0 178.837 2.165 $0,0149 Sitzung Q11 43 8 18 0 319.530 3.319 $0,0262 Sitzung Q12 101 20 29 0 237.930 2.823 $0,0177 Sitzung Mit Graphify # Q Zeit Dateien Tools Graph Input Output Kosten Rohdaten Q1 52 2 7 3 122.192 1.141 $0,0081 Sitzung Q2 19 5 9 3 110.969 988 $0,0083 Sitzung Q3 (ausgeschlossen) 41 6 12 3 147.471 1.807 $0,0126 Sitzung Q4 46 5 19 4 353.484 2.520 $0,0203 Sitzung Q5 61 3 20 3 314.420 3.192 $0,0191 Sitzung Q6 41 5 17 3 281.416 2.545 $0,0169 Sitzung Q7 64 7 15 3 323.928 2.882 $0,0172 Sitzung Q8 45 3 21 3 261.055 2.920 $0,0186 Sitzung Q9 41 12 11 3 312.040 2.646 $0,0180 Sitzung Q10 45 8 12 3 294.761 2.035 $0,0161 Sitzung Q11 50 7 14 3 337.304 3.281 $0,0229 Sitzung Q12 47 19 15 3 240.162 3.054 $0,0192 Sitzung ","date":"15 September 2026","permalink":"https://constantinsblog.eu/de/posts/graphify-llm-query-tool/","section":"Posts","summary":"Ein gemessener Vergleich von Graphify und gewöhnlicher Quellsuche anhand von zwölf Fragen zu einem FastAPI-Repository, einschließlich Zeit, Tokens, Kosten und der Frage, ob der Wissensgraph seinen zusätzlichen Aufwand rechtfertigt.","title":"Graphify vs. grep: Hilft ein Wissensgraph einem KI-Coding-Agenten?"},{"content":"","date":null,"permalink":"https://constantinsblog.eu/de/tags/cloud/","section":"Tags","summary":"","title":"Cloud"},{"content":"Verantwortlicher #Constantin Ponfick\nc/o Impressumservice Dein-Impressum\nStettiner Str. 41\n35410 Hungen\nDeutschland\nE-Mail: info@constantinsblog.eu Telefon: 0157 9234 1658 Hosting und Zugriffsdaten #Diese Website wird als statische Website über GitHub Pages veröffentlicht. GitHub gibt ausdrücklich an, dass beim Besuch einer GitHub-Pages-Website die IP-Adresse unabhängig davon protokolliert und zu Sicherheitszwecken gespeichert wird, ob die besuchende Person bei GitHub angemeldet ist. Darüber hinaus können technisch notwendige Verbindungs- und Nutzungsdaten verarbeitet werden, insbesondere Datum und Uhrzeit des Abrufs, angeforderte URL, Referrer, Informationen über das Gerät und den Browser sowie Daten zur Nutzung der Website.\nDie Verarbeitung dient der Bereitstellung, Stabilität und Sicherheit der Website. Rechtsgrundlage für die Verarbeitung durch mich ist Art. 6 Abs. 1 lit. f DSGVO; mein berechtigtes Interesse liegt im sicheren und zuverlässigen Betrieb der Website. GitHub verarbeitet die Daten nach eigenen Angaben als Verantwortlicher für seine Dienste. Eine konkrete Speicherdauer für die GitHub-Pages-Zugriffsprotokolle wird in der für GitHub Pages einschlägigen Dokumentation nicht genannt. Einzelheiten finden Sie in der GitHub-Datenschutzerklärung und in der Dokumentation zu GitHub Pages.\nCookies, lokale Speicherung und Analyse #Diese Website verwendet keine Analyse- oder Werbedienste und setzt keine nicht notwendigen Cookies ein. Die Einstellung für die Darstellung (z. B. helles oder dunkles Erscheinungsbild) kann lokal im Browser gespeichert werden. Dabei werden keine Daten an mich übertragen.\nKontaktaufnahme per E-Mail oder Telefon #Wenn Sie mich per E-Mail oder Telefon kontaktieren, verarbeite ich die von Ihnen übermittelten Angaben ausschließlich zur Bearbeitung Ihrer Anfrage und für die damit verbundene Kommunikation. Rechtsgrundlage ist Art. 6 Abs. 1 lit. f DSGVO; sofern die Anfrage auf den Abschluss oder die Durchführung eines Vertrags gerichtet ist, zusätzlich Art. 6 Abs. 1 lit. b DSGVO.\nIch lösche die Anfrage, sobald sie für den Zweck der Kommunikation nicht mehr erforderlich ist, sofern keine gesetzlichen Aufbewahrungspflichten entgegenstehen.\nExterne Links #Auf dieser Website befinden sich Links zu externen Websites, insbesondere zu sozialen Netzwerken. Beim Anklicken eines solchen Links verlassen Sie diese Website. Für die Datenverarbeitung auf der Zielseite ist ausschließlich deren Betreiber verantwortlich.\nIhre Rechte #Sie haben im Rahmen der gesetzlichen Voraussetzungen das Recht auf Auskunft, Berichtigung, Löschung, Einschränkung der Verarbeitung, Datenübertragbarkeit und Widerspruch gegen die Verarbeitung. Eine erteilte Einwilligung können Sie jederzeit mit Wirkung für die Zukunft widerrufen.\nSie haben außerdem das Recht, sich bei einer Datenschutzaufsichtsbehörde zu beschweren. Zuständig ist insbesondere die Aufsichtsbehörde an Ihrem gewöhnlichen Aufenthaltsort, an Ihrem Arbeitsplatz oder am Ort des mutmaßlichen Verstoßes.\nStand: September 2026\n","date":null,"permalink":"https://constantinsblog.eu/de/privacy-policy/","section":"Notizen über Softwareentwicklung, Cloud und KI","summary":"","title":"Datenschutzerklärung"},{"content":"Angaben gemäß § 5 DDG #Constantin Ponfick\nc/o Impressumservice Dein-Impressum\nStettiner Str. 41\n35410 Hungen\nDeutschland\nKontakt # Telefon: 0157 9234 1658 E-Mail: info@constantinsblog.eu Inhaltlich Verantwortlicher gemäß § 18 Abs. 2 MStV #Constantin Ponfick\nc/o Impressumservice Dein-Impressum\nStettiner Str. 41\n35410 Hungen\nDeutschland\nVerbraucherschlichtung gemäß § 36 VSBG #Ich bin weder bereit noch verpflichtet, an Streitbeilegungsverfahren vor einer Verbraucherschlichtungsstelle teilzunehmen.\nDatenschutz #Informationen zum Umgang mit personenbezogenen Daten finden Sie in der Datenschutzerklärung.\nStand: September 2026\n","date":null,"permalink":"https://constantinsblog.eu/de/imprint/","section":"Notizen über Softwareentwicklung, Cloud und KI","summary":"","title":"Impressum"},{"content":" Dieser deutschsprachige Beitrag ist eine KI-unterstützte Übersetzung des englischen Originals. Ich wollte diesen Blog veröffentlichen, ohne einen Server zu betreiben. Gleichzeitig wollte ich das Quellrepository nicht öffentlich machen. Das Repository enthält mehr als die fertige Website. Dazu gehören die Historie des Blogs, unfertige Artikel, die Konfiguration und möglicherweise persönliche Notizen, die ich später ergänze.\nHugo schafft bereits eine nützliche Grenze für dieses Problem. Der Quelltext bleibt an einem Ort, während das Verzeichnis public/ die statischen Dateien enthält, die Besucher benötigen. Bei meinem Deployment bleibt der Quelltext in einem privaten Repository. Nur das generierte Verzeichnis wird über ein zweites, öffentliches Repository veröffentlicht.\nDas Ergebnis ist eine öffentliche Website mit privatem Quellmaterial und ohne zusätzlichen Server, den ich betreiben muss. Die Website selbst wird dadurch nicht privat. Alles, was Hugo in die generierte Ausgabe aufnimmt, bleibt öffentlich.\nDieser Beitrag erklärt, wie sich dieses Setup mit GitHub Actions, einem SSH-Deploy-Key und GitHub Pages nachbauen lässt.\nDas Setup mit zwei Repositories #Die Repositories haben unterschiedliche Aufgaben:\nRepository Sichtbarkeit Inhalt blog Privat Hugo-Inhalte, Konfiguration, Skripte und der GitHub-Actions-Workflow gh-pages-blog Öffentlich Generierte Dateien aus Hugos public/-Verzeichnis Das Deployment folgt einem festen Weg vom Quelltext bis zur Website:\nflowchart LR source[Privates Blog-Repository] --\u003e|Push auf main| actions[GitHub Actions] actions --\u003e|bauen und prüfen| output[Hugo-public-Verzeichnis] actions --\u003e|SSH-Push| repository[Öffentliches Deployment-Repository] repository --\u003e|main aus dem Wurzelverzeichnis veröffentlichen| pages[GitHub Pages] pages --\u003e domain[constantinsblog.eu] Ich behandle das öffentliche Repository als Artefakt-Repository. Dort schreibe ich keine Beiträge und bearbeite kein generiertes HTML. Falls das Repository verloren ginge, könnte ich seinen Inhalt wiederherstellen, indem ich das private Quellrepository erneut baue.\nFür diesen Ansatz werden folgende Dinge benötigt:\nEin privates Repository mit der Hugo-Website Ein öffentliches Repository für die generierte Website Ein main-Branch im öffentlichen Repository Das öffentliche Repository kann mit einem ersten Commit beginnen. Beim ersten Deployment wird sein Inhalt durch die generierte Website ersetzt.\nDem Workflow Zugriff auf das öffentliche Repository geben #Der Workflow läuft im privaten Repository, benötigt aber Schreibzugriff auf das öffentliche Repository. Dafür verwende ich einen eigenen SSH-Deploy-Key statt eines persönlichen Schlüssels oder eines Tokens mit Zugriff auf mehrere Repositories.\nDas neue Schlüsselpaar wird lokal erzeugt:\nssh-keygen -t ed25519 -C \u0026#34;blog-pages-deployment\u0026#34; -f ./blog-pages-deploy-key Dadurch entstehen ein privater Schlüssel namens blog-pages-deploy-key und ein öffentlicher Schlüssel namens blog-pages-deploy-key.pub.\nIm öffentlichen Deployment-Repository öffne ich Settings → Deploy keys, füge den öffentlichen Schlüssel hinzu und aktiviere Allow write access. Der Schlüssel ist dadurch auf dieses eine Repository beschränkt.\nIm privaten Quellrepository öffne ich Settings → Secrets and variables → Actions und hinterlege den privaten Schlüssel als Repository-Secret mit dem Namen GH_PAGES_DEPLOY_KEY.\nDer private Schlüssel darf niemals in eines der Repositories eingecheckt werden. Nachdem beide Teile bei GitHub hinterlegt wurden, können die lokalen Schlüsseldateien entfernt werden, falls sie nicht mehr benötigt werden. Sollte der private Schlüssel offengelegt werden, muss der Deploy-Key aus dem öffentlichen Repository entfernt und ersetzt werden.\nHugo mit GitHub Actions bauen #Der Workflow startet nach einem Push auf main. Zusätzlich erlaube ich manuelle Ausführungen, was beim Testen von Deployment-Änderungen hilfreich ist:\nname: Build and publish Hugo site on: push: branches: [main] workflow_dispatch: permissions: contents: read Die explizit gesetzte Berechtigung ist bewusst eng gefasst. Der Workflow muss das private Quellrepository nur lesen. Der SSH-Deploy-Key stellt unabhängig davon den erforderlichen Schreibzugriff auf das öffentliche Repository bereit.\nDie ersten Schritte checken den Quelltext aus und installieren eine festgelegte Hugo-Version:\njobs: publish: runs-on: ubuntu-latest steps: - name: Checkout source uses: actions/checkout@v5 with: submodules: recursive fetch-depth: 0 - name: Install Hugo uses: peaceiris/actions-hugo@v3 with: hugo-version: \u0026#39;0.165.0\u0026#39; extended: true Die festgelegte Version vermeidet eine Abhängigkeit von der Hugo-Version, die zufällig auf dem Runner installiert ist. Ich aktiviere Hugo Extended, weil das Theme diese Variante benötigt.\nDer Build selbst ist streng konfiguriert:\n- name: Build site run: \u0026gt;- hugo --gc --cleanDestinationDir --minify --printI18nWarnings --printPathWarnings --panicOnWarning --cleanDestinationDir entfernt Dateien aus vorherigen Builds. Mit --panicOnWarning verhindert bereits eine Hugo-Warnung die Veröffentlichung. Das Ergebnis landet in public/, das im privaten Quellrepository weiterhin ignoriert wird.\nDie generierte Website prüfen #Ein erfolgreich ausgeführter Hugo-Befehl garantiert nicht, dass jeder generierte interne Link auf eine vorhandene Datei zeigt. Deshalb habe ich eine kleine lokale Prüfung als weitere Freigabestufe ergänzt:\n- name: Check generated internal links run: python3 scripts/check-internal-links.py public Das Skript verarbeitet das generierte HTML und prüft Links und Ressourcen derselben Website aus href und src gegen den Inhalt von public/. Es ruft keine externen Websites auf und versucht nicht, jede mögliche Browser-Ressource zu validieren. Sein Zweck ist enger gefasst. Es soll fehlende interne Seiten und Dateien vor dem Deployment finden.\nDie Prüfung verwendet ausschließlich die Python-Standardbibliothek und erfordert deshalb keinen weiteren Installationsschritt. Sie ist an dieser Stelle besonders nützlich, weil sie Hugos endgültige Ausgabe untersucht, nachdem Inhaltspfade, Sprachpräfixe und Ausgabeformate aufgelöst wurden.\nEin einfacheres Setup kann diese Prüfung weglassen. Die Deployment-Architektur hängt nicht davon ab. Ich stoppe den Workflow jedoch lieber, als eine funktionierende Website bewusst durch eine generierte Ausgabe zu ersetzen, die bereits diese grundlegende Prüfung nicht besteht.\nDie generierten Dateien in das öffentliche Repository pushen #Nach erfolgreichem Build und bestandener Prüfung lädt der Workflow den privaten Deploy-Key:\n- name: Configure Pages deploy key uses: webfactory/ssh-agent@v0.10.0 with: ssh-private-key: ${{ secrets.GH_PAGES_DEPLOY_KEY }} Anschließend kann er das öffentliche Repository über SSH klonen:\n- name: Checkout Pages repository run: | git clone --branch main \\ git@github.com:cponfick/gh-pages-blog.git \\ \u0026#34;$GITHUB_WORKSPACE/gh-pages-blog\u0026#34; Mein vollständiger Workflow behandelt auch ein Repository ohne Branches. Der normale Deployment-Pfad bleibt jedoch einfacher, wenn der erste main-Branch bereits beim Einrichten des öffentlichen Repositories erstellt wird.\nDie generierte Ausgabe ersetzt danach den Inhalt des geklonten Repositories:\n- name: Publish generated files run: | rsync --archive --delete \\ --exclude=\u0026#39;.git/\u0026#39; \\ public/ gh-pages-blog/ git -C gh-pages-blog config user.name \u0026#34;github-actions[bot]\u0026#34; git -C gh-pages-blog config user.email \\ \u0026#34;41898282+github-actions[bot]@users.noreply.github.com\u0026#34; git -C gh-pages-blog add --all if git -C gh-pages-blog diff --cached --quiet then echo \u0026#34;No changes to publish\u0026#34; exit 0 fi git -C gh-pages-blog commit -m \u0026#34;Publish blog\u0026#34; git -C gh-pages-blog push origin main Die wichtige Option ist rsync --delete. Wenn ich eine Seite oder Datei aus dem Hugo-Quelltext entferne, muss auch die zugehörige generierte Datei aus dem öffentlichen Repository verschwinden. Ohne --delete könnten alte Dateien öffentlich erreichbar bleiben, obwohl sie nicht mehr zur aktuellen Website gehören.\nDas .git-Verzeichnis wird ausgeschlossen, da es zum Deployment-Repository und nicht zur generierten Website gehört. Der Workflow vermeidet außerdem leere Commits. Dadurch steht jeder Commit im öffentlichen Repository für eine tatsächliche Änderung der veröffentlichten Ausgabe.\nGitHub Pages konfigurieren #Im öffentlichen Repository öffne ich Settings → Pages → Build and deployment und konfiguriere Folgendes:\nSource: Deploy from a branch Branch: main Folder: / (root) Diese Einstellung passt zum Workflow. Der Inhalt von Hugos public/-Verzeichnis wird direkt in das Wurzelverzeichnis von main kopiert. Anschließend sorgt ein gewöhnlicher Git-Push dafür, dass GitHub Pages diesen Branch veröffentlicht.\nDer Push erfolgt mit dem SSH-Deploy-Key. Dieses Detail ist wichtig, weil GitHub dokumentiert, dass Commits eines Workflows mit dem GITHUB_TOKEN keinen Pages-Build auslösen. Der Deploy-Key ermöglicht den Zugriff auf das zweite Repository und macht die Aktualisierung zu einem normalen Push auf den von Pages verwendeten Branch.\nZusätzlich liegt eine leere Datei static/.nojekyll im Hugo-Quelltext. Hugo kopiert sie als .nojekyll in das Wurzelverzeichnis der generierten Website. Dadurch weiß das Branch-basierte GitHub Pages, dass die vorhandene statische Ausgabe ohne Verarbeitung durch Jekyll ausgeliefert werden soll.\nEine eigene Domain hinzufügen #Für meine eigene Domain enthält der Hugo-Quelltext die Datei static/CNAME mit dem Hostnamen:\nconstantinsblog.eu Hugo kopiert diese Datei nach public/, und der Workflow veröffentlicht sie im Wurzelverzeichnis des öffentlichen Repositories. So bleiben die Domaininformationen Teil jedes Deployments, statt beim nächsten Ersetzen der generierten Dateien durch rsync --delete entfernt zu werden.\nDie zugehörigen DNS-Einträge müssen auf GitHub Pages zeigen. In den Pages-Einstellungen von GitHub sind die erforderlichen Einträge aufgeführt. Nach der Verifizierung der Domain verwaltet GitHub auch HTTPS. Die Domain selbst kann weiterhin Registrierungsgebühren verursachen, aber für dieses Setup sind weder eine separate virtuelle Maschine noch ein zusätzlicher Webhosting-Tarif erforderlich.\nAbwägungen und Sicherheitsgrenzen #GitHub unterstützt außerdem Artefakt-basierte Pages-Deployments mit actions/upload-pages-artifact und actions/deploy-pages. Wenn der verwendete GitHub-Tarif ein Pages-Deployment direkt aus dem privaten Quellrepository erlaubt, entfallen bei diesem Ansatz das zweite Repository und der SSH-Schlüssel.\nIch habe mich für das separate öffentliche Repository entschieden, weil es eine klare Grenze zwischen privatem Quelltext und öffentlicher Ausgabe schafft. Es funktioniert außerdem, wenn die Website aus einem öffentlichen Repository veröffentlicht werden muss. Die Nachteile sind die zusätzliche Einrichtung, generierte Commits und die Verantwortung für den Schutz des Deploy-Keys.\nEntscheidend ist, dass die Sichtbarkeit des Repositories keine Datei schützt, die nach public/ kopiert wird. Entwürfe, Notizen, Metadaten und statische Dateien werden öffentlich, sobald Hugo sie in den Build aufnimmt. Deshalb markiere ich Entwürfe entsprechend und behandle das generierte Verzeichnis als Inhalt, den jeder einsehen kann.\nDie entstandene Grenze #Mit diesem Setup beginnt eine Veröffentlichung mit einem Push in das private Repository und endet damit, dass GitHub Pages einen Commit aus dem öffentlichen Repository ausliefert. Build, Prüfung und Synchronisierung laufen ohne einen Deployment-Befehl auf meinem Laptop und ohne einen Server, den ich warten muss.\nDie nützliche Grenze verläuft nicht zwischen einer privaten und einer öffentlichen Website. Sie liegt zwischen privatem Quellmaterial und öffentlicher Build-Ausgabe. Hugo erzeugt diese Grenze, GitHub Actions prüft sie, und das Deployment-Repository enthält nur die Dateien, die GitHub Pages ausliefern muss.\n","date":"6 September 2026","permalink":"https://constantinsblog.eu/de/posts/private-hugo-blog-github-pages/","section":"Posts","summary":"Wie ich den Quelltext eines Hugo-Blogs in einem privaten Repository behalte und die generierte Website mit GitHub Actions und GitHub Pages veröffentliche.","title":"Privater Quelltext, öffentliche Website: Einen Hugo-Blog mit GitHub Actions veröffentlichen"},{"content":" Dieser deutschsprachige Beitrag ist eine KI-unterstützte Übersetzung des englischen Originals. Ich betreibe einen öffentlichen ModSecurity- und OWASP-CRS-WAF vor einem durch IAM geschützten Application-Container. Mein Ziel ist, den WAF zum vorgesehenen öffentlichen Einstiegspunkt zu machen und ihn zur einzigen bereitgestellten Komponente zu machen, die das Origin-Zugangstoken besitzt. „Privat“ bedeutet hier, dass Scaleway eine X-Auth-Token-Authentifizierung verlangt; der generierte Application-Endpunkt bleibt am Edge des Providers erreichbar.\nDieser Beitrag dokumentiert, wie ich diese Architektur mit Scaleway Serverless Containers umgesetzt und debuggt habe. Er ist kein Argument dafür, dass jede Anwendung einen WAF braucht. Die hier beschriebenen Probleme – hostbasiertes Routing, TLS-Verifizierung des Upstreams und der Umgang mit Weiterleitungen – entstehen dadurch, dass ein authentifizierter Reverse Proxy vor einem privaten Origin steht. Dies ist die endgültige Konfiguration, nachdem ich eine Routing-Schleife, TLS-Fehler beim Upstream und durchgesickerte Weiterleitungs-URLs behoben hatte.\nEs gibt zwei unterschiedliche Namen:\nName Beispiel Aufgabe Öffentlicher Hostname app.example.com Browseradresse und Ziel von Weiterleitungen. Generierter Upstream-Hostname private-app.provider.example Leitet die WAF-Anfrage über den Serverless-Ingress zur Anwendung. Diese Rollen getrennt zu halten, war der Schlüssel für das gesamte Design. Die Debugging-Reihenfolge war daher: Die Upstream-Anfrage an den richtigen Container routen, diese Verbindung verifizieren und anschließend browserseitige Weiterleitungen wieder auf die öffentliche Domain zurückübersetzen.\nWarum ModSecurity und das OWASP Core Rule Set #Das WAF-Image verwendet ModSecurity mit dem OWASP Core Rule Set (CRS) und wird von NGINX ausgeliefert. ModSecurity stellt die Prüf-Engine bereit; CRS liefert gepflegte und allgemein nützliche Regeln für verbreitete Angriffsmuster im Web. Das ist ein besserer Ausgangspunkt, als eine Sammlung anwendungsspezifischer regulärer Ausdrücke im Proxy zu schreiben.\nIch habe diese Kombination gewählt, weil sie Open Source, portabel und als offizielles NGINX-Container-Image verfügbar ist. Außerdem bleibt die Zuständigkeit an der HTTP-Grenze: Die Anwendung ist weiterhin für Authentifizierung, Autorisierung, Validierung und Geschäftsregeln verantwortlich, während der WAF eine Ebene allgemeiner Anfrageprüfung ergänzt.\nEin Regelsatz bietet keinen einsatzbereiten Rundumschutz. Starte im DetectionOnly-Modus, prüfe die Audit-Logs, ergänze gezielte Ausnahmen für nachvollziehbare False Positives und aktiviere das Blockieren erst, wenn der beobachtete Datenverkehr dies unterstützt. Pinne das Image anhand von Version und Digest, damit Regelaktualisierungen bewusst erfolgen und testbar sind.\nDer WAF ist außerdem weder eine DDoS-Lösung noch eine Netzwerkisolation. Ein privater Scaleway-Container hat weiterhin einen Provider-Endpunkt, aber Anfragen dorthin benötigen ein gültiges X-Auth-Token. Ratenbegrenzung auf Plattformebene und Schutz vor volumetrischen Angriffen sind davon getrennte Aufgaben.\nZuerst Upstream-Anfragen mit dem generierten Hostnamen routen #Meine ursprüngliche Proxy-Konfiguration übernahm den eingehenden Host explizit – eine verbreitete Entscheidung, wenn eine Anwendung ihren öffentlichen Hostnamen sehen muss:\nproxy_set_header Host $http_host; Das war eine Überschreibung und kein NGINX-Standardverhalten: Das Proxy-Modul setzt den Upstream-Host normalerweise auf $proxy_host.\nIn dieser Topologie ist das unsicher. Wenn app.example.com auf den WAF zeigt, kann das Zurücksenden dieses Hosts über den gemeinsam genutzten Ingress des Providers dazu führen, dass die Origin-Anfrage des WAF wieder an ihn selbst geroutet wird:\nWAF -\u0026gt; shared ingress -\u0026gt; WAF -\u0026gt; shared ingress -\u0026gt; ... In meinem Fall zeigte sich die Schleife als irreführender Fehler 400 Request Header Or Cookie Too Large. Kein Browser-Cookie musste zu groß sein; bei jedem Durchlauf wurden Weiterleitungs-Header angehängt, bis NGINX die Anfrage ablehnte.\nDie Origin-Anfrage muss stattdessen den generierten Hostnamen verwenden:\nproxy_set_header Host $proxy_host; Wenn proxy_pass den generierten Application-Endpunkt verwendet, ist $proxy_host der Hostname dieses Endpunkts. Der Serverless-Ingress kann die Anfrage dadurch an den Application-Container ausliefern.\nDanach das Origin-Zertifikat einschließlich seiner Kette verifizieren #Der WAF sollte die TLS-Verifizierung nicht einfach deshalb deaktivieren, weil der Upstream ein generierter Provider-Endpunkt ist. Er sendet ein Origin-Zugangstoken und muss daher verifizieren, dass er das vorgesehene Origin erreicht hat.\nproxy_ssl_server_name on; proxy_ssl_name $proxy_host; proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt; proxy_ssl_verify on; proxy_ssl_verify_depth 3; SNI (proxy_ssl_server_name) und proxy_ssl_name sorgen dafür, dass die TLS-Anfrage zum generierten Endpunkt passt. proxy_ssl_verify validiert dessen Zertifikat gegen das CA-Bundle des Container-Images.\nDie explizite Verifizierungstiefe wird leicht übersehen. Verwaltete Endpunkte können ein Leaf-Zertifikat gefolgt von mehreren Zwischenzertifikaten und schließlich einer vertrauenswürdigen Root präsentieren. NGINX verwendet für proxy_ssl_verify_depth standardmäßig 1, wodurch eine gültige Kette abgelehnt werden kann; eine Tiefe, die die präsentierte Kette abdeckt, erhält die Verifizierung aufrecht, statt sie abzuschwächen.\nNachdem das Routing funktionierte, erschienen Upstream-Fehler im Browser weiterhin nur als 502. Das Proxy-Fehlerlog zeigte die tatsächliche Ursache, und diese Meldungen wiesen in sehr unterschiedliche Richtungen:\nconnect() failed -\u0026gt; DNS-, Netzwerk-, Adressfamilien- oder Firewall-Problem SSL handshaking failed -\u0026gt; Problem mit SNI, CA-Bundle, Zertifikat oder Verifizierungstiefe Bei Containern macht das Schreiben der Fehler nach Standard Error sie in den Scaleway-Cockpit-Logs verfügbar:\nerror_log /dev/stderr info; Zum Schluss Weiterleitungen an der WAF-Grenze umschreiben #Nach der Korrektur von Routing und TLS war das nächste Symptom eine absolute Weiterleitung der Anwendung mit dem generierten Hostnamen:\nLocation: https://private-app.provider.example/login Der Browser darf nicht an diese interne Implementierungsadresse geschickt werden. Schreibe die Antwort stattdessen an der öffentlichen Grenze um, anstatt den Host-Header des Upstreams zu ändern:\nproxy_redirect https://$proxy_host/ https://${PUBLIC_HOST}/; PUBLIC_HOST sollte ein expliziter, für das Deployment spezifischer Wert wie app.example.com sein und kein reflektierter Request-Header. Dadurch bleiben Weiterleitungsziele auf einer Allowlist und die öffentliche Adresse ist eine bewusste Konfiguration.\nproxy_redirect ändert nur die Antwort-Header Location und Refresh. Die Anwendung benötigt ihre öffentliche URL weiterhin, wenn sie Links in HTML, API-Antworten, E-Mails oder OAuth-Callbacks erzeugt. Konfiguriere nach Möglichkeit eine explizite externe Basis-URL in der Anwendung. Alternativ kannst du den allowlisteten öffentlichen Namen als X-Forwarded-Host übergeben und die Anwendung so konfigurieren, dass sie diesem Header nur vom WAF aus vertraut. Domainbezogene Cookies brauchen eine eigene Behandlung, etwa durch Anwendungskonfiguration oder proxy_cookie_domain.\nEine kompakte Konfiguration #Der relevante Ausschnitt aus meiner WAF-Konfiguration steht unten. Der umgebende TLS-Listener, die ModSecurity-Regelkonfiguration, Anfragebegrenzungen und andere deploymentspezifische Einstellungen sind nicht enthalten.\nSpeichere dies im offiziellen CRS-Image als NGINX-Template, zum Beispiel unter /etc/nginx/templates/conf.d/default.conf.template; der Startprozess ersetzt ${ORIGIN_AUTH_TOKEN} und ${PUBLIC_HOST}. Plain NGINX ersetzt Umgebungsvariablen in Konfigurationsdateien ohne diese Vorverarbeitung nicht.\nlocation = /livez { access_log off; default_type text/plain; return 200 \u0026#34;ok\\n\u0026#34;; } location / { proxy_pass https://private-app.provider.example; proxy_http_version 1.1; # Scaleway ingress routing proxy_set_header Host $proxy_host; # Request context and WAF-to-origin authentication proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto https; proxy_set_header X-Forwarded-Port 443; proxy_set_header X-Forwarded-Host ${PUBLIC_HOST}; proxy_set_header X-Auth-Token ${ORIGIN_AUTH_TOKEN}; # Authenticated TLS to the origin proxy_ssl_server_name on; proxy_ssl_name $proxy_host; proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt; proxy_ssl_verify on; proxy_ssl_verify_depth 3; # Browser-facing URLs proxy_redirect https://$proxy_host/ https://${PUBLIC_HOST}/; proxy_connect_timeout 10s; proxy_read_timeout 120s; proxy_send_timeout 120s; } Das Origin-Token ist der geheime Schlüssel eines dedizierten Scaleway-IAM-API-Keys. Gib seiner IAM-Anwendung die Berechtigung ContainersPrivateAccess und keine unabhängigen weiteren Berechtigungen. Verwende eine IAM-Bedingung, wenn der Zugriff enger als auf Projektebene eingeschränkt werden muss. Speichere den Schlüssel in der Secret-Konfiguration der Container-Plattform, niemals im Image oder Repository.\nDas kurze Verbindungs-Timeout und das längere Lese-Timeout sind absichtlich gewählt: Nach erfolgreichem Verbindungsaufbau kann Warten sinnvoll sein, zum Beispiel während eines Cold Starts. Eine Verbindung, die nicht schnell hergestellt werden kann, weist normalerweise auf ein anderes Problem hin.\nLiveness des WAF von der Bereitschaft des Origins trennen #Der WAF und die Anwendung brauchen unterschiedliche Health Checks:\nGET /livez -\u0026gt; nimmt der WAF-Prozess Anfragen an? GET /readyz -\u0026gt; kann der WAF den authentifizierten Health-Endpunkt der Anwendung erreichen? Verwende den lokalen /livez-Endpunkt für die Startup- oder Liveness-Probe des WAF-Containers. Laut Scaleways Dokumentation zu Health Checks markieren wiederholte Liveness-Fehler den Container als fehlerhaft und stoppen die Weiterleitung von Datenverkehr an ihn, anstatt ihn neu zu starten. Ein ausgefallenes Origin darf daher keinen gesunden WAF aus dem Dienst nehmen; der WAF ist die Komponente, die benötigt wird, um den Fehler zu untersuchen.\nVerwende /readyz über den Proxy in Smoke Tests des Deployments. Damit wird der gesamte Pfad abgedeckt: WAF, Upstream-TLS, Serverless-Routing, Origin-Authentifizierung und Health der Anwendung. Eine abschließende Prüfung der Weiterleitung vervollständigt die grundlegenden Checks:\ncurl -fsS https://app.example.com/livez curl -fsS https://app.example.com/readyz curl -sSI https://app.example.com/ \\ | tr -d \u0026#39;\\r\u0026#39; \\ | grep -Fxi \u0026#39;Location: https://app.example.com/login\u0026#39; Das nützliche mentale Modell besteht darin, Upstream-Routing, TLS-Identität und browserseitige URLs als getrennte Verantwortlichkeiten zu betrachten. Der generierte Hostname ist für Scaleways Ingress bestimmt, TLS validiert diesen generierten Endpunkt, und der öffentliche Hostname ist ein expliziter Kontext für Anwendung und Browser statt der Routing-Schlüssel des Upstreams. Diese Trennung verhindert Proxy-Schleifen und sorgt dafür, dass die Origin-URL nicht in Weiterleitungen durchsickert.\n","date":"5 September 2026","permalink":"https://constantinsblog.eu/de/posts/serverless-waf-reverse-proxy/","section":"Posts","summary":"Wie ich hostbasiertes Routing, Upstream-TLS und Weiterleitungen zwischen einem öffentlichen WAF und einem IAM-privaten Scaleway Serverless Container konfiguriert und debuggt habe.","title":"Eine WAF vor Scaleway Serverless Containern debuggen"},{"content":"","date":null,"permalink":"https://constantinsblog.eu/de/tags/nginx/","section":"Tags","summary":"","title":"Nginx"},{"content":"","date":null,"permalink":"https://constantinsblog.eu/de/tags/reverse-proxy/","section":"Tags","summary":"","title":"Reverse Proxy"},{"content":"","date":null,"permalink":"https://constantinsblog.eu/de/tags/scaleway/","section":"Tags","summary":"","title":"Scaleway"},{"content":"","date":null,"permalink":"https://constantinsblog.eu/de/tags/security/","section":"Tags","summary":"","title":"Security"},{"content":"","date":null,"permalink":"https://constantinsblog.eu/de/tags/serverless/","section":"Tags","summary":"","title":"Serverless"},{"content":" Dieser deutschsprachige Beitrag ist eine KI-unterstützte Übersetzung des englischen Originals. Am 15. Juli 2019 habe ich meine Bachelorarbeit Berechnung der Minkowski-Summe für einfache Polygone abgegeben. Sie ist eine ziemlich mathematische Arbeit, aber das behandelte Problem hat eine anschauliche physikalische Interpretation: Welche Form entsteht, wenn man eine Form über eine andere bewegt und alle Positionen sammelt, die sie erreichen kann?\nDieser Beitrag ist die Fassung, die ich gerne parallel zur Bachelorarbeit geschrieben hätte. Er konzentriert sich auf die zentrale Idee, anstatt alle Definitionen, Beweise und Implementierungsdetails zu wiederholen.\nDie Form, die durch Addition aller Punkte entsteht #Für zwei Punktmengen A und B in der Ebene ist die Minkowski-Summe definiert als\nA ⊕ B = {a + b | a ∈ A, b ∈ B}\nBei der Addition handelt es sich um Vektoraddition. Man wählt aus jeder Form einen Punkt, addiert ihre Koordinaten und erhält dadurch einen Punkt der Summe. Wiederholt man dies für jedes mögliche Paar, wird das Ergebnis vollständig ausgefüllt.\nDie Definition ist einfach, ihre Implementierung jedoch nicht. Es gibt unendlich viele Punktpaare. Selbst die endliche Menge der Polygonecken beschreibt für sich genommen noch nicht das vollständige Ergebnis. Die entscheidende Beobachtung ist, dass der Rand genügend Struktur enthält, um das Ergebnis zu konstruieren.\nDiese Operation findet sich überall dort, wo Geometrie und Algorithmen aufeinandertreffen:\nIn der Bewegungsplanung kann ein Roboter, der nur verschoben werden darf, durch einen Referenzpunkt ersetzt werden. Für ein Hindernis O und die Roboterform R bilden die verbotenen Positionen dieses Punktes die Menge O ⊕ (−R). Dabei ist −R die am Referenzpunkt gespiegelte Roboterform. Bei CNC-Verfahren und im 3D-Druck wird die Geometrie um die Grundfläche des Werkzeugs oder der Düse versetzt, um Wege für den jeweiligen Referenzpunkt abzuleiten. Bei der Kollisionserkennung und Bildverarbeitung wird dieselbe Operation als geometrische Form der Dilatation verwendet. Der einfache Fall: konvexe Polygone #Für zwei konvexe Polygone bieten sich zwei Algorithmen an.\nDer direkte Ansatz bildet jede Eckpunktsumme vᵢ + wⱼ und berechnet anschließend die konvexe Hülle. Bei n und m Ecken entstehen zunächst n · m Kandidaten. Der Hüllenalgorithmus entfernt danach alle Punkte, die nicht auf dem Rand liegen können.\nDer bessere Algorithmus nutzt aus, dass die Kanten eines gegen den Uhrzeigersinn orientierten konvexen Polygons bereits zyklisch nach ihrer Richtung geordnet sind. In der Formulierung der Bachelorarbeit beginnt man in jedem Polygon beim xy-lexikografisch kleinsten Eckpunkt und addiert diese beiden Startpunkte. Anschließend werden die beiden Kantenfolgen anhand ihrer Polarwinkel zusammengeführt. In jedem Schritt rückt man entlang der Kante mit dem kleineren Winkel vor und fügt danach die nächste Eckpunktsumme hinzu. Zeigen beide Kanten in dieselbe Richtung, rückt man in beiden Polygonen vor. Die resultierende Kante entspricht der Summe der beiden Kantenvektoren. Jede Eingabekante wird genau einmal verarbeitet, wodurch sich eine Laufzeit von O(n + m) ergibt.\nDarin steckt eine kleine, aber wichtige algorithmische Erkenntnis: Die Geometrie liefert uns die Ordnung kostenlos. Sobald wir sie nutzen, müssen wir nicht mehr alle möglichen Eckpunktpaare erzeugen und sortieren.\nWarum konkave Polygone alles verändern #Bei konkaven Polygonen funktioniert dieser einmalige Rundgang nicht mehr. Die Summe zweier konkaver Polygone kann Einbuchtungen und mehrere Randkomponenten in Zwischenkonstruktionen enthalten. Außerdem können sich Kanten überlagern oder schneiden. Eine lokale Entscheidung über die nächste Kante reicht nicht mehr aus, um zu bestimmen, welche Teile im endgültigen Rand erhalten bleiben.\nDer Hauptteil meiner Bachelorarbeit verwendet deshalb die Konvolutionsmethode. Ihre wesentliche Idee besteht darin, zunächst eine bewusst redundante Anordnung zu konstruieren und erst danach zu entscheiden, welche Teile zur Minkowski-Summe gehören.\nDer Algorithmus in drei Ideen #1. Kanten um Eckpunkte verschieben #Wir nehmen an, dass die Ränder beider Polygone gegen den Uhrzeigersinn orientiert sind. Eine orientierte Kante pᵢpᵢ₊₁ des Polygons P wird um einen Eckpunkt qⱼ des Polygons Q verschoben. Dadurch entsteht das Segment [pᵢ + qⱼ, pᵢ₊₁ + qⱼ]. Dieses Segment wird nur aufgenommen, wenn seine Richtung in dem Winkelintervall liegt, das von den beiden an qⱼ angrenzenden Kanten aufgespannt wird. Dieselbe Konstruktion wird symmetrisch für jede Kante von Q und den jeweils passenden Eckpunkt von P durchgeführt.\nDie ausgewählten Kanten-Eckpunkt-Verschiebungen bilden die Konvolution. Ihre Segmente verbinden sich zu geschlossenen Kurven, die jedoch nicht einfach sein müssen. Ist mindestens eines der Eingabepolygone konvex, erzeugt der Algorithmus der Bachelorarbeit einen Konvolutionskreis. Bei zwei konkaven Polygonen können mehrere Kreise erforderlich sein. Diese Kurven enthalten Kandidaten für den Rand des Ergebnisses. Sie können sich schneiden oder Regionen einschließen, die nicht zum Ergebnis gehören.\nEin einzelner Konvolutionskreis enthält höchstens O(nm) Segmente. Die Konstruktion aller erforderlichen Kreise für zwei konkave Polygone benötigt im Algorithmus der Bachelorarbeit O(n² · m²) Zeit.\n2. Segmente in eine planare Datenstruktur überführen #Die Segmente werden an allen Schnittpunkten geteilt und in einer doppelt verketteten Kantenliste (DCEL) gespeichert. Eine DCEL erfasst Eckpunkte, gerichtete Halbkanten, ihre Zwillinge, die jeweils nächste Kante am Rand einer Fläche und die Fläche, an die jede Kante grenzt.\nDiese Datenstruktur bildet die Brücke zwischen Geometrie und Topologie. Anstatt komplizierte Fragen für jeden einzelnen Punkt zu beantworten, können wir den Rand jeder Fläche in der Anordnung ablaufen und daraus Schlussfolgerungen ziehen. Die Implementierung muss außerdem deckungsgleiche oder überlappende Segmente verarbeiten. Dieses unscheinbare Detail wird entscheidend, wenn Koordinaten durch Java-double-Werte dargestellt werden.\n3. Das Ergebnis mithilfe von Windungszahlen auswählen #Die Konvolution beschreibt mehr als nur die Minkowski-Summe. Im letzten Schritt müssen die Flächen klassifiziert werden. Der Algorithmus berechnet für jede Fläche ihre Windungszahl. Vereinfacht gesagt gibt sie an, wie oft die orientierte Konvolution einen Punkt innerhalb dieser Fläche umläuft.\nFlächen mit einer positiven Windungszahl gehören zu P ⊕ Q. Gleichbedeutend damit folgt die Implementierung den Halbkanten neben der unbegrenzten Fläche, um den äußeren Rand zu erhalten. Begrenzte Flächen mit der Windungszahl null werden als Löcher behandelt. Eine Fläche mit der Windungszahl zwei gehört weiterhin zum Ergebnis und ist kein Loch. Der Algorithmus versucht also nicht, den endgültigen Rand bereits während seiner Konstruktion zu erraten. Stattdessen konstruiert er alle plausiblen Ränder, beschriftet die Regionen und filtert die Anordnung mithilfe einer topologischen Invariante.\nDiese Trennung ist die zentrale Idee der Bachelorarbeit:\nDie Geometrie schlägt Kanten vor. Die Topologie entscheidet, welche Regionen erhalten bleiben.\nWas die Implementierung schwierig machte #Die mathematische Beschreibung ist übersichtlich. Im Code verbergen sich die Sonderfälle.\nIch habe die Algorithmen in Java 8 mit dem ADSToolbox-Framework der Universität Bayreuth implementiert und getestet.\nVon Rundung zu expliziten Vergleichsregeln #Dass bei Gleitkommaberechnungen Rundungsfehler auftreten, war nicht überraschend. Überrascht hat mich, wie schnell aus einer winzigen numerischen Abweichung ein topologischer Fehler werden kann. Sollten sich drei Segmente in einem Punkt treffen, die Berechnungen erzeugen aber zwei fast identische Punkte, erkennt die DCEL keinen gemeinsamen Eckpunkt mehr. Halbkanten können unverbunden bleiben, Flächen schließen sich möglicherweise nicht und Windungszahlen können falsch werden.\nExakte Gleichheit ist bei berechneten double-Werten deshalb meistens die falsche Frage. Die Implementierung der Bachelorarbeit ging damit defensiv um. Sie versuchte die Berechnung zunächst ohne Rundung. Schlugen die Konsistenzprüfungen der DCEL fehl, wiederholte sie den Versuch mit schrittweise gröberer Dezimalrundung bis hinunter zu vier Nachkommastellen. Ich erwog außerdem den Einsatz von BigDecimal, aber die Anpassung des umgebenden, auf double basierenden Frameworks lag außerhalb des Umfangs der Bachelorarbeit.\nSpäter habe ich mich in meiner quelloffenen Kotlin-Multiplatform-Bibliothek Komp-Geom bewusster mit diesem Problem beschäftigt. Anstatt direkte Vergleiche und willkürliche Toleranzen über die Algorithmen zu verteilen, bildet die Bibliothek die Vergleichsstrategie über ein DoubleEquivalence-Interface ab. Die Standardimplementierung betrachtet zwei Werte als gleichwertig, wenn\n|a − b| ≤ ε · max(1, |a|, |b|)\nmit standardmäßig ε = 10⁻¹⁰ gilt. In der Nähe von null verhält sich dies wie eine absolute Toleranz. Bei größeren Werten skaliert die Toleranz mit ihrem Betrag. Dieselbe Strategie stellt Operationen für Gleichheit, den Vergleich mit null und Größenvergleiche bereit. Geometrische Operationen können eine andere Äquivalenz erhalten, wenn ihr Maßstab oder ihre Anforderungen dies verlangen.\nDas ist ein besserer Entwurf, als rohe ==-Vergleiche in einer Geometriebibliothek zu verstecken. Es ist jedoch kein universelles Heilmittel. Näherungsweise Gleichheit hängt vom Maßstab und vom Zweck einer Berechnung ab und ist im mathematischen Sinn nicht transitiv. Algorithmen, die stärkere Garantien benötigen, können exakte oder adaptive geometrische Prädikate erfordern. Die wichtigste Erkenntnis für mich ist, dass der Vergleich von Zahlen zum Entwurf und zur API eines Algorithmus gehört und nicht in eine nachträglich ergänzte Sammlung von Sonderfällen.\nEinige weitere praktische Erkenntnisse aus der Bachelorarbeit sind mir ebenfalls geblieben:\nEingaben früh normalisieren. Eine einheitliche Orientierung und das Entfernen redundanter Eckpunkte vereinfachen jeden späteren Schritt. Redundante Objekte vermeiden. Die Konvolution kann schnell wachsen. Unnötige Allokationen und wiederholte Berechnungen werden dadurch teuer. Datenstrukturen passend zum Zugriffsmuster wählen. In einem Teil des Algorithmus ist der wahlfreie Zugriff auf eine Folge wichtig. Eine verkettete Liste würde aus einer linearen Operation unbemerkt eine quadratische machen. Nicht nur Koordinaten, sondern auch die Topologie testen. Zusätzlich zu manuell berechneten JUnit-Fällen prüfte die Implementierung, ob jede Halbkante einen Vorgänger und einen Nachfolger besitzt, jeder Flächenrand ein einfaches Polygon bildet und jede Randkante dieselbe Windungszahl für ihre Fläche ergibt. Die Laufzeit der Konvolutionsmethode ist für zwei einfache Polygone durch O(n² · m²) beschränkt. Sowohl die Konstruktion aller Konvolutionskreise für zwei konkave Eingaben als auch die DCEL im schlechtesten Fall haben in der Bachelorarbeit diese Schranke. Die Experimente zeigen außerdem, warum die Anordnung in der Praxis den größten Aufwand verursacht. In einem Beispiel enthielt die DCEL 5.794 Halbkanten, von denen nur 27 den äußeren Rand des Ergebnisses bildeten. Die Methode ist nicht das letzte Wort bei Minkowski-Summen, aber sie ist ein allgemeines Verfahren für den in der Bachelorarbeit behandelten nicht konvexen Fall.\nRückblick #Mir gefällt an diesem Projekt, dass die sichtbare Form das Ergebnis mehrerer gedanklicher Ebenen ist: Vektoraddition, Kantenorientierung, planare Unterteilungen und Windungszahlen. Keine dieser Ideen reicht für sich allein aus. Gemeinsam verwandeln sie eine kontinuierliche geometrische Definition in einen endlichen Algorithmus.\nDer naheliegende nächste Schritt, den ich bereits in der Bachelorarbeit beschrieben habe, ist eine reduzierte Konvolution. Sie lässt Segmente aus, die an reflexiven Eckpunkten entstehen und nicht zum Rand beitragen können. Dadurch entstehen weniger Schnittpunkte und es fällt weniger Verwaltungsaufwand an. Gleichzeitig werden jedoch die geschlossenen Konvolutionskreise aufgebrochen, weshalb Windungszahlen allein das Ergebnis nicht mehr klassifizieren können. Stattdessen sind zusätzliche geometrische Filter erforderlich. Diesen Kompromiss zu implementieren und zu untersuchen wäre ein naheliegendes Folgeprojekt.\nWenn ich dies Jahre später aufschreibe, wird auch die allgemeinere Erkenntnis deutlicher: Wird eine direkte Konstruktion unübersichtlich, kann es wirkungsvoller sein, zunächst eine reichhaltige Zwischenrepräsentation aufzubauen und die Antwort anschließend mit der passenden Invariante daraus zu extrahieren.\n","date":"10 Februar 2026","permalink":"https://constantinsblog.eu/de/posts/minkowski-sum-bachelor-thesis/","section":"Posts","summary":"Ein praxisnaher Einblick in den Algorithmus meiner Bachelorarbeit von 2019, der die Minkowski-Summe zweier einfacher Polygone aus ihrer Konvolution extrahiert.","title":"Formen addieren: Meine Bachelorarbeit über Minkowski-Summen"},{"content":"","date":null,"permalink":"https://constantinsblog.eu/de/tags/thesis/","section":"Tags","summary":"","title":"Thesis"},{"content":"","date":null,"permalink":"https://constantinsblog.eu/de/tags/machine-learning/","section":"Tags","summary":"","title":"Machine Learning"},{"content":"","date":null,"permalink":"https://constantinsblog.eu/de/tags/natural-language-processing/","section":"Tags","summary":"","title":"Natural Language Processing"},{"content":"","date":null,"permalink":"https://constantinsblog.eu/de/tags/topic-modeling/","section":"Tags","summary":"","title":"Topic Modeling"},{"content":" Dieser deutschsprachige Beitrag ist eine KI-unterstützte Übersetzung des englischen Originals. Am 10. November 2021 habe ich meine Masterarbeit Implementation, Evaluation, and Comparison of the GLDA Topic Model abgegeben. Damals dachte ich, ich würde über einen ziemlich speziellen Teil der Forschung zur natürlichen Sprachverarbeitung schreiben. Im Rückblick aus der ChatGPT-Ära wirkt die Arbeit eher wie eine kleine Momentaufnahme aus der Zeit, in der Sprachmodelle lernten, Wörter als Geometrie zu behandeln.\nIn der Arbeit ging es um Gaussian Latent Dirichlet Allocation, kurz GLDA. Dieser Name ist nicht gerade darauf ausgelegt, Leserinnen und Leser anzuziehen. Die zugrunde liegende Frage ist allerdings weiterhin interessant:\nKönnen wir herausfinden, worum es in einer Sammlung von Dokumenten geht, ohne die Themen vorher vorzugeben?\nDiese Frage steckt hinter Suchsystemen, Empfehlungssystemen, der Erkundung von Dokumenten und vielen Systemen, die wir inzwischen beiläufig „KI“ nennen. Interessant ist nicht nur, ob ein Modell brauchbare Ergebnisse liefert. Es geht auch darum, wie es Bedeutung repräsentiert, was es zusammenfasst und was dabei verloren geht.\nVon einer Bag of Words zu einem Raum der Bedeutungen #Die klassische Antwort auf Topic Modeling ist Latent Dirichlet Allocation (LDA). Ein Dokument wird als Mischung aus Topics behandelt, und ein Topic ist eine Wahrscheinlichkeitsverteilung über Wörter. Ein Nachrichtenartikel könnte zu 40 % aus Politik, zu 30 % aus Wirtschaft und zu 30 % aus internationalen Angelegenheiten bestehen. Das Modell bekommt diese Bezeichnungen nicht vorgegeben. Es leitet sie aus der Verwendung von Wörtern im gesamten Korpus ab.\nLDA ist elegant, behandelt Wörter aber als diskrete Identitäten. Das Wort Auto ist ein Eintrag im Vokabular und Automobil ein anderer. Wenn ihre Verwendung im Umfeld das Modell nicht indirekt miteinander verbindet, gibt es kein eingebautes Verständnis dafür, dass sie verwandt sind.\nWort-Embeddings vermittelten eine andere Intuition. Statt ein Wort als ID darzustellen, repräsentiert man es als Punkt in einem hochdimensionalen Raum. Wörter, die in ähnlichen Kontexten vorkommen, landen tendenziell nahe beieinander. Bedeutung wird zu etwas, das sich mit Abständen und Richtungen annähern lässt.\nDas war die zentrale Veränderung in GLDA: Die Verteilungen über diskrete Worttypen aus LDA werden durch Gaußverteilungen über Wort-Embedding-Vektoren ersetzt. Ein Topic war nicht mehr einfach eine Liste von Vokabeln. Es wurde zu einer Region in einem Embedding-Raum, deren Form durch einen Mittelwert und eine Kovarianz beschrieben wird.\nDas klingt nach einer kleinen Änderung. Das ist es nicht. Sobald Wörter zu 50- oder 100-dimensionalen Vektoren werden, wird das Inferenzproblem deutlich aufwendiger. Aus Wahrscheinlichkeitsmassen werden Wahrscheinlichkeitsdichten. Kovarianzmatrizen kommen ins Spiel. Numerische lineare Algebra wird zu einem Teil dessen, was zuvor wie ein unkompliziertes Topic-Modell aussah.\nWenn Wahrscheinlichkeit auf Softwareentwicklung trifft #Die Arbeit hatte zwei praktische Ziele. Erstens implementierte ich zwei Inferenzverfahren für GLDA, kollabiertes Gibbs-Sampling und stochastische Variationsinferenz, im InfoFormalizer-Framework. Zweitens entwickelte ich eine Experiment-Pipeline, um GLDA und eine konventionelle LDA-Implementierung unter denselben Bedingungen zu vergleichen. Für die dokumentierten Experimente kam der Gibbs-Sampler zum Einsatz. Die stochastische Variationsinferenz bezog ich nicht in die Evaluation ein, weil Unterschiede zu ihrer Referenzimplementierung verhinderten, dass ich ihre Korrektheit mit ausreichender Sicherheit bestätigen konnte.\nDas Framework war genauso wichtig wie das Modell. Vergleiche von Machine-Learning-Algorithmen lassen sich überraschend leicht unfair durchführen. Unterschiedliche Vorverarbeitung, Korpora, Evaluationswerkzeuge oder ein nicht dokumentierter Parameter können ein Ergebnis deutlich besser aussehen lassen, als es tatsächlich ist. Ich wollte Datenaufbereitung, Training, Metriken und nachgelagerte Aufgaben in einer reproduzierbaren Pipeline zusammenführen.\nDie Implementierung machte außerdem den Rechenaufwand unübersehbar. LDA war in diesen Experimenten vergleichsweise günstig. Bei zählbasierten Topic-Wort-Statistiken vermieden die üblichen Inferenzoperationen die lineare Algebra mit vollbesetzten Kovarianzmatrizen, die GLDA erforderte. Bei der naiven GLDA-Inferenz und dem stochastischen Variationsverfahren skalierten Operationen wie Matrixinvertierung und Determinantenberechnung kubisch mit der Embedding-Dimension. Für den in den Experimenten verwendeten Gibbs-Sampler reduzierten eine Cholesky-Zerlegung und der Alias-Sampling-Trick die relevante Komplexität, das Training blieb jedoch aufwendig.\nAuf dem Laptop, den ich für die Experimente verwendet habe, war der Unterschied sehr konkret: Jedes LDA-Experiment war innerhalb von 15 Minuten abgeschlossen, während GLDA-Experimente mit den Filmbewertungsdaten und 100-dimensionalen Embeddings fünf bis zehn Stunden dauerten. Mehr semantische Information gibt es nicht umsonst.\nDiese Lektion ist gut gealtert. Moderne Sprachmodelle verstecken enorme Rechenkosten hinter einer freundlichen Chat-Oberfläche, aber die grundlegende Frage bleibt: Kauft uns der zusätzliche Rechenaufwand tatsächlich eine Fähigkeit, die wir brauchen?\nEin Topic-Modell kann verständlich sein, ohne nützlich zu sein #Ich evaluierte die Modelle auf drei sehr unterschiedlichen Korpora:\nBBC News mit bekannten Kategorien wie Sport, Wirtschaft, Politik und Technologie. NIPS Papers, eine thematisch enger gefasste und technischere Sammlung. The Large Movie Review Dataset (ACL IMDB), dessen Labels Stimmung statt Themen beschreiben. Beurteile ein Topic selbst #Ein Topic-Modell beschreibt jedes Topic anhand seiner am höchsten bewerteten Wörter. Dies sind zwei tatsächliche Topics aus den BBC-News-Experimenten. Beide Modelle wurden mit 20 Topics trainiert:\nLDA: mr people government party uk told labour work make election GLDA-50: fact reason hoped concerned doubt suggested similarly hoping aware worried Welches davon sagt dir mehr über den Korpus? Das erste Topic würde ich als britische Politik bezeichnen. Die zweite Liste enthält Wörter, die untereinander zusammenhängen, lässt sich aber wesentlich schwieriger mit einem Themengebiet der BBC News verbinden. Das beweist nicht, dass ein Modell grundsätzlich besser ist. Es zeigt, warum die Evaluation eines unüberwachten Modells eine Entscheidung darüber erfordert, welche Art von Struktur wichtig ist.\nEs gab zwei Möglichkeiten, die Topics zu beurteilen. Die erste war qualitativ: Konnte eine Person anhand der wichtigsten Wörter ein Thema verstehen? Die zweite war quantitativ: Deuteten Metriken wie Topic Coherence oder ein nachgelagerter Klassifikator darauf hin, dass das Modell etwas Nützliches gefunden hatte?\nDiese Antworten stimmten nicht immer überein. GLDA erzeugte im Nachrichtenkorpus häufig verständliche Topics, fand aber auch breite, wiederkehrende Topics aus Wörtern wie Verben, Namen oder Zeitangaben. Weil ähnliche Wörter im Embedding-Raum in ähnlichen Kontexten vorkommen können, gruppierte GLDA manchmal Wörter, die im weitesten Sinne semantisch verwandt, für den Korpus aber nicht besonders aussagekräftig waren.\nBesonders deutlich wurde der Unterschied bei der Sentiment-Aufgabe. Verteilungsbasierte Embeddings können Antonyme nahe beieinander platzieren, weil sie oft in ähnlichen Kontexten vorkommen. Beispielsweise haben „der Schauspieler war gut“ und „der Schauspieler war schlecht“ eine ähnliche grammatische Struktur. Das ist nützlich, wenn es um Verteilungsähnlichkeit geht. Weniger nützlich ist es, wenn positive von negativen Rezensionen getrennt werden sollen.\nDie Klassifikationsergebnisse machten den Punkt noch deutlicher. Bei BBC News schnitten LDA und das 50-dimensionale GLDA-Modell ähnlich ab. Bei 60 Topics erreichte LDA in meinem Experiment eine Genauigkeit von 90 %, GLDA-50 kam auf 91 %. Bei ACL IMDB erreichte LDA dagegen 76,2 %, während GLDA-50 bei gleicher Topic-Anzahl auf 64,0 % kam. Das Modell, das allgemeinere semantische Ähnlichkeiten erfasste, war deshalb nicht automatisch der bessere Feature-Generator für Sentiment.\nDas war eine der nützlichsten Schlussfolgerungen der Arbeit: Ein Modell kann Strukturen finden, ohne die Struktur zu finden, die deine Anwendung braucht.\nDer Zeitpunkt ist schwer zu ignorieren #Ich habe die Arbeit abgegeben, bevor ChatGPT als öffentliches Produkt existierte. GPT-3 war bereits Teil der Diskussion, und Wort-Embeddings waren etabliert. Die aktuelle LLM-Ära hatte Sprachmodelle jedoch noch nicht zu einem alltäglichen Werkzeug für Millionen von Menschen gemacht.\nSeitdem hat sich der Schwerpunkt in der natürlichen Sprachverarbeitung verschoben. Statische Embeddings wie die von mir verwendeten GloVe-Vektoren weisen jedem Worttyp einen Vektor zu. Transformer-Sprachmodelle erzeugen kontextabhängige Repräsentationen. Im Englischen können sie beispielsweise das Wort „bank“ in „river bank“ anders repräsentieren als in „bank account“. Große Modelle können außerdem Erklärungen, Zusammenfassungen und Labels erzeugen und sogar plausible Namen für die Topics vorschlagen, die sie entdecken.\nDieser Fortschritt verändert meinen Blick auf die Arbeit, macht das zugrunde liegende Problem aber nicht überflüssig.\nEin LLM kann einen Korpus zusammenfassen, aber seine Antwort ist nicht unbedingt eine transparente Karte dieses Korpus. Es kann selbstbewusst eine Kategorie erfinden, unterschiedliche Themen zusammenführen oder die auffälligsten statt der repräsentativsten Dokumente beschreiben. Ein Topic-Modell ist deutlich weniger eloquent, aber seine Annahmen und Messungen lassen sich leichter untersuchen. Bei klassischem LDA können die Topic-Wort-Verteilungen inspiziert werden. Bei GLDA kann man die Parameter der Gaußverteilungen und die für jedes Topic repräsentativsten Wörter untersuchen, zusammen mit Dokument-Topic-Vektoren, Vorverarbeitung und Evaluations-Pipeline.\nAußerdem ist die Unterscheidung zwischen Repräsentation und Erklärung hilfreich. Embeddings helfen einem Modell dabei, Sprache in einem geometrischen Raum zu organisieren. Ein LLM kann diese Organisation oder seine eigene interne Repräsentation in eine flüssige Erklärung übersetzen. Flüssigkeit ist wertvoll, aber nicht dasselbe wie Treue zur tatsächlichen Struktur.\nHeute würde ich die Experimente wahrscheinlich mit kontextabhängigen Repräsentationen für Dokumente und Tokens, stärkeren Baselines und besserem Hyperparameter-Tuning wiederholen. Außerdem würde ich Menschen die Topics direkt beurteilen lassen, statt anzunehmen, dass eine Kohärenzmetrik wie die von mir verwendeten UMass- oder cNPMI-Messungen menschlichem Verständnis entspricht. Das wären naheliegende Erweiterungen der unvollendeten Arbeit.\nWas mir geblieben ist #Die größte Überraschung war nicht, dass GLDA langsamer als LDA war. Die Gleichungen machten das einigermaßen vorhersehbar. Die Überraschung war, wie schwierig es war, für ein unüberwachtes Modell zu definieren, was „besser“ bedeutet.\nEin höherer Wert bei einer Kohärenzmetrik bedeutet nicht unbedingt ein nützlicheres Topic. Ein besserer Klassifikator beweist nicht, dass das Modell die interessanteste Struktur entdeckt hat. Ein Topic, das in vielen Dokumenten vorkommt, kann Rauschen sein oder der Hintergrundkontext, der spezialisierte Topics überhaupt erst verständlich macht.\nDie Experimente haben außerdem ein Prinzip bekräftigt, das ich auch in der Softwareentwicklung nutze: Vergleiche sind nur dann aussagekräftig, wenn der Weg zum Ergebnis sichtbar ist. Ein Modellname, eine Benchmark-Zahl oder eine polierte Demo reichen nicht aus. Wir müssen wissen, welche Daten in das System gelangt sind, welche Annahmen getroffen wurden, was gemessen wurde und wo die Methode scheitert.\nDeshalb bin ich froh, GLDA vor der aktuellen Welle von LLMs bearbeitet zu haben. Das Projekt war im Vergleich zu heutigen Modellen klein, zwang mich aber, die Mechanismen hinter einer Aussage über Sprache und Bedeutung genau zu betrachten. Es hat mir gezeigt, dass es mächtig ist, Wörter in einen Vektorraum zu überführen, dadurch aber keine Mehrdeutigkeit verschwindet. Sie bekommt lediglich ein Koordinatensystem.\nUnd manchmal lohnt es sich noch immer, vor der Frage, ob ein Modell einen Korpus erklären kann, eine einfachere Frage zu stellen: Welche Struktur ist tatsächlich vorhanden?\n","date":"5 Januar 2026","permalink":"https://constantinsblog.eu/de/posts/gaussian-lda-master-thesis/","section":"Posts","summary":"Ein Rückblick auf meine Masterarbeit von 2021 über Gaussian LDA, Wort-Embeddings und die überraschend schwierige Frage, ob ein Topic-Modell überhaupt etwas verstanden hat.","title":"Vor ChatGPT: Was ich aus meiner Masterarbeit über Bedeutung in Texten gelernt habe"},{"content":" Dieser deutschsprachige Beitrag ist eine KI-unterstützte Übersetzung des englischen Originals. Was passiert, wenn zwei berechnete Punkte nur beinahe übereinstimmen? In der Computergeometrie kann so ein kleiner Unterschied das Ergebnis eines Algorithmus verändern. Mit Komp-Geom beschäftige ich mich mit solchen Fragen und entwickle dafür Werkzeuge in Kotlin.\nDas Projekt hat drei Teile:\nKomp-Geom ist die Kotlin-Multiplatform-Bibliothek mit geometrischen Grundformen, Operationen und Algorithmen. Komp-Geom Visualizer zeigt, was die Algorithmen tun. So lassen sich auch schwierige Randfälle untersuchen. Komp-Geom Examples zeigt anhand kleiner Java-Programme, wie sich die Bibliothek verwenden lässt. Mit Tests prüfe ich, ob ein Algorithmus das erwartete Ergebnis liefert. Aber es macht mehr Spaß, ihn bei der Arbeit zu sehen: Hier zeigt der Visualisierer die vom Bentley-Ottmann-Algorithmus gefundenen Schnittpunkte in Orange. Warum ich daran arbeite #Mich interessiert an Computergeometrie, wie eng Algorithmen und ihre praktische Umsetzung zusammenhängen. Eine Formel allein reicht nicht, wenn Rundungsfehler einen Vergleich verändern oder eine API die Verwendung unnötig schwer macht. Ich möchte verstehen, wo diese Probleme auftreten, und eine Bibliothek bauen, die sich in Kotlin gut verwenden lässt.\nDie drei Teile helfen mir dabei auf unterschiedliche Weise: In der Bibliothek setze ich die Algorithmen um, im Visualisierer kann ich ihr Verhalten nachvollziehen und mit den Beispielen prüfe ich, wie gut sich die Bibliothek in anderen Programmen verwenden lässt.\nWoran ich arbeite # Geometrische Grundformen in einer, zwei und drei Dimensionen Vergleiche von Gleitkommazahlen, bei denen kleine Rundungsfehler berücksichtigt werden Algorithmen wie die Suche nach dem nächsten Punktepaar (Closest Pair) und die Berechnung der konvexen Hülle (Convex Hull) Kotlin-Multiplatform-APIs mit unveränderlichen Datenstrukturen und veränderlichen Varianten für rechenintensive Fälle Tests, Dokumentation und Benchmarks für die unterstützten Plattformen Komp-Geom ist noch in Entwicklung. Ich ergänze Algorithmen, arbeite an verständlicheren Visualisierungen und überarbeite die API, wenn die Beispiele zeigen, dass etwas umständlich ist.\nCode und Demo # Bibliothek\nKomp-Geom Quellcode, Dokumentation und Tests der Kotlin-Multiplatform-Bibliothek.\nVisualisierer\nKomp-Geom Visualizer Den Quellcode ansehen oder die Algorithmen direkt im Browser ausprobieren.\nLive-Demo öffnen ↗ Beispiele\nKomp-Geom Examples Beispiele für die Verwendung der Bibliothek in Java-Programmen.\n","date":null,"permalink":"https://constantinsblog.eu/de/projects/komp-geom/","section":"Projekte","summary":"Computergeometrie zum Ausprobieren: eine Kotlin-Multiplatform-Bibliothek, ein Visualisierer und Java-Beispiele.","title":"Komp-Geom"},{"content":"","date":null,"permalink":"https://constantinsblog.eu/de/tags/personal-projects/","section":"Tags","summary":"","title":"Personal Projects"},{"content":"Nicht jede Idee passt in einen Blogbeitrag. Hier stelle ich Projekte vor, an denen ich länger arbeite, und zeige, welche Fragen mich dabei beschäftigen.\n","date":null,"permalink":"https://constantinsblog.eu/de/projects/","section":"Projekte","summary":"","title":"Projekte"},{"content":"","date":null,"permalink":"https://constantinsblog.eu/de/categories/","section":"Categories","summary":"","title":"Categories"},{"content":" Dieser deutschsprachige Beitrag ist eine KI-unterstützte Übersetzung des englischen Originals. Ich bin Constantin, Softwareentwickler aus Hamburg. In diesem Blog halte ich fest, was ich beim Entwickeln von Software, bei der Arbeit mit Cloud-Infrastruktur und beim Erkunden von Ideen außerhalb meines Berufsalltags lerne. Ich schreibe diese Dinge auf, weil nützliche Erkenntnisse leicht in Vergessenheit geraten und vielleicht jemandem helfen, der vor einem ähnlichen Problem steht.\nHintergrund #Ich habe an der Universität Bayreuth Informatik studiert. Während meines Masterstudiums lag mein Schwerpunkt auf maschinellem Lernen, insbesondere auf der Verarbeitung natürlicher Sprache.\nNach meinem Abschluss begann ich meine Laufbahn als Backend-Entwickler im IoT-Umfeld bei einem Maschinenbauunternehmen. Dabei sammelte ich Erfahrung mit verteilten Systemen, Cloud-Architekturen und dem Betrieb von Software außerhalb einer lokalen Entwicklungsumgebung.\nIm Laufe der Jahre habe ich mit Technologien aus den AWS- und Azure-Ökosystemen gearbeitet. Dazu gehören Infrastructure as Code, containerisierte Anwendungen und Kubernetes. Zu meinen Erfahrungen zählen außerdem Spring Boot, relationale Datenbanken, Docker, Helm, AWS CDK und Terraform.\nWie ich Softwareentwicklung angehe #Mir ist wichtig, Software zu entwickeln, die langfristig stabil, wartbar und verständlich bleibt. Neue Funktionen sind wichtig, genauso wie vorhersehbare Deployments, klarer Code, aussagekräftige Tests und bewusste technische Entscheidungen.\nWorüber ich schreibe #Die meisten Beiträge handeln von Softwareentwicklung, Cloud-Architektur und persönlichen Projekten. Mich interessieren besonders die Entscheidungen hinter einer Lösung, die Alternativen, die nicht funktioniert haben, und die Erkenntnisse, die sich auf zukünftige Aufgaben übertragen lassen. Wenn ich mich auf berufliche Erfahrungen beziehe, bleibe ich allgemein und teile keine Details über die Projekte oder Systeme selbst.\nIch beschäftige mich außerdem mit KI-unterstützter Softwareentwicklung. Der gesamte Hype rund um KI überzeugt mich nicht, aber ich gehe davon aus, dass sie beeinflussen wird, wie wir Software entwickeln, bereitstellen und warten. Ich möchte verstehen, wo diese Werkzeuge die Entwicklungsarbeit tatsächlich verbessern, wo sie neue Risiken mit sich bringen und wie sie sich einsetzen lassen, ohne auf sorgfältiges technisches Urteilsvermögen zu verzichten.\nDie Themen dieses Blogs können sich mit der Zeit erweitern, wenn ich weitere Erfahrungen finde, die es wert sind, festgehalten zu werden.\nAbseits der Softwareentwicklung #Außerhalb meiner beruflichen Arbeit schaue ich gerne Fußball und spiele Basketball. Basketball hält mich aktiv und verbindet zwei Dinge, die ich schätze: Teamarbeit und Strategie. Auch wenn ich nicht mehr so gut bin wie früher oder so gut, wie ich gerne wäre, schätze ich weiterhin die Herausforderung und die Möglichkeit, mich zu verbessern.\nAnderswo #Ich bin überzeugt, dass Softwareentwicklung vom gemeinsamen Austausch lebt. Wer über etwas sprechen möchte, das ich geschrieben habe, Erfahrungen austauschen oder einfach Hallo sagen möchte, kann sich gerne bei mir melden.\n","date":null,"permalink":"https://constantinsblog.eu/de/about/","section":"Notizen über Softwareentwicklung, Cloud und KI","summary":"","title":"Über Constantin"}]