Import tests: Shayan1/Prod/tests/test_llm_player
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch
|
||||
from nash.game import Game
|
||||
from nash.players import LLMPlayer, make_player, PLAYER_KINDS
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_anthropic():
|
||||
with patch("anthropic.Anthropic") as mock:
|
||||
yield mock
|
||||
|
||||
|
||||
def test_llm_player_initialization(mock_anthropic):
|
||||
game = Game(
|
||||
players=["P1", "P2"],
|
||||
strategies=[["Cooperate", "Defect"], ["Cooperate", "Defect"]],
|
||||
payoffs=[
|
||||
[[2.0, 0.0], [3.0, 1.0]],
|
||||
[[2.0, 3.0], [0.0, 1.0]]
|
||||
]
|
||||
)
|
||||
# Test PLAYER_KINDS
|
||||
assert "llm" in PLAYER_KINDS
|
||||
|
||||
# Test make_player
|
||||
player = make_player("llm", game, player_index=0, model="my-test-model")
|
||||
assert isinstance(player, LLMPlayer)
|
||||
assert player.game == game
|
||||
assert player.player_index == 0
|
||||
assert player.model == "my-test-model"
|
||||
mock_anthropic.assert_called_once()
|
||||
|
||||
|
||||
def test_llm_player_choose_clean_json(mock_anthropic):
|
||||
game = Game(
|
||||
players=["P1", "P2"],
|
||||
strategies=[["Cooperate", "Defect"], ["Cooperate", "Defect"]],
|
||||
payoffs=[
|
||||
[[2.0, 0.0], [3.0, 1.0]],
|
||||
[[2.0, 3.0], [0.0, 1.0]]
|
||||
]
|
||||
)
|
||||
logs = []
|
||||
|
||||
def debug_logger(player_index, message):
|
||||
logs.append((player_index, message))
|
||||
|
||||
player = make_player("llm", game, player_index=0, debug_logger=debug_logger)
|
||||
|
||||
mock_client = mock_anthropic.return_value
|
||||
mock_response = MagicMock()
|
||||
mock_content = MagicMock()
|
||||
mock_content.text = '{"reasoning": "I want to cooperate to maximize payoff.", "strategy_index": 0}'
|
||||
mock_response.content = [mock_content]
|
||||
mock_client.messages.create.return_value = mock_response
|
||||
|
||||
# Choose action with history
|
||||
history = [(0, 1), (1, 0)]
|
||||
action = player.choose(history)
|
||||
|
||||
# Assert return value is strategy_index
|
||||
assert action == 0
|
||||
|
||||
# Assert logs captured reasoning
|
||||
assert len(logs) == 1
|
||||
assert logs[0] == (0, "I want to cooperate to maximize payoff.")
|
||||
|
||||
# Check that client.messages.create was called
|
||||
mock_client.messages.create.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("model_output,expected_idx,expected_reasoning", [
|
||||
(
|
||||
'```json\n{"reasoning": "Markdown fence with json key", "strategy_index": 1}\n```',
|
||||
1,
|
||||
"Markdown fence with json key"
|
||||
),
|
||||
(
|
||||
'```\n{"reasoning": "Markdown fence without json key", "strategy_index": 0}\n```',
|
||||
0,
|
||||
"Markdown fence without json key"
|
||||
),
|
||||
(
|
||||
'Here is the response:\n{"reasoning": "Surrounding text", "strategy_index": 1}\nHope this helps!',
|
||||
1,
|
||||
"Surrounding text"
|
||||
),
|
||||
(
|
||||
'Some intro...\n```json\n{"reasoning": "Intro and fence and outro", "strategy_index": 0}\n```\nSome outro...',
|
||||
0,
|
||||
"Intro and fence and outro"
|
||||
),
|
||||
])
|
||||
def test_llm_player_resilient_json_parsing(mock_anthropic, model_output, expected_idx, expected_reasoning):
|
||||
game = Game(
|
||||
players=["P1", "P2"],
|
||||
strategies=[["Cooperate", "Defect"], ["Cooperate", "Defect"]],
|
||||
payoffs=[
|
||||
[[2.0, 0.0], [3.0, 1.0]],
|
||||
[[2.0, 3.0], [0.0, 1.0]]
|
||||
]
|
||||
)
|
||||
player = LLMPlayer(game, player_index=0)
|
||||
|
||||
mock_client = mock_anthropic.return_value
|
||||
mock_response = MagicMock()
|
||||
mock_content = MagicMock()
|
||||
mock_content.text = model_output
|
||||
mock_response.content = [mock_content]
|
||||
mock_client.messages.create.return_value = mock_response
|
||||
|
||||
action = player.choose([])
|
||||
assert action == expected_idx
|
||||
|
||||
|
||||
def test_llm_player_invalid_json_raises(mock_anthropic):
|
||||
game = Game(
|
||||
players=["P1", "P2"],
|
||||
strategies=[["Cooperate", "Defect"], ["Cooperate", "Defect"]],
|
||||
payoffs=[
|
||||
[[2.0, 0.0], [3.0, 1.0]],
|
||||
[[2.0, 3.0], [0.0, 1.0]]
|
||||
]
|
||||
)
|
||||
player = LLMPlayer(game, player_index=0)
|
||||
|
||||
mock_client = mock_anthropic.return_value
|
||||
mock_response = MagicMock()
|
||||
mock_content = MagicMock()
|
||||
mock_content.text = "This is just text with no json braces at all."
|
||||
mock_response.content = [mock_content]
|
||||
mock_client.messages.create.return_value = mock_response
|
||||
|
||||
with pytest.raises(ValueError, match="No JSON object found in model response"):
|
||||
player.choose([])
|
||||
|
||||
|
||||
def test_llm_player_prompt_contents(mock_anthropic):
|
||||
game = Game(
|
||||
players=["P1", "P2"],
|
||||
strategies=[["Cooperate", "Defect"], ["Cooperate", "Defect"]],
|
||||
payoffs=[
|
||||
[[2.0, 0.0], [3.0, 1.0]],
|
||||
[[2.0, 3.0], [0.0, 1.0]]
|
||||
]
|
||||
)
|
||||
player = LLMPlayer(game, player_index=0)
|
||||
|
||||
mock_client = mock_anthropic.return_value
|
||||
mock_response = MagicMock()
|
||||
mock_content = MagicMock()
|
||||
mock_content.text = '{"reasoning": "strategic choice", "strategy_index": 1}'
|
||||
mock_response.content = [mock_content]
|
||||
mock_client.messages.create.return_value = mock_response
|
||||
|
||||
history = [(0, 1), (1, 0)]
|
||||
player.choose(history)
|
||||
|
||||
# Retrieve the arguments passed to create
|
||||
mock_client.messages.create.assert_called_once()
|
||||
kwargs = mock_client.messages.create.call_args[1]
|
||||
|
||||
# Check messages structure
|
||||
assert "messages" in kwargs
|
||||
messages = kwargs["messages"]
|
||||
assert len(messages) == 1
|
||||
prompt = messages[0]["content"]
|
||||
|
||||
# 1. Player name
|
||||
assert "Player 'P1'" in prompt
|
||||
|
||||
# 2. Strategy options
|
||||
assert "Cooperate" in prompt
|
||||
assert "Defect" in prompt
|
||||
|
||||
# 3. Payoff matrix formatting
|
||||
# Profile: (Cooperate | Cooperate) -> Payoffs: (2.00, 2.00)
|
||||
assert "Profile: (Cooperate | Cooperate) -> Payoffs: (2.00, 2.00)" in prompt
|
||||
assert "Profile: (Defect | Cooperate) -> Payoffs: (3.00, 0.00)" in prompt
|
||||
|
||||
# 4. History of recent rounds
|
||||
# Round 1: P1: Cooperate, P2: Defect
|
||||
assert "Round 1: P1: Cooperate, P2: Defect" in prompt
|
||||
assert "Round 2: P1: Defect, P2: Cooperate" in prompt
|
||||
|
||||
|
||||
def test_llm_player_empty_history(mock_anthropic):
|
||||
game = Game(
|
||||
players=["P1", "P2"],
|
||||
strategies=[["Cooperate", "Defect"], ["Cooperate", "Defect"]],
|
||||
payoffs=[
|
||||
[[2.0, 0.0], [3.0, 1.0]],
|
||||
[[2.0, 3.0], [0.0, 1.0]]
|
||||
]
|
||||
)
|
||||
player = LLMPlayer(game, player_index=0)
|
||||
|
||||
mock_client = mock_anthropic.return_value
|
||||
mock_response = MagicMock()
|
||||
mock_content = MagicMock()
|
||||
mock_content.text = '{"reasoning": "no history", "strategy_index": 0}'
|
||||
mock_response.content = [mock_content]
|
||||
mock_client.messages.create.return_value = mock_response
|
||||
|
||||
player.choose([])
|
||||
|
||||
kwargs = mock_client.messages.create.call_args[1]
|
||||
prompt = kwargs["messages"][0]["content"]
|
||||
assert "No rounds played yet." in prompt
|
||||
Reference in New Issue
Block a user