Design wechseln

LLM strukturierte Ausgabe: JSON-Schema-Erzwingung und zuverlässige Tool-Aufrufe

Im Produktionsbetrieb Alarm: Agent-Tool-Aufruf schlägt fehl, fünf Retries hintereinander – alles Parameterformatfehler. Im Log: Das Feld city sollte "北京" sein, das LLM liefert {"name": "北京", "id": null}. Parser bricht ab, die gesamte Datenpipeline steht still.

Das war ein großer Fehler, den wir letztes Jahr gemacht haben.

Seitdem habe ich mich systematisch mit strukturierter LLM-Ausgabe beschäftigt – von OpenAI Structured Outputs über Anthropic Tool Use bis Instructor Auto-Retry und Outlines constrained Decoding. Anfangs dachte ich, ein „besserer Prompt“ reiche – tatsächlich ist es eine Frage der Zuverlässigkeitsarchitektur, nicht des Promptings.

Dieser Artikel stellt die dreistufige Zuverlässigkeitsarchitektur vor: Parameter-Validierung, Fehler-Retry, constrained Decoding. Am Ende vergleichen wir OpenAI, Claude und Gemini und geben produktionsreife Code-Vorlagen mit.

1. Warum strukturierte Ausgabe das Fundament jedes Agents ist

Zuerst das Problem „Format Drift“ – kein Einzelfall, sondern der Albtraum jedes Agent-Entwicklers.

Drei Formen des Format Drift

Erstens: fehlende Felder. Sie erwarten ein Objekt mit name, age, email – das LLM liefert {"name": "张三"}. Die anderen Felder fehlen. Nicht immer, aber gelegentlich. In Produktion bedeutet „gelegentlich“ „unweigerlich“.

Zweitens: Typfehler. In der Doku steht klar: user_id ist Integer. Das LLM liefert "user_id": "12345" als String. Pydantic wirft einen Fehler, die gesamte Aufrufkette bricht ab.

Drittens: überflüssiger Inhalt. Am heimtückischsten. Sie wollen JSON – davor kommt „Here is the response:“, danach „I hope this helps!“. Der JSON-Parser scheitert.

5–10 %
JSON-Mode-Fehlerrate
<0,1 %
Structured-Outputs-Fehlerrate

OpenAIs offizielle Daten sprechen für sich: JSON Mode (nur gültiges JSON) scheitert in 5–10 % der Fälle, Structured Outputs (Schema-Erzwingung) unter 0,1 % – zwei Größenordnungen Unterschied.

Warum das so wichtig ist

„Parsing schlägt fehl – ein paar Retries mehr.“ Klingt einfach.

Retry ist aber nicht kostenlos.

API-Kosten. Ein GPT-4-Aufruf kostet schnell ein paar Cent, fünf Retries multiplizieren das. Bei 100.000 Anfragen pro Tag und durchschnittlich zwei Retries pro Request – rechnen Sie selbst.

Latenz. Ein Aufruf dauert zwei Sekunden, drei Retries: über sechs Sekunden Wartezeit. In Echtzeit-Dialogen inakzeptabel.

Nutzererlebnis. Wetteranfrage, Agent hängt zehn Sekunden, dann „Systemfehler“. Beim nächsten Mal kommt der Nutzer nicht wieder.

Strukturierte Ausgabe ist kein Nice-to-have, sondern das Fundament stabiler Agent-Systeme. Die Lösung liegt nicht in „besseren Prompts“, sondern in zuverlässiger Architektur.

2. Dreistufige Zuverlässigkeitsarchitektur

Diese Architektur habe ich nach vielen Fehlschlägen zusammengestellt. Kein Allheilmittel – aber sie senkt Formatfehler von 5–10 % auf nahezu null.

L1: Parameter-Validierung – erste Verteidigungslinie

Einfaches Prinzip: Pydantic definiert die erwartete Struktur, erzwingt Typkonvertierung, Whitelist-Filter.

from pydantic import BaseModel, Field, field_validator
from typing import Optional, List
from datetime import datetime

class ToolCallParams(BaseModel):
    """工具调用参数模型"""
    city: str = Field(..., min_length=1, max_length=50, description="城市名称")
    date: Optional[datetime] = Field(None, description="查询日期")
    units: str = Field("metric", pattern="^(metric|imperial)$")

    @field_validator("city")
    @classmethod
    def validate_city(cls, v: str) -> str:
        # 白名单校验
        allowed_cities = {"北京", "上海", "广州", "深圳", "杭州"}
        if v not in allowed_cities:
            raise ValueError(f"不支持的城市: {v},目前支持: {allowed_cities}")
        return v

Pydantic übernimmt drei Dinge: Typkonvertierung (String "123" → Integer 123), Erkennung fehlender Felder, benutzerdefinierte Validierung. Die wichtigste und grundlegendste Schicht.

L2: Fehler-Retry – Selbstkorrektur mit Feedback

Schlägt die LLM-Antwort bei der Validierung fehl, wird nicht blind wiederholt – die Fehlermeldung geht zurück ans Modell zur Korrektur. Instructor kapselt diese Logik elegant.

import instructor
from openai import OpenAI
from pydantic import ValidationError

client = instructor.patch(OpenAI())

def get_weather_with_retry(user_query: str, max_retries: int = 3):
    """带错误反馈的重试机制"""
    messages = [{"role": "user", "content": user_query}]

    for attempt in range(max_retries):
        try:
            response = client.chat.completions.create(
                model="gpt-4o",
                response_model=ToolCallParams,  # Pydantic 模型
                messages=messages,
                temperature=0.1  # 结构化输出用低温
            )
            return response  # 自动校验通过

        except ValidationError as e:
            # 把错误喂给 LLM 让它修正
            error_msg = f"参数校验失败: {str(e)}\n请修正后重新返回正确的 JSON 格式。"
            messages.append({"role": "assistant", "content": "生成参数中..."})
            messages.append({"role": "user", "content": error_msg})

            if attempt == max_retries - 1:
                raise Exception(f"重试 {max_retries} 次后仍然失败: {e}")

# 使用示例
result = get_weather_with_retry("帮我查一下北京明天的天气")

Kernidee: Das LLM rät nicht blind – es weiß, was falsch ist und warum. Mit Feedback korrigiert es. In Tests stieg die Retry-Erfolgsrate von 60 % auf über 95 %.

L3: Constrained Decoding – Fehler an der Quelle verhindern

L1 und L2 sind nachträgliche Korrektur, L3 ist Prävention.

Beim constrained Decoding wird bei jedem generierten Token per Finite-State-Machine (FSM) der Auswahlbereich eingeschränkt – nur Schema-konforme Token-Sequenzen sind möglich. Wie eine Bremse: ungültige Ausgabe ist technisch ausgeschlossen.

Zwei gängige Implementierungen:

Outlines (Open Source, für lokale Modelle):

from outlines import models, generate
import json

# 加载本地模型
model = models.transformers("Qwen/Qwen2.5-7B-Instruct")

# 定义 Schema
schema = {
    "type": "object",
    "properties": {
        "name": {"type": "string"},
        "age": {"type": "integer"}
    },
    "required": ["name", "age"]
}

# 创建受约束生成器
generator = generate.json(model, schema)
result = generator("提取用户信息: 张三今年28岁")
# 100% 符合 Schema,不需要任何重试

vLLM guided_json (für große deployte Modelle):

from vllm import LLM, SamplingParams

llm = LLM(model="Qwen/Qwen2.5-72B-Instruct")
sampling_params = SamplingParams(
    temperature=0.0,
    guided_decoding_backend="outlines",
    guided_json={  # 直接传 JSON Schema
        "type": "object",
        "properties": {
            "tool_name": {"type": "string"},
            "arguments": {"type": "object"}
        }
    }
)

L3 kostet Kompilierungs-Overhead – die FSM muss pro Schema vorab aufgebaut werden. Bei häufig wechselnden Schemas entsteht Verzögerung. Für die meisten Agent-Anwendungen sind Schemas stabil – der Overhead ist akzeptabel.

Welche Schichten wann?

SzenarioEmpfehlung
OpenAI APIL1 + L2 (Pydantic + Instructor)
Claude APIL1 + L2 (Claude ohne Strict Mode)
Lokales ModellL1 + L3 (Outlines/vLLM guided_json)
Höchste ZuverlässigkeitL1 + L2 + L3 kombiniert

3. Anbietervergleich: OpenAI, Claude, Gemini

Die Unterschiede zwischen Anbietern sind groß – ohne Vergleich landen Sie schnell in Fallstricken.

OpenAI: Strict Mode, erzwungene Konformität

Im August 2024 führte OpenAI Structured Outputs ein – derzeit die zuverlässigste kommerzielle API-Lösung.

Kernmechanismus: Parameter strict: true. Die Ausgabe wird auf das definierte JSON Schema gezwungen – 100 % Konformität. Unter der Haube: Grammar-based Constrained Decoding, ähnlich wie Outlines.

from openai import OpenAI

client = OpenAI()

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "提取用户信息"}],
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "user_info",
            "strict": True,  # 关键参数
            "schema": {
                "type": "object",
                "properties": {
                    "name": {"type": "string"},
                    "age": {"type": "integer"}
                },
                "required": ["name", "age"]
            }
        }
    }
)
# 输出 100% 符合 Schema

OpenAI-Angabe: Strict Mode unter 0,1 % Fehlerrate. In der Praxis keine Formatfehler – mit Einschränkung: rekursive Schemas nicht unterstützt, komplexe Verschachtelung erfordert Workarounds.

Anthropic Claude: Tool Use ohne Garantie

Claude nutzt Tool Use für strukturierte Ausgabe: Tool definieren, Claude ruft es mit Parametern auf. Aber: Der strict-Parameter kann gesetzt werden – wird laut offizieller Doku ignoriert. Keine Schema-Garantie.

Original aus der Anthropic-Dokumentation (Stand April 2026):

“The strict parameter is currently ignored for tool definitions. Claude will make a best effort to provide valid arguments, but does not guarantee schema compliance.”

Es wird sein Bestes geben – ohne Garantie. Bei Claude-Tool-Aufrufen unbedingt L1 (Parameter-Validierung) und L2 (Fehler-Retry) einplanen.

import anthropic

client = anthropic.Anthropic()

# Claude 的工具定义
tools = [{
    "name": "get_weather",
    "input_schema": {
        "type": "object",
        "properties": {
            "city": {"type": "string"}
        },
        "required": ["city"]
    }
}]

response = client.messages.create(
    model="claude-3.5-sonnet",
    max_tokens=1024,
    tools=tools,
    messages=[{"role": "user", "content": "北京天气"}]
)

# 重要:必须手动校验 tool_use 的参数
for block in response.content:
    if block.type == "tool_use":
        # 这里要做 Pydantic 校验
        validated_params = ToolCallParams.model_validate(block.input)

Google Gemini: Controlled Generation

Gemini nutzt Controlled Generation über response_schema.

import google.generativeai as genai

model = genai.GenerativeModel('gemini-1.5-pro')

response = model.generate_content(
    "提取用户信息",
    generation_config={
        "response_mime_type": "application/json",
        "response_schema": {
            "type": "object",
            "properties": {
                "name": {"type": "string"},
                "age": {"type": "integer"}
            },
            "required": ["name", "age"]
        }
    }
)

Gemini liegt zwischen OpenAI und Claude – Constraint vorhanden, aber ohne OpenAIs harte Erzwingung. In Tests etwa 1–2 % Fehlerrate: besser als JSON Mode, schwächer als Strict Mode.

Open-Source-Modelle: Outlines/vLLM

Qwen, Llama, Mistral etc. haben keine native strukturierte Ausgabe – externe Tools nötig: Outlines und vLLM guided_json.

Interessant: Open-Source plus Outlines kann zuverlässiger sein als manche kommerzielle APIs – die FSM ist harte Constraint, kein „best effort“.

Entscheidungshilfe

AnforderungEmpfehlungGrund
Reine API, maximale StabilitätOpenAI Structured Outputs<0,1 % Fehlerrate
Komplexes Reasoning + ToolsClaude + L1/L2Starke Reasoning-Fähigkeit, eigene Validierung
Private Modell-DeploymentQwen/Llama + OutlinesKostenkontrolle, hohe Zuverlässigkeit
Höchste Format-Anforderungen (Finanz, Medizin)OpenAI Strict oder OutlinesNahezu null Fehler
Schneller PrototypInstructor + beliebige APIGekapselt, Auto-Retry

4. Produktionsreife Code-Vorlagen

Vier Vorlagen, die ich in Produktion validiert habe – direkt anpassbar.

Vorlage 1: OpenAI Structured Outputs – vollständiges Beispiel

"""
OpenAI Structured Outputs 完整示例
适用于:工具调用、数据提取、报表生成等场景
"""
from openai import OpenAI
from pydantic import BaseModel, Field
from typing import List, Optional
import json

# 1. 定义 Pydantic 模型
class SearchQuery(BaseModel):
    """搜索查询参数"""
    keywords: List[str] = Field(
        ...,
        min_length=1,
        max_length=5,
        description="搜索关键词列表"
    )
    filters: Optional[dict] = Field(
        default=None,
        description="可选的过滤条件"
    )
    limit: int = Field(
        default=10,
        ge=1,
        le=100,
        description="返回结果数量"
    )

# 2. Pydantic 模型转 JSON Schema
def model_to_schema(model: type[BaseModel]) -> dict:
    """将 Pydantic 模型转换为 JSON Schema"""
    schema = model.model_json_schema()
    # 清理 Pydantic 添加的元数据
    schema.pop("title", None)
    for prop in schema.get("properties", {}).values():
        prop.pop("title", None)
    return schema

# 3. 结构化输出调用
client = OpenAI()

def extract_search_params(user_input: str) -> SearchQuery:
    """从用户输入提取搜索参数"""
    schema = model_to_schema(SearchQuery)

    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {
                "role": "system",
                "content": "你是一个搜索助手,帮助用户提取搜索参数。"
            },
            {"role": "user", "content": user_input}
        ],
        response_format={
            "type": "json_schema",
            "json_schema": {
                "name": "search_query",
                "strict": True,
                "schema": schema
            }
        },
        temperature=0.1  # 结构化输出用低温
    )

    # 4. 解析并二次校验
    raw_content = response.choices[0].message.content
    data = json.loads(raw_content)
    return SearchQuery.model_validate(data)

# 使用示例
if __name__ == "__main__":
    query = extract_search_params(
        "我想找一些关于 Python 异步编程的文章,只要最近一个月的,最多 20 条"
    )
    print(query)
    # SearchQuery(keywords=['Python', '异步编程'], filters={'date_range': 'last_month'}, limit=20)

Vorlage 2: Instructor Auto-Retry

"""
Instructor 自动重试示例
适用于:Claude API、OpenAI JSON Mode(非 Strict)、需要容错的场景
"""
import instructor
from openai import OpenAI
from pydantic import BaseModel, Field, ValidationError

class AgentAction(BaseModel):
    """Agent 行动决策"""
    action_type: str = Field(
        ...,
        pattern="^(search|execute|respond|clarify)$"
    )
    parameters: dict = Field(default_factory=dict)
    reasoning: str = Field(..., min_length=10)

# patch OpenAI client
client = instructor.patch(OpenAI())

def get_agent_decision(
    context: str,
    user_request: str,
    max_retries: int = 3
) -> AgentAction:
    """
    获取 Agent 的行动决策,带自动重试

    Args:
        context: 当前对话上下文
        user_request: 用户请求
        max_retries: 最大重试次数

    Returns:
        AgentAction: 校验后的行动决策
    """
    messages = [
        {"role": "system", "content": "你是一个智能助手,分析用户需求并决定下一步行动。"},
        {"role": "user", "content": f"上下文: {context}\n\n用户请求: {user_request}"}
    ]

    try:
        response = client.chat.completions.create(
            model="gpt-4o",
            response_model=AgentAction,  # Instructor 自动校验
            messages=messages,
            max_retries=max_retries,  # 内置重试
            temperature=0.1
        )
        return response

    except ValidationError as e:
        # Instructor 已经重试了 max_retries 次
        raise Exception(f"格式错误无法修复,请检查模型定义: {e}")

# 使用示例
decision = get_agent_decision(
    context="用户正在查询天气信息",
    user_request="帮我查北京明天的天气,要是晴天就推荐户外活动"
)
print(f"行动类型: {decision.action_type}")
print(f"参数: {decision.parameters}")
print(f"推理过程: {decision.reasoning}")

Vorlage 3: Outlines – lokales Modell

"""
Outlines 本地模型结构化输出示例
适用于:私有部署、成本敏感、隐私要求高的场景
"""
from outlines import models, generate
from pydantic import BaseModel
from typing import List
import json

# 定义数据结构
class ProductInfo(BaseModel):
    """商品信息"""
    name: str
    price: float
    category: str
    tags: List[str]

# 加载模型(第一次加载会有几秒延迟)
model = models.transformers("Qwen/Qwen2.5-7B-Instruct")

# 创建结构化生成器
# 注意:schema 会在首次调用时编译为 FSM,有约 1-2 秒开销
schema_str = json.dumps(ProductInfo.model_json_schema())
generator = generate.json(model, schema_str)

def extract_product_info(description: str) -> ProductInfo:
    """
    从商品描述提取结构化信息

    Args:
        description: 商品描述文本

    Returns:
        ProductInfo: 结构化的商品信息
    """
    prompt = f"从以下商品描述中提取关键信息,以 JSON 格式返回:\n{description}"

    # 生成结果 100% 符合 Schema
    result = generator(prompt)

    # 转换为 Pydantic 模型(二次校验,确保万无一失)
    return ProductInfo.model_validate(result)

# 使用示例
description = """
这款蓝牙耳机采用最新的降噪技术,价格 299 元,
属于数码配件类,适合运动、通勤等场景使用。
"""
product = extract_product_info(description)
print(product)
# ProductInfo(name='蓝牙耳机', price=299.0, category='数码配件', tags=['运动', '通勤'])

Vorlage 4: Vollständiger Tool-Aufruf-Ablauf

"""
完整的工具调用参数验证流程
包含:Schema 定义 → LLM 调用 → 参数校验 → 失败重试 → 工具执行
"""
from openai import OpenAI
from pydantic import BaseModel, Field, field_validator, ValidationError
from typing import Callable, Dict, Any
import json

# 1. 定义工具参数模型
class WeatherQueryParams(BaseModel):
    """天气查询工具参数"""
    city: str = Field(..., min_length=1, max_length=50)
    date_offset: int = Field(default=0, ge=-7, le=7, description="日期偏移,0表示今天")

    @field_validator("city")
    @classmethod
    def validate_city(cls, v: str) -> str:
        allowed = {"北京", "上海", "广州", "深圳", "杭州", "成都", "武汉"}
        if v not in allowed:
            raise ValueError(f"不支持的城市,可选: {allowed}")
        return v

# 2. 工具调用管理器
class ToolCallManager:
    """管理工具调用的完整流程"""

    def __init__(self):
        self.client = OpenAI()
        self.tools: Dict[str, Callable] = {}

    def register_tool(self, name: str, func: Callable, param_model: type[BaseModel]):
        """注册工具"""
        self.tools[name] = {
            "function": func,
            "param_model": param_model
        }

    def execute_with_retry(
        self,
        tool_name: str,
        user_request: str,
        max_retries: int = 3
    ) -> Any:
        """执行工具调用,带重试"""

        tool_config = self.tools[tool_name]
        param_model = tool_config["param_model"]
        schema = param_model.model_json_schema()

        messages = [
            {"role": "system", "content": f"提取工具 '{tool_name}' 的调用参数"},
            {"role": "user", "content": user_request}
        ]

        for attempt in range(max_retries):
            try:
                # 调用 LLM 获取参数
                response = self.client.chat.completions.create(
                    model="gpt-4o",
                    messages=messages,
                    response_format={
                        "type": "json_schema",
                        "json_schema": {
                            "name": tool_name,
                            "strict": True,
                            "schema": schema
                        }
                    },
                    temperature=0.1
                )

                # 校验参数
                params = param_model.model_validate_json(
                    response.choices[0].message.content
                )

                # 执行工具
                return tool_config["function"](params)

            except ValidationError as e:
                # 反馈错误,让 LLM 修正
                messages.append({
                    "role": "user",
                    "content": f"参数校验失败: {e}\n请修正参数格式。"
                })
                continue

        raise Exception(f"工具调用失败,重试 {max_retries} 次后仍无法通过校验")

# 3. 使用示例
def get_weather(params: WeatherQueryParams) -> str:
    """模拟天气查询"""
    # 这里是实际的 API 调用逻辑
    return f"{params.city} 未来 {params.date_offset} 天天气晴朗"

manager = ToolCallManager()
manager.register_tool("get_weather", get_weather, WeatherQueryParams)

result = manager.execute_with_retry(
    "get_weather",
    "帮我查一下北京明天的天气"
)
print(result)  # 北京未来 1 天天气晴朗

Diese Vorlagen decken die häufigsten Szenarien ab – nach Bedarf kombinieren und anpassen.

5. Best Practices für Produktion

Code allein reicht nicht – in Produktion zählen Details.

Temperature: nicht zu hoch

Für strukturierte Ausgabe: 0,0–0,2. OpenAI empfiehlt diesen Bereich, in der Praxis am stabilsten.

Hohe Temperature macht Ausgaben „kreativer“ und zufälliger. Zufälligkeit ist der Feind strukturierter Ausgabe – Sie brauchen Determinismus. Bei Temperature 0,7 lag die Fehlerrate bei 15 %, bei 0,1 praktisch keine Formatprobleme mehr.

Retry-Strategie: nicht jeden Fehler retryen

Vor dem Retry den Fehlertyp prüfen:

FehlertypRetry?Grund
Parameterformat (fehlende Felder, falscher Typ)Ja + FehlerfeedbackLLM kann korrigieren
API-Fehler (429, 500)Ja + BackoffTemporäres Serverproblem
Business-Validierung (Stadt nicht in Whitelist)Nein, Fehler zurückgebenNutzerbestätigung nötig
Tool-Ausführung fehlgeschlagen (leeres Ergebnis)Nein, FallbackTool-Problem, kein LLM-Problem

Unbegrenztes Retry bei Whitelist-Verletzung: LLM rät zehnmal falsch, dann Timeout. Fehlertypen trennen.

Performance-Overhead im Vergleich

AnsatzLatenzKostenZuverlässigkeit
Prompt-Constraint (ohne Spezialparameter)+0 ms+0 %5–10 % Fehler
JSON Mode (nur OpenAI)+50 ms+0 %2–5 % Fehler
Structured Outputs (Strict)+100 ms+0 %<0,1 % Fehler
Instructor Retry+200–500 ms/Retry× Retry-KostenNahe 0 % Fehler
Outlines FSM+1–2 s (Erstkompilierung)+0 %100 % Konformität

Abwägung: maximale Stabilität → Structured Outputs oder Outlines; schneller Prototyp → Instructor; knappes Budget → JSON Mode plus manuelle Validierung.

Monitoring: drei Pflichtmetriken

Nach dem Go-Live unbedingt beobachten:

  1. Format-Fehlerrate: Anteil fehlgeschlagener Validierungen. Über 1 % → Ursache suchen.
  2. Durchschnittliche Retry-Anzahl: Normal 0,5–1,5. Über 2 → Modell oder Schema prüfen.
  3. Durchschnittliche Latenz: Strukturierte Ausgabe +50–200 ms gegenüber normaler Ausgabe – im Rahmen halten.

Mit Prometheus + Grafana wöchentlich auswerten. Einmal sprang die Retry-Anzahl von 0,8 auf 2,5 – Ursache: Schema geändert, Code nicht synchronisiert. Monitoring hat rechtzeitig gewarnt.

Fazit

Kernbotschaft: Strukturierte Ausgabe ist 2026 kein ungelöstes Problem – mit der richtigen Methode.

Die dreistufige Architektur (Parameter-Validierung + Fehler-Retry + constrained Decoding) deckt alles ab – vom „läuft irgendwie“ bis „läuft stabil“. Bei der Anbieterwahl: OpenAI Strict Mode am stabilsten, Claude erfordert eigene Validierung, Open-Source plus Outlines oft überraschend zuverlässig.

Code-Vorlagen stehen in Kapitel 4 – anpassen und einsetzen. Wer gerade mit Agent-Entwicklung startet: mit Instructor beginnen – gut gekapselt, Auto-Retry und Fehlerfeedback eingebaut. Später bei Bedarf Outlines für 100 % harte Konformität.

Bei Fragen gerne kommentieren oder direkt melden. Hoffentlich erspart Ihnen dieser Artikel ein paar Fehltritte.

OpenAI Structured Outputs – vollständiger Ablauf

Von der Pydantic-Modelldefinition bis zum strukturierten API-Aufruf

⏱️ Estimated time: 15 min

  1. 1

    Step 1: Pydantic-Datenmodell definieren

    Pydantic-Modellklasse mit Field-Constraints anlegen:

    • `Field(..., min_length=1, max_length=50)` für Stringlängen
    • `Field(default=10, ge=1, le=100)` für numerische Bereiche
    • `@field_validator` für benutzerdefinierte Logik (z. B. Whitelist-Filter)
    • `Optional[T]` für optionale Felder
  2. 2

    Step 2: Pydantic-Modell in JSON Schema konvertieren

    Mit `model.model_json_schema()` konvertieren:

    ```python
    schema = SearchQuery.model_json_schema()
    schema.pop("title", None) # Pydantic-Metadaten entfernen
    ```

    Schema muss OpenAI Structured Outputs Anforderungen entsprechen.
  3. 3

    Step 3: OpenAI API mit Strict Mode aufrufen

    In der API-Anfrage `response_format` setzen:

    • `type: "json_schema"` – strukturierter Ausgabetyp
    • `strict: True` – erzwungene Konformität
    • `json_schema.name` – Schema-Name (frei wählbar)
    • `json_schema.schema` – konvertiertes JSON Schema aus dem vorherigen Schritt
  4. 4

    Step 4: Antwort parsen und erneut validieren

    Strict Mode garantiert 100 % Konformität – dennoch zweite Validierung empfohlen:

    • `json.loads()` für die Antwortzeichenkette
    • `model.model_validate(data)` für Pydantic-Prüfung
    • `ValidationError` abfangen und Grenzfälle behandeln
  5. 5

    Step 5: Temperature-Parameter konfigurieren

    Niedrige Temperature für strukturierte Ausgabe:

    ```python
    temperature=0.1 # empfohlen 0.0–0.2
    ```

    Hohe Temperature erhöht Zufälligkeit und gefährdet Formatstabilität.

FAQ

Was tun bei fehlerhaftem JSON aus dem LLM?
Dreistufige Zuverlässigkeitsarchitektur:

• L1 Parameter-Validierung: Pydantic-Modelle mit automatischer Typkonvertierung und Feldprüfung
• L2 Fehler-Retry: Instructor-Bibliothek mit automatischem Retry und Fehlerrückmeldung ans LLM
• L3 Constrained Decoding: Outlines oder vLLM guided_json – Konformität bereits bei der Generierung
Unterschied zwischen OpenAI und Claude bei strukturierter Ausgabe?
OpenAI Structured Outputs mit strict garantiert 100 % Formatkonformität, Fehlerrate &lt;0,1 %; Claude Tool Use ohne Garantie – strict wird offiziell ignoriert, L1/L2-Validierung selbst nötig. Für maximale Stabilität: OpenAI; für komplexes Reasoning: Claude plus eigene Validierung.
Welches Schema für strukturierte Ausgabe wählen?
Je nach Szenario:

• **OpenAI API**: Structured Outputs + Strict Mode (stabilste Option)
• **Claude API**: Pydantic + Instructor-Retry (eigene Validierung nötig)
• **Lokales Modell**: Outlines oder vLLM guided_json (kosteneffizient, hohe Zuverlässigkeit)
• **Schneller Prototyp**: Instructor (gut gekapselt, sofort einsetzbar)
• **Finanz/Medizin**: OpenAI Strict oder Outlines (nahezu null Fehler)
Wie Temperature einstellen?
Für strukturierte Ausgabe: 0,0–0,2. OpenAI empfiehlt diesen Bereich, in der Praxis am stabilsten. Hohe Temperature erhöht Zufälligkeit und Formatfehler. Bei 0,7 lag die Fehlerrate bei 15 %, bei 0,1 praktisch keine Formatprobleme mehr.
Wie hoch ist der Performance-Overhead?
Deutliche Unterschiede je nach Ansatz:

• **Prompt-Constraint**: +0 ms Latenz, 5–10 % Fehlerrate
• **JSON Mode**: +50 ms, 2–5 % Fehlerrate
• **Structured Outputs**: +100 ms, &lt;0,1 % Fehlerrate
• **Instructor Retry**: +200–500 ms pro Retry, nahe 0 % Fehlerrate
• **Outlines FSM**: +1–2 s Erstkompilierung, 100 % Konformität

Abwägung nach Zuverlässigkeitsbedarf und Budget.
Welche Fehler retryen, welche nicht?
Fehlertypen unterscheiden:

**Retry sinnvoll**:
• Parameterformat (fehlende Felder, falscher Typ) – LLM kann korrigieren
• API-Fehler (429, 500) – temporäre Serverprobleme

**Kein Retry**:
• Business-Validierung (Stadt nicht in Whitelist) – Nutzerbestätigung nötig
• Tool-Ausführung fehlgeschlagen (leeres Ergebnis) – Fallback statt Retry

Unbegrenztes Retry führt zu Timeouts – Fehlertypen trennen für effiziente Verarbeitung.

14 Min. Lesezeit · Veröffentlicht am: 6. Mai 2026 · Aktualisiert am: 9. Juli 2026

Ähnliche Beiträge

Kommentare

Melde dich mit GitHub an, um einen Kommentar zu hinterlassen

Easton BlogEaston Blog