로컬 LLM으로 'Slay the Spire 2' 에이전트 구축하기: 교훈과 미해결 과제

개발자가 KoboldCPP/Ollama를 통해 로컬 LLM을 사용하여 Slay the Spire 2를 플레이하는 에이전트를 구축했습니다. 게임은 커뮤니티 모드를 통해 REST API로 노출되며, 에이전트는 중간에 위치합니다: 게임 상태 읽기 → 도구와 함께 LLM 호출 → 액션 실행 → 반복.
설정 및 성능
설정은 KoboldCPP를 통해 RTX 4090에서 Qwen3.5-27B(Q4_K_M)를 사용합니다. 성능 지표: 액션당 약 10초, 액션 성공률 약 88%. 최고 결과: Act 1 보스 격파. 프로젝트는 GitHub에서 확인할 수 있습니다: https://github.com/Alex5418/STS2-Agent.
작동하는 것들
- 상태 기반 도구 라우팅 — 한 번에 20개 이상의 도구를 노출하는 대신, 현재 게임 상태와 관련된 1-3개의 도구만 제공됩니다. 전투에는
play_card,end_turn,use_potion이 제공됩니다. 맵 화면에는choose_map_node가 제공됩니다. 이로 인해 환각 도구 호출이 극적으로 감소했습니다. - 단일 도구 모드 — 작은 모델은 액션 후 게임 상태가 어떻게 변하는지 예측할 수 없습니다(예: 카드를 플레이한 후 카드 인덱스가 이동함). 따라서 응답당 첫 번째 도구 호출만 실행된 후 게임 상태를 다시 가져오고 모델에게 다시 요청합니다. 느리지만 훨씬 더 신뢰할 수 있습니다.
- 텍스트 기반 도구 호출 파서(폴백) — KoboldCPP는 종종 구조화된 JSON 대신 텍스트로 도구 호출을 출력합니다. 다중 패턴 정규식 폴백은 다음과 같은 형식을 포착합니다:
json [{"name": "play_card", "arguments": {...}}],Made a function call ... to play_card with arguments = {...},play_card({"card_index": 1, "target": "NIBBIT_0"}), 그리고end_turn과 같은 인수가 없는 도구의 단순 언급. 이는 그렇지 않으면 손실될 액션의 약 15-20%를 복구합니다. - 에너지 가드 — 남은 에너지의 클라이언트 측 추적. 모델이 감당할 수 없는 카드를 플레이하려고 하면 API 호출이 차단되고 턴이 자동으로 종료됩니다. 이는 가장 일반적인 오류 루프(모델이 동일한 감당할 수 없는 카드를 3회 이상 재시도)를 방지합니다.
- 적 턴을 위한 스마트 대기 — 적의 턴 동안 게임 상태는 "Play Phase: False."라고 표시합니다. 이에 대해 LLM 호출을 낭비하는 대신, 에이전트는 플레이어의 턴이 다시 될 때까지 매 1초마다 폴링합니다.
개방형 문제
- 모델이 시스템 프롬프트 규칙을 일관되게 따르지 않음 — 시스템 프롬프트는 "적의 의도가 공격이면, 방어 카드를 먼저 플레이하세요."와 같은 내용을 말합니다. 모델은 이를 약 30%의 경우에 따릅니다. 나머지 70%는 관계없이 공격만 플레이합니다. 시도된 해결책: 더 강력한 어조("반드시 먼저 방어해야 합니다"), 프롬프트의 몇 가지 샘플 예시, 계산된 힌트 주입("경고: 15의 들어오는 피해"). 어느 것도 신뢰할 수 없습니다. 질문: 작은 모델이 조건부 규칙을 따르도록 하는 더 나은 프롬프트 전략이 있을까요? 아니면 이것이 27B에서의 근본적인 한계일까요?
- KoboldCPP와의 도구 호출 신뢰성 — 텍스트 폴백 파서가 있더라도, 약 12%의 응답은 사용 가능한 도구 호출을 생성하지 않습니다. 모델은 때때로 빈
<think></think>블록 뒤에 잘못된 JSON을 출력합니다. Ollama OpenAI 호환성 레이어도 가끔arguments를 딕셔너리 대신 문자열로 반환합니다. 질문: 14-30B 범위에서 도구 호출에 특히 신뢰할 수 있는 모델을 찾은 사람이 있을까요? 개발자는 Phi-4(14B)를 잠시 시도했지만 적절한 비교는 하지 않았습니다. Mistral-Small이나 Command-R을 고려 중입니다. - 컨텍스트 창 관리 — 각 게임 상태는 마크다운으로 약 800-1500 토큰입니다. 시스템 프롬프트(약 500 토큰)와 대화 기록을 포함하면 컨텍스트가 빠르게 채워집니다. 현재는 마지막 5개의 교환만 유지하고 상태 전환(전투 → 맵 등) 시 기록을 재설정합니다. 그러나 모델은 전투 간에 기억이 없습니다 — 실수로부터 배울 수 없습니다. 질문: 롤링 요약 접근 방식이 작동할까요? 예: 마지막 전투를 "당신은 Jaw Worm와 싸웠습니다. 턴 2에 방어하지 않아 15의 피해를 입었습니다. 4턴 만에 승리했습니다."로 압축하는 것.
- 로컬 모델의 더 나은 구조화된 출력 — 핵심 문제는 모델이 JSON 도구 호출을 출력해야 하지만, 실제로는 먼저 자연어로 생각하고 싶어 한다는 것입니다. Qwen3.5는 제거되는
<think>블록을 사용하지만, 때로는 생각과 도구 호출이 뒤섞입니다. 질문: 두 단계 접근 방식이 더 잘 작동할까요? 1단계: "게임 상태를 분석하고 무엇을 할지 결정하세요"(자유 텍스트). 2단계: "이제 정확히 하나의 도구 호출을 출력하세요"(제약됨). 이는 지연 시간을 두 배로 늘리지만 신뢰성을 향상시킬 수 있습니다. 이 패턴을 시도한 사람이 있을까요? - 모델 간 A/B 테스트 — 개발자는 비교를 위해 액션을 기록하는 JSONL 로깅 시스템을 가지고 있습니다.
📖 전체 소스 읽기: r/LocalLLaMA
👀 See Also

솔로 SaaS 창업자가 클로드의 프로젝트 지식을 활용해 매일 20~30분을 절약하는 방법
인도 중소기업을 위한 CRM을 운영하는 솔로 창업자($11.2K MRR)가 Claude의 프로젝트 지식 기능을 통해 매일 반복되던 컨텍스트 설정을 제품, 고객, 성장 도메인에 걸친 지속적이고 선별된 지식으로 대체한 방법을 공유합니다.

개발자가 디자인 도구 없이 Claude AI로 앱 아이콘 디자인
한 개발자가 macOS 프로세스 관리자 'PIDKill'을 만들고, 그 앱 아이콘을 Claude AI, 특히 Claude Code와 Claude 웹만을 사용해 디자인했습니다. 최종 디자인은 프로세스 종료를 나타내기 위해 SF Mono 폰트에 글리치 효과와 빨간색 취소선을 사용합니다.

개발자가 보고한 AI 코딩 과제: 설계 결정과 실제 사용자 디버깅
iOS 앱을 Claude Code로 5개월간 개발한 한 개발자는 AI가 기능적인 코드를 쉽게 생성할 수 있지만, 디자인 결정과 실제 사용자에게만 나타나는 문제 디버깅이 가장 어려운 부분이라고 보고했습니다. 앱은 22만 줄의 코드를 보유하고 있으며 실제 사용자들이 테스트 중입니다.

LLMs는 테라바이트 규모의 CI 로그를 분석하기 위해 SQL 쿼리를 몇 초 만에 생성합니다
Mendral의 AI 에이전트가 자체 SQL 쿼리를 작성하여 불안정한 테스트를 3주 전의 의존성 업데이트로 추적했습니다. 수억 개의 로그 라인을 수십 개의 쿼리로 몇 초 만에 스캔했습니다. 이 시스템은 매주 15억 개의 CI 로그 라인을 처리하며, ClickHouse에서 35:1로 압축됩니다.