Skip to content

Commit 16bcc64

Browse files
committed
feat(strategy): add strategy manager class
1 parent fbd5700 commit 16bcc64

2 files changed

Lines changed: 120 additions & 0 deletions

File tree

src/balatrollm/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
__version__ = "1.0.0"
44

55
from .client import BalatroClient, BalatroError
6+
from .strategy import StrategyManager
67

78
__all__ = [
89
"BalatroClient",
910
"BalatroError",
11+
"StrategyManager",
1012
]

src/balatrollm/strategy.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
"""Strategy template management for BalatroLLM."""
2+
3+
import json
4+
from pathlib import Path
5+
from typing import Any
6+
7+
from jinja2 import Environment, FileSystemLoader
8+
9+
STRATEGIES_DIR = Path(__file__).parent / "strategies"
10+
11+
12+
class StrategyManager:
13+
"""Manages Jinja2 strategy templates for LLM prompts.
14+
15+
A strategy consists of four files:
16+
- STRATEGY.md.jinja: High-level strategic guidance
17+
- GAMESTATE.md.jinja: Current game state rendering
18+
- MEMORY.md.jinja: Action history and error context
19+
- TOOLS.json: Tool definitions for each game state
20+
21+
Usage:
22+
sm = StrategyManager("default")
23+
strategy_text = sm.render_strategy(gamestate)
24+
gamestate_text = sm.render_gamestate(gamestate)
25+
memory_text = sm.render_memory(history)
26+
tools = sm.get_tools("SELECTING_HAND")
27+
"""
28+
29+
def __init__(self, name: str, strategies_dir: Path = STRATEGIES_DIR):
30+
"""Initialize the strategy manager.
31+
32+
Args:
33+
name: Name of the strategy (subdirectory in strategies_dir)
34+
strategies_dir: Base directory containing strategy folders
35+
36+
Raises:
37+
FileNotFoundError: If strategy directory or required files don't exist
38+
"""
39+
self.path = strategies_dir / name
40+
41+
if not self.path.exists():
42+
raise FileNotFoundError(f"Strategy not found: {name}")
43+
44+
# Verify required files exist
45+
required = [
46+
"STRATEGY.md.jinja",
47+
"GAMESTATE.md.jinja",
48+
"MEMORY.md.jinja",
49+
"TOOLS.json",
50+
]
51+
missing = [f for f in required if not (self.path / f).exists()]
52+
if missing:
53+
raise FileNotFoundError(f"Strategy '{name}' missing files: {missing}")
54+
55+
self.env = Environment(loader=FileSystemLoader(self.path))
56+
self.env.filters["from_json"] = json.loads
57+
58+
# Load tools
59+
with open(self.path / "TOOLS.json") as f:
60+
self._tools = json.load(f)
61+
62+
def render_strategy(self, gamestate: dict[str, Any]) -> str:
63+
"""Render the strategy guidance template.
64+
65+
Args:
66+
gamestate: Current game state from BalatroBot
67+
68+
Returns:
69+
Rendered strategy guidance text
70+
"""
71+
template = self.env.get_template("STRATEGY.md.jinja")
72+
return template.render(G=gamestate)
73+
74+
def render_gamestate(self, gamestate: dict[str, Any]) -> str:
75+
"""Render the game state template.
76+
77+
Args:
78+
gamestate: Current game state from BalatroBot
79+
80+
Returns:
81+
Rendered game state text for LLM context
82+
"""
83+
template = self.env.get_template("GAMESTATE.md.jinja")
84+
return template.render(G=gamestate)
85+
86+
def render_memory(
87+
self,
88+
history: list[dict[str, Any]],
89+
last_error: str | None = None,
90+
last_failure: str | None = None,
91+
) -> str:
92+
"""Render the memory/history template.
93+
94+
Args:
95+
history: List of previous actions with method, params, reasoning
96+
last_error: Error message from last invalid LLM response
97+
last_failure: Error message from last failed API call
98+
99+
Returns:
100+
Rendered memory context text
101+
"""
102+
template = self.env.get_template("MEMORY.md.jinja")
103+
return template.render(
104+
history=history,
105+
last_error_call_msg=last_error,
106+
last_failed_call_msg=last_failure,
107+
)
108+
109+
def get_tools(self, state: str) -> list[dict[str, Any]]:
110+
"""Get tool definitions for a game state.
111+
112+
Args:
113+
state: Game state string (e.g., "SELECTING_HAND", "SHOP")
114+
115+
Returns:
116+
List of OpenAI function calling tool definitions
117+
"""
118+
return self._tools.get(state, [])

0 commit comments

Comments
 (0)