Raciocínio Guiado por Schema: vLLM, XGrammar e Pydantic

Tradução automática Este artigo foi traduzido automaticamente a partir da versão original em inglês.

Este artigo destina-se a engenheiros Python que precisam de outputs do modelo que o código a jusante consiga validar. Aprenderá a definir um schema Pydantic, a solicitar structured output ao vLLM e a adicionar verificações na aplicação para semântica e políticas.

Repetir uma chamada a um LLM não garante JSON válido. A amostra seguinte pode falhar da mesma forma, e as chamadas repetidas acrescentam latência e custo.

O Schema-Guided Reasoning (SGR) impõe um schema enquanto o modelo gera cada token. Define os campos obrigatórios com Pydantic, e o motor de inferência bloqueia os tokens que violariam essa estrutura. O resultado é sintaticamente válido por construção, em vez de depender de retries.

TL;DR. O SGR utiliza constrained decoding para manter o output de um LLM dentro de um schema Pydantic. O vLLM pode utilizar XGrammar para restringir a estrutura gerada. Valide as regras semânticas no código da aplicação.


O que é o Schema-Guided Reasoning?

O Schema-Guided Reasoning é uma técnica que Rinat Abdullin descreveu em julho de 2025. Em vez de permitir que o modelo complete texto livremente (o que pode ser inconsistente ou ambíguo), fornece-lhe um template rígido que define:

  • que passos a resposta deve representar
  • a ordem pretendida desses passos, para que um revisor possa inspecionar o percurso entre os dados e a decisão
  • onde deve concentrar a atenção

Pense nisto como uma checklist cognitiva que o modelo tem de seguir.

Visão geral do SGRVisão geral do SGR

O que o schema controla

Campos como churn_analysis, margin_math e max_discount_percent tornam explícitos os outputs intermédios pretendidos. Um schema restringe a forma devolvida. Por si só, não faz com que um campo dependa de outro, nem prova que a decisão está correta.

Isto proporciona:

  • raciocínio reproduzível em execuções repetidas
  • outputs auditáveis, em que cada passo pode ser inspecionado
  • campos intermédios que pode avaliar face a um dataset de teste
  • modelos mais pequenos que se tornam viáveis, uma vez que o schema fornece a estrutura que, de outro modo, o modelo teria de aprender
  • Abdullin escreve que um aumento de precisão de 5–10% não é “invulgar” nos casos que observou; trata-se de uma observação de um practitioner, não de um resultado de benchmark, por isso meça-o no seu próprio workload

SGR vs Chain of Thought vs prompt engineering

As três abordagens diferem sobretudo no grau de restrição imposto ao modelo.

Comparação do SGRComparação do SGR

FuncionalidadePrompt EngineeringChain of ThoughtSchema-Guided Reasoning
Estrutura do outputTexto variávelProsa livreJSON/Pydantic rígido
Mecanismo de controloPersuasão semântica (“Please output JSON”)Prompting heurístico (“Let’s think step by step”)Constrained decoding (baseado em gramática)
Fluxo de raciocínioDeterminado pelo modeloDeterminado pelo modeloO developer descreve uma topologia pretendida
AuditabilidadeBaixa (requer parsing)Baixa (requer leitura de prosa)Elevada (inspeção ao nível dos campos)
IntegraçãoDifícil (parsing com regex)Difícil (formato variável)Requer suporte de schema e validação
Taxa de erroElevada (variabilidade de formato)Moderada (alucinação do formato)Outputs inválidos segundo o schema são bloqueados; os erros semânticos permanecem
Requisito do modeloForte capacidade de seguir instruçõesForte capacidade de raciocínioTambém funciona com modelos mais pequenos

Prompt engineering: persuasão semântica

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!

Está a confiar que a compreensão do modelo sobre “output JSON” se sobreponha à sua tendência para ser conversacional. Uma atualização do modelo, uma alteração da temperatura ou um exemplo few-shot diferente podem quebrar o parser.

Chain of Thought: traço de raciocínio útil, o mesmo problema de estrutura

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.

A CoT pode melhorar a precisão da tarefa quando o prompt, o modelo, a tarefa e a avaliação a suportam, mas deixa o resultado em prosa, difícil de analisar de forma fiável. Pode acabar por fazer uma segunda chamada a um LLM apenas para extrair dados estruturados.

SGR: chain of thought estruturada

O SGR pode disponibilizar campos intermédios para inspeção e avaliação. O facto de isso melhorar a precisão da tarefa depende do modelo, do prompt, da tarefa e da forma como esses campos são utilizados; o schema apenas formaliza a sua estrutura:

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

O schema descreve estes campos por essa ordem. Um schema de objeto único não cria um passo de validação separado entre eles. Utilize chamadas separadas ou verificações na aplicação quando as decisões posteriores tiverem de depender dos resultados anteriores.


Padrões de SGR

O SGR tem três padrões fundamentais que podem ser combinados em workflows maiores.

Padrões de SGRPadrões de SGR

1. Cascade: passos de raciocínio sequenciais

Cascade representa uma ordem de raciocínio numa única resposta estruturada. Não impõe uma transição de estado entre campos.

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"]

Boas aplicações: avaliação de candidatos, classificação de documentos, análise de conformidade e diagnóstico médico.

É pedido ao modelo que devolva brief_candidate_summary, rate_skill_match e final_recommendation por essa ordem. Se a ordem for um requisito de política, imponha-a com chamadas separadas ou lógica determinística na aplicação.


2. Routing: uma instrução switch semântica

Routing faz com que o modelo escolha um caminho de entre um conjunto de opções, implementado com tipos Union.

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]

Boas aplicações: classificação de intenção, seleção de ferramentas, triagem de suporte e dispatch multi-agent.

Os valores Literal específicos de cada ramo ajudam a validação a distinguir os membros da união. Não tornam o routing correto. Valide o resultado e faça o dispatch no código da aplicação. Para um discriminador Pydantic explícito, configure e teste uma discriminated union.


3. Cycle: raciocínio repetido com listas

Cycle obriga o modelo a produzir vários itens, com limites para a quantidade.

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)]

Boas aplicações: avaliação de risco, extração de problemas, tool calls em paralelo e planeamento em vários passos.

Os limites MinLen e MaxLen obrigam à produção de pelo menos 2 e no máximo 4 itens. Combinado com Routing, é assim que se faz dispatch de um batch de tool calls com largura fixa.


Como fazer o SGR funcionar: constrained decoding

Os padrões anteriores são apenas schemas Pydantic. O que os torna vinculativos é o constrained decoding (também designado Structured Output).

O constrained decoding modifica o passo de geração de tokens. Em vez de permitir que o modelo faça sampling livremente a partir do seu vocabulário, o motor aplica uma grammar mask que bloqueia os tokens que violariam o schema. Isto acontece no inference engine, não no código da aplicação.

[!TIP] O SGR não requer “reasoning models” como o o1 ou o DeepSeek-R1. Funciona bem com modelos instruction-tuned e especialmente bem com modelos destilados a partir de modelos de raciocínio.

Cloud providers que o suportam

A documentação dos seguintes providers anunciava suporte para structured output quando este artigo foi atualizado, em 2026-06-07. O suporte, os subconjuntos de schema, o grau de strictness e o comportamento perante falhas variam consoante o modelo e o endpoint:

ProviderSuporte
OpenAIStructured Outputs (incluindo Azure)
Google/GeminiSuporte para JSON Schema desde novembro de 2025 (Pydantic e Zod)
MistralCustom Structured Output
GrokStructured Outputs para vários modelos
Fireworks AIJSON Schema
CerebrasStructured Outputs
OpenRouterDepende do provider downstream e da rota selecionados

Inference engines que o suportam

Para modelos self-hosted, os principais engines têm um backend de constrained decoding:

EngineBackend
vLLMxgrammar ou guidance
SGLangOutlines, XGrammar ou llguidance
TensorRT-LLMGuidedDecoding
OllamaStructured Outputs

Por que este artigo se centra no vLLM e no XGrammar

Algumas razões:

  • O vLLM é um dos inference engines open source para LLM mais utilizados em produção, pelo que aquilo que construir aqui será facilmente portável.
  • O XGrammar é implementado em C++ e os benchmarks citados medem o seu overhead em condições específicas. Meça-o com o seu modelo, schema, hardware e configuração de serving.
  • A API do vLLM é compatível com a OpenAI, o que mantém baixo o custo de migração a partir de cloud providers.
  • O XGrammar suporta schemas complexos e aninhados, unions e estruturas recursivas.

Como o XGrammar impõe schemas

Imposição pelo xgrammarImposição pelo xgrammar

Onde ocorre o masking

O XGrammar modifica os output logits depois do forward pass do modelo e antes do sampling. Não altera o modelo. Filtra os tokens que podem ser selecionados.

Um loop de inferência normal tem o seguinte aspeto:

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

O XGrammar insere-se entre os passos 1 e 2:

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

O modelo continua a calcular a sua distribuição de probabilidade completa na GPU. O GrammarMatcher do XGrammar gera a bitmask na CPU; depois, o serving engine move essa bitmask para o device dos logits e aplica-a in-place antes do sampling. Para logits na GPU, o XGrammar utiliza um GPU kernel para essa aplicação. Os tokens inválidos recebem logits com o valor -∞, o que faz com que a sua probabilidade seja exatamente 0 depois do softmax.

Duas fases

O XGrammar divide o trabalho entre compile time e runtime. Este design reduz o trabalho repetido da gramática.

Fase 1: compilação da gramática, uma vez por 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)

Durante a compilação, o XGrammar:

  1. Converte o JSON Schema numa Context-Free Grammar.
  2. Constrói um Pushdown Automaton (PDA), uma máquina de estados com uma stack que consegue lidar com estruturas aninhadas como {"a": {"b": {"c": ...}}}.
  3. Pré-calcula que tokens são válidos em cada posição da gramática. O resultado é a “adaptive token mask cache”.
  4. Categoriza os tokens como “context-independent” (cacheáveis) ou “context-dependent” (têm de ser verificados em runtime face ao estado da stack).

[!NOTE] O artigo do XGrammar indica que cerca de 99% dos tokens eram context-independent nas suas medições (artigo). Considere esse valor específico do benchmark e não uma proporção universal.

Fase 2: geração da mask em runtime, para cada token

Em cada passo de geração:

  1. O GrammarMatcher acompanha a posição atual na gramática.
  2. Consulta a mask pré-calculada para os tokens context-independent.
  3. Executa o PDA para verificar os restantes tokens context-dependent.
  4. Combina-os numa bitmask final, move-a para o device dos logits e aplica-a nesse device.

Por que razão utilizar pushdown automata em vez de regex?

Por causa do nesting. Uma expressão regular (uma máquina de estados finitos) não consegue fazer match de forma fiável com estruturas como:

{ "user": { "profile": { "settings": { "theme": "dark" } } } }

A parte difícil são as chavetas de fecho }}}: é necessário memorizar quantas chavetas foram abertas. Um Pushdown Automaton tem uma stack que acompanha essa informação, pelo que consegue lidar com profundidade de nesting arbitrária. É também por isso que o XGrammar consegue impor Union types, objetos aninhados e schemas recursivos, nos quais as abordagens baseadas em regex ficam aquém.

Um exemplo concreto: gerar um campo float

Quando o modelo está a gerar "max_discount_percent":, o XGrammar sabe, a partir do schema, que vem a seguir um float. O conjunto de tokens válidos depende do estado do parser e do tokenizer. No início do número, a mask pode admitir um dígito ou um sinal de menos; depois de um dígito, pode admitir continuações como outro dígito, um ponto decimal ou um marcador de expoente.

  • Uma aspa, {, [, true, false ou null não pode iniciar este número, pelo que a mask bloqueia os respetivos tokens nesse estado.
  • O forward pass pode ter atribuído uma probabilidade elevada ao token correspondente a "fifteen". Como esse token não pode dar continuidade a este campo numérico, a mask remove-o e o modelo tem de escolher uma continuação numérica válida.

O que afeta o overhead

Três razões:

  1. Geração e transferência da mask. O XGrammar gera masks na CPU e o serving engine transfere-as para o device dos logits para aplicação in-place. O grau de sobreposição depende da implementação do serving e do workload.
  2. Caching. A maior parte do trabalho de validação é feita em compile time. Em runtime, o trabalho consiste sobretudo em lookups na cache.
  3. Implementação em C++. O hot path está em C++, não em Python, e a mask é aplicada in-place aos logits.

Os benchmarks citados reportam baixo overhead para as gramáticas, tokenizers, hardware e workloads testados. Esses resultados não estabelecem uma garantia geral de latência ou throughput.


Implementação prática com vLLM

O projeto sgr-discount-manager é uma demo externa ilustrativa. Este artigo não fixa o seu commit nem afirma que os snippets abaixo tenham sido executados neste repositório.

Workflow do agenteWorkflow do agente

Estrutura do projeto

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

Passo 1: definir os schemas

# 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):
    """
    Structured response for dynamic pricing. The fields record an intended 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. Model-proposed decision; application code approves it.
    max_discount_percent: float = Field(...,
        description="Proposed discount percentage. Application code enforces policy.")

Passo 2: um cliente LLM que ativa o XGrammar

# sgr/utils/llm_client.py
import json
from typing import TypeVar

from openai import OpenAI
from pydantic import BaseModel

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"):
        # Local vLLM commonly has no authentication. EMPTY is not authentication.
        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 structured outputs to constrain the JSON shape at generation time.
        """
        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 v0.12+ structured outputs. Configure the backend on the server.
        completion = self.client.chat.completions.create(
            model=self.model,
            messages=enhanced_messages,
            temperature=0.1,  # Reduces sampling variation; it is not deterministic.
            extra_body={"structured_outputs": {"json": schema_dict}},
        )

        raw_response = completion.choices[0].message.content
        return schema_class.model_validate_json(raw_response)

[!NOTE] No vLLM atual, structured_outputs: {"json": schema_dict} solicita JSON que corresponda ao schema. Configure o structured-output backend com a opção --structured-outputs-config.backend do servidor quando necessário. Trata-se de imposição por software no inference server, não de imposição por hardware.

Passo 3: orquestrar o agente

# sgr/agent.py
from decimal import Decimal

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 approve_discount(offer: PricingLogic, context: dict) -> Decimal:
    """Enforce the pricing policy independently of the model's explanation."""
    cart_value = Decimal(str(context["current_cart_value"]))
    margin = Decimal(str(context["cart_profit_margin"]))
    proposed = Decimal(str(offer.max_discount_percent))

    if cart_value <= 0 or not Decimal("0") <= margin <= Decimal("1"):
        raise ValueError("Invalid pricing context")

    gross_profit = cart_value * margin
    discount_cost = cart_value * proposed / Decimal("100")
    policy_cap = min(margin * Decimal("100"), Decimal("20"))
    if not Decimal("0") <= proposed <= policy_cap or discount_cost > gross_profit:
        raise ValueError("Proposed discount violates pricing policy")

    return proposed.quantize(Decimal("0.01"))

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: model proposal, then deterministic policy enforcement ---
        print("🧠 Proposing Offer (Schema Enforced)...")
        offer = llm.run_sgr(history, PricingLogic)
        approved_discount = approve_discount(offer, context)

        # Audit log: reasoning is inspectable; pricing is application-enforced.
        print(f"   [Audit] Math: {offer.margin_math}")
        print(f"   [Audit] Approved Discount: {approved_discount}%")

        return (
            "We value your loyalty! Here's a special "
            f"{approved_discount}% discount with code SAVE{approved_discount:.0f}."
        )

    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}")

Passo 4: executar o vLLM com XGrammar

# Start vLLM server with XGrammar backend
vllm serve \
    --model Qwen/Qwen2.5-7B-Instruct \
    --port 8000 \
    --structured-outputs-config.backend xgrammar

# Run the agent
uv run python -m sgr.agent

Output ilustrativo

🤖 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%
🧠 Proposing Offer (Schema Enforced)...
   [Audit] Math: Cart $200 * 0.20 Margin = $40
   [Audit] Approved Discount: 15.00%

💬 Final Reply: We value your loyalty! Here's a special 15.00% discount
   with code SAVE15.

Este output é ilustrativo. O schema restringe a estrutura, mas a aplicação tem de verificar se a aritmética e a política de descontos estão corretas antes de utilizar a oferta.


Schema, vLLM e checklist para produção

Design do schema

  1. Ordene os campos de acordo com o fluxo de raciocínio pretendido. Trate essa ordem como documentação, a menos que chamadas separadas ou verificações na aplicação a imponham.
  2. Escreva descrições Field descritivas. Orientam a atenção do modelo tanto quanto o nome do campo.
  3. Restrinja com Literal e Annotated. Utilize Literal["a", "b"] para enums e Annotated[int, Ge(1), Le(10)] para limites.
  4. Mantenha os schemas focados. Um schema por fase de raciocínio e, depois, composição através de várias chamadas.

Configuração do vLLM

  1. Utilize uma temperatura baixa (0.1–0.3) para reduzir a variação do sampling. Para testes repetíveis, fixe o modelo e a configuração de serving, utilize o modo determinístico do servidor, caso exista, e verifique o resultado.
  2. Deixe o XGrammar tratar da estrutura. Não tente contrariá-lo com instruções de formatação no prompt.
  3. Meça o uso de tokens com o mesmo prompt, modelo, schema e tarefa. O SGR pode emitir mais ou menos tokens do que uma resposta CoT em formato livre.

Considerações para produção

  1. Versione os schemas da mesma forma que versiona as APIs.
  2. Mesmo com SGR, os erros de rede e do servidor continuam a exigir um tratamento robusto.
  3. Registe os outputs SGR brutos para conformidade e debugging; em seguida, registe separadamente a decisão determinística da política.
  4. Recalcule preços, limites, permissões e outras regras semânticas no código da aplicação antes de as utilizar; teste valores-limite que o schema, por si só, não consegue rejeitar.

Conclusão

Descreve uma topologia de raciocínio em Pydantic e deixa que o constrained decoding imponha a estrutura do output. O resultado é:

  • sintaticamente válido por construção, eliminando o loop de retry e reparse
  • auditável ao nível dos campos
  • utilizável com modelos mais pequenos, porque estes deixam de ter de acertar sozinhos no formato
  • potencialmente mais barato de executar, se a validação reduzir os retries ou se o schema e o modelo escolhidos utilizarem menos tokens de output

A demo sgr-discount-manager é um ponto de partida externo. Verifique as suas dependências fixadas e a compatibilidade atual com o vLLM antes de a considerar uma referência executável.


Principais conclusões

  1. O Schema-Guided Reasoning torna explícita uma topologia de raciocínio pretendida, em vez de depender apenas de instruções em prosa.
  2. O constrained decoding impede JSON inválido durante a geração, o que é mais limpo do que validar e repetir posteriormente.
  3. Coloque os campos de análise antes dos campos de decisão quando a resposta deve documentar esse percurso de raciocínio. Utilize chamadas separadas ou verificações na aplicação quando a ordem tiver de ser imposta.
  4. Utilize SGR quando o código a jusante depender da estrutura, não quando o produto for prosa em formato livre.

Referências

SGR Framework

xgrammar

vLLM

Projeto de demonstração

  • sgr-discount-manager — demo ilustrativa mais antiga; não contém todos os exemplos de código deste artigo