[!NOTE] Automatische Übersetzung Dieser Artikel wurde automatisch aus der englischen Originalversion übersetzt.
Schema-gestütztes Reasoning auf vLLM: Structured Outputs mithilfe von xgrammar und Pydantic
Ein erneuter Aufruf von LLM garantiert keine gültigen JSON. Das nächste Beispiel kann auf dieselbe Weise fehlschlagen, und wiederholte Aufrufe führen zu zusätzlichem Latency sowie zu höheren Kosten.
Schema-gestütztes Reasoning (SGR) stellt sicher, dass ein Schema eingehalten wird, während der Model jeweils einen Token erzeugt. Die erforderlichen Felder werden mithilfe von Pydantic definiert, wobei der Inference-Motor solche Tokens blockiert, die diese Struktur verletzen. Dadurch entstehen von vornherein syntaktisch korrekte Objekte, anstatt dass wiederholt Versuche unternommen werden müssen.
Zusammenfassung. SGR nutzt Constrained Decoding, um die Ausgabe eines LLM an ein von Ihnen kontrolliertes Pydantic-Schema zu binden. In Kombination mit dem xgrammar Backend von vLLM ergeben sich somit stets gültige JSON-Objekte bei nahezu vernachlässigbarem Latency-Aufwand.
Was ist ein schema-gesteuerter Reasoning?
Schema-gestütztes Reasoning ist eine Technik, die Rinat Abdullin im Jahr 2024 verfasst. Anstatt es dem Model zu überlassen, den Text frei zu vervollständigen – was zu Inkonsistenzen oder Mehrdeutigkeiten führen kann – wird ihm ein strenges Template zur Verfügung gestellt, das Folgendes definiert:
- Welche Schritte es durchlaufen muss, damit eine Auslassung der Analyse nicht möglich ist.
- Die Reihenfolge dieser Schritte, sodass der Reasoning von den Daten bis zur Entscheidung verläuft.
- Wo es seine Attention konzentrieren sollte, damit die Analysegründlichkeit genau dort anwendet wird, wo sie wichtig ist.
Betrachten Sie es als eine kognitive Checkliste, der der Model folgen muss.
Was durch das Schema gesteuert wird
Felder wie zum Beispiel churn_analysis, margin_math, und max_discount_percent Stellen Sie die erwarteten Zwischenergebnisse ausdrücklich dar. Der Model muss die erforderliche Struktur vor dem Erstellen der endgültigen Rabattentscheidung vollständig füllen.
Das liefert Ihnen:
- reproduzierbare Reasoning Ergebnisse bei wiederholten Ausführungen
- überprüfbare Ausgaben, bei denen jeder Schritt inspizierbar ist
- Zwischenwerte, anhand derer man einen Test Dataset durchführen kann
- kleinere Models Strukturen, die handhabbar werden, da das Schema vorschreibt, was sonst von den Model gelernt werden müsste
- eine gemeldete Genauigkeitsverbesserung von 5–10 % die ursprünglichen Produktivlasten der Praktiker, was Sie anhand Ihrer eigenen Daten überprüfen sollten.
SGR im Vergleich zur Gedankenkette und zur Prompt-Entwicklung
Die drei Ansätze unterscheiden sich hauptsächlich darin, inwieweit sie die Model stark einschränken.
| Funktion | Prompt Ingenieurwesen | Kettendenkprozess | Schema-gestütztes Reasoning |
|---|---|---|---|
| Ausgabestruktur | Variable Texte | Freiformprosa | starres JSON/Pydantic |
| Steuermechanismus | Semantische Überzeugungskraft („Bitte geben Sie JSON aus“) | heuristische Anleitung („Lassen Sie uns Schritt für Schritt darüber nachdenken“) | Constrained Decoding (grammatikbasiert) |
| Reasoning Fluss | Model bestimmt | Model bestimmt | Der Entwickler bestimmt die (Schema-Topologie). |
| Auditorierbarkeit | Niedrig (erfordert Parsing) | Niedrig (erfordert Lesen des Textes) | Hohe Prüfgenauigkeit (Ebene der Feldprüfung) |
| Integration | Schwierig (variabler Format) | Trivial (Serialisierung von nativen Objekten) | |
| Fehlerrate | Hoch (Formatvielfalt) | Moderieren (Hallucination des Formats) | |
| Model Anforderung | Starke Befehlsbefolgung | Starke Reasoning-Funktionalität | Es funktioniert auch mit kleineren Models. |
Prompt Engineering: semantische Überzeugungskraft
Please analyze the customer data and output your response as valid JSON
with the following structure: {"discount": <number>, "reason": <string>}
Be careful with the formatting!
Sie hoffen, dass das Verständnis des Model für „Ausgabe JSON” seine Neigung zur konversativen Kommunikation überwiegt. Ein Update des Model, eine Änderung der Temperatur oder ein anderes Few-Shot-Beispiel können Ihren Parser lahmlegen.
Gedankenkette: bessere Reasoning, dasselbe Strukturproblem
Let's think step by step:
1. First, I'll analyze the customer's churn risk...
2. Then I'll calculate the margin...
3. Therefore, I recommend a 15% discount.
CoT erhöht die Genauigkeit von Reasoning, verschlechtert jedoch die Struktur erheblich. Das Ergebnis sind unvorhersehbare Texte, die nahezu unmöglich zuverlässig解析bar sind. In der Regel muss man eine zweite Aufrufung von LLM durchführen, um die strukturierten Daten extrahieren zu können.
SGR: strukturierte Gedankenkette
SGR bewahrt die Intuition von CoT, dass mittlere Reasoning-Werte die Genauigkeit verbessern. Es formalisiert lediglich die jeweiligen Schritte:
class PricingLogic(BaseModel):
# 1. Data Analysis (must complete before decision)
churn_analysis: str = Field(..., description="Analyze churn_probability")
financial_analysis: str = Field(..., description="Analyze cart_value and margin")
# 2. Math Enforcement (explicit calculation)
margin_math: str = Field(..., description="Calculate: 'Cart $X * Y% = $Z'")
# 3. Decision Constraint (bounded by prior analysis)
max_discount_percent: float = Field(..., description="Max allowed discount")
# 4. Final Output
offer_code: str
customer_message: str
Der Model kann keine Ausgabe generieren. max_discount_percent bis churn_analysis, financial_analysis, und margin_math Sie werden mit Inhalten befüllt. Das Schema stellt die Reihenfolge von Reasoning sicher.
SGR-Muster
SGR weist drei Kernmuster auf, aus denen größere Workflows zusammengesetzt werden.
1. Cascade: sequenzielle Reasoning-Schritte
Cascade stellt eine strikte Reihenfolge nach Reasoning sicher. Jedes Feld muss vor dem nächsten ausgefüllt werden.
from pydantic import BaseModel
from typing import Literal, Annotated
from annotated_types import Ge, Le
class CandidateEvaluation(BaseModel):
"""Evaluate a job candidate with enforced reasoning order."""
# Step 1: Summarize (forces context awareness)
brief_candidate_summary: str
# Step 2: Rate (bounded integer)
rate_skill_match: Annotated[int, Ge(1), Le(10)]
# Step 3: Decide (constrained choices)
final_recommendation: Literal["hire", "reject", "hold"]
Gute Anwendungsfälle: Bewertung von Kandidaten, Klassifizierung von Dokumenten, Konformitätsanalyse, medizinische Diagnose.
Der Model muss schreiben brief_candidate_summary Bevor ein Agent bewerten kann, muss er zunächst bewertet werden, und bevor er empfehlen kann, muss er zuvor bewertet worden sein. Es gibt keinen Abkürzungsweg.
2. Routing: ein semantisches Switch-Spruchteil
Routing zwingt den Model dazu, sich aus einer Menge von Optionen für einen bestimmten Pfad zu entscheiden, wobei dies mithilfe von … implementiert wird. Union Typen.
from pydantic import BaseModel
from typing import Literal, Union
class FeatureLookup(BaseModel):
"""Route to database lookup."""
rationale: str
tool_name: Literal["fetch_user_features"] = "fetch_user_features"
user_id: str
class GeneralResponse(BaseModel):
"""Standard response for non-pricing queries."""
tool_name: Literal["respond"] = "respond"
content: str
class RouterSchema(BaseModel):
"""The model must pick exactly ONE branch."""
action: Union[FeatureLookup, GeneralResponse]
Gute Anwendungsfälle sind die Intent-Klassifizierung, die Auswahl von Tools, die Priorisierung von Supportanfragen sowie die multi-agent-Verteilung.
Der Literal Diskriminierertool_name) lässt den Model einen einzigen Branch auswählen und füllt lediglich die Felder aus, die dieser Branch benötigt.
3. Zyklus: wiederholte Ausführung von Reasoning mit Listen
Cycle zwingt den Model dazu, mehrere Elemente zu erzeugen, wobei die Anzahl durch Grenzwerte begrenzt wird.
from pydantic import BaseModel
from typing import List, Literal, Annotated
from annotated_types import MinLen, MaxLen
class RiskFactor(BaseModel):
explanation: str
severity: Literal["low", "medium", "high"]
class RiskAssessment(BaseModel):
"""Generate 2-4 risk factors."""
factors: Annotated[List[RiskFactor], MinLen(2), MaxLen(4)]
Gute Anwendungsfälle sind die Risikobewertung, die Extraktion von Problemen, paralleler Tool Calls-Ablauf sowie mehrstufige Planning-Prozesse.
Der MinLen und MaxLen Die Grenzwerte erzwingen mindestens 2 und höchstens 4 Elemente. In Kombination mit Routing ist dies die Methode, um einen fest definierten Satz an Tool Calls zu verarbeiten.
SGR in die Praxis umsetzen: Constrained Decoding
Die obigen Muster sind lediglich Pydantic-Schemata. Was sie bindend macht, ist Constrained Decoding (auch als Structured Output bezeichnet).
Constrained Decoding ändert den Generierungsprozess von Token. Anstelle zu ermöglichen, dass die Model-Beispiele frei aus ihrem Wortschatz wählen, wendet das Engine eine Grammatikmaske an, die solche Tokens-Elemente blockiert, die gegen das Schema verstoßen würden. Dies findet im Inference-Engine statt und nicht im Code Ihrer Anwendung.
[!TIP] SGR benötigt nicht wie o1 oder DeepSeek-R1 „Reasoning Models@“. Er funktioniert einwandfrei mit instruction-tuned Models und insbesondere hervorragend mit aus Reasoning abgeleiteten Models.
Cloud-Anbieter, die dies unterstützen
Die meisten modernen LLM-Anbieter stellen Structured Outputs über Constrained Decoding bereit:
| Anbieter | Unterstützung |
|---|---|
| OpenAI | Structured Outputs (einschließlich Azure). GPT-5 verwendet JSON Schema über llguidance. |
| Google/Gemini | JSON Schema Support seit November 2025 (Pydantic und Zod) |
| Mistral | Eingestellte Structured Output |
| Grok | Structured Outputs für mehrere Models |
| Feuerwerkseffekte AI | JSON Schema |
| Cerebras | Structured Outputs |
| OpenRouter | Hängt vom nachgelagerten Anbieter ab und entspricht JSON Schema |
Inference-Engineer, die diese unterstützen
Für selbst gehostete Models verfügen alle gängigen Engine über einen Constrained Decoding Backend:
| Motor | Backend |
|---|---|
| vLLM | xGrammar oder Leitfaden |
| SGLang | Übersichten, XGrammar, oder LL-Guidance |
| TensorRT-LLM | GuidedDecoding |
| Ollama | Structured Outputs |
Warum sich dieser Artikel auf vLLM und xgrammar konzentriert
Einige Gründe:
- vLLM ist der am weitesten verbreitete Open-Source LLM Inference-Motor, wodurch die hier entwickelten Lösungen problemlos portierbar sind.
- xgrammar ist in C++ implementiert und verursacht nur einen vernachlässigbaren Latency-Aufwand.
- Die API von vLLM ist mit OpenAI kompatibel, was eine kostengünstige Migration von Cloud-Anbietern ermöglicht.
- xgrammar kann komplexe, verschachtelte Schemata, Unionen sowie rekursive Strukturen verarbeiten.
Im nächsten Abschnitt wird erläutert, wie xgrammar tatsächlich eine Schema-Verifizierung auf der Ebene von Token durchführt.
Wie xgrammar Schemata durchsetzt
Dieser Abschnitt ist genau zu verstehen, da er die Art und Weise verändert, wie man SGR Workflows debuggt und optimiert.
Wo die Maskierung stattfindet
xgrammar modifiziert die Ausgabelogits nach dem Forward-Pass des Model und vor der Stichprobenziehung. Es ändert den Model selbst nicht; stattdessen filtert es aus, welche Tokens ausgewählt werden dürfen.
Eine Standard-Inference-Schleife sieht wie folgt aus:
1. Input tokens → GPU Forward Pass → Logits (probability scores for all ~128K tokens)
2. Logits → Sampling (temperature, top-p, etc.) → Next Token
3. Repeat until done
xgrammar gerät zwischen den Schritten 1 und 2 ins Stocken:
1. Input tokens → GPU Forward Pass → Raw Logits
2. Raw Logits → xgrammar Logits Processor → Masked Logits
3. Masked Logits → Sampling → Next Token (guaranteed valid)
4. Repeat until done
Der Model berechnet weiterhin seine vollständige Wahrscheinlichkeitsverteilung auf der GPU. Anschließend wird xgrammar auf der CPU ausgeführt und wendet vor dem Sampling einen Bitmask auf diese Logits an. Ungültige Tokens erhalten dabei ihre Logits gesetzt auf -∞, wodurch ihre Wahrscheinlichkeit nach der Softmax-Berechnung exakt 0 wird.
Zwei Phasen
xgrammar teilt die Arbeit in eine Kompilierzeitphase sowie Runtime auf, und genau das sorgt für seine hohe Geschwindigkeit.
Phase 1: Grammatik-Kompilierung, einmal pro Schema
# This happens once per schema
tokenizer_info = xgr.TokenizerInfo.from_huggingface(tokenizer)
grammar_compiler = xgr.GrammarCompiler(tokenizer_info)
compiled_grammar = grammar_compiler.compile_json_schema(schema_json)
Während der Kompilierung: xgrammar:
- Wandelt den JSON Schema in eine kontextfreie Grammatik um.
- Erstellt einen Stapelautomaten (PDA), der ein State Machine mit einem Stack ist, um damit verschachtelte Strukturen verarbeiten zu können.
{"a": {"b": {"c": ...}}}. - Es wird im Voraus berechnet, welche Tokens an jeder Grammatikposition gültig sind. Das Ergebnis ist der „adaptive Token-Masken-Cache“.
- Tokens werden als „kontextunabhängig“ (kann im Cache gespeichert werden) oder als „kontextabhängig“ (muss bei Runtime anhand des Stack-Zustands überprüft werden) kategorisiert.
[!NOTE] Etwa 99 % von Tokens weisen sich als kontextunabhängig heraus und werden schließlich im Cache gespeichert.Der XGrammar-Artikel). Die meisten Gültigkeitsprüfungen bei Runtime bestehen lediglich aus Cache-Aufrufen, weshalb xgrammar so schnell ist.
Phase 2: Erstellung der Runtime-Maske, für jeden Token
In jedem Generierungsschritt:
- Der
GrammarMatcherEs verfolgt die aktuelle Position innerhalb der Grammatik. - Es ruft die im Voraus berechnete Maske für kontextunabhängige Tokens ab.
- Es führt einen PDA-Ablauf durch, um die verbleibenden kontextabhängigen Tokens zu überprüfen.
- Es kombiniert diese Ergebnisse zu einer endgültigen Bitmaske und wendet sie auf die Logits an.
Warum Pushdown-Automaten und nicht reguläre Ausdrücke?
Wegen der Verkettung. Eine reguläre Ausdruckskonstruktion (ein endlicher State Machine) kann Strukturen wie diese nicht zuverlässig abgleichen:
{ "user": { "profile": { "settings": { "theme": "dark" } } } }
Der schwierige Teil sind die schließenden Klammern. }}}: Man muss sich merken, wie viele Elemente man geöffnet hat. Ein Pushdown-Automat verfügt über einen Stack, der dies überwacht, wodurch er beliebige Nesttiefe bewältigen kann. Genau deshalb kann xgrammar Union-Typen, verschachtelte Objekte sowie rekursive Schemata durchsetzen – Dinge, bei denen herkömmliche regex-basierte Ansätze an ihre Grenzen stoßen.
Ein konkretes Beispiel: Erstellung eines Float-Felds
Wenn der Model generiert "max_discount_percent":, xgrammar erkennt aus dem Schema, dass ein float folgt nun. Die Maske:
- ermöglicht (Wahrscheinlichkeit unverändert):
0,1,2, …,9,.,- - Blöcke (Wahrscheinlichkeit auf 0 gesetzt):
",{,[,true,false,nullsowie der Rest des Wortschatzes von über 128 K.
Der Vorwärtslauf könnte dem Wort eine hohe Wahrscheinlichkeit zugewiesen haben. "fifteen"Nach der Maskierung durch xgrammar beträgt die Wahrscheinlichkeit für Token 0. Model muss Ziffern ausgeben.
Warum „nahe null Overhead“
Drei Gründe:
- Parallelverarbeitung. Maskenberechnung auf dem CPU überschneidet sich mit dem nächsten Vorwärtslauf GPU. Während der GPU werden Logits für die Berechnung erzeugt Token N+1-Problem, das CPU berechnet gerade das Masken-Objekt für Token N.
- Caching. Der größte Teil der Gültigkeitsprüfungen erfolgt bereits zur Kompilierzeit. Runtime Es handelt sich größtenteils um Cache-Aufrufe.
- C++-Implementierung. Der Hot Path wird in C++ und nicht in Python ausgeführt, wobei die Maske direkt auf die Logits angewendet wird.
In Benchmarks xgrammar ist der Overhead nahezu vernachlässigbar, und die strukturierte Generierung kann gelegentlich schneller sein als die unbeschränkte Generierung, da das eingeschränkte Vokabular das Sampling erschwert.
Praktische Implementierung mit vLLM
Der Referenzwert ist der sgr – Rabattmanager Ein Projekt, eine kleine Demo, die SGR zur dynamischen Preiskalkulation verwendet.
Projektstruktur
sgr/
├── agent.py # Main orchestration
├── models/
│ └── schemas.py # Pydantic SGR schemas
├── prompts/
│ ├── routing.py # Phase 1 prompts
│ └── pricing.py # Phase 3 prompts
├── store/
│ └── hybrid_store.py # Hot/Cold data retrieval
└── utils/
└── llm_client.py # LLM client wrapper with xgrammar
Schritt 1: Definition der Schemata
# sgr/models/schemas.py
from pydantic import BaseModel, Field
from typing import Literal, Union
# --- Phase 1: Routing (Union for branching) ---
class FeatureLookup(BaseModel):
"""Route to DB lookup if pricing context is needed."""
rationale: str
tool_name: Literal["fetch_user_features"] = "fetch_user_features"
user_id: str
class GeneralResponse(BaseModel):
"""Standard response for non-pricing queries."""
tool_name: Literal["respond"] = "respond"
content: str
class RouterSchema(BaseModel):
action: Union[FeatureLookup, GeneralResponse]
# --- Phase 2: Pricing Logic (Cascade for sequential reasoning) ---
class PricingLogic(BaseModel):
"""
Strict reasoning topology for dynamic pricing.
Fields are ordered to enforce the analysis→decision flow.
"""
# 1. Data Analysis (Reflection)
churn_analysis: str = Field(...,
description="Analyze churn_probability (High > 0.7).")
financial_analysis: str = Field(...,
description="Analyze cart_value and profit_margin.")
# 2. Hard Math Enforcement
margin_math: str = Field(...,
description="Calculate absolute profit: 'Cart $200 * 0.20 Margin = $40'.")
# 3. The Decision Constraint
max_discount_percent: float = Field(...,
description="Max allowed discount %. NEVER exceed margin.")
# 4. Final Output
offer_code: str = Field(..., description="Generated code (e.g. SAVE20).")
customer_message: str = Field(..., description="The final polite offer text.")
Schritt 2: Ein LLM-Client, der xgrammar aktiviert
# sgr/utils/llm_client.py
from openai import OpenAI
from pydantic import BaseModel
from typing import TypeVar
import json
T = TypeVar("T", bound=BaseModel)
class LLMClient:
"""Wrapper for vLLM with xgrammar-enforced structured generation."""
def __init__(self, base_url: str = "http://localhost:8000/v1"):
self.client = OpenAI(base_url=base_url, api_key="EMPTY")
self.model = self._get_available_model()
def _get_available_model(self) -> str:
"""Auto-detect the model running on vLLM server."""
try:
models = self.client.models.list()
if models.data:
return models.data[0].id
except Exception:
pass
return "Qwen/Qwen2.5-7B-Instruct"
def run_sgr(self, messages: list[dict], schema_class: type[T]) -> T:
"""Run inference with Schema-Guided Response constraints.
Uses vLLM's guided_json with xgrammar backend to enforce
strict schema constraints at the token generation level.
"""
schema_dict = schema_class.model_json_schema()
# Enhance system message with schema for model guidance
enhanced_messages = messages.copy()
if enhanced_messages and enhanced_messages[0]["role"] == "system":
schema_json = json.dumps(schema_dict, indent=2)
enhanced_messages[0] = {
"role": "system",
"content": (
enhanced_messages[0]["content"]
+ f"\n\nRespond with JSON matching this schema:\n{schema_json}"
),
}
# vLLM's guided_json with the xgrammar backend
completion = self.client.chat.completions.create(
model=self.model,
messages=enhanced_messages,
temperature=0.1, # Low temp for deterministic reasoning
extra_body={
"guided_json": schema_dict, # Pydantic schema as dict
"guided_decoding_backend": "xgrammar", # Hardware-enforced
},
)
raw_response = completion.choices[0].message.content
return schema_class.model_validate_json(raw_response)
[!HINWEIS] >
guided_jsonnimmt ein JSON Schema-Diktum entgegen. Mitguided_decoding_backend: "xgrammar"Der LLM kann ausschließlich Tokens erzeugen, die gültige JSON-Objekte darstellen, die mit Ihrem Schema übereinstimmen.
Schritt 3: Die Agent orchestrieren
# sgr/agent.py
from .models.schemas import PricingLogic, RouterSchema
from .prompts.routing import build_routing_prompt
from .prompts.pricing import build_pricing_context_prompt, ASSISTANT_FETCH_MESSAGE
from .store.hybrid_store import HybridFeatureStore
from .utils.llm_client import LLMClient
def pricing_agent(user_query: str, user_id: str) -> str:
"""Process a pricing query with three-phase SGR workflow."""
llm = LLMClient()
feature_store = HybridFeatureStore()
# Build conversation history
history = [
{"role": "system", "content": build_routing_prompt(user_id)},
{"role": "user", "content": user_query},
]
# --- Phase 1: Routing (Uses RouterSchema) ---
print(f"🤖 Processing: '{user_query}' for {user_id}")
decision = llm.run_sgr(history, RouterSchema)
print(f"📍 Routing decision: {decision.action.tool_name}")
if decision.action.tool_name == "respond":
return decision.action.content
# --- Phase 2: Context Retrieval ---
if decision.action.tool_name == "fetch_user_features":
print(f"🔍 Fetching features for {user_id}...")
context = feature_store.get_user_context(user_id)
if not context:
return "Error: User profile not found."
print(f" [Data] LTV: ${context.get('user_ltv')} | "
f"Margin: {context.get('cart_profit_margin', 0) * 100}%")
# Inject context into conversation
history.append({"role": "assistant", "content": ASSISTANT_FETCH_MESSAGE})
history.append({
"role": "user",
"content": build_pricing_context_prompt(
churn_prob=context.get("churn_probability", 0.5),
cart_val=context.get("current_cart_value", 100),
margin=context.get("cart_profit_margin", 0.2),
user_ltv=context.get("user_ltv", 0),
),
})
# --- Phase 3: SGR Logic Execution (Uses PricingLogic) ---
print("🧠 Calculating Offer (Schema Enforced)...")
offer = llm.run_sgr(history, PricingLogic)
# Audit log — the SGR benefit: explicit reasoning traces
print(f" [Audit] Math: {offer.margin_math}")
print(f" [Audit] Max Allowed: {offer.max_discount_percent}%")
return offer.customer_message
return "I'm sorry, I couldn't process your request."
if __name__ == "__main__":
response = pricing_agent("I want a discount or I'm leaving!", "user_102")
print(f"\n💬 Final Reply: {response}")
Schritt 4: Ausführung von vLLM mit xgrammar
# Start vLLM server with xgrammar backend (default in recent versions)
python -m vllm.entrypoints.openai.api_server \
--model Qwen/Qwen2.5-7B-Instruct \
--port 8000
# Run the agent
uv run python -m sgr.agent
Beispielausgabe
🤖 Processing: 'I want a discount or I'm leaving!' for user_102
📍 Routing decision: fetch_user_features
🔍 Fetching features for user_102...
[Data] LTV: $1,500 | Margin: 20%
🧠 Calculating Offer (Schema Enforced)...
[Audit] Math: Cart $200 * 0.20 Margin = $40
[Audit] Max Allowed: 15.0%
💬 Final Reply: We value your loyalty! Here's a special 15% discount
with code SAVE15. This reflects our appreciation for your continued
business with us.
Das Audit-Log zeigt die tatsächliche Arbeitsweise des Model: Er berechnete den Margebetrag (40 bei 20 %) und legte eine Obergrenze für den Rabatt fest, damit das Angebot weiterhin innerhalb der Gewinnvorgaben bleibt.
Empfehlungen für die Praxis
Schema-Design
- Ordnen Sie die Felder nach dem Reasoning-Fluss. Analysefelder kommen vor Entscheidungsfeldern.
- Verfassen Sie beschreibende Texte.
FieldBeschreibungen. Sie leiten die Attention des Model genauso stark wie der Feldname selbst. - Einschränkungen durch Anwendung von
LiteralundAnnotated. Verwenden SieLiteral["a", "b"]für Enumerationen undAnnotated[int, Ge(1), Le(10)]für die Grenzen. - Halten Sie die Schemata fokussiert. Ein Schema pro Reasoning-Phase und anschließende Kombination durch mehrere Aufrufe.
vLLM-Konfiguration
- Verwenden Sie eine niedrige Temperatur (0,1–0,3), um eine deterministische Reasoning zu erzielen.
- Überlassen Sie es xgrammar, die Struktur zu verwalten. Konfrontieren Sie es nicht mit Formatierungsanweisungen in den Prompt.
- Achten Sie auf die Nutzung von Token. SGR verwendet in der Regel weniger Tokens als CoT, da dort keine ausführlichen Beschreibungen enthalten sind.
Produktionsaspekte
- Versionieren Sie Ihre Schemata auf dieselbe Weise wie APIs.
- Auch bei Verwendung von SGR sind fehlerfreie Handhabungsmechanismen für Netzwerk- und Serverfehler weiterhin erforderlich.
- Protokollieren Sie die rohen Ausgaben von SGR zur Einhaltung von Vorgaben sowie zum Debugging.
- Testen Sie mit Randfällen, damit das Schema an den Grenzbereichen weiterhin funktioniert.
Fazit
SGR ist es, das es ermöglicht, den Übergang von einer Funktionalität im Demo-Zustand zu einer stabilen Implementierung in der Produktion zu schaffen. Man definiert die Reasoning-Topologie in Pydantic, lässt xgrammar diese zu Decode Zeit überprüfen, und das Ergebnis ist:
- Immer gültig, ohne Wiederholungszyklen oder Parsing-Fehler
- Auf Feldebene überprüfbar
- Mit kleineren Models nutzbar, da diese nicht mehr selbst das Format festlegen müssen
- Günstiger in der Ausführung, da weniger Tokens, weniger Wiederholungen sowie kleinere Models benötigt werden
Der sgr – Rabattmanager Demo verbindet jedes Codebeispiel aus diesem Beitrag mit einem echten vLLM-Server. Klonen Sie diesen Server und beginnen Sie damit, die Schemata an Ihre eigenen Workflow anzupassen.
Wichtige Erkenntnisse
- Die durch das Schema gesteuerte Reasoning-Methodik macht die Reasoning-Topologie explizit, anstatt darauf zu vertrauen, dass der Model den darin enthaltenen Anweisungen folgt.
- Constrained Decoding verhindert bereits zur Erstellung Zeit ungültige JSON-Werte, was sauberer ist als eine anschließende Validierung mit erneutem Versuch.
- Setzen Sie Analysefelder vor Entscheidungsfelder ein, wenn das Schema vorschreibt, dass Reasoning vor der Ausgabe erfolgen muss.
- Verwenden Sie SGR dann, wenn der nachgeschaltete Code von einer bestimmten Struktur abhängt – und nicht, wenn das Endergebnis aus freier Formulierung besteht.
Referenzen
SGR Framework
- Schema-gestütztes Reasoning (SGR) — Der ursprüngliche Framework von Rinat Abdullin
- SGR Muster — Cascade, Routing, Zyklusmuster
xgrammar
- XGrammar: Ein flexibler und effizienter Engine zur strukturierten Erzeugung von Inhalten für große Sprachmodelle Models — Yixin Dong et al., arXiv:2411.15100 (technischer Artikel mit Benchmarks) xgrammar GitHub — Schnelle, flexible Bibliothek für strukturierte Generierung
- xgrammar-Dokumentation — Offizielle Dokumentation mit Leitfaden für den schnellen Einstieg xgrammar Schnellstart — Einführung in xgrammar
- Effiziente strukturierte Generierung mithilfe von XGrammar erreichen — MLC-Blogbeitrag zu den Interna von xgrammar
vLLM
- vLLM Structured Outputs — Offizielle Dokumentation
Demo-Projekt
- sgr – Rabattmanager — Funktionsfähige Demo mit allen Codebeispielen aus diesem Beitrag