Zum Inhalt springen

LLMs integrieren: Muster, die in Produktion bestehen

Praktische Muster für zuverlässige, kosteneffiziente LLM-Funktionen: Prompt-Engineering, Streaming, strukturierte Ausgaben, Fallbacks und Kosten.

3 Min. Lesezeit
Code-Editor zeigt KI-Integration mit Streaming-Ausgabe

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.

tstypescript
// 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",
    },
  });
}
tstypescript
// 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.

tstypescript
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.

tstypescript
// 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.

tstypescript
// 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.

tstypescript
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.

Wilfredo Rujel

Wilfredo Rujel

Full-Stack-Softwareentwickler

Diesen Beitrag teilenX