Phase 05 - Lesson 29

Seguimiento del Estado del Diálogo

"Quiero un restaurante barato en el norte... mejor que sea moderado... y agrega italiano." Tres turnos, tres actualizaciones de estado. El DST mantiene sincronizado el diccionario slot-valor para que la reserva funcione.

Tipo: Build Lenguajes: Python Prerrequisitos: Fase 5 · 17 (Chatbots), Fase 5 · 20 (Salidas Estructuradas) Tiempo: ~75 minutos

El Problema

En un sistema de diálogo orientado a tareas, el objetivo del usuario se codifica como un conjunto de pares slot-valor: {cuisine: italian, area: north, price: moderate}. Cada turno del usuario puede agregar, cambiar o eliminar un slot. El sistema debe leer toda la conversación y producir el estado actual correctamente.

Equivócate en un solo slot y el sistema reserva el restaurante equivocado, agenda el vuelo equivocado o cobra a la tarjeta equivocada. El DST es la bisagra entre lo que dijo el usuario y lo que ejecuta el backend.

Por qué sigue importando en 2026 a pesar de los LLM:

  • Los dominios sensibles al cumplimiento (banca, salud, reservas aéreas) requieren valores de slot deterministas, no generación de formato libre.
  • Los agentes que usan herramientas aún necesitan resolver slots antes de llamar a las APIs.
  • La corrección en múltiples turnos es más difícil de lo que parece: "no, mejor que sea el jueves."

El pipeline moderno: conceptos clásicos de DST + extractores basados en LLM + guardrails de salida estructurada.

El Concepto

DST: historial del diálogo → estado slot-valor

Estructura de la tarea. Un schema define dominios (restaurante, hotel, taxi) y sus slots (cuisine, area, price, people). Cada slot puede estar vacío, lleno con un valor de un conjunto cerrado (price: {cheap, moderate, expensive}), o un valor de formato libre (name: "The Copper Kettle").

Dos formulaciones de DST.

  • Clasificación. Para cada par (slot, candidate_value), predecir sí/no. Funciona para slots de vocabulario cerrado. Estándar antes de 2020.
  • Generación. Dado el diálogo, generar valores de slot como texto libre. Funciona para slots de vocabulario abierto. El estándar moderno.

Métrica. Joint Goal Accuracy (JGA) — la fracción de turnos en los que cada slot es correcto. Todo o nada. El leaderboard de MultiWOZ 2.4 alcanza alrededor del 83% en 2026.

Arquitecturas.

  1. Basada en reglas (regex de slot + palabra clave). Baseline sólido para dominios estrechos. Depurable.
  2. TripPy / BERT-DST. Generación basada en copia con codificación BERT. Estándar pre-LLM.
  3. LDST (LLaMA + LoRA). LLM ajustado por instrucción con prompting de dominio-slot. Alcanza calidad nivel ChatGPT en MultiWOZ 2.4.
  4. Sin ontología (2024–26). Omite el schema; genera nombres de slot y valores directamente. Maneja dominios abiertos.
  5. Prompt + salida estructurada (2024–26). LLM con schema Pydantic + decodificación restringida. 5 líneas de código, listo para producción.

Los modos clásicos de fallo

  • Correferencia entre turnos. "Quedémonos con la primera opción." Necesita resolver cuál opción.
  • Sobrescribir vs anexar. El usuario dice "agrega italiano." ¿Reemplazas cuisine o anexas?
  • Confirmaciones implícitas. "Ok, genial" — ¿eso aceptó la reserva ofrecida?
  • Corrección. "Mejor que sea a las 19h." Debe actualizar la hora sin borrar los otros slots.
  • Correferencia a la enunciación anterior del sistema. "Sí, ese." ¿Cuál "ese"?

Constrúyelo

Paso 1: extractor de slot basado en reglas

Consulta code/main.py. Regex + diccionarios de sinónimos cubren el 70% de las enunciaciones canónicas en dominios estrechos:

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 fuera del vocabulario canónico. Funciona para confirmaciones deterministas de slot.

Paso 2: bucle de actualización 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

Tres invariantes:

  • Nunca reinicies un slot que el usuario no tocó.
  • La negación explícita ("olvida la cuisine") debe borrar.
  • La corrección del usuario ("mejor...") debe sobrescribir, no anexar.

Paso 3: DST guiado por LLM con salida estructurada

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 garantiza un objeto de estado válido. Sin regex, sin incompatibilidades de schema, sin slots alucinados.

Paso 4: evaluación 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)

Calibra: ¿en qué fracción de los turnos el sistema acierta TODOS los slots? Para MultiWOZ 2.4, los mejores sistemas de 2026: 80-83%. Tu sistema dentro del dominio debería superar eso en tu vocabulario estrecho, o el baseline de LLM te gana.

Paso 5: manejo de la corrección

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)

Al detectar una corrección, sobrescribe el último slot actualizado en lugar de anexar. Difícil de acertar sin la ayuda de un LLM. El patrón moderno: deja siempre que el LLM regenere todo el estado a partir del historial en lugar de actualizar incrementalmente — esto maneja las correcciones de forma natural.

Trampas

  • Costo de la regeneración del historial completo. Dejar que el LLM regenere el estado en cada turno cuesta O(n²) tokens en total. Limita el historial o resume los turnos más antiguos.
  • Deriva del schema. Agregar nuevos slots después rompe los datos de entrenamiento antiguos. Versiona tu schema.
  • Sensibilidad a mayúsculas. "Italian" vs "italian" vs "ITALIAN" — normaliza en todas partes.
  • Herencia implícita. Si el usuario ya especificó "para 4 personas", una nueva solicitud para una hora diferente no debe borrar people. Pasa siempre el historial completo.
  • Formato libre vs conjunto cerrado. Los nombres, horas y direcciones necesitan slots de formato libre; cuisines y areas son cerrados. Mezcla ambos en el schema.

Úsalo

El stack de 2026:

Situación Enfoque
Dominio estrecho (una o dos intenciones) Basado en reglas + regex
Dominio amplio, datos etiquetados disponibles LDST (LLaMA + LoRA con datos estilo MultiWOZ)
Dominio amplio, sin etiquetas, listo para prod LLM + Instructor + schema Pydantic
Hablado / voz ASR + normalizador + LLM-DST
Flujo de reserva multidominio LLM guiado por schema con modelos Pydantic por dominio
Sensible al cumplimiento Basado en reglas como primario, LLM como fallback con flujo de confirmación

Entrégalo

Guarda 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.

Ejercicios

  1. Fácil. Construye el rastreador de estado basado en reglas en code/main.py para 3 slots (cuisine, area, price). Pruébalo en 10 diálogos hechos a mano. Mide la JGA.
  2. Medio. El mismo dataset con Instructor + Pydantic + un LLM pequeño. Compara la JGA. Inspecciona los turnos más difíciles.
  3. Difícil. Implementa ambos y enruta: basado en reglas como primario, LLM como fallback cuando el basado en reglas emite <2 slots con confianza. Mide la JGA combinada y el costo de inferencia por turno.

Términos Clave

Término Lo que dice la gente Lo que realmente significa
DST Seguimiento del estado del diálogo Mantener el diccionario slot-valor a lo largo de los turnos del diálogo.
Slot Unidad de intención del usuario Parámetro nombrado que el backend necesita (cuisine, date).
Dominio El área de la tarea Restaurante, hotel, taxi — conjuntos de slots.
JGA Joint Goal Accuracy Fracción de turnos en los que cada slot es correcto. Todo o nada.
MultiWOZ El benchmark Dataset Wizard-of-Oz multidominio; evaluación estándar de DST.
DST sin ontología Sin schema Generar nombres de slot y valores directamente, sin lista fija.
Corrección "Mejor..." Turno que sobrescribe un slot previamente lleno.

Lecturas Adicionales

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