---
title: Assistent
description: >-
  Ein In-Page-Assistent, der in deinen Docs verankert ist — vorgeschlagene Fragen, eigene Anweisungen, Dimensionierung des Retrievals, Provider-Adapter vom Vercel AI Gateway bis zu jedem OpenAI-kompatiblen Endpunkt und die Server-Ausgabe, die er braucht.
---

Füge einen Assistenten hinzu, der Leserfragen in einem In-Page-Chat-Panel beantwortet, unterstützt von einem streamenden Server-Endpunkt und dem [AI SDK](https://ai-sdk.dev). Er ist opt-in, und statische Docs bleiben vollständig statisch, bis du ihn aktivierst:

```ts blume.config.ts lineNumbers
ai: {
  assistant: {
    enabled: true,
  },
}
```

Ohne weitere Angaben werden Antworten über das [Vercel AI Gateway](#adapters) von `openai/gpt-5.5` gestreamt. Ein anderes Modell oder einen anderen Provider wählst du mit einem [Adapter](#adapters).

## Vorgeschlagene Fragen [#suggested-questions]

Fülle den leeren Zustand mit ein paar Einstiegs-Prompts. Jeder wird als klickbarer Vorschlag dargestellt — klicke einen an, um ihn abzusenden — mit einem optionalen [Lucide-Icon](/docs/content/components#icon) neben dem Label:

```ts blume.config.ts lineNumbers
ai: {
  assistant: {
    enabled: true,
    suggestions: [
      { label: "What is Blume?", icon: "rocket" },
      { label: "How do I write a docs page?", icon: "file-text" },
      { label: "How do I configure the theme?", icon: "settings" },
    ],
  },
}
```

`label` ist die Frage, die gestellt wird; `icon` ist optional. Lässt du `suggestions` weg (oder leer), öffnet sich das Panel mit einem einfachen Eingabefeld.

## Eigene Anweisungen [#custom-instructions]

Ergänze deinen eigenen System-Prompt-Text mit `instructions` — Identität, Sprache, Tonfall oder alles andere, was der Assistent im Blick behalten soll:

```ts blume.config.ts lineNumbers
ai: {
  assistant: {
    enabled: true,
    instructions:
      "You are Bloomy, the Acme docs assistant. Answer in the language the question was asked in, and keep answers under three paragraphs.",
  },
}
```

Dein Text wird an die eingebauten Anweisungen **angehängt** und ersetzt sie nicht: Der eingebaute Teil trägt den [Grounding](#grounding)-Vertrag — antworte nur anhand der abgerufenen Seiten, zitiere sie als Markdown-Links — von dem die Zitate im Chat-Panel abhängen, deshalb bleibt er unabhängig von deinen Ergänzungen erhalten.

## Grounding

Der Assistent ist **in deinen Docs verankert**. Für jede Frage ruft er die relevantesten Seiten ab — über denselben lexikalischen [Orama](/docs/configuration/search)-Index, der auch die Suche auf der Seite antreibt — und injiziert sie in den System-Prompt des Modells, sodass Antworten aus deinen Inhalten statt aus dem Eigenwissen des Modells kommen. Der Assistent wird angewiesen, nur anhand der abgerufenen Seiten zu antworten, zu sagen, wenn etwas nicht abgedeckt ist, und die Seiten zu zitieren, aus denen er geschöpft hat.

Die Seite, auf der sich der Leser gerade befindet, wird dem Kontext zuerst hinzugefügt und dient dazu, das Retrieval auf die Sprache dieser Seite zu beschränken, damit Antworten relevant für die aktuelle Stelle in den Docs bleiben. Das Retrieval läuft zur Anfragezeit aus einem in den Build eingebackenen Snapshot, funktioniert also unabhängig von deinem [Such](/docs/configuration/search)-Provider — selbst mit `search: false` — und braucht keine Konfiguration.

Grounding ist für jeden Adapter aktiv außer **[Inkeep](#inkeep)**, das sein eigenes Retrieval über die Inhalte ausführt, die du in seinem Dashboard indexiert hast.

## Retrieval-Größe [#retrieval-size]

Wie viel Dokumentation eine Frage mitträgt, ist der größte Hebel dafür, wie lange der Leser auf das erste Wort wartet: Das Modell liest jedes injizierte Zeichen, bevor es ein Token ausgibt. Bei einem gehosteten Frontier-Modell ist das unsichtbar, bei einem selbst gehosteten Backend dominiert es. `retrieval` dimensioniert das:

```ts blume.config.ts lineNumbers
ai: {
  assistant: {
    enabled: true,
    retrieval: {
      maxResults: 3, // fewer pages retrieved per question
      excerptChars: 1200, // shorter excerpt from each one
      contextBudget: 3000, // smaller total injection
    },
  },
}
```

| Option | Standard | Beschreibung |
| --- | --- | --- |
| `maxResults` | `6` | Pro Frage abgerufene Dokumente. |
| `excerptChars` | `2000` | Zeichen, die von jeder abgerufenen Seite behalten werden. |
| `contextBudget` | `10000` | Insgesamt injizierte Zeichen über alle Auszüge hinweg. |

Die drei sind nicht austauschbar. `contextBudget` begrenzt die gesamte Injektion, `excerptChars` entscheidet, wie tief der Auszug in eine einzelne lange Seite hineinreicht — erhöhe es, wenn eine Seite die ganze Antwort enthält und der Auszug sie abschneidet — und `maxResults` begrenzt, wie viele Seiten das Retrieval hinzufügt. Die Seite, die der Leser gerade ansieht, wird zusätzlich zu den abgerufenen injiziert, eine Antwort kann also bis zu eine Seite mehr zitieren als `maxResults`.

Die Standardwerte passen zu einem gehosteten Modell. Senke sie, wenn du von eigener Hardware auslieferst und die Zeit bis zum ersten Token wichtiger ist als der Recall; Antworten bleiben in beiden Fällen verankert, und der Assistent ist angewiesen, zu sagen, wenn etwas nicht abgedeckt ist, statt die Lücke zu füllen.

## Externer Endpunkt [#external-endpoint]

Du hast schon ein API-Backend für AI? Richte das Panel darauf aus und halte den Docs-Build statisch:

```ts blume.config.ts lineNumbers
ai: {
  assistant: {
    enabled: true,
    endpoint: "https://api.example.com/v1/docs/ask",
  },
}
```

Blume sendet denselben `POST`-Body wie seine eingebaute Route:

```json
{
  "messages": [{ "role": "user", "content": "How do I deploy?" }],
  "page": { "path": "/deployment" }
}
```

Gib eine erfolgreiche Antwort zurück, deren Body ein reiner UTF-8-Textstream ist. Liegt der Endpunkt auf einem anderen Origin, erlaube den Docs-Origin per CORS: akzeptiere `OPTIONS` und `POST`, lasse den Request-Header `content-type` zu und gib die CORS-Header sowohl beim Preflight als auch bei der gestreamten Antwort zurück. Mit gesetztem `endpoint` erzeugt Blume die Chat-UI, aber keine Server-Route, keinen Grounding-Snapshot, keine Provider-Abhängigkeit und keine Warnung zu Provider-Secrets; dein Backend verantwortet Retrieval, Authentifizierung, Rate Limiting, Modellzugriff und Zitate. Ein daneben gesetzter Adapter wird ignoriert.

## Cross-Origin-Aufrufer [#cross-origin-callers]

Der generierte Endpunkt antwortet dem In-Page-Assistenten auf dessen eigenem Origin. Um ihn auch von einer anderen Site aus aufzurufen — etwa von einer Marketing-Seite mit einem Frage-Feld — führe den Origin dieser Site in `cors` auf:

```ts blume.config.ts lineNumbers
ai: {
  assistant: {
    enabled: true,
    cors: ["https://www.example.com"],
  },
}
```

Die Route antwortet dann auf den `OPTIONS`-Preflight des Browsers und nennt einen aufgeführten Origin bei jeder Antwort — bei der gestreamten Antwort genauso wie bei den Fehler-Status, damit der Aufrufer einen abgelehnten Body von einem Provider-Ausfall unterscheiden kann. Origins, die nicht aufgeführt sind, bekommen keinen Header und bleiben der Same-Origin-Regel des Browsers unterworfen. Jeder Eintrag wird auf seinen Origin reduziert, `https://www.example.com/docs/` und `https://www.example.com` bedeuten also dasselbe. Damit jede Seite die Route aufrufen darf, führe `"*"` statt einzelner Origins auf.

Der Aufrufer sendet denselben `POST`-Body, den der Vertrag für den [externen Endpunkt](#external-endpoint) beschreibt, und liest denselben Textstream zurück. Sende ihn als JSON mit einem `content-type: application/json`-Header:

```ts
const response = await fetch("https://docs.example.com/api/ask", {
  body: JSON.stringify({
    messages: [{ role: "user", content: "How do I deploy?" }],
  }),
  headers: { "content-type": "application/json" },
  method: "POST",
});
```

Der Content-Type ist entscheidend: Astros Cross-Site-Request-Prüfung weist einen Cross-Origin-`POST` ohne Content-Type oder mit einem formularartigen wie `text/plain` mit einem 403 ab, bevor die Route läuft, und diese Antwort trägt keine CORS-Header, sodass der Browser sie als Netzwerkfehler statt als Status meldet. Der Preflight erlaubt alle Request-Header, die der Aufrufer anfragt, ein Fetch-Wrapper, der eigene Header hinzufügt, braucht also keine zusätzliche Konfiguration.

`cors` betrifft nur die generierte Route; mit einem externen `endpoint` ist CORS Sache dieses Backends, und beides zugleich zu setzen ist ein Konfigurationsfehler. Der Endpunkt bleibt in beiden Fällen unauthentifiziert, die Hinweise zum [Rate Limiting](#rate-limiting) gelten also auch für Cross-Origin-Traffic.

## Server-Ausgabe erforderlich [#server-output-required]

Blumes eingebautes Assistenten-Backend ist eine Server-Route (`POST /api/ask`) und kann daher nicht auf einem statischen Build laufen. Gib einen Host-Adapter aus `blume/deploy` an, um zur Server-Ausgabe zu wechseln:

```ts blume.config.ts lineNumbers
import { vercel } from "blume/deploy";

export default defineConfig({
  deployment: vercel(),
});
```

Ein statischer Build mit aktiviertem Assistenten und ohne externen `endpoint` schlägt sofort fehl, mit einer Meldung, die dich auffordert, einen Host-Adapter zu setzen. Die Adapter findest du unter [Deployment](/docs/deployment).

## Adapter [#adapters]

`provider` wählt das Backend, das antwortet. Sein Wert ist ein **Adapter**: eine kleine Funktion, die aus `blume/ai` exportiert wird, die eigenen Optionen dieses Backends entgegennimmt und einen einfachen Deskriptor zurückgibt, den Blume in die generierte Route schreibt. Jeder Adapter verantwortet sein Modell, die Umgebungsvariable, aus der sein Key gelesen wird, wie er [Reasoning](#reasoning) abbildet und welches Provider-SDK er braucht — es gibt also keinen gemeinsamen Satz an Feldern, der über Backends hinweg abgeglichen werden müsste:

```ts blume.config.ts lineNumbers
import { defineConfig } from "blume";
import { openrouter } from "blume/ai";

export default defineConfig({
  ai: {
    assistant: {
      enabled: true,
      provider: openrouter({ model: "anthropic/claude-sonnet-4-5" }),
    },
  },
});
```

| Adapter | Antwortet mit | API-Key-Umgebungsvariable | Zu installierendes SDK |
| --- | --- | --- | --- |
| [`gateway()`](#vercel-ai-gateway) (Standard) | einem `provider/model`-String über das Vercel AI Gateway | `AI_GATEWAY_API_KEY` | keins — bei Blume enthalten |
| [`openrouter()`](#openrouter) | jedem [OpenRouter](https://openrouter.ai)-Modell | `OPENROUTER_API_KEY` | `@openrouter/ai-sdk-provider` |
| [`llmgateway()`](#llmgateway) | jedem [LLMGateway](https://llmgateway.io)-Modell | `LLMGATEWAY_API_KEY` | `@ai-sdk/openai-compatible` |
| [`inkeep()`](#inkeep) | einem [Inkeep](https://inkeep.com)-QA-Modell | `INKEEP_API_KEY` | `@ai-sdk/openai-compatible` |
| [`openaiCompatible()`](#openai-compatible-endpoints) | dem, was dein Endpunkt ausliefert | die `apiKeyEnv`, die du angibst | `@ai-sdk/openai-compatible` |

Die SDKs sind optionale Peer-Dependencies, füge deinem Projekt also das hinzu, das dein Adapter braucht (z. B. `npm install @openrouter/ai-sdk-provider`). Fehlt es, bricht `blume build` ab, bevor Vite läuft, und nennt das Paket und den Befehl, der es installiert; auch [`blume doctor`](/docs/cli/doctor) meldet es.

Der Deskriptor, den ein Adapter zurückgibt, besteht aus reinen Daten — seine Art, seine Optionen, die Umgebungsvariablen, die er liest, und das SDK, das er braucht —, sodass die generierte Route (und die per [Eject](/docs/configuration/customization#eject) übernommene) ihn als Literale einbettet und das Provider-SDK per Namen importiert. Nichts liest `blume.config.ts` zur Anfragezeit, und kein Secret wird je in eine Route geschrieben: Adapter nehmen den **Namen** der Umgebungsvariable entgegen, die den Key enthält, und die Route liest den Wert über Astros [`getSecret()`](https://docs.astro.build/en/guides/environment-variables/#retrieving-secrets-programmatically), sodass jeder Deployment-Adapter ihn auf seine eigene Weise bereitstellt: als Umgebungsvariablen auf Node, Vercel und Netlify und über die [Bindings](https://docs.astro.build/en/guides/integrations-guide/cloudflare/#environment-variables-and-secrets) des Workers auf Cloudflare.

### Vercel AI Gateway

Der Standard. `model` ist ein `provider/model`-String, du wechselst Modelle also durch Änderung dieses Strings (`openai/gpt-5.5`, `anthropic/claude-sonnet-4-5` und so weiter), ohne ein Provider-SDK installieren zu müssen. Das Gateway liest `AI_GATEWAY_API_KEY` aus deiner Umgebung und wird beim Deployment auf Vercel automatisch verdrahtet, wo es sich auch mit dem OIDC-Token des Deployments authentifizieren kann:

```ts blume.config.ts lineNumbers
import { defineConfig } from "blume";
import { gateway } from "blume/ai";

export default defineConfig({
  ai: {
    assistant: {
      enabled: true,
      provider: gateway({ model: "anthropic/claude-sonnet-4-5" }),
    },
  },
});
```

Lässt du `provider` weg, entspricht das `gateway({ model: "openai/gpt-5.5" })`.

### OpenRouter

Jedes Modell auf [OpenRouter](https://openrouter.ai), über dessen eigenen AI-SDK-Provider:

```ts blume.config.ts lineNumbers
import { defineConfig } from "blume";
import { openrouter } from "blume/ai";

export default defineConfig({
  ai: {
    assistant: {
      enabled: true,
      provider: openrouter({
        model: "anthropic/claude-sonnet-4-5",
        reasoning: "none",
      }),
    },
  },
});
```

### LLMGateway

Jedes Modell auf [LLMGateway](https://llmgateway.io), über dessen OpenAI-kompatiblen Endpunkt:

```ts blume.config.ts lineNumbers
import { defineConfig } from "blume";
import { llmgateway } from "blume/ai";

export default defineConfig({
  ai: {
    assistant: {
      enabled: true,
      provider: llmgateway({ model: "openai/gpt-5.5" }),
    },
  },
});
```

`baseUrl` überschreibt den voreingestellten Endpunkt (`https://api.llmgateway.io/v1`), wenn du LLMGateway selbst betreibst.

### Inkeep

[Inkeep](https://inkeep.com) antwortet anhand der Inhalte, die du im Inkeep-Dashboard indexiert hast — es führt sein eigenes Retrieval aus — deshalb lässt Blume es **ungegroundet**: Es wird kein Snapshot der Seiten dieser Site injiziert, und die Optionen zur [Retrieval-Größe](#retrieval-size) gelten nicht. Eine Reasoning-Steuerung hat es auch nicht, deshalb akzeptiert der Adapter kein `reasoning`:

```ts blume.config.ts lineNumbers
import { defineConfig } from "blume";
import { inkeep } from "blume/ai";

export default defineConfig({
  ai: {
    assistant: {
      enabled: true,
      provider: inkeep({ model: "inkeep-qa-expert" }),
    },
  },
});
```

`baseUrl` überschreibt den voreingestellten Endpunkt (`https://api.inkeep.com/v1`).

### OpenAI-kompatible Endpunkte [#openai-compatible-endpoints]

Jeder Endpunkt, der die OpenAI-API spricht, funktioniert über `openaiCompatible()` — gib seine `baseUrl`, das `model`, das er ausliefert, und die Umgebungsvariable mit seinem Key an. Ein generischer Endpunkt hat für keins davon eine Voreinstellung, deshalb sind alle drei Pflicht; `name` ist der Provider-Name, den das AI SDK meldet, und ist standardmäßig `openai-compatible`:

```ts blume.config.ts lineNumbers
import { defineConfig } from "blume";
import { openaiCompatible } from "blume/ai";

export default defineConfig({
  ai: {
    assistant: {
      enabled: true,
      provider: openaiCompatible({
        baseUrl: "https://my-gateway.example.com/v1",
        apiKeyEnv: "MY_GATEWAY_API_KEY",
        model: "gpt-4o",
        name: "my-gateway",
      }),
    },
  },
});
```

### Optionen, die jeder Adapter akzeptiert [#options-every-adapter-takes]

**`apiKeyEnv`** lässt einen Adapter auf eine andere Umgebungsvariable als seine Standardvariable zeigen — `gateway({ apiKeyEnv: "DOCS_GATEWAY_KEY" })` liest diese Variable statt `AI_GATEWAY_API_KEY`, und die Warnung zu fehlenden Secrets bei `blume dev`/`build` prüft ebenfalls sie. Solange der Key nicht gesetzt ist, antwortet die deployte Route mit `503` und einer Meldung, die die Variable nennt. Die Route liest einen Request-Body nur bis 64 KB und beantwortet alles Größere mit `413`.

**`headers`** sendet bei jedem Aufruf statische Request-Header mit — etwa einen Header, der den Aufrufer identifiziert, bei einem gemeinsam genutzten Backend, damit dessen eigene Observability oder sein Rate Limiting deine Docs von anderem Traffic unterscheiden kann:

```ts blume.config.ts lineNumbers
provider: openaiCompatible({
  baseUrl: "https://llm.internal.example.com/v1",
  apiKeyEnv: "INTERNAL_LLM_API_KEY",
  model: "gpt-4o",
  headers: { "X-Caller-Id": "docs" },
}),
```

Die Werte werden unverändert in die generierte Route geschrieben, halte Secrets also in `apiKeyEnv` statt in `headers`. Der `Authorization`-Header des API-Keys wird zuerst angewendet, ein eigener Header kann ihn also nicht verdrängen.

**`providerOptions`** reicht alles andere direkt an die [`providerOptions`](https://ai-sdk.dev/docs/foundations/prompts#provider-options) des AI SDK durch, in der eigenen Form des SDK — geschlüsselt nach Provider, dann nach Option —, sodass eine neue Modellsteuerung nie ein eigenes Blume-Feld braucht:

```ts blume.config.ts lineNumbers
provider: gateway({
  model: "openai/gpt-5.5",
  providerOptions: { openai: { textVerbosity: "low" } },
}),
```

Blume bildet nur die Optionen ab, die es benennt (`model`, `reasoning`, `apiKeyEnv`, `headers`), und leitet `providerOptions` unverändert weiter. Es muss also JSON sein — es wird in die Route eingebettet — und den Key verwenden, den der zugrunde liegende Provider erwartet (`openai` für ein OpenAI-Modell hinter dem Gateway, `openrouter` bei OpenRouter). Den Assistenten zu aktivieren schaltet außerdem React für die In-Page-Island ein — siehe [Customization](/docs/configuration/customization#interactive-islands).

## Reasoning

Reasoning-Modelle denken nach, bevor sie antworten, und wie viel sie das standardmäßig tun, unterscheidet sich je Modell. Bei verankerten Docs-Q&A tragen die abgerufenen Auszüge die Antwort, der größte Teil dieses Nachdenkens ist also Latenz, die der Leser aussitzt. Die Option `reasoning` eines Adapters legt fest, wie viel das Modell nachdenkt: `"none"`, `"minimal"`, `"low"`, `"medium"`, `"high"` oder `"xhigh"`:

```ts blume.config.ts lineNumbers
provider: gateway({ model: "openai/gpt-5.5", reasoning: "none" }),
```

Jeder Adapter sendet die Stufe als die eigene Reasoning-Steuerung seines Backends, deshalb liegt sie beim Adapter statt bei `assistant`:

| Adapter | Was aus der Stufe wird |
| --- | --- |
| `gateway()` | Die [`reasoning`](https://ai-sdk.dev/docs/ai-sdk-core/reasoning)-Aufrufoption des AI SDK, die das Gateway auf die Einstellung des Modells abbildet — etwa auf OpenAIs `reasoning_effort`. |
| `openrouter()` | OpenRouters `reasoning.effort`, am Modell gesetzt. Sein Provider ignoriert die Aufrufoption des AI SDK, deshalb wird die Stufe dort platziert, wo OpenRouter sie liest. |
| `llmgateway()` | `reasoning_effort` im Request, über die Aufrufoption des AI SDK. |
| `openaiCompatible()` | `reasoning_effort` im Request, der Endpunkt muss diesen Parameter also akzeptieren. |
| `inkeep()` | Nicht verfügbar. Inkeep führt seine eigene QA-Pipeline ohne Reasoning-Steuerung aus, deshalb hat der Adapter keine `reasoning`-Option, und eine zu setzen ist ein Konfigurationsfehler. |

Das Modell muss die von dir gewählte Stufe unterstützen: OpenAI weist eine Stufe ab, die ein Modell nicht anbietet (`"none"` und `"xhigh"` gibt es nur bei manchen), prüfe also die Dokumentation des Modells, bevor du eine setzt. Lässt du es weg, bleibt der Standard des Modells erhalten. Wie die [Retrieval-Größe](#retrieval-size) tauscht es Gründlichkeit gegen die Zeit bis zum ersten Token, und Antworten bleiben in beiden Fällen verankert.

## Analytics

Mit einem konfigurierten [Analytics-Provider](/docs/configuration/analytics) meldet der Assistent seine Nutzung über dasselbe `track()`, das auch das Seiten-Feedback-Widget nutzt, sodass Fragen direkt neben deinen Seitenaufrufen landen:

| Ereignis | Wann | Eigenschaften |
| --- | --- | --- |
| `ask` | Eine Frage wird abgesendet | `path`, `questionChars` |
| `ask_answer` | Die Antwort ist fertig gestreamt | `path`, `questionChars`, `ms`, `chars` |
| `ask_error` | Die Anfrage schlägt fehl, bricht ab oder kommt leer zurück | `path`, `questionChars`, `ms`, `status` |

`path` ist die Seite, von der aus der Leser gefragt hat (der ausgelieferte Pfadname, er stimmt also mit dem Feedback-Widget und deinen Seitenaufrufen unter einem `base` überein), `questionChars` die Länge der Frage, `ms` die Zeit vom Absenden der Frage bis zum letzten Chunk und `chars` die Länge der Antwort. `status` ist der HTTP-Status: `0`, wenn überhaupt keine Antwort ankam (offline, DNS, CORS), und `200`, wenn die Antwort in Ordnung war, ihr Stream aber mitten in der Antwort abbrach — so zeigt sich ein Provider- oder Credential-Fehler, da das Backend seine Header schon gesendet hat — oder nichts lieferte. Wird die Konversation mitten in der Antwort geleert, wird keines der beiden Ergebnisse gemeldet.

Der Text der Frage erreicht nie einen Provider: Es ist freie Lesereingabe (eingefügte Keys, Fehlerlogs, Namen), die gegen die Bedingungen und die Größenlimits pro Wert der meisten Provider verstoßen würde. Er reist nur auf dem DOM-Event `blume:track`, als `question` in `detail.props`, sodass ein Listener, den du schreibst, ihn dorthin weiterleiten kann, wo er nach deiner Entscheidung hingehört. Eine eigene Chat-UI, die auf `useAssistant` aus `blume/hooks` aufbaut, meldet dieselben Ereignisse. Ist kein Provider konfiguriert, sind die eingebauten Provider-Aufrufe No-ops, aber das `blume:track`-Event wird trotzdem ausgelöst, sodass eine eigene Integration, die darauf lauscht, sie erhält.

## Rate Limiting

Der Endpunkt `POST /api/ask` ist **nicht authentifiziert** — er muss es sein, damit der In-Page-Assistent ihn aufrufen kann. Blume validiert jede Anfrage — verwirft fehlerhafte Bodies, begrenzt sie auf 1–40 Nachrichten und akzeptiert nur die Rollen `user`/`assistant`, damit ein Aufrufer keinen eigenen System-Prompt injizieren und die Route als allgemeinen LLM-Proxy umnutzen kann — um zu begrenzen, wie viel ein einzelner Aufruf bei deinem Modell verbrauchen kann; verhindern kann es aber nicht, dass jemand den Endpunkt wiederholt aufruft. Wenn Kostenmissbrauch ein Thema ist, stelle einen Rate Limiter vor die Route — das Edge-Rate-Limiting deines Hosters (z. B. von Vercel), eine Middleware oder die Ausgabelimits pro Key bei deinem Modellanbieter.

Der Endpunkt wird im [Agent-Readability-Manifest](/docs/discoverability/agent-discovery#agent-readability) neben der übrigen maschinenlesbaren Oberfläche der Site bekannt gemacht.
