diff --git a/concordia/agents/entity_agent.py b/concordia/agents/entity_agent.py index b69fc0f9..24d83c8f 100644 --- a/concordia/agents/entity_agent.py +++ b/concordia/agents/entity_agent.py @@ -156,10 +156,3 @@ def observe(self, observation: str) -> None: self._phase = component_v2.Phase.UPDATE self._parallel_call_('update') - - def get_last_log(self): - logs = self._parallel_call_('get_last_log') - return { - '__act__': self._act_component.get_last_log(), - **logs, - } diff --git a/concordia/agents/entity_agent_with_logging.py b/concordia/agents/entity_agent_with_logging.py new file mode 100644 index 00000000..99f9dc1b --- /dev/null +++ b/concordia/agents/entity_agent_with_logging.py @@ -0,0 +1,88 @@ +# 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. + +"""A modular entity agent using the new component system with side logging.""" + +from collections.abc import Mapping +import types +from typing import Any + +from concordia.agents import entity_agent +from concordia.typing import agent +from concordia.typing import component_v2 +from concordia.utils import measurements as measurements_lib + +import reactivex as rx + + +class EntityAgentWithLogging(entity_agent.EntityAgent, agent.GenerativeAgent): + """An agent that exposes the latest information of each component.""" + + def __init__( + self, + agent_name: str, + act_component: component_v2.ActingComponent, + context_processor: component_v2.ContextProcessorComponent | None = None, + context_components: Mapping[str, component_v2.ContextComponent] = ( + types.MappingProxyType({}) + ), + component_logging: measurements_lib.Measurements | None = None, + ): + """Initializes the agent. + + The passed components will be owned by this entity agent (i.e. their + `set_entity` method will be called with this entity as the argument). + + Whenever `get_last_log` is called, the latest values published in all the + channels in the given measurements object will be returned as a mapping of + channel name to value. + + Args: + agent_name: The name of the agent. + act_component: The component that will be used to act. + context_processor: The component that will be used to process contexts. If + None, a NoOpContextProcessor will be used. + context_components: The ContextComponents that will be used by the agent. + component_logging: The channels where components publish events. + """ + super().__init__(agent_name=agent_name, + act_component=act_component, + context_processor=context_processor, + context_components=context_components) + self._log: Mapping[str, Any] = {} + self._tick = rx.subject.Subject() + self._component_logging = component_logging + if self._component_logging is not None: + self._channel_names = list(self._component_logging.available_channels()) + channels = [ + self._component_logging.get_channel(channel) # pylint: disable=attribute-error pytype mistakenly forgets that `_component_logging` is not None. + for channel in self._channel_names + ] + rx.with_latest_from(self._tick, *channels).subscribe(self._set_log) + else: + self._channel_names = [] + + def _set_log(self, log: tuple[Any, ...]) -> None: + """Set the logging object to return from get_last_log. + + Args: + log: A tuple with the tick first, and the latest log from each component. + """ + tick_value, *channel_values = log + assert tick_value is None + self._log = dict(zip(self._channel_names, channel_values, strict=True)) + + def get_last_log(self): + self._tick.on_next(None) # Trigger the logging. + return self._log diff --git a/concordia/components/agent/v2/all_similar_memories.py b/concordia/components/agent/v2/all_similar_memories.py index 55be55aa..c07d396e 100644 --- a/concordia/components/agent/v2/all_similar_memories.py +++ b/concordia/components/agent/v2/all_similar_memories.py @@ -23,7 +23,7 @@ from concordia.document import interactive_document from concordia.language_model import language_model from concordia.memory_bank import legacy_associative_memory -import termcolor +from concordia.typing import logging _ASSOCIATIVE_RETRIEVAL = legacy_associative_memory.RetrieveAssociative() @@ -42,7 +42,7 @@ def __init__( ), num_memories_to_retrieve: int = 25, pre_act_key: str = 'Relevant memories', - verbose: bool = False, + logging_channel: logging.LoggingChannel = logging.NoOpLoggingChannel, ): """Initialize a component to report relevant memories (similar to a prompt). @@ -54,16 +54,15 @@ def __init__( num_memories_to_retrieve: The number of memories to retrieve. pre_act_key: Prefix to add to the output of the component when called in `pre_act`. - verbose: Whether to print the state of the component. + logging_channel: The channel to log debug information to. """ super().__init__(pre_act_key) - self._verbose = verbose self._model = model self._memory_component_name = memory_component_name self._state = '' self._components = dict(components) self._num_memories_to_retrieve = num_memories_to_retrieve - self._last_log = None + self._logging_channel = logging_channel def _make_pre_act_value(self) -> str: agent_name = self.get_entity().name @@ -108,21 +107,12 @@ def _make_pre_act_value(self) -> str: terminators=(), ) - if self._verbose: - print(termcolor.colored(prompt.view().text(), 'green'), end='') - print(termcolor.colored(f'Query: {query}\n', 'green'), end='') - print(termcolor.colored(new_prompt.view().text(), 'green'), end='') - print(termcolor.colored(result, 'green'), end='') - - self._last_log = { - 'State': result, + self._logging_channel({ + 'Key': self.get_pre_act_key(), + 'Value': result, 'Initial chain of thought': prompt.view().text().splitlines(), 'Query': f'{query}', 'Final chain of thought': new_prompt.view().text().splitlines(), - } + }) return result - - def get_last_log(self): - if self._last_log: - return self._last_log.copy() diff --git a/concordia/components/agent/v2/concat_act_component.py b/concordia/components/agent/v2/concat_act_component.py index ff1d4cdf..00ee2e8e 100644 --- a/concordia/components/agent/v2/concat_act_component.py +++ b/concordia/components/agent/v2/concat_act_component.py @@ -22,9 +22,12 @@ from concordia.typing import clock as game_clock from concordia.typing import component_v2 from concordia.typing import entity as entity_lib +from concordia.typing import logging from concordia.utils import helper_functions from typing_extensions import override +DEFAULT_PRE_ACT_KEY = 'Act' + class ConcatActComponent(component_v2.ActingComponent): """A component which concatenates contexts from context components. @@ -42,6 +45,8 @@ def __init__( model: language_model.LanguageModel, clock: game_clock.GameClock, component_order: Sequence[str] | None = None, + pre_act_key: str = DEFAULT_PRE_ACT_KEY, + logging_channel: logging.LoggingChannel = logging.NoOpLoggingChannel, ): """Initializes the agent. @@ -58,6 +63,8 @@ def __init__( component cannot appear twice in the component order. All components in the component order must be in the `ComponentContextMapping` passed to `get_action_attempt`. + pre_act_key: Prefix to add to the context of the component. + logging_channel: The channel to use for debug logging. Raises: ValueError: If the component order is not None and contains duplicate @@ -76,7 +83,8 @@ def __init__( + ', '.join(self._component_order) ) - self._last_log = None + self._pre_act_key = pre_act_key + self._logging_channel = logging_channel def _context_for_action( self, @@ -116,14 +124,14 @@ def get_action_attempt( max_tokens=2200, answer_prefix=output, ) - self._make_update_log(output, prompt) + self._log(output, prompt) return output elif action_spec.output_type == entity_lib.OutputType.CHOICE: idx = prompt.multiple_choice_question( question=call_to_action, answers=action_spec.options ) output = action_spec.options[idx] - self._make_update_log(output, prompt) + self._log(output, prompt) return output elif action_spec.output_type == entity_lib.OutputType.FLOAT: prefix = self.get_entity().name + ' ' @@ -132,7 +140,7 @@ def get_action_attempt( max_tokens=2200, answer_prefix=prefix, ) - self._make_update_log(sampled_text, prompt) + self._log(sampled_text, prompt) try: return str(float(sampled_text)) except ValueError: @@ -143,12 +151,11 @@ def get_action_attempt( 'Supported output types are: FREE, CHOICE, and FLOAT.' ) - def _make_update_log(self, - result: str, - prompt: interactive_document.InteractiveDocument): - self._last_log = {'Output': result, - 'Prompt': prompt.view().text().splitlines()} - - def get_last_log(self): - if self._last_log: - return self._last_log.copy() + def _log(self, + result: str, + prompt: interactive_document.InteractiveDocument): + self._logging_channel({ + 'Key': self._pre_act_key, + 'Value': result, + 'Prompt': prompt.view().text().splitlines(), + }) diff --git a/concordia/components/agent/v2/constant.py b/concordia/components/agent/v2/constant.py index 3c5d3224..1467a9b4 100644 --- a/concordia/components/agent/v2/constant.py +++ b/concordia/components/agent/v2/constant.py @@ -15,6 +15,7 @@ """A simple acting component that aggregates contexts from components.""" from concordia.components.agent.v2 import action_spec_ignored +from concordia.typing import logging DEFAULT_PRE_ACT_KEY = 'Constant' @@ -27,6 +28,7 @@ def __init__( self, state: str, pre_act_key: str = DEFAULT_PRE_ACT_KEY, + logging_channel: logging.LoggingChannel = logging.NoOpLoggingChannel, ): """Initializes the agent. @@ -34,6 +36,7 @@ def __init__( state: the state of the component. pre_act_key: Prefix to add to the output of the component when called in `pre_act`. + logging_channel: The channel to use for debug logging. Raises: ValueError: If the component order is not None and contains duplicate @@ -44,6 +47,3 @@ def __init__( def _make_pre_act_value(self) -> str: return self._state - - def get_last_log(self): - return {'State': self._state} diff --git a/concordia/components/agent/v2/identity.py b/concordia/components/agent/v2/identity.py index 402405a8..784051d6 100644 --- a/concordia/components/agent/v2/identity.py +++ b/concordia/components/agent/v2/identity.py @@ -23,8 +23,8 @@ from concordia.memory_bank import legacy_associative_memory from concordia.typing import component_v2 from concordia.typing import entity as entity_lib +from concordia.typing import logging from concordia.utils import concurrency -import termcolor DEFAULT_PRE_ACT_KEY = 'Identity characteristics' @@ -54,8 +54,7 @@ def __init__( ), num_memories_to_retrieve: int = 25, pre_act_key: str = DEFAULT_PRE_ACT_KEY, - verbose: bool = False, - log_color: str = 'green', + logging_channel: logging.LoggingChannel = logging.NoOpLoggingChannel, ): """Initialize an identity component. @@ -67,19 +66,16 @@ def __init__( num_memories_to_retrieve: how many related memories to retrieve per query pre_act_key: Prefix to add to the output of the component when called in `pre_act`. - verbose: whether or not to print the result for debugging - log_color: color to print the debug log + logging_channel: The channel to use for debug logging. """ super().__init__(pre_act_key) self._model = model self._memory_component_name = memory_component_name - self._last_log = None self._queries = queries self._num_memories_to_retrieve = num_memories_to_retrieve - self._verbose = verbose - self._log_color = log_color + self._logging_channel = logging_channel def _query_memory(self, query: str) -> str: agent_name = self.get_entity().name @@ -111,21 +107,10 @@ def _make_pre_act_value(self) -> str: [f'{query}: {result}' for query, result in zip(self._queries, results)] ) - self._last_log = { - 'State': output, - } - if self._verbose: - self._log(output) + self._logging_channel({'Key': self.get_pre_act_key(), 'Value': output}) return output - def _log(self, entry: str): - print(termcolor.colored(entry, self._log_color), end='') - - def get_last_log(self): - if self._last_log: - return self._last_log.copy() - class IdentityWithoutPreAct(action_spec_ignored.ActionSpecIgnored): """An identity component that does not output its state to pre_act.""" @@ -151,6 +136,3 @@ def pre_act( def update(self) -> None: self._component.update() - - def get_last_log(self): - return self._component.get_last_log() diff --git a/concordia/components/agent/v2/instructions.py b/concordia/components/agent/v2/instructions.py index 9e065def..a68952d3 100644 --- a/concordia/components/agent/v2/instructions.py +++ b/concordia/components/agent/v2/instructions.py @@ -15,6 +15,7 @@ """Component that provides the default role playing instructions to an agent.""" from concordia.components.agent.v2 import constant +from concordia.typing import logging DEFAULT_INSTRUCTIONS_PRE_ACT_KEY = 'Role playing instructions' @@ -22,9 +23,12 @@ class Instructions(constant.Constant): """A component that provides the role playing instructions for the agent.""" - def __init__(self, - agent_name: str, - pre_act_key: str = DEFAULT_INSTRUCTIONS_PRE_ACT_KEY): + def __init__( + self, + agent_name: str, + pre_act_key: str = DEFAULT_INSTRUCTIONS_PRE_ACT_KEY, + logging_channel: logging.LoggingChannel = logging.NoOpLoggingChannel, + ): state = ( f'The instructions for how to play the role of {agent_name} are as ' 'follows. This is a social science experiment studying how well you ' @@ -38,4 +42,5 @@ def __init__(self, f'into account all information about {agent_name} that you have. ' 'Always use third-person limited perspective.' ) - super().__init__(state=state, pre_act_key=pre_act_key) + super().__init__( + state=state, pre_act_key=pre_act_key, logging_channel=logging_channel) diff --git a/concordia/components/agent/v2/legacy_act_component.py b/concordia/components/agent/v2/legacy_act_component.py index ebbcb035..efafcd67 100644 --- a/concordia/components/agent/v2/legacy_act_component.py +++ b/concordia/components/agent/v2/legacy_act_component.py @@ -22,9 +22,12 @@ from concordia.typing import clock as game_clock from concordia.typing import component_v2 from concordia.typing import entity as entity_lib +from concordia.typing import logging from concordia.utils import helper_functions from typing_extensions import override +DEFAULT_PRE_ACT_KEY = 'Act' + class ActComponent(component_v2.ActingComponent): """A component which mimics the older act behavior of basic_agent.py. @@ -42,6 +45,8 @@ def __init__( model: language_model.LanguageModel, clock: game_clock.GameClock, component_order: Sequence[str] | None = None, + pre_act_key: str = DEFAULT_PRE_ACT_KEY, + logging_channel: logging.LoggingChannel = logging.NoOpLoggingChannel, ): """Initializes the agent. @@ -58,6 +63,8 @@ def __init__( component cannot appear twice in the component order. All components in the component order must be in the `ComponentContextMapping` passed to `get_action_attempt`. + pre_act_key: Prefix to add to the context of the component. + logging_channel: The channel to use for debug logging. Raises: ValueError: If the component order is not None and contains duplicate @@ -76,7 +83,8 @@ def __init__( + ', '.join(self._component_order) ) - self._last_log = None + self._pre_act_key = pre_act_key + self._logging_channel = logging_channel def _context_for_action( self, @@ -117,14 +125,14 @@ def get_action_attempt( max_tokens=2200, answer_prefix=output, ) - self._make_update_log(output, prompt) + self._log(output, prompt) return output elif action_spec.output_type == entity_lib.OutputType.CHOICE: idx = prompt.multiple_choice_question( question=call_to_action, answers=action_spec.options ) output = action_spec.options[idx] - self._make_update_log(output, prompt) + self._log(output, prompt) return output elif action_spec.output_type == entity_lib.OutputType.FLOAT: prefix = self.get_entity().name + ' ' @@ -133,7 +141,7 @@ def get_action_attempt( max_tokens=2200, answer_prefix=prefix, ) - self._make_update_log(sampled_text, prompt) + self._log(sampled_text, prompt) try: return str(float(sampled_text)) except ValueError: @@ -144,12 +152,11 @@ def get_action_attempt( 'Supported output types are: FREE, CHOICE, and FLOAT.' ) - def _make_update_log(self, - result: str, - prompt: interactive_document.InteractiveDocument): - self._last_log = {'Output': result, - 'Prompt': prompt.view().text().splitlines()} - - def get_last_log(self): - if self._last_log: - return self._last_log.copy() + def _log(self, + result: str, + prompt: interactive_document.InteractiveDocument): + self._logging_channel({ + 'Key': self._pre_act_key, + 'Value': result, + 'Prompt': prompt.view().text().splitlines(), + }) diff --git a/concordia/components/agent/v2/observation.py b/concordia/components/agent/v2/observation.py index e39084a7..871022ba 100644 --- a/concordia/components/agent/v2/observation.py +++ b/concordia/components/agent/v2/observation.py @@ -22,8 +22,9 @@ from concordia.components.agent.v2 import memory_component from concordia.document import interactive_document from concordia.language_model import language_model + from concordia.memory_bank import legacy_associative_memory -import termcolor +from concordia.typing import logging DEFAULT_OBSERVATION_PRE_ACT_KEY = 'Observation' DEFAULT_OBSERVATION_SUMMARY_PRE_ACT_KEY = 'Summary of recent observations' @@ -39,8 +40,7 @@ def __init__( memory_component_name: str = ( memory_component.DEFAULT_MEMORY_COMPONENT_NAME), pre_act_key: str = DEFAULT_OBSERVATION_PRE_ACT_KEY, - verbose: bool = False, - log_color: str = 'green', + logging_channel: logging.LoggingChannel = logging.NoOpLoggingChannel, ): """Initializes the observation component. @@ -53,17 +53,14 @@ def __init__( in `pre_observe` and to retrieve observations from in `pre_act`. pre_act_key: Prefix to add to the output of the component when called in `pre_act`. - verbose: Whether to print the observations. - log_color: Color to print the log. + logging_channel: The channel to use for debug logging. """ super().__init__(pre_act_key) self._clock_now = clock_now self._timeframe = timeframe self._memory_component_name = memory_component_name - self._verbose = verbose - self._log_color = log_color - self._last_log = None + self._logging_channel = logging_channel def pre_observe( self, @@ -73,7 +70,7 @@ def pre_observe( self._memory_component_name, type_=memory_component.MemoryComponent) memory.add( - f'[observation] {observation.strip()}', + f'[observation] {observation}', metadata={'tags': ['observation']}, ) return '' @@ -88,26 +85,16 @@ def _make_pre_act_value(self) -> str: time_until=self._clock_now(), add_time=True, ) + # removes memories that are not observations mems = memory.retrieve(scoring_fn=interval_scorer) # Remove memories that are not observations. mems = [mem.text for mem in mems if '[observation]' in mem.text] result = '\n'.join(mems) + '\n' - self._last_log = { - 'Summary': 'observation', - 'state': result, - } - if self._verbose: - self._log(result) + self._logging_channel( + {'Key': self.get_pre_act_key(), 'Value': result.splitlines()}) return result - def _log(self, entry: str): - print(termcolor.colored(entry, self._log_color), end='') - - def get_last_log(self): - if self._last_log: - return self._last_log.copy() - class ObservationSummary(action_spec_ignored.ActionSpecIgnored): """Component that summarizes observations from a segment of time.""" @@ -126,8 +113,7 @@ def __init__( prompt: str | None = None, display_timeframe: bool = True, pre_act_key: str = DEFAULT_OBSERVATION_SUMMARY_PRE_ACT_KEY, - verbose: bool = False, - log_color='green', + logging_channel: logging.LoggingChannel = logging.NoOpLoggingChannel, ): """Initializes the component. @@ -146,8 +132,7 @@ def __init__( display_timeframe: Whether to display the time interval as text. pre_act_key: Prefix to add to the output of the component when called in `pre_act`. - verbose: Whether to print the observations. - log_color: Color to print the log. + logging_channel: The channel to use for debug logging. """ super().__init__(pre_act_key) self._model = model @@ -162,10 +147,7 @@ def __init__( ) self._display_timeframe = display_timeframe - self._verbose = verbose - self._log_color = log_color - - self._last_log = None + self._logging_channel = logging_channel def _make_pre_act_value(self) -> str: agent_name = self.get_entity().name @@ -216,20 +198,11 @@ def _make_pre_act_value(self) -> str: ) + segment_end.strftime('- %d %b %Y %H:%M:%S]: ') result = f'{interval} {result}' - if self._verbose: - self._log(result) - - self._last_log = { - 'Summary': 'observation summary', - 'State': result, + self._logging_channel({ + 'Key': self.get_pre_act_key(), + 'Value': result, 'Chain of thought': prompt.view().text().splitlines(), - } + }) return result - def get_last_log(self): - if self._last_log: - return self._last_log.copy() - - def _log(self, entry: str): - print(termcolor.colored(entry, self._log_color), end='') diff --git a/concordia/components/agent/v2/report_function.py b/concordia/components/agent/v2/report_function.py index cefc9243..ec657b56 100644 --- a/concordia/components/agent/v2/report_function.py +++ b/concordia/components/agent/v2/report_function.py @@ -23,6 +23,7 @@ from typing import Callable from concordia.components.agent.v2 import action_spec_ignored +from concordia.typing import logging DEFAULT_PRE_ACT_KEY = 'Report' @@ -33,7 +34,9 @@ class ReportFunction(action_spec_ignored.ActionSpecIgnored): def __init__( self, function: Callable[[], str], + *, pre_act_key: str = DEFAULT_PRE_ACT_KEY, + logging_channel: logging.LoggingChannel = logging.NoOpLoggingChannel, ): """Initializes the component. @@ -42,19 +45,17 @@ def __init__( component. pre_act_key: Prefix to add to the output of the component when called in `pre_act`. + logging_channel: The channel to use for debug logging. """ super().__init__(pre_act_key) self._function = function - self._last_log = None + self._logging_channel = logging_channel def _make_pre_act_value(self) -> str: """Returns state of this component obtained by calling a function.""" - state = self._function() - self._last_log = { - 'State': state, - } - return state - - def get_last_log(self): - if self._last_log: - return self._last_log.copy() + value = self._function() + self._logging_channel({ + 'Key': self.get_pre_act_key(), + 'Value': value, + }) + return value diff --git a/concordia/components/agent/v2/self_perception.py b/concordia/components/agent/v2/self_perception.py index 9d2f26c4..b39e8a55 100644 --- a/concordia/components/agent/v2/self_perception.py +++ b/concordia/components/agent/v2/self_perception.py @@ -22,7 +22,7 @@ from concordia.document import interactive_document from concordia.language_model import language_model from concordia.memory_bank import legacy_associative_memory -import termcolor +from concordia.typing import logging DEFAULT_PRE_ACT_KEY = 'Who they are' @@ -40,8 +40,7 @@ def __init__( ), num_memories_to_retrieve: int = 100, pre_act_key: str = DEFAULT_PRE_ACT_KEY, - verbose: bool = False, - log_color: str = 'green', + logging_channel: logging.LoggingChannel = logging.NoOpLoggingChannel, ): """Initializes the SelfPerception component. @@ -53,8 +52,7 @@ def __init__( num_memories_to_retrieve: Number of memories to retrieve. pre_act_key: Prefix to add to the output of the component when called in `pre_act`. - verbose: Whether to print the state or not for debugging. - log_color: color to print the debug log. + logging_channel: The channel to use for debug logging. """ super().__init__(pre_act_key) self._model = model @@ -62,9 +60,7 @@ def __init__( self._components = dict(components) self._num_memories_to_retrieve = num_memories_to_retrieve - self._verbose = verbose - self._log_color = log_color - self._last_log = None + self._logging_channel = logging_channel def _make_pre_act_value(self) -> str: agent_name = self.get_entity().name @@ -100,19 +96,10 @@ def _make_pre_act_value(self) -> str: memory.add(f'[self reflection] {result}', metadata={}) - self._last_log = { - 'Summary': question, - 'State': result, + self._logging_channel({ + 'Key': self.get_pre_act_key(), + 'Value': result, 'Chain of thought': prompt.view().text().splitlines(), - } - if self._verbose: - self._log(prompt.view().text()) + }) return result - - def _log(self, entry: str): - print(termcolor.colored(entry, self._log_color), end='') - - def get_last_log(self): - if self._last_log: - return self._last_log.copy() diff --git a/concordia/components/agent/v2/situation_perception.py b/concordia/components/agent/v2/situation_perception.py index 1c717be9..bc1766e3 100644 --- a/concordia/components/agent/v2/situation_perception.py +++ b/concordia/components/agent/v2/situation_perception.py @@ -22,7 +22,7 @@ from concordia.document import interactive_document from concordia.language_model import language_model from concordia.memory_bank import legacy_associative_memory -import termcolor +from concordia.typing import logging DEFAULT_PRE_ACT_KEY = 'What situation they are in' @@ -41,8 +41,7 @@ def __init__( clock_now: Callable[[], datetime.datetime] | None = None, num_memories_to_retrieve: int = 25, pre_act_key: str = DEFAULT_PRE_ACT_KEY, - verbose: bool = False, - log_color: str = 'green', + logging_channel: logging.LoggingChannel = logging.NoOpLoggingChannel, ): """Initializes the component. @@ -55,8 +54,7 @@ def __init__( num_memories_to_retrieve: The number of recent memories to retrieve. pre_act_key: Prefix to add to the output of the component when called in `pre_act`. - verbose: Whether to print intermediate reasoning. - log_color: color to print the debug log. + logging_channel: The channel to use for debug logging. """ super().__init__(pre_act_key) self._model = model @@ -65,9 +63,7 @@ def __init__( self._clock_now = clock_now self._num_memories_to_retrieve = num_memories_to_retrieve - self._verbose = verbose - self._log_color = log_color - self._last_log = None + self._logging_channel = logging_channel def _make_pre_act_value(self) -> str: agent_name = self.get_entity().name @@ -105,19 +101,11 @@ def _make_pre_act_value(self) -> str: ) result = f'{agent_name} is currently {result}' - self._last_log = { + self._logging_channel({ + 'Key': self.get_pre_act_key(), 'Summary': question, - 'State': result, + 'Value': result, 'Chain of thought': prompt.view().text().splitlines(), - } - if self._verbose: - print(termcolor.colored(prompt.view().text(), 'green'), end='') + }) return result - - def _log(self, entry: str): - print(termcolor.colored(entry, self._log_color), end='') - - def get_last_log(self): - if self._last_log: - return self._last_log.copy() diff --git a/concordia/factory/agent/temporary_entity_agent__main_role.py b/concordia/factory/agent/temporary_entity_agent__main_role.py index 6629a83c..8ceaf811 100644 --- a/concordia/factory/agent/temporary_entity_agent__main_role.py +++ b/concordia/factory/agent/temporary_entity_agent__main_role.py @@ -16,13 +16,14 @@ import datetime -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 formative_memories from concordia.clocks import game_clock from concordia.components.agent import v2 as agent_components from concordia.language_model import language_model from concordia.memory_bank import legacy_associative_memory +from concordia.utils import measurements as measurements_lib DEFAULT_PLANNING_HORIZON = 'the rest of the day, focusing most on the near term' @@ -37,7 +38,7 @@ def build_agent( memory: associative_memory.AssociativeMemory, clock: game_clock.MultiIntervalClock, update_time_interval: datetime.timedelta, -) -> entity_agent.EntityAgent: +) -> entity_agent_with_logging.EntityAgentWithLogging: """Build an agent. Args: @@ -59,14 +60,17 @@ def build_agent( raw_memory = legacy_associative_memory.AssociativeMemoryBank(memory) + measurements = measurements_lib.Measurements() instructions = agent_components.instructions.Instructions( - agent_name=agent_name) + agent_name=agent_name, + logging_channel=measurements.get_channel('Instructions').on_next, + ) observation = agent_components.observation.Observation( clock_now=clock.now, timeframe=clock.get_step_size(), pre_act_key='Observation', - verbose=True, + logging_channel=measurements.get_channel('Observation').on_next, ) observation_summary = agent_components.observation.ObservationSummary( @@ -75,15 +79,16 @@ def build_agent( timeframe_delta_from=datetime.timedelta(hours=4), timeframe_delta_until=datetime.timedelta(hours=1), pre_act_key='Summary of recent observations', - verbose=True, + logging_channel=measurements.get_channel('ObservationSummary').on_next, ) time_display = agent_components.report_function.ReportFunction( function=clock.current_time_interval_str, pre_act_key='Current time', + logging_channel=measurements.get_channel('TimeDisplay').on_next, ) identity_characteristics = agent_components.identity.IdentityWithoutPreAct( model=model, - verbose=True, + logging_channel=measurements.get_channel('Identity').on_next, ) self_perception_label = ( f'\nQuestion: What kind of person is {agent_name}?\nAnswer') @@ -92,7 +97,7 @@ def build_agent( components={_get_class_name( identity_characteristics): 'Identity characteristics'}, pre_act_key=self_perception_label, - verbose=True, + logging_channel=measurements.get_channel('SelfPerception').on_next, ) situation_perception_label = ( f'\nQuestion: What kind of situation is {agent_name} in ' @@ -107,7 +112,8 @@ def build_agent( }, clock_now=clock.now, pre_act_key=situation_perception_label, - verbose=True, + logging_channel=measurements.get_channel( + 'SituationPerception').on_next, ) ) person_by_situation_label = ( @@ -131,6 +137,7 @@ def build_agent( _get_class_name(time_display): 'The current date/time is'}, num_memories_to_retrieve=10, pre_act_key=relevant_memories_label, + logging_channel=measurements.get_channel('AllSimilarMemories').on_next, ) plan_components = {} @@ -190,12 +197,14 @@ def build_agent( model=model, clock=clock, component_order=component_order, + logging_channel=measurements.get_channel('ActComponent').on_next, ) - agent = entity_agent.EntityAgent( + agent = entity_agent_with_logging.EntityAgentWithLogging( agent_name=agent_name, act_component=act_component, context_components=components_of_agent, + component_logging=measurements, ) return agent diff --git a/concordia/typing/component_v2.py b/concordia/typing/component_v2.py index 6b0406e2..678f6eb8 100644 --- a/concordia/typing/component_v2.py +++ b/concordia/typing/component_v2.py @@ -17,7 +17,7 @@ import abc from collections.abc import Mapping import enum -from typing import Any, TypeVar +from typing import TypeVar from concordia.typing import entity as entity_lib @@ -83,10 +83,6 @@ def get_entity(self) -> "EntityWithComponents": raise RuntimeError("Entity is not set.") return self._entity - def get_last_log(self) -> Mapping[str, Any]: - """Returns a dictionary with latest log of activity.""" - return {} - class EntityWithComponents(entity_lib.Entity): """An entity that contains components.""" diff --git a/concordia/typing/logging.py b/concordia/typing/logging.py new file mode 100644 index 00000000..ebd72612 --- /dev/null +++ b/concordia/typing/logging.py @@ -0,0 +1,22 @@ +# 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. + +"""Types for logging.""" + +from collections.abc import Mapping +from typing import Any, Callable + +LoggingChannel = Callable[[Mapping[str, Any]], None] + +NoOpLoggingChannel = lambda x: None