08. 수집 노드 세 개 재설계¶
이 문서는 수집 노드 재설계 당시의 기록입니다. 현재 node params와 optional slot 동작은 SeniMate 시나리오와 Policy를 참고하세요.
daily_life_check · meal_check · health_check. 안부 → 식사 → 건강으로 이어지는 같은 맥락의 노드들이다.
무엇이 문제였나¶
각 노드가 한 턴 만에 넘어갔다.
노드마다 봇 턴이 정확히 하나뿐이고, 그 한 턴이 "직전 답 받아주기 + 다음 주제 묻기"로 쪼개진다. 사용자가 한 말에 반응할 자리가 없어 설문처럼 느껴진다.
여기에 노드별 문제가 겹쳐 있었다.
| 노드 | 문제 |
|---|---|
daily_life_check |
description 이 얇았다가, 고치면서 넣은 예시가 이중 질문을 유발 |
meal_check |
미사용 param 2종, fallback 응답 없음 |
health_check |
description 1562자 누적 패치. 슬롯 4개인데 수집 순서가 불분명 |
설계¶
1. 같은 틀로 통일¶
세 노드가 같은 맥락이므로 description 을 동일한 4단 구조로 다시 썼다.
전환 턴에는 다음 노드 description 의 앞 400자만 전달되므로(_NEXT_NODE_GUIDE_MAX_CHARS), 목적과 핵심 금지를 앞쪽에 배치했다.
| 노드 | 이전 | 이후 |
|---|---|---|
daily_life_check |
458자 | 425자 |
meal_check |
487자 | 447자 |
health_check |
1562자 | 466자 |
health_check 의 1562자는 대부분 이미 다른 계층이 담당하는 내용이었다.
| 기존 description 항목 | 지금 담당하는 곳 |
|---|---|
| "위로해줘 요청 시 질문 덧붙이지 말 것" | request dialog act 규칙 |
| "화제를 무시하고 바꾸지 마세요" | [경청] 공통 규칙 |
| "직전 턴의 공감·제안을 반복하지 마세요" | anti-repeat + prompts.bot 18번 |
| "허락+실제 질문을 복수로 하지 말 것" | handoff 의 "질문은 최대 한 개만" |
대신 수집 순서를 명시했다. 슬롯이 4개인 노드에는 이게 필요하다.
먼저 몸이 어떤지 한 문장으로만 묻는다
→ 괜찮다고 하면 더 캐묻지 말고 짧게 받아준 뒤 이어간다
→ 불편하다고 하면 어디가 어떻게 불편한지 한 가지만 듣고,
그 증상 이름을 그대로 넣어 정도를 묻는다
2. min_turns — 이번 설계의 핵심¶
노드가 한 턴 만에 넘어가지 않도록 최소 체류 턴을 도입했다.
# MinTurnPolicy
if not context.min_turns_satisfied and not context.turn_limit_reached:
return TaskPolicyResult(outcome="continue_conversation", ...)
continue_conversation 턴은 같은 질문을 반복하지도, 다음 주제를 꺼내지도 않는다. 사용자가 방금 한 말을 이어받는다.
[대화 이어가기] 이 노드에서 필요한 답은 이미 들었습니다. **다시 묻지 마세요.**
그리고 **다음 주제로 넘어가지도 마세요.** 사용자가 방금 한 말에서 한 조각을 골라
구체적으로 반응하고, 그 이야기 안에서 자연스럽게 한 걸음만 더 들어가세요.
질문은 하지 않아도 됩니다. 공감이나 짧은 맞장구로 끝내도 좋습니다.
| 노드 | min_turns |
max_turns |
이유 |
|---|---|---|---|
daily_life_check |
2 | 3 | |
meal_check |
2 | 3 | |
health_check |
2 | 4 | 슬롯 4개라 한 턴 더 |
상한이 항상 이긴다. 두 값이 충돌하면 노드를 떠난다.
min_turns는 이전에 되돌린 항목이다. 이번에는 근본 원인(off_topic 오판정, 전환 지침 누락)을 먼저 고친 뒤 적용했고, 단독 테스트로 동작을 확인했다. → 06
3. 죽은 스키마 제거¶
코드에서 참조 0건이던 param 4종을 뺐다.
| 노드 | 제거한 param | 상태 |
|---|---|---|
meal_check |
allowed_slot_values, nutrition_advice |
런타임 참조 없음 |
health_check |
health_advice, empathy_levels |
런타임 참조 없음 |
completion_slots 는 dst_nlu 가 NLU 힌트로, dst_logging 이 기대 슬롯 계산에 실제로 읽으므로 유지했다.
4. responses 를 세트로 맞췄다¶
당시 response schema:
| 노드 | 공통 키 | 추가 키 |
|---|---|---|
daily_life_check |
default, follow_up, empathy, no_input, not_understood |
— |
meal_check |
default, follow_up, empathy, no_input, not_understood |
— |
health_check |
default, follow_up, empathy, no_input, not_understood |
medical_advice |
fallback 문구는 노드 성격에 맞췄다. responses 는 이제 "그대로 쓸 문장"이 아니라 전달할 뜻의 기준이므로(07), 그 노드가 지금 알고 싶어 하는 것을 더 짧고 쉬운 말로 담는다.
daily_life_check 요즘 어떻게 지내시는지만 편하게 말씀해 주세요.
meal_check 오늘 식사하셨는지만 편하게 말씀해 주세요.
health_check 몸은 좀 어떠신지만 편하게 말씀해 주세요.
검증¶
python -m pytest # 156 passed
python -m cli.chatbot --scenario dm/scenario/senimate.json --validate-only
실제 LLM(gpt-4o / gpt-4o-mini) 전체 흐름:
'오늘 심심해' daily_life_check continue_conversation daily_life='심심해'
→ 심심하셨군요. 가끔은 그런 날도 있죠. 혹시 오늘 식사는 잘 챙겨 드셨나요?
'TV만 봐' daily_life_check slots_complete daily_life='TV만 봐'
→ TV를 보며 시간을 보내셨군요...
'응 먹었지' meal_check continue_conversation meal_status='adequate'
'김치찌개 먹었어' meal_check slots_complete meal_status='adequate'
→ 김치찌개를 드셨군요. 맛있게 드셨다니 좋네요...
'허리가 좀 아파' health_check continue_conversation health_status='complaint', symptom='허리 통증'
→ 허리가 아프시군요. 혹시 허리 통증이 어느 정도인지 말씀해주실 수 있을까요?
'이틀 됐어' health_check slots_complete
확인된 것:
- 각 노드가 2턴씩 쓴다.
continue_conversation→slots_complete - 슬롯이 제자리에 들어간다.
{daily_life, meal_status, health_status, symptom} health_check가 증상 이름을 그대로 넣어 되물었다 ("허리 통증이 어느 정도인지") — 설계 의도대로
회귀 테스트¶
dm/tests/test_task_policy_collect_slots.py 5건 추가.
| 테스트 | 고정하는 동작 |
|---|---|
test_min_turns_holds_the_node_even_when_slots_are_filled |
슬롯이 차도 최소 턴 전에는 머문다 |
test_node_advances_once_min_turns_is_met |
채우면 넘어간다 |
test_max_turns_beats_min_turns |
상한이 하한을 이긴다 |
test_without_min_turns_the_node_advances_immediately |
지정 안 하면 종전 동작 |
test_invalid_min_turns_falls_back_to_default |
잘못된 값은 기본값으로 |
테스트 스텁의 결함도 함께 고쳤다¶
DummyExecutor.get_max_turns() 가 노드 params 를 무시하고 항상 기본값을 반환하고 있었다. 실제 executor 와 달라서 max_turns 가 걸리는 경로를 검증할 수 없었다. 실제 동작에 맞췄다.
남은 것¶
prompts.bot정리가 필요하다. 17번이 명시적으로 금지하는 허락 질문("여쭤봐도 될까요?")이 계속 나온다. 18개 항목이 서로 겹치고 길어서 개별 항목이 묻힌다.health_check가pain_level을 못 채운다."이틀 됐어"는 기간이지 정도가 아닌데slots_complete로 넘어갔다. 전 슬롯이optional이라 진행을 막지 않는다.pain_level을required로 올릴지는 제품 판단이 필요하다.min_turns값이 경험적이다. 2턴이 적절한지는 실사용 관찰이 필요하다. 노드마다 다르게 둘 수도 있다.