# Vendored from SkillRL (Apache-2.0 License) # Original: agent_system/environments/env_manager.py # Trimmed to only include AlfWorldEnvironmentManager and its helpers. from typing import List, Dict, Any from collections import defaultdict import numpy as np from skillopt.envs.alfworld.vendor.env_base import EnvironmentManagerBase, to_numpy from skillopt.envs.alfworld.vendor.alfworld_prompts import ( ALFWORLD_TEMPLATE, ALFWORLD_TEMPLATE_NO_HIS, ALFWORLD_TEMPLATE_WITH_MEMORY, ) from skillopt.envs.alfworld.vendor.memory import SimpleMemory def parse_gamefile(infos): gamefile = [] for info in infos: if 'extra.gamefile' in info: gamefile.append(info['extra.gamefile']) else: gamefile.append(None) return gamefile def set_gamefile(infos, gamefile): for i in range(len(infos)): if 'extra.gamefile' in infos[i]: infos[i]['extra.gamefile'] = gamefile[i] else: infos[i]['extra.gamefile'] = None return infos class AlfWorldEnvironmentManager(EnvironmentManagerBase): """Manages parallel ALFWorld environments with observation templating.""" def __init__(self, envs, projection_f, config): self.memory = SimpleMemory() self.retrieval_memory = None super().__init__(envs, projection_f, config) def reset(self, kwargs): text_obs, image_obs, infos = self.envs.reset() self.gamefile = parse_gamefile(infos) self.memory.reset(batch_size=len(text_obs)) self.tasks = [] self.pre_text_obs = text_obs self.extract_task(text_obs) full_text_obs = self.build_text_obs(text_obs, self.envs.get_admissible_commands, init=True) return {'text': full_text_obs, 'image': image_obs, 'anchor': text_obs}, infos def step(self, text_actions: List[str]): actions, valids = self.projection_f(text_actions, self.envs.get_admissible_commands) text_obs, image_obs, rewards, dones, infos = self.envs.step(actions) self.memory.store({'text_obs': self.pre_text_obs, 'action': actions}) self.pre_text_obs = text_obs full_text_obs = self.build_text_obs(text_obs, self.envs.get_admissible_commands) if infos[0].get("extra.gamefile") is None: infos = set_gamefile(infos, self.gamefile) for i, info in enumerate(infos): info['is_action_valid'] = to_numpy(valids[i]) next_observations = {'text': full_text_obs, 'image': image_obs, 'anchor': text_obs} rewards = to_numpy(rewards) dones = to_numpy(dones) return next_observations, rewards, dones, infos def extract_task(self, text_obs: List[str]): for obs in text_obs: task_start = obs.find('Your task is to: ') if task_start != -1: self.tasks.append(obs[task_start + len('Your task is to: '):].strip()) else: raise ValueError("Task description not found in text observation.") def build_text_obs(self, text_obs: List[str], admissible_actions: List[List[str]], init: bool = False) -> List[str]: postprocess_text_obs = [] if not init and self.config.env.history_length > 0: memory_contexts, valid_lens = self.memory.fetch( self.config.env.history_length, obs_key="text_obs", action_key="action", ) for i in range(len(text_obs)): reformatted_admissible_actions = "\n ".join( f"'{s}'" for s in admissible_actions[i] if s != 'help' ) if init or self.config.env.history_length <= 0: obs = ALFWORLD_TEMPLATE_NO_HIS.format( current_observation=text_obs[i], admissible_actions=reformatted_admissible_actions, ) else: obs = ALFWORLD_TEMPLATE.format( task_description=self.tasks[i], step_count=len(self.memory[i]), history_length=valid_lens[i], action_history=memory_contexts[i], current_step=len(self.memory[i]) + 1, current_observation=text_obs[i], admissible_actions=reformatted_admissible_actions, ) postprocess_text_obs.append(obs) return postprocess_text_obs def _process_batch(self, batch_idx, total_batch_list, total_infos, success): for i in reversed(range(len(total_batch_list[batch_idx]))): batch_item = total_batch_list[batch_idx][i] if batch_item['active_masks']: info = total_infos[batch_idx][i] won_value = float(info['won']) success['success_rate'].append(won_value) gamefile = info.get("extra.gamefile") if gamefile: self._process_gamefile(gamefile, won_value, success) return def _process_gamefile(self, gamefile, won_value, success): tasks = [ "pick_and_place", "pick_two_obj_and_place", "look_at_obj_in_light", "pick_heat_then_place_in_recep", "pick_cool_then_place_in_recep", "pick_clean_then_place_in_recep", ] for task in tasks: if task in gamefile: success[f"{task}_success_rate"].append(won_value) break