Adrian RomoAdrian Romo

Projekte

Engineering Case Studies.

Backend-Systeme, AWS-Architekturen, Voice-Plattformen und Integrationsframeworks, die ich entworfen, ausgeliefert und betrieben habe. Jede Case Study beschreibt Kontext, Ansatz und Ergebnis.

Aktiv

Einkaufswagen-Bridge

Wandelt eine Einkaufs­liste aus dem Mahlzeiten­planer in einen überprüfbaren Warenkorb-Vorschlag um – und bleibt dann bewusst stehen

## Problem Mein selbst gehosteter Essensplaner weiß, was ich einkaufen muss. Die Website meines Supermarkts kennt die Preise, weiß, welche Aktionen gelten, und was im Abverkauf ist. Das jede Woche von Hand abzugleichen, ist genau die Art von wiederkehrender Kleinaufgabe, die Automatisierung übernehmen sollte. ## Meine Rolle Ich habe alles entworfen und gebaut, inklusive der Abgrenzungen – die machen den Großteil des Designs aus. ## Rahmenbedingungen - Spanischsprachige Lebensmitteldaten, bei denen dasselbe Produkt mehrere Namen hat und das Matching deterministisch genug sein muss, um es zu überprüfen. - Die Aktionslogik ist wirklich komplex: Multi-Buy-Angebote, prozentuale und feste Rabatte, Abverkaufspreise, Treueprämien. - Es geht um Geld. Diese Einschränkung hat alles andere bestimmt. ## Architektur Bedarfe aus der Einkaufsliste werden normalisiert, deterministisch mit dem Katalog abgeglichen und gegen aktive Aktionen ausgewertet, um einen **Warenkorb-Vorschlag zur menschlichen Überprüfung** zu erzeugen. Lokale Präferenzspeicherung sorgt dafür, dass eine einmalige Korrektur dauerhaft gemerkt wird. Die Funktionen werden über Model Context Protocol bereitgestellt, sodass ein Assistant sie aufrufen kann. Dry-Run ist der Standard. Warenkorb-Änderungen sind möglich, aber hinter einem expliziten Apply-Flag deaktiviert. Die Out-of-Scope-Liste ist fest im Projekt verankert und kein TODO: kein Checkout, keine Bezahlung, keine Lieferterminplanung, keine Bestellübermittlung, kein Einlösen von Treuepunkten, kein autonomer Einkauf und kein Versuch, Bot-Schutz zu umgehen. ## Ergebnis Läuft end-to-end ab v0.1.0, wird wöchentlich genutzt, mit einer Fixtures-First-Test-Suite, sodass die Aktionslogik geprüft werden kann, ohne eine Live-Seite zu belasten. ## Was ich anders machen würde Nichts Strukturelles – das ist das Projekt, bei dem ich die Abgrenzung beim ersten Versuch richtig gesetzt habe, und der Grund dafür ist, dass ich die Out-of-Scope-Liste vor der Feature-Liste geschrieben habe. **Automatisierung, die mit Geld zu tun hat, sollte vorschlagen, nicht handeln.** Der Review-Schritt kostet mich dreißig Sekunden pro Woche und eliminiert eine ganze Fehlerkategorie, gegen die ich sonst hätte anentwickeln müssen.

PythonModel Context Protocolpytest
Case Study lesen
Aktiv

Kurzvideo-Pipeline

GPU-lokale Script-to-Render-Automatisierung, doppelt bereitgestellt: CUDA zu Hause, CPU-only für öffentliche Demo

## Problem Konsistent Kurzvideos zu produzieren ist eigentlich ein Planungsproblem, das sich als kreatives tarnt. Ich wollte die mechanische Hälfte — Skript, passendes Footage, Untertitel, Musikbett, Rendern — automatisieren und auf Hardware laufen lassen, die ich bereits besitze, statt pro Minute für gehostete Generierung zu zahlen. ## Meine Rolle Das ist ein **Fork des Open-Source-Projekts MoneyPrinterTurbo von harry0703**, das den Kern-Generator liefert. Die Upstream-Autoren haben den Großteil der Commits beigesteuert und verdienen die Anerkennung für das Fundament. Mein Beitrag ist die Homelab-Integration und eine Reihe von Erweiterungen: ein lizenzierter Musikpool, damit Audio rechtlich abgesichert ist, „Content Packs“, die die Identität eines Accounts von der Generierungsmaschine trennen, Insight-Messung, die über Hashtag-Zählen hinausgeht, und ein Motion-Clip-Pool, der durch die reale Uhrzeit begrenzt ist. ## Einschränkungen - Eine GPU, die mit einem lokalen LLM-Stack und einem „Second Brain“ geteilt wird, das ein morgendliches Briefing ausführt. Die Video-Generierung darf die zeitgesteuerten Prozesse nicht ausbremsen. - Das öffentliche Demo hat überhaupt keine GPU, also muss derselbe Code auf CPU-only zurückfallen können. ## Architektur Thema zu Skript zu passendem Footage zu Untertiteln zu Musik zu gerendertem Short. Hardware-beschleunigtes Encoding und lokale Speech-to-Text auf der Workstation; derselbe Stack CPU-only hinter dem Reverse Proxy des Homelabs als öffentliches Demo ausgerollt. ## Ergebnis Zwei Live-Deployments aus einer Codebasis. Rund 57 eigene Commits on top von Upstream, konzentriert auf Scheduling, Lizenzierung und Ressourcen-Contention, weniger am Generator selbst. ## Was ich anders machen würde Der Bug, an den ich mich erinnere, war ein Ressourcen-Contention-Problem und hat mir etwas Grundsätzliches beigebracht: Ein vom Scheduler eingereihtes Job erbt immer ein frisches Dauer-Budget, nicht das verbleibende Zeitfenster an der Uhr. Etwas, das eigentlich über Nacht laufen sollte, lief vier Stunden in den Arbeitstag hinein und blockierte die GPU, die das morgendliche Briefing gebraucht hätte. **Ein Dauer-Limit kann niemals eine reale Uhrzeit begrenzen** — wenn ein Job zu einer bestimmten Zeit fertig sein muss, muss genau diese Zeit als Constraint kodiert werden.

PythonFFmpegCUDAfaster-whisperStreamlitDocker
Case Study lesen2 verwandte Beiträge
Aktiv

Homelab Zweitgehirn

Eine kompilierte Memory-Schicht, die Infrastrukturfragen in 2–5K Tokens mit Quellenangaben beantwortet

## Problem Ich wollte ein lokales Modell, auf meiner eigenen GPU, das Fragen zu meiner eigenen Infrastruktur beantworten kann — was hängt wovon ab, warum ist etwas so konfiguriert, wie es ist, was hat sich kürzlich geändert. Einfaches Retrieval über meine Dokumentation kostete pro Frage 30.000 bis 50.000 Tokens und hatte kein Zeitverständnis: Eine Entscheidung aus dem Juni und eine Beobachtung von heute Morgen kamen mit gleicher Autorität zurück. ## Meine Rolle Ich habe das komplette System alleine entworfen und gebaut, in etwa acht Wochen, als 220 gemergte Pull Requests. ## Rahmenbedingungen - Muss komplett lokal laufen. Eine 24GB Consumer-GPU, die oft mit anderen Aufgaben beschäftigt ist. - Darf niemals eine weitere Quelle der Wahrheit werden. Git und Live-Runtime-Beobachtungen bleiben maßgeblich; alles Abgeleitete muss löschbar und rekonstruierbar sein. - Muss ehrlich sein. Ein Operations-Assistent, der halluziniert, ist schlimmer als gar keiner, weil du eine dünne Antwort nicht von einer falschen unterscheiden kannst. ## Architektur Eine **kompilierte** Schicht über den maßgeblichen Quellen: ein temporaler Fakten-Store, der Fakten schließt statt sie zu überschreiben, ein Beziehungsgraph (940 Knoten, 1.449 Kanten), generationsgestempelte Zusammenfassungskapseln und ein Vektorindex — alles zusammengebaut zu einem token-budgetierten Kontextpaket. Das Retrieval ist gestuft und bricht früh ab: exakt, dann lexikalisch BM25, dann Kapsel, dann Graph, dann semantisch. Konflikte werden durch feste Priorität gelöst: Runtime vor Fakt vor Kapsel, und ein abgeleitetes Artefakt, dessen Generation hinter der Quelle zurückliegt, wird als veraltet markiert, sodass der Query-Pfad auf die Rohdaten zurückfällt. Darüber liegen die Analyse-Schichten: Verfall (welche dokumentierten Entscheidungen sind nicht mehr gültig), Korrektheitsvalidierung gegen Live-Quellen mit einem festgeschriebenen Baseline und einer CI-Regression-Absicherung, Drift-Abgleich zwischen deklarierter und beobachteter Realität, Blindspot-Erkennung, kausales und Multi-Hop-Reasoning sowie ein gesteuerter Detect-Propose-Approve-Act-Loop, dessen Executor bei einem Draft-Pull-Request stoppt und niemals merged. Durchgehend gilt eine Regel: **begründet oder blockiert.** Auswahl und Ranking sind deterministisch und offline reproduzierbar. Ein Sprachmodell darf ein bereits ausgewähltes Ergebnis formulieren, aber niemals eines hinzufügen, entfernen oder umklassifizieren. ## Ergebnis Etwa 99 % weniger Tokens pro Query, validiert im Shadow-Mode gegen den bisherigen Ansatz vor der Umstellung. 31 Kommandozeilentools, 93 Read-Only-Tools über Model Context Protocol exponiert und 293 Tests allein über das Memory-Subsystem innerhalb einer 116.000-Zeilen-Python-Operations-Schicht mit 1.865 Tests. ## Was ich anders machen würde Ich habe den Fakten-Store gebaut, bevor ich das System gebaut habe, das prüft, ob die Fakten noch stimmen. Die gehören in denselben Release — ein Gedächtnis ohne Selbst-Audit ist ein selbstbewusster Cache. Und ich hätte von Anfang an „was habe ich beobachtet“ von „was schließe ich daraus“ im Detector-Contract getrennt; das Vermischen hat eine Reihe gut belegter Alerts produziert, die alle denselben Bug hatten.

PythonSQLite FTS5QdrantOllamaModel Context ProtocolFastAPI
Case Study lesen7 verwandte Beiträge
Aktiv

Homelab Ops

14 Hosts, etwa 110 Container und die Disziplin, die Doku übersichtlich zu halten

## Problem Ein Homelab hört genau dann auf, ein Hobby zu sein, wenn dein Haushalt davon abhängt. Bei mir war dieser Punkt erreicht — DNS, Hausautomation, Medien, Dokumentenspeicher und SSO-nahe Passwortdienste laufen alle darauf. Ab diesem Moment ist der Ausfallmodus nicht mehr „ein Dienst ist down“. Es ist „ich weiß nicht mehr, warum das so konfiguriert ist, und die einzige Person, die es wusste, war ich selbst vor sechs Wochen“. ## Meine Rolle Alleiniger Architekt und Betreiber. Jeder Host, jede Compose-Datei, jede Entscheidungsdokumentation. ## Rahmenbedingungen - Einzelner Betreiber, abends und am Wochenende. Alles, was regelmäßig meine Aufmerksamkeit braucht, wird sie irgendwann nicht mehr bekommen. - Echte Abhängigkeiten. DNS- und Hausautomationsausfälle sind im Haushalt innerhalb von Minuten sichtbar. - Kein Cloud-Notausgang für die Dinge, die zählen — der Punkt ist, dass es hier läuft. ## Architektur Ein Proxmox-Knoten hostet zweckgetrennte Docker-VMs: gemeinsame Infrastruktur (Reverse Proxy, SSO, Metriken, Logs, Uptime, Push-Benachrichtigungen), ein operatives Backend (selbstgehostetes Git, CI-Runner, ein Append-only Event-Ledger), benutzerorientierte Wissens- und Dokumenten-Apps sowie eine Sandbox für Experimente und öffentliche Demos. Ein NAS hält Persistenz und Backups und dient gleichzeitig als zweiter DNS-Knoten. Eine separate GPU-Workstation betreibt den lokalen Modell-Stack. Der Ingress läuft über einen einzigen Reverse Proxy mit Forward-Auth vor allem Internen. DNS läuft hochverfügbar über zwei Knoten mit einseitigem Policy-Sync aus einer festgelegten Quelle der Wahrheit, sodass das Replikat niemals zur zweiten Meinung abdriften kann. Alles ist Git-gestützt und Git-first: Inventar, Compose-Dateien, Runbooks und Entscheidungsdokumentationen leben in einem Repo, und die laufende Flotte wird von Read-only-Collector-Agents gegen dieses Repo verglichen, die selbst nichts ändern und ihre eigenen Fehler aufzeichnen, statt abzustürzen. ## Ergebnis 14 Hosts und etwa 110 Container, dokumentiert in 368 Markdown-Seiten: 21 Architektur-Entscheidungsdokumente, 131 Runbooks — inklusive Wiederherstellungsprozeduren für jeden zustandsbehafteten Dienst — und Postmortems, ehrlich genug, um auch die Fixes zu enthalten, die nicht funktioniert haben. ## Was ich anders machen würde Die Entscheidungsdokumente von Anfang an schreiben, statt sie später rekonstruieren zu müssen. Das Teure war nie die Konfiguration; es war das Wiederherstellen der Gründe hinter einer Konfiguration, die ich längst vergessen hatte. Und die USV kaufen, bevor dich ein unsauberer Stromausfall dazu zwingt, es zu lernen.

ProxmoxDockerTraefikAuthentikPrometheusGrafana
Case Study lesen7 verwandte Beiträge
Aktiv

Diese Seite — Portfolio als Produkt

Next.js 14 App mit einem RAG-Assistenten, LLM-i18n-Pipeline und mehrsprachigem PDF-Lebenslauf

## Problem Ein Lebenslauf behauptet Seniorität; er kann sie nicht demonstrieren. Ich wollte ein Portfolio, das selbst ein Produktionssystem ist – jede Funktion ein verifizierbares Arbeitsbeispiel, das ein Recruiter anklicken, lesen und hinterfragen kann. ## Meine Rolle Alles: Produkt, Design, Backend, Frontend und Betrieb – alles alleine entworfen, gebaut und betrieben. ## Einschränkungen - Die serverlose Bereitstellung darf die Postgres-Verbindungen nicht erschöpfen, und öffentliche Seiten müssen ISR-cachebar hinter strengen Sicherheitsheadern bleiben. - Inhalte werden in Englisch, Spanisch und Deutsch ausgeliefert, ohne jeden Beitrag dreifach zu erstellen. - Der KI-Assistent antwortet nur auf Basis meiner echten Inhalte, innerhalb eines täglichen Kostenbudgets, und muss gegen Prompt-Injection resistent sein. ## Architektur Next.js 14 App Router mit TypeScript und Prisma auf Postgres. Der Assistent ist retrieval-augmented: Die Site-Inhalte werden in Stücke zerlegt und in pgvector eingebettet, pro Frage abgerufen und mit OpenAI-Modellen beantwortet – mit Prompt-Injection-Schutz, IP-basierten Ratenlimits, einem täglichen Token-Budget und cron-gesteuertem Re-Indexing. Eine LLM-Übersetzungspipeline generiert die spanischen und deutschen Lokalisierungen mit hash-basierter Staleness-Erkennung. Die Admin-Oberfläche sitzt hinter NextAuth-Anmeldeinformationen plus WebAuthn-Passkeys unter einer nonce-basierten CSP. Das Lebenslauf-PDF wird serverseitig mit PDFKit in allen drei Sprachen gerendert. Die Blogsuchen verwenden die Volltextsuche von Postgres; jede Route gibt kanonische/hreflang-Metadaten, JSON-LD, pro Route Open Graph-Bilder, eine Sitemap, RSS und llms.txt aus. ## Ergebnis Die Site, die du gerade liest: mehrsprachiger Blog und Fallstudien, ein KI-Assistent, der auf meinem eigenen Schreiben basiert, und ein recruiter-bereites PDF-Lebenslauf – alles aus einem Codebase generiert und in der Produktion betrieben. ## Was ich anders machen würde Die i18n-Routing von Anfang an entwerfen – das Nachrüsten bedeutete, dass jede öffentliche Seite jetzt in zwei Routentrees existiert. Und Projektmetriken im Schema anstatt in Prosa unterbringen, damit sie als Highlight-Chips gerendert werden können.

Next.jsTypeScriptPrismaPostgrespgvectorOpenAI
Case Study lesen1 verwandter Beitrag
Aktiv

Omnichannel-Integrationsframework — Praxisbeispiel

Eine Factory-Method-Grundlage für Slack, Teams, Google Chat und Webex bei Resolve Systems

## Problem Jeder Enterprise-Chat-Kanal — Slack, Microsoft Teams, Google Chat, Webex — begann als individuelle Integration. Wenn der Fulfillment-Code je nach Kanal verzweigt, pflegst du im Grunde N Produkte, die unter Zeitdruck auseinanderdriften. ## Meine Rolle Ich habe das auf dem Factory Method basierende Integrations-Framework und die gemeinsamen Testing-Utilities entworfen und implementiert, auf denen jeder Channel-Adapter aufbaut. Das ist das Rückgrat, gegen das jeder Kanal auf der Plattform geschrieben wird, inklusive Voice. Das Designziel war eine Schnittstelle, die nichts darüber aussagt, *wann* eine Antwort eintrifft: Die Chat-Kanäle sind asynchron, ein Telefonanruf ist synchron und blockierend, und beides sind Adapter hinter derselben Factory. Eine Abstraktion, die nur für Kanäle mit demselben Timing-Modell funktioniert, ist kein Integrations-Framework, sondern ein Chat-Framework. ## Einschränkungen - Kanäle unterscheiden sich in Authentifizierungsmodellen, Payload-Strukturen und Rate Limits — diese Unterschiede mussten innerhalb der Adapter bleiben. - Bestehende Live-Integrationen mussten weiter funktionieren, während das Framework darunter ersetzt wurde. - Secure-by-default-Patterns (Authentifizierung, MFA, Secrets-Handling) waren für Enterprise-Kunden Pflicht. ## Architektur Channel-Adapter normalisieren jede eingehende Nachricht in einen gemeinsamen Envelope; eine Factory Method instanziiert den richtigen Adapter pro Kanal; der Fulfillment-Code arbeitet mit Intents und Entities und verzweigt niemals nach Kanal. Gemeinsame Testing-Utilities liefern jedem neuen Adapter von Anfang an dieselbe Conformance-Suite. ## Ergebnis Slack, Microsoft Teams, Google Chat und Webex laufen alle auf einem Rückgrat, und jeder neue Kanal kostet weniger als der vorherige. Über zwei Jahre wurden keine kritischen Sicherheitslücken in diesen Enterprise-Integrationen festgestellt. ## Was ich anders machen würde Das gemeinsame Intent-Schema am ersten Tag definieren und wie eine API versionieren — divergierende Intent-Definitionen später zusammenzuführen ist der teure Weg. Den Adapter zuletzt bauen: zuerst den gemeinsamen Envelope und die Testing-Story richtig hinbekommen.

PythonREST APIsSlackMicrosoft TeamsGoogle ChatWebex
Case Study lesen2 verwandte Beiträge
Abgeschlossen

IVR Voice Agent — Praxisbeispiel

Asynchrone Chatbot-Gespräche nahtlos in Amazon Connect IVR-Flows bei Resolve Systems integrieren

## Problem Der virtuelle Support-Agent von Resolve Systems wurde für asynchronen Chat entwickelt, bei dem eine Antwort innerhalb von zwei Sekunden als flott empfunden wird. Ein Telefonanruf ist jedoch ein blockierender, Echtzeit-Kanal – Anrufer hören jede Verzögerung in der Pipeline als Stille. Die Plattform benötigte einen Sprachkanal, ohne das bestehende Chatbot-Backend neu schreiben zu müssen. ## Meine Rolle Ich habe die Architektur von Anfang bis Ende entworfen und implementiert: Intent-Routing, Speech-to-Text- und Text-to-Speech-Orchestrierung über Amazon Connect, Amazon Lex, AWS Lambda und Polly sowie die Übergabe an die bestehende Fulfillment-Logik. Der entscheidende Teil, der das möglich machte, war nicht sprachspezifisch. Ich habe die Factory-Method-Integrationsschicht entworfen und gebaut, auf der die Chatbot-Plattform läuft: Jeder Kanal wird in einen einheitlichen Envelope normalisiert, eine Factory instanziiert den passenden Adapter, und das Fulfillment arbeitet auf Intents und Entities, ohne jemals nach Kanal zu verzweigen. Die Eigenschaft, die zählte, ist, dass **die Abstraktion zeitneutral ist**. Slack, Teams, Google Chat und Webex sind asynchron; ein Telefonanruf ist synchron und blockierend. Beides sind Adapter. Die Schnittstelle macht keine Aussage darüber, ob eine Antwort in zwei Sekunden oder zweihundert Millisekunden eintrifft, sodass Voice gegen eine bereits existierende Schnittstelle hinzugefügt wurde, anstatt die Produktionsinfrastruktur zu verzweigen, um sie zu unterstützen. Diese Grenze von Anfang an richtig zu ziehen, ist der Grund, warum der schwierigste Kanal überhaupt hinzugefügt werden konnte. ## Einschränkungen - Voice-Latenzbudgets sind unerbittlich – Anrufer bemerken Unterbrechungen unter einer Sekunde, die Chat-Nutzer nie wahrnehmen. - Das asynchrone Fulfillment-Backend war Produktionsinfrastruktur, die mit allen Chat-Kanälen geteilt wurde; es konnte für Voice nicht verzweigt werden. - Enterprise-Sicherheitsanforderungen – Authentifizierung, Umgang mit Secrets – galten für jeden neuen Hop. ## Architektur Amazon Connect erfasst Audio und streamt es an Lex zur Intent-Erkennung; eine Lambda-Bridge übersetzt zwischen dem synchronen IVR-Flow und dem asynchronen Chatbot-Backend, dann synthetisiert Polly die Antworten zurück in den Anruf. Downstream-Lookups starten spekulativ beim Beginn der Äußerung statt erst nach der Intent-Erkennung, und die Reise des Anrufers wird über Connect, Lex, Lambda und downstream APIs korreliert, sodass Traces die Kanalgrenze überleben. ## Ergebnis Als Produktionskanal der virtuellen Support-Agent-Plattform ausgeliefert, die dazu beitrug, das Helpdesk-Anrufvolumen um 40–60 % zu reduzieren. ## Wie es weiterging Der Kanal wurde inzwischen vom AWS-Stack auf Vapi migriert, das als Plattform mehr Echtzeit-Orchestrierung übernimmt als der Ansatz mit zusammengesetzten Primitiven. Das ist eine klare Aussage: Nach dem Aufwand, die Bridge zu bauen, stellte sich eine speziell für Voice gebaute Plattform als der bessere Agent heraus. Die Evaluation weiterer Voice-Plattformen läuft parallel dazu, denn die entscheidenden Einschränkungen bei solchen Systemen sind selten nur technischer Natur. Überlebt hat die Migration der Teil, den ich genauso weiterbauen würde – die darunterliegende Integrationsschicht. Da das Fulfillment nie nach Kanal verzweigte, wurde es durch den Austausch des Voice-Frontends nicht berührt. ## Was ich anders machen würde Cross-Service-Korrelation von Anfang an mitdenken, statt sie erst während der Stabilisierung hinzuzufügen – Traces, die an einer Kanalgrenze sterben, sind keine Traces. Und frühzeitig Budget für Barge-in-Cancellation-Semantik einplanen: Jede laufende Synthese wird stornierbar, was die Idempotenzanforderungen downstream verändert.

PythonAWS LambdaAmazon ConnectAmazon LexAmazon Polly
Case Study lesen3 verwandte Beiträge