Skip to content

Commit 7f4cbb4

Browse files
committed
Ref: Change get_agent and prompt rendering (needs to use tournament)
1 parent 16f8117 commit 7f4cbb4

13 files changed

Lines changed: 120 additions & 80 deletions

File tree

codeclash/agents/__init__.py

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,18 @@
1+
from minisweagent.environments.docker import DockerEnvironment
2+
13
from codeclash.agents.abstract import Player
24
from codeclash.agents.dummy import Dummy
35
from codeclash.agents.minisweagent import MiniSWEAgent
46
from codeclash.agents.utils import GameContext
5-
from codeclash.constants import DIR_WORK
6-
from codeclash.games.abstract import CodeGame
77

88

9-
def get_agent(config: dict, prompts: dict, game: CodeGame) -> Player:
9+
def get_agent(
10+
config: dict, game_context: GameContext, environment: DockerEnvironment
11+
) -> Player:
1012
agents = {
1113
"dummy": Dummy,
1214
"mini": MiniSWEAgent,
1315
}.get(config["agent"])
1416
if agents is None:
1517
raise ValueError(f"Unknown agent type: {config['agent']}")
16-
environment = game.get_environment(
17-
f"{game.game_id}.{config['name']}"
18-
) # NOTE: MUST be branch_name (defined in agents/abstract.py)
19-
return agents(
20-
config,
21-
environment,
22-
GameContext(
23-
id=game.game_id,
24-
log_env=game.log_env,
25-
log_local=game.log_local,
26-
name=game.name,
27-
player_id=config["name"],
28-
prompts=prompts,
29-
round=1,
30-
rounds=game.rounds,
31-
working_dir=str(DIR_WORK),
32-
),
33-
)
18+
return agents(config, environment, game_context)

codeclash/agents/abstract.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ def __init__(
2727
"""Unique ID that doesn't clash even accross multiple games. Used for git tags."""
2828
self.environment = environment
2929
self.game_context = game_context
30-
self.game_context.render_and_set_prompts()
3130
self.logger = get_logger(
3231
self.name,
3332
log_path=self.game_context.log_local / f"{self.name}.log",
@@ -47,7 +46,6 @@ def pre_run_hook(self, *, new_round: int) -> None:
4746
if new_round == 1:
4847
self._tag_round(0)
4948
self.game_context.round = new_round
50-
self.game_context.render_and_set_prompts()
5149

5250
def post_run_hook(self, *, round: int) -> None:
5351
"""Should be called after we called the run method."""

codeclash/agents/minisweagent.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88

99
import yaml
1010
from jinja2 import Template
11-
from minisweagent import Environment, Model
11+
from minisweagent import Model
1212
from minisweagent.agents.default import AgentConfig, DefaultAgent
13+
from minisweagent.environments.docker import DockerEnvironment
1314
from minisweagent.models.litellm_model import LitellmModel
1415
from minisweagent.run.utils.save import save_traj
1516
from rich.console import Console
@@ -28,7 +29,7 @@ class ClashAgent(DefaultAgent):
2829
def __init__(
2930
self,
3031
model: Model,
31-
env: Environment,
32+
env: DockerEnvironment,
3233
name: str,
3334
game_context: GameContext,
3435
*,
@@ -56,7 +57,7 @@ def render_template(self, template: str, **kwargs) -> str:
5657
| asdict(self.env.config)
5758
| asdict(self.model.config)
5859
| platform.uname()._asdict()
59-
| self.game_context.to_dict()
60+
| self.game_context.to_template_vars()
6061
)
6162
return Template(template).render(**kwargs, **cs, **os.environ)
6263

@@ -69,7 +70,7 @@ class MiniSWEAgent(Player):
6970
"""Player with agentic code editing capabilities"""
7071

7172
def __init__(
72-
self, config: dict, environment: Environment, game_context: GameContext
73+
self, config: dict, environment: DockerEnvironment, game_context: GameContext
7374
):
7475
super().__init__(config, environment=environment, game_context=game_context)
7576

codeclash/agents/utils.py

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -36,24 +36,15 @@ class GameContext:
3636
rounds: int
3737
working_dir: str
3838

39-
def render_and_set_prompts(self):
40-
"""Render and set prompts using the current game context."""
39+
def _render_prompt_templates(self) -> dict:
4140
context = asdict(self)
42-
del context["prompts"]
43-
for key, template_str in self.prompts.items():
44-
rendered = Template(template_str).render(**context)
45-
setattr(self, key, rendered)
46-
47-
def to_dict(self):
48-
"""Convert the GameContext to a dictionary, including dynamically added attributes."""
49-
result = asdict(self)
50-
declared = set(self.__dataclass_fields__)
51-
for attr in dir(self):
52-
if (
53-
not attr.startswith("_")
54-
and attr not in declared
55-
and not callable(getattr(self, attr))
56-
):
57-
result[attr] = getattr(self, attr)
58-
del result["prompts"]
59-
return result
41+
return {
42+
key: Template(template_str).render(**context)
43+
for key, template_str in self.prompts.items()
44+
}
45+
46+
def to_template_vars(self) -> dict[str, str]:
47+
"""Convert the GameContext to a dictionary for rendering prompts in the agent"""
48+
out = asdict(self) | self._render_prompt_templates()
49+
out.pop("prompts")
50+
return out

codeclash/games/abstract.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import os
33
import subprocess
44
from abc import ABC, abstractmethod
5-
from collections import Counter
65
from pathlib import Path
76

87
from minisweagent.environments.docker import DockerEnvironment
@@ -24,12 +23,15 @@ def __init__(self, config: dict, *, tournament_id: str, local_output_dir: Path):
2423
The central method is `run_round`, which takes a list of agents and returns the winner of the round.
2524
2625
At the end of the the tournament, run the `end` method to clean up the game and agents and write the metadata.
26+
27+
Args:
28+
config: The overall config for the tournament.
29+
tournament_id: The id of the tournament.
30+
local_output_dir: The host/local directory to write logs to.
2731
"""
2832
self.url_gh: str = f"git@github.com:{GH_ORG}/{self.name}.git"
2933
self.artifacts: list[Path] = []
3034
"""Artifact objects that we might want to clean up after the game."""
31-
self.scoreboard: list[tuple[int, str]] = []
32-
"""List of (round number, winner (player id))"""
3335
self.game_config: dict = config["game"]
3436
self.config: dict = config
3537
self.game_id: str = tournament_id
@@ -40,8 +42,6 @@ def __init__(self, config: dict, *, tournament_id: str, local_output_dir: Path):
4042
)
4143
self.environment: DockerEnvironment = self.get_environment()
4244
"""The running docker environment for executing the game"""
43-
# assert len(config["players"]) >= 2, "At least two players are required"
44-
"""Total number of rounds to play"""
4545
self._metadata: dict = {
4646
"name": self.name,
4747
"config": self.config,
@@ -92,7 +92,6 @@ def get_metadata(self) -> dict:
9292
return self._metadata
9393

9494
def end(self, cleanup: bool = False):
95-
self.logger.info("Overall score: %s", Counter([x[1] for x in self.scoreboard]))
9695
(self.log_local / "metadata.json").write_text(json.dumps(self.get_metadata()))
9796
if cleanup:
9897
for artifact in self.artifacts:

codeclash/games/battlecode/main.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import re
2+
import shlex
23
from pathlib import Path
34
from typing import Any
45

@@ -52,7 +53,7 @@ def execute_round(self, agents: list[Any]) -> dict[str, str]:
5253
f"--p{idx+1}-dir src --p{idx+1} {agent.name}"
5354
for idx, agent in enumerate(agents)
5455
]
55-
cmd = f"{self.run_cmd_round} {' '.join(args)}"
56+
cmd = f"{self.run_cmd_round} {shlex.join(args)}"
5657
self.logger.info(f"Running command: {cmd}")
5758
response = self.environment.execute(cmd)
5859
assert response["returncode"] == 0, response

codeclash/games/corewar/main.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import re
2+
import shlex
23
from pathlib import Path
34

45
from codeclash.agents.abstract import Player
@@ -52,7 +53,7 @@ def determine_winner(
5253

5354
def execute_round(self, agents: list[Player]) -> dict[str, str]:
5455
args = [f"/{agent.name}/warriors/warrior.red" for agent in agents]
55-
cmd = f"{self.run_cmd_round} {' '.join(args)}"
56+
cmd = f"{self.run_cmd_round} {shlex.join(args)}"
5657
self.logger.info(f"Running command: {cmd}")
5758
response = self.environment.execute(cmd)
5859
assert response["returncode"] == 0, response

codeclash/games/robotrumble/main.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import shlex
12
from pathlib import Path
23

34
from codeclash.agents.abstract import Player
@@ -42,7 +43,7 @@ def determine_winner(
4243

4344
def execute_round(self, agents: list[Player]) -> dict[str, str]:
4445
args = [f"/{agent.name}/robot.py" for agent in agents]
45-
cmd = f"{self.run_cmd_round} {' '.join(args)}"
46+
cmd = f"{self.run_cmd_round} {shlex.join(args)}"
4647
self.logger.info(f"Running command: {cmd}")
4748
response = self.environment.execute(cmd)
4849
assert response["returncode"] == 0, response

codeclash/tournaments/abstract.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
import traceback
44
from pathlib import Path
55

6-
from codeclash.constants import DIR_LOGS
6+
from codeclash.agents import get_agent
7+
from codeclash.agents.abstract import Player
8+
from codeclash.agents.utils import GameContext
9+
from codeclash.constants import DIR_LOGS, DIR_WORK
710
from codeclash.utils.environment import create_file_on_container
811
from codeclash.utils.log import get_logger
912

codeclash/tournaments/pvp_training.py

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
from codeclash.agents import get_agent
66
from codeclash.agents.abstract import Player
7+
from codeclash.agents.utils import GameContext
8+
from codeclash.constants import DIR_WORK
79
from codeclash.games import get_game
810
from codeclash.games.abstract import CodeGame
911
from codeclash.tournaments.abstract import AbstractTournament
@@ -24,13 +26,38 @@ def __init__(
2426
)
2527
self.agents: list[Player] = []
2628
for agent_conf in self.config["players"]:
27-
self.agents.append(get_agent(agent_conf, self.config["prompts"], self.game))
29+
self.agents.append(self.get_agent(agent_conf, self.config["prompts"]))
2830
self.logger = get_logger(self.game.name)
31+
self.scoreboard: list[tuple[int, str]] = []
32+
33+
@property
34+
def rounds(self) -> int:
35+
return self.config["game"]["rounds"]
36+
37+
def get_agent(self, agent_config: dict, prompts: dict) -> Player:
38+
"""Create an agent with environment and game context."""
39+
environment = self.game.get_environment(
40+
f"{self.game.game_id}.{agent_config['name']}"
41+
)
42+
43+
game_context = GameContext(
44+
id=self.game.game_id,
45+
log_env=self.game.log_env,
46+
log_local=self.game.log_local,
47+
name=self.game.name,
48+
player_id=agent_config["name"],
49+
prompts=prompts,
50+
round=1,
51+
rounds=self.rounds,
52+
working_dir=str(DIR_WORK),
53+
)
54+
55+
return get_agent(agent_config, game_context, environment)
2956

3057
def run(self) -> None:
3158
"""Main execution function that runs all rounds."""
3259
try:
33-
for round_num in range(1, self.game.rounds + 1):
60+
for round_num in range(1, self.rounds + 1):
3461
self.run_training_round(round_num)
3562
finally:
3663
self.cleanup()
@@ -44,7 +71,7 @@ def run_training_round(self, round_num: int) -> None:
4471
winner = result["winner"]
4572

4673
# Handle bookkeeping that was previously in the game
47-
self.game.scoreboard.append((round_num, winner))
74+
self.scoreboard.append((round_num, winner))
4875
self.logger.info(f"Round {round_num} winner: {winner}")
4976

5077
# Write log to file

0 commit comments

Comments
 (0)