콘텐츠로 이동

Dialog Act 정의

현재 런타임이 사용하는 dialog act 목록이다. 정의 위치는 dm/core/dialog/policy/models.pyDialogAct 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 의미 파악 실패 오타·비문·말이 끊긴 경우

questionrequest 의 구분

정보를 원하면 question, 행위를 원하면 request 다.

발화 act 봇이 해야 할 일
"무슨 차가 좋아?" question 답을 말한다
"차 추천해줘" request 실제로 추천한다
"다른 차는 없어?" request_alt 앞서 말한 것과 다른 것을 제시한다

request_altrequest 의 특수형이다. 이미 받은 제안을 거부하고 다른 것을 요구하는 경우로 한정한다.

왜 감정 표현(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_postprocessinform으로 바꿉니다. intent fallback도 off_topicnull로 보지 않습니다.

어디서 소비되나

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_inputresponses.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 / NegateHandleroutcome 이 빈 문자열이라 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로 보충한다.

nlu.intent == user_wants_to_end
OR nlu.dialogue_act == goodbye
OR nlu.end_signal == true

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 로 오면 인사에 "못 알아들었어요"로 되묻는다.

관련 문서