Persona Chat Engine: 프로젝트 설계
이 프로젝트는 ai-server/, hf-serve/, train/의 주요 디렉토리 3개로 나누어 구성하였습니다.
- ai-server에서는 game-server와의 통신을 통해 전/후처리 및 RAG와 내장 경량 fallback model을 통하여 지능적인 npc 답변 및 게임 상태 update를 담당하고
- hf-serve에서는 실제 LoRA방식으로 fine tuning한 main model을 serving하여 ai-server의 전처리를 통과한 경우에 main model추론을 진행하게 설계하였고
- train에서는 main model의 학습을 진행한 Colab notebook으로 구성하였습니다.
⚙️ AI 서버 (ai_server/)
역할 & 데이터 흐름
-
게임 서버 요청 수신(
app.py)- 최소 입력만 와도 동작:
{ text, npc_id, player_id, ... } - 옵션: 상태/컨텍스트 부족 시
rag/docs/npc_config.json등에서 NPC 메타를 조회해 보강
- 최소 입력만 와도 동작:
-
전처리·프롬프트 구성(
pipeline/preprocess.py,utils/context_parser.py,manager/prompt_builder.py)- 태그/컨텍스트/플레이어 발화를 묶어
<SYS>,<CTX>,<PLAYER>,<NPC>포맷으로 구성
- 태그/컨텍스트/플레이어 발화를 묶어
-
추론 요청(
utils/hf_client.py,models/fallback_model.py,pipeline/generator.py)- 조건 불충족 input →
fallback_model.py에서 대체 응답 생성 - 조건 충족 input →
generator.py에서 payload 구성 후 HF Space/predict_mainPOST
- 조건 불충족 input →
-
후처리(
pipeline/postprocess.py)- 모델 응답에서 대사 텍스트, delta, flag를 파싱·정규화
- flags → 시그모이드+threshold, delta → 범위 클램프·라운딩
-
게임 서버 응답(
schemas.py)- 표준 JSON으로 반환
{ "text": "NPC의 대답...", "delta": {"trust": 0.10, "relationship": 0.08}, "flags": {"give_item": true, "npc_main_story": false}, "meta": {"npc_id": "mother_abandoned_factory"} }
📁 디렉토리 구조
ai-server/
├── app.py # FastAPI 엔트리포인트
├── config.py # 서버 설정 및 모델 경로 관리
├── schemas.py # 요청/응답 데이터 구조 정의
├── requirements.txt # 의존성 패키지 목록
├── pipeline/ # 대화 흐름 처리 모듈
│ ├── preprocess.py # 입력 전처리 및 프롬프트 구성
│ ├── postprocess.py # 모델 출력 후처리
│ └── generator.py # 모델 추론 요청 처리
├── rag/ # RAG 기반 flag 해석 모듈
│ ├── rag_manager.py
│ └── docs/npc_config.json
├── utils/ # 유틸리티 모듈
│ ├── hf_client.py # HF API 통신
│ └── context_parser.py # 대화 맥락 파싱
├── models/ # 모델 로딩 및 fallback 처리
│ ├── emotion_model.py # emotion model을 이용한 inference 진행
│ ├── fallback_model.py # fallback model을 이용한 inference 진행
│ └── model_loader.py
└── manager/
├── agent_manager.py
├── dialogue_manager.py # 전체 pipeline 모듈 관리
└── prompt_builder.py
🧩 RAG 타입별 매핑 (11종)
| type | 조회 시점 | 조회 조건 | 사용 모듈/함수 | 주요 목적 |
|---|---|---|---|---|
trigger_def | preprocess_input | npc_id, quest_stage | retrieve(...) | 메인 경로 조건 판정 |
fallback | preprocess_input | npc_id, quest_stage | retrieve(...) | fallback prompt 구성 |
forbidden_trigger_list | preprocess_input | npc_id | _load_forbidden_trigger_data | 금지 트리거 감지 |
trigger_meta | preprocess_input | npc_id, trigger | _load_trigger_meta | 특수 fallback 시 delta/action 확정 |
lore | build_main_prompt | npc_id, quest_stage/any | RAG main docs | 세계관/배경 설명 |
description | build_main_prompt | npc_id, quest_stage | RAG main docs | 현재 상황 설명 |
flag_def | postprocess_pipeline | npc_id, quest_stage, flag_name | pre_data["rag_main_docs"] | flag threshold/예시 문장 |
main_res_validate | postprocess_pipeline | npc_id, quest_stage | pre_data["rag_main_docs"] | 응답 검증 기준 |
npc_persona | build_main_prompt | npc_id | retrieve(...) | NPC 성격·특성 반영 |
dialogue_turn | postprocess_pipeline | npc_id, quest_stage | retrieve(...) | 대화 예시 참조 |
| (없음) | fallback_final_check | pre_data["trigger_meta"] | - | 응답 의미 일치 검증 |
📌 데이터 흐름 요약
-
preprocess_input()
- trigger_def → 메인 조건 판정
- forbidden_trigger_list + trigger_meta → 특수 fallback 감지
- fallback → 일반 fallback 스타일
-
build_main_prompt()
- lore + description + npc_persona → 메인 prompt 컨텍스트 구성
-
build_fallback_prompt()
- fallback_style + trigger_meta → fallback prompt 구성
-
postprocess_pipeline()
- flag_def → flag threshold/예시
- main_res_validate → 응답 검증
-
fallback_final_check()
- trigger_meta → delta/action 의미 일치 검증
⚙️ 추론 서버 (hf-serve/)
플레이어 발화와 게임 상태를 기반으로 NPC의 응답, 감정 변화량(delta), 플래그 확률/임계값을 예측합니다.
🚀 주요 기능
- API 엔드포인트
/predict_main- JSON payload로 prompt를 받아 모델 추론 결과 반환
- 커스텀 헤드 예측
delta_head: trust / relationship 변화량flag_head: 각 flag의 확률flag_threshold_head: 각 flag의 임계값
- 모델 실시간 업데이트
- Colab 학습 후
latest브랜치 업로드 →/ping_reload호출 시 즉시 재로드
- Colab 학습 후
📂 디렉토리 구조
hf-serve/
├─ app.py # Gradio UI + API 라우팅
├─ inference.py # 모델 추론 로직
├─ model_loader.py # 모델/토크나이저 로드
├─ utils_prompt.py # prompt 생성 함수
├─ flags.json # flag index → name 매핑
├─ requirements.txt # 의존성 패키지
└─ README.md # (현재 문서)
⚙️ 추론 로직 개요
이 서버의 핵심은 run_inference() 함수로,
NPC 메인 모델에 프롬프트를 입력하고 응답·상태 변화를 예측하는 전 과정을 담당합니다.
처리 흐름
-
프롬프트 토크나이즈
- 입력된 prompt를 토크나이저로 변환하여 텐서 형태로 준비
- 길이 제한(
MAX_LENGTH)과 디바이스(DEVICE) 설정 적용
-
언어모델 응답 생성
- 사전 정의된 추론 파라미터(
GEN_PARAMS)로model.generate()실행
→ NPC의 대사 텍스트 생성 - 생성된 토큰을 디코딩하여 최종 문자열로 변환
- 사전 정의된 추론 파라미터(
-
히든 상태 추출
output_hidden_states=True로 모델 실행- 마지막 레이어의 hidden state를 가져옴
-
<STATE> 토큰 위치 풀링
<STATE>토큰이 있는 위치의 hidden state를 평균(pooling)
→ NPC 상태를 대표하는 벡터로 사용- 없을 경우 마지막 토큰의 hidden state 사용
-
커스텀 헤드 예측
delta_head: trust / relationship 변화량 예측flag_head: 각 flag의 발생 확률 예측flag_threshold_head: 각 flag의 임계값 예측
-
index → name 매핑
flags.json의 순서(flags_order)를 기반으로
예측 벡터를{flag_name: 값}형태의 딕셔너리로 변환
반환 형식
{
"npc_output_text": "<NPC 응답>",
"deltas": { "trust": 0.xx, "relationship": 0.xx },
"flags_prob": { "flag_name": 확률, ... },
"flags_thr": { "flag_name": 임계값, ... }
}
📜 Prompt 포맷
모델은 학습 시 아래와 같은 구조의 prompt를 사용합니다.
<SYS>
NPC_ID={npc_id}
NPC_LOCATION={npc_location}
TAGS:
quest_stage={quest_stage}
relationship={relationship}
trust={trust}
npc_mood={npc_mood}
player_reputation={player_reputation}
style={style}
</SYS>
<RAG>
LORE: ...
DESCRIPTION: ...
</RAG>
<PLAYER_STATE>
...
</PLAYER_STATE>
<CTX>
...
</CTX>
<PLAYER>...
<STATE>
<NPC>
💡 일반적인 LLM 추론과의 차이점
이 서버는 단순히 텍스트를 생성하는 것에 그치지 않고,
<STATE> 토큰 기반 상태 벡터를 추출하여 커스텀 헤드에서 **감정 변화량(delta)**과
플래그 확률/임계값을 동시에 예측합니다.
이를 통해 대사 생성과 게임 상태 업데이트를 한 번의 추론으로 처리할 수 있습니다.