13. min_turns 가 안 찬 턴이 다음 노드의 질문을 던지던 문제¶
meal_check 는 min_turns=2 인데 슬롯이 한 턴 만에 차면 바로 다음 노드의 질문을 던지고 있었다. 노드에 머무는 의미가 없었다.
증상¶
[meal_check] B: 식사를 잘 챙겨 드셨다니 다행이에요. 몸은 좀 어떠세요? ← health_check 의 질문
U: 머리가 좀 아퍼
[meal_check] B: 머리가 아프시군요. … 혹시 몸 상태는 어떠신가요? ← 또 health_check 의 질문
min_turns=2 라서 meal_check 에 한 턴 더 머물러야 하는데, 머무는 동안 식사 얘기를 하지 않았다.
원인 — 규칙끼리 모순이었다¶
TaskPolicyPipeline 은 제대로 동작하고 있었다.
if not context.min_turns_satisfied and not context.turn_limit_reached:
... outcome="continue_conversation"
문제는 그 턴에 생성되는 프롬프트였다. 실제로 찍어보니:
[대화 이어가기] … **다음 주제로 넘어가지도 마세요.** …
모든 필요한 정보가 수집되었습니다. … 다음 주제인 '건강챙기기'에 대해 자연스럽게 질문하세요.
다음 단계 노드의 지침입니다. **지금 만드는 전환 문장부터** 이 지침을 지키세요. …
**이 턴에서 물을 것은 다음 하나뿐입니다:** "몸은 좀 어떠세요?" …
이 턴은 주제를 넘기는 턴입니다. **질문은 다음 주제에 대해 한 개만** 하세요. …
"넘어가지 마세요" 뒤에 넘어가라는 지시가 네 개 붙어 있었다. 뒤쪽이 훨씬 구체적이라 그쪽이 이긴다.
원인은 _add_slot_progress 였다. continue_conversation 을 보지 않고 all_slots_filled 만으로 전환 턴을 선언한다.
if turn.all_slots_filled:
rules.add_progress(_slots_filled_rule(turn)) # "다음 주제인 X를 물으세요"
if not rules.suppress_progress_notes:
rules.is_handoff_turn = True # → 6단계 전환 규칙 전부 켜짐
12단계 에서 추가한 HANDOFF_OPENING_QUESTION 이 여기에 함께 실리면서 증상이 더 선명해졌다. 다음 노드의 열 질문을 문자 그대로 지목해 주고 있었으니 그대로 나온 것이다.
고친 것¶
1. stay_in_node 플래그¶
suppress_progress_notes 와 같은 자리의 스위치를 하나 더 뒀다.
_Rules 필드 |
타입 | 기본값 | 의미 |
|---|---|---|---|
is_handoff_turn |
bool |
false |
다음 노드로 넘기는 턴인지 |
stay_in_node |
bool |
false |
min_turns 미충족으로 슬롯 완료 후에도 현재 노드에 머무는지 |
| 단계 | stay_in_node 일 때 |
|---|---|
| 4. 턴 상황 | 그대로 (CONTINUE_CONVERSATION 이 여기서 켠다) |
| 5. 슬롯 진행 | 수집 완료 안내를 붙이지 않는다 |
| 6. 노드 전환 | 통째로 건너뛴다 |
| 7. 턴 예산 | 그대로 |
if turn.all_slots_filled and not rules.stay_in_node:
...
def _add_handoff(rules, turn):
if not rules.is_handoff_turn or rules.suppress_progress_notes or rules.stay_in_node:
return
2. 머물 주제를 이름으로 못박는다¶
"다음 주제로 넘어가지 마세요"만으로는 무엇을 물어야 하는지를 알려주지 않는다. 이 턴에 할 일을 명시했다.
CONTINUE_CONVERSATION = (
"\n[대화 이어가기] 이 노드에서 필요한 답은 이미 들었지만 **아직 이 주제에 더 머물 차례입니다.** "
"**다음 주제로 넘어가지 마세요.** 다음 노드가 물을 것을 미리 꺼내면 안 됩니다.\n"
"이미 받은 답을 그대로 다시 묻지 말고, **앞선 대화를 돌아보고** 사용자가 한 말 중 한 조각을 "
"골라 구체적으로 반응하세요. 그리고 **같은 주제 안에서 아직 묻지 않은 것**을 한 가지만 "
"더 물어보세요. 받은 답에서 한 걸음 더 들어가는 질문이어야 합니다. "
"더 들어갈 것이 마땅치 않으면 질문 없이 공감이나 짧은 맞장구로 끝내도 좋습니다."
)
# 이름을 주지 않으면 "이 주제"가 무엇인지 모델이 다음 노드 쪽으로 넓게 해석한다.
CONTINUE_CONVERSATION_TOPIC = (
"\n지금 머물러야 할 주제는 '{topic}' 입니다. 이 주제와 관련된 것만 물으세요."
)
이전 문구는 "질문은 하지 않아도 됩니다" 로 끝나 이 턴을 사실상 비워 두고 있었다. 비어 있으니 뒤에 붙은 전환 지시가 그 자리를 채웠다.
결과¶
이전 [meal_check] U: 머리가 좀 아퍼
B: 머리가 아프시군요. … 혹시 몸 상태는 어떠신가요? ← 다음 노드 질문
지금 [meal_check] U: 머리가 좀 아퍼
B: 머리가 아프시군요. 식사는 잘 챙겨 드셨다고 하셨는데,
혹시 드신 음식이 어떤 거였는지 궁금해요. ← 같은 주제 심화
[health_check] U: 괜찮아요 좀 쉬면 나아요
B: 머리가 아프시다고 하셨는데, 몸 상태는 좀 어떠세요? ← 이제서야 전환
daily_life_check 도 같다 — "집에 계시는군요. 집에서 주로 어떤 일로 시간을 보내세요?". 한 곳을 고쳐 세 노드가 모두 달라졌다. 노드별 문구가 아니라 공통 규칙이 원인이었기 때문이다.
12단계의 이중 질문 개선도 회귀하지 않았다.
테스트¶
test_continue_conversation_turn_carries_no_handoff_rules
# 수집 완료 안내 + 전환 규칙 4종이 모두 없는지. "몸은 좀 어떠세요?" 문자열까지 확인
test_continue_conversation_turn_names_the_topic_to_stay_on
test_slots_filled_without_continue_still_hands_off
# 억제가 과하지 않은지 — min_turns 가 찼으면 평소대로 전환
test_every_rule_starts_on_its_own_line 가 새 문구의 개행 누락을 바로 잡아냈다. 규약 테스트가 값을 했다.
원칙¶
프롬프트에 서로 반대되는 지시가 함께 실릴 수 있는지를 먼저 확인할 것. 모델은 모순을 알려주지 않고, 대개 더 구체적인 쪽을 따른다.
그리고 "하지 마세요"만 있는 턴은 비어 있는 턴이다. 대신 무엇을 할지를 함께 줘야 한다.
11·12단계와 같은 계열이다 — 문구를 덧붙이기 전에 어떤 정보가 실리는지 먼저 볼 것.