Skip to content

Commit 98cb15b

Browse files
authored
Merge pull request #23 from AssassinWS/main
add the smoke test for chess environment
2 parents 86518d0 + f718e1e commit 98cb15b

3 files changed

Lines changed: 126 additions & 4 deletions

File tree

src/agentfly/rewards/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from .alfworld_reward import alfworld_episode_reward
2+
from .chess_reward import chess_puzzle_reward, chess_puzzle_reward_simple
23
from .code_reward import code_reward_test
34
from .gui_reward import gui_reward
45
from .math_reward import (
@@ -37,4 +38,6 @@
3738
"scienceworld_reward",
3839
"gui_reward",
3940
"code_reward_test",
41+
"chess_puzzle_reward",
42+
"chess_puzzle_reward_simple",
4043
]

src/agentfly/rewards/chess_reward.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515

1616
@reward(name="chess_puzzle_reward", env_cls=ChessPuzzleEnv, pool_size=8)
17-
async def chess_puzzle_reward(prediction: str, env: ChessPuzzleEnv) -> Dict[str, Any]:
17+
async def chess_puzzle_reward(final_response: str, env: ChessPuzzleEnv) -> Dict[str, Any]:
1818
"""
1919
Calculate reward for chess puzzle solving based on Stockfish evaluation.
2020
@@ -29,7 +29,7 @@ async def chess_puzzle_reward(prediction: str, env: ChessPuzzleEnv) -> Dict[str,
2929
- Making progress even with imperfect moves
3030
3131
Args:
32-
prediction (str): The agent's final response/output (not used directly).
32+
final_response (str): The agent's final response/output (not used directly).
3333
env (ChessPuzzleEnv): The chess puzzle environment instance.
3434
3535
Returns:
@@ -139,7 +139,7 @@ async def chess_puzzle_reward(prediction: str, env: ChessPuzzleEnv) -> Dict[str,
139139

140140
@reward(name="chess_puzzle_reward_simple", env_cls=ChessPuzzleEnv, pool_size=8)
141141
async def chess_puzzle_reward_simple(
142-
prediction: str, env: ChessPuzzleEnv
142+
final_response: str, env: ChessPuzzleEnv
143143
) -> Dict[str, Any]:
144144
"""
145145
Simple binary reward for chess puzzle solving.
@@ -149,7 +149,7 @@ async def chess_puzzle_reward_simple(
149149
where you only care about correct solutions.
150150
151151
Args:
152-
prediction (str): The agent's final response/output (not used).
152+
final_response (str): The agent's final response/output (not used).
153153
env (ChessPuzzleEnv): The chess puzzle environment instance.
154154
155155
Returns:
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# test_chess_smoke.py
2+
"""
3+
End-to-end smoke test for the chess puzzle agent pipeline.
4+
5+
Verifies that ReactAgent + chess tools + chess reward work together:
6+
- Agent receives a puzzle
7+
- Tools dispatch to ChessPuzzleEnv
8+
- Trajectories are populated after run()
9+
- Reward function produces a valid result
10+
11+
Requires: Stockfish installed (brew install stockfish / apt-get install stockfish)
12+
"""
13+
14+
import shutil
15+
from unittest.mock import AsyncMock
16+
17+
import pytest
18+
19+
from agentfly.agents import ReactAgent
20+
from agentfly.envs.chess_env import ChessPuzzleEnv
21+
from agentfly.rewards import chess_puzzle_reward
22+
from agentfly.tools import chess_get_legal_moves, chess_get_state, chess_move
23+
24+
# Skip if Stockfish is not available
25+
pytestmark = pytest.mark.skipif(
26+
shutil.which("stockfish") is None,
27+
reason="Stockfish not installed",
28+
)
29+
30+
# A simple mate-in-1 puzzle: White plays Qxf7#
31+
MATE_IN_1_PUZZLE = {
32+
"puzzle_id": "smoke_mate1",
33+
"fen": "r1bqkb1r/pppp1ppp/2n2n2/4p2Q/2B1P3/8/PPPP1PPP/RNB1K1NR w KQkq - 4 4",
34+
"moves": "h5f7",
35+
}
36+
37+
38+
def _make_react_responses():
39+
"""Return a sequence of canned ReAct responses for the smoke test.
40+
41+
Turn 0: agent checks the board state
42+
Turn 1: agent makes the winning move Qxf7#
43+
"""
44+
return [
45+
# Turn 0 – get state
46+
(
47+
'Thought: Let me look at the current board position first.\n'
48+
'Action: chess_get_state\n'
49+
'Input: {}'
50+
),
51+
# Turn 1 – make the winning move
52+
(
53+
'Thought: I see White can play Qxf7# for checkmate.\n'
54+
'Action: chess_move\n'
55+
'Input: {"move": "h5f7"}'
56+
),
57+
]
58+
59+
60+
@pytest.mark.asyncio
61+
async def test_chess_smoke_e2e():
62+
"""Smoke test: ReactAgent solves a mate-in-1 puzzle with chess tools."""
63+
64+
canned = _make_react_responses()
65+
call_idx = 0
66+
67+
async def fake_generate(messages_list, **kwargs):
68+
nonlocal call_idx
69+
idx = min(call_idx, len(canned) - 1)
70+
call_idx += 1
71+
return [canned[idx]]
72+
73+
tools = [chess_move, chess_get_state, chess_get_legal_moves]
74+
75+
agent = ReactAgent(
76+
model_name_or_path="Qwen/Qwen2.5-3B-Instruct",
77+
tools=tools,
78+
backend="client",
79+
reward_fn=chess_puzzle_reward,
80+
monitors=[],
81+
debug=True,
82+
)
83+
84+
# Replace LLM engine methods with mocks
85+
agent.llm_engine = AsyncMock()
86+
agent.llm_engine.generate_async = fake_generate
87+
agent.llm_engine.preprocess = lambda: None
88+
agent.llm_engine.postprocess = lambda: None
89+
90+
messages = [
91+
{
92+
"messages": [
93+
{
94+
"role": "user",
95+
"content": (
96+
"Solve this chess puzzle. The position is a mate-in-1. "
97+
"Find the winning move for White."
98+
),
99+
}
100+
],
101+
**MATE_IN_1_PUZZLE,
102+
}
103+
]
104+
105+
await agent.run(
106+
messages=messages,
107+
max_turns=2,
108+
num_chains=1,
109+
enable_streaming=False,
110+
)
111+
112+
# Trajectories should be populated
113+
trajectories = agent.trajectories
114+
assert len(trajectories) > 0, "Expected at least one trajectory"
115+
116+
# The trajectory should contain messages
117+
traj = trajectories[0]
118+
assert "messages" in traj
119+
assert len(traj["messages"]) > 1, "Expected multiple messages in trajectory"

0 commit comments

Comments
 (0)