Dialog Act 정의¶
현재 런타임이 사용하는 dialog act 목록이다. 정의 위치는 dm/core/dialog/policy/models.py 의 DialogAct enum 이다.
Dialog act 란¶
발화를 "무슨 내용인가"가 아니라 "무슨 행위를 수행하는가" 로 분류하는 라벨이다. intent 와 역할이 다르다.
| Dialog act | Intent | |
|---|---|---|
| 답하는 질문 | 이 발화로 무엇을 하고 있나 | 사용자가 무엇을 이루려 하나 |
| 도메인 의존성 | 독립. 모든 시나리오가 같은 값을 쓴다 | 의존. 시나리오마다 새로 정의한다 |
| 값의 출처 | 아래 고정 16종 | 시나리오 설계자가 정의 |
같은 발화가 두 값을 동시에 갖는다. "예약해줘"는 dialog act 가 request(행위 요청), intent 가 book_restaurant 다. act 는 어느 도메인에서나 같지만 intent 는 그 서비스에만 있는 이름이다.
전체 목록 (16종)¶
축은 다섯 가지다.
정보 제공¶
| 값 | 의미 | 예 |
|---|---|---|
inform |
무언가를 진술한다. 기본값 | "날씨가 좋네", "어제 병원 갔어" |
응답¶
| 값 | 의미 | 예 |
|---|---|---|
affirm |
긍정 | "응", "네", "그래", "좋아" |
deny |
부정 | "아니", "싫어", "안 해" |
negate |
부인 | 앞선 내용을 뒤집는다 |
confirm |
확인 | 제시된 내용을 맞다고 확인한다 |
correct |
정정 | "아니 그게 아니라", "아까 말한 거 바꿀게" |
상대에게 요구¶
| 값 | 의미 | 예 |
|---|---|---|
question |
정보를 묻는다 | "너는 누구야?", "병원 어디로 가야 해?" |
request |
행위를 해달라고 한다 | "차 추천해줘", "위로 좀 해줘" |
request_alt |
이미 받은 제안 대신 다른 것을 원한다 | "다른 건 없어?", "그거 말고" |
repeat |
직전 발화를 다시 요청 | "뭐라고?", "다시 말해줘" |
help |
사용법·방법을 요청 | "어떻게 하는 거야?" |
대화 관리¶
| 값 | 의미 | 예 |
|---|---|---|
greeting |
인사 | "안녕", "반가워요" |
thank |
감사 | "고마워", "감사합니다" |
goodbye |
작별·종료 | "잘 가", "이제 그만할게" |
진행 불가¶
| 값 | 의미 | 예 |
|---|---|---|
silence |
무응답 | 발화 없음 / 빈 입력 |
null |
의미 파악 실패 | 오타·비문·말이 끊긴 경우 |
question 과 request 의 구분¶
정보를 원하면 question, 행위를 원하면 request 다.
| 발화 | act | 봇이 해야 할 일 |
|---|---|---|
| "무슨 차가 좋아?" | question |
답을 말한다 |
| "차 추천해줘" | request |
실제로 추천한다 |
| "다른 차는 없어?" | request_alt |
앞서 말한 것과 다른 것을 제시한다 |
request_alt 는 request 의 특수형이다. 이미 받은 제안을 거부하고 다른 것을 요구하는 경우로 한정한다.
왜 감정 표현(complain)을 넣지 않았나¶
"심심해", "아파", "외로워" 같은 발화는 dialog act 로는 inform 이다. 감정은 행위가 아니라 별개의 차원이므로 sentiment 필드로 다루는 것이 맞다. dialog act 축에 감정을 섞으면 도메인 색채가 들어가 다른 시나리오에서 재사용할 수 없다.
시니어 케어처럼 공감이 핵심인 도메인은 sentiment 를 별도 슬롯/필드로 두고 NLG 제약을 건다.
값이 정해지는 경로¶
NLU LLM 응답의 dialogue_act 필드
└─ 유효한 값이면 그대로 사용
└─ 없거나 알 수 없는 값이면 DialogAct.from_intent(intent) 로 추론
└─ 그래도 매칭 안 되면 inform
NLUResult._coerce_dialogue_act() 가 처리한다. dialogue_act 를 반환하지 않는 NLU 응답도 intent 로부터 값을 얻는다.
intent → act 폴백 매핑¶
DialogAct.from_intent()
| intent 값 | → act |
|---|---|
affirm, confirm, yes, agree |
affirm |
deny, reject, no, disagree |
deny |
negate |
negate |
correct, correction, fix, amend |
correct |
question, ask, info_request, inquiry |
question |
request, action_request, ask_action |
request |
repeat, ask_repeat |
repeat |
help, ask_help |
help |
greeting, greet, hello |
greeting |
thank, thanks, thank_you |
thank |
goodbye, bye, farewell, user_wants_to_end |
goodbye |
silence, no_response |
silence |
unknown, null |
null |
| 그 외 전부 | inform |
LLM이
off_topic을 내도nlu_postprocess가inform으로 바꿉니다. intent fallback도off_topic을null로 보지 않습니다.
어디서 소비되나¶
1. FallbackPolicy — 대화 회복¶
dm/core/dialog/policy/global_policies.py
| act | 동작 |
|---|---|
silence |
no_input 맥락을 전달해 LLM 응답을 생성하고 현재 node를 유지한다 |
null |
not_understood 맥락을 전달해 LLM 응답을 생성하고 현재 node를 유지한다 |
| 그 외 | 개입하지 않는다 |
실제 입력이 비어 있으면 stale entity가 있어도 silence가 우선합니다. 반대로 실제
텍스트와 entity가 함께 있으면 null이어도 알아들은 정보가 있으므로 fallback으로
가로채지 않습니다. responses.no_input과 responses.not_understood는 정상 생성에
그대로 쓰는 고정 문구가 아니라 LLM 실패 시 안전망입니다.
각 종류는 별도 연속 counter를 사용하고 정상 발화에서 초기화합니다. 예산 소진 시
FallbackPolicy가 직접 종료하지 않고 fallback_exhausted를 기록한 뒤 executor와
TransitionEngine에 제어를 넘깁니다.
2. ImplicitAnswerPolicy — 암묵 슬롯 추론¶
dm/core/dialog/policy/task_policies.py
| act | 추론 |
|---|---|
affirm, confirm |
긍정 값으로 슬롯을 채운다 (adequate 등) |
deny, negate |
부정 값으로 슬롯을 채운다 (skipped 등) |
NLU 가 entity 를 못 뽑았을 때 "응"/"아니"만으로 슬롯을 채우는 경로다. 채울 값은 노드의 next_nodes[].condition 에 등장하는 값에서 역추론한다.
3. DialogActRouter — 태스크 노드 분기¶
dm/core/dialog/policy/task_handlers.py
| act | outcome | 노드 응답 템플릿 키 | 슬롯 상태와 무관하게 동작 |
|---|---|---|---|
question |
question |
answer |
예 |
request |
request |
acknowledge_request |
예 |
request_alt |
request_alt |
alternative |
예 |
repeat |
repeat |
(직전 질문 재생) | 예 |
help |
help |
help |
예 |
correct |
correct |
correction |
예 |
greeting |
greeting |
default |
아니오 |
thank |
thank |
acknowledge_thanks |
아니오 |
silence |
silence |
initial |
아니오 |
null |
null |
initial |
아니오 |
inform, affirm, confirm, deny, negate |
(없음) | — | 라우팅하지 않음 |
InformHandler / ConfirmHandler / DenyHandler / NegateHandler 는 outcome 이 빈 문자열이라 None 을 반환한다. 이 발화들은 슬롯 경로로 흘러가야 하기 때문이다.
goodbye 는 라우터에 등록하지 않는다. NLG speech-act rule과 종료 global_transitions가 처리한다.
라우팅 게이트¶
TaskPolicyPipeline 은 두 갈래로 나눈다.
if routed is not None and not context.slot_updates and (
routed.outcome in _USER_DEMAND_OUTCOMES or context.blocking_missing
):
return routed
- 사용자 요구 6종 (
question,request,request_alt,repeat,help,correct) — 슬롯 상태와 무관하게 라우팅한다. 사용자가 요구했는데 슬롯 질문을 이어가면 요청 회피가 된다. - 나머지 (
silence,null,greeting,thank) —blocking_missing(required 슬롯 미충족)이 있을 때만 노드를 붙잡는다. optional 슬롯만 남은 노드를null로 붙잡으면 같은 질문을 반복하게 된다.
4. 종료 Routing¶
EndIntentPolicy는 제거되었다. 종료는 executor가 현재 node의 응답을 만든 뒤 TransitionEngine이 처리한다.
Scenario가 종료 전이를 선언하지 않으면 runtime은 다음 조건을 가진 기본 global transition을 terminal node로 보충한다.
goodbye는 명시적 종료 신호지만 thank는 종료 신호가 아니다. Scenario가 자체 종료 전이를 선언하면 runtime 기본 조건 대신 그 scenario condition이 적용된다.
5. NLG 화법 제약¶
dm/core/llm/prompts/domain_rules.py 의 _DIALOG_ACT_RULES 가 act 별 말하기 제약을 준다. 이 경로는 라우팅 게이트와 무관하게 매 턴 동작하므로, 라우터가 타지 않는 노드에서도 화법은 지켜진다.
| act | 제약 요지 |
|---|---|
question |
질문에 먼저 답한다. 모르면 모른다고 한다. 회피 금지 |
request |
요청부터 구체적으로 응한다. 자기 이야기로 돌리지 않는다 |
request_alt |
앞서 제시한 것과 다른 내용을 낸다 |
correct |
새로 말한 값을 정답으로 받고 무엇이 바뀌었는지 확인한다 |
greeting |
인사로 받아준다. 인사를 건너뛰고 질문하지 않는다 |
thank |
짧게 받고 이어간다. 작별 인사를 하지 않는다 |
goodbye |
새 질문 없이 짧게 마무리한다 |
규칙은 도메인 중립이어야 한다. 시나리오 고유 표현은 시나리오 prompts.bot 에 둔다.
마무리 node에서는 공통 CLOSING_ACK_DIALOG_ACTS(affirm, goodbye, thank, silence)를 사용합니다. 이 집합에 없는 act는 대화를 이어가려는 것으로 보고 system.conversation_resuming=true를 condition context에 노출합니다. NLG와 routing이 같은 판정을 공유하므로 문장과 이동이 어긋나지 않습니다.
6. 로깅 / 현재 턴¶
| 키 | 위치 |
|---|---|
dialogue_act |
TurnContext.nlu / 응답 details |
task_dialogue_act |
TaskResponseBuilder 가 기록 |
일반 실행 경로는 last_dialogue_act를 매 턴 DialogueState.context에 복제하지 않는다. NLUResult.from_context()의 해당 필드는 legacy fallback 경로다.
NLU 프롬프트의 지시¶
dm/core/llm/prompts/prompts_nlu.py
- 허용값 16종을 명시한다.
- act 별 판정 기준을 예시와 함께 제시한다 (
question/request/request_alt/correct/greeting/thank/goodbye). - 빈 발화는
intent = empty,dialogue_act = silence. null은 의미를 정말로 파악할 수 없을 때만 쓴다. 인사말은greeting, 짧은 감상은inform, 거절·회피는deny또는goodbye처럼 의미에 맞는 값을 쓴다.
이 지시가 지켜지지 않으면 FallbackPolicy 가 오작동한다. 인사말이 null 로 오면 인사에 "못 알아들었어요"로 되묻는다.