econagents 0.0.3__py3-none-any.whl → 0.0.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
econagents/__init__.py CHANGED
@@ -12,7 +12,7 @@ from econagents.core.state.game import GameState, MetaInformation, PrivateInform
12
12
  from econagents.llm.openai import ChatOpenAI
13
13
 
14
14
  # Don't manually change, let poetry-dynamic-versioning handle it.
15
- __version__ = "0.0.3"
15
+ __version__ = "0.0.7"
16
16
 
17
17
  __all__: list[str] = [
18
18
  "AgentRole",
econagents/cli.py ADDED
@@ -0,0 +1,136 @@
1
+ import argparse
2
+ import asyncio
3
+ import sys
4
+ import json
5
+ from pathlib import Path
6
+ from typing import Dict, List, Any
7
+
8
+ from econagents.config_parser.base import BaseConfigParser
9
+
10
+
11
+ async def async_main(args: argparse.Namespace):
12
+ """Asynchronous main function to run the experiment."""
13
+ config_path = Path(args.config_path).resolve()
14
+ login_payloads_path = Path(args.login_payloads_file).resolve()
15
+
16
+ if not config_path.is_file():
17
+ print(f"Error: Configuration file not found at {config_path}", file=sys.stderr)
18
+ sys.exit(1)
19
+
20
+ if not login_payloads_path.is_file():
21
+ print(f"Error: Login payloads file not found at {login_payloads_path}", file=sys.stderr)
22
+ sys.exit(1)
23
+
24
+ try:
25
+ # Load configuration using the base parser
26
+ parser = BaseConfigParser(config_path)
27
+ config = parser.config
28
+ except Exception as e:
29
+ print(f"Error loading or parsing config file {config_path}: {e}", file=sys.stderr)
30
+ sys.exit(1)
31
+
32
+ login_payloads: List[Dict[str, Any]] = [] # Initialize empty list
33
+ try:
34
+ with open(login_payloads_path, "r") as f:
35
+ for line_num, line in enumerate(f, 1):
36
+ line = line.strip() # Remove leading/trailing whitespace
37
+ if not line: # Skip empty lines
38
+ continue
39
+ try:
40
+ payload = json.loads(line)
41
+ if not isinstance(payload, dict):
42
+ raise ValueError("Each line must be a valid JSON object.")
43
+ login_payloads.append(payload)
44
+ except json.JSONDecodeError as e:
45
+ print(f"Error decoding JSON on line {line_num} in {login_payloads_path}: {e}", file=sys.stderr)
46
+ sys.exit(1)
47
+ except ValueError as e:
48
+ print(f"Error processing line {line_num} in {login_payloads_path}: {e}", file=sys.stderr)
49
+ sys.exit(1)
50
+
51
+ if not login_payloads: # Check if any payloads were loaded
52
+ print(f"Error: No valid login payloads found in {login_payloads_path}", file=sys.stderr)
53
+ sys.exit(1)
54
+
55
+ except Exception as e:
56
+ print(f"Error reading login payloads file {login_payloads_path}: {e}", file=sys.stderr)
57
+ sys.exit(1)
58
+
59
+ # Ensure the number of payloads matches the number of agents defined
60
+ num_defined_agents = len(config.agents)
61
+ if len(login_payloads) != num_defined_agents:
62
+ print(
63
+ f"Error: Number of login payloads ({len(login_payloads)}) in {login_payloads_path} "
64
+ f"does not match the number of agents defined in the config ({num_defined_agents}).",
65
+ file=sys.stderr,
66
+ )
67
+ sys.exit(1)
68
+
69
+ # Extract gameId from the first payload for display purposes (assuming all payloads are for the same game)
70
+ # Add basic check to ensure payloads exist and have gameId
71
+ game_id_display = "N/A"
72
+ if login_payloads and isinstance(login_payloads[0], dict) and "gameId" in login_payloads[0]:
73
+ game_id_display = login_payloads[0]["gameId"]
74
+
75
+ print(f"Starting experiment '{config.name}' with Game ID: {game_id_display}...")
76
+ print(f"Using config: {config_path}")
77
+ print(f"Using login payloads from: {login_payloads_path}")
78
+ print(f"Number of agents: {len(login_payloads)}")
79
+
80
+ try:
81
+ await parser.run_experiment(login_payloads)
82
+ print("Experiment finished.")
83
+ except Exception as e:
84
+ print(f"Error running experiment: {e}", file=sys.stderr)
85
+ sys.exit(1)
86
+
87
+
88
+ def run_cli():
89
+ """Entry point function for the CLI script."""
90
+ parser = argparse.ArgumentParser(
91
+ description=(
92
+ "Economic Agents CLI - Run experiments with AI agents in economic simulations.\n\n"
93
+ "Example usage:\n"
94
+ " econagents run config.yaml --login-payloads-file payloads.jsonl\n\n"
95
+ "The config file should be a YAML file defining the experiment setup.\n"
96
+ "The login payloads file should be a JSONL file with login credentials for each agent."
97
+ ),
98
+ formatter_class=argparse.RawDescriptionHelpFormatter
99
+ )
100
+ subparsers = parser.add_subparsers(dest="command", required=True, help="Available commands")
101
+
102
+ # --- Run Command ---
103
+ run_parser = subparsers.add_parser(
104
+ "run",
105
+ help="Run an experiment defined by a YAML configuration file.",
106
+ description=(
107
+ "Run an economic agent experiment using the specified configuration.\n\n"
108
+ "Example:\n"
109
+ " econagents run experiments/market_sim.yaml --login-payloads-file credentials.jsonl"
110
+ ),
111
+ formatter_class=argparse.RawDescriptionHelpFormatter
112
+ )
113
+ run_parser.add_argument(
114
+ "config_path",
115
+ type=str,
116
+ help="Path to the experiment configuration YAML file that defines the agent behaviors and experiment parameters."
117
+ )
118
+ run_parser.add_argument(
119
+ "--login-payloads-file",
120
+ required=True,
121
+ type=str,
122
+ help="Path to a JSON Lines (.jsonl) file containing login credentials for each agent, one per line."
123
+ )
124
+
125
+ args = parser.parse_args()
126
+
127
+ if args.command == "run":
128
+ try:
129
+ asyncio.run(async_main(args))
130
+ except KeyboardInterrupt:
131
+ print("\nExperiment interrupted by user.")
132
+ sys.exit(0)
133
+
134
+
135
+ if __name__ == "__main__":
136
+ run_cli()
@@ -0,0 +1,18 @@
1
+ """
2
+ Configuration parsers for different server types.
3
+
4
+ This package provides configuration parsers for different server types:
5
+ - Base: No custom event handlers
6
+ - Basic: Custom event handler for player-is-ready messages
7
+ - IBEX-TUDelft: Handles player-is-ready messages and role assignment
8
+ """
9
+
10
+ from econagents.config_parser.base import BaseConfigParser
11
+ from econagents.config_parser.basic import BasicConfigParser
12
+ from econagents.config_parser.ibex_tudelft import IbexTudelftConfigParser
13
+
14
+ __all__ = [
15
+ "BaseConfigParser",
16
+ "BasicConfigParser",
17
+ "IbexTudelftConfigParser",
18
+ ]
@@ -0,0 +1,518 @@
1
+ import importlib
2
+ import logging
3
+ import tempfile
4
+ from pathlib import Path
5
+ from typing import Any, Dict, List, Literal, Optional, Type, cast
6
+ from datetime import datetime, date, time
7
+
8
+ import yaml
9
+ from pydantic import BaseModel, Field, create_model
10
+
11
+ from econagents.core.game_runner import GameRunner, GameRunnerConfig, HybridGameRunnerConfig, TurnBasedGameRunnerConfig
12
+ from econagents.core.manager.phase import PhaseManager, TurnBasedPhaseManager, HybridPhaseManager
13
+ from econagents.core.state.fields import EventField
14
+ from econagents.core.state.game import GameState, MetaInformation, PrivateInformation, PublicInformation
15
+ from econagents.core.state.market import MarketState
16
+ from econagents.core.agent_role import AgentRole
17
+
18
+ TYPE_MAPPING = {
19
+ "str": str,
20
+ "int": int,
21
+ "float": float,
22
+ "bool": bool,
23
+ "list": list,
24
+ "dict": dict,
25
+ "datetime": datetime,
26
+ "date": date,
27
+ "time": time,
28
+ "any": Any,
29
+ "MarketState": MarketState,
30
+ }
31
+
32
+
33
+ class EventHandler(BaseModel):
34
+ """Configuration for an event handler."""
35
+
36
+ event: str
37
+ custom_code: Optional[str] = None
38
+ custom_module: Optional[str] = None
39
+ custom_function: Optional[str] = None
40
+
41
+
42
+ class AgentRoleConfig(BaseModel):
43
+ """Configuration for an agent role."""
44
+
45
+ role_id: int
46
+ name: str
47
+ llm_type: str = "ChatOpenAI"
48
+ llm_params: Dict[str, Any] = Field(default_factory=dict)
49
+ prompts: List[Dict[str, str]] = Field(default_factory=list)
50
+ task_phases: List[int] = Field(default_factory=list)
51
+ task_phases_excluded: List[int] = Field(default_factory=list)
52
+
53
+ def create_agent_role(self) -> AgentRole:
54
+ """Create an AgentRole instance from this configuration."""
55
+ # Dynamically create the LLM provider
56
+ llm_class = getattr(importlib.import_module("econagents.llm"), self.llm_type)
57
+ llm_instance = llm_class(**self.llm_params)
58
+
59
+ # Create a dynamic AgentRole subclass
60
+ agent_role_attrs = {
61
+ "role": self.role_id,
62
+ "name": self.name,
63
+ "llm": llm_instance,
64
+ "task_phases": self.task_phases,
65
+ "task_phases_excluded": self.task_phases_excluded,
66
+ }
67
+ agent_role = type(
68
+ f"Dynamic{self.name}Role",
69
+ (AgentRole,),
70
+ agent_role_attrs,
71
+ )
72
+
73
+ return agent_role()
74
+
75
+
76
+ class AgentMappingConfig(BaseModel):
77
+ """Configuration mapping agent IDs to role IDs."""
78
+
79
+ id: int
80
+ role_id: int
81
+
82
+
83
+ class AgentConfig(BaseModel):
84
+ """Configuration for an agent role."""
85
+
86
+ role_id: int
87
+ name: str
88
+ llm_type: str = "ChatOpenAI"
89
+ llm_params: Dict[str, Any] = Field(default_factory=dict)
90
+
91
+ def create_agent_role(self) -> AgentRole:
92
+ """Create an AgentRole instance from this configuration."""
93
+ # Dynamically create the LLM provider
94
+ llm_class = getattr(importlib.import_module("econagents.llm"), self.llm_type)
95
+ llm_instance = llm_class(**self.llm_params)
96
+
97
+ # Create a dynamic AgentRole subclass
98
+ agent_role = type(
99
+ f"Dynamic{self.name}Role", (AgentRole,), {"role": self.role_id, "name": self.name, "llm": llm_instance}
100
+ )
101
+
102
+ return agent_role()
103
+
104
+
105
+ class StateFieldConfig(BaseModel):
106
+ """Configuration for a field in the state."""
107
+
108
+ name: str
109
+ type: str
110
+ default: Any = None
111
+ default_factory: Optional[str] = None
112
+ event_key: Optional[str] = None
113
+ exclude_from_mapping: bool = False
114
+ optional: bool = False
115
+ events: Optional[List[str]] = None
116
+ exclude_events: Optional[List[str]] = None
117
+
118
+
119
+ class StateConfig(BaseModel):
120
+ """Configuration for a game state."""
121
+
122
+ meta_information: List[StateFieldConfig] = Field(default_factory=list)
123
+ private_information: List[StateFieldConfig] = Field(default_factory=list)
124
+ public_information: List[StateFieldConfig] = Field(default_factory=list)
125
+
126
+ def create_state_class(self) -> Type[GameState]:
127
+ """Create a GameState subclass from this configuration using create_model."""
128
+
129
+ def resolve_field_type(field_type_str: str) -> Any:
130
+ """Resolve type string to Python type."""
131
+ if field_type_str in TYPE_MAPPING:
132
+ return TYPE_MAPPING[field_type_str]
133
+ else:
134
+ try:
135
+ resolved_type = eval(
136
+ field_type_str, {"list": list, "dict": dict, "Any": Any, "MarketState": MarketState}
137
+ )
138
+ return resolved_type
139
+ except (NameError, SyntaxError):
140
+ raise ValueError(f"Unsupported field type: {field_type_str}")
141
+
142
+ def get_default_factory(factory_name: str) -> Any:
143
+ """Get default factory function."""
144
+ if factory_name == "list":
145
+ return list
146
+ elif factory_name == "dict":
147
+ return dict
148
+ else:
149
+ try:
150
+ return eval(factory_name, {"MarketState": MarketState})
151
+ except (NameError, SyntaxError):
152
+ raise ValueError(f"Unsupported default_factory: {factory_name}")
153
+
154
+ def create_fields_dict(field_configs: List[StateFieldConfig]) -> Dict[str, Any]:
155
+ """Create a dictionary of field definitions for create_model."""
156
+ fields = {}
157
+ for field in field_configs:
158
+ base_type = resolve_field_type(field.type)
159
+ field_type = Optional[base_type] if field.optional else base_type
160
+
161
+ event_field_args = {
162
+ "event_key": field.event_key,
163
+ "exclude_from_mapping": field.exclude_from_mapping,
164
+ "events": field.events,
165
+ "exclude_events": field.exclude_events,
166
+ }
167
+ # Handle default vs default_factory
168
+ if field.default_factory:
169
+ event_field_args["default_factory"] = get_default_factory(field.default_factory)
170
+ else:
171
+ # Pydantic handles Optional defaults correctly (None if optional and no default)
172
+ event_field_args["default"] = field.default
173
+
174
+ # EventField needs to be the default value passed to create_model
175
+ field_definition = EventField(**event_field_args) # type: ignore
176
+ fields[field.name] = (field_type, field_definition)
177
+ return fields
178
+
179
+ # Create dynamic classes using create_model
180
+ meta_fields = create_fields_dict(self.meta_information)
181
+ DynamicMeta = create_model(
182
+ "DynamicMeta",
183
+ __base__=MetaInformation,
184
+ **meta_fields,
185
+ )
186
+
187
+ private_fields = create_fields_dict(self.private_information)
188
+ DynamicPrivate = create_model(
189
+ "DynamicPrivate",
190
+ __base__=PrivateInformation,
191
+ **private_fields,
192
+ )
193
+
194
+ public_fields = create_fields_dict(self.public_information)
195
+ DynamicPublic = create_model(
196
+ "DynamicPublic",
197
+ __base__=PublicInformation,
198
+ **public_fields,
199
+ )
200
+
201
+ # Create the final game state class
202
+ DynamicGameState = create_model(
203
+ "DynamicGameState",
204
+ __base__=GameState,
205
+ meta=(DynamicMeta, Field(default_factory=DynamicMeta)),
206
+ private_information=(DynamicPrivate, Field(default_factory=DynamicPrivate)),
207
+ public_information=(DynamicPublic, Field(default_factory=DynamicPublic)),
208
+ )
209
+
210
+ # Cast to Type[GameState] for type hinting
211
+ return cast(Type[GameState], DynamicGameState)
212
+
213
+
214
+ class ManagerConfig(BaseModel):
215
+ """Configuration for a manager."""
216
+
217
+ type: str = "TurnBasedPhaseManager"
218
+ event_handlers: List[EventHandler] = Field(default_factory=list)
219
+
220
+ def create_manager(
221
+ self, game_id: int, state: GameState, agent_role: Optional[AgentRole], auth_kwargs: Dict[str, Any]
222
+ ) -> PhaseManager:
223
+ """Create a PhaseManager instance from this configuration."""
224
+
225
+ manager_class: Type[PhaseManager]
226
+
227
+ if self.type == "TurnBasedPhaseManager":
228
+ manager_class = TurnBasedPhaseManager
229
+ elif self.type == "HybridPhaseManager":
230
+ manager_class = HybridPhaseManager
231
+ else:
232
+ raise ValueError(f"Invalid manager type: {self.type}")
233
+
234
+ # Create the manager instance
235
+ manager = manager_class(
236
+ auth_mechanism_kwargs=auth_kwargs,
237
+ state=state,
238
+ agent_role=agent_role,
239
+ )
240
+
241
+ # Set Game ID
242
+ if hasattr(manager, "game_id"):
243
+ setattr(manager, "game_id", game_id)
244
+
245
+ # Register event handlers
246
+ for handler in self.event_handlers:
247
+ # Create a handler function based on the configuration
248
+ async def create_handler(message, handler=handler):
249
+ # Execute custom code if specified
250
+ if handler.custom_code:
251
+ # Use exec to run the custom code with access to manager and message
252
+ local_vars = {"manager": manager, "message": message}
253
+ exec(handler.custom_code, globals(), local_vars)
254
+
255
+ # Import and execute custom function if specified
256
+ if handler.custom_module and handler.custom_function:
257
+ try:
258
+ module = importlib.import_module(handler.custom_module)
259
+ func = getattr(module, handler.custom_function)
260
+ await func(manager, message)
261
+ except (ImportError, AttributeError) as e:
262
+ manager.logger.error(f"Error importing custom handler: {e}")
263
+
264
+ # Register the handler
265
+ manager.register_event_handler(handler.event, create_handler)
266
+
267
+ return manager
268
+
269
+
270
+ class RunnerConfig(BaseModel):
271
+ """Configuration for a game runner."""
272
+
273
+ type: str = "GameRunner"
274
+ protocol: str = "ws"
275
+ hostname: str
276
+ path: str = "wss"
277
+ port: int
278
+ game_id: int
279
+ logs_dir: str = "logs"
280
+ log_level: str = "INFO"
281
+ prompts_dir: str = "prompts"
282
+ phase_transition_event: str = "phase-transition"
283
+ phase_identifier_key: str = "phase"
284
+ observability_provider: Optional[Literal["langsmith", "langfuse"]] = None
285
+
286
+ # For hybrid game runners
287
+ continuous_phases: List[int] = Field(default_factory=list)
288
+ min_action_delay: int = 5
289
+ max_action_delay: int = 10
290
+
291
+ def create_runner_config(self) -> GameRunnerConfig:
292
+ """Create a GameRunnerConfig instance from this configuration."""
293
+ # Map string log level to int
294
+ log_levels = {
295
+ "DEBUG": logging.DEBUG,
296
+ "INFO": logging.INFO,
297
+ "WARNING": logging.WARNING,
298
+ "ERROR": logging.ERROR,
299
+ "CRITICAL": logging.CRITICAL,
300
+ }
301
+
302
+ log_level_int = log_levels.get(self.log_level.upper(), logging.INFO)
303
+
304
+ # Base arguments for constructor - explicitly defining each parameter
305
+ if self.type == "TurnBasedGameRunner":
306
+ return TurnBasedGameRunnerConfig(
307
+ protocol=self.protocol,
308
+ hostname=self.hostname,
309
+ path=self.path,
310
+ port=self.port,
311
+ game_id=self.game_id,
312
+ logs_dir=Path.cwd() / self.logs_dir,
313
+ log_level=log_level_int,
314
+ prompts_dir=Path.cwd() / self.prompts_dir,
315
+ phase_transition_event=self.phase_transition_event,
316
+ phase_identifier_key=self.phase_identifier_key,
317
+ observability_provider=self.observability_provider,
318
+ state_class=None,
319
+ )
320
+ elif self.type == "HybridGameRunner":
321
+ return HybridGameRunnerConfig(
322
+ protocol=self.protocol,
323
+ hostname=self.hostname,
324
+ path=self.path,
325
+ port=self.port,
326
+ game_id=self.game_id,
327
+ logs_dir=Path.cwd() / self.logs_dir,
328
+ log_level=log_level_int,
329
+ prompts_dir=Path.cwd() / self.prompts_dir,
330
+ phase_transition_event=self.phase_transition_event,
331
+ phase_identifier_key=self.phase_identifier_key,
332
+ observability_provider=self.observability_provider,
333
+ continuous_phases=self.continuous_phases,
334
+ min_action_delay=self.min_action_delay,
335
+ max_action_delay=self.max_action_delay,
336
+ )
337
+ else:
338
+ raise ValueError(f"Invalid runner type: {self.type}")
339
+
340
+
341
+ class ExperimentConfig(BaseModel):
342
+ """Configuration for an entire experiment."""
343
+
344
+ name: str
345
+ description: str = ""
346
+ prompt_partials: List[Dict[str, str]] = Field(default_factory=list)
347
+ agent_roles: List[AgentRoleConfig] = Field(default_factory=list)
348
+ agents: List[AgentMappingConfig] = Field(default_factory=list)
349
+ state: StateConfig
350
+ manager: ManagerConfig
351
+ runner: RunnerConfig
352
+ _temp_prompts_dir: Optional[Path] = None
353
+
354
+ def _compile_inline_prompts(self) -> Path:
355
+ """Compile prompts from config into a temporary directory.
356
+
357
+ Returns:
358
+ Path to the temporary directory containing compiled prompts
359
+ """
360
+ # Create a temporary directory for prompts
361
+ temp_dir = Path(tempfile.mkdtemp(prefix="econagents_prompts_"))
362
+ self._temp_prompts_dir = temp_dir
363
+
364
+ # Create _partials directory
365
+ partials_dir = temp_dir / "_partials"
366
+ partials_dir.mkdir(parents=True, exist_ok=True)
367
+
368
+ # Write prompt partials
369
+ for partial in self.prompt_partials:
370
+ partial_file = partials_dir / f"{partial['name']}.jinja2"
371
+ partial_file.write_text(partial["content"])
372
+
373
+ # Write prompts for each agent role
374
+ for role in self.agent_roles:
375
+ if not hasattr(role, "prompts") or not role.prompts:
376
+ continue
377
+
378
+ for prompt in role.prompts:
379
+ # Each prompt should be a dict with one key (type) and one value (content)
380
+ for prompt_type, content in prompt.items():
381
+ # Parse the prompt type to get the base type and phase
382
+ parts = prompt_type.split("_phase_")
383
+ base_type = parts[0] # system or user
384
+ phase = parts[1] if len(parts) > 1 else None
385
+
386
+ # Create the prompt file name
387
+ if phase:
388
+ file_name = f"{role.name.lower()}_{base_type}_phase_{phase}.jinja2"
389
+ else:
390
+ file_name = f"{role.name.lower()}_{base_type}.jinja2"
391
+
392
+ # Write the prompt file
393
+ prompt_file = temp_dir / file_name
394
+ prompt_file.write_text(content)
395
+
396
+ return temp_dir
397
+
398
+ async def run_experiment(self, login_payloads: List[Dict[str, Any]], game_id: int) -> None:
399
+ """Run the experiment from this configuration."""
400
+ # Create state class
401
+ state_class = self.state.create_state_class()
402
+ role_configs = {role_config.role_id: role_config for role_config in self.agent_roles}
403
+
404
+ if not self.agent_roles and self.agents:
405
+ raise ValueError(
406
+ "Configuration has 'agents' but no 'agent_roles'. Cannot determine agent role configurations."
407
+ )
408
+
409
+ agent_to_role_map = {agent_map.id: agent_map.role_id for agent_map in self.agents}
410
+
411
+ # Create managers for each agent
412
+ agents = []
413
+ for payload in login_payloads:
414
+ agent_id = payload.get("agent_id")
415
+ if agent_id is None:
416
+ raise ValueError(f"Login payload missing 'agent_id' field: {payload}")
417
+
418
+ role_id = agent_to_role_map.get(agent_id)
419
+ if role_id is None:
420
+ raise ValueError(f"No role_id mapping found for agent {agent_id}")
421
+
422
+ if role_id not in role_configs:
423
+ raise ValueError(f"No agent role configuration found for role_id {role_id}")
424
+
425
+ agent_role_instance = role_configs[role_id].create_agent_role()
426
+
427
+ agents.append(
428
+ self.manager.create_manager(
429
+ game_id=game_id,
430
+ state=state_class(game_id=game_id),
431
+ agent_role=agent_role_instance,
432
+ auth_kwargs=payload,
433
+ )
434
+ )
435
+
436
+ # Create runner config
437
+ runner_config = self.runner.create_runner_config()
438
+ runner_config.state_class = state_class
439
+ runner_config.game_id = game_id
440
+
441
+ # Compile inline prompts if needed
442
+ if any(hasattr(role, "prompts") and role.prompts for role in self.agent_roles):
443
+ prompts_dir = self._compile_inline_prompts()
444
+ runner_config.prompts_dir = prompts_dir
445
+
446
+ # Create and run game runner
447
+ runner = GameRunner(config=runner_config, agents=agents)
448
+ await runner.run_game()
449
+
450
+ # Clean up temporary prompts directory
451
+ if self._temp_prompts_dir and self._temp_prompts_dir.exists():
452
+ import shutil
453
+
454
+ shutil.rmtree(self._temp_prompts_dir)
455
+
456
+
457
+ class BaseConfigParser:
458
+ """Base configuration parser with no custom event handlers."""
459
+
460
+ def __init__(self, config_path: Path):
461
+ """
462
+ Initialize the config parser with a path to a YAML configuration file.
463
+
464
+ Args:
465
+ config_path: Path to the YAML configuration file
466
+ """
467
+ self.config_path = config_path
468
+ self.config = self.load_config()
469
+
470
+ def load_config(self) -> ExperimentConfig:
471
+ """Load the experiment configuration from the YAML file."""
472
+ with open(self.config_path, "r") as file:
473
+ config_data = yaml.safe_load(file)
474
+
475
+ # Handle backward compatibility with old format
476
+ if not config_data.get("agent_roles") and "agents" in config_data:
477
+ # Check if the agents field contains role configurations
478
+ if config_data["agents"] and "name" in config_data["agents"][0]:
479
+ # Old format with agent configurations in "agents" field
480
+ config_data["agent_roles"] = config_data.pop("agents")
481
+ config_data["agents"] = []
482
+
483
+ return ExperimentConfig(**config_data)
484
+
485
+ def create_manager(
486
+ self, game_id: int, state: GameState, agent_role: Optional[AgentRole], auth_kwargs: Dict[str, Any]
487
+ ) -> PhaseManager:
488
+ """
489
+ Create a manager instance based on the configuration.
490
+ This base implementation has no custom event handlers.
491
+
492
+ Args:
493
+ game_id: The game ID
494
+ state: The game state instance
495
+ agent_role: The agent role instance
496
+ auth_kwargs: Authentication mechanism keyword arguments
497
+
498
+ Returns:
499
+ A PhaseManager instance
500
+ """
501
+ return self.config.manager.create_manager(
502
+ game_id=game_id, state=state, agent_role=agent_role, auth_kwargs=auth_kwargs
503
+ )
504
+
505
+ async def run_experiment(self, login_payloads: List[Dict[str, Any]], game_id: int) -> None:
506
+ """
507
+ Run the experiment from this configuration.
508
+
509
+ Args:
510
+ login_payloads: A list of dictionaries containing login information for each agent
511
+ """
512
+ await self.config.run_experiment(login_payloads, game_id)
513
+
514
+
515
+ async def run_experiment_from_yaml(yaml_path: Path, login_payloads: List[Dict[str, Any]], game_id: int) -> None:
516
+ """Run an experiment from a YAML configuration file."""
517
+ parser = BaseConfigParser(yaml_path)
518
+ await parser.run_experiment(login_payloads, game_id)