
OpenAI API와 Local LLM으로 만드는 인게임 퀘스트 자동 생성기
플레이어 상태와 월드 데이터를 바탕으로 퀘스트 초안을 만들고 안전하게 게임 데이터로 바꾸는 구조를 설명합니다. OpenAI API와 로컬 LLM을 선택하는 기준, 프롬프트 설계, 검증 코드를 함께 다룹니다.
퀘스트 생성에 LLM을 쓰는 위치
LLM은 퀘스트 시스템 전체를 대체하는 도구라기보다 정해진 규칙 안에서 퀘스트 초안을 제안하는 도구로 쓰는 편이 안전하다. 보상 수치 계산, 아이템 지급, 퀘스트 완료 판정은 기존 게임 로직이 맡고 모델은 제목, 동기, 목표 조합, NPC 대사처럼 서술과 후보 생성에 집중한다.
예를 들어 플레이어가 숲 지역에서 약초를 수집 중이고 마을 연금술사와 친밀도가 높다면 생성기는 약초 5개 수집이라는 검증 가능한 목표에 연금술사의 맥락을 붙일 수 있다. 반대로 모델이 임의로 존재하지 않는 몬스터나 아이템을 만들게 두면 퀘스트가 실행 불가능해진다.
flowchart LR
A[게임 상태 수집] --> B[허용 데이터로 컨텍스트 구성]
B --> C[OpenAI API 또는 Local LLM]
C --> D[구조화된 퀘스트 초안]
D --> E[서버 규칙 검증]
E -->|통과| F[템플릿으로 퀘스트 등록]
E -->|실패| G[수정 요청 또는 폐기]
핵심은 모델 출력이 게임 데이터베이스에 직접 닿지 않게 만드는 것이다. 생성 결과는 어디까지나 후보이며 게임 서버가 허용 목록과 규칙으로 최종 결정한다.

먼저 고정해야 할 데이터 경계
프롬프트에 월드 전체를 넣으면 비용과 지연 시간이 늘고 모델이 중요하지 않은 정보에 흔들릴 가능성도 커진다. 현재 퀘스트를 만드는 데 필요한 데이터만 작은 문맥으로 전달한다.
{
"player": {
"level": 8,
"region": "pine_forest",
"activeQuestCount": 2,
"knownNpcIds": ["alchemist_erin", "guard_ron"]
},
"availableTargets": [
{ "id": "herb_moonleaf", "type": "item", "name": "달잎초" },
{ "id": "wolf_gray", "type": "enemy", "name": "회색 늑대" }
],
"questRules": {
"maxObjectives": 2,
"allowedObjectiveTypes": ["collect", "defeat", "talk"],
"rewardGoldRange": [20, 60]
}
}
여기서 id는 사람이 읽는 이름보다 중요하다. 게임은 이름이 아니라 안정적인 식별자로 대상을 찾고 완료 여부를 판정해야 한다. 표시 이름은 나중에 현지화 테이블에서 가져올 수 있다.
또한 비밀 정보, 내부 운영 메모, 다른 플레이어의 데이터는 프롬프트에 넣지 않는다. 특히 OpenAI API를 클라이언트에 직접 연결해 API 키를 포함하는 방식은 피해야 한다. Unity 클라이언트는 자체 백엔드에 요청하고 백엔드가 인증과 호출량 제한을 처리하는 구성이 일반적이다.
모델이 작성할 범위를 좁히는 프롬프트
좋은 프롬프트는 문학적인 지시보다 출력 계약을 분명히 한다. 모델이 새로운 ID를 만들지 못하게 하고 목표 종류와 개수, 보상 범위를 제한한다. 응답은 사람이 읽기 좋은 문단보다 JSON 같은 구조화된 형식이 다루기 쉽다.
당신은 판타지 RPG의 퀘스트 기획 보조자다.
반드시 제공된 NPC ID와 대상 ID만 사용한다.
목표는 1개 또는 2개이며 allowedObjectiveTypes 안에서만 선택한다.
보상 골드는 rewardGoldRange를 벗어나면 안 된다.
새로운 아이템, 적, 지역, NPC를 만들지 않는다.
다음 JSON 구조만 반환한다.
{
"title": "string",
"giverNpcId": "string",
"summary": "string",
"objectives": [
{ "type": "collect|defeat|talk", "targetId": "string", "count": 1 }
],
"rewardGold": 0,
"failureReason": null
}
구조화된 출력 기능을 제공하는 API를 쓴다면 JSON 스키마를 함께 지정해 형식을 더 강하게 제한할 수 있다. 다만 형식이 맞는다고 게임 규칙까지 맞는 것은 아니다. targetId가 실제로 존재하는지 count가 허용 범위인지 검증하는 코드는 반드시 별도로 둔다.
Unity 클라이언트와 백엔드의 역할 분리
Unity는 생성 요청과 결과 표시를 담당하고 권한이 필요한 일은 서버가 맡는다. 생성 과정이 길어질 수 있으므로 UI에서는 로딩 상태를 제공하고 실패했을 때 기존 템플릿 퀘스트로 대체할 수 있어야 한다.
[System.Serializable]
public class QuestGenerationRequest
{
public string playerId;
public string regionId;
}
public async Task RequestQuestAsync()
{
var request = new QuestGenerationRequest
{
playerId = CurrentPlayer.Id,
regionId = CurrentPlayer.RegionId
};
// API 키가 없는 자체 백엔드 엔드포인트로 전송한다.
QuestDraft draft = await questApi.CreateDraftAsync(request);
if (draft == null)
{
ShowFallbackQuest();
return;
}
questUi.ShowPreview(draft);
}
백엔드는 플레이어 ID로 서버 저장 데이터를 조회한 뒤 필요한 정보만 모델에 전달한다. OpenAI API를 쓴다면 서버의 환경 변수나 비밀 관리 서비스에서 API 키를 읽는다. Local LLM도 같은 HTTP 인터페이스 뒤에 두면 호출부를 거의 바꾸지 않고 공급자를 교체할 수 있다.
검증은 모델 응답 뒤에 한다
검증기는 모델을 신뢰하지 않는 코드다. 입력 오류, 모델의 형식 이탈, 의도하지 않은 프롬프트 주입에 모두 대비한다. 최소한 다음 조건은 확인하는 편이 좋다.
- 퀘스트 제공자와 목표 대상 ID가 허용 목록에 있는가
- 목표 수와 목표 타입이 규칙에 맞는가
- 수집 또는 처치 수량이 양수이며 상한을 넘지 않는가
- 보상이 레벨과 지역에 맞는 범위에 있는가
- 이미 진행 중인 퀘스트와 같은 목표를 과도하게 중복하지 않는가
public bool ValidateQuest(QuestDraft quest, QuestRules rules, WorldCatalog catalog)
{
if (!catalog.ContainsNpc(quest.GiverNpcId)) return false;
if (quest.Objectives.Count is < 1 or > 2) return false;
if (quest.RewardGold < rules.MinGold || quest.RewardGold > rules.MaxGold) return false;
foreach (var objective in quest.Objectives)
{
if (!rules.AllowedTypes.Contains(objective.Type)) return false;
if (!catalog.ContainsTarget(objective.TargetId)) return false;
if (objective.Count < 1 || objective.Count > rules.MaxTargetCount) return false;
}
return true;
}
검증에 실패한 결과를 자동으로 여러 번 재시도하면 비용과 대기 시간이 늘 수 있다. 먼저 실패 사유를 기록하고 한 번 정도만 더 구체적인 제약을 넣어 재생성한 뒤 계속 실패하면 템플릿 퀘스트를 제공하는 방식이 예측 가능하다.
OpenAI API와 Local LLM 선택 기준
OpenAI API는 별도 추론 서버를 운영하지 않아도 되고 품질이 안정적인 편이라 초기 프로토타입에 적합하다. 반면 네트워크 지연과 호출 비용을 고려해야 하며 서비스 정책과 데이터 처리 요구사항도 검토해야 한다.
Local LLM은 오프라인 플레이나 사내망 환경, 민감한 개발 데이터를 다루는 환경에서 선택지가 될 수 있다. 그러나 GPU 자원, 모델 배포, 동시 요청 처리, 한국어 출력 품질을 직접 점검해야 한다. 작은 모델일수록 자유 서술 대신 선택형 목표와 짧은 요약처럼 제약이 강한 작업에서 더 안정적으로 동작하는 경향이 있다.
두 방식을 함께 쓰는 방법도 있다. 개발 중에는 API로 프롬프트와 데이터 계약을 빠르게 다듬고 출시 환경에서는 요구 지연 시간과 운영 비용에 맞춰 Local LLM 또는 템플릿 혼합 구조를 평가할 수 있다.

품질을 측정하는 간단한 방법
생성기가 재미있는지 확인하려면 인상만으로 판단하지 말고 테스트 케이스를 만든다. 레벨, 지역, 진행 중인 퀘스트, 잠금 해제된 콘텐츠가 서로 다른 플레이어 상태를 준비하고 생성 결과를 저장한다.
다음 항목을 기록하면 개선 지점을 찾기 쉽다.
- 유효성 검사 통과율
- 목표나 보상이 기존 퀘스트와 중복되는 비율
- 요청부터 결과 표시까지의 지연 시간
- 플레이테스트에서 수락된 퀘스트의 비율
- 폴백 템플릿이 사용된 횟수
처음부터 무한한 동적 퀘스트를 목표로 삼기보다 특정 지역의 일일 의뢰처럼 범위가 좁은 기능부터 시작하는 편이 좋다. 데이터 계약과 검증기가 자리를 잡으면 NPC 대사 변형, 퀘스트 제목 현지화, 난이도 조절로 확장할 수 있다.
마무리
LLM 기반 퀘스트 생성기의 완성도는 모델 자체보다 경계 설계에 크게 좌우된다. 모델에는 선택 가능한 재료와 서술 작업을 맡기고 게임 서버는 규칙과 보상을 통제한다. 이 역할 분리가 되어 있으면 OpenAI API와 Local LLM 중 어떤 실행 환경을 선택하더라도 퀘스트 시스템의 안정성을 유지할 수 있다.


