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
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.
- Baseado em regras (regex de slot + palavra-chave). Baseline forte para domínios estreitos. Depurável.
- TripPy / BERT-DST. Geração baseada em cópia com codificação BERT. Padrão pré-LLM.
- LDST (LLaMA + LoRA). LLM ajustado por instrução com prompting de domínio-slot. Alcança qualidade nível ChatGPT no MultiWOZ 2.4.
- Sem ontologia (2024–26). Pule o schema; gere nomes de slot e valores diretamente. Lida com domínios abertos.
- 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
- Fácil. Construa o rastreador de estado baseado em regras em
code/main.pypara 3 slots (cuisine, area, price). Teste em 10 diálogos feitos à mão. Meça a JGA. - Médio. O mesmo dataset com Instructor + Pydantic + um LLM pequeno. Compare a JGA. Inspecione os turnos mais difíceis.
- 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
- Budzianowski et al. (2018). MultiWOZ — A Large-Scale Multi-Domain Wizard-of-Oz — o benchmark canônico.
- Feng et al. (2023). Towards LLM-driven Dialogue State Tracking (LDST) — ajuste por instrução com LLaMA + LoRA para DST.
- Heck et al. (2020). TripPy — A Triple Copy Strategy for Value Independent Neural Dialog State Tracking — o cavalo de batalha do DST baseado em cópia.
- King, Flanigan (2024). Unsupervised End-to-End Task-Oriented Dialogue with LLMs — TOD não supervisionado baseado em EM.
- Leaderboard do MultiWOZ — resultados canônicos de DST.