더 단순한 AI 결정이 더 잘 작동했던 이유: Jev 기반 게임 심사관에서 제가 바꾼 것
요약
작성자는 Jev 기반의 측면 사고 퍼즐 게임 'Sideways'를 개발하며, AI 결정 로직을 설계하는 과정에서 중요한 교훈을 얻었습니다. 초기에는 AI가 의미론적 판단을 내리고 코드가 이를 제어하는 복잡한 아키텍처를 시도했으나, 결국 결정을 단순화하는 것이 더 효과적이었습니다.
핵심 포인트
- AI의 '지능'보다 결정 로직의 단순성이 중요함.
- 복잡한 의미론적 구조는 오히려 게임 경험을 저해할 수 있음.
- AI가 판단하고 TypeScript 같은 코드가 결과를 결정하는 분리된 아키텍처를 사용함.
- 결정 과정에 확률 기반 점수와 명확한 규칙(TypeScript)을 결합하여 구현함.
저는 Sideways라는 작은 측면 사고 퍼즐 게임을 만들고 있습니다.
기본 상호작용은 간단합니다.
플레이어는 신비한 상황을 보고 질문을 던지다가 무엇이 실제로 일어났는지 발견할 때까지 질문합니다.
예를 들어:
Player:
빛인가요?
...
또는:
Player:
깨어나는 장치가 소리를 내나요?
...
게임 마스터는 Jev로 구동됩니다.
저는 일반적인 목적의 챗봇이 설명을 생성하는 것을 의도적으로 피하고 싶었습니다.
제가 원했던 것은 이와 훨씬 가까운 것이었습니다:
자연어 입력
↓
확률적 의미론적 결정
...
여러 반복 끝에, 저는 이것이 거의 자연어에 대한 확률적 IF 문을 구축하는 것과 같다는 것을 깨달았습니다.
그리고 예상치 못한 것을 배웠습니다:
그러한 의미론적 IF 문을 더 정교하게 만드는 것이 실제로 게임을 망쳤습니다.
해결책은 AI를 더 똑똑하게 만드는 것이 아니었습니다.
해결책은 결정을 더 작게 만드는 것이었습니다.
원래 아이디어: AI가 의미를 이해하고, 코드가 행동을 제어한다
아키텍처는 간단한 규칙을 따릅니다:
AI = 의미론적 판단
코드 = 결정론적 정책
사용자 = 결과적인 의도
모델은 어떤 UI를 보여줄지 직접적으로 결정하지 않습니다.
브라우저는 숨겨진 퍼즐 솔루션을 받지 않습니다.
모델은 제한된 의미론적 판단을 생성하고, TypeScript가 그 판단을 제품의 결과로 변환합니다.
질문에는 다음과 같은 공개 결과가 있을 수 있습니다:
YES (예)
NO (아니요)
PARTLY (부분적으로)
...
이론 제출의 경우:
SOLVED (해결됨)
PARTIAL (부분적)
NOT_SOLVED (미해결)
이 분리는 여전히 저에게 맞다고 느껴집니다.
실수는 아키텍처 자체가 아니었습니다.
실수는 제가 한 플레이어 문장에서 추출하려고 시도한 의미론적 구조가 너무 많았다는 것입니다.
버전 2: 심사관이 너무 영리해졌다
버전 2에서는 질문 판단이 대략 다음과 같았습니다:
{
wellFormed, // 잘 구성됨
relevant, // 관련성 있음
...
그 값들 대부분은 0과 1 사이의 확률이었습니다.
그런 다음 TypeScript가 다음과 같은 규칙을 적용했습니다:
if (wellFormed < 0.8) {
return
다시 말해, 결정론적 TypeScript가 이 점수들을 결합했습니다.
목표는 합리적이었습니다.
저는 다음을 구별하고 싶었습니다:
"불이 있다."
→ 부분적(PARTIAL)
다음과:
"불이 손님을 깨운다."
→ 해결됨(SOLVED)
그리고 다음과:
"불은 있지만, 진동이 손님을 깨운다."
→ 미해결(NOT_SOLVED)
개념적으로 Version 2가 훨씬 더 표현력이 좋았습니다.
코드도 체계적으로 보였습니다.
# 그리고 소프트웨어 테스트는 훌륭했습니다
Version 2는 다음을 통과했습니다:
880개의 단위/API 테스트
82개의 데스크톱/모바일 E2E 테스트
누수 스캐닝(Leak scanning)
...
저는 보정 데이터가 있었습니다.
별도의 엔지니어링-일반화 코퍼스도 가지고 있었습니다.
숨겨진 채점 메타데이터는 서버 측에 유지되었습니다.
제공자 호출은 제한적(bounded)으로 유지되었습니다.
모든 것이 좋아 보였습니다.
그런 다음 실제 모델을 사용했습니다.
# 실제 게임은 더 나쁘게 느껴졌다
하나의 퍼즐은 소리나 물리적 접촉 없이 손님이 깨어나는 것에 관한 것입니다.
숨겨진 메커니즘은 시각적인 빛 신호입니다.
저는 이것을 시도했습니다:
빛인가요?
Version 2:
정보가 충분하지 않습니다(Not enough information)
그런 다음:
손님을 깨우는 것이 빛인가요?
Version 2:
정보가 충분하지 않습니다(Not enough information)
저는 정확한 아이디어를 이론으로 제출했습니다:
손님을 깨우는 것이 빛인가요?
Version 2:
아직은 아닙니다(Not quite yet).
그것만으로도 걱정스러웠습니다.
그런 다음 저는 이것을 시도했습니다:
깨어나는 메커니즘이 소리를 내나요?
결과:
다시 표현해 주세요(Please rephrase)
심지어:
깨어나는 기계가 소리를 내나요?
도출된 결과:
다시 표현해 주세요(Please rephrase)
단어 `mechines`는 명백한 오타입니다.
하지만 의도하는 의미는 여전히 인간이 파악하기 쉽습니다.
이 시점에서 아키텍처는 기술적으로 깔끔했지만, 실제 게임 마스터(Game Master)는 이상하게 경직되어 느껴졌습니다.
# 무엇이 잘못되었을까
아마도 단일한 원인은 없었을 것입니다.
사실 Version 3은 여러 가지를 한 번에 변경했기 때문에, 그중 어느 하나가 독립적으로 문제를 해결했다고 주장할 수 없습니다.
하지만 네 가지 설계 문제가 명확해졌습니다.
# 1. 0.8 임계값이 너무 보수적이었을 수 있다
이것이 저의 첫 번째 의심이었습니다.
만약 Jev가 실제로 다음과 같은 값을 반환했다면:
wellFormed = 0.74
relevant = 0.92
contradicted = 0.89
다음 질문에 대해:
does the wake up mechines make sound?
이는 시스템이 상당히 많은 것을 이해했음을 의미했을 것입니다.
하지만 제 코드는 먼저 이것을 수행했습니다:
if (wellFormed < 0.8) {
return "REPHRASE";
}
따라서 그 게이트 아래의 모든 유용한 정보는 무관해졌습니다.
확률은 연속적이었습니다.
제 애플리케이션 정책이 그것을 절벽(cliff)으로 만들었습니다.
0.7999 → 거부됨 (rejected)
0.8000 → 수락됨 (accepted)
더 낮은 임계값이 결과를 개선했을 수도 있습니다.
저는 여전히 이것이 그럴듯한 설명이라고 생각합니다.
하지만 Version 2에는 여러 개의 임계값이 있었기 때문에, 하나를 낮춘다고 해서 전체 문제를 반드시 해결할 수는 없습니다.
# 2. 여러 불확실한 결정들이 하드 게이트와 연결됨
더 깊은 문제는 단순히 `0.8`이 너무 높았을 수도 있다는 것이 아니었습니다.
그러한 경계가 여러 개 있었습니다.
시스템은 효과적으로 다음과 같아졌습니다:
IF 충분히 복구 가능하고 (recoverable enough)
AND 충분히 관련성이 있고 (relevant enough)
AND 충분히 뒷받침된다면 (supported enough)
...
각 의미론적 차원(semantic dimension)은 개별적으로 합리적일 수 있었습니다.
하지만 최종적인 동작은 이 모든 것이 올바르게 상호작용하는 것에 달려 있었습니다.
저는 하나의 불확실한 AI 결정을 여러 개의 불확실한 AI 결정으로 대체하고, 그것들을 결정론적 절벽(deterministic cliffs)을 사용하여 연결했습니다.
그것이 전체 제품을 더 취약하게 만들었습니다.
# 3. 인간이 함께 이해하는 것들을 분해함
다음 것을 고려해 보세요:
does the wake up mechines make sound?
인간은 아마도 다음을 의식적으로 평가하지 않을 것입니다:
grammar quality
→ relevance
→ referent resolution
...
우리는 다음과 같은 것을 더 많이 합니다:
"mechines"는 아마도 "mechanism"을 의미한다.
↓
그것들은 깨어나는 메커니즘(wake-up mechanism)을 의미한다.
...
이것은 하나의 의미론적 해석입니다.
Version 2는 그 해석을 여러 개의 점수(scores)로 분해했습니다.
이는 TypeScript 관점에서는 우아했지만,
의미론적 결정(semantic-decision) 관점에서는 부자연스러웠을 수 있습니다.
# 4. 심사관은 시나리오를 알았지만, 어떤 미스터리가 해결되고 있는지 명시적으로는 몰랐다
이것은 가장 유용했던 변경 사항 중 하나가 되었습니다.
예시:
is it light?
단독 영어로 볼 때, `it`은 모호합니다.
하지만 이것은 단독 영어가 아닙니다.
플레이어는 특정한 미스터리를 해결하고 있습니다.
조용한 경보 퍼즐의 경우, 실제로 조사되는 질문은 대략 다음과 같습니다:
What causes the sleeping guest to wake
when the alarm makes no sound
and nothing touches the guest?
인간 게임 마스터는 자연스럽게 다음을 해석합니다:
is it light?
→
Is light what causes the guest to wake?
Version 2에는 시나리오와 숨겨진 답이 있었지만, **미스터리 초점(mystery focus)**에 대한 명시적인 표현은 없었습니다.
이것이 Version 3에서 변경되었습니다.
# Version 3: 심사관당 하나의 의미론적 결정
더 많은 예시나 더 많은 임계값, 또는 더 많은 점수를 추가하여 Version 2를 개선하려고 하기보다는, 저는 복잡성을 제거했습니다.
질문 심사관(Question Judge)은 이제 하나의 범주형 의미론적 결정을 내립니다.
개념적으로:
type QuestionSemanticClass =
| "SUPPORTED"
| "CONTRADICTED"
...
Jev는 이러한 선택지들에 대한 단일 확률 분포를 반환합니다.
그런 다음 TypeScript가 결정론적 매핑을 수행합니다:
SUPPORTED
→ YES
...
더 이상 `wellFormed >= 0.8`과 같은 활성 게이트는 없습니다.
독립적인 의미론적 명사(semantic Nouls)의 사슬도 없습니다.
단 하나의 의미론적 분류만 있습니다.
# 해결책 심사관 역시 같은 방식으로 단순화되었습니다
Version 2에는 다섯 개의 독립적인 신호가 있었습니다.
Version 3에는 하나의 분류만 있습니다:
type SolutionSemanticClass =
| "CORE_CAUSAL_EXPLANATION"
| "CORE_WITH_CONTEXT_ERROR"
...
제품 매핑은 여전히 결정론적입니다:
CORE_CAUSAL_EXPLANATION
→ SOLVED
...
따라서 저는 의미론적 구분을 포기한 것이 아닙니다.
단지 그것들을 생성하는 데 필요한 개별 결정의 수를 줄였을 뿐입니다.
# 또한 `mysteryFocus`를 추가했습니다
이제 모든 퍼즐에는 플레이어가 설명하려고 노력하는 내용에 대한 작고 서버 전용의 설명이 포함됩니다.
예를 들어:
What causes the sleeping guest to wake
when the alarm makes no sound
and nothing touches the guest?
이것은 답을 포함하고 있지 않습니다.
단지 심사관에게 인간 게임 마스터(Game Master)가 자연스럽게 갖는 것과 동일한 문맥적 틀을 제공할 뿐입니다.
이는 다음과 같은 짧은 언어에 도움이 됩니다:
is it light?
does it buzz?
is it moving?
하드코딩된 구문 예외를 만들지 않으면서 말이죠.
# 공유 루브릭을 간소화했습니다
또 다른 변경 사항은 덜 눈에 띄었지만 중요했습니다.
이전의 루브릭들은 점진적으로 의미론적 지침과 특별한 구분을 축적해 왔습니다.
Version 3는 의도적으로 더 작은 질문으로 되돌아갔습니다:
> 이 퍼즐에서 플레이어의 진술을 가장 잘 설명하는 의미론적 범주는 무엇입니까?
루브릭은 여전히 모델에게 다음 사항들을 허용하도록 지시합니다:
non-native English
minor spelling mistakes
missing articles
...
하지만 그러한 속성들에 대한 여러 독립적인 측정치를 요구하지는 않습니다.
다시 말해:
> 덜 결정하세요(Decide less).
# 단순화 후 무슨 일이 일어났을까요?
차이는 즉각적이었습니다.
여기에 실제 보호 미리 보기 결과가 있습니다.
## 이전: Version 2
is it light?
→ Not enough information
is it light that wakes the guest?
→ Not enough information
does the wake up mechanism make sound?
→ Please rephrase
does the wake up machines make sound?
→ Please rephrase
is it light that wakes the guest?[Theory]
→ Not quite yet.
이제 이것을 Version 3과 비교해 보세요.
# 이후: Version 3
is it light?
→ Yes
is brightness involved?
→ Yes
something bright wake him?
→ Yes
이것은 특히 중요합니다.
문법이 좋지 않더라도:
something bright wake him?
의도된 의미를 복구할 수 있습니다.
새로운 심사관은 그렇게 처리했습니다.
다음으로:
does the wake up machines make sound?
→ No
오타가 시스템이 전체 질문을 거부하도록 만들지 않았습니다.
마찬가지로:
does it buzz?
→ No
그리고:
is there vibration?
→ No
짧은 질문들도 사용 가능해졌습니다.
# 혼합된 진술도 올바르게 작동하기 시작했습니다
저는 이것을 시도해 보았습니다:
something bright wake him?
does the wake up machines make sound?
결과는 다음과 같았습니다:
부분적으로
UI와 함께:
"일부는 맞지만, 다른 부분은 아닙니다."
이것이 바로 `PARTLY`가 의도했던 의미입니다.
불확실성이 아닙니다.
근접도가 아닙니다.
실제 혼합된 의미적 진실입니다.
# 가장 중요한 것은, 간결하고 정확한 이론들이 퍼즐을 풀기 시작했다는 것입니다.
질문:
"빛이 그를 깨우나요?"
결과:
"예"
그런 다음 저는 이와 완전히 동일한 텍스트를 이론으로 재사용했습니다:
"빛이 그를 깨우나요?"
결과:
"맞아요."
다른 표현:
"방이 밝아지면서 그가 잠에서 깹니다"
질문:
"예"
이론:
"맞아요."
이는 인간 게임 마스터가 행동해야 하는 방식에 훨씬 가깝습니다.
# 잘못된 인과 메커니즘은 여전히 실패했습니다
단순화가 올바른 키워드를 포함하는 모든 것을 맹목적으로 수용한다는 의미는 아니었습니다.
예를 들어:
"램프가 있지만 진동이 그를 깨웁니다"
질문:
"아니요"
이론:
"아직 아닙니다."
이는 잘못된 `SOLVED` 결과가 보수적인 부분적 판단보다 이 게임에 훨씬 더 나쁘다는 것을 의미합니다.
지금까지 Version 3은 올바른 인과 설명과 잘못된 인과 설명을 구분하는 것을 즉시 파괴하지 않으면서, 더 관용적이 되었습니다.
# 그 행동은 또 다른 퍼즐로 일반화되었습니다
저는 내용물이 변하는 것처럼 보이는 비어 있는 프레임을 포함하는 다른 퍼즐을 테스트했습니다.
몇 가지 예:
"프레임이 큰가?"
→ 관련 없음
"동물인가?"
→ 아니요
"TV인가?"
→ 아니요
"기계인가?"
→ 아니요
그런 다음:
"태양 빛에 색이 변합니다"
→ 예
이론으로 제출:
"태양 빛에 색이 변합니다 → 맞아요."
다시 말하지만, 문법은 완벽하지 않습니다.
하지만 인과적 아이디어는 정확합니다.
그리고 심사관은 그것을 받아들였습니다.
# Version 3은 완벽하지 않습니다
경계 사례 주변에는 여전히 불안정성이 있습니다.
예를 들어, 한 세션 동안:
"일출과 일몰인가?"
→ 예
반면 비슷한 문구가 나중에 다음과 같이 나왔습니다:
"일출과 일몰인가?"
→ 정보가 충분하지 않음
그것은 시스템이 의미론적 수준에서 마법처럼 결정론적이지 않다는 것을 알려줍니다.
제가 그렇게 기대해서도 안 되는 부분입니다.
중요한 차이점은 주요 사용자 대면 실패 사례가 다음과 같이 바뀐 것입니다:
"무엇을 요청하는지 분명히 이해하지만, 문장을 다듬어 주시겠어요."
진정으로 모호한 경계에 대한 간헐적인 의견 불일치로 바뀌었습니다.
PoC(Proof-of-Concept) 게임의 경우, 이것은 훨씬 더 나은 실패 방식입니다.
# 그래서 실제로 무엇이 그것을 고쳤는가?
솔직히 말하자면:
**어떤 개별적인 변경 사항이 그것을 고쳤는지 저는 모릅니다.**
버전 3에서는 여러 가지를 함께 변경했습니다:
여러 연속적인 의미론적 게이트 제거
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기