! O Problema
Engines de xadrez como Stockfish são extremamente fortes, mas suas "explicações" são apenas números (centipawns) e variações brutas. Jogadores intermediários não conseguem entender por que um lance é melhor.
O Gap de Entendimento
- ✗Engine diz: "e4 +0.35, d4 +0.20" — Ok, mas por quê?
- ✗LLM puro alucina: "Nf3 ataca a dama" — Não, não ataca.
- ✗Análises humanas são caras: Coaches cobram $50-100/hora.
A solução foi combinar a precisão do Stockfish com a capacidade explicativa do LLM, mas ancorando as explicações nas variações reais do engine para evitar alucinações.
🏗️ Arquitetura
Pipeline de Análise
📊 MultiPV Analysis
Em vez de pedir só o "melhor lance", o sistema pede os top-N lances com suas variações completas. Isso permite comparar alternativas e explicar por que um lance é melhor que outro.
def evaluate_position_multipv(board: chess.Board, depth: int = 20, multipv: int = 3, pv_len: int = 6):
"""
Avalia posição com MultiPV.
Returns: [{
rank: 1,
move_uci: "e2e4",
move_san: "e4",
pv_san: ["e4", "e5", "Nf3", "Nc6", "Bb5", "a6"],
cp: 35, # centipawns
mate: null, # ou número de lances até mate
delta_cp: 0 # diferença para o melhor
}]
"""
engine = chess.engine.SimpleEngine.popen_uci(STOCKFISH_PATH)
info_list = engine.analyse(board, chess.engine.Limit(depth=depth), multipv=multipv)
candidates = []
for idx, info in enumerate(info_list):
pv = info.get('pv', [])
first_move = pv[0]
pov = info['score'].pov(board.turn)
candidates.append({
'rank': idx + 1,
'move_san': board.san(first_move),
'pv_san': _format_pv_san(board, pv, pv_len),
'cp': pov.score(),
'mate': pov.mate() if pov.is_mate() else None,
})
# Calcula delta vs melhor lance
best_cp = candidates[0]['cp']
for c in candidates:
c['delta_cp'] = best_cp - c['cp'] if c['cp'] else None
return candidates💡 delta_cp
O delta_cp mostra quanto cada alternativa "perde" em relação ao melhor lance.
Ex: se e4 = +35cp e d4 = +20cp, então delta_cp de d4 = 15cp (perde 0.15 peões).
📝 Grounded Explanations
O segredo para evitar alucinações é ancorar o LLM nas variações reais do engine. O prompt inclui as PVs formatadas, e a validação verifica se a explicação menciona lances que realmente existem.
header = """
Você é um treinador de xadrez. Explique para um jogador {level}
EM PORTUGUÊS DO BRASIL, de forma clara e concisa, por que o
melhor lance é melhor que as outras opções.
Regras:
(1) SEMPRE cite lances específicos das PVs
(2) Foque em consequências concretas nas próximas 2-3 jogadas
(3) NÃO faça afirmações sem citar uma PV
(4) Use frases curtas e bullets quando fizer sentido
"""
# Formata candidatos para o prompt
body = """
Top candidates for this position:
1. e4 → +0.35 pawns → PV: e4 e5 Nf3 Nc6 Bb5 a6
2. d4 → +0.20 pawns (loses 0.15 pawns) → PV: d4 d5 c4 e6 Nc3 Nf6
3. Nf3 → +0.15 pawns (loses 0.20 pawns) → PV: Nf3 d5 d4 Nf6 c4 e6
"""Validação Automática
def validate_explanation(explanation: str, candidates: List[dict]) -> List[str]:
"""Valida se a explicação menciona lances corretos."""
warnings = []
# Extrai todos os lances das PVs
pv_moves = set()
for c in candidates:
for san in c.get('pv_san', [])[:10]:
pv_moves.add(san)
# Extrai lances mencionados na explicação
mentioned = extract_san_moves(explanation)
# Verifica se lances mencionados existem nas PVs
for mv in mentioned:
if mv not in pv_moves:
warnings.append(f"Move '{mv}' is not present in PVs.")
# Garante que o melhor lance foi mencionado
top_move = candidates[0].get('move_san')
if top_move and top_move not in explanation:
warnings.append(f"Top move '{top_move}' not referenced.")
return warnings🛡️ Resultado
Se o LLM menciona um lance que não existe nas PVs, o sistema retorna um warning. Isso permite detectar alucinações antes de mostrar ao usuário.
💡 Progressive Hints
Para treino, revelar o lance imediatamente não ajuda. O sistema oferece dicas progressivas em 3 níveis, do mais vago ao mais específico.
Level 1: Pista
"Pense em mover seu cavalo."
Indica a peça, não a casa. Se há captura ou xeque, adiciona nota.
Level 2: Plano
"Melhor lance: Nf3. Compare com d4, Nc3."
Revela o lance + alternativas + delta_cp.
Level 3: Variante
"Nf3 → d5 d4 Nf6 c4 e6"
PV completa + explicação LLM (opcional).
def generate_progressive_hints_for_fen(fen: str, candidates: List[dict], level: int):
best = candidates[0]
piece_name = _piece_name_from_uci(fen, best['move_uci'])
san = best['move_san']
pv = best['pv_san']
if level == 1:
# Pista: foco na peça, sem revelar a casa
hint = f"Think about moving your {piece_name}."
if 'x' in san:
hint += " It may involve a capture."
if '+' in san:
hint += " It creates a threat."
elif level == 2:
# Plano: revela o melhor lance + alternativas
hint = {
'best_move': san,
'alternatives': [c['move_san'] for c in candidates[1:3]],
'delta_cp': candidates[1].get('delta_cp'),
'message': "Why is this more effective? Consider king safety."
}
else:
# Variante completa
hint = {
'best_move': san,
'pv_san': pv,
'message': "This line keeps evaluation."
}
return hint🔌 Multi-Provider LLM
O sistema suporta 5 providers de LLM, permitindo escolher por custo, velocidade, ou preferência. Configurável via env vars ou per-request.
OpenAI
GPT-4, GPT-3.5 — Chat Completions API.
Anthropic
Claude 3.5 Sonnet, Haiku — Messages API.
Gemini 1.5 Flash, Pro — GenerativeAI.
OpenRouter
Proxy para múltiplos modelos — OpenAI-compatible.
def generate_completion_simple(prompt: str, provider: str = None, model: str = None):
"""Generate text using selected provider."""
provider_name = (provider or os.getenv('LLM_PROVIDER') or 'openai').lower()
if provider_name == 'openai':
client = OpenAI()
return client.chat.completions.create(
model=model or os.getenv('OPENAI_MODEL'),
messages=[{"role": "system", "content": prompt}]
).choices[0].message.content
if provider_name == 'anthropic':
client = Anthropic()
return client.messages.create(
model=model or os.getenv('ANTHROPIC_MODEL'),
messages=[{"role": "user", "content": prompt}]
).content[0].text
if provider_name == 'google':
genai.configure(api_key=os.getenv('GOOGLE_API_KEY'))
model_obj = genai.GenerativeModel(model or 'gemini-1.5-flash')
return model_obj.generate_content(prompt).text
# ... openrouter, glm🔗 API Endpoints
Avaliação MultiPV pura (sem LLM). Retorna top-N candidatos com PVs e scores.
Avaliação + explicação LLM grounded. Inclui warnings de validação.
Hints progressivos (level 1-3). Level 3 pode incluir explicação.
Baixa partidas do Lichess e analisa em batch.
Stack Técnico
📊 Resultados
Explore Outros Projetos
Veja outros sistemas que construí