NPC 페르소나와 프롬프트 메모리로 설계하는 다이나믹 대화 시스템

NPC 페르소나와 프롬프트 메모리로 설계하는 다이나믹 대화 시스템

NPC 페르소나와 프롬프트 메모리를 분리해 일관성 있는 AI 대화를 설계하는 방법을 다룹니다. 기억 우선순위, 토큰 예산, 감정 상태, 검증 규칙까지 게임 기획 관점에서 정리합니다.

TL;DR

다이나믹 NPC 대화의 핵심은 모델에게 많은 설정을 한꺼번에 넣는 일이 아니라 변하지 않는 페르소나상황에 따라 교체되는 메모리를 분리하는 데 있다. 페르소나는 말투·가치관·금기처럼 일관성을 보장하고 메모리는 퀘스트·관계도·최근 사건처럼 현재 대화에 필요한 사실만 우선순위와 토큰 예산에 맞춰 주입한다.

NPC 대화가 쉽게 무너지는 이유는 무엇인가?

LLM 기반 NPC는 대사를 즉석에서 만들 수 있지만 설계가 단순하면 같은 인물이 장면마다 다른 사람처럼 행동한다. 대표적인 원인은 다음 세 가지다.

문제발생 원인플레이 경험에 미치는 영향
캐릭터 붕괴성격, 말투, 세계관 규칙이 프롬프트마다 달라짐NPC가 설정과 다른 선택을 함
기억 과부하모든 대화 로그와 퀘스트 데이터를 매번 주입중요한 사실이 묻히고 응답 비용이 증가
퀘스트 충돌생성된 대사가 게임 상태를 직접 바꿈완료되지 않은 퀘스트를 완료했다고 말하거나 보상을 약속함

따라서 대화 시스템은 ‘자유 생성기’가 아니라 게임 상태를 읽고 허용된 표현으로 응답을 만드는 제약된 내러티브 인터페이스로 설계해야 한다.

NPC의 고정 페르소나와 상황별 메모리가 대화 응답으로 합쳐지는 구조

NPC 페르소나와 프롬프트 메모리는 어떻게 나눌까?

고정 페르소나: 바뀌지 않는 연기 지침

페르소나는 저장형 메모리가 아니라 NPC 정의 데이터다. 대화마다 유지해야 하는 규칙만 포함한다.

필드예시설계 목적
정체성항구 도시의 세관장 ‘마레나’역할과 지식 범위 고정
욕구밀수를 막아 도시의 신뢰를 지킨다행동 동기 제공
성격 축원칙적 0.85, 사교성 0.35반응 경향 제어
말투짧은 문장, 존댓말, 법률 용어 사용문체 일관성 확보
금기증거 없이 시민을 범인으로 지목하지 않음서사·윤리적 안전장치
비공개 정보부두 창고의 장부를 의심함조건부로만 드러날 단서

성격을 서술문으로만 쓰면 조정하기 어렵다. 반복 테스트가 필요한 프로젝트라면 0~1 범위의 수치 축을 함께 두는 편이 좋다. 예를 들어 경계심 0.8 이상인 NPC는 낯선 플레이어에게 개인 정보를 먼저 제공하지 않도록 응답 정책을 만들 수 있다.

프롬프트 메모리: 지금 이 장면에 필요한 사실

메모리는 변하는 정보다. 모든 사실을 영구 보존하지 말고 용도에 따라 나눈다.

메모리 계층예시유지 방식
월드 상태축제 진행일, 전쟁 단계, 날씨게임 상태에서 조회
퀘스트 상태smuggling_case = investigating권위 있는 퀘스트 시스템에서 조회
관계 메모리플레이어가 경비병을 도왔음요약 후 장기 보관
에피소드 메모리직전 대화에서 장부를 언급함최근 N턴 또는 TTL로 만료
검색 메모리과거에 건넨 증거품임베딩 검색 후 상위 K개 선택

여기서 가장 중요한 원칙은 퀘스트 상태를 대화 모델의 기억으로 취급하지 않는 것이다. 퀘스트 완료 여부, 보상 지급, 플래그 변경은 게임의 권위 시스템(authoritative system)이 결정한다. 모델은 이를 읽어 설명할 뿐이다.

프롬프트 메모리 우선순위는 어떻게 계산할까?

주입할 메모리는 관련성만으로 고르면 안 된다. 최신성, 중요도, 현재 퀘스트와의 연결성을 함께 반영해야 한다. 각 메모리 항목의 점수는 다음처럼 정의할 수 있다.

S(m)=0.45R(m,q)+0.30I(m)+0.15F(m)+0.10E(m)S(m) = 0.45R(m,q) + 0.30I(m) + 0.15F(m) + 0.10E(m)
  • R(m,q)R(m,q): 현재 발화 qq와 메모리 mm의 의미적 관련성
  • I(m)I(m): 퀘스트·관계·세계관 측면의 중요도
  • F(m)F(m): 현재 퀘스트 또는 대화 목표와의 일치도
  • E(m)E(m): 최근성. 시간 경과에 따라 감쇠시킨 값

최근성은 지수 감쇠로 관리할 수 있다.

E(m)=eλΔtE(m) = e^{-\lambda \Delta t}

여기서 Δt\Delta t는 마지막 확인 이후의 시간 또는 게임 내 경과 일수이며 λ\lambda는 기억을 얼마나 빨리 잊을지 결정하는 감쇠 계수다. 단, ‘플레이어가 NPC의 동생을 구했다’처럼 장기 관계를 바꾸는 사건은 높은 중요도를 부여해 최근성이 낮아져도 유지한다.

토큰 예산을 먼저 배정한다

컨텍스트 창이 크더라도 대화 프롬프트에 모든 데이터를 넣는 것은 좋은 해결책이 아니다. 예를 들어 입력 예산을 2,400토큰으로 정했다면 다음처럼 배분할 수 있다.

블록권장 예산내용
시스템 규칙300출력 형식, 금지 행동, 사실 우선 원칙
페르소나450정체성, 성격 축, 말투, 금기
권위 상태500퀘스트, 인벤토리, 관계도, 장소
검색 메모리700점수 상위 3~6개 요약
최근 대화350직전 문맥
응답 여유100짧은 대사와 선택지 생성

실제 토큰 수는 사용하는 모델과 토크나이저에 따라 달라진다. 중요한 것은 수치 자체보다 블록별 상한을 두고 낮은 우선순위 메모리부터 잘라내는 정책이다.

다이나믹 대화 시스템은 어떻게 구현할까?

다음 순서는 엔진과 LLM 공급자가 달라도 그대로 적용할 수 있다.

  1. 게임 상태를 읽는다. 플레이어 위치, 활성 퀘스트, NPC 관계도, 소지 증거품을 서버 또는 로컬 세이브에서 조회한다.
  2. 후보 메모리를 검색하고 점수화한다. 대화 주제와 NPC ID로 필터링한 뒤 관련성·중요도·최근성 점수로 정렬한다.
  3. 프롬프트를 조립한다. 고정 페르소나, 권위 상태, 상위 메모리, 최근 대화, 출력 스키마 순으로 넣는다.
  4. 구조화된 응답을 검증한다. 모델 출력은 JSON 스키마로 받고 허용되지 않은 퀘스트 변경이나 아이템 지급 요청은 무시한다.
  5. 승인된 이벤트만 게임에 반영한다. 예를 들어 requestQuestAdvance는 대화 결과가 아니라 퀘스트 조건 검사 결과로 확정한다.
interface Memory {
  id: string;
  summary: string;
  relevance: number;
  importance: number;
  questFit: number;
  recency: number;
}

function selectMemories(memories: Memory[], limit = 4) {
  return memories
    .map((memory) => ({
      ...memory,
      score:
        0.45 * memory.relevance +
        0.30 * memory.importance +
        0.15 * memory.questFit +
        0.10 * memory.recency,
    }))
    .sort((a, b) => b.score - a.score)
    .slice(0, limit);
}

const dialogueContract = {
  reply: "NPC가 플레이어에게 말할 대사",
  emotion: "neutral | wary | grateful | angry",
  offeredTopics: ["플레이어가 선택할 수 있는 후속 화제"],
  requestedEvents: ["게임 시스템이 별도로 검증할 이벤트 요청"],
};

requestedEvents는 신뢰할 수 없는 요청이다. 예를 들어 모델이 give_reward_gold_500을 반환해도 경제 시스템이 보상 조건, 중복 수령 여부, 지급 한도를 확인하기 전에는 적용하면 안 된다.

flowchart TD
    A[플레이어 발화] --> B[게임 상태 조회]
    B --> C[관련 메모리 검색 및 점수화]
    C --> D[페르소나 + 상태 + 메모리 프롬프트 조립]
    D --> E[구조화된 LLM 응답]
    E --> F[스키마 및 게임 규칙 검증]
    F -->|통과| G[NPC 대사 표시]
    F -->|실패| H[안전한 기본 대사 반환]
    G --> I[대화 요약 메모리 저장]

감정 상태와 관계도는 대사에만 쓰지 않는다

관계도는 호감도 하나로 끝내기보다 대화에 직접 영향을 주는 축으로 분리하는 편이 낫다.

관계 축낮을 때높을 때
신뢰사실 확인을 요구비공개 단서를 공유
친밀감사적인 질문을 회피안부와 과거를 언급
공포방어적이거나 회피협박에 순응할 가능성 증가
존중플레이어 결정을 의심조언을 받아들임

감정 상태는 영구적인 관계도와 분리한다. wary는 한 번의 수상한 행동으로 바뀔 수 있는 단기 상태이고 신뢰는 여러 사건을 통해 천천히 변하는 장기 상태다. 이 둘을 같은 값으로 처리하면 사과 한 번으로 관계 전체가 비현실적으로 회복된다.

세관장 NPC가 플레이어의 증거품과 신뢰도에 따라 서로 다른 화제를 제안하는 대화 UI 예시

NPC가 모순된 사실을 말하지 않게 하려면?

우선순위 규칙을 프롬프트와 검증 계층에 모두 명시해야 한다.

  1. 권위 상태의 사실은 검색 메모리나 모델의 추정보다 우선한다.
  2. 확정되지 않은 정보는 ‘의심’, ‘소문’, ‘확인 필요’로만 표현한다.
  3. NPC의 지식 범위를 벗어난 사실은 알고 있는 척하지 않는다.
  4. 출력에 포함된 인물, 장소, 퀘스트 ID는 현재 데이터 테이블의 허용 목록으로 검사한다.

예를 들어 세관장이 범인 ID를 모르는 상태라면 ‘밀수꾼은 레온이다’라는 단정 대신 ‘부두 창고를 드나든 사람이 있다는 보고가 있다’처럼 증거 수준에 맞는 문장을 생성하게 한다. 이 규칙은 환각 방지뿐 아니라 추리 퀘스트의 정보 통제에도 유용하다.

자주 묻는 질문 (FAQ)

NPC마다 전체 대화 로그를 저장해야 하나요?

아니다. 최근 대화 일부와 장기적으로 의미 있는 사건 요약을 분리해 저장하는 편이 효율적이다. 퀘스트 진행도와 보상 상태는 반드시 별도 게임 데이터에서 조회한다.

벡터 검색만으로 메모리를 선택해도 되나요?

부족하다. 의미적 유사도는 관련 정보를 찾는 데 좋지만 퀘스트 중요도나 최신성을 보장하지 않는다. 관련성 점수에 중요도와 상태 일치도를 결합해야 한다.

생성형 대화가 선택지형 대화를 완전히 대체할 수 있나요?

대체하기보다 역할을 나누는 편이 안정적이다. 분위기·반응·힌트 설명은 생성형 대화에 맡기고 퀘스트 수락·보상 선택·분기 확정은 명시적인 UI 선택지와 게임 규칙으로 처리한다.

정리

좋은 다이나믹 NPC 대화는 긴 프롬프트가 아니라 명확한 정보 경계에서 출발한다. 고정 페르소나는 연기의 일관성을 맡고 프롬프트 메모리는 현재 장면의 맥락을 공급하며 퀘스트와 경제 시스템은 생성 모델 밖에서 권위를 유지한다. 이 세 층을 분리하면 대화의 자유로움은 유지하면서도 디버깅, 밸런싱, QA가 가능한 게임 시스템으로 만들 수 있다.

#NPC 대화 시스템#NPC 페르소나#프롬프트 메모리#AI 게임 디자인#LLM 게임 개발#다이나믹 대화

계속 읽어보기

이런 글은 어떠세요?

< Back to Logs