Otter
B1Python package

Docs

Otter ships as a pip package. Load it, configure parameters, and query predictions — fully local, no server required.

# install
pip install otter-chess

Quickstart

Initialize the model, pass it a position (FEN), game history list, rating level, time control format, and remaining clock fraction to receive skill-conditioned and time-aware predictions.

from otter_chess import OtterModel

# Initializing OtterModel loads the cached model weights automatically
model = OtterModel(device="cpu")

result = model.predict(
    fen="r1bqkbnr/pppp1ppp/2n5/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 2 3",
    player_elo=1600,          # target active rating bracket
    opponent_elo=1500,        # target opponent rating bracket
    history_moves=["e2e4", "e7e5", "g1f3", "b8c6"], # preceding moves
    time_control="600+0",       # rapid base + increment format
    time_remaining=480         # clock time in seconds (fraction auto-calculated)
)

print(result["win_probability"])   # Win evaluation between -1 and +1
print(result["moves"][0]["move"])  # e.g., "f1b5" (Ruy Lopez)

Model card

Otter is a human chess AI model of 15.3 million parameters trained on 6.1 billion positions from 117 million rapid games on Lichess spanning club to titled play. Rather than optimizing for the strongest move, it's conditioned on a target rating band and predicts the move a player at that level would plausibly make — including known blind spots for that band. Full methodology is in the paper linked on the home page.

CLASSOtterModel(checkpoint_path=None, device="cpu", history_k=20, download_url=None)

Loads model configuration and initializes checkpoint parameters on the target hardware device.

checkpoint_path
str | None
Path to local model weights (.safetensors or .pt). If None, queries standard cache paths or downloads weights.
device
str
Execution device string, e.g. "cpu" or "cuda" (or other hardware backends).
history_k
int
Length limit of the preceding move history window context (default is 20 moves).
download_url
str | None
Fallback URL used to fetch model weights if they are not stored locally. Defaults to the model.safetensors release on Hugging Face.
METHODmodel.predict(fen, player_elo, opponent_elo, history_moves, time_control, clock_fraction, top_k)

Runs the model inference pipeline to output move distributions conditioned on skill and temporal pressure.

fen
str
FEN representation of the current board state.
player_elo
int
Elo rating bucket of the active player (ranges from below 1100 to above 2000).
opponent_elo
int
Elo rating bucket of the opponent.
history_moves
list[str] | None
List of all prior moves in the game represented as UCI strings (e.g. ["e2e4", "e7e5"]).
time_control
str
Time control format in base+increment seconds (e.g. "180+2" for Blitz, "600+0" for Rapid).
clock_fraction
float | None
Remaining clock time represented as a fraction between 0.0 and 1.0.
time_remaining
int | None
Time remaining in seconds. If provided, clock_fraction is calculated automatically using the base time control seconds.
top_k
int
Number of move suggestions to return (default is 5).

Returns

A dictionary containing:

  • "fen": The board FEN string analyzed.
  • "win_probability": The model's evaluation of the position between -1.0 (losing) and +1.0 (winning).
  • "moves": A list of predicted UCI moves and probabilities sorted from most likely to least likely.
  • "aux_predictions": Decoded properties from the auxiliary head (moving piece, captured piece, checks probability, from/to squares, and confidences).

WebGPU / WASM runtime

For browser-based or client-side JavaScript applications, you can execute the exported ONNX model directly on the client using onnxruntime-web. This allows for fully local execution using WebGPU (or WebAssembly as a fallback) without sending game positions to a server.

To load and run the model in your JavaScript code:

import * as ort from 'onnxruntime-web';

// Create an inference session with WebGPU acceleration
const session = await ort.InferenceSession.create('/path/to/policy_model.onnx', {
  executionProviders: ['webgpu', 'wasm']
});

// Feed active elo, opponent elo, board state, move history, etc.
const feeds = {
  board: new ort.Tensor('float32', boardData, [1, 18, 8, 8]),
  history_ids: new ort.Tensor('int64', historyIds, [1, 20]),
  active_elo: new ort.Tensor('int64', [activeEloBucket], [1]),
  ... // and other required inputs
};

const results = await session.run(feeds);

Input / output formats

Positions in, moves out — FEN for board state, UCI for moves (e.g. e2e4, e7e8q for promotion). Both APIs also accept and return PGN move lists if you'd rather work with full game history.