SeniMate Node Graph¶
dm/scenario/senimate_weather.json 기준의 node 흐름입니다.
기본 흐름¶
기본 경로는 default_next로 연결된 선형 구조입니다.
greeting_introduction
→ daily_life_check
→ meal_check
→ health_check
→ general_conversation
→ conversation_closure
→ complete
Node 요약¶
| Node | 역할 | 수집 슬롯 | 기본 다음 node |
|---|---|---|---|
greeting_introduction |
위치 기반 날씨, 자기소개와 대화 가능 여부 확인 | — | daily_life_check |
daily_life_check |
생활·기분·활동 안부 확인 | daily_life |
meal_check |
meal_check |
최근 식사 상태 확인 | meal_status |
health_check |
health_check |
몸 상태와 필요한 증상 확인 | health_status, pain_level, health_issue, symptom |
general_conversation |
general_conversation |
사용자가 꺼낸 소재의 자유 대화 | — | 조건에 따라 stay 또는 closure |
conversation_closure |
새 질문 없이 대화 마무리 | — | terminal |
전체 runtime routing¶
정적 graph edge 외에 global transition과 action: stay를 포함한 전체 흐름입니다.
flowchart TD
INPUT["현재 사용자 턴"] --> ACTION_END{"Executor Action END?"}
ACTION_END -- "예" --> COMPLETE([Session complete])
ACTION_END -- "아니오" --> END_INTENT{"intent =<br/>user_wants_to_end?"}
END_INTENT -- "예" --> CLOSURE["conversation_closure"]
END_INTENT -- "아니오" --> SILENT{"dialogue_act = silence<br/>AND no_input_count > 2?"}
SILENT -- "예" --> CLOSURE
SILENT -- "아니오" --> CURRENT{"현재 node"}
CURRENT --> GREETING["greeting_introduction"]
CURRENT --> DAILY["daily_life_check"]
CURRENT --> MEAL["meal_check"]
CURRENT --> HEALTH["health_check"]
CURRENT --> GENERAL["general_conversation"]
CURRENT --> CLOSURE
GREETING --> G_TURN{"node_turns = 1?"}
G_TURN -- "예" --> GREETING
G_TURN -- "아니오" --> DAILY
DAILY --> DAILY_MIN{"min_turns 충족?"}
DAILY_MIN -- "아니오" --> DAILY
DAILY_MIN -- "예" --> MEAL
MEAL --> MEAL_MIN{"min_turns 충족?"}
MEAL_MIN -- "아니오" --> MEAL
MEAL_MIN -- "예" --> HEALTH
HEALTH --> HEALTH_MIN{"min_turns 충족?"}
HEALTH_MIN -- "아니오" --> HEALTH
HEALTH_MIN -- "예" --> HEALTH_MISSING{"complaint AND<br/>symptom empty AND<br/>health_issue empty?"}
HEALTH_MISSING -- "예" --> HEALTH
HEALTH_MISSING -- "아니오" --> GENERAL
GENERAL --> GENERAL_END{"종료 intent?"}
GENERAL_END -- "예" --> CLOSURE
GENERAL_END -- "아니오" --> GENERAL
CLOSURE --> ACK{"affirm / goodbye /<br/>thank / silence?"}
ACK -- "예" --> COMPLETE([Session complete])
ACK -- "아니오" --> RESUME{"미수집 slot"}
RESUME -->|"daily_life empty"| DAILY
RESUME -->|"meal_status empty"| MEAL
RESUME -->|"health_status empty"| HEALTH
RESUME -->|"그 외"| GENERAL
Global transitions¶
Global transition은 모든 node에서 node-local routing보다 먼저 평가합니다.
flowchart TD
T1["1. user_wants_to_end"] -->|"intent 일치"| C["conversation_closure"]
T1 -->|"불일치"| T2["2. silent_close"]
T2 -->|"연속 무응답 3회"| C
T2 -->|"불일치"| LOCAL["node-local routing"]
배열의 선언 순서가 우선순위입니다. Closure 재개는 global transition이 아니라 closure node의 next_nodes가 담당합니다.
Node-local routing¶
greeting_introduction¶
flowchart LR
G["greeting_introduction"] --> T{"system.node_turns = 1?"}
T -- "예" --> G
T -- "아니오" --> D["daily_life_check"]
첫 응답에서는 weather Skill이 세션 위치의 날씨를 조회합니다. 성공하면 결과를 참고해
날씨·소개·대화 가능 여부를 NLG로 말하고, 실패하면 params.fallback으로 날씨 조회
실패를 짧게 알립니다. 다음 일상 질문은 미리 하지 않습니다.
daily_life_check¶
flowchart LR
D["daily_life_check"] -->|"min_turns 미충족"| D
D -->|"진행 가능"| M["meal_check"]
daily_life는 optional이므로 미수집 자체가 진행을 막지는 않습니다.
meal_check¶
flowchart LR
M["meal_check"] -->|"min_turns 미충족"| M
M -->|"진행 가능"| H["health_check"]
meal_status도 optional이며 adequate 또는 skipped 값을 사용합니다.
health_check¶
flowchart TD
H["health_check"] --> MIN{"min_turns 충족?"}
MIN -- "아니오" --> H
MIN -- "예" --> NEED{"health_status = complaint<br/>AND symptom empty<br/>AND health_issue empty?"}
NEED -- "예" --> H
NEED -- "아니오" --> G["general_conversation"]
불편하다고 말한 경우 symptom 또는 health_issue 중 하나도 듣지 못했다면 한 번 더 건강 node에 머뭅니다.
general_conversation¶
flowchart TD
G["general_conversation"] --> END{"intent = user_wants_to_end?"}
END -- "예" --> C["conversation_closure"]
END -- "아니오" --> G
이 node는 max_turns: 6입니다. turn_limit_enabled를 끄지 않았으므로 턴 상한 응답 경로가 열릴 수 있지만, next_nodes는 종료 intent가 아니면 action: stay입니다. 대화 횟수만으로 closure로 가지 않습니다. 화법은 prompts.bot과 노드 description이 담당합니다.
사용자가 명시적으로 종료하지 않으면 자유 대화를 유지합니다.
conversation_closure¶
flowchart TD
C["conversation_closure<br/>terminal: true"] --> LIMIT{"max_turns 도달?"}
LIMIT -- "예" --> ACTION_END["session_ended=true<br/>action_end"]
ACTION_END --> COMPLETE([Session complete])
LIMIT -- "아니오" --> RESUME{"system.conversation_resuming?"}
RESUME -- "false" --> COMPLETE
RESUME -- "true" --> SLOT{"미수집 slot"}
SLOT -->|"daily_life"| D["daily_life_check"]
SLOT -->|"meal_status"| M["meal_check"]
SLOT -->|"health_status"| H["health_check"]
SLOT -->|"없음"| G["general_conversation"]
system.conversation_resuming은 현재 node가 end node이고 dialog act가 affirm, goodbye, thank, silence 중 하나가 아닐 때 true입니다.
Closure는 terminal: true를 명시하므로 successor가 있어도 terminal node입니다. 재개 규칙을 node 내부에 둘 수 있고, 조건이 일치하지 않으면 TransitionEngine의 terminal 판정이 세션을 닫습니다.
Closure의 max_turns=2에 먼저 도달하면 GeneralResponseExecutor가 session_ended=true를 반환하고 Action END가 최우선으로 종료합니다. 이 경로에서는 node-local 재개 조건을 평가하지 않습니다.
general_conversation에는 node turn limit 경로가 없습니다. 사용자의 명시적인 user_wants_to_end만 closure 이동을 결정합니다.
정적 graph와 runtime graph의 차이¶
NetworkX 정적 edge:
greeting → daily → meal → health → general → closure
closure → daily
closure → meal
closure → health
closure → general
정적 edge가 아닌 runtime routing:
greeting_introduction의action: stayhealth_check의 자기 자신을 향한 조건부 targetgeneral_conversation의action: stay- 모든 global transition
- executor와 policy의 transition request
- terminal complete 처리
Closure의 단계별 재개는 이제 next_nodes[].target이므로 정적 edge와 runtime condition 양쪽에 존재합니다. Edge가 있다고 무조건 이동하는 것은 아니며 system.conversation_resuming과 slot 조건을 먼저 평가합니다.
따라서 graph 시각화만 확인하지 말고 detailed 출력의 transition reason도 함께 확인해야 합니다.