18. 런타임 내부 프롬프트 정리¶
목적¶
시나리오의 prompts.bot, 노드 description, responses를 수정해도 실제 응답에 반영되지 않는 경우가 있었다.
원인은 시나리오 밖의 Python 코드에도 말투, 공감 방식, 질문 형태, 표현 금지 규칙이 들어 있었기 때문이다. 일부 코드는 LLM이 응답을 생성한 뒤 결과 문자열까지 직접 변경했다.
이번 변경의 원칙은 다음과 같다.
런타임은 대화 상태와 라우팅에 필요한 제약만 제공하고, 실제 말투와 표현은 시나리오가 결정한다.
문제였던 응답 생성 구조¶
NLG 호출에는 시나리오 시스템 프롬프트만 전달되는 것이 아니었다.
system message
└─ prompts.bot 또는 metadata.system_prompt
user message
├─ 사용자 입력, 슬롯, 대화 기록
├─ 노드 description과 responses.default
├─ Python domain rules
├─ 반복 및 문장 재작성 지침
└─ 출력 형식 지침
LLM 응답 이후
└─ post policy가 응답 문자열을 다시 변경
시나리오 지침보다 Python 지침이 더 구체적이면 Python 지침이 결과를 지배했다. 후처리 단계에서 문자열을 변경하는 경우에는 LLM과 시나리오가 올바른 응답을 만들었더라도 최종 결과가 달라졌다.
1. 기본 NLG 프롬프트 축소¶
대상: dm/core/llm/prompts/prompts_nlg.py
기존 기본 시스템 프롬프트에는 다음 내용이 고정되어 있었다.
- 상냥하고 따뜻한 말투
- 존댓말
- 사용자 발화에 먼저 공감
- 한 번에 하나씩 질문
- 주제 이탈 시 친절한 안내
- 이모티콘 금지
- 특정 공감 표현 사용 또는 금지
이 내용은 모든 시나리오에 적용할 런타임 규칙이 아니라 시나리오별 페르소나와 화법이다. 기본 프롬프트를 다음 한 문장으로 축소했다.
별도의 prompts.bot 또는 metadata.system_prompt가 있으면 기존과 같이 시나리오 값이 우선한다.
2. 역할별 마무리 지침 통합¶
대상: dm/core/llm/prompts/prompts_nlg.py
기존에는 일반 모드와 환자 응답자 모드에 서로 다른 지침을 다시 붙였다. 역할 정보는 이미 domain_rules와 시나리오에 있으므로 중복 역할 설명을 제거했다.
다만 LLM이 환자: ..., (웃으며) ... 같은 설명을 출력하지 않도록 출력 형식 계약은 유지했다.
이는 말투 지침이 아니라 API 응답 형식에 대한 제약이다.
3. 기준 응답 재작성 지침 제거¶
대상: dm/core/llm/prompts/prompts_nlg.py
기존에는 responses.default 또는 fallback template을 전달하면서 다음과 같은 추가 지침을 붙였다.
- 문장을 그대로 사용하지 말 것
- 뜻과 말투만 참고할 것
- 사용자 발화에 맞춰 새 문장으로 만들 것
- 이미 답한 부분은 다시 묻지 말 것
이 지침 때문에 시나리오에 작성한 문장이 그대로 사용되지 않고 런타임이 항상 재작성을 요구했다.
현재는 해당 문장을 다음과 같이 시나리오 데이터로만 전달한다.
정상 턴에는 responses.default 하나만 전달하고, fallback 턴에는 정책이 선택한 fallback template을 전달하는 기존 선택 로직은 유지했다.
4. anti-repeat 프롬프트 제거¶
대상: dm/core/llm/prompts/prompts_nlg.py
기존에는 최근 봇 발화 세 개를 별도 목록으로 다시 넣고 다음 내용을 강제했다.
- 같거나 비슷한 표현을 다시 쓰지 말 것
- 같은 내용을 다른 표현으로 바꿀 것
- 과거 소재를 다시 요약하거나 평가하지 말 것
이는 특정 표현 패턴을 코드에서 통제하는 숨은 프롬프트였다. 시나리오가 반복을 의도한 경우에도 막을 수 있고, 시나리오에서 반복 정책을 변경해도 코드가 우선할 수 있어 제거했다.
대화 히스토리 자체는 기존과 같이 NLG 입력에 포함된다. 모델과 시나리오는 전체 문맥을 보고 응답할 수 있지만, 런타임이 표현 다양성을 강제하지는 않는다.
5. domain rules에서 화법 제거¶
대상:
dm/core/llm/prompts/domain_rules.pydm/core/llm/prompts/domain_rules_text.py
완전히 제거한 공통 규칙¶
LISTENINGFREE_CONVERSATIONASK_DIRECTLY
위 규칙에는 다음과 같은 화법이 들어 있었다.
- 구체적인 한 조각을 골라 공감
- 특정 공감 문장으로 시작하지 않기
- 물음표 개수 제한
- 대화 지속 여부를 묻는 문장 금지
- 허락을 구하는 질문 형태 금지
- 자유 대화에서 질문 또는 공감을 생략하는 방식
free_conversation 여부에 따라 이 화법 규칙을 교체하던 분기도 함께 제거했다.
기존 상태 규칙에서 제거한 내용¶
- 공감의 순서와 강도
- 친절하고 부드럽게 말하라는 지침
- 짧게 또는 한두 문장으로 말하라는 지침
- 자연스럽게 질문하거나 전환하라는 지침
- 권장 문장과 금지 문장 예시
- 재질문할 때 사용할 표현
유지한 내용¶
다음은 말투가 아니라 대화 상태와 실행 제약이므로 유지했다.
- 응답자/환자 역할
- 안전 확인과 긴급 연락 안내
- 사용자 질문·요청·정정·거부·작별의 우선 처리
- 종료 노드에서 대화를 재개하는 조건
- 무응답 및 이해 실패 상태
- 필수·선택 슬롯의 수집 상태
- 한 번에 처리할 슬롯 범위
- 다음 노드 전환과 이미 지난 주제
- 종료 의도와 턴 제한
즉 domain_rules는 무엇을 어떤 말투로 표현할지 정하지 않고, 현재 턴에서 해야 하거나 하지 말아야 할 행동만 전달한다.
6. task_response_builder 숨은 플래그는 이번 범위에서 제외¶
정상 응답 intent_data의 response_goal, do_not_repeat_current_question 등은 NLG JSON에 붙는 숨은 지시이지만, 프롬프트 정리의 본론이 아니므로 그대로 둔다.
7. RepeatGuard는 이번 범위에서 제외¶
RepeatGuardPolicy의 "다시 정리해서 말씀드리면" 접두어는 반복을 고치지 못하고, 오판 시 같은 문장 앞에만 붙는 문제가 있다. 다만 이번 정리의 핵심은 시나리오 밖 화법 프롬프트를 줄이는 것이므로, 후처리 접두어와 repeat 재생 경로는 그대로 둔다. 별도 이슈로 다룬다.
8. 세션 사용자 정보 지침 축소¶
대상: dm/core/llm/prompts/prompts_nlg.py
기존에는 이름, 나이, 위치, 언어, profile을 자연스럽게 활용하고 반복하지 말라는 개인화 화법이 코드에 있었다. 해당 화법을 제거했다.
세션 사용자 정보 안의 문자열이 프롬프트 명령으로 실행되지 않도록 하는 데이터 경계는 유지했다.
9. 이전 턴에 응답하는 것처럼 보이던 입력 순서 수정¶
대상:
dm/core/llm/prompts/prompts_nlg.pydm/core/llm/prompts/prompts_nlu.pydm/core/llm/prompts/domain_rules_text.py
기존 NLG 프롬프트는 현재 사용자 입력을 먼저 보여준 뒤, 그 아래에 과거 대화 히스토리를 넣었다. 히스토리의 마지막 항목은 직전 봇 응답이므로 프롬프트상 가장 나중에 등장하는 대화 발화가 과거 봇 문장이었다.
이 순서를 실제 대화 시간순으로 변경했다.
또한 bot-first로 이미 출력된 responses.default가 히스토리와 기준 응답에 동시에 들어가지 않도록 했다.
"아니 떠오르는 게 없어", "별일 없어" 같은 사실 부정이 제안 거부로 처리되지 않도록 NLU의 negate와 deny 정의도 분리했다.
deny: 봇의 제안·요청·진행을 거부negate: 질문 대상의 사실이나 존재를 부정하는 답변
domain_rules_text.py에서도 negate가 deny 문구를 공유하지 않고 별도의 사실 부정 상태만 전달한다.
삭제하지 않은 영역¶
NLU 프롬프트¶
prompts_nlu.py는 의도, 엔티티, 슬롯, 라우팅 정보를 JSON으로 추출하기 위한 구조적 프롬프트다. 최종 응답의 말투를 정하는 프롬프트가 아니므로 이번 범위에서 변경하지 않았다.
fallback 처리¶
다음 fallback과 오류 응답은 정상 응답이 실패했을 때의 안전망이므로 유지했다.
- LLM 오류 기본 응답
- 무응답 및 이해 실패 응답
- 선택 슬롯 포기 시 fallback
- 슬롯 완료 응답 검증 실패 시 fallback
- 주제 이탈 시 fallback
안전 응답¶
자해·응급 상황의 고정 안전 응답은 시나리오 말투보다 안전 정책이 우선해야 하므로 유지했다.
명시적인 반복 요청¶
사용자가 실제로 이전 질문을 다시 말해 달라고 요청한 경우 직전 질문을 재출력하는 동작은 유지했다. 제거한 것은 정상 응답에 고정 접두어를 붙이던 후처리다.
변경 후 책임 경계¶
| 영역 | 책임 |
|---|---|
prompts.bot |
페르소나, 말투, 공감 방식, 질문 스타일 |
노드 description |
현재 단계의 목적과 수행 내용 |
노드 responses |
시나리오 기준 응답과 fallback 문구 |
| NLU 프롬프트 | 의도·슬롯·라우팅 정보 추출 |
| domain rules | 상태·전환·종료·안전에 필요한 최소 행동 제약 |
| post policies | 상태 기록. RepeatGuard 접두어는 이번 범위에서 유지 |
예상되는 동작 변화¶
- 시나리오의 말투 변경이 Python 화법 규칙에 덮이지 않는다.
responses.default를 런타임이 강제로 재작성하라고 지시하지 않는다.- 코드가 표현 다양성이나 공감 방식을 강제하지 않는다.
- 반복 방지, 질문 방식, 공감 강도가 필요하면 해당 시나리오의
prompts.bot에 명시해야 한다.
검증¶
변경된 정상 응답 및 규칙 테스트를 포함해 다음 결과를 확인했다.
IDE lint 오류와 git diff --check 오류는 없다.
로컬에서 실제 응답을 확인할 때는 Python 프로세스를 재시작해야 하며, 실행 중이던 기존 세션이 아니라 새 대화를 시작해야 한다.