VoltarProjeto Pessoal
Chess + AITraining Tool

PVCoach

Coach de xadrez que combina Stockfish com LLM para explicar movimentos. Análise MultiPV, hints progressivos, explicações fundamentadas nas variações do engine, e validação automática para evitar alucinações.

5
LLM Providers
MultiPV
Top-N Analysis
3
Hint Levels
Grounded
Explanations

! 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

FEN Position
Stockfish MultiPVdepth=14, multipv=3
1. e4+0.35
2. d4+0.20
3. Nf3+0.15
LLM (Grounded Prompt)Explicação baseada nas PVs
ValidationCross-check com engine

📊 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.

stockfish_engine.py
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.

Prompt Template
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

api_server.py
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).

api_server.py
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.

Google

Gemini 1.5 Flash, Pro — GenerativeAI.

OpenRouter

Proxy para múltiplos modelos — OpenAI-compatible.

llm_providers.py
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

POST/api/evaluate_position

Avaliação MultiPV pura (sem LLM). Retorna top-N candidatos com PVs e scores.

POST/api/explain_position

Avaliação + explicação LLM grounded. Inclui warnings de validação.

POST/api/hints_position

Hints progressivos (level 1-3). Level 3 pode incluir explicação.

POST/api/analyze

Baixa partidas do Lichess e analisa em batch.

Stack Técnico

PythonFastAPIStockfishpython-chessOpenAIAnthropicGoogle AIReactLichess API

📊 Resultados

0
Alucinações com validação ativa
5
Providers LLM suportados
Cache
TTL para avaliações e explicações
3
Níveis de hints progressivos

Explore Outros Projetos

Veja outros sistemas que construí

Case Study: PVCoach — Chess Training with AI