Pinned Post

OpenSpec UI: ein Live-Dashboard für deine Specs

Bild
OpenSpec UI: ein Live-Dashboard für deine Specs Wer mit einem KI-Agenten und OpenSpec arbeitet, kennt das Bild. Im Projekt wächst ein Verzeichnis openspec/ heran: Changes mit proposal.md , design.md , tasks.md und ihren Delta-Specs, daneben die kanonischen Specs der einzelnen Capabilities. Die Absicht hinter dem Code steht damit endlich geschrieben, statt sich in ihm zu verstecken. Nur verteilt sie sich über Dutzende Markdown-Dateien, und der Editor zeigt eben Dateien. Was er nicht zeigt, ist der Zustand. Woran arbeitet der Agent gerade? Welcher Task ist der nächste offene? Und was hat sich verändert, während ich zehn Minuten woanders hingeschaut habe? Genau diese Lücke füllt OpenSpec UI , ein kleines Phoenix-LiveView-Dashboard, das lokal neben deinem Projekt läuft und den OpenSpec-Workspace im Browser zeigt — live, während gearbeitet wird. Der Code liegt offen auf GitLab: https://gitlab.com/public_elixir/openspec_ui Ein Blick statt Datei-Hopping Du startest den Server, gibst d...

Wann du KEIN ML nehmen solltest

Nach vier Artikeln über das ML in meiner App kommt der ehrlichste: Mein liebster Teil des Klassifikations-Features ist der Teil, der gar kein ML ist. Denn die reifste KI-Entscheidung ist oft, keine zu treffen.

Diese Serie war ein Plädoyer für pragmatisches ML in Elixir. Sie wäre aber unehrlich, wenn ich nicht auch über die Grenze schreiben würde, über die Stelle also, an der ich das Modell bewusst NICHT entscheiden lasse.

Erinnerung an die geschichtete Pipeline aus Artikel 3: Bevor das Embedding-Modell überhaupt gefragt wird, läuft eine deterministische Regel-Engine. Und die ist kein Notbehelf, sondern Absicht.

Warum überhaupt Regeln, wenn ich doch ein Modell habe? Weil manche Muster gar nicht unsicher sind. Meine Miete geht jeden Monat an denselben Vermieter, mein Gehalt kommt vom selben Arbeitgeber, und den ÖPNV-Abbucher kenne ich ohnehin. „Ähnlichkeit schätzen" ist hier die falsche Operation, denn ich weiß die Antwort. Ein Modell darauf anzusetzen würde eine sichere Tatsache in eine Wahrscheinlichkeit verwandeln, und das ist ein Rückschritt, kein Fortschritt.

Eine Regel ist deshalb schlicht: eine Menge von Bedingungen, die alle zutreffen müssen, und die erste passende aktive Regel gewinnt.

# Alle Bedingungen müssen matchen. Keine "Wahrscheinlichkeit",
# kein Schwellwert, eine Regel trifft zu oder nicht.
def matches?(%Rule{conditions: conditions}, transaction) do
conditions != [] and Enum.all?(conditions, &condition_match?(&1, transaction))
end

Die Bedingungen arbeiten auf den konkreten Feldern einer Transaktion (Empfänger, Auftraggeber, Verwendungszweck, IBAN, Betrag), und zwar mit genau den Operatoren, die ein Mensch erwartet:

# Text: contains / equals / starts_with
"contains" -> String.contains?(actual, expected)
"equals" -> actual == expected
"starts_with" -> String.starts_with?(actual, expected)

# Betrag: lt / gt / between (inklusiv, reihenfolge-unabhängig)
"lt" -> Decimal.lt?(amount, value)
"gt" -> Decimal.gt?(amount, value)
"between" -> not below_low? and not above_high?

Und entscheidend: Regeln speisen nicht in das ML-Lernsignal aus Artikel 3 ein:

# Anders als set_group/2 schreibt eine Regel KEINE ClassifierCorrection.
# Regeln sind deterministisch und bleiben bewusst aus dem ML-Lernsignal.
defp apply_rule(transaction, rule) do
# ... weist die Gruppe der Regel zu, merged die Tags ...
end

Warum diese Trennung? Weil Regel und Modell verschiedene Wahrheiten ausdrücken. Eine Regel sagt: „Das ist per Definition so." Eine Korrektur sagt: „Das Modell hat geraten und lag daneben, hier ein Beispiel." Würde ich Regeln als Trainingsbeispiele einspeisen, würde ich das Modell mit Dingen „verbessern", die es nie hätte raten müssen, und damit das Signal verwässern, das aus echten Fehlern kommt.

Das Muster, das dahinter steht, gilt weit über meine App hinaus:

  • Deterministisch, wenn du die Antwort kennst. Regeln sind vorhersehbar, sofort wirksam, in Sekunden zu debuggen („welche Regel hat gegriffen?") und brauchen keine Rechenpower. Wenn ein if reicht, ist ein neuronales Netz keine Eleganz, sondern Ballast.
  • ML, wenn der Raum offen ist. Das Modell verdient sich seinen Platz genau dort, wo ich nicht alle Fälle aufzählen kann: beim unbekannten Händler, der neuen Abbuchung, dem kreativ formatierten Verwendungszweck.
  • Lass beide das tun, was sie am besten können. Die geschichtete Pipeline ist kein Kompromiss, sondern eine Arbeitsteilung: Gewissheit zuerst, Schätzung als Auffangnetz.

Der Hype legt nahe, möglichst viel durch ein Modell zu schicken. Gutes Engineering fragt das Gegenteil: Wie wenig muss durch das Modell, damit der teure, unsichere Teil so klein wie möglich bleibt? Jede Transaktion, die eine Regel abfängt, ist eine, bei der ich mir um Schwellwerte, Fehlklassifikation und Modell-Verfügbarkeit keine Gedanken machen muss.

Das ist für mich der Kern guter ML-Integration. Elixir macht es leicht, weil deterministischer Code und Modell-Inferenz im selben Modul nebeneinander stehen, ohne Sprachgrenze und ohne Service-Hop. Das Beste am ML in meiner App ist, dass ich es nur dort einsetze, wo es sich wirklich lohnt.

Danke fürs Lesen dieser Serie. Eine kleine App, fünf Entscheidungen, kein Python. Wenn dich der Code interessiert oder du selbst mit Nx/Bumblebee liebäugelst, schreib mir einfach, ich teile gern mehr.

Kommentare

Beliebte Posts aus diesem Blog

Splitting an ML model and a web app across two BEAM nodes — the technical blueprint

Jido in Practice: Agents in Elixir as Composable Actions

A Foundation Model in the BEAM: On-Chain Anomalies with Google TimesFM in Elixir