LLMs integrieren: Muster, die in Produktion bestehen
Praktische Muster für zuverlässige, kosteneffiziente LLM-Funktionen: Prompt-Engineering, Streaming, strukturierte Ausgaben, Fallbacks und Kosten.

LLMs sind jetzt Infrastruktur
2024 waren KI-Funktionen noch ein Unterscheidungsmerkmal. 2026 sind sie Grundvoraussetzung. Die Frage ist nicht mehr, ob man LLMs integriert, sondern wie man es zuverlässig, wartbar und wirtschaftlich tut.
Dieser Leitfaden behandelt Produktionsmuster – keine Demos.
Muster 1: Streaming zuerst
Ein LLM kann 5 bis 30 Sekunden brauchen, um eine vollständige Antwort zu generieren. Ohne Streaming starrt der Nutzer auf einen leeren Bildschirm. Mit Streaming sieht er den Inhalt sofort erscheinen.
// app/api/chat/route.ts
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
export async function POST(req: Request) {
const { messages } = await req.json();
const stream = client.messages.stream({
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages,
});
// Return a ReadableStream that the client can consume incrementally
const readable = new ReadableStream({
async start(controller) {
for await (const chunk of stream) {
if (
chunk.type === "content_block_delta" &&
chunk.delta.type === "text_delta"
) {
controller.enqueue(new TextEncoder().encode(chunk.delta.text));
}
}
controller.close();
},
});
return new Response(readable, {
headers: {
"Content-Type": "text/plain; charset=utf-8",
"Transfer-Encoding": "chunked",
},
});
}// Client-side — render as the stream arrives
async function streamResponse(prompt: string) {
const response = await fetch("/api/chat", {
method: "POST",
body: JSON.stringify({ messages: [{ role: "user", content: prompt }] }),
});
const reader = response.body!.getReader();
const decoder = new TextDecoder();
let result = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
result += decoder.decode(value, { stream: true });
setOutput(result); // Update UI incrementally
}
}Muster 2: Strukturierte Ausgaben für zuverlässiges Parsen
Freier Text aus einem LLM ist als strukturierte Daten unzuverlässig. Nutze Tools bzw. Function Calling, um typisierte Antworten zu erhalten.
import Anthropic from "@anthropic-ai/sdk";
import { z } from "zod";
const client = new Anthropic();
const ProductExtractionSchema = z.object({
name: z.string(),
price: z.number(),
currency: z.string(),
availability: z.enum(["in_stock", "out_of_stock", "limited"]),
features: z.array(z.string()),
});
type ProductExtraction = z.infer<typeof ProductExtractionSchema>;
async function extractProductInfo(rawText: string): Promise<ProductExtraction> {
const response = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
tools: [
{
name: "extract_product",
description: "Extract structured product information from text",
input_schema: {
type: "object",
properties: {
name: { type: "string" },
price: { type: "number" },
currency: { type: "string" },
availability: {
type: "string",
enum: ["in_stock", "out_of_stock", "limited"],
},
features: { type: "array", items: { type: "string" } },
},
required: ["name", "price", "currency", "availability", "features"],
},
},
],
tool_choice: { type: "tool", name: "extract_product" },
messages: [{ role: "user", content: rawText }],
});
const toolUse = response.content.find((c) => c.type === "tool_use");
if (!toolUse || toolUse.type !== "tool_use") {
throw new Error("LLM did not use the extraction tool");
}
return ProductExtractionSchema.parse(toolUse.input);
}Das liefert dir typsichere, zur Laufzeit validierte Ausgaben – kein fehleranfälliges Parsen von LLM-Antworten per Regex mehr.
Muster 3: Prompt-Verwaltung
Prompts sind Code. Behandle sie auch so.
// prompts/summarize.ts — versioned, testable prompts
export const SUMMARIZE_PROMPT = {
version: "v3",
system: `You are a technical content summarizer.
Your summaries are:
- Concise: 3-5 bullet points maximum
- Technical: preserve exact technical terms
- Actionable: focus on what the reader should know or do
Never include filler phrases like "In this article..." or "To summarize..."`,
user: (content: string, maxBullets: number = 5) =>
`Summarize the following technical content in ${maxBullets} bullet points:\n\n${content}`,
};
// Use the prompt
const response = await client.messages.create({
model: "claude-sonnet-4-6",
system: SUMMARIZE_PROMPT.system,
messages: [{ role: "user", content: SUMMARIZE_PROMPT.user(articleText) }],
max_tokens: 512,
});Bewahre Prompts in eigenen Dateien auf, versioniere sie und schreibe Tests gegen Referenzausgaben für kritische Prompts.
Muster 4: Kostenmanagement
Die Kosten für LLMs skalieren mit der Nutzung. Ungecachte, ungedrosselte LLM-Aufrufe können in großem Maßstab überraschend teuer werden.
// 1. Cache responses for identical inputs
import { createHash } from "crypto";
async function cachedLLMCall(prompt: string, options: LLMOptions) {
const cacheKey = createHash("sha256")
.update(`${options.model}:${options.system}:${prompt}`)
.digest("hex");
const cached = await redis.get(`llm:${cacheKey}`);
if (cached) return JSON.parse(cached);
const response = await callLLM(prompt, options);
// Cache for 24 hours — same input → same output
await redis.setEx(`llm:${cacheKey}`, 86400, JSON.stringify(response));
return response;
}
// 2. Use the right model for the task
const MODEL_TIERS = {
classification: "claude-haiku-4-5-20251001", // Fast, cheap, good for routing
summarization: "claude-sonnet-4-6", // Balanced capability and cost
complex_reasoning: "claude-opus-4-6", // Use sparingly for hard problems
};
// 3. Track costs per feature
async function trackLLMCost(
feature: string,
inputTokens: number,
outputTokens: number,
model: string,
) {
const cost = calculateCost(model, inputTokens, outputTokens);
await metrics.increment("llm.cost", cost, { feature, model });
await metrics.increment("llm.tokens.input", inputTokens, { feature });
await metrics.increment("llm.tokens.output", outputTokens, { feature });
}Muster 5: Kontrollierte Degradation
LLM-APIs haben Latenz, Rate Limits und gelegentliche Ausfälle. Deine Funktion sollte kontrolliert degradieren, statt komplett auszufallen.
async function getAISuggestions(content: string): Promise<Suggestion[]> {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000); // 5s timeout
try {
const suggestions = await callLLM(content, { signal: controller.signal });
clearTimeout(timeout);
return suggestions;
} catch (error) {
clearTimeout(timeout);
if (error.name === "AbortError") {
console.warn("LLM timeout — using rule-based fallback");
} else {
console.error("LLM error:", error);
}
// Fall back to deterministic, rule-based suggestions
return getRuleBasedSuggestions(content);
}
}Habe immer einen Fallback. LLM-gestützte Funktionen sollten das Erlebnis verbessern, nicht zu einer Abhängigkeit werden, die es zum Absturz bringt.
Die Produktions-Checkliste
Bevor du eine LLM-Funktion ausrollst:
- Streaming für jede Antwort, die länger als 1 Sekunde dauert
- Strukturierte Ausgaben für alle Daten, die geparst werden
- Validierung und Bereinigung von Eingaben (Prompt Injection ist real)
- Zwischenspeicherung von Antworten für wiederholte Eingaben
- Kostenerfassung pro Funktion
- Timeout- und Fallback-Verhalten
- Rate Limiting pro Nutzer/IP
- Protokollierung von Ein- und Ausgaben (mit Vorsicht – Datenschutz beachten)
- Evaluationssuite: 20+ Testfälle mit erwarteten Ausgaben
LLMs sind mächtige Werkzeuge. Erst das Engineering drumherum entscheidet, ob deine KI-Funktion ein Wettbewerbsvorteil oder eine Belastung ist.


