LLM-Features, die dein Framework
nicht neu erfinden.

Der Standardweg für "KI in meine App bringen" liegt irgendwo zwischen "das Vercel-SDK direkt verdrahten und beten" und "ein separates Agent-Framework mit eigener Datenbank einführen". Voltro geht einen dritten Weg: Agents, Tools, RAG, Streaming-Threads — allesamt First-Class-Primitive in derselben Runtime wie deine Mutations und deine reaktiven Queries. Das Vercel AI SDK übernimmt die Schwerarbeit; @voltro/ai gibt dir eine stabile Oberfläche, die mit deinen Tests funktioniert.

support.agent.tsx
TypeScript
// agents/support.agent.tsx — DESCRIPTOR (browser-safe wire contract)
import { defineAgent } from '@voltro/ai/agent'
import { Schema } from 'effect'

export const support = defineAgent({
  name:  'support',
  input: Schema.Struct({ prompt: Schema.String }),
})

// agents/support.agent.server.tsx — EXECUTOR (server-only behaviour)
import { defineAgentExecutor } from '@voltro/ai'
import { searchDocs } from '../tools/searchDocs.tool'

export default defineAgentExecutor(support, {
  system:   'You are a friendly support agent. Be concise.',
  tools:    { searchDocs },
  maxSteps: 8,
})

// The framework synthesises TWO procedures for free:
//   support.send     — kicks off a streaming turn
//   support.messages — reactive query, streams deltas

// On the client:
const { data: messages } = useSubscription(
  'app', ['support.messages', { threadId }], { threadId },
)
const send = useMutation('app', 'support.send')
// Live typewriter bubble is just a row with streaming: true.

Sechs Primitive für das AI-Feature, das du sowieso bauen wolltest.

defineAgent — das Chat-Primitiv

Eine einzige .agent.tsx-Datei deklariert einen LLM-Chat mit typisiertem Input, einem System-Prompt, optionalen Tools und einem Token-Budget. Das Framework synthetisiert automatisch eine streamende send-Action plus eine reaktive Query, die persistierte Message-Deltas in den Browser streamt.

defineTool — typisiertes Function-Calling

Eine *.tool.tsx-Datei deklariert einen Schema-typisierten Input und Output. Das Framework bindet sie als Model-Tool ein. Inputs werden validiert; das Ergebnis wird gegen das Output-Schema dekodiert, bevor das Modell es sieht.

pgvector-RAG out of the box

vector(1536) ist ein First-Class-Spaltentyp. Der vectorEmbedding()-Mixin fügt die Spalte, den HNSW-Index UND einen Re-Embed-Hook hinzu, der bei jedem Insert/Update das embed von @voltro/ai aufruft. nearestNeighbours('text', k) embeddet die Query inline.

Streaming als React-Zeile

runAssistant patcht eine persistierte Message-Zeile, während die Deltas eintreffen. Die reaktive Subscription streamt jeden Patch. Der Live-Schreibmaschineneffekt ist einfach eine Zeile mit streaming: true — kein manuelles WebSocket-Geflecht, kein SSE-Handler.

Durable Agent-Loops per Workflows

Für mehrstufiges Agent-Reasoning, das einen Crash überleben muss (der Model-Call, dann ein Tool-Call, dann das Warten auf eine menschliche Freigabe), wickle die Schleife in eine *.workflow.tsx. Jedes Schritt-Ergebnis wird journaled; die Wiederaufnahme spielt ab der Stelle weiter, an der sie abgebrochen ist.

Provider-Wechsel + Mock für Tests

@voltro/ai kapselt das Vercel AI SDK hinter einer stabilen Oberfläche. Tausche den Provider (Anthropic, OpenAI, lokales Ollama), ohne die Aufrufstellen anzufassen. mockAi(...) installiert einen deterministischen Provider für Tests — keine echten Model-Calls in der CI.

Der Agent-Loop IST die Runtime.

Ein Agent, der (a) Tokens in einen Browser streamen, (b) Tools aufrufen muss, die deine Datenbank berühren, (c) einen Deploy mitten im Loop überleben und (d) 1000 gleichzeitige Nutzer bedienen soll — das sind vier Probleme, die Voltros vorhandene Primitive bereits lösen. Reaktive Queries übernehmen das Streaming. Mutations + Actions übernehmen die Tool-Calls. Workflows übernehmen die Durability. Multi-Tenancy übernimmt die Isolation.

Wähle die richtige Form:

  • One-Shot-Prompt → Text. generateText(prompt). In jedem Handler.
  • Schema-beschränkter Output. generateObject(schema, prompt). In einer Mutation oder Action.
  • Live-Chat-Thread. defineAgent + useSubscription auf der synthetisierten messages-Query.
  • Langer Agent-Loop mit Freigaben. Wickle die Schleife in einen Workflow; nutze awaitSignal für die Freigabe.
  • RAG-Suche. vectorEmbedding()-Mixin an der Quell-Tabelle; nearestNeighbours auf der Query.

Eine typisierte Antwort, ein Tool und ein Flow, der auf einen Menschen wartet.

triage.ts
ts
// A schema out, not a string to parse and hope about.
const triage = await ai.generateObject({
  model:  'anthropic:claude-sonnet-4-5',
  schema: Schema.Struct({
    severity: Schema.Literal('low', 'medium', 'high'),
    summary:  Schema.String,
    owner:    Schema.optional(TeamId),
  }),
  prompt: ticket.body,
})

triage.severity   // 'low' | 'medium' | 'high' — typed, validated

Ein Modell, das ein Schema statt eines Strings zurückgibt, ist der Unterschied zwischen einem Feature und einer Demo. Die Pause ist die andere Hälfte: ein Freigabeschritt ist ein Workflow-Schritt — auf einen Menschen zu warten ist damit genauso durable wie auf eine API zu warten.

KI-Primitive im Detail.

Was heißt es, dass KI ein Primitiv ist?

Dass ein Prompt, ein Tool, ein Agent und eine Evaluation so deklariert werden wie eine Query oder eine Mutation — mit Schemas, einer Zugriffsentscheidung und einem Namen, den die Runtime kennt. Kein Helfer, den du in einen Handler importierst, sondern ein Ding, das das Framework validieren, instrumentieren, budgetieren und wiederholen kann.

Der unmittelbare Gewinn ist strukturierte Ausgabe, die tatsächlich strukturiert ist. Du deklarierst die erwartete Form, und die Runtime validiert die Antwort des Modells dagegen — ein Handler bekommt also ein typisiertes Objekt statt eines Strings, den er parsen und hoffen muss.

Der zweite Gewinn: alles andere weiß schon, was damit zu tun ist. Kosten und Token-Verbrauch werden pro Aufruf, pro Procedure und pro Mandant zugeordnet, weil der Aufruf ein bekanntes Primitiv ist; ein Lauf kann aufgezeichnet und wiederholt werden, weil die Runtime ihn gesehen hat.

Wie funktionieren Tools ohne handgeschriebenen Klebstoff?

Ein Tool ist eine Deklaration mit Input-Schema und Handler — und deine bestehenden Procedures lassen sich per Opt-in an der Deklaration, die sie ohnehin tragen, als Tools anbieten. Das Schema, gegen das eine Procedure validiert, ist dasselbe, das dem Modell gezeigt wird: es gibt keine zweite Beschreibung zum Synchronhalten.

Weil ein Tool eine Procedure ist, erbt es die Garantien, statt sie zu umgehen. Ein Agent, der ein Tool ruft, geht durch dieselbe Zugriffsentscheidung, dasselbe Tenant-Scoping und dieselbe Validierung wie ein Browser — ein Agent erreicht keine Daten, die sein Subject nicht erreichen darf.

Genau das überlassen die meisten nachgerüsteten Agent-Frameworks dir, und genau darauf kommt es an, sobald ein Agent Endnutzern ausgesetzt ist statt einem internen Skript.

Wie verhindere ich, dass ein Modellwechsel still die Qualität senkt?

Indem du ihn als Regressionstest behandelst. Agent-Läufe werden aufgezeichnet, und `voltro eval` spielt sie mit einem Judge gegen Golden Cases erneut ab und endet bei Qualitätsverlust mit einem Fehlercode — eine Prompt-Änderung oder ein Modell-Upgrade ist damit von CI gegated statt von dem beurteilt, der es zufällig ausprobiert hat.

Erwartungen sind die leichtere Variante: deklarier, was über ein Verhalten gelten soll, und lass Verletzungen dort melden, wo sie auftreten. Experimente decken die andere Richtung ab — deterministisches Bucketing, damit zwei Prompts oder zwei Modelle auf echtem Traffic vergleichbar sind.

Nichts davon braucht einen separaten Evaluationsdienst. Die Läufe sind bereits aufgezeichnet, weil die Aufrufe Primitive sind — die Evaluation liest also, was passiert ist, statt eine Näherung davon neu zu fahren.

Was hindert KI-Kosten daran, mich zu überraschen?

Ein deklariertes Kostenbudget, das ein Primitiv ist und kein Dashboard-Alarm. Weil die Runtime jeden Aufruf und seinen Token-Verbrauch sieht, ist das Überschreiten eines Budgets etwas, worauf sie reagieren kann — ablehnen, degradieren, umleiten — statt etwas, das jemand auf einer Rechnung bemerkt.

Die Zuordnung erfolgt pro Mandant wie pro Procedure — erst das macht nutzungsbasierte Preise oder Limits pro Kunde überhaupt ausdrückbar. Ein gemeinsamer Zähler über alle Mandanten kann die Frage, die ein SaaS wirklich hat, nicht beantworten.

Auch Caching greift: ein semantischer Cache kann eine fast identische Anfrage ohne Modellaufruf bedienen. Der Index, der das Fast-Duplikat findet, ist heute pro Prozess — eine prozessübergreifende semantische Suche bräuchte einen dauerhaften Index, was dokumentiert statt angedeutet ist.

Was deklariert statt geklebt wird.

KI-Primitive und was ihre Deklaration einbringt
PrimitivWas es einbringt
definePromptEin benannter, versionierbarer Prompt mit typisierten Eingaben statt eines Template-Strings im Handler.
defineToolEin schema-validiertes Tool; eine bestehende Procedure lässt sich per Opt-in als eines anbieten.
defineAgentEine Tool-nutzende Schleife, die Zugriff, Mandant und Validierung von den gerufenen Procedures erbt.
Strukturierte AusgabeEine deklarierte Ausgabeform, zur Laufzeit validiert — der Handler bekommt ein typisiertes Objekt statt eines Strings.
defineEvalAufgezeichnete Läufe mit einem Judge gegen Golden Cases wiederholen; ein Deploy-Gate statt Bauchgefühl.
defineCostBudgetToken- und Kostengrenzen, die die Runtime durchsetzen kann, zugeordnet pro Procedure und Mandant.

Häufige Fragen

Welche Modell-Anbieter werden unterstützt?

Die Schicht ist anbieter-agnostisch: ein Anbieter ist die Implementierung einer Spezifikation, ein Wechsel ist also eine Konfigurationsänderung und kein Umschreiben deiner Prompts und Tools. Ein Mock-Anbieter kommt für Tests mit, damit eine Suite nie von einem Netzwerkaufruf oder einem Budget abhängt.

Kann ein Agent Daten erreichen, die der Nutzer nicht darf?

Nein. Ein Tool ist eine Procedure — der Aufruf eines Agenten geht durch dieselbe Zugriffsentscheidung, dasselbe Tenant-Scoping und dieselbe Validierung wie jeder andere Aufrufer. Der Agent handelt als Subject statt neben dem Autorisierungssystem.

Wird die strukturierte Ausgabe wirklich validiert?

Ja, zur Laufzeit gegen das deklarierte Schema. Das ist der Unterschied zwischen typisierter Ausgabe und einer Typannotation über einem geparsten String — eine nicht passende Modellantwort ist ein Fehler an der Grenze statt einer Überraschung drei Schichten später.

Wie teste ich KI-Verhalten in CI?

Läufe aufzeichnen und mit `voltro eval` gegen Golden Cases und einen Judge wiederholen — das endet bei Regression mit einem Fehlercode. Für Unit-Tests liefert der Mock-Anbieter deterministische Antworten, damit eine Suite schnell und kostenlos ist.

Überlebt lang laufende KI-Arbeit einen Absturz?

Ja — dafür ist das Flows-Plugin da: durable Multi-Step-Pipelines, deterministisch oder agentic, mit Human-in-the-Loop-Schritten, journalisiert wie jeder andere Workflow, sodass ein Lauf fortsetzt statt neu zu starten.

AI ist nur eine weitere Handler-Form.

Öffne das Framework. Schau es dir selbst an.

Jede Primitive auf dieser Seite ist heute im Framework. Klone den Starter, lass `voltro dev` laufen, in zwei Minuten ist es auf dem Bildschirm.