샤일록의 법정Shylock
플레이어의 선택에 LLM 캐릭터들이 실시간으로 반응하는 법정 추리 웹 게임. 인증 서버, RAG, 로컬 LLM 서빙, 프로덕션 배포까지 혼자 구축했습니다.
- 사실은 DB가, 문장은 LLM이. 인물 관계를 원문 근거가 달린 그래프(노드 7 · 엣지 17)로 저장했습니다. LLM은 관계를 지어내지 않고, DB에서 꺼낸 사실을 문장으로 풀어내기만 합니다. 저장소는 환경변수 스위치로 Postgres(재귀 CTE)와 Neo4j를 바꿔 끼울 수 있습니다.
- 그래프가 정말 필요한지 실험으로 확인. 그래프를 넣었을 때와 뺐을 때를 비교하는 ablation(조건별 n=42)을 돌렸습니다. 여러 관계를 건너야 답할 수 있는 멀티홉 질문의 정답률이 21%에서 100%로 올랐습니다(Fisher p=3.6×10⁻¹⁵). 단일 사실 질문에서는 차이가 없다는 것도 함께 확인했습니다.
- LLM이 전부 실패해도 판결은 나온다. 판사 ‘공작’의 판정은 LLM judge가 맡습니다. 로컬 모델이 실패하면 외부 API가, 그것도 실패하면 결정론적 규칙이 판정하는 3단 폴백입니다.
- 월 $200 API 비용을 로컬 모델로. 평가 기준을 통과한 26B 오픈소스 모델을 직접 서빙합니다. 기능별 환경변수 스위치가 있어서, 문제가 생긴 기능만 바로 Claude · Cohere로 되돌릴 수 있습니다. 도구 호출(Function Calling) 에이전트는 호출하면 안 되는 12개 상황에서 12번 모두 호출해 기준 미달로 판정했고, 이 기능은 API에 남겼습니다.
- 서버는 작게, 추론은 맥북에서. 1GB EC2에는 백엔드만 두고, 추론은 맥북의 Ollama가 맡습니다. 둘은 Cloudflare Tunnel + Zero Trust로 연결했습니다. Hexagonal · DDD · Vertical Slice 구조라 도메인 코드는 그대로 두고 어댑터만 추가했습니다.
- 추론 위치만 바꿔 약 580배 빠르게. 같은 임베딩 모델을 EC2(t3.micro)에서 직접 돌리면 CPU 크레딧이 바닥나 평균 7.5초(p95 29초)가 걸렸습니다. 맥북(Apple Silicon)에서 돌리고 결과만 네트워크로 받으니 약 13ms가 됐습니다.
- N+1은 조용히 넘어가지 않게. ORM
relationship()은 진짜 애그리게이트(Trial–ChoiceHistory) 하나에만 쓰고, 나머지 FK는 ID 참조와 수동 join으로 처리합니다. AsyncSession에서 로드 안 된 관계를 건드리면 즉시 예외가 나서, N+1 쿼리가 몰래 생기지 않고 DDD 애그리게이트 경계도 지켜집니다. - 인증부터 배포까지 직접. Google OAuth + RS256 JWT 인증 서버, nginx, Docker Compose 배포를 직접 구축했습니다.
ERD
‖ 정확히 하나 ○< 0개 이상 · 실선 = DB 외래 키, 점선 = 원문 줄 번호(ftln) 범위를 가리키는 soft ref · alembic 033 기준
불러오는 중…
Postgres와 같은 인물 관계 데이터를 그래프로 미러링한 대안 게이트웨이 ·
×2 = 대칭 관계를 A→B, B→A 두 방향 엣지로 저장