"북쪽에 있는 저렴한 식당을 원해요... 아니요, 보통 가격대로 바꿔주세요... 그리고 이탈리안으로 해주세요." 세 번의 대화 차례(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)인지 이해합니다.
작업 지향형 대화 시스템(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 답변 완료
개념
작업 구조(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).
규칙 기반(slot regex + keyword). 좁은 도메인에서는 강한 기준선(baseline)입니다. 디버깅(debugging)이 쉽습니다.
TripPy / BERT-DST. BERT 인코딩(encoding)을 사용하는 복사 기반 생성(copy-based generation)입니다. LLM 이전 시대의 표준 방식입니다.
LDST (LLaMA + LoRA). 도메인-슬롯 프롬프팅(domain-slot prompting)을 사용하는 지시어 튜닝(instruction-tuned) LLM입니다. MultiWOZ 2.4에서 ChatGPT 수준의 품질에 도달합니다.
온톨로지 프리(ontology-free; 2024-26). 스키마를 건너뛰고 슬롯 이름과 값을 직접 생성합니다. 열린 도메인에 대응합니다.
프롬프트 + 구조화 출력(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"],
}
defextract_cuisine(utterance):
for canonical, synonyms in CUISINE_SYNONYMS.items():
ifany(syn in utterance.lower() for syn in synonyms):
return canonical
returnNone
표준 어휘 밖에서는 취약합니다. 하지만 결정적인 슬롯 확인(deterministic slot confirmation)에는 잘 작동합니다.
Step 2: 상태 갱신 루프
defupdate_state(state, utterance):
new_state = dict(state)
for slot, extractor in SLOT_EXTRACTORS.items():
value = extractor(utterance)
if value isnotNone:
new_state[slot] = value
for slot in NEGATION_CLEARS:
if is_negated(utterance, slot):
new_state[slot] = Nonereturn new_state
세 가지 불변 조건(invariant)을 지킵니다.
사용자가 건드리지 않은 슬롯은 절대 초기화하지 않습니다.
명시적 부정(negation), 예를 들어 "never mind the cuisine"은 슬롯을 비워야 합니다.
사용자 정정, 예를 들어 "actually..."는 추가가 아니라 덮어쓰기여야 합니다.
Step 3: 구조화 출력을 사용하는 LLM 기반 DST
from pydantic import BaseModel
from typing importLiteral, Optionalimport instructor
classRestaurantState(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] = Nonedefllm_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 평가
defjoint_goal_accuracy(predicted_states, gold_states):
correct = sum(1for p, g inzip(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"}
defis_correction(utterance):
returnany(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.
연습문제
쉬움.code/main.py의 규칙 기반 상태 추적기를 cuisine, area, price 세 슬롯에 대해 만듭니다. 직접 만든 대화 10개로 시험해 보고 JGA를 측정합니다.
중간. 같은 데이터셋(dataset)을 Instructor + Pydantic + 작은 LLM으로 풀어 봅니다. JGA를 비교하고 가장 어려운 대화 차례를 살펴봅니다.
어려움. 두 방식을 모두 구현하고 라우팅(routing)합니다. 규칙 기반을 1차로 두고, 규칙 기반이 신뢰도(confidence) 있는 슬롯을 2개 미만으로만 내놓을 때 LLM 보조(fallback)를 사용합니다. 결합 JGA와 대화 차례당 추론 비용(inference cost)을 측정합니다.