Spaces:
Running
Running
| from typing import Union, List | |
| from tinytroupe.extraction import logger | |
| from tinytroupe.utils import JsonSerializableRegistry | |
| from tinytroupe.experimentation import Proposition | |
| from tinytroupe.environment import TinyWorld | |
| from tinytroupe.agent import TinyPerson | |
| import tinytroupe.utils as utils | |
| DEFAULT_FIRST_N = 10 | |
| DEFAULT_LAST_N = 100 | |
| class InterventionBatch: | |
| """ | |
| A wrapper around multiple Intervention instances that allows chaining set_* methods. | |
| """ | |
| def __init__(self, interventions): | |
| self.interventions = interventions | |
| def __iter__(self): | |
| """Makes the batch iterable and compatible with list()""" | |
| return iter(self.interventions) | |
| def set_textual_precondition(self, text): | |
| for intervention in self.interventions: | |
| intervention.set_textual_precondition(text) | |
| return self | |
| def set_functional_precondition(self, func): | |
| for intervention in self.interventions: | |
| intervention.set_functional_precondition(func) | |
| return self | |
| def set_effect(self, effect_func): | |
| for intervention in self.interventions: | |
| intervention.set_effect(effect_func) | |
| return self | |
| def set_propositional_precondition(self, proposition, threshold=None): | |
| for intervention in self.interventions: | |
| intervention.set_propositional_precondition(proposition, threshold) | |
| return self | |
| def as_list(self): | |
| """Return the list of individual interventions.""" | |
| return self.interventions | |
| class Intervention: | |
| def __init__(self, targets: Union[TinyPerson, TinyWorld, List[TinyPerson], List[TinyWorld]], | |
| first_n:int=DEFAULT_FIRST_N, last_n:int=DEFAULT_LAST_N, | |
| name: str = None): | |
| """ | |
| Initialize the intervention. | |
| Args: | |
| target (Union[TinyPerson, TinyWorld, List[TinyPerson], List[TinyWorld]]): the target to intervene on | |
| first_n (int): the number of first interactions to consider in the context | |
| last_n (int): the number of last interactions (most recent) to consider in the context | |
| name (str): the name of the intervention | |
| """ | |
| self.targets = targets | |
| # initialize the possible preconditions | |
| self.text_precondition = None | |
| self.precondition_func = None | |
| # effects | |
| self.effect_func = None | |
| # which events to pay attention to? | |
| self.first_n = first_n | |
| self.last_n = last_n | |
| # name | |
| if name is None: | |
| self.name = self.name = f"Intervention {utils.fresh_id(self.__class__.__name__)}" | |
| else: | |
| self.name = name | |
| # the most recent precondition proposition used to check the precondition | |
| self._last_text_precondition_proposition = None | |
| self._last_functional_precondition_check = None | |
| # propositional precondition (optional) | |
| self.propositional_precondition = None | |
| self.propositional_precondition_threshold = None | |
| self._last_propositional_precondition_check = None | |
| ################################################################################################ | |
| # Intervention flow | |
| ################################################################################################ | |
| def create_for_each(cls, targets, first_n=DEFAULT_FIRST_N, last_n=DEFAULT_LAST_N, name=None): | |
| """ | |
| Create separate interventions for each target in the list. | |
| Args: | |
| targets (list): List of targets (TinyPerson or TinyWorld instances) | |
| first_n (int): the number of first interactions to consider in the context | |
| last_n (int): the number of last interactions (most recent) to consider in the context | |
| name (str): the name of the intervention | |
| Returns: | |
| InterventionBatch: A wrapper that allows chaining set_* methods that will apply to all interventions | |
| """ | |
| if not isinstance(targets, list): | |
| targets = [targets] | |
| interventions = [cls(target, first_n=first_n, last_n=last_n, | |
| name=f"{name}_{i}" if name else None) | |
| for i, target in enumerate(targets)] | |
| return InterventionBatch(interventions) | |
| def __call__(self): | |
| """ | |
| Execute the intervention. | |
| Returns: | |
| bool: whether the intervention effect was applied. | |
| """ | |
| return self.execute() | |
| def execute(self): | |
| """ | |
| Execute the intervention. It first checks the precondition, and if it is met, applies the effect. | |
| This is the simplest method to run the intervention. | |
| Returns: | |
| bool: whether the intervention effect was applied. | |
| """ | |
| logger.debug(f"Executing intervention: {self}") | |
| if self.check_precondition(): | |
| self.apply_effect() | |
| logger.debug(f"Precondition was true, intervention effect was applied.") | |
| return True | |
| logger.debug(f"Precondition was false, intervention effect was not applied.") | |
| return False | |
| def check_precondition(self): | |
| """ | |
| Check if the precondition for the intervention is met. | |
| """ | |
| # | |
| # Textual precondition | |
| # | |
| if self.text_precondition is not None: | |
| self._last_text_precondition_proposition = Proposition(claim=self.text_precondition, target=self.targets, first_n=self.first_n, last_n=self.last_n) | |
| llm_precondition_check = self._last_text_precondition_proposition.check() | |
| else: | |
| llm_precondition_check = True | |
| # | |
| # Functional precondition | |
| # | |
| if self.precondition_func is not None: | |
| self._last_functional_precondition_check = self.precondition_func(self.targets) | |
| else: | |
| self._last_functional_precondition_check = True # default to True if no functional precondition is set | |
| # | |
| # Propositional precondition | |
| # | |
| self._last_propositional_precondition_check = True | |
| if self.propositional_precondition is not None: | |
| if self.propositional_precondition_threshold is not None: | |
| score = self.propositional_precondition.score(target=self.targets) | |
| if score >= self.propositional_precondition_threshold: | |
| self._last_propositional_precondition_check = False | |
| else: | |
| if not self.propositional_precondition.check(target=self.targets): | |
| self._last_propositional_precondition_check = False | |
| return llm_precondition_check and self._last_functional_precondition_check and self._last_propositional_precondition_check | |
| def apply_effect(self): | |
| """ | |
| Apply the intervention's effects. This won't check the precondition, | |
| so it should be called after check_precondition. | |
| """ | |
| self.effect_func(self.targets) | |
| ################################################################################################ | |
| # Pre and post conditions | |
| ################################################################################################ | |
| def set_textual_precondition(self, text): | |
| """ | |
| Set a precondition as text, to be interpreted by a language model. | |
| Args: | |
| text (str): the text of the precondition | |
| """ | |
| self.text_precondition = text | |
| return self # for chaining | |
| def set_functional_precondition(self, func): | |
| """ | |
| Set a precondition as a function, to be evaluated by the code. | |
| Args: | |
| func (function): the function of the precondition. | |
| Must have the a single argument, targets (either a TinyWorld or TinyPerson, or a list). Must return a boolean. | |
| """ | |
| self.precondition_func = func | |
| return self # for chaining | |
| def set_effect(self, effect_func): | |
| """ | |
| Set the effect of the intervention. | |
| Args: | |
| effect (str): the effect function of the intervention | |
| """ | |
| self.effect_func = effect_func | |
| return self # for chaining | |
| def set_propositional_precondition(self, proposition:Proposition, threshold:int=None): | |
| """ | |
| Set a propositional precondition using the Proposition class, | |
| optionally with a score threshold. | |
| """ | |
| self.propositional_precondition = proposition | |
| self.propositional_precondition_threshold = threshold | |
| return self | |
| ################################################################################################ | |
| # Inspection | |
| ################################################################################################ | |
| def precondition_justification(self): | |
| """ | |
| Get the justification for the precondition. | |
| """ | |
| justification = "" | |
| # text precondition justification | |
| if self._last_text_precondition_proposition is not None: | |
| justification += f"{self._last_text_precondition_proposition.justification} (confidence = {self._last_text_precondition_proposition.confidence})\n\n" | |
| # functional precondition justification | |
| if self.precondition_func is not None: | |
| if self._last_functional_precondition_check == True: | |
| justification += f"Functional precondition was met.\n\n" | |
| else: | |
| justification += "Preconditions do not appear to be met.\n\n" | |
| # propositional precondition justification | |
| if self.propositional_precondition is not None: | |
| if self._last_propositional_precondition_check == True: | |
| justification += f"Propositional precondition was met.\n\n" | |
| else: | |
| justification += "Propositional precondition was not met.\n\n" | |
| return justification | |
| return justification | |