대화 상태 추적(Dialogue State Tracking; DST)

"북쪽에 있는 저렴한 식당을 원해요... 아니요, 보통 가격대로 바꿔주세요... 그리고 이탈리안으로 해주세요." 세 번의 대화 차례(turn), 세 번의 상태 갱신(state update)입니다. DST는 예약(booking)이 제대로 작동하도록 슬롯-값 사전(slot-value dict)을 계속 맞춰둡니다.

유형: Build 언어: Python 선수 강의: Phase 5 · 17 (Chatbots), Phase 5 · 20 (Structured Outputs) 예상 시간: 약 75분

학습 목표

  • 작업 지향형 대화(task-oriented dialogue)에서 상태(state), 슬롯(slot), 도메인(domain)의 역할을 설명합니다.
  • 분류(classification) 기반 DST와 생성(generation) 기반 DST를 비교합니다.
  • 결합 목표 정확도(Joint Goal Accuracy; JGA)가 왜 엄격한 지표(metric)인지 이해합니다.
  • 규칙 기반(rule-based) DST와 LLM + 구조화 출력(structured output) DST의 절충(tradeoff)을 구분합니다.

문제

작업 지향형 대화 시스템(task-oriented dialogue system)에서 사용자의 목표는 슬롯-값 쌍(slot-value pair)의 집합으로 표현됩니다. 예를 들어 {cuisine: italian, area: north, price: moderate}입니다. 사용자의 각 대화 차례는 슬롯을 추가하거나, 바꾸거나, 제거할 수 있습니다. 시스템은 전체 대화(conversation)를 읽고 현재 상태를 정확히 출력해야 합니다.

슬롯 하나만 틀려도 시스템은 잘못된 식당을 예약하거나, 잘못된 항공편(flight) 일정을 잡거나, 잘못된 카드(card)에 결제(charge)할 수 있습니다. DST는 사용자가 말한 내용과 백엔드(backend)가 실행할 동작 사이를 잇는 경첩(hinge)입니다.

LLM이 있는 2026년에도 DST가 중요한 이유는 다음과 같습니다.

  • 금융(banking), 의료(healthcare), 항공권 예약(airline booking)처럼 규정 준수가 중요한(compliance-sensitive) 도메인에서는 자유 형식 생성(free-form generation)이 아니라 결정적(deterministic) 슬롯 값이 필요합니다.
  • 도구를 사용하는 에이전트(tool-use agent)도 API를 호출하기 전에 슬롯 해석(slot resolution)이 필요합니다.
  • 여러 차례에 걸친 정정(multi-turn correction)은 보기보다 어렵습니다. 예를 들어 "아니요, 목요일로 바꿔주세요"라는 말은 이전 상태를 갱신해야 합니다.

현대적인 파이프라인(pipeline)은 고전적인 DST 개념, LLM 추출기(extractor), 구조화 출력 가드레일(guardrail)을 결합합니다.

사전 테스트

2문제 · 이 강의를 시작하기 전에 얼마나 알고 있는지 확인해보세요

1.작업 지향형 대화 시스템(task-oriented dialogue system)에서 대화 상태(dialogue state), 즉 슬롯-값 사전(slot-value dict)의 역할은 무엇인가요?

2.분류(classification) 기반 DST와 생성(generation) 기반 DST의 차이는 무엇인가요?

0/2 답변 완료

개념

dialogue state tracking — slots fill, update, correct across turns turn 1 "I want a cheap restaurant in the north." cuisine: — area: north price: cheap people: — turn 2 "Italian food please." cuisine: italian area: north price: cheap people: — turn 3 "Actually make it moderate pricing." cuisine: italian area: north price: moderate people: — turn 4 "For four people." cuisine: italian area: north price: moderate people: 4 final state (JGA counts if and only if all slots match gold) {cuisine: italian, area: north, price: moderate, people: 4} → pass to backend: book_restaurant(cuisine=italian, area=north, price=moderate, people=4) invariants — correction overwrites (turn 3); explicit negation clears; untouched slots persist

작업 구조(task structure). 스키마(schema)는 도메인과 슬롯을 정의합니다. 도메인은 식당, 호텔, 택시 같은 작업 영역이고, 슬롯은 cuisine, area, price, people 같은 매개변수(parameter)입니다. 각 슬롯은 비어 있거나, 닫힌 집합(closed set)의 값을 갖거나(예: price는 {cheap, moderate, expensive} 중 하나), 자유 형식(free-form) 값을 가질 수 있습니다(예: name은 "The Copper Kettle"처럼).

두 가지 DST 정식화(formulation).

  • 분류(Classification).(slot, candidate_value) 쌍에 대해 yes/no를 예측합니다. 닫힌 어휘(closed-vocab) 슬롯에 잘 맞습니다. 2020년 이전의 표준 방식입니다.
  • 생성(Generation). 대화가 주어졌을 때 슬롯 값을 자유 텍스트로 생성합니다. 열린 어휘(open-vocab) 슬롯에 맞습니다. 현대적인 기본 방식입니다.

평가 지표. 결합 목표 정확도(Joint Goal Accuracy; JGA)는 모든 슬롯이 정확한 대화 차례의 비율입니다. 전부 맞히지 못하면 0점인 전부-아니면-전무(all-or-nothing) 방식입니다. MultiWOZ 2.4 리더보드(leaderboard) 상위권은 2026년 기준 약 83% 수준입니다.

아키텍처(architecture).

  1. 규칙 기반(slot regex + keyword). 좁은 도메인에서는 강한 기준선(baseline)입니다. 디버깅(debugging)이 쉽습니다.
  2. TripPy / BERT-DST. BERT 인코딩(encoding)을 사용하는 복사 기반 생성(copy-based generation)입니다. LLM 이전 시대의 표준 방식입니다.
  3. LDST (LLaMA + LoRA). 도메인-슬롯 프롬프팅(domain-slot prompting)을 사용하는 지시어 튜닝(instruction-tuned) LLM입니다. MultiWOZ 2.4에서 ChatGPT 수준의 품질에 도달합니다.
  4. 온톨로지 프리(ontology-free; 2024-26). 스키마를 건너뛰고 슬롯 이름과 값을 직접 생성합니다. 열린 도메인에 대응합니다.
  5. 프롬프트 + 구조화 출력(2024-26). Pydantic 스키마와 제약 디코딩(constrained decoding)을 사용하는 LLM 방식입니다. 다섯 줄 정도의 코드로 운영 환경에 바로 투입할 수 있는(production-ready) 패턴을 만들 수 있습니다.

고전적인 실패 양상(failure mode)

  • 대화 차례 사이의 상호 참조(co-reference). "첫 번째 옵션으로 할게요." 어떤 옵션을 가리키는지 해석(resolve)해야 합니다.
  • 덮어쓰기(overwrite) vs 추가(append). 사용자가 "Italian을 추가해줘"라고 말했을 때 cuisine을 교체할지, 추가할지 정해야 합니다.
  • 암묵적 확인(implicit confirmation). "좋아요"라는 말이 제안된 예약을 수락(accept)한 것인지 판단해야 합니다.
  • 정정(correction). "아니요, 오후 7시로 해주세요."는 다른 슬롯을 지우지 않고 time 슬롯만 갱신해야 합니다.
  • 이전 시스템 발화에 대한 상호 참조. "네, 그걸로요."에서 "그것"이 무엇인지 알아야 합니다.

직접 만들기

Step 1: 규칙 기반 슬롯 추출기

code/main.py를 확인합니다. 정규식(regex)과 동의어 사전(synonym dictionary)은 좁은 도메인의 표준적인(canonical) 발화 중 70% 정도를 처리할 수 있습니다.

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

표준 어휘 밖에서는 취약합니다. 하지만 결정적인 슬롯 확인(deterministic slot confirmation)에는 잘 작동합니다.

Step 2: 상태 갱신 루프

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

세 가지 불변 조건(invariant)을 지킵니다.

  • 사용자가 건드리지 않은 슬롯은 절대 초기화하지 않습니다.
  • 명시적 부정(negation), 예를 들어 "never mind the cuisine"은 슬롯을 비워야 합니다.
  • 사용자 정정, 예를 들어 "actually..."는 추가가 아니라 덮어쓰기여야 합니다.

Step 3: 구조화 출력을 사용하는 LLM 기반 DST

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 조합은 유효한 상태 객체(state object)를 보장합니다. 정규식도, 스키마 불일치도, 환각으로 만들어진 슬롯(hallucinated slot)도 없습니다.

Step 4: 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)

보정 기준은 다음과 같습니다. 시스템이 모든 슬롯을 맞힌 대화 차례의 비율은 얼마인가? MultiWOZ 2.4에서 2026년 상위 시스템은 80-83% 수준입니다. 여러분이 만든 좁은 어휘 기반의 도메인 내(in-domain) 시스템이라면 이보다 높아야 하고, 그렇지 않다면 LLM 기준선이 더 나을 수 있습니다.

Step 5: 정정 처리

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)

정정이 감지되면 추가가 아니라 마지막으로 갱신된 슬롯을 덮어씁니다. LLM의 도움 없이 정확히 처리하기는 어렵습니다. 현대적인 패턴은 점진적으로(incrementally) 갱신하는 대신 매 대화 차례마다 전체 이력에서 상태를 다시 생성하도록 LLM에 맡기는 것입니다. 이 방식은 정정을 자연스럽게 처리합니다.

흔한 함정

  • 전체 이력 재생성 비용(full-history regeneration cost). LLM이 매 대화 차례마다 상태 전체를 다시 만들면 총 토큰(token) 비용이 O(n²)이 됩니다. 이력을 잘라내거나(cap) 오래된 대화 차례를 요약(summarize)합니다.
  • 스키마 변동(schema drift). 새 슬롯을 나중에 추가하면 기존 학습 데이터가 깨질 수 있습니다. 스키마에 버전을 매깁니다.
  • 대소문자 민감도(case sensitivity). "Italian", "italian", "ITALIAN"을 모두 정규화(normalize)합니다.
  • 암묵적 상속(implicit inheritance). 사용자가 이전에 "4명을 위해"라고 했다면, 다른 시간을 새로 요청해도 people 슬롯은 지우지 않아야 합니다. 항상 전체 이력을 함께 전달합니다.
  • 자유 형식 vs 닫힌 집합. 이름, 시간, 주소는 자유 형식 슬롯이 필요하고, cuisine과 area는 닫힌 집합이 적합합니다. 스키마에서 두 가지를 함께 사용합니다.

사용하기

2026년 기준 추천 구성은 다음과 같습니다.

상황접근 방식
좁은 도메인 (의도 1-2개)규칙 기반 + 정규식
넓은 도메인, 라벨이 붙은 데이터 있음LDST (MultiWOZ 형식 데이터로 LLaMA + LoRA)
넓은 도메인, 라벨 없음, 운영 투입 필요LLM + Instructor + Pydantic 스키마
음성(spoken / voice)ASR + 정규화기(normalizer) + LLM-DST
다중 도메인 예약 흐름도메인별 Pydantic 모델을 갖춘 스키마 가이드형 LLM
규정 준수 민감규칙 기반을 1차로, 확인 흐름이 있는 LLM을 보조로 사용

산출물 만들기

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

Guide the student in Korean.

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.

연습문제

  1. 쉬움. code/main.py의 규칙 기반 상태 추적기를 cuisine, area, price 세 슬롯에 대해 만듭니다. 직접 만든 대화 10개로 시험해 보고 JGA를 측정합니다.
  2. 중간. 같은 데이터셋(dataset)을 Instructor + Pydantic + 작은 LLM으로 풀어 봅니다. JGA를 비교하고 가장 어려운 대화 차례를 살펴봅니다.
  3. 어려움. 두 방식을 모두 구현하고 라우팅(routing)합니다. 규칙 기반을 1차로 두고, 규칙 기반이 신뢰도(confidence) 있는 슬롯을 2개 미만으로만 내놓을 때 LLM 보조(fallback)를 사용합니다. 결합 JGA와 대화 차례당 추론 비용(inference cost)을 측정합니다.

핵심 용어

용어흔한 설명실제 의미
DSTDialogue state tracking대화 차례에 걸쳐 슬롯-값 사전을 유지합니다.
Slot사용자 의도의 단위백엔드가 필요로 하는 이름이 붙은 매개변수입니다(cuisine, date 등).
Domain작업 영역식당, 호텔, 택시처럼 슬롯 집합을 정하는 영역입니다.
JGAJoint Goal Accuracy모든 슬롯이 맞은 대화 차례의 비율입니다. 전부-아니면-전무 방식입니다.
MultiWOZ벤치마크Multi-domain Wizard-of-Oz 데이터셋이며 DST 평가 표준입니다.
Ontology-free DST스키마 없음고정된 목록 없이 슬롯 이름과 값을 직접 생성합니다.
Correction"Actually..."이미 채운 슬롯을 덮어쓰는 대화 차례입니다.

더 읽을거리

실습 코드

이 강의의 실습 코드 1개

main
Code

산출물

이 강의에서 생성된 프롬프트, 스킬, 코드 산출물 1개

dst-designer

Design a dialogue state tracker — schema, extractor, update policy, evaluation.

Skill

확인 문제

3문제 · 모두 맞추면 완료 표시가 가능합니다

1.MultiWOZ 2.4에서 결합 목표 정확도(Joint Goal Accuracy; JGA)가 최신 시스템에서도 약 83% 수준에 머뭅니다. JGA가 엄격한 지표(metric)로 여겨지는 이유는 무엇인가요?

2.사용자가 'I want Italian food in the north'라고 말한 뒤 'Actually, make it moderate price.'라고 이어서 말합니다. 상태 추적기(state tracker)는 기존 슬롯을 어떻게 처리해야 하나요?

3.매 대화 차례(turn)마다 전체 대화 이력(conversation history)에서 대화 상태를 다시 생성하도록 LLM을 사용하면 정정(correction)을 자연스럽게 처리할 수 있지만, 어떤 실질적인 문제가 발생하나요?

0/3 답변 완료

추가 문제 풀기

AI가 강의 내용을 바탕으로 새로운 문제를 생성합니다