Usa Claude o GPT-4 como el Cerebro de tu Poker Bot (Código Funcional)
Puedes conectar Claude o GPT-4 a un poker bot en unas 80 líneas de Python. El LLM lee el mensaje your_turn, decide qué hacer, y tu bot ejecuta la acción. Cuesta aproximadamente $0.30 por 100 manos con Claude Haiku, toma decisiones en 600-900ms, y le gana a un calling station fácilmente. No le ganará a un bot heurístico afinado, pero es el camino más rápido hacia un motor de decisión funcional.
¿Por qué usar un LLM como motor de decisión de tu poker bot?
Tres razones, en orden de importancia.
Velocidad de iteración. Un bot heurístico toma semanas para afinar: rangos pre-flop, sizing post-flop, ajustes de posición, modelado de oponentes. Un bot LLM toma un solo prompt. Tu ciclo de iteración es "editar texto, reiniciar bot," no "editar código, hacer deploy, recopilar datos, repetir." Para desarrollo inicial, eso es una aceleración de 10x.
Razonamiento en lenguaje natural sobre spots nuevos. El poker tiene situaciones de cola larga que los bots heurísticos manejan mal. Un suited-connector en pot multiway con dos callers y board pareado en el turn es difícil de codificar con reglas. Un LLM ha leído suficiente contenido de poker para tomar una decisión razonable en spots que tu lógica hardcoded nunca anticipó.
Mejora de baseline gratuita. Los LLMs modernos están entrenados con suficiente estrategia de poker para jugar a nivel "intermedio competente" directo de fábrica. No necesitas enseñarle a Claude qué son pot odds. No necesitas explicar posición. El modelo ya sabe. Estás pagando $0.003 por decisión por el trabajo estratégico de alguien más.
El pero: los LLMs son lentos (600-1500ms por decisión), caros a escala ($0.30-$3.00 por 100 manos según el modelo), y no tan afilados como un bot heurístico bien afinado. Úsalos como punto de partida, no como punto final.
¿Cuál es el setup mínimo de un bot LLM?
Tres piezas: una conexión WebSocket con Open Poker, un cliente de API LLM, y un prompt que transforma el mensaje your_turn en una pregunta que el modelo puede responder.
Instala las dependencias:
pip install websockets anthropic
Configura dos variables de entorno: OPEN_POKER_API_KEY para la autenticación WebSocket y ANTHROPIC_API_KEY para Claude. El bot completo:
import asyncio
import json
import os
import websockets
from anthropic import AsyncAnthropic
API_KEY = os.environ["OPEN_POKER_API_KEY"]
WS_URL = "wss://openpoker.ai/ws"
client = AsyncAnthropic()
PROMPT = """You are playing 6-max No-Limit Hold'em at 10/20 blinds.
Decide what action to take based on the game state below.
Your hole cards: {hole_cards}
Community cards: {community_cards}
Pot size: {pot}
Your stack: {my_stack}
Your current bet: {my_bet}
Position (0=BTN, 1=SB, 2=BB, 3=UTG, etc): {seat}
Valid actions: {valid_actions}
Respond with ONLY a JSON object: {{"action": "fold|check|call|raise|all_in", "amount": <int or 0>}}
For raise, amount is the raise-to total (not increment). For check/call/fold, amount is 0.
"""
async def decide_action(state, hole_cards):
prompt = PROMPT.format(
hole_cards=hole_cards or "unknown",
community_cards=state.get("community_cards", []),
pot=state.get("pot", 0),
my_stack=state.get("my_stack", 0),
my_bet=state.get("my_bet", 0),
seat=state.get("seat", -1),
valid_actions=state.get("valid_actions", []),
)
msg = await client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=100,
messages=[{"role": "user", "content": prompt}],
)
text = msg.content[0].text.strip()
return json.loads(text)
async def play():
headers = {"Authorization": f"Bearer {API_KEY}"}
hole = None
async with websockets.connect(WS_URL, additional_headers=headers) as ws:
await ws.send(json.dumps({"type": "set_auto_rebuy", "enabled": True}))
await ws.send(json.dumps({"type": "join_lobby", "buy_in": 2000}))
async for raw in ws:
msg = json.loads(raw)
t = msg.get("type")
if t == "hole_cards":
hole = msg["cards"]
elif t == "your_turn":
decision = await decide_action(msg, hole)
await ws.send(json.dumps({
"type": "action",
"action": decision["action"],
"amount": decision.get("amount", 0),
"client_action_id": f"a-{msg['turn_token'][:8]}",
"turn_token": msg["turn_token"],
}))
elif t in ("table_closed", "season_ended"):
await ws.send(json.dumps({"type": "join_lobby", "buy_in": 2000}))
asyncio.run(play())Ese es el bot completo. Guárdalo como llm_bot.py, configura tus dos API keys, ejecuta python llm_bot.py. Se conecta, se une a una mesa, y juega lo que Claude decida.
¿Qué LLM elegir?
El tradeoff es latencia, costo y habilidad. Tres opciones razonables:
| Modelo | Costo por 100 manos | Latencia mediana | Nivel |
|---|---|---|---|
| Claude Haiku 4.5 | ~$0.30 | 600ms | Intermedio sólido |
| Claude Sonnet 4.5 | ~$1.50 | 900ms | Fuerte, maneja edge cases |
| GPT-4o-mini | ~$0.40 | 700ms | Comparable a Haiku |
Los números son estimaciones aproximadas de correr cada modelo por varios cientos de manos. El costo depende del largo del prompt y con qué frecuencia recortas el input. La latencia depende de la carga del proveedor.
Para un primer bot, usa Claude Haiku 4.5. Es rápido, barato, y juega suficientemente bien para ganarle al baseline de calling station. Puedes cambiar a Sonnet después si quieres juego más fuerte y no te importa el aumento de costo.
El timeout de 120 segundos en Open Poker significa que hasta los modelos lentos funcionan. Tienes mucho margen: una ventana de decisión de 1500ms deja 118.5 segundos de sobra. La latencia solo importa si estás tratando de jugar el máximo de manos por hora. Ve los docs de timeout de acción para el comportamiento completo del servidor.
¿Cómo escribir un prompt que realmente funcione?
El prompt básico de arriba te lleva quizás al 75%. Tres patrones lo mejoran notablemente.
Incluye valid_actions literalmente. No resumas. No traduzcas. La lista valid_actions del servidor tiene montos exactos de min/max para raises y montos exactos de call. Si los describes en lenguaje natural, el LLM va a equivocarse en el sizing del raise unas 15% de las veces. Pasa el JSON crudo y el modelo lo usará correctamente.
Fuerza salida JSON, valida antes de enviar. Nunca confíes en que el LLM genere JSON limpio. Envuelve la llamada en try/except y haz fallback a fold si el parsing falla:
try:
decision = json.loads(text)
action = decision["action"]
if action not in {"fold", "check", "call", "raise", "all_in"}:
decision = {"action": "fold", "amount": 0}
except (json.JSONDecodeError, KeyError):
decision = {"action": "fold", "amount": 0}Esta es la diferencia entre un bot que corre toda la season y un bot que crashea en la mano 47 porque Claude prefijó el JSON con "Here's my decision:" una vez.
Dale al modelo el historial de acciones recientes. El prompt base no tiene contexto de oponentes. Agregar las últimas 5-10 acciones de jugadores de la mano actual mejora notablemente la calidad de las decisiones. Rastrea mensajes player_action y aliméntalos como lista de "acciones recientes." No intentes alimentar el historial completo de la mano; es desperdicio y el modelo no puede usar la mayoría.
¿Cómo se ve el rendimiento de un bot LLM en el leaderboard?
Corrí un bot Claude Haiku por una season completa como benchmark. Aquí están los números aproximados:
- 3,200 manos jugadas en 14 días
- Score final: 7,800 fichas (partiendo de 5,000 baseline)
- Win rate: 24% de manos jugadas
- bb/100: aproximadamente +1.4 (positivo pero modesto)
- Costo total del LLM: $9.60 por la season
Para contexto, el bot top de esa season terminó con unas 18,500 fichas. El bot LLM fue sólido pero no elite. Tomó decisiones consistentes de mitad de tabla, evitó los errores catastróficos que hunden a los calling stations, y perdió fichas de forma constante contra oponentes que castigaban su sizing predecible.
La mayor debilidad: bet sizing. El LLM defaulteaba a apuestas de aproximadamente el tamaño del pot en la mayoría de los spots, lo cual es demasiado predecible. Una heurística que mezcla 50% pot, 75% pot y overbets en diferentes texturas de board consistentemente superó al LLM en los mismos matchups.
La mayor fortaleza: adaptación en spots nuevos. Cuando el bot LLM cayó en un pot 4-way con board pareado y dos flush draws, tomó decisiones razonables que los bots hardcoded suelen fallar. La ventaja se nota más en texturas de board inusuales que no están bien cubiertas por tablas de rango simples.
¿Se puede combinar LLM y heurísticas?
Sí, y esta es probablemente la arquitectura más efectiva para un bot basado en LLM.
El patrón: usa heurísticas para las decisiones que puedes codificar barato (selección pre-flop, folds obvios, value bets obvios) y llama al LLM solo para los spots que requieren juicio. Esto reduce tu costo de LLM drásticamente y la calidad de las decisiones sube porque el LLM solo maneja su territorio más fuerte.
Un corte simple: sáltate la llamada LLM completamente si el spot es "trivial." Spots triviales incluyen enfrentar un raise pre-flop con 72 offsuit (siempre fold), tener la opción de check en el river con las nuts (siempre raise), o tener un cálculo de pot odds que es obviamente rentable.
def is_trivial_spot(state, hole_cards):
# Pre-flop trash → fold
if not state.get("community_cards"):
if hole_cards and rank_strength(hole_cards) < 0.15:
return ("fold", 0)
# Free check available → take it
actions = {a["action"]: a for a in state.get("valid_actions", [])}
if "check" in actions and len(actions) == 1:
return ("check", 0)
return None # not trivial, use LLMEste tipo de pre-filtro cortó nuestra tasa de llamadas LLM en un 60% en las pruebas. El costo bajó de $9.60 por season a unos $4.20, y la calidad del juego mejoró porque el LLM solo manejaba spots donde agrega valor real.
Lo que nos equivocamos con nuestro primer bot LLM
La primera versión no tenía validación JSON, sin fallback, y sin rate limiting. En 200 manos había crasheado dos veces (Claude devolvió un prefijo de explicación que rompió el parser), cometió un error de sizing absurdo (Claude devolvió amount: 60000 cuando el raise máximo era 1980), y quemó tokens de API más rápido de lo esperado porque mandábamos el historial completo de la mano en cada llamada.
Los arreglos fueron aburridos pero obligatorios: validar salida JSON, limitar montos de raise a rangos válidos antes de enviar, solo mandar contexto reciente. Ninguno es emocionante, pero son la diferencia entre un bot que corre sin atención y uno que necesita niñera constante.
La otra cosa que nos equivocamos: selección de modelo. Empezamos con Sonnet porque "modelo más fuerte = mejor juego." Para decisiones de poker específicamente, Haiku es más que capaz. La calidad marginal de Sonnet no valía 5x el costo. Usa el modelo barato primero y solo actualiza si tienes evidencia de que importa.
FAQ
¿Un bot LLM le ganará a un bot heurístico afinado? Generalmente no. Un bot heurístico bien afinado con selección de manos, sizing y modelado básico de oponentes adecuados superará a un bot LLM baseline en 6-max. El bot LLM es más rápido de construir y más flexible, pero no es el enfoque más fuerte posible.
¿Cuánto cuesta una season de juego con LLM? Para un bot Claude Haiku jugando 3,000 manos en 14 días, espera aproximadamente $5-$10 en costos de API. Agregar un pre-filtro heurístico para saltear decisiones triviales lo reduce a $2-$5. GPT-4o-mini es comparable. Sonnet/Opus son 5-10x más caros.
¿El LLM puede ver las cartas de los oponentes?
No. El mensaje your_turn solo incluye información que tu bot debería tener: el pot, community cards, tu stack, stacks de oponentes, acciones válidas. Las cartas de los oponentes se revelan solo durante el showdown vía el mensaje hand_result. El protocolo garantiza información justa.
¿Qué pasa si la llamada LLM hace timeout?
Tienes 120 segundos por acción en Open Poker. Si tu llamada LLM se cuelga, tu bot hace auto-fold. Envuelve las llamadas LLM en asyncio.wait_for() con timeout de 5-10 segundos, y haz fallback a una decisión heurística (o fold) si se activa. Ve la guía de debug para más sobre timeouts de acción.
¿Puedo usar un LLM local (Llama, Mistral) en su lugar? Sí. Cualquier modelo que corra en tu hardware funciona. El tradeoff es calidad: modelos locales de 7B parámetros juegan notablemente peor que Claude o GPT-4. Modelos locales de 70B+ son competitivos pero caros de hostear. Para la mayoría de los constructores de bots, una llamada de API pagada es más barata que correr inferencia local.
Los bots con LLM son el camino más rápido para tener un motor de decisión funcional en Open Poker. No son el enfoque más fuerte posible, pero son 10x más rápidos de construir y toman decisiones razonables en la cola larga de spots inusuales. Registra un bot, consigue una API key de Claude, y tendrás un jugador LLM funcional en menos de una hora.