Phase 05 - Lesson 29

Rastreamento de Estado de Diálogo

"Quero um restaurante barato no norte... na verdade, mude para moderado... e adicione italiano." Três turnos, três atualizações de estado. O DST mantém o dicionário slot-valor sincronizado para que a reserva funcione.

Tipo: Build Linguagens: Python Pré-requisitos: Fase 5 · 17 (Chatbots), Fase 5 · 20 (Saídas Estruturadas) Tempo: ~75 minutos

O Problema

Em um sistema de diálogo orientado a tarefas, o objetivo do usuário é codificado como um conjunto de pares slot-valor: {cuisine: italian, area: north, price: moderate}. Cada turno do usuário pode adicionar, alterar ou remover um slot. O sistema precisa ler toda a conversa e produzir o estado atual corretamente.

Erre um único slot e o sistema reserva o restaurante errado, agenda o voo errado ou cobra o cartão errado. O DST é a dobradiça entre o que o usuário disse e o que o backend executa.

Por que isso ainda importa em 2026, apesar dos LLMs:

  • Domínios sensíveis a conformidade (bancos, saúde, reservas aéreas) exigem valores de slot determinísticos, não geração em formato livre.
  • Agentes que usam ferramentas ainda precisam resolver slots antes de chamar APIs.
  • Correção em múltiplos turnos é mais difícil do que parece: "na verdade, não, mude para quinta-feira."

O pipeline moderno: conceitos clássicos de DST + extratores baseados em LLM + guardrails de saída estruturada.

O Conceito

DST: histórico do diálogo → estado slot-valor

Estrutura da tarefa. Um schema define domínios (restaurante, hotel, táxi) e seus slots (cuisine, area, price, people). Cada slot pode estar vazio, preenchido com um valor de um conjunto fechado (price: {cheap, moderate, expensive}), ou um valor em formato livre (name: "The Copper Kettle").

Duas formulações de DST.

  • Classificação. Para cada par (slot, candidate_value), prever sim/não. Funciona para slots de vocabulário fechado. Padrão antes de 2020.
  • Geração. Dado o diálogo, gerar valores de slot como texto livre. Funciona para slots de vocabulário aberto. O padrão moderno.

Métrica. Joint Goal Accuracy (JGA) — a fração de turnos em que todos os slots estão corretos. Tudo ou nada. O leaderboard do MultiWOZ 2.4 chega a cerca de 83% em 2026.

Arquiteturas.

  1. Baseado em regras (regex de slot + palavra-chave). Baseline forte para domínios estreitos. Depurável.
  2. TripPy / BERT-DST. Geração baseada em cópia com codificação BERT. Padrão pré-LLM.
  3. LDST (LLaMA + LoRA). LLM ajustado por instrução com prompting de domínio-slot. Alcança qualidade nível ChatGPT no MultiWOZ 2.4.
  4. Sem ontologia (2024–26). Pule o schema; gere nomes de slot e valores diretamente. Lida com domínios abertos.
  5. Prompt + saída estruturada (2024–26). LLM com schema Pydantic + decodificação restrita. 5 linhas de código, pronto para produção.

Os modos clássicos de falha

  • Co-referência entre turnos. "Vamos ficar com a primeira opção." Precisa resolver qual opção.
  • Sobrescrever vs anexar. Usuário diz "adicione italiano." Você substitui cuisine ou anexa?
  • Confirmações implícitas. "Ok, legal" — isso aceitou a reserva oferecida?
  • Correção. "Na verdade, mude para 19h." Precisa atualizar o horário sem limpar os outros slots.
  • Co-referência à fala anterior do sistema. "Sim, essa." Qual "essa"?

Construa

Passo 1: extrator de slot baseado em regras

Veja code/main.py. Regex + dicionários de sinônimos cobrem 70% das falas canônicas em domínios estreitos:

CUISINE_SYNONYMS = {
    "italian": ["italian", "pasta", "pizza", "italy"],
    "chinese": ["chinese", "chow mein", "noodles"],
}


def extract_cuisine(utterance):
    for canonical, synonyms in CUISINE_SYNONYMS.items():
        if any(syn in utterance.lower() for syn in synonyms):
            return canonical
    return None

Frágil fora do vocabulário canônico. Funciona para confirmações determinísticas de slot.

Passo 2: laço de atualização de estado

def update_state(state, utterance):
    new_state = dict(state)
    for slot, extractor in SLOT_EXTRACTORS.items():
        value = extractor(utterance)
        if value is not None:
            new_state[slot] = value
    for slot in NEGATION_CLEARS:
        if is_negated(utterance, slot):
            new_state[slot] = None
    return new_state

Três invariantes:

  • Nunca redefina um slot que o usuário não tocou.
  • Negação explícita ("esqueça a cuisine") deve limpar.
  • Correção do usuário ("na verdade...") deve sobrescrever, não anexar.

Passo 3: DST guiado por LLM com saída estruturada

from pydantic import BaseModel
from typing import Literal, Optional
import instructor

class RestaurantState(BaseModel):
    cuisine: Optional[Literal["italian", "chinese", "indian", "thai", "any"]] = None
    area: Optional[Literal["north", "south", "east", "west", "center"]] = None
    price: Optional[Literal["cheap", "moderate", "expensive"]] = None
    people: Optional[int] = None
    day: Optional[str] = None


def llm_dst(history, llm):
    prompt = f"""You track the slot values of a restaurant booking across turns.
Dialogue so far:
{render(history)}

Update the state based on the latest user turn. Output only the JSON state."""
    return llm(prompt, response_model=RestaurantState)

Instructor + Pydantic garante um objeto de estado válido. Sem regex, sem incompatibilidades de schema, sem slots alucinados.

Passo 4: avaliação por JGA

def joint_goal_accuracy(predicted_states, gold_states):
    correct = sum(1 for p, g in zip(predicted_states, gold_states) if p == g)
    return correct / len(predicted_states)

Calibre: em que fração dos turnos o sistema acerta TODOS os slots? Para o MultiWOZ 2.4, os melhores sistemas de 2026: 80-83%. Seu sistema dentro do domínio deve superar isso no seu vocabulário estreito, ou o baseline de LLM te vence.

Passo 5: tratamento de correção

CORRECTION_CUES = {"actually", "no wait", "on second thought", "change that to"}


def is_correction(utterance):
    return any(cue in utterance.lower() for cue in CORRECTION_CUES)

Ao detectar uma correção, sobrescreva o último slot atualizado em vez de anexar. Difícil de acertar sem a ajuda de um LLM. O padrão moderno: sempre deixe o LLM regenerar todo o estado a partir do histórico em vez de atualizar incrementalmente — isso lida naturalmente com correções.

Armadilhas

  • Custo da regeneração de histórico completo. Deixar o LLM regenerar o estado a cada turno custa O(n²) tokens no total. Limite o histórico ou resuma os turnos mais antigos.
  • Deriva de schema. Adicionar novos slots depois quebra dados de treino antigos. Versione seu schema.
  • Sensibilidade a maiúsculas. "Italian" vs "italian" vs "ITALIAN" — normalize em todos os lugares.
  • Herança implícita. Se o usuário já especificou "para 4 pessoas", um novo pedido para um horário diferente não deve limpar people. Sempre passe o histórico completo.
  • Formato livre vs conjunto fechado. Nomes, horários e endereços precisam de slots de formato livre; cuisines e areas são fechados. Misture os dois no schema.

Use

A stack de 2026:

Situação Abordagem
Domínio estreito (uma ou duas intenções) Baseado em regras + regex
Domínio amplo, dados rotulados disponíveis LDST (LLaMA + LoRA em dados estilo MultiWOZ)
Domínio amplo, sem rótulos, pronto para prod LLM + Instructor + schema Pydantic
Falado / voz ASR + normalizador + LLM-DST
Fluxo de reserva multi-domínio LLM guiado por schema com modelos Pydantic por domínio
Sensível a conformidade Baseado em regras como primário, LLM como fallback com fluxo de confirmação

Entregue

Salve como outputs/skill-dst-designer.md:

---
name: dst-designer
description: Design a dialogue state tracker — schema, extractor, update policy, evaluation.
version: 1.0.0
phase: 5
lesson: 29
tags: [nlp, dialogue, task-oriented]
---

Given a use case (domain, languages, vocab openness, compliance needs), output:

1. Schema. Domain list, slots per domain, open vs closed vocabulary per slot.
2. Extractor. Rule-based / seq2seq / LLM-with-Pydantic. Reason.
3. Update policy. Regenerate-whole-state / incremental; correction handling; negation handling.
4. Evaluation. Joint Goal Accuracy on a held-out dialogue set, slot-level precision/recall, confusion on the hardest slot.
5. Confirmation flow. When to explicitly ask the user to confirm (destructive actions, low-confidence extractions).

Refuse LLM-only DST for compliance-sensitive slots without a rule-based secondary check. Refuse any DST that cannot roll back a slot on user correction. Flag schemas without version tags.

Exercícios

  1. Fácil. Construa o rastreador de estado baseado em regras em code/main.py para 3 slots (cuisine, area, price). Teste em 10 diálogos feitos à mão. Meça a JGA.
  2. Médio. O mesmo dataset com Instructor + Pydantic + um LLM pequeno. Compare a JGA. Inspecione os turnos mais difíceis.
  3. Difícil. Implemente os dois e faça roteamento: baseado em regras como primário, LLM como fallback quando o baseado em regras emite <2 slots com confiança. Meça a JGA combinada e o custo de inferência por turno.

Termos-Chave

Termo O que as pessoas dizem O que de fato significa
DST Rastreamento de estado de diálogo Manter o dicionário slot-valor ao longo dos turnos do diálogo.
Slot Unidade de intenção do usuário Parâmetro nomeado que o backend precisa (cuisine, date).
Domínio A área da tarefa Restaurante, hotel, táxi — conjuntos de slots.
JGA Joint Goal Accuracy Fração de turnos em que todos os slots estão corretos. Tudo ou nada.
MultiWOZ O benchmark Dataset Wizard-of-Oz multi-domínio; avaliação padrão de DST.
DST sem ontologia Sem schema Gerar nomes de slot e valores diretamente, sem lista fixa.
Correção "Na verdade..." Turno que sobrescreve um slot previamente preenchido.

Leitura Adicional

0 lifetime access. Curriculum based on AI Engineering from Scratch by Rohit Ghumare (MIT, used under attribution).