Reward Rover Playground: Technische Dokumentation

Wichtige Klarstellung: RAG vs. RL

Das Projekt implementiert kein RAG-System, sondern einen Reinforcement Learning (RL) Agenten.

FeatureRAG (Dein Knowledge Vault)Reinforcement Learning (Reward Rover)
Kern-PrinzipRetrieval + GenerationTrial & Error (Belohnung/Bestrafung)
DatenbasisVektor-Datenbank (Embeddings)Q-Table (State-Action Values)
ZielTextgenerierung / AntwortEntscheidungsfindung / Pfadoptimierung
ModellLLM (Claude/OpenAI)Q-Learning Algorithmus (Mathematisch)

1. Architektur-Überblick

πŸ’‘ Vergleich zum RAG-Projekt: Die Grundstruktur ist identisch (React Frontend + Express Backend), was deine Konsistenz in der Architekturentscheidung beweist.

Plaintext

  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚                         FRONTEND (React)                        β”‚
  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
  β”‚  β”‚  RLGame.tsx β”‚  β”‚  Tile.tsx   β”‚  β”‚ EnvironmentBrowser.tsx  β”‚  β”‚
  β”‚  β”‚  (307KB)    β”‚  β”‚  (Memoized) β”‚  β”‚  (Level-Auswahl)        β”‚  β”‚
  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
  β”‚                            β”‚                                    β”‚
  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
  β”‚  β”‚                   lib/rl/ (getestet)                     β”‚   β”‚
  β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚   β”‚
  β”‚  β”‚  β”‚ qLearning.tsβ”‚ β”‚ gridUtils.tsβ”‚ β”‚ portalUtils.ts  β”‚     β”‚   β”‚
  β”‚  β”‚  β”‚  (26 Tests) β”‚ β”‚  (27 Tests) β”‚ β”‚   (24 Tests)    β”‚     β”‚   β”‚
  β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚   β”‚
  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                β”‚ HTTP/REST
                                β–Ό
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚                         BACKEND (Express)                       β”‚
  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
  β”‚  β”‚  index.js  β”‚  β”‚  auth.js   β”‚  β”‚      daily.js           β”‚    β”‚
  β”‚  β”‚ (API Routen)β”‚  β”‚ (JWT)      β”‚  β”‚   (Daily Challenges)    β”‚    β”‚
  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
  β”‚                            β”‚                                    β”‚
  β”‚                            β–Ό                                    β”‚
  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
  β”‚  β”‚                   SQLite (db.js)                        β”‚    β”‚
  β”‚  β”‚   Users β”‚ SavedStates β”‚ DailyChallenges β”‚ Submissions   β”‚    β”‚
  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

2. Der Q-Learning Algorithmus (KernstΓΌck)

Im Gegensatz zum RAG-System, das externe Intelligenz (APIs) nutzt, lΓ€uft hier die β€œIntelligenz” lokal im Browser ab.

2.1 Die Bellman-Gleichung

Parameter:

  • (Learning Rate): 0.1 – Wie schnell ΓΌberschreibt neues Wissen altes Wissen?

  • (Discount Factor): 0.85 – Wie wichtig sind zukΓΌnftige Belohnungen gegenΓΌber sofortigen?

  • (Exploration Rate): Balance zwischen Erkunden (Zufall) und Nutzen (Wissen).

2.2 Implementierung (qLearning.ts)

TypeScript

// Die Q-Wert Update-Funktion (Zeile 138-146)
export const updateQValue = (
  currentQValue: number,
  reward: number,
  maxNextQ: number,
  alpha: number,
  gamma: number
): number => {
  return currentQValue + alpha * (reward + gamma * maxNextQ - currentQValue);
};

2.3 Epsilon-Greedy Aktionsauswahl

TypeScript

// chooseAction in qLearning.ts (Zeile 35-76)
export const chooseAction = (
  grid: TileState[][],
  pos: Position,
  explorationRate: number,
  biasDirection?: Position | null
): Position => {
  const possible = getPossibleActions(grid, pos);

  // 1. Optional: Bias-Richtung (35% Chance zum Ziel)
  if (biasDirection && Math.random() < 0.35) {
    return biasMatch;
  }

  // 2. Exploration: ZufΓ€llige Aktion mit Wahrscheinlichkeit Ξ΅
  if (Math.random() < explorationRate) {
    return possible[Math.floor(Math.random() * possible.length)];
  }

  // 3. Exploitation: Beste Q-Wert Aktion
  let best = possible[0];
  let bestQ = grid[best.y][best.x].qValue;
  // ... finde beste Aktion (Code gekΓΌrzt)
  return best;
};

2.4 Reward-Struktur (constants.ts)

TypeScript

export const STEP_PENALTY = -1;        // Jeder Schritt kostet
export const REWARD_VALUE = 12;        // Belohnungs-Feld
export const PUNISHMENT_VALUE = -15;   // Bestrafungs-Feld
export const OBSTACLE_PENALTY = -20;   // Wand-Kollision
export const GOAL_REWARD = 24;         // Ziel erreicht
export const PORTAL_COOLDOWN_STEPS = 4; // Portal-Wartezeit

3. Frontend-Architektur

3.1 Tech Stack

JSON

{
  "react": "^18.3.1",
  "react-router-dom": "^6.26.2",
  "@tanstack/react-query": "^5.56.2",   // Server-State
  "tailwindcss": "^3.4.11",             // Styling
  "zod": "^3.23.8",                     // Validierung
  "recharts": "^2.12.7",                // Diagramme
  "vite": "^5.4.1",                     // Build-Tool
  "vitest": "^4.0.10"                   // Testing
}

3.2 Drei Spielmodi

  1. Playground Mode: Eigene Umgebungen bauen, Hyperparameter live anpassen, Q-Werte visualisieren.

  2. Random Mode: Prozedural generierte Level, Bonus-System, Speedrun.

  3. Comparison Mode: Zwei Agenten parallel trainieren um Parameter zu vergleichen.

3.3 State Management

Die Trennung von Daten und UI ist hier kritisch, da sich der State (grid) extrem schnell Γ€ndert (bei jedem Schritt des Agenten).

TypeScript

// Playground State Interface (RLGame.tsx ~1024-1036)
interface PlaygroundState {
  agent: Position;
  goal: Position;
  grid: TileState[][];        // 2D Array mit Q-Werten
  isRunning: boolean;
  episode: number;
  totalReward: number;
  currentSteps: number;
  episodeHistory: EpisodeStats[];
  spawn: Position;
  portalCooldowns: Record<string, number>;
}

4. Backend-Architektur

πŸ’‘ Vergleich zum RAG-Projekt: Beide nutzen Express.js. WΓ€hrend der RAG-Server hauptsΓ€chlich als Proxy zu OpenAI/Anthropic dient, hat dieses Backend eigene komplexe Logik (Auth, Seeds, DB-Sync).

4.1 Express Server Setup

JavaScript

import express from "express";
import cors from "cors";
// ... imports

const app = express();
app.use(cors({ origin: process.env.CORS_ORIGIN || "*" }));
app.use(express.json({ limit: "2mb" }));

4.2 Authentifizierung (Erweitert)

Im Gegensatz zum simplen Passwortschutz des RAG-Vaults nutzt Reward Rover echtes OAuth (Google/Apple) und JWT.

JWT Token Erstellung:

JavaScript

const createToken = (user) => {
  return jwt.sign(
    { id: user.id, username: user.username, role: user.role },
    JWT_SECRET,
    { expiresIn: "7d" },
  );
};

OAuth Flow (Google):

JavaScript

app.post(`${API_PREFIX}/auth/oauth/google`, async (req, res) => {
  const ticket = await googleClient.verifyIdToken({
    idToken,
    audience: GOOGLE_CLIENT_ID,
  });
  // ... user resolve logic ...
  const token = createToken(user);
  return res.json({ token, user });
});

4.3 API-Endpunkte

EndpunktMethodeAuthBeschreibung
/api/auth/*POST-Login/Register/OAuth
/api/savePOSTJWTUmgebung speichern
/api/loadGETJWTEigene Umgebungen laden
/api/daily/currentGET-Tages-Challenge holen (Seed)
/api/daily/submitPOSTJWTScore einreichen
/api/leaderboardGET-Highscores

5. Datenbank-Schema (SQLite)

πŸ’‘ Unterschied zum RAG-Projekt: Der RAG-Ansatz nutzt eine JSON-Datei als Vektor-Store. Hier wird eine relationale Datenbank (SQLite) benΓΆtigt, um Benutzerbeziehungen und Highscores abzubilden.

SQL

-- Benutzer mit OAuth-UnterstΓΌtzung
CREATE TABLE Users (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  username TEXT NOT NULL UNIQUE,
  password_hash TEXT NOT NULL,
  role TEXT NOT NULL DEFAULT 'user',
  provider TEXT NOT NULL DEFAULT 'local', -- 'local', 'google', 'apple'
  provider_id TEXT,
  email TEXT
);

-- TΓ€gliche Herausforderungen (Deterministisch)
CREATE TABLE DailyChallenges (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  date_key TEXT NOT NULL UNIQUE,   -- '2024-01-15'
  seed INTEGER NOT NULL,           -- Deterministischer Seed
  config_json TEXT NOT NULL        -- Grid-Konfiguration
);

-- Performance Optimierungen
PRAGMA foreign_keys = ON;
PRAGMA journal_mode = WAL; -- Write-Ahead Logging fΓΌr Concurrency

6. Daily Challenge System (Fairness durch Mathe)

Ein Highlight ist die Reproduzierbarkeit der Level durch Seeded RNG.

1. Hash vom Datum:

JavaScript

const hashDateToSeed = (dateKey) => { /* Wandelt String '2026-01-15' in Integer Hash */ };
  1. Mulberry32 PRNG:

Ein extrem schneller Pseudo-Random-Number-Generator. Er garantiert, dass random() fΓΌr denselben Seed immer die exakt gleiche Sequenz an Zahlen liefert.

Warum wichtig? Jeder User bekommt weltweit das exakt gleiche Level fΓΌr den gleichen Tag. Das macht das Leaderboard fair.


7. Testing-Strategie

πŸ’‘ Vergleich: Das RAG-Projekt ist schwer zu testen (nicht-deterministische LLM-Antworten). Reward Rover hingegen ist rein algorithmisch und daher perfekt durch Unit-Tests abgedeckt (77 Tests).

Struktur:

  • qLearning.test.ts (26 Tests)

  • gridUtils.test.ts (27 Tests)

  • portalUtils.test.ts (24 Tests)

Beispiel-Test (Q-Learning):

TypeScript

describe('updateQValue', () => {
  it('should update Q-value using Bellman equation', () => {
    // Q = 0 + 0.1 * (10 + 0.9 * 5 - 0) = 1.45
    const newQ = updateQValue(0, 10, 5, 0.1, 0.9);
    expect(newQ).toBeCloseTo(1.45);
  });
});

8. Deployment

πŸ’‘ Gemeinsamkeit: Beide Projekte laufen auf demselben Hetzner VPS (91.99.236.172) und nutzen PM2 zur Prozessverwaltung.

Frontend (Netlify):

Wird als statische Site gebaut (npm run build) und via netlify.toml konfiguriert (Redirects fΓΌr React Router).

Backend (VPS):

Deployment via rsync Script. Nginx dient als Reverse Proxy, PM2 hΓ€lt den Node-Prozess am Leben.


9. Potenzielle Interview-Fragen

Q: Was ist der Unterschied zwischen Exploration und Exploitation?

A: Exploration () probiert zufΓ€llige Aktionen, um neue Wege zu entdecken. Exploitation () nutzt das bereits gelernte Wissen (Q-Table), um die Belohnung zu maximieren.

Q: Warum brauchst du einen Discount Factor ()?

A: Ohne würde der Agent nur den sofortigen Schritt optimieren (gierig). Mit berücksichtigt er auch zukünftige Rewards, was wichtig ist, um z.B. erst einen Umweg zu gehen, um spÀter eine große Belohnung zu erhalten.

Q: Warum SQLite statt PostgreSQL?

A: FΓΌr die erwartete Last ist SQLite im WAL-Modus absolut ausreichend. Es vereinfacht das Deployment (kein separater DB-Server-Prozess), Backups sind einfach (Datei kopieren) und die Latenz ist minimal, da es im selben Prozessraum liegt.

Q: Vergleich zum RAG-Projekt: Wann nutzt du was?

A: Wenn ich faktisches Wissen abrufen und sprachlich verarbeiten muss, nutze ich RAG (Vektorsuche + LLM). Wenn ich ein System brauche, das Strategien lernt und Entscheidungen in einer definierten Umgebung treffen muss, nutze ich Reinforcement Learning (Q-Learning).


See also

see also

Type:
Tags:
Status:
Location:
Created: 2026-01-15 17:24

Source