Skip to content

Commit 40f6c61

Browse files
jzleibocopybara-github
authored andcommitted
[unstable] Improve more game master components especially 'next_action_spec'.
PiperOrigin-RevId: 735605779 Change-Id: Idcccdd9f6bb880881c6bfe7cd1c5d4b5d67a8217
1 parent b4bf4e0 commit 40f6c61

File tree

13 files changed

+762
-91
lines changed

13 files changed

+762
-91
lines changed

concordia/components/game_master/unstable/event_resolution.py

+5-6
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from concordia.components.agent.unstable import memory as memory_component
2222
from concordia.document import interactive_document
2323
from concordia.language_model import language_model
24-
from concordia.thought_chains import thought_chains
24+
from concordia.thought_chains.unstable import thought_chains
2525
from concordia.typing import logging
2626
from concordia.typing.unstable import entity as entity_lib
2727
from concordia.typing.unstable import entity_component
@@ -98,7 +98,6 @@ def pre_act(
9898
action_spec: entity_lib.ActionSpec,
9999
) -> str:
100100
result = ''
101-
prompt_to_log = ''
102101
self._active_entity_name = None
103102
self._putative_action = None
104103
if action_spec.output_type == entity_lib.OutputType.RESOLVE:
@@ -124,10 +123,10 @@ def pre_act(
124123
result = f'{self._pre_act_key}: {event_statement}'
125124
prompt_to_log = prompt.view().text()
126125

127-
self._logging_channel(
128-
{'Key': self._pre_act_key,
129-
'Value': result,
130-
'Prompt': prompt_to_log})
126+
self._logging_channel(
127+
{'Key': self._pre_act_key,
128+
'Value': result,
129+
'Prompt': prompt_to_log})
131130
return result
132131

133132
def get_active_entity_name(self) -> str | None:

concordia/components/game_master/unstable/instructions.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,11 @@
3939
)
4040
NEXT_ACTING_RESPONSE_EXAMPLE = 'Rowan'
4141
NEXT_ACTION_SPEC_RESPONSE_EXAMPLE_1 = (
42-
'type: choice options: open the door, bar the door, flee'
42+
'prompt: What would Kerensa do?;;type: choice;;options: '
43+
'open the door, bar the door, flee'
4344
)
44-
NEXT_ACTION_SPEC_RESPONSE_EXAMPLE_2 = 'type: free'
45+
NEXT_ACTION_SPEC_RESPONSE_EXAMPLE_2 = (
46+
'prompt: What would Morwenna say?;;type: free')
4547
RESOLVE_RESPONSE_PUTATIVE_ACTION_EXAMPLE = (
4648
'What is Yorik attempting to do?\n'
4749
'Yorik investigates the discarded machinery.')

concordia/components/game_master/unstable/next_acting.py

+155-12
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,25 @@
2121
from concordia.components.agent.unstable import memory as memory_component
2222
from concordia.document import interactive_document
2323
from concordia.environment.scenes.unstable import runner as scene_runner
24+
from concordia.environment.unstable import engine as engine_lib
2425
from concordia.language_model import language_model
2526
from concordia.typing import logging
2627
from concordia.typing.unstable import entity as entity_lib
2728
from concordia.typing.unstable import entity_component
29+
from concordia.typing.unstable import scene as scene_lib
2830

2931

3032
DEFAULT_NEXT_ACTING_COMPONENT_NAME = '__next_acting__'
3133
# Initiative is the Dungeons & Dragons term for the rule system that controls
3234
# turn taking.
3335
DEFAULT_NEXT_ACTING_PRE_ACT_KEY = '\nInitiative'
3436

37+
DEFAULT_NEXT_ACTION_SPEC_COMPONENT_NAME = '__next_action_spec__'
38+
DEFAULT_NEXT_ACTION_SPEC_PRE_ACT_KEY = '\nType of action'
39+
40+
_DEFAULT_CALL_TO_NEXT_ACTION_SPEC = (
41+
'In what action spec format should {name} respond?')
42+
3543

3644
class NextActing(entity_component.ContextComponent):
3745
"""A component that decides whose turn is next.
@@ -82,7 +90,6 @@ def pre_act(
8290
action_spec: entity_lib.ActionSpec,
8391
) -> str:
8492
result = ''
85-
prompt_to_log = ''
8693
if action_spec.output_type == entity_lib.OutputType.NEXT_ACTING:
8794
entity_name = self.get_entity().name
8895
prompt = interactive_document.InteractiveDocument(self._model)
@@ -96,12 +103,7 @@ def pre_act(
96103
question='Whose turn is next?',
97104
answers=self._player_names)
98105
result = self._player_names[idx]
99-
prompt_to_log = prompt.view().text()
100106

101-
self._logging_channel(
102-
{'Key': self._pre_act_key,
103-
'Value': result,
104-
'Prompt': prompt_to_log})
105107
return result
106108

107109

@@ -162,7 +164,6 @@ def pre_act(
162164
action_spec: entity_lib.ActionSpec,
163165
) -> str:
164166
result = ''
165-
prompt_to_log = ''
166167
if action_spec.output_type == entity_lib.OutputType.NEXT_ACTING:
167168
entity_name = self.get_entity().name
168169
prompt = interactive_document.InteractiveDocument(self._model)
@@ -178,10 +179,152 @@ def pre_act(
178179
question='Whose turn is next?',
179180
answers=scene_participants)
180181
result = scene_participants[idx]
181-
prompt_to_log = prompt.view().text()
182182

183-
self._logging_channel(
184-
{'Key': self._pre_act_key,
185-
'Value': result,
186-
'Prompt': prompt_to_log})
187183
return result
184+
185+
186+
class NextActionSpec(entity_component.ContextComponent):
187+
"""A component that decides whose turn is next.
188+
"""
189+
190+
def __init__(
191+
self,
192+
model: language_model.LanguageModel,
193+
player_names: Sequence[str],
194+
components: Mapping[
195+
entity_component.ComponentName, str
196+
] = types.MappingProxyType({}),
197+
call_to_next_action_spec: str = _DEFAULT_CALL_TO_NEXT_ACTION_SPEC,
198+
pre_act_key: str = DEFAULT_NEXT_ACTION_SPEC_PRE_ACT_KEY,
199+
logging_channel: logging.LoggingChannel = logging.NoOpLoggingChannel,
200+
):
201+
"""Initializes the component.
202+
203+
Args:
204+
model: The language model to use for the component.
205+
player_names: Names of players to choose from.
206+
components: The components to condition the answer on. This is a mapping
207+
of the component name to a label to use in the prompt.
208+
call_to_next_action_spec: prompt to use for the game master to decide on
209+
what action spec to use for the next turn. Will be formatted to
210+
substitute {name} for the name of the player whose turn is next.
211+
pre_act_key: Prefix to add to the output of the component when called
212+
in `pre_act`.
213+
logging_channel: The channel to use for debug logging.
214+
215+
Raises:
216+
ValueError: If the component order is not None and contains duplicate
217+
components.
218+
"""
219+
super().__init__()
220+
self._model = model
221+
self._player_names = player_names
222+
self._components = dict(components)
223+
self._call_to_next_action_spec = call_to_next_action_spec
224+
self._pre_act_key = pre_act_key
225+
self._logging_channel = logging_channel
226+
227+
def _get_named_component_pre_act_value(self, component_name: str) -> str:
228+
"""Returns the pre-act value of a named component of the parent entity."""
229+
return (
230+
self.get_entity().get_component(
231+
component_name, type_=action_spec_ignored.ActionSpecIgnored
232+
).get_pre_act_value()
233+
)
234+
235+
def pre_act(
236+
self,
237+
action_spec: entity_lib.ActionSpec,
238+
) -> str:
239+
result = ''
240+
if action_spec.output_type == entity_lib.OutputType.NEXT_ACTION_SPEC:
241+
entity_name = self.get_entity().name
242+
prompt = interactive_document.InteractiveDocument(self._model)
243+
component_states = '\n'.join([
244+
f"{entity_name}'s"
245+
f' {prefix}:\n{self._get_named_component_pre_act_value(key)}'
246+
for key, prefix in self._components.items()
247+
])
248+
prompt.statement(f'{component_states}\n')
249+
idx = prompt.multiple_choice_question(
250+
question='Whose turn is next?',
251+
answers=self._player_names)
252+
active_player = self._player_names[idx]
253+
prompt.statement(
254+
'Example formatted action specs:\n1). "prompt: p;;type: free"\n'
255+
'2). "prompt: p;;type: choice;;options: x, y, z".\nNote that p is a '
256+
'string of any length, typically a question, and x, y, z, etc are '
257+
'multiple choice answer responses. For instance, a valid format '
258+
'could be indicated as '
259+
'prompt: Where will Edgar go?;;type: choice;;'
260+
'options: home, London, Narnia, the third moon of Jupiter')
261+
result = prompt.open_question(
262+
question=self._call_to_next_action_spec.format(name=active_player),
263+
max_tokens=512)
264+
265+
return result
266+
267+
268+
class NextActionSpecFromSceneSpec(entity_component.ContextComponent):
269+
"""A component that decides the next action spec using the current scene spec.
270+
"""
271+
272+
def __init__(
273+
self,
274+
scenes: Sequence[scene_lib.ExperimentalSceneSpec],
275+
memory_component_name: str = (
276+
memory_component.DEFAULT_MEMORY_COMPONENT_NAME
277+
),
278+
pre_act_key: str = DEFAULT_NEXT_ACTION_SPEC_PRE_ACT_KEY,
279+
logging_channel: logging.LoggingChannel = logging.NoOpLoggingChannel,
280+
):
281+
"""Initializes the component.
282+
283+
Args:
284+
scenes: All scenes to be used in the episode.
285+
memory_component_name: The name of the memory component.
286+
pre_act_key: Prefix to add to the output of the component when called
287+
in `pre_act`.
288+
logging_channel: The channel to use for debug logging.
289+
290+
Raises:
291+
ValueError: If the component order is not None and contains duplicate
292+
components.
293+
"""
294+
super().__init__()
295+
self._memory_component_name = memory_component_name
296+
self._pre_act_key = pre_act_key
297+
self._logging_channel = logging_channel
298+
299+
# Extract all scene type specs from the provided scenes.
300+
self._scene_type_specs = {}
301+
for scene in scenes:
302+
self._scene_type_specs[scene.scene_type.name] = scene.scene_type
303+
304+
def _get_named_component_pre_act_value(self, component_name: str) -> str:
305+
"""Returns the pre-act value of a named component of the parent entity."""
306+
return (
307+
self.get_entity().get_component(
308+
component_name, type_=action_spec_ignored.ActionSpecIgnored
309+
).get_pre_act_value()
310+
)
311+
312+
def _get_current_scene_type(self) -> scene_lib.ExperimentalSceneTypeSpec:
313+
memory = self.get_entity().get_component(
314+
self._memory_component_name, type_=memory_component.Memory
315+
)
316+
scene_type_str = scene_runner.get_current_scene_type(memory=memory)
317+
scene_type_spec = self._scene_type_specs[scene_type_str]
318+
return scene_type_spec
319+
320+
def pre_act(
321+
self,
322+
action_spec: entity_lib.ActionSpec,
323+
) -> str:
324+
action_spec_string = ''
325+
if action_spec.output_type == entity_lib.OutputType.NEXT_ACTION_SPEC:
326+
scene_type_spec = self._get_current_scene_type()
327+
action_spec = scene_type_spec.action_spec
328+
action_spec_string = engine_lib.action_spec_to_string(action_spec)
329+
330+
return action_spec_string

concordia/components/game_master/unstable/switch_act.py

+13-7
Original file line numberDiff line numberDiff line change
@@ -173,9 +173,9 @@ def _next_entity_action_spec(
173173
action_spec: entity_lib.ActionSpec) -> str:
174174
context = self._context_for_action(contexts)
175175
if DEFAULT_NEXT_ACTION_SPEC_COMPONENT_NAME in contexts:
176-
# action_spec_string = _convert_to_string(
177-
# next_action_spec['scene_type'].action_spec)
178-
result = ''
176+
result = str(contexts[DEFAULT_NEXT_ACTION_SPEC_COMPONENT_NAME])
177+
if not result:
178+
result = f'prompt: {entity_lib.DEFAULT_CALL_TO_ACTION};;type: free'
179179
self._log(result, context)
180180
else:
181181
# YOLO case
@@ -185,12 +185,18 @@ def _next_entity_action_spec(
185185
# Then ask the GM to reformat their answer in whatever string format can
186186
# be used by the engine and its parser.
187187
chain_of_thought.statement(
188-
'Example formatted action specs:\n"type: free"\n'
189-
'"type: choice options: x, y, z"')
188+
'Example formatted action specs:\n1). "prompt: p;;type: free"\n'
189+
'2). "prompt: p;;type: choice;;options: x, y, z".\nNote that p is a '
190+
'string of any length, typically a question, and x, y, z, etc are '
191+
'multiple choice answer responses. For instance, a valid format '
192+
'could be indicated as '
193+
'prompt: Where will Edgar go?;;type: choice;;'
194+
'options: home, London, Narnia, the third moon of Jupiter')
190195
next_action_spec_string = chain_of_thought.open_question(
191-
question='Format the decision prompt type as an action spec.')
196+
question='In what action spec format should the next player respond?')
192197
if 'type:' not in next_action_spec_string:
193-
next_action_spec_string = 'type: free' + next_action_spec_string
198+
next_action_spec_string = (
199+
f'prompt: {entity_lib.DEFAULT_CALL_TO_ACTION};;type: free')
194200

195201
result = next_action_spec_string
196202
self._log(result, chain_of_thought)

concordia/environment/unstable/engine.py

+27-2
Original file line numberDiff line numberDiff line change
@@ -72,20 +72,45 @@ def run_loop(
7272
def action_spec_parser(next_action_spec_string: str) -> entity_lib.ActionSpec:
7373
"""Parse the next action spec string into an action spec."""
7474
if 'type: free' in next_action_spec_string:
75+
splits = next_action_spec_string.split(';;')
76+
if len(splits) != 2:
77+
call_to_action = entity_lib.DEFAULT_CALL_TO_ACTION
78+
else:
79+
call_to_action = splits[0].split('prompt: ')[1]
7580
return entity_lib.ActionSpec(
76-
call_to_action=next_action_spec_string,
81+
call_to_action=call_to_action,
7782
output_type=entity_lib.OutputType.FREE,
7883
)
7984
elif (
8085
'type: choice' in next_action_spec_string
8186
and 'options: ' in next_action_spec_string
8287
):
88+
splits = next_action_spec_string.split(';;')
89+
if 'prompt: ' not in next_action_spec_string:
90+
call_to_action = entity_lib.DEFAULT_CALL_TO_ACTION
91+
else:
92+
call_to_action = splits[0].split('prompt: ')[1]
8393
return entity_lib.ActionSpec(
84-
call_to_action=next_action_spec_string,
94+
call_to_action=call_to_action,
8595
output_type=entity_lib.OutputType.CHOICE,
8696
options=tuple(next_action_spec_string.split('options: ')[1].split(',')),
8797
)
8898
else:
8999
raise RuntimeError(
90100
'Invalid next action spec string: \"{}\"'.format(
91101
next_action_spec_string))
102+
103+
104+
def action_spec_to_string(action_spec: entity_lib.ActionSpec) -> str:
105+
"""Convert an action spec to a string."""
106+
if action_spec.output_type == entity_lib.OutputType.FREE:
107+
return f'prompt: {action_spec.call_to_action};;type: free'
108+
elif action_spec.output_type == entity_lib.OutputType.CHOICE:
109+
return (
110+
f'prompt: {action_spec.call_to_action};;type: choice options: '
111+
+ ', '.join(action_spec.options)
112+
)
113+
else:
114+
raise RuntimeError(
115+
'Invalid action spec output type: \"{}\"'.format(
116+
action_spec.output_type))

concordia/environment/unstable/engines/asynchronous.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
from typing import Any
2020

2121
from concordia.environment.unstable import engine as engine_lib
22-
from concordia.typing.unstable import agent as agent_lib
2322
from concordia.typing.unstable import entity as entity_lib
2423

2524

@@ -120,8 +119,8 @@ def terminate(self,
120119

121120
def run_loop(
122121
self,
123-
game_master: agent_lib.GenerativeAgent,
124-
entities: Sequence[agent_lib.GenerativeAgent],
122+
game_master: entity_lib.Entity,
123+
entities: Sequence[entity_lib.Entity],
125124
premise: str = '',
126125
max_steps: int = 100,
127126
verbose: bool = False,

0 commit comments

Comments
 (0)