Создание AI-агента для Pokemon Red: Qwen 2.5, WebLLM, TensorFlow.js | AiManual
AiManual Logo Ai / Manual.
24 Янв 2026 Гайд

Pokemon Red на автопилоте: как собрать AI-агента на чистом браузере

Подробный гайд по созданию автономного AI-агента для Pokemon Red в браузере без серверов. WebLLM, TensorFlow.js и WASM-эмулятор на практике.

Зачем вообще это нужно?

Представьте: вы хотите запустить AI-агента, который играет в Pokemon Red. Обычный путь - арендовать GPU, поднять сервер, настроить API. Но что если все это можно сделать прямо в браузере? Без долларов на облака, без сложной инфраструктуры.

Именно это мы и сделаем. На чистом клиенте. С использованием Qwen 2.5 через WebLLM, TensorFlow.js для обучения нейросети-политики и WASM-эмулятора Game Boy. Звучит как магия, но к 2026 году это уже стандартная практика.

Важный момент: все работает локально. Никаких API-ключей, никаких счетов за облако. Открыли страницу - агент начал играть. Закрыли - все остановилось. Идеально для демонстраций и экспериментов.

Архитектура, которая не сломается

Прежде чем лезть в код, нужно понять, как все устроено. Наша система состоит из трех основных компонентов:

  • WASM-эмулятор Game Boy - запускает Pokemon Red прямо в браузере, отдает скриншоты и принимает нажатия кнопок
  • WebLLM с Qwen 2.5 1.5B - локальная LLM, которая анализирует состояние игры и принимает стратегические решения
  • TensorFlow.js с нейросетью-политикой - учится на действиях LLM, со временем начинает играть лучше и быстрее
КомпонентЗадачаПочему именно он
WASM GB EmulatorЭмуляция игры, захват состоянияРаботает в браузере, не требует плагинов
Qwen 2.5 1.5BСтратегическое планированиеОптимальное соотношение размер/качество для клиента
TensorFlow.js Policy NetОптимизация действийМожет обучаться в реальном времени

Собираем по частям

1WASM-эмулятор: ставим игру на паузу

Сначала нужен эмулятор. Берем gbemu-wasm - он уже умеет все, что нам нужно. Устанавливаем:

npm install gbemu-wasm@latest

Инициализируем эмулятор и загружаем ROM:

import { GBEmulator } from 'gbemu-wasm';

const emulator = await GBEmulator.create();
await emulator.loadROM('/roms/pokemon-red.gb');

// Получаем текущий кадр
const frame = emulator.getFrame(); // Uint8Array с пикселями

// Отправляем действие
emulator.pressButton('A');
emulator.releaseButton('A');
💡
Не загружайте пиратские ROM! Используйте только легальные копии игр, которыми вы владеете. Технически эмулятор работает с любым ROM, но юридически - только с вашими.

2WebLLM: запускаем Qwen 2.5 локально

Здесь начинается магия. WebLLM от MLCommons позволяет запускать LLM прямо в браузере через WebGPU. На 2026 год актуальная версия - 0.3.8 с поддержкой Qwen 2.5.

npm install @mlc-ai/web-llm@0.3.8

Инициализируем модель. Важно: модель качается при первом запуске, так что первый раз будет долго.

import { CreateWebWorkerEngine } from '@mlc-ai/web-llm';

const engine = await CreateWebWorkerEngine(
  new Worker(new URL('./worker.ts', import.meta.url)),
  {
    model: 'Qwen2.5-1.5B-Instruct-q4f16_1',
    temperature: 0.7,
    maxTokens: 512
  }
);

// Промпт для анализа состояния игры
const analyzePrompt = `Ты играешь в Pokemon Red. Текущий экран: [описание экрана].
Твои покемоны: [список]. Противник: [противник].
Какое действие предпринять? Выбери одно:
1. Атаковать [атака]
2. Сменить покемона
3. Использовать предмет
4. Сбежать

Объясни выбор.`;

const response = await engine.chat.completions.create({
  messages: [{ role: 'user', content: analyzePrompt }]
});

Qwen 2.5 1.5B весит около 900 МБ в квантизации q4. Убедитесь, что у пользователей есть быстрый интернет и достаточно места в кэше. Для мобильных устройств лучше использовать еще более легкие версии.

3TensorFlow.js: учимся на своих ошибках

LLM умная, но медленная. Каждый запрос занимает секунды. Нейросеть-политика учится повторять действия LLM, но делает это за миллисекунды.

Создаем простую сверточную сеть, которая по скриншоту предсказывает действие:

import * as tf from '@tensorflow/tfjs';

// Сеть для анализа скриншотов 160x144 с 4 каналами (RGBA)
const createPolicyNetwork = () => {
  const model = tf.sequential();
  
  // Сверточные слои для анализа изображения
  model.add(tf.layers.conv2d({
    inputShape: [144, 160, 4],
    filters: 32,
    kernelSize: 8,
    strides: 4,
    activation: 'relu'
  }));
  
  model.add(tf.layers.conv2d({
    filters: 64,
    kernelSize: 4,
    strides: 2,
    activation: 'relu'
  }));
  
  model.add(tf.layers.conv2d({
    filters: 64,
    kernelSize: 3,
    strides: 1,
    activation: 'relu'
  }));
  
  model.add(tf.layers.flatten());
  
  // Полносвязные слои для принятия решений
  model.add(tf.layers.dense({ units: 512, activation: 'relu' }));
  model.add(tf.layers.dense({ units: 256, activation: 'relu' }));
  
  // Выход: вероятности для каждого действия
  model.add(tf.layers.dense({ 
    units: 14, // A, B, Start, Select, Up, Down, Left, Right + комбинации
    activation: 'softmax' 
  }));
  
  return model;
};

const policyNet = createPolicyNetwork();
policyNet.compile({
  optimizer: tf.train.adam(0.001),
  loss: 'categoricalCrossentropy'
});

Связываем все вместе

Теперь нужна логика, которая координирует все компоненты. Создаем основной цикл агента:

class PokemonAgent {
  constructor(emulator, llmEngine, policyNet) {
    this.emulator = emulator;
    this.llm = llmEngine;
    this.policy = policyNet;
    this.memory = []; // Для обучения с подкреплением
    this.useLLM = true; // Начинаем с LLM, потом переключаемся на политику
  }
  
  async step() {
    // 1. Получаем текущее состояние
    const frame = this.emulator.getFrame();
    const gameState = this.extractState(frame);
    
    // 2. Выбираем действие
    let action;
    if (this.useLLM && Math.random() < 0.3) {
      // Иногда спрашиваем LLM для улучшения стратегии
      action = await this.getLLMAction(gameState);
      this.recordForTraining(frame, action); // Запоминаем для обучения
    } else {
      // Обычно используем быструю политику
      action = this.getPolicyAction(frame);
    }
    
    // 3. Применяем действие
    this.executeAction(action);
    
    // 4. Обучаем политику на новых данных
    if (this.memory.length > 100) {
      await this.trainPolicy();
    }
    
    // 5. Через час переключаемся на политику
    if (this.steps > 3600 && this.useLLM) {
      this.useLLM = false;
      console.log('Переключились на чистую политику');
    }
  }
  
  async getLLMAction(state) {
    const prompt = this.buildPrompt(state);
    const response = await this.llm.chat.completions.create({
      messages: [{ role: 'user', content: prompt }]
    });
    return this.parseLLMResponse(response.choices[0].message.content);
  }
  
  getPolicyAction(frame) {
    const tensor = tf.tensor(frame).reshape([1, 144, 160, 4]);
    const prediction = this.policy.predict(tensor);
    const actionIdx = tf.argMax(prediction, 1).dataSync()[0];
    tensor.dispose();
    prediction.dispose();
    return this.actions[actionIdx];
  }
}

Ошибки, которые всех ломают

Видел десятки попыток повторить эту архитектуру. Все спотыкаются об одно и то же:

  • Утечки памяти в TensorFlow.js. Каждый тензор нужно явно освобождать с помощью .dispose(). Иначе через час браузер упадет.
  • Слишком частые вызовы LLM. WebLLM не предназначен для запросов каждые 100 мс. Делайте паузы между стратегическими решениями.
  • Игнорирование контекста игры. Pokemon Red - игра с состоянием. Нужно отслеживать не только текущий экран, но и историю.

Самая частая ошибка: пытаться обрабатывать каждый кадр через LLM. Это бессмысленно. Игрок-человек не принимает 60 решений в секунду. Делайте стратегические паузы: оценили ситуацию, приняли решение, выполнили серию действий.

Оптимизации, которые реально работают

Базовая версия работает. Но медленно. Вот что ускорит агента в 5-10 раз:

  1. Кэширование решений LLM. Похожие ситуации = одинаковые действия. Хэшируйте состояние игры, сохраняйте решения.
  2. Предобработка скриншотов. Не нужно скармливать сети полный 160x144 кадр. Ресайз до 80x72, конвертация в grayscale - и производительность взлетает.
  3. Пакетное обучение политики. Не обучайте сеть после каждого действия. Копите 100-200 примеров, потом одним батчем.

Вот как выглядит оптимизированный предобработчик:

class FrameProcessor {
  static preprocess(frame) {
    // Быстрее делать все в одном тензоре
    return tf.tidy(() => {
      let tensor = tf.tensor(frame).reshape([144, 160, 4]);
      
      // Только красный канал (достаточно для Pokemon)
      tensor = tensor.slice([0, 0, 0], [144, 160, 1]);
      
      // Ресайз для сети
      tensor = tf.image.resizeBilinear(tensor, [72, 80]);
      
      // Нормализация
      tensor = tensor.div(255.0);
      
      return tensor;
    });
  }
}

Что дальше? Куда развивать архитектуру

Рабочий агент - только начало. Дальше можно:

  • Добавить долговременную память. LLM забывает, что было 10 минут назад. Нужно хранить ключевые события (поймал покемона, проиграл битву).
  • Иерархическое планирование. Одна LLM для стратегии ("идти в пещеру Диджи"), другая для тактики ("бить электричеством против воды").
  • Мультиагентность. Запустить 10 копий агента параллельно, чтобы исследовать разные стратегии быстрее.

Если хотите углубиться в проектирование сложных агентов, посмотрите гайд по современным AI-агентам. Там разобраны именно такие продвинутые архитектуры.

💡
Интересный факт: такой агент может играть лучше человека. Не потому что умнее, а потому что не устает и не совершает эмоциональных ошибок. За 24 часа непрерывной игры он увидит больше ситуаций, чем средний игрок за месяц.

Чеклист перед запуском

Перед тем как запускать агента на долгую сессию:

  1. Проверьте, что WebGPU поддерживается браузером (navigator.gpu не undefined)
  2. Убедитесь, что модель Qwen 2.5 полностью загрузилась (индикатор в WebLLM)
  3. Протестируйте эмулятор вручную - игра должна запускаться без артефактов
  4. Настройте автосохранение состояния каждые 1000 шагов (в LocalStorage)
  5. Добавьте логгирование действий для отладки

И главное - не ждите, что агент с первого раза пройдет игру. Он будет тупить, зацикливаться, проигрывать. Но с каждым часом будет становиться умнее. Как настоящий покемон.

Эта архитектура - не только про Pokemon. Те же принципы работают для любых игр, симуляторов, даже для автоматизации браузера. Клиентский AI-агент на WebLLM и TensorFlow.js открывает дверь в мир автономных приложений, которые работают полностью локально.

Если хотите копнуть еще глубже в локальные AI-системы, рекомендую полное руководство по локальным RAG-системам. Там те же принципы, но примененные к работе с документами.