홈으로 돌아가기

Python에서 LLM과 함께하는 체스, 환각 현상 없이

이 기사는 python-chess를 사용한 검증으로 LLM 기반 체스 AI 구현을 설명합니다. 프롬프트와 안정성을 위한 대체 기능에 FEN/PGN/UCI를 사용합니다. OpenRouter를 통한 무료 액세스

오류 없이 Llama에서 체스 AI 만들기
Advertisement 728x90

LLM과 파이썬으로 체스 AI 만들기 — 비용 없고 환각 현상도 없음

개발자들은 게임 앱에 LLM을 통합해야 할 때가 많지만, 오픈아이의 API는 비용이 많이 듭니다. 저렴한 대안으로는 무료 오픈소스 모델을 제공하는 오픈라우터(OpenRouter)가 있습니다. 이 서비스는 :free 태그가 붙은 모델을 통해 라마 4(Llama 4)를 무료로 사용할 수 있으며, 메시지 양 제한이 있습니다.

클라이언트 초기화는 표준 openai 인터페이스를 사용합니다:

class LLMAI:
    def __init__(self, model_name="meta-llama/llama-4-maverick-17b-128e-instruct:free"):
        self.client = openai.OpenAI(
            base_url="https://openrouter.ai/api/v1",
            api_key=OPENROUTER_API_KEY,
        )
        self.model_name = model_name
        self.move_count = 0

base_url를 설정하면 기존 코드와 호환됩니다. 각 수마다 모델에게 프롬프트를 전달해 계산 부담을 최소화합니다.

Google AdInline article slot

보드 상태를 프롬프트에 전달하는 방법

LLM은 시각적 보드를 처리하지 못하므로, 텍스트 기반으로 상황을 구성해야 합니다. 표준 FEN과 PGN 형식에 더해 python-chess에서 추출한 유효한 수 목록을 추가해 환각 현상을 방지합니다.

상황 구성 코드:

# 현재 위치를 FEN 형식으로 가져오기
fen = board.fen()

# 수의 이력을 PGN 형식으로 가져오기
pgn_moves = []
temp_board = chess.Board()

# 수의 이력
for move in board.move_stack:
    pgn_moves.append(temp_board.san(move))
    temp_board.push(move)

pgn_history = " ".join(pgn_moves) if pgn_moves else "시작 위치"

# 유효한 수 목록 가져오기
legal_moves = [move.uci() for move in board.legal_moves]
legal_moves_str = ", ".join(legal_moves)

시스템 프롬프트는 UCI 표기법(예: e2e4)으로 응답을 제한합니다:

Google AdInline article slot
prompt = f"""당신은 {'백' if board.turn == chess.WHITE else '흑'} 쪽으로 체스를 두고 있습니다.

현재 위치 (FEN): {fen}
수 이력: {pgn_history}
수 번호: {self.move_count}

사용 가능한 수 (UCI 형식): {legal_moves_str}
가능한 선택지 중 가장 좋은 수를 고르고, 단지 UCI 수 코드만 반환하세요 (예: e2e4, g1f3, e7e8q).
설명, 분석, 추가 텍스트는 포함하지 마세요.
응답에는 반드시 UCI 수 코드만 포함되어야 합니다."""

FEN(현재 상태), PGN(이력), UCI 목록(제약 조건)을 결합하면 기본 프롬프트보다 정확도가 80~90% 증가합니다.

수 검증 및 백업 메커니즘

LLM은 지시사항을 무시하고 텍스트나 불법 수를 반환할 수 있습니다. 해결책은 바로 'AI 심판' 패턴입니다. python-chess가 UCI 수를 보드에 적용하기 전에 유효성 검사를 수행합니다.

UI 루프 로직:

Google AdInline article slot
if move_uci:
    try:
        move = chess.Move.from_uci(move_uci)
        if move in self.board.legal_moves:
            # 수 실행 전 애니메이션
            self.animate_move(move)
            self.board.push(move)
            self.move_history.append(move.uci())
            self.last_ai_response = f"LLM이 수를 두었습니다: {move.uci()}"
            print(self.last_ai_response)
        else:
            # 백업: 무작위 수 선택
            move = random.choice(list(self.board.legal_moves))
            self.animate_move(move)
            self.board.push(move)
            self.move_history.append(move.uci())
            self.last_ai_response = f"LLM 실패, 무작위 수: {move.uci()}"
            print(self.last_ai_response)
    except ValueError:
        # 백업: 무작위 수 선택
        move = random.choice(list(self.board.legal_moves))

예시 로그:

  • 요청 #1: LLM이 'e7e5' 응답 → 유효한 수.
  • 요청 #2: LLM이 '좋아요, 제가 생각하기엔 가장 좋은 수는 d7d5예요' → 백업으로 a7a6 선택.

이 방식은 충돌 없이 원활한 게임 플레이를 보장합니다.

핵심 아키텍처 구성 요소

이 프로젝트는 다음과 같은 요소에 의존합니다:

  • python-chess: 보드 관리, 수 검증, FEN/PGN/UCI 처리.
  • pygame: 보드 렌더링, 수 애니메이션.
  • openai-client + OpenRouter: LLM 추론 호출.
  • 백업 로직: random.choice를 활용한 내구성 확보.

모듈러 설계: LLMAI 클래스는 UI와 분리되어 있으며, 유효한 수만 전달합니다. 체스 외에도 바둑이나 체스와 유사한 규칙 기반 게임으로 쉽게 확장 가능합니다.

| 구성 요소 | 역할 | 라이브러리 |

|-----------|------|---------|

| 보드 | 상태 및 검증 | python-chess |

| LLM | 수 선택 | OpenRouter Llama 4 |

| UI | 렌더링 및 입력 | pygame |

| 심판 | 검증 | try/except |

무엇이 가장 중요한가?

  • 오픈라우터는 프로토타이핑용으로 강력한 LLM에 무료로 접근할 수 있게 하며, 요청량 제한이 있습니다.
  • FEN, PGN, UCI 목록을 활용한 엄격한 프롬프트는 환각 현상을 10~20%로 줄입니다.
  • 'AI 심판' 패턴과 백업 메커니즘은 안정성을 보장합니다: LLM은 제안하고, 라이브러리는 검증하고 적용합니다.
  • python-chess는 파이썬에서 체스 논리를 다루는 사실상의 표준이며, 캐슬링, 엔파산 등 모든 규칙을 커버합니다.
  • 이 프로젝트는 비결정론적 모델을 결정론적 애플리케이션에 통합하는 방법을 보여줍니다.

— Editorial Team

Advertisement 728x90

다음 읽기