Введение
Я работаю с LLM довольно давно и застал модели времен GPT-3.5, примерно в то же время мне нужно было сделать проект по учебе в этой области, тогда я выбрал именно тему шахмат, потому-что не видел конкретно таких решений раньше, конечно ИИ в онлайн шахматах и так был практически непобедим, но мысль сыграть конкретно с нейросетью уровня Chat GPT, мне показалась интересной. Основная проблема – заставить чат бот играть в игру и не делать ничего лишнего.
В этой статье я разберу архитектуру своего проекта: шахмат на Python, где в качестве соперника выступает LLM:
-
Как объяснить текстовой нейросети, что происходит на доске 8х8.
-
Как заставить ее делать валидные ходы и не ломать игру.
-
Как собрать такую систему бесплатно, используя OpenRouter.
Проблема первая: бюджет
Обычно для таких экспериментов все берут OpenAI, но гонять запросы на каждый шахматный ход в их моделях для такого проекта – это сложно и дорого. Моей целью было сделать проект с нулевым бюджетом.
Поэтому я выбрал OpenRouter. Это агрегатор нейросетей, у которого есть доступ к ряду мощных опенсорсных моделей абсолютно бесплатно (хоть и с ограничением по количеству запросов) – у них есть тег :free.
Вот как выглядит инициализация клиента в моем проекте:
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
Интерфейс библиотеки openai позволяет легко переопределить base_url, поэтому мы бесшовно подключаемся к бесплатной Llama 4 через OpenRouter, не меняя привычный код.
Проблема вторая: LLM не видит доску. Как передать контекст?
Если просто написать промпт «Ты играешь черными, твой ход такой то / такой то», модель огорчится вами и сойдет с ума. Ей нужен контекст. В шахматах есть два стандарта записи:
-
FEN (Forsyth-Edwards Notation) — слепок текущего состояния доски.
-
PGN (Portable Game Notation) — история ходов.
Чтобы минимизировать галлюцинации, я передаю в модель и 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)
Собрав эти данные, формируется жесткий системный промпт. Моя задача была отучить модель болтать и заставить её вернуть ровно 4 символа (например, e2e4):
prompt = f"""Ты играешь в шахматы как {'белые' if board.turn == chess.WHITE else 'черные'}.
Текущая позиция (FEN): {fen}
История ходов: {pgn_history}
Ход номер: {self.move_count}
Доступные ходы в UCI формате: {legal_moves_str}
Выбери ЛУЧШИЙ ход из доступных и верни ТОЛЬКО UCI код хода (например: e2e4, g1f3, e7e8q).
Не добавляй никаких объяснений, анализа или дополнительного текста.
Ответ должен содержать только UCI код хода."""
Третья проблема: Паттерн «AI-Рефери» и Fallback
Даже с указанием «выбери из списка легальных ходов», Llama (или любая другая модель) может выдать галлюцинацию, предложить невозможный ход или вернуть текст с рассуждениями. Если передать это напрямую в графический движок (pygame), приложение упадет с ошибкой.
Нужен слой валидации. В моем коде за это отвечает блок try/except внутри логики UI. Если LLM возвращает недопустимый ход (не проходит проверку python-chess), срабатывает Fallback-механизм – скрипт делает случайный валидный ход, чтобы игра не прерывалась.
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:
# Fallback к случайному ходу
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:
# Fallback к случайному ходу
move = random.choice(list(self.board.legal_moves))
В консоли этот процесс выглядит так:
Запрос к LLM для хода #1...
LLM ответил: 'e7e5'
LLM сделал ход: e7e5
Запрос к LLM для хода #2...
LLM ответил: 'Хорошо, я думаю лучший ход это d7d5'
LLM дал некорректный ответ, случайный ход: a7a6
Архитектура проекта
Выводы
-
Бесплатный ИИ существует (хоть и с условностями): OpenRouter и опенсорсные модели отлично справляются с подобными проектами, позволяя не тратить деньги на API.
-
Контекст решает всё: Чем больше жестких рамок вы зададите в промпте (FEN + PGN + список легальных ходов), тем адекватнее будет ответ.
-
Защищайте свой код: Паттерн «Рефери», когда классический детерминированный код (в данном случае
python-chess) стоит между LLM и состоянием приложения, – это база для построения надежных AI-систем.
P.S. Не знаю принято тут так писать или нет, но, Спасибо за внимание.
Автор: bonel1to


