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:
arsam
2026-09-18 23:14:51 +03:30
parent 4f4e3396f4
commit d05c9fb6a0
+209
View File
@@ -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