basic_info = '''
# You are a household agent. Here is some Python code defining a household environment:

# Entity class an object/receptacle in the environment, including its properties
class Entity:
    def __init__(self, **kwargs):
        self.name = None 
        self.loc = None # location
        self.in_on = None # the receptacle that the object is in/on
        self.ishot, self.iscool, self.isclean = None, None, None
        self.isopen, self.ison, self.istoggled = None, None, None
        self.pickupable, self.openable, self.toggleable = None, None, None
        self.heatable, self.coolable, self.cleanable = None, None, None
        self.isobject, self.isreceptacle, self.isreceptacleobject = None, None, None
        self.type = None
        self.checked = None
        for key, value in kwargs.items():                
            setattr(self, key, value)
            
        assert self.name is not None
        assert self.type is not None

# Entitys class stores all the entities in the environment
class Entitys:
    # return the entity with the given name, where the format of the given name is similar to "apple 22".
    def __getitem__(self, entity_name):
        ...
        
# Agent class represents the state of the agent, including its location,
# what it's holding, entities it has seen, as well as the actions it can take.
class Agent:
    def __init__(self, ...):
        self.holding = None
        self.location = None
        self.seen_entitys = Entitys()

    # Here are some assistant methods the agent can using:
    
    # return the receptacletypes that can contain the objecttype
    def find_canbe_contained(self, objecttype: str):
        ...
    
    # return an object(Entity) with the given objecttype
    def find_object(self, objecttype: str):
        ...
    
    # return a list of object(Entity) with the given objecttype
    def find_objects(self, objecttype: str):
        ...
    
    # return a receptacle(Entity) with the given receptacletype
    def find_receptacle(self, receptacletype: str):
        ...
    
    # return a list of receptacle(Entity) with the given receptacletype
    def find_receptacles(self, receptacletype: str):
        ...
        
    # name2type transforms a name to a type, like 'apple', 'apple 22' or 'appletype ' -> 'appletype '
    def name2type(self, name: str):
        ...
    
    # Here are the admissible actions the agent can take:
    
    # Explore the environment and observe the entities in it.
    def explore(self):
        ...
    
    # Go to a receptacle
    # For example: goto('countertop 1'). 
    # Only goto(receptacle.name) and goto(object.in_on) is allowed. goto(entity.loc) is prohibited.
    def goto(self, receptacle):
        ...

    # Take an object from a receptacle if the agent is not holding anything.
    # For example: take('soapbar 1', 'towelholder 1')
    def take(self, object, receptacle):
        ...
        
    # Put an object in or on a receptacle if the agent is holding it. 
    # For example: put('soapbar 1', 'cabinet 1')
    def put(self, object, receptacle):
        ...

    # Open a receptacle and observe its contents. 
    # For example: open_receptacle('cabinet 1')
    def open_receptacle(self, receptacle):
        ...

    # Close an opened receptacle.
    # For example: close_receptacle('cabinet 1')
    def close_receptacle(self, receptacle):
        ...
    
    # Clean an object with sinkbasin. 
    # For example: clean('soapbar 1', 'sinkbasin 1')
    def clean(self, object, receptacle):
        ...

    # Heat an object with a receptacle. 
    # For example: heat('tomato 1', 'microwave 1')
    def heat(self, object, receptacle):
        ...

    # Cool an object with a receptacle. 
    # For example: cool('pan 2', 'fridge 1')
    def cool(self, object, receptacle):
        ...

    # Turn on an object. 
    # For example: turn_on('desklamp 1')
    def turn_on(self, object):
        ...
        
    # Method need to be completed for the task: <task>
    <task_method>:
        ...
    
'''.strip()

get_solution_prompt = f'''
{basic_info}
    
# Now complete the `<task_method>` to solve the task by composing the agent's methods to interact with the environment. 

# Here is an successful example of a solution to the task:

<example>

# Here is the actual task.
# <task>, that is, <task_method_desc>
# Referring to the successful example and its code, you should complete your solution function below:
<task_method>:
# Note: Do not directly call the `<example_method_name>` in the example. You should use its code as a reference.
'''.strip()

simple_example = '''
# pick an object with given objecttype.
# here is the completed `def pick_object(self, objecttype)`:
def pick_object(self, objecttype):
    if self.name2type(self.holding) != self.name2type(objecttype):
        # I need to find the object
        entity = self.find_object(objecttype)
        if entity is None:
            # I can't find object, maybe I need to find receptacletypes that can contain the object
            r_types = self.find_canbe_contained(objecttype)
            if r_types is None:
                # I even can't find the receptacletypes that can contain the object, so I need to explore
                self.explore()
                return # after explore, maybe I will find new entity in env, so return for the next decision-making
            else:
                # I find receptacletypes that can contain the object. For each receptacletype, I need to confirm if the object is in the receptacle that corresponding to the receptacletype
                for r_type in r_types:
                    r_entitys = self.find_receptacles(r_type)
                    if r_entitys is None: # I can't find the receptacle, continue to find the next receptacletype
                        continue
                    for r_entity in r_entitys:
                        self.goto(r_entity.name)
                        if r_entity.openable and not r_entity.isopen:
                            self.open_receptacle(r_entity.name)
                            return # after open the receptacle, maybe I will find new entity in env, return for the next decision-making
        else:
            # I find the object, then I need to go to its receptacle and then take it
            self.goto(entity.in_on)
            r_entity = self.seen_entitys[entity.in_on]
            if r_entity.openable and not r_entity.isopen:
                self.open_receptacle(r_entity.name)
            self.take(entity.name, entity.in_on)
            return # after taking the object, return for the next decision-making
'''.strip()

clean_example = '''
# define environment and agent
receptacles = ['diningtable 1','drawer 2', 'drawer 1', 'sinkbasin 1', 'toilet 1', 'sidetable 2', 'sidetable 1', 'cabinet 1', 'countertop 1', 'microwave 1', 'fridge 1']
agent = Agent(receptacles)

# Your task is to: put a clean lettuce in diningtable / clean a lettuce and put it in diningtable.
# here is a solution:
def solution(agent, start_from=1):
    # General plan: I need to get a list of receptacles to find the lettuce, take the lettuce to the sinkbasin, clean it and put it in a diningtable.
    if start_from <= 1:
        print("[Step 1] get a list of receptacles where the lettuce is likely to appear.")
        # I can ask the assistant to do that.
        answer = ask(f'Given a list of receptacles, please sort them in descending order based on the likelihood of finding a lettuce in each of them. The list of receptacles is: {agent.receptacles}. You should directly return a Python list.')
        recep_to_check = literal_eval(answer)
        # expectation: the returned recep_to_check should not be empty.
        assert recep_to_check, f'Error in [Step 1]: recep_to_check should not be empty. {agent.report()}'

    if start_from <= 2:
        print("[Step 2] go to each receptacle in the list until seeing a lettuce")
        for receptacle in recep_to_check:
            observation = agent.goto(receptacle)
            # check if the receptacle is closed. If so, open it.
            if 'closed' in observation:
                observation = agent.open_receptacle(receptacle)
            # check if a lettuce is in/on the receptacle.
            if 'lettuce' in observation:
                break
        # expectation: I should be able to find a receptacle where a lettuce is in/on it.
        assert 'lettuce' in observation, f'Error in [Step 2]: There is no lettuce in/on {recep_to_check}. {agent.report()}'

    if start_from <= 3:
        print("[Step 3] identify the lettuce I juts found and take it")
        # I need to get the identifier of the lettuce. I can ask the assistant to do that.
        answer = ask(f'From the observation, get the identifier of an object. For example, On the cabinet 1, you see a cloth 2, and a toiletpaper 2. The identifier of cloth is 2. Now, {observation} The identifier of the lettuce? Only Output a single number without any other words. ')
        found_lettuce = f'lettuce {answer}'
        observation = agent.take(found_lettuce, receptacle)
        # expectation: I should be able to take the lettuce from the receptacle.
        assert agent.holding == found_lettuce, f'Error in [Step 3]: I cannot take {found_lettuce} from the {receptacle}. {agent.report()}'
    
    if start_from <= 4:
        print("[Step 4] go to a sinkbasin to clean the lettuce. ")
        # I should go to the sinkbasin first if I want to clean the lettuce.
        observation = agent.goto('sinkbasin 1')
        # check if the sinkbasin is closed. If so, open it.
        if 'closed' in observation:
            observation = agent.open_receptacle('sinkbasin 1')
        observation = agent.clean(found_lettuce, 'sinkbasin 1')
        # expectation: I should be able to clean the lettuce.
        assert f'You clean the {found_lettuce} using the sinkbasin 1.' in observation, f'Error in [Step 4]: I cannot clean the {found_lettuce} using the sinkbasin 1. {agent.report()} I should have been at sinkbasin 1 and holding {found_lettuce}.'
    
    if start_from <= 5:
        print("[Step 5] go to a diningtable and put the lettuce on it. ")
        # There are multiple diningtables, and I only need to go to one of them.
        observation = agent.goto('diningtable 1')
        # check if the diningtable is closed. If so, open it.
        if 'closed' in observation:
            observation = agent.open_receptacle('diningtable 1')
        observation = agent.put(found_lettuce, 'diningtable 1')
        # expectation: I should be able to put the lettuce on the diningtable.
        assert f'You put the {found_lettuce} in/on the diningtable 1.' in observation, f'Error in [Step 5]: I cannot put the {found_lettuce} on the diningtable 1. {agent.report()}'
'''.strip()

heat_example = '''
# define environment and agent
receptacles = ['diningtable 1','drawer 2', 'drawer 1', 'sinkbasin 1', 'toilet 1', 'sidetable 2', 'sidetable 1', 'cabinet 1', 'countertop 1', 'microwave 1', 'fridge 1']
agent = Agent(receptacles)

# Your task is to: put a hot lettuce in diningtable / heat some lettuce and put it in diningtable.
# here is a solution:
def solution(agent, start_from=1):
    # General plan: I need to get a list of receptacles to find the lettuce, take the lettuce to the microwave, heat it and put it in a diningtable.
    if start_from <= 1:
        print("[Step 1] get a list of receptacles where the lettuce is likely to appear.")
        # I can ask the assistant to do that.
        answer = ask(f'Given a list of receptacles, please sort them in descending order based on the likelihood of finding a lettuce in each of them. The list of receptacles is: {agent.receptacles}. You should directly return a Python list.')
        recep_to_check = literal_eval(answer)
        # expectation: the returned recep_to_check should not be empty.
        assert recep_to_check, f'Error in [Step 1]: recep_to_check should not be empty. {agent.report()}'

    if start_from <= 2:
        print("[Step 2] go to each receptacle in the list until seeing a lettuce")
        for receptacle in recep_to_check:
            observation = agent.goto(receptacle)
            # check if the receptacle is closed. If so, open it.
            if 'closed' in observation:
                observation = agent.open_receptacle(receptacle)
            # check if a lettuce is in/on the receptacle.
            if 'lettuce' in observation:
                break
        # expectation: I should be able to find a receptacle where a lettuce is in/on it.
        assert 'lettuce' in observation, f'Error in [Step 2]: There is no lettuce in/on {recep_to_check}. {agent.report()}'

    if start_from <= 3:
        print("[Step 3] identify the lettuce I juts found and take it")
        # I need to get the identifier of the lettuce. I can ask the assistant to do that.
        answer = ask(f'From the observation, get the identifier of an object. For example, On the cabinet 1, you see a cloth 2, and a toiletpaper 2. The identifier of cloth is 2. Now, {observation} The identifier of the lettuce? Only Output a single number without any other words. ')
        found_lettuce = f'lettuce {answer}'
        observation = agent.take(found_lettuce, receptacle)
        # expectation: I should be able to take the lettuce from the receptacle.
        assert agent.holding == found_lettuce, f'Error in [Step 3]: I cannot take {found_lettuce} from the {receptacle}. {agent.report()}'
    
    if start_from <= 4:
        print("[Step 4] go to a microwave to heat the lettuce")
        # I should go to a microwave to heat the lettuce.
        observation = agent.goto('microwave 1')
        # check if the microwave is closed. If so, open it.
        if 'closed' in observation:
            observation = agent.open_receptacle('microwave 1')
        observation = agent.heat(found_lettuce, 'microwave 1')
        # expectation: I should be able to heat the lettuce.
        assert f'You heat the {found_lettuce} using the microwave 1.' in observation, f'Error in [Step 4]: I cannot heat the {found_lettuce} using the microwave 1. {agent.report()} I should have been at microwave 1 and holding {found_lettuce}. '
    
    if start_from <= 5:
        print("[Step 5] go to a diningtable and put the lettuce on it")
        # There are multiple diningtables, and I only need to go to one of them.
        observation = agent.goto('diningtable 1')
        # check if the diningtable is closed. If so, open it.
        if 'closed' in observation:
            observation = agent.open_receptacle('diningtable 1')
        observation = agent.put(found_lettuce, 'diningtable 1')
        # expectation: I should be able to put the lettuce on the diningtable.
        assert f'You put the {found_lettuce} in/on the diningtable 1.' in observation, f'Error in [Step 5]: I cannot put the {found_lettuce} on the diningtable 1. {agent.report()}'
'''.strip()

cool_example = '''
# define environment and agent
receptacles = ['diningtable 1','drawer 2', 'drawer 1', 'sinkbasin 1', 'toilet 1', 'sidetable 2', 'sidetable 1', 'cabinet 1', 'countertop 1', 'microwave 1', 'fridge 1']
agent = Agent(receptacles)

# Your task is to: put a cold lettuce in diningtable / cool some lettuce and put it in diningtable.
# here is a solution:
def solution(agent, start_from=1):
    # General plan: I need to get a list of receptacles to find the lettuce, take the lettuce to the fridge, cool it and put it in a diningtable.
    if start_from <= 1:
        print("[Step 1] get a list of receptacles where the lettuce is likely to appear.")
        # I can ask the assistant to do that.
        answer = ask(f'Given a list of receptacles, please sort them in descending order based on the likelihood of finding a lettuce in each of them. The list of receptacles is: {agent.receptacles}. You should directly return a Python list.')
        recep_to_check = literal_eval(answer)
        # expectation: the returned recep_to_check should not be empty.
        assert recep_to_check, f'Error in [Step 1]: recep_to_check should not be empty. {agent.report()}'

    if start_from <= 2:
        print("[Step 2] go to each receptacle in the list until seeing a lettuce")
        for receptacle in recep_to_check:
            observation = agent.goto(receptacle)
            # check if the receptacle is closed. If so, open it.
            if 'closed' in observation:
                observation = agent.open_receptacle(receptacle)
            # check if a lettuce is in/on the receptacle.
            if 'lettuce' in observation:
                break
        # expectation: I should be able to find a receptacle where a lettuce is in/on it.
        assert 'lettuce' in observation, f'Error in [Step 2]: There is no lettuce in/on {recep_to_check}. {agent.report()}'

    if start_from <= 3:
        print("[Step 3] identify the lettuce I juts found and take it")
        # I need to get the identifier of the lettuce. I can ask the assistant to do that.
        answer = ask(f'From the observation, get the identifier of an object. For example, On the cabinet 1, you see a cloth 2, and a toiletpaper 2. The identifier of cloth is 2. Now, {observation} The identifier of the lettuce? Only Output a single number without any other words. ')
        found_lettuce = f'lettuce {answer}'
        observation = agent.take(found_lettuce, receptacle)
        # expectation: I should be able to take the lettuce from the receptacle.
        assert agent.holding == found_lettuce, f'Error in [Step 3]: I cannot take {found_lettuce} from the {receptacle}. {agent.report()}'
    
    if start_from <= 4:
        print("[Step 4] go to a fridge to cool the lettuce")
        # I should go to a fridge to cool the lettuce.
        observation = agent.goto('fridge 1')
        # check if the fridge is closed. If so, open it.
        if 'closed' in observation:
            observation = agent.open_receptacle('fridge 1')
        observation = agent.cool(found_lettuce, 'fridge 1')
        # expectation: I should be able to cool the lettuce.
        assert f'You cool the {found_lettuce} using the fridge 1.' in observation, f'Error in [Step 4]: I cannot cool the {found_lettuce} using the fridge 1. {agent.report()} I should have been at fridge 1 and holding {found_lettuce}.'
    
    if start_from <= 5:
        print("[Step 5] go to a diningtable and put the lettuce on it")
        # There are multiple diningtables, and I only need to go to one of them.
        observation = agent.goto('diningtable 1')
        # check if the diningtable is closed. If so, open it.
        if 'closed' in observation:
            observation = agent.open_receptacle('diningtable 1')
        observation = agent.put(found_lettuce, 'diningtable 1')
        # expectation: I should be able to put the lettuce on the diningtable.
        assert f'You put the {found_lettuce} in/on the diningtable 1.' in observation, f'Error in [Step 5]: I cannot put the {found_lettuce} on the diningtable 1. {agent.report()}'
'''.strip()

# puttwo_example = '''
# # pick_two_cool_then_place, that is, with given objecttype and receptacletype, 
# # 1. pick an object and record its name by `self.old_object_name`, and go to a fridge and cool it, then place it in a receptacle. 
# # 2. pick another object that `object.name != self.old_object_name`, and go to a fridge and cool it, then place it in a receptacle.
# # here is the completed `def pick_two_cool_then_place(self, objecttype, receptacletype)`:

# def pick_two_cool_then_place(self, objecttype, receptacletype):
#     def pick_two_cool_then_place():
#         if self.name2type(self.holding) != self.name2type(objecttype):
#             # Find the object
#             entity = self.find_object(objecttype)
#             if entity is None:
#                 # Find receptacletypes that can contain the object
#                 r_types = self.find_canbe_contained(objecttype)
#                 if r_types is None:
#                     # Explore the environment
#                     self.explore()
#                     return
#                 else:
#                     # Check each receptacletype to find the object
#                     for r_type in r_types:
#                         r_entitys = self.find_receptacles(r_type)
#                         if r_entitys is None:
#                             continue
#                         for r_entity in r_entitys:
#                             self.goto(r_entity.name)
#                             if r_entity.openable and not r_entity.isopen:
#                                 self.open_receptacle(r_entity.name)
#                                 return
#             else:
#                 # Go to the object's receptacle and take it
#                 if hasattr(self, "old_object_name") and self.old_object_name == entity.name:
#                     object_entitys = self.find_objects(objecttype)
#                     if len(object_entitys) == 1:
#                         self.explore() # if there is only one object, then explore the environment to find more objects
#                         return
#                     else:
#                         pick_two_cool_then_place() # if there are more than one object, then pick_two_cool_then_place again
#                         return
#                 self.goto(entity.in_on)
#                 r_entity = self.seen_entitys[entity.in_on]
#                 if r_entity.openable and not r_entity.isopen:
#                     self.open_receptacle(r_entity.name)
#                 self.take(entity.name, entity.in_on)
#                 return
#         else:
#             # Cool the object
#             r_entity = self.find_receptacle('fridge')
#             if r_entity is None:
#                 # Explore the environment to find the fridge
#                 self.explore()
#                 return
#             else:
#                 self.goto(r_entity.name)
#                 if r_entity.openable and not r_entity.isopen:
#                     self.open_receptacle(r_entity.name)
#                 # set the old_object_name to the holding
#                 setattr(self, "old_object_name", self.holding)
#                 self.cool(self.holding, r_entity.name)
#                 # Place the object in the receptacle
#                 r_entity = self.find_receptacle(receptacletype)
#                 if r_entity is None:
#                     # Explore the environment to find the receptacle
#                     self.explore()
#                     return
#                 else:
#                     self.goto(r_entity.name)
#                     if r_entity.openable and not r_entity.isopen:
#                         self.open_receptacle(r_entity.name)
#                     self.put(self.holding, r_entity.name)
#                     return
                
#     pick_two_cool_then_place() # start the pick_two_cool_then_place process
# '''.strip()

puttwo_example = '''
# look_at_two_obj, that is, with given objecttype, 
# 1. pick an object and record its name by `self.old_object_name`, and go to a desklamp then turn on it to examine the object. 
# 2. pick another object that `object.name != self.old_object_name`, and go to a desklamp then turn on it to examine the object.
# here is the completed `def look_at_two_obj(self, objecttype)`:

def look_at_two_obj(self, objecttype):
    def look_at_two_obj():
        # we need look at two objects (or twice), so here we define a recursive function
        if self.name2type(self.holding) != self.name2type(objecttype):
            # Find the object
            entity = self.find_object(objecttype)
            if entity is None:
                # Find receptacletypes that can contain the object
                r_types = self.find_canbe_contained(objecttype)
                if r_types is None:
                    # Explore the environment
                    self.explore()
                    return
                else:
                    # Check each receptacletype to find the object
                    for r_type in r_types:
                        r_entitys = self.find_receptacles(r_type)
                        if r_entitys is None:
                            continue
                        for r_entity in r_entitys:
                            self.goto(r_entity.name)
                            if r_entity.openable and not r_entity.isopen:
                                self.open_receptacle(r_entity.name)
                                return
            else:
                # Go to the object's receptacle and take it
                if hasattr(self, "old_object_name") and self.old_object_name == entity.name:
                    # hasattr means that we have looked one object before, so we need to find another to look
                    object_entitys = self.find_objects(objecttype)
                    if len(object_entitys) == 1:
                        self.explore() # if there is only one object, then explore the environment to find more objects
                        return
                    else:
                        look_at_two_obj() # if there are more than one object, then look_at_two_obj again
                        return
                self.goto(entity.in_on)
                r_entity = self.seen_entitys[entity.in_on]
                if r_entity.openable and not r_entity.isopen:
                    self.open_receptacle(r_entity.name)
                self.take(entity.name, entity.in_on)
                return
        else:
            # Find the desklamp to look at object
            r_entity = self.find_object("desklamp")
            if r_entity is None:
                # Explore the environment
                self.explore()
                return
            else:
                self.goto(r_entity.name)
                if not r_entity.iso:
                    self.turn_on(r_entity.name) # turn on the desklamp to look at object
                
                setattr(self, "old_object_name", self.holding)
                return
                
    look_at_two_obj() # start the look_at_two_obj process
'''.strip()

examine_example = '''
# pick_and_place, that is, with given objecttype and receptacletype, pick an object and place it in a receptacle.
# here is the completed `def pick_and_place(self, objecttype, receptacletype)`:
def pick_and_place(self, objecttype, receptacletype):
    # Pick an object with the given objecttype
    if self.name2type(self.holding) != self.name2type(objecttype):
        # Find the object
        entity = self.find_object(objecttype)
        if entity is None:
            # Find receptacletypes that can contain the object
            r_types = self.find_canbe_contained(objecttype)
            if r_types is None:
                # Explore the environment
                self.explore()
                return
            else:
                # Check each receptacletype to find the object
                for r_type in r_types:
                    r_entitys = self.find_receptacles(r_type)
                    if r_entitys is None:
                        continue
                    for r_entity in r_entitys:
                        self.goto(r_entity.name)
                        if r_entity.openable and not r_entity.isopen:
                            self.open_receptacle(r_entity.name)
                            return
        else:
            # Go to the object's receptacle and take it
            self.goto(entity.in_on)
            r_entity = self.seen_entitys[entity.in_on]
            if r_entity.openable and not r_entity.isopen:
                self.open_receptacle(r_entity.name)
            self.take(entity.name, entity.in_on)
            return
    else:
        # Place the object in the receptacle
        r_entity = self.find_receptacle(receptacletype)
        if r_entity is None:
            # Explore the environment to find the receptacle
            self.explore()
            return
        else:
            self.goto(r_entity.name)
            if r_entity.openable and not r_entity.isopen:
                self.open_receptacle(r_entity.name)
            self.put(self.holding, r_entity.name)
            return
'''.strip()

code_check_prompt = '''
You are given a Python code snippet define a function called solution. 

[Code]
<solution_func>

Question 1: Are there any syntax error present in the code? Answer Yes/No.
Question 2: Fix the syntax errors and output an error-free version of the code. Only Output the revised code after [Revised code] without any other words.
'''.strip()

feedback_fix_prompt = f'''
{basic_info}

# Here is an example of a solution to the task:

<example>

# Here is the actual task.
# <task>, that is, <task_method_desc>

You have generated code of <task_method> to solve the task as follows:
<python_program_0>

<python_program_1>

<python_program_2>
 
However, you executed the <task_method> function and get an interaction history as follows:
<interaction_history>

Let's think step by step. Referring to the successful case and the interaction history, you should generate superior solution function.

<task_method>:
# Note: Do not directly call the `<example_method_name>` in the example. You should use its code as a reference.
'''.strip()

get_start_from_prompt = f'''
Previously, you generated some code defining a solution function as in [Previous solution]. The previous code is executed and outputs some error. Now you just revised the code as in [Revised solution]. Determine from which step these two version differs. You should only output the step number without saying any other words.

[Previous solution]
<previous_solution>

[Revised solution]
<revised_solution>
'''.strip()

