From 9ec24571d88efb2f71f46791783f533a162ffbb1 Mon Sep 17 00:00:00 2001 From: Sasha Vezhnevets Date: Wed, 6 Nov 2024 08:55:31 -0800 Subject: [PATCH 01/14] Replace a pub_coordination_friendships scenario with a simpler one, with pub closures. PiperOrigin-RevId: 693744954 Change-Id: Ia8eef36e764cd3e545438654b7d7ef114d0b53bc --- examples/modular/environment/pub_coordination.py | 2 +- examples/modular/scenario/scenarios.py | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/examples/modular/environment/pub_coordination.py b/examples/modular/environment/pub_coordination.py index dd1c1d3..57cbf64 100644 --- a/examples/modular/environment/pub_coordination.py +++ b/examples/modular/environment/pub_coordination.py @@ -492,7 +492,7 @@ def configure_scenes( } if closed_pub: - players_in_the_know = rng.choice(player_configs) + players_in_the_know = player_configs[0] player_name = players_in_the_know.name per_player_premise[player_name].append( f'{player_name} have learnt that {closed_pub} is closed today. Going' diff --git a/examples/modular/scenario/scenarios.py b/examples/modular/scenario/scenarios.py index bea6da7..a1bfd3b 100644 --- a/examples/modular/scenario/scenarios.py +++ b/examples/modular/scenario/scenarios.py @@ -211,16 +211,17 @@ class ScenarioConfig: focal_is_resident=False, tags=('coordination', 'persuasion'), ), - pub_coordination_friendships_0=ScenarioConfig( + + pub_coordination_closures_0=ScenarioConfig( description=( - 'resident population of focal agents in a pub coordination scenario' - ' with friendships with a supporting agent who is stubborn and a' - ' rational visitor agent' + 'visitor population of focal agents in a pub coordination scenario' + ' with a chance of a pub being closed and a' + ' rational visitor agent and a stubborn on supporting agent.' ), - substrate_config=SUBSTRATE_CONFIGS['pub_coordination_friendships'], + substrate_config=SUBSTRATE_CONFIGS['pub_coordination_closures'], background_agent_module='rational_agent', time_and_place_module='pub_coordination_london', - focal_is_resident=True, + focal_is_resident=False, tags=('coordination', 'persuasion', 'social networks'), ), haggling_0=ScenarioConfig( From 187cd09cab8557ef0bc207d00f5dceb928e7d21d Mon Sep 17 00:00:00 2001 From: Sasha Vezhnevets Date: Wed, 6 Nov 2024 09:17:56 -0800 Subject: [PATCH 02/14] Increment version number to 1.8.7 PiperOrigin-RevId: 693752111 Change-Id: If572c646de3f4a0188021f2e23f4efa578bc9304 --- CHANGELOG.md | 13 +++++++++++-- setup.py | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8146144..87bf7b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,13 +4,22 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/). + +## [1.8.7] - 2024-11-5 + +### Added + +- add parochial universalization agent to the main factories +- add Jinja2 to the requirements to improve the prompting experience +- Add get_raw_memories and get_raw_memories_as_text functions on the memory component. + + ## [1.8.6] - 2024-10-29 ### Added - Add ability to save and load rational and basic agents to/from json. -- Add a version of agent development colab that uses GCP hosted model - +- Add a version of agent development colab that uses GCP hosted model. ## [1.8.5] - 2024-10-21 diff --git a/setup.py b/setup.py index 31bd6d4..3ea17c1 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ def _remove_excluded(description: str) -> str: setuptools.setup( name='gdm-concordia', - version='1.8.6', + version='1.8.7', license='Apache 2.0', license_files=['LICENSE'], url='https://github.com/google-deepmind/concordia', From 33fa99c888b4e15b26e20092f0911fe27f74cfc9 Mon Sep 17 00:00:00 2001 From: Sasha Vezhnevets Date: Thu, 7 Nov 2024 03:20:37 -0800 Subject: [PATCH 03/14] adding ability to override background agent module. PiperOrigin-RevId: 694053569 Change-Id: Ia56a584f29367291006b18daaf1633cb3c29a4f6 --- examples/modular/scenario/scenarios.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/examples/modular/scenario/scenarios.py b/examples/modular/scenario/scenarios.py index a1bfd3b..9818dec 100644 --- a/examples/modular/scenario/scenarios.py +++ b/examples/modular/scenario/scenarios.py @@ -341,6 +341,7 @@ def build_simulation( support_agent_base_module: str = DEFAULT_IMPORT_SUPPORT_AGENT_MODULE, env_base_module: str = DEFAULT_IMPORT_ENV_BASE_MODULE, seed: int | None = None, + override_background_agent_module: types.ModuleType | None = None, ) -> RunnableSimulationWithMemories: """Builds a simulation from a scenario configuration.""" substrate_config = scenario_config.substrate_config @@ -348,9 +349,12 @@ def build_simulation( simulation = importlib.import_module( f'{env_base_module}.{substrate_config.environment}' ) - background_agent_module = importlib.import_module( - f'{agent_base_module}.{scenario_config.background_agent_module}' - ) + if override_background_agent_module is not None: + background_agent_module = override_background_agent_module + else: + background_agent_module = importlib.import_module( + f'{agent_base_module}.{scenario_config.background_agent_module}' + ) if scenario_config.focal_is_resident: resident_agent_module = focal_agent_module visitor_agent_module = background_agent_module From cee1819a59a1abcc34b8551663a1e61978cfa675 Mon Sep 17 00:00:00 2001 From: "Joel Z. Leibo" Date: Tue, 12 Nov 2024 17:23:22 -0800 Subject: [PATCH 04/14] Fix bug in Schelling diagram payoffs preventing some players from seeing round outcomes in reality show PiperOrigin-RevId: 695925661 Change-Id: Id33580687b71e3adb467ea5426c874be65a70175 --- .../components/game_master/schelling_diagram_payoffs.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/concordia/components/game_master/schelling_diagram_payoffs.py b/concordia/components/game_master/schelling_diagram_payoffs.py index d975e69..f3aa066 100644 --- a/concordia/components/game_master/schelling_diagram_payoffs.py +++ b/concordia/components/game_master/schelling_diagram_payoffs.py @@ -121,6 +121,9 @@ def __init__( self._partial_states = {player.name: '' for player in self._players} self._player_scores = {player.name: 0 for player in self._players} + self._map_names_to_players = { + player.name: player for player in self._players} + self._resolution_scene = resolution_scene self._current_scene = current_scene.CurrentScene( name='current scene type', @@ -265,6 +268,11 @@ def update_after_event( # Use the outcome summarization function to get the state. self._set_outcome_messages(rewards, binary_joint_action, joint_action) self._memory.extend([self.state(),]) + for player_name, partial_state in self._partial_states.items(): + if partial_state: + if isinstance(self._map_names_to_players[player_name], + entity_agent.EntityAgent): + self._map_names_to_players[player_name].observe(partial_state) joint_action_for_log = str(self._partial_joint_action) payoffs_for_log = self.state() From 62ad3d0d443e746841ca94b06337cb187e603a5d Mon Sep 17 00:00:00 2001 From: "Joel Z. Leibo" Date: Tue, 12 Nov 2024 18:41:19 -0800 Subject: [PATCH 05/14] Increment version number to 1.8.8 PiperOrigin-RevId: 695942456 Change-Id: I5b99e10cb2bba3700b8338db78984a05ed168eb4 --- CHANGELOG.md | 7 +++++++ setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87bf7b4..148b273 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/). +## [1.8.8] - 2024-11-13 + +### Fixed + +- Fixed a bug in the Schelling diagram payoffs component preventing some players +from seeing outcomes on certain steps. + ## [1.8.7] - 2024-11-5 diff --git a/setup.py b/setup.py index 3ea17c1..9c65079 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ def _remove_excluded(description: str) -> str: setuptools.setup( name='gdm-concordia', - version='1.8.7', + version='1.8.8', license='Apache 2.0', license_files=['LICENSE'], url='https://github.com/google-deepmind/concordia', From 56807c94e17cf2593d8454fc42a73ce51b65a2af Mon Sep 17 00:00:00 2001 From: "Joel Z. Leibo" Date: Wed, 13 Nov 2024 08:33:50 -0800 Subject: [PATCH 06/14] Fix time serialization in associative_memory PiperOrigin-RevId: 696147310 Change-Id: Ia08e7e64b18d2ac188100286de7128f9611ec73e --- concordia/associative_memory/associative_memory.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/concordia/associative_memory/associative_memory.py b/concordia/associative_memory/associative_memory.py index ccd9d50..8929f00 100644 --- a/concordia/associative_memory/associative_memory.py +++ b/concordia/associative_memory/associative_memory.py @@ -85,10 +85,14 @@ def get_state(self) -> entity_component.ComponentState: """Converts the AssociativeMemory to a dictionary.""" with self._memory_bank_lock: + serialized_times = self._memory_bank['time'].apply( + lambda x: x.strftime('[%d-%b-%Y-%H:%M:%S]') + ).tolist() output = { 'seed': self._seed, 'stored_hashes': list(self._stored_hashes), 'memory_bank': self._memory_bank.to_json(), + 'time': serialized_times, } if self._interval: output['interval'] = self._interval.total_seconds() @@ -101,6 +105,10 @@ def set_state(self, state: entity_component.ComponentState) -> None: self._seed = state['seed'] self._stored_hashes = set(state['stored_hashes']) self._memory_bank = pd.read_json(state['memory_bank']) + self._memory_bank['time'] = [ + datetime.datetime.strptime(t, '[%d-%b-%Y-%H:%M:%S]') + for t in state['time'] + ] if 'interval' in state: self._interval = datetime.timedelta(seconds=state['interval']) From 853abec1ece384f2df144c84791e5d1ca91aefe1 Mon Sep 17 00:00:00 2001 From: "Joel Z. Leibo" Date: Wed, 13 Nov 2024 08:51:20 -0800 Subject: [PATCH 07/14] Add support for scene-wise computation of metrics PiperOrigin-RevId: 696152643 Change-Id: I530337d334b8b1d9b5ba7b2776300acfdb111e76 --- concordia/environment/scenes/runner.py | 17 +++++++- .../factory/environment/basic_game_master.py | 5 ++- concordia/typing/logging.py | 12 +++++- concordia/typing/scene.py | 3 ++ concordia/utils/helper_functions.py | 42 +++++++++++++++++++ 5 files changed, 76 insertions(+), 3 deletions(-) diff --git a/concordia/environment/scenes/runner.py b/concordia/environment/scenes/runner.py index 6f2d3c3..b3bdfed 100644 --- a/concordia/environment/scenes/runner.py +++ b/concordia/environment/scenes/runner.py @@ -14,12 +14,14 @@ """Scene runner.""" -from collections.abc import Sequence +from collections.abc import Mapping, Sequence from concordia.agents import deprecated_agent from concordia.environment import game_master from concordia.typing import clock as game_clock +from concordia.typing import logging as logging_lib from concordia.typing import scene as scene_lib +from concordia.utils import helper_functions def _get_interscene_messages( @@ -61,6 +63,7 @@ def run_scenes( players: Sequence[deprecated_agent.BasicAgent], clock: game_clock.GameClock, verbose: bool = False, + compute_metrics: Mapping[str, logging_lib.Metric] | None = None, ) -> None: """Run a sequence of scenes. @@ -70,6 +73,7 @@ def run_scenes( players: full list of players (a subset may participate in each scene) clock: the game clock which may be advanced between scenes verbose: if true then print intermediate outputs + compute_metrics: Optionally, a function to compute metrics. """ players_by_name = {player.name: player for player in players} if len(players_by_name) != len(players): @@ -135,3 +139,14 @@ def run_scenes( print(f'{participant.name} -- conclusion: {message}') participant.observe(message) this_scene_game_master_memory.add(message) + + # Branch off a metric scene if applicable + if scene.scene_type.save_after_each_scene: + serialized_agents = {} + for participant in participants: + serialized_agents = {} + json_representation = helper_functions.save_to_json(participant) + serialized_agents[participant.name] = json_representation + + if compute_metrics is not None: + compute_metrics(serialized_agents) diff --git a/concordia/factory/environment/basic_game_master.py b/concordia/factory/environment/basic_game_master.py index 81894de..570934a 100644 --- a/concordia/factory/environment/basic_game_master.py +++ b/concordia/factory/environment/basic_game_master.py @@ -14,7 +14,7 @@ """A Generic Environment Factory.""" -from collections.abc import Callable, Sequence +from collections.abc import Callable, Mapping, Sequence import operator from concordia import components as generic_components @@ -295,6 +295,7 @@ def run_simulation( scenes: Sequence[scene_lib.SceneSpec], secondary_environments: Sequence[game_master.GameMaster] = tuple(), summarize_entire_episode_in_log: bool = True, + compute_metrics: Callable[[Mapping[str, str]], None] | None = None, ) -> str: """Run a simulation. @@ -307,6 +308,7 @@ def run_simulation( secondary_environments: Sequence of secondary game masters for scenes. summarize_entire_episode_in_log: Optionally, include summaries of the full episode in the log. + compute_metrics: Optionally, a function to compute metrics. Returns: an HTML string log of the simulation. @@ -317,6 +319,7 @@ def run_simulation( scenes=scenes, players=players, clock=clock, + compute_metrics=compute_metrics, ) result_html_log = create_html_log( model=model, diff --git a/concordia/typing/logging.py b/concordia/typing/logging.py index ebd7261..b343590 100644 --- a/concordia/typing/logging.py +++ b/concordia/typing/logging.py @@ -14,9 +14,19 @@ """Types for logging.""" -from collections.abc import Mapping +from collections.abc import Mapping, Sequence +import dataclasses from typing import Any, Callable +from concordia.typing import entity as entity_lib LoggingChannel = Callable[[Mapping[str, Any]], None] NoOpLoggingChannel = lambda x: None + + +@dataclasses.dataclass(frozen=True) +class Metric: + question: str + output_type: entity_lib.OutputType + options: Sequence[str] | None = None + context: str | None = None diff --git a/concordia/typing/scene.py b/concordia/typing/scene.py index ad06200..c071d34 100644 --- a/concordia/typing/scene.py +++ b/concordia/typing/scene.py @@ -39,6 +39,8 @@ class SceneTypeSpec: the game master to ask the agents to produce during steps of this scene. override_game_master: optionally specify a game master to use instead of the default one. + save_after_each_scene: optionally specify whether to save the agent's state + after each scene. """ name: str @@ -52,6 +54,7 @@ class SceneTypeSpec: | None ) = None override_game_master: game_master.GameMaster | None = None + save_after_each_scene: bool = False @dataclasses.dataclass(frozen=True) diff --git a/concordia/utils/helper_functions.py b/concordia/utils/helper_functions.py index 920f4d8..a28e67e 100644 --- a/concordia/utils/helper_functions.py +++ b/concordia/utils/helper_functions.py @@ -17,10 +17,13 @@ from collections.abc import Iterable, Sequence import datetime import functools +import json +from concordia.agents import entity_agent_with_logging from concordia.document import interactive_document from concordia.language_model import language_model from concordia.typing import component +from concordia.typing import entity_component from concordia.utils import concurrency @@ -144,3 +147,42 @@ def apply_recursively( getattr(parent_component, function_name)() else: getattr(parent_component, function_name)(function_arg) + + +def save_to_json( + agent: entity_agent_with_logging.EntityAgentWithLogging, +) -> str: + """Saves an agent to JSON data. + + This function saves the agent's state to a JSON string, which can be loaded + afterwards with `rebuild_from_json`. The JSON data + includes the state of the agent's context components, act component, memory, + agent name and the initial config. The clock, model and embedder are not + saved and will have to be provided when the agent is rebuilt. The agent must + be in the `READY` phase to be saved. + + Args: + agent: The agent to save. + + Returns: + A JSON string representing the agent's state. + + Raises: + ValueError: If the agent is not in the READY phase. + """ + + if agent.get_phase() != entity_component.Phase.READY: + raise ValueError('The agent must be in the `READY` phase to be saved.') + + data = { + component_name: agent.get_component(component_name).get_state() + for component_name in agent.get_all_context_components() + } + + data['act_component'] = agent.get_act_component().get_state() + + config = agent.get_config() + if config is not None: + data['agent_config'] = config.to_dict() + + return json.dumps(data) From 0151181af579b99bbe924630eb5a29958792d2ae Mon Sep 17 00:00:00 2001 From: "Joel Z. Leibo" Date: Wed, 13 Nov 2024 10:07:36 -0800 Subject: [PATCH 08/14] Catch another type of together API exception PiperOrigin-RevId: 696177257 Change-Id: Idcb276ee0c63c036e04fbf43a81a1ff623068726 --- concordia/language_model/together_ai.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/concordia/language_model/together_ai.py b/concordia/language_model/together_ai.py index 4ee7103..274c0a7 100644 --- a/concordia/language_model/together_ai.py +++ b/concordia/language_model/together_ai.py @@ -205,11 +205,14 @@ def sample_text( ) except (together.error.RateLimitError, together.error.APIError, - together.error.ServiceUnavailableError) as err: + together.error.ServiceUnavailableError, + together.error.InvalidRequestError) as err: if attempts >= _NUM_SILENT_ATTEMPTS: print(f' Exception: {err}') print(f' Text exception prompt: {prompt}') - if isinstance(err, together.error.APIError): + if isinstance(err, together.error.APIError) or isinstance( + err, together.error.InvalidRequestError + ): # If hit the error that arises from a prompt that is too long then # re-run the trimming function with a more pessimistic guess of the # the number of characters per token. @@ -282,11 +285,14 @@ def _sample_choice( ) except (together.error.RateLimitError, together.error.APIError, - together.error.ServiceUnavailableError) as err: + together.error.ServiceUnavailableError, + together.error.InvalidRequestError) as err: if attempts >= _NUM_SILENT_ATTEMPTS: print(f' Exception: {err}') print(f' Choice exception prompt: {augmented_prompt}') - if isinstance(err, together.error.APIError): + if isinstance(err, together.error.APIError) or isinstance( + err, together.error.InvalidRequestError + ): # If hit the error that arises from a prompt that is too long then # re-run the trimming function with a more pessimistic guess of the # the number of characters per token. From 3a7cc996ebe8f9f40c30fb096ca8a38ac983b77f Mon Sep 17 00:00:00 2001 From: "Joel Z. Leibo" Date: Wed, 13 Nov 2024 11:47:55 -0800 Subject: [PATCH 09/14] Fix environment factory test (pytype issue) PiperOrigin-RevId: 696216987 Change-Id: Ic3e7c32f2bc7c326966f70f7674dd8cc2ad850a9 --- concordia/environment/scenes/runner.py | 4 +- .../factory/environment/basic_game_master.py | 9 ++- .../factory/environment/factories_test.py | 14 +++-- concordia/utils/helper_functions.py | 42 ------------- concordia/utils/json.py | 59 +++++++++++++++++++ 5 files changed, 74 insertions(+), 54 deletions(-) create mode 100644 concordia/utils/json.py diff --git a/concordia/environment/scenes/runner.py b/concordia/environment/scenes/runner.py index b3bdfed..1ce4401 100644 --- a/concordia/environment/scenes/runner.py +++ b/concordia/environment/scenes/runner.py @@ -21,7 +21,7 @@ from concordia.typing import clock as game_clock from concordia.typing import logging as logging_lib from concordia.typing import scene as scene_lib -from concordia.utils import helper_functions +from concordia.utils import json as json_lib def _get_interscene_messages( @@ -145,7 +145,7 @@ def run_scenes( serialized_agents = {} for participant in participants: serialized_agents = {} - json_representation = helper_functions.save_to_json(participant) + json_representation = json_lib.save_to_json(participant) serialized_agents[participant.name] = json_representation if compute_metrics is not None: diff --git a/concordia/factory/environment/basic_game_master.py b/concordia/factory/environment/basic_game_master.py index 570934a..4495714 100644 --- a/concordia/factory/environment/basic_game_master.py +++ b/concordia/factory/environment/basic_game_master.py @@ -18,8 +18,7 @@ import operator from concordia import components as generic_components -from concordia.agents import deprecated_agent -from concordia.agents import entity_agent +from concordia.agents import entity_agent_with_logging from concordia.associative_memory import associative_memory from concordia.associative_memory import blank_memories from concordia.associative_memory import importance_function @@ -42,7 +41,7 @@ def build_game_master( embedder: Callable[[str], np.ndarray], importance_model: importance_function.ImportanceModel, clock: game_clock.MultiIntervalClock, - players: Sequence[deprecated_agent.BasicAgent], + players: Sequence[entity_agent_with_logging.EntityAgentWithLogging], shared_memories: Sequence[str], shared_context: str, blank_memory_factory: blank_memories.MemoryFactory, @@ -191,7 +190,7 @@ def build_decision_scene_game_master( model: language_model.LanguageModel, memory: associative_memory.AssociativeMemory, clock: game_clock.MultiIntervalClock, - players: Sequence[deprecated_agent.BasicAgent], + players: Sequence[entity_agent_with_logging.EntityAgentWithLogging], decision_action_spec: agent_lib.ActionSpec, payoffs: gm_components.schelling_diagram_payoffs.SchellingPayoffs, verbose: bool = False, @@ -289,7 +288,7 @@ def create_html_log( def run_simulation( *, model: language_model.LanguageModel, - players: Sequence[deprecated_agent.BasicAgent | entity_agent.EntityAgent], + players: Sequence[entity_agent_with_logging.EntityAgentWithLogging], primary_environment: game_master.GameMaster, clock: game_clock.MultiIntervalClock, scenes: Sequence[scene_lib.SceneSpec], diff --git a/concordia/factory/environment/factories_test.py b/concordia/factory/environment/factories_test.py index 7270009..b015b4a 100644 --- a/concordia/factory/environment/factories_test.py +++ b/concordia/factory/environment/factories_test.py @@ -19,12 +19,13 @@ from absl.testing import absltest from absl.testing import parameterized -from concordia.agents import deprecated_agent +from concordia.agents import entity_agent_with_logging from concordia.associative_memory import associative_memory from concordia.associative_memory import blank_memories from concordia.associative_memory import formative_memories from concordia.associative_memory import importance_function from concordia.clocks import game_clock +from concordia.components import agent as agent_components from concordia.environment import game_master from concordia.factory.environment import basic_game_master from concordia.language_model import no_language_model @@ -63,12 +64,15 @@ def test_give_me_a_name(self, environment_name: str): importance=importance_model_gm.importance, clock_now=clock.now, ) - player_a = deprecated_agent.BasicAgent( + act_component = agent_components.concat_act_component.ConcatActComponent( model=model, - agent_name='Rakshit', clock=clock, - components=[], - update_interval=datetime.timedelta(hours=1), + component_order=[], + ) + player_a = entity_agent_with_logging.EntityAgentWithLogging( + agent_name='Rakshit', + act_component=act_component, + context_components={}, ) players = [player_a] diff --git a/concordia/utils/helper_functions.py b/concordia/utils/helper_functions.py index a28e67e..920f4d8 100644 --- a/concordia/utils/helper_functions.py +++ b/concordia/utils/helper_functions.py @@ -17,13 +17,10 @@ from collections.abc import Iterable, Sequence import datetime import functools -import json -from concordia.agents import entity_agent_with_logging from concordia.document import interactive_document from concordia.language_model import language_model from concordia.typing import component -from concordia.typing import entity_component from concordia.utils import concurrency @@ -147,42 +144,3 @@ def apply_recursively( getattr(parent_component, function_name)() else: getattr(parent_component, function_name)(function_arg) - - -def save_to_json( - agent: entity_agent_with_logging.EntityAgentWithLogging, -) -> str: - """Saves an agent to JSON data. - - This function saves the agent's state to a JSON string, which can be loaded - afterwards with `rebuild_from_json`. The JSON data - includes the state of the agent's context components, act component, memory, - agent name and the initial config. The clock, model and embedder are not - saved and will have to be provided when the agent is rebuilt. The agent must - be in the `READY` phase to be saved. - - Args: - agent: The agent to save. - - Returns: - A JSON string representing the agent's state. - - Raises: - ValueError: If the agent is not in the READY phase. - """ - - if agent.get_phase() != entity_component.Phase.READY: - raise ValueError('The agent must be in the `READY` phase to be saved.') - - data = { - component_name: agent.get_component(component_name).get_state() - for component_name in agent.get_all_context_components() - } - - data['act_component'] = agent.get_act_component().get_state() - - config = agent.get_config() - if config is not None: - data['agent_config'] = config.to_dict() - - return json.dumps(data) diff --git a/concordia/utils/json.py b/concordia/utils/json.py new file mode 100644 index 0000000..1534a25 --- /dev/null +++ b/concordia/utils/json.py @@ -0,0 +1,59 @@ +# Copyright 2023 DeepMind Technologies Limited. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Json helper functions.""" + +import json + +from concordia.agents import entity_agent_with_logging +from concordia.typing import entity_component + + +def save_to_json( + agent: entity_agent_with_logging.EntityAgentWithLogging, +) -> str: + """Saves an agent to JSON data. + + This function saves the agent's state to a JSON string, which can be loaded + afterwards with `rebuild_from_json`. The JSON data + includes the state of the agent's context components, act component, memory, + agent name and the initial config. The clock, model and embedder are not + saved and will have to be provided when the agent is rebuilt. The agent must + be in the `READY` phase to be saved. + + Args: + agent: The agent to save. + + Returns: + A JSON string representing the agent's state. + + Raises: + ValueError: If the agent is not in the READY phase. + """ + + if agent.get_phase() != entity_component.Phase.READY: + raise ValueError('The agent must be in the `READY` phase to be saved.') + + data = { + component_name: agent.get_component(component_name).get_state() + for component_name in agent.get_all_context_components() + } + + data['act_component'] = agent.get_act_component().get_state() + + config = agent.get_config() + if config is not None: + data['agent_config'] = config.to_dict() + + return json.dumps(data) From bfde374ae9cbae6029c27be22e6464215430054a Mon Sep 17 00:00:00 2001 From: "Joel Z. Leibo" Date: Wed, 13 Nov 2024 16:52:30 -0800 Subject: [PATCH 10/14] Fix another pytype issue PiperOrigin-RevId: 696315992 Change-Id: I4cb543da859c4cb3d72b8a39767b6656952fbe3e --- concordia/components/game_master/conversation.py | 4 +++- concordia/components/game_master/direct_effect.py | 3 ++- concordia/environment/scenes/runner.py | 3 ++- concordia/thought_chains/thought_chains.py | 3 ++- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/concordia/components/game_master/conversation.py b/concordia/components/game_master/conversation.py index 5bd197a..5b3f701 100644 --- a/concordia/components/game_master/conversation.py +++ b/concordia/components/game_master/conversation.py @@ -20,6 +20,7 @@ from concordia import components as generic_components from concordia.agents import deprecated_agent +from concordia.agents import entity_agent from concordia.associative_memory import associative_memory from concordia.associative_memory import blank_memories from concordia.clocks import game_clock @@ -33,6 +34,7 @@ from concordia.utils import helper_functions import termcolor + CONVERSATIONALIST_STYLES = ( 'succinct', 'laconic', @@ -89,7 +91,7 @@ class Conversation(component.Component): def __init__( self, - players: Sequence[deprecated_agent.BasicAgent], + players: Sequence[deprecated_agent.BasicAgent | entity_agent.EntityAgent], model: language_model.LanguageModel, memory: associative_memory.AssociativeMemory, clock: game_clock.MultiIntervalClock, diff --git a/concordia/components/game_master/direct_effect.py b/concordia/components/game_master/direct_effect.py index e9ee5e2..0e8b9b9 100644 --- a/concordia/components/game_master/direct_effect.py +++ b/concordia/components/game_master/direct_effect.py @@ -20,6 +20,7 @@ import datetime from concordia.agents import deprecated_agent +from concordia.agents import entity_agent from concordia.associative_memory import associative_memory from concordia.document import interactive_document from concordia.language_model import language_model @@ -37,7 +38,7 @@ class DirectEffect(component.Component): def __init__( self, - players: Sequence[deprecated_agent.BasicAgent], + players: Sequence[deprecated_agent.BasicAgent | entity_agent.EntityAgent], clock_now: Callable[[], datetime.datetime], model: language_model.LanguageModel, memory: associative_memory.AssociativeMemory, diff --git a/concordia/environment/scenes/runner.py b/concordia/environment/scenes/runner.py index 1ce4401..4beb4b0 100644 --- a/concordia/environment/scenes/runner.py +++ b/concordia/environment/scenes/runner.py @@ -17,6 +17,7 @@ from collections.abc import Mapping, Sequence from concordia.agents import deprecated_agent +from concordia.agents import entity_agent from concordia.environment import game_master from concordia.typing import clock as game_clock from concordia.typing import logging as logging_lib @@ -60,7 +61,7 @@ def _get_interscene_messages( def run_scenes( environment: game_master.GameMaster, scenes: Sequence[scene_lib.SceneSpec], - players: Sequence[deprecated_agent.BasicAgent], + players: Sequence[deprecated_agent.BasicAgent | entity_agent.EntityAgent], clock: game_clock.GameClock, verbose: bool = False, compute_metrics: Mapping[str, logging_lib.Metric] | None = None, diff --git a/concordia/thought_chains/thought_chains.py b/concordia/thought_chains/thought_chains.py index acefcf8..92ea736 100644 --- a/concordia/thought_chains/thought_chains.py +++ b/concordia/thought_chains/thought_chains.py @@ -19,6 +19,7 @@ import random from concordia.agents import deprecated_agent +from concordia.agents import entity_agent from concordia.document import interactive_document from concordia.language_model import language_model from concordia.typing import agent as agent_types @@ -322,7 +323,7 @@ class AccountForAgencyOfOthers: def __init__( self, model: language_model.LanguageModel, - players: Sequence[deprecated_agent.BasicAgent], + players: Sequence[deprecated_agent.BasicAgent | entity_agent.EntityAgent], verbose: bool = False, ): self._model = model From ad7949468a1632884689387565d2a7c445c7b067 Mon Sep 17 00:00:00 2001 From: Sasha Vezhnevets Date: Thu, 14 Nov 2024 04:27:57 -0800 Subject: [PATCH 11/14] Move pub closure probability and friendship matrix to time and place module into the time and place module. PiperOrigin-RevId: 696472872 Change-Id: Ia4e940afe7a906350592abf80bb05341f9df2e6c --- .../pub_coordination_london_closures.py | 66 +++++++++++++++++++ .../modular/environment/pub_coordination.py | 50 +++++++------- .../environment/pub_coordination_closures.py | 38 ----------- .../pub_coordination_closures_friendships.py | 40 ----------- .../pub_coordination_friendships.py | 35 ---------- examples/modular/scenario/scenarios.py | 16 +---- 6 files changed, 93 insertions(+), 152 deletions(-) create mode 100644 examples/modular/environment/modules/pub_coordination_london_closures.py delete mode 100644 examples/modular/environment/pub_coordination_closures.py delete mode 100644 examples/modular/environment/pub_coordination_closures_friendships.py delete mode 100644 examples/modular/environment/pub_coordination_friendships.py diff --git a/examples/modular/environment/modules/pub_coordination_london_closures.py b/examples/modular/environment/modules/pub_coordination_london_closures.py new file mode 100644 index 0000000..b2317b9 --- /dev/null +++ b/examples/modular/environment/modules/pub_coordination_london_closures.py @@ -0,0 +1,66 @@ +# Copyright 2024 DeepMind Technologies Limited. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""A set of pub names and reasons to like them.""" + +import random +from examples.modular.environment import pub_coordination +from examples.modular.environment.modules import pub_coordination_london + +YEAR = pub_coordination_london.YEAR +MONTH = pub_coordination_london.MONTH +DAY = pub_coordination_london.DAY + +NUM_PUBS = pub_coordination_london.NUM_PUBS + + +def sample_parameters(seed: int | None = None): + """Samples a set of parameters for the world configuration.""" + seed = seed if seed is not None else random.getrandbits(63) + rng = random.Random(seed) + + pubs = rng.sample( + list(pub_coordination_london.PUB_PREFERENCES.keys()), + pub_coordination_london.NUM_PUBS, + ) + pub_preferences = { + k: pub_coordination_london.PUB_PREFERENCES[k] for k in pubs + } + + config = pub_coordination.WorldConfig( + year=pub_coordination_london.YEAR, + location="London", + event="European football cup", + game_countries=pub_coordination_london.EURO_CUP_COUNTRIES, + venues=pubs, + venue_preferences=pub_preferences, + social_context=pub_coordination_london.SOCIAL_CONTEXT, + random_seed=seed, + pub_closed_probability=0.7 + ) + + all_names = list(pub_coordination_london.MALE_NAMES) + list( + pub_coordination_london.FEMALE_NAMES + ) + + rng.shuffle(all_names) + config.people = all_names + + for _, name in enumerate(pub_coordination_london.MALE_NAMES): + config.person_data[name] = {"gender": "male"} + for _, name in enumerate(pub_coordination_london.FEMALE_NAMES): + config.person_data[name] = {"gender": "female"} + config.player_who_knows_closed_pub = all_names[0] + + return config diff --git a/examples/modular/environment/pub_coordination.py b/examples/modular/environment/pub_coordination.py index 57cbf64..fcbff2d 100644 --- a/examples/modular/environment/pub_coordination.py +++ b/examples/modular/environment/pub_coordination.py @@ -89,6 +89,9 @@ class WorldConfig: num_supporting_players: int = 1 num_games: int = 3 random_seed: int = 42 + pub_closed_probability: float = 0.0 + player_who_knows_closed_pub: str | None = None + relationship_matrix: Mapping[str, Mapping[str, float]] | None = None def get_shared_memories_and_context( @@ -208,7 +211,11 @@ def configure_players(sampled_settings: Any, rng: random.Random) -> tuple[ for i in range(sampled_settings.num_main_players): name = names[i] - favorite_pub = sampled_settings.venues[i % num_pubs] + if 'favorite_pub' in sampled_settings.person_data[name]: + favorite_pub = sampled_settings.person_data[name]['favorite_pub'] + else: + favorite_pub = sampled_settings.venues[i % num_pubs] + gender = sampled_settings.person_data[name]['gender'] config = configure_player( name, @@ -225,7 +232,10 @@ def configure_players(sampled_settings: Any, rng: random.Random) -> tuple[ for i in range(sampled_settings.num_supporting_players): name = names[sampled_settings.num_main_players + i] gender = sampled_settings.person_data[name]['gender'] - favorite_pub = sampled_settings.venues[1] + if 'favorite_pub' in sampled_settings.person_data[name]: + favorite_pub = sampled_settings.person_data[name]['favorite_pub'] + else: + favorite_pub = sampled_settings.venues[1] config = configure_player( name, gender, @@ -334,7 +344,7 @@ def add_choice_scene_spec( scene_type_name: str, pubs: Sequence[str], rng: random.Random, - use_relational_matrix: bool = False, + relationship_matrix: Mapping[str, Mapping[str, float]] | None, verbose: bool = False, ) -> tuple[scene_lib.SceneTypeSpec, CoordinationPayoffs]: """Add a minigame scene spec. @@ -350,7 +360,7 @@ def add_choice_scene_spec( scene_type_name: the name of the scene type. pubs: the pubs to use. rng: the random number generator to use. - use_relational_matrix: whether to use relational matrix or not. + relationship_matrix: whether to use relational matrix or not. verbose: whether to print verbose output or not. Returns: @@ -366,10 +376,6 @@ def add_choice_scene_spec( } names = [cfg.name for cfg in player_configs] - if use_relational_matrix: - relational_matrix = sample_symmetric_relationship_matrix(names, rng) - else: - relational_matrix = None coordination_payoffs = CoordinationPayoffs( model=model, @@ -380,7 +386,7 @@ def add_choice_scene_spec( players=players, acting_player_names=[cfg.name for cfg in player_configs], outcome_summarization_fn=outcome_summary_fn, - relational_matrix=relational_matrix, + relational_matrix=relationship_matrix, clock_now=clock.now, name='scoring function', verbose=verbose, @@ -400,9 +406,9 @@ def add_choice_scene_spec( verbose=verbose, ) - if use_relational_matrix: + if relationship_matrix: friendship_statements = generate_relationship_statements( - names, relational_matrix, rng + names, relationship_matrix, rng ) else: friendship_statements = {name: [''] for name in names} @@ -431,10 +437,8 @@ def configure_scenes( main_player_configs: Sequence[formative_memories.AgentConfig], supporting_player_configs: Sequence[formative_memories.AgentConfig], start_time: datetime.datetime, - pub_closed_probability: float, sampled_settings: Any, rng: random.Random, - use_relational_matrix: bool = False, ) -> tuple[ Sequence[scene_lib.SceneSpec], Callable[[], Mapping[str, float]], @@ -450,10 +454,8 @@ def configure_scenes( main_player_configs: configs for the main characters supporting_player_configs: configs for the supporting characters start_time: the start time/date in the game world for the first scene - pub_closed_probability: the probability that a pub is closed sampled_settings: the sampled settings for the world configuration rng: the random number generator to use - use_relational_matrix: whether to use relational matrix or not Returns: scenes: a sequence of scene specifications @@ -465,6 +467,8 @@ def configure_scenes( scenes = [] pubs = sampled_settings.venues + pub_closed_probability = sampled_settings.pub_closed_probability + secondary_environments = [] for i in range(sampled_settings.num_games): @@ -492,8 +496,11 @@ def configure_scenes( } if closed_pub: - players_in_the_know = player_configs[0] - player_name = players_in_the_know.name + if sampled_settings.player_who_knows_closed_pub: + player_name = sampled_settings.player_who_knows_closed_pub + else: + player_name = rng.choice(player_configs).name + per_player_premise[player_name].append( f'{player_name} have learnt that {closed_pub} is closed today. Going' ' there would be a bad idea.' @@ -521,7 +528,7 @@ def configure_scenes( scene_type_name=DECISION_SCENE_TYPE, pubs=pubs, rng=rng, - use_relational_matrix=use_relational_matrix, + relationship_matrix=sampled_settings.relationship_matrix, ) coordination_payoffs.append(this_coordination_payoff) scene_specs[DECISION_SCENE_TYPE] = choice_scene_spec @@ -685,8 +692,6 @@ def __init__( resident_visitor_modules: Sequence[types.ModuleType] | None = None, supporting_agent_module: types.ModuleType | None = None, time_and_place_module: str | None = None, - pub_closed_probability: float = 0.0, - use_relational_matrix: bool = False, seed: int | None = None, ): """Initialize the simulation object. @@ -711,9 +716,6 @@ def __init__( time_and_place_module: optionally, specify a module containing settings that create a sense of setting in a specific time and place. If not specified, a random module will be chosen from the default options. - pub_closed_probability: the probability that a pub is closed. Zero by - default. - use_relational_matrix: whether to use relational matrix or not. seed: the random seed to use. """ # Support for these parameters will be added in a future addition coming @@ -905,9 +907,7 @@ def __init__( supporting_player_configs=supporting_player_configs, sampled_settings=sampled_settings, start_time=start_time, - pub_closed_probability=pub_closed_probability, rng=self._rng, - use_relational_matrix=use_relational_matrix, ) ) diff --git a/examples/modular/environment/pub_coordination_closures.py b/examples/modular/environment/pub_coordination_closures.py deleted file mode 100644 index 18b3b1a..0000000 --- a/examples/modular/environment/pub_coordination_closures.py +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright 2024 DeepMind Technologies Limited. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""A Concordia Environment Configuration.""" - -from examples.modular.environment import pub_coordination - - -class Simulation(pub_coordination.Simulation): - """Simulation with pub closures.""" - - def __init__( - self, - **kwargs, - ): - """Initialize the simulation object. - - The launch script assumes this API object has a run() method. - - Args: - **kwargs: arguments to pass to the base class. - """ - - super().__init__( - pub_closed_probability=0.7, - **kwargs, - ) diff --git a/examples/modular/environment/pub_coordination_closures_friendships.py b/examples/modular/environment/pub_coordination_closures_friendships.py deleted file mode 100644 index 83f2928..0000000 --- a/examples/modular/environment/pub_coordination_closures_friendships.py +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright 2024 DeepMind Technologies Limited. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""A Concordia Environment Configuration.""" - - -from examples.modular.environment import pub_coordination - - -class Simulation(pub_coordination.Simulation): - """Simulation with pub closures.""" - - def __init__( - self, - **kwargs, - ): - """Initialize the simulation object. - - The launch script assumes this API object has a run() method. - - Args: - **kwargs: arguments to pass to the base class. - """ - - super().__init__( - pub_closed_probability=0.7, - use_relational_matrix=True, - **kwargs, - ) diff --git a/examples/modular/environment/pub_coordination_friendships.py b/examples/modular/environment/pub_coordination_friendships.py deleted file mode 100644 index 61650ff..0000000 --- a/examples/modular/environment/pub_coordination_friendships.py +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright 2024 DeepMind Technologies Limited. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""A Concordia Environment Configuration.""" - -from examples.modular.environment import pub_coordination - - -class Simulation(pub_coordination.Simulation): - """Simulation with pub closures.""" - - def __init__(self, **kwargs): - """Initialize the simulation object. - - The launch script assumes this API object has a run() method. - - Args: - **kwargs: arguments to pass to the base class. - """ - - super().__init__( - use_relational_matrix=True, - **kwargs, - ) diff --git a/examples/modular/scenario/scenarios.py b/examples/modular/scenario/scenarios.py index 9818dec..c274c57 100644 --- a/examples/modular/scenario/scenarios.py +++ b/examples/modular/scenario/scenarios.py @@ -102,18 +102,6 @@ class ScenarioConfig: environment='pub_coordination', supporting_agent_module='basic_puppet_agent', ), - pub_coordination_closures=SubstrateConfig( - description=( - 'pub attendance coordination with one pub sometimes being closed' - ), - environment='pub_coordination_closures', - supporting_agent_module='basic_puppet_agent', - ), - pub_coordination_friendships=SubstrateConfig( - description='pub attendance coordination with friendship network', - environment='pub_coordination_friendships', - supporting_agent_module='basic_puppet_agent', - ), haggling=SubstrateConfig( description='haggling over a price', environment='haggling', @@ -218,9 +206,9 @@ class ScenarioConfig: ' with a chance of a pub being closed and a' ' rational visitor agent and a stubborn on supporting agent.' ), - substrate_config=SUBSTRATE_CONFIGS['pub_coordination_closures'], + substrate_config=SUBSTRATE_CONFIGS['pub_coordination'], background_agent_module='rational_agent', - time_and_place_module='pub_coordination_london', + time_and_place_module='pub_coordination_london_closures', focal_is_resident=False, tags=('coordination', 'persuasion', 'social networks'), ), From 4c56926437791d95ff097de03fbe3a2a326ece68 Mon Sep 17 00:00:00 2001 From: Sasha Vezhnevets Date: Fri, 15 Nov 2024 07:31:13 -0800 Subject: [PATCH 12/14] improve messaging to the agents when using friendship matrix PiperOrigin-RevId: 696881816 Change-Id: I358db06527eef9336b50218e8f9dec3b792bdf41 --- .../modular/environment/pub_coordination.py | 66 ++++++++++++++----- 1 file changed, 51 insertions(+), 15 deletions(-) diff --git a/examples/modular/environment/pub_coordination.py b/examples/modular/environment/pub_coordination.py index fcbff2d..bfc25df 100644 --- a/examples/modular/environment/pub_coordination.py +++ b/examples/modular/environment/pub_coordination.py @@ -115,6 +115,7 @@ def configure_player( favorite_pub: str, is_main: bool, all_player_names_str: str, + friend_names_str: str, pub_preferences: dict[str, Sequence[str]], year: int, rng: random.Random, @@ -126,7 +127,9 @@ def configure_player( gender: the gender of the player favorite_pub: the favorite pub of the player is_main: whether the player is a main character or not - all_player_names_str: the names of all the players in one string + all_player_names_str: the names of all the players in one comma-separated + string + friend_names_str: the names of the friends in one comma-separated string pub_preferences: the preferences of all the pubs year: the year of the simulation to sample the age of the players rng: the random number generator to use @@ -152,7 +155,11 @@ def configure_player( _CALL_TO_ACTION.format(name=name): favorite_pub } extras['favourite_pub'] = favorite_pub - + goal = ( + f'Have a good time. To have a good time, {name} would like to' + f' watch the game in the same pub as {friend_names_str}.' + f' {name} would prefer everyone went to {favorite_pub}.' + ) config = formative_memories.AgentConfig( name=name, gender=gender, @@ -162,30 +169,50 @@ def configure_player( day=rng.randint(1, 28), ), formative_ages=[16, 20], - goal=( - f'Have a good time. To have a good time, {name} would like to' - f' watch the game in the same pub as {all_player_names_str}.' - f' {name} would prefer everyone went to {favorite_pub}.' - ), + goal=goal, context=( - f"{all_player_names_str}' are best friends. {name} has" - f' a favorite pub which is {favorite_pub}. They are also aware of the' - f' following:{reasons}' + f"{all_player_names_str}' are close friends. {name} has" + f' a favorite pub which is {favorite_pub}. They love that pub for the' + f' following reasons: {reasons}' ), traits=( f"{name}'s personality is like " + player_traits_and_styles.get_trait(flowery=True) ), extras=extras, - specific_memories=( - f'[goal] {name} goals is to have a good time. {name} would like to' - f' watch the game in the same pub as {all_player_names_str}.' - f' {name} would prefer everyone went to {favorite_pub}.' - ), + specific_memories=f'[goal] {goal}', ) return config +def _get_friends_names(name, all_names, relationship_matrix): + """Get the names of the friends of a player. + + Args: + name: name of the player + all_names: names of all the players + relationship_matrix: relationship matrix of the players + + Returns: + names of the friends of the player from the relationship matrix or if it + is None, then all the names that are not the player itself + """ + if ( + relationship_matrix + and name in relationship_matrix + ): + direct_friends = [] + for friend_name in relationship_matrix[name]: + if ( + relationship_matrix[name][friend_name] == 1.0 + and friend_name != name + ): + direct_friends.append(friend_name) + return ', '.join(direct_friends) + else: + return ', '.join([x for x in all_names if x != name]) + + def configure_players(sampled_settings: Any, rng: random.Random) -> tuple[ list[formative_memories.AgentConfig], list[formative_memories.AgentConfig], @@ -216,6 +243,9 @@ def configure_players(sampled_settings: Any, rng: random.Random) -> tuple[ else: favorite_pub = sampled_settings.venues[i % num_pubs] + friend_names = _get_friends_names( + name, names, sampled_settings.relationship_matrix + ) gender = sampled_settings.person_data[name]['gender'] config = configure_player( name, @@ -223,6 +253,7 @@ def configure_players(sampled_settings: Any, rng: random.Random) -> tuple[ favorite_pub, is_main=True, all_player_names_str=all_players, + friend_names_str=friend_names, pub_preferences=sampled_settings.venue_preferences, year=sampled_settings.year, rng=rng, @@ -236,12 +267,17 @@ def configure_players(sampled_settings: Any, rng: random.Random) -> tuple[ favorite_pub = sampled_settings.person_data[name]['favorite_pub'] else: favorite_pub = sampled_settings.venues[1] + + friend_names = _get_friends_names( + name, names, sampled_settings.relationship_matrix + ) config = configure_player( name, gender, favorite_pub, is_main=False, all_player_names_str=all_players, + friend_names_str=friend_names, pub_preferences=sampled_settings.venue_preferences, year=sampled_settings.year, rng=rng, From 9ca553f641d7ebb23a60935731c1f2c192569bfc Mon Sep 17 00:00:00 2001 From: Sasha Vezhnevets Date: Fri, 15 Nov 2024 08:28:32 -0800 Subject: [PATCH 13/14] Move support agent configuration into time and place module and minor improvements - Make clear prices are integers - add extra parametrisation for prices - update the haggling scenario accordingly PiperOrigin-RevId: 696896500 Change-Id: I06e8d41a6f8c753dcb52ab824a26ab04103c9a68 --- examples/modular/environment/haggling.py | 85 +++++++++++++------ .../modules/fruitville_haggling_gullible.py | 17 ++++ 2 files changed, 76 insertions(+), 26 deletions(-) diff --git a/examples/modular/environment/haggling.py b/examples/modular/environment/haggling.py index 532dc19..d55786c 100644 --- a/examples/modular/environment/haggling.py +++ b/examples/modular/environment/haggling.py @@ -22,7 +22,7 @@ import functools import random import types -from typing import Union +from typing import Union, Any from concordia.agents import entity_agent_with_logging from concordia.associative_memory import associative_memory @@ -79,9 +79,13 @@ class WorldConfig: num_supporting_players: The number of supporting players in the scenario. only_match_with_support: Whether to only match with supporting players. buyer_base_reward_min: The minimum base reward for the buyer. + buyer_base_reward_max: The maximum base reward for the buyer. + seller_base_reward_min: The minimum base reward for the seller. seller_base_reward_max: The maximum base reward for the seller. num_games: The number of games to play. num_main_players: The number of main players in the scenario. + supporting_player_parameters: The parameters to be passed to the supporting + players factory build_agent function. random_seed: The random seed for the random number generator. """ @@ -96,9 +100,12 @@ class WorldConfig: num_supporting_players: int = 0 only_match_with_support: bool = False buyer_base_reward_min: int = 5 + buyer_base_reward_max: int = 6 + seller_base_reward_min: int = 1 seller_base_reward_max: int = 2 num_games: int = 2 num_main_players: int = 3 + supporting_player_parameters: dict[str, Any] | None = None random_seed: int = 42 @@ -133,8 +140,10 @@ def get_shared_memories_and_context(premise: str) -> tuple[Sequence[str], str]: shared_memories = [ 'Fruits are sold by weight.', ( - 'The price of one kilogram of fruit is, on average, 3 coins. 1 coin' - ' is really cheap and 5 coins is really expensive.' + 'The price of one kilogram of fruit is, on average, 3 coins. 1 coin ' + 'is really cheap and 5 coins is really expensive. The smallest value ' + 'of transaction is 1 coin, all prices have to be in multiples of 1 ' + 'coin. No fractional values are allowed.' ), ] shared_context = premise @@ -142,7 +151,12 @@ def get_shared_memories_and_context(premise: str) -> tuple[Sequence[str], str]: def configure_player( - name: str, gender: str, year: int, is_main: bool, rng: random.Random + name: str, + gender: str, + year: int, + is_main: bool, + rng: random.Random, + supporting_player_parameters: dict[str, Any] | None = None, ): """Configure a player. @@ -152,6 +166,7 @@ def configure_player( year: the year of the simulation to sample the age of the players is_main: whether the player is a main character or not rng: the random number generator to use + supporting_player_parameters: the parameters for the supporting player Returns: config: the config for the player @@ -160,16 +175,21 @@ def configure_player( 'player_specific_memories': [f'{name} always drives a hard bargain.'], 'main_character': is_main, } - if not is_main: - extras['fixed_response_by_call_to_action'] = { - f'Would {name} accept the offer?:': 'accept', - f'What price would {name} propose?:': '3 coins', + if not is_main and supporting_player_parameters: + + fixed_response = { + key: value.format(name=name) + for key, value in supporting_player_parameters[ + 'fixed_response_by_call_to_action' + ].items() } - extras['specific_memories'] = [ - f'{name} does not care about the price. {name} will accept any offer!' - ' They are very vocal about it and will not haggle and will praise any' - ' offer.' - ] + specific_memories = [] + + for memory in supporting_player_parameters['specific_memories']: + specific_memories.append(memory.format(name=name)) + + extras['fixed_response_by_call_to_action'] = fixed_response + extras['specific_memories'] = specific_memories return formative_memories.AgentConfig( name=name, @@ -232,6 +252,7 @@ def configure_players( sampled_settings.year, is_main=False, rng=rng, + supporting_player_parameters=sampled_settings.supporting_player_parameters, ) player_configs.append(config) @@ -429,8 +450,14 @@ def configure_scenes( for i in range(sampled_settings.num_games * len(pairs)): - buyer_base_reward = rng.randint(sampled_settings.buyer_base_reward_min, 6) - seller_base_reward = rng.randint(1, sampled_settings.seller_base_reward_max) + buyer_base_reward = rng.randint( + sampled_settings.buyer_base_reward_min, + sampled_settings.buyer_base_reward_max, + ) + seller_base_reward = rng.randint( + sampled_settings.seller_base_reward_min, + sampled_settings.seller_base_reward_max, + ) this_game_players = pairs[i % len(pairs)] @@ -690,26 +717,32 @@ def __init__( player_config.name ), ) - explicit_preference = agent_components.constant.Constant( - pre_act_key='Explicit preference', - state=( - f'{player_config.name} will accept any offer! They are very vocal' - ' about it and will not haggle and will praise any offer.' - ), - ) + additional_components = { + 'Guiding principle of good conversation': conversation_style + } + if ( + 'explciti_preference_component' + in sampled_settings.supporting_player_parameters + ): + explicit_preference = agent_components.constant.Constant( + pre_act_key='Explicit preference', + state=sampled_settings.supporting_player_parameters[ + 'explciti_preference_component' + ], + ) + additional_components['Explicit preference'] = explicit_preference + player = self._build_supporting_agent( config=player_config, model=self._model, memory=self._all_memories[player_config.name], clock=self._clock, update_time_interval=MAJOR_TIME_STEP, - additional_components={ - 'Guiding principle of good conversation': conversation_style, - 'Explicit preference': explicit_preference, - }, + additional_components=additional_components, fixed_response_by_call_to_action=player_config.extras[ 'fixed_response_by_call_to_action' ], + search_in_prompt=True, ) supporting_players.append(player) diff --git a/examples/modular/environment/modules/fruitville_haggling_gullible.py b/examples/modular/environment/modules/fruitville_haggling_gullible.py index 85b8ab0..65e8f09 100644 --- a/examples/modular/environment/modules/fruitville_haggling_gullible.py +++ b/examples/modular/environment/modules/fruitville_haggling_gullible.py @@ -123,7 +123,24 @@ def sample_parameters(seed: int | None = None): buyer_base_reward_min=6, seller_base_reward_max=1, only_match_with_support=True, + supporting_player_parameters={ + "fixed_response_by_call_to_action": { + "Would {name} accept the offer?:": "accept", + "What price would {name} propose?:": "3 coins", + }, + "specific_memories": [ + "{name} does not care about the price. {name} will accept any" + " offer! They are very vocal about it and will not haggle and" + " will praise any offer." + ], + "explciti_preference_component": ( + "{name} does not care about the price. {name} will accept any" + " offer! They are very vocal about it and will not haggle and" + " will praise any offer." + ), + }, ) + all_names = list(MALE_NAMES) + list(FEMALE_NAMES) rng = random.Random(config.random_seed) rng.shuffle(all_names) From b75846806fa5098a7b302ec7d4cf2f1e17a21fbc Mon Sep 17 00:00:00 2001 From: Sasha Vezhnevets Date: Fri, 15 Nov 2024 09:34:29 -0800 Subject: [PATCH 14/14] fix sampling of seller price PiperOrigin-RevId: 696913804 Change-Id: I333b5f1300ce2ef343f71e5166d9346f5f4eeb0f --- examples/modular/environment/haggling_multi_item.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/modular/environment/haggling_multi_item.py b/examples/modular/environment/haggling_multi_item.py index 67c674d..89af45a 100644 --- a/examples/modular/environment/haggling_multi_item.py +++ b/examples/modular/environment/haggling_multi_item.py @@ -446,7 +446,7 @@ def configure_scenes( for item in sampled_settings.items_for_sale } seller_base_reward_per_item = { - item: rng.randint(sampled_settings.seller_base_reward_max, 6) + item: rng.randint(1, sampled_settings.seller_base_reward_max) for item in sampled_settings.items_for_sale }