An AI coach for League of Legends. Turns a player's ranked match history into specific, actionable coaching — not another stats dashboard. It reads the match timeline (per-minute state + kill/objective/ward events), extracts the signals that explain why games are lost, and uses an LLM to deliver plain-English feedback like:
Overextending after kills — 3 of your 5 deaths came within 60s of getting a kill, all in the 10–15 min window. You're chasing tempo instead of resetting.
(Local, single-user tool — run against any Riot ID. See Running it.)
Most "stats" tools stop at end-of-game numbers (KDA, CS, gold) — which only ever produce generic advice: "your KDA is low, die less." The interesting signal lives in the timeline: when deaths happen, where on the map, and what preceded them. That's what lets the coach say why and when, not just what.
So the engineering focus isn't the API calls — it's the feature-extraction layer that turns thousands of raw timeline events into ~15 meaningful signals the LLM can reason over.
flowchart LR
A[Riot API<br/>match + timeline] --> B[Ingestion<br/>Python]
B --> C[(PostgreSQL<br/>normalized schema)]
C --> D[Feature extraction<br/>SQL → compact JSON]
D --> E[LLM coach<br/>Gemini, structured output]
E --> F[Coaching insights]
Two coaching modes, both fed by derived features (never raw timeline JSON):
| Mode | Input | Example output |
|---|---|---|
| Per-match | one match's timeline features | "Your deaths spike 10–15 min after kills." |
| Trend | aggregates over last N matches (GROUP BY) |
"This death-timing pattern shows up in 7 of your last 10 losses." |
- Relational schema design from scratch — normalized
players / matches / participants / participant_frames / timeline_events, foreign keys, and partial indexes tuned for the hot death-timing query. (My prior projects used Firestore; this was deliberate first-time relational modeling.) - Time-series feature engineering in SQL —
width_bucketdeath-window bucketing, self-joins for "deaths within 60s of a kill," lane-opponent gold diff at 10/15 min via correlated subqueries. - LLM integration — structured prompting + typed/validated output (no fragile text parsing), a frozen cacheable system prompt, and a feature layer that keeps token cost and noise down instead of dumping raw JSON at the model.
- Ingestion pipeline — rate-limited Riot API client, idempotent upserts, batch parsing of match + timeline payloads.
| Layer | Choice |
|---|---|
| Language | Python 3.11 |
| Database | PostgreSQL 16 (Docker) |
| DB driver | psycopg 3 (connection pool) |
| API client | httpx |
| LLM | Gemini API (structured outputs) |
| Data source | Riot Games API (account-v1, match-v5 + timeline) |
Why the timeline, not just end-game stats?
Summary stats can tell you a death count; only the timeline knows those deaths clustered at 12–14 min in the enemy jungle right after a kill. The whole product thesis is that timing and context are what make coaching non-generic, so the timeline endpoint is the backbone, not an add-on.
Why compute features in SQL instead of sending raw JSON to the LLM?
A single timeline is tens of thousands of tokens of mostly-noise. LLMs reason poorly (and expensively) over raw event dumps. So SQL does the measurement (~15 signals) and the model does the interpretation — the split that keeps the output sharp and the cost low.
Why store all 10 participants, not just the tracked player?
Comparisons ("gold diff vs your lane opponent at 15") are impossible without the opponent's frames. Storing the full lobby is what makes benchmark-relative features work.
Local, single-user tool. Needs a Riot API key and a Gemini API key.
# 1. Postgres
docker compose up -d
# 2. Install
python3 -m venv .venv && source .venv/bin/activate
pip install -e .
# 3. Configure — copy and fill in keys
cp .env.example .env # RIOT_API_KEY, GEMINI_API_KEY
# 4. Create the schema
python -m scripts.init_db
# 5. Analyze a player (Riot ID = gameName#tagLine)
python -m scripts.run_pipeline "Faker" "KR1" --count 10Get a Riot key at https://developer.riotgames.com (a Personal API Key — the Development key expires every 24h).
migrations/001_init.sql # normalized schema
src/game_analyzer/
riot/client.py # rate-limited Riot API client
riot/ingest.py # match + timeline → Postgres
features.py # timeline → compact feature dict ← the core
llm/prompt.py # frozen, cacheable coaching prompt
llm/coach.py # Gemini call, structured output
scripts/
init_db.py # apply migrations
run_pipeline.py # end-to-end demo
- Seed real role/rank benchmarks (currently a small reference table)
- Per-champion trend breakdowns
- Map-zone death classification (river / enemy jungle overextension)
- Web frontend with trend charts (Recharts) + per-match insight cards
- "Coach chat" — ask follow-up questions about a specific game
Personal, non-commercial learning project. Not affiliated with or endorsed by
Riot Games. Secrets live in .env (gitignored) — no keys are committed.
