# Copyright (c) Microsoft. All rights reserved. from typing import Optional from autogen_core.models import AssistantMessage, UserMessage class HistoryPromptBuilder: """ Builds prompts using a history of observations and actions. Supports two prompt styles: - chat: multi-turn user/assistant messages - single: a single formatted prompt with optional history """ def __init__(self, max_history: int = -1, prompt_type: str = "chat"): """ Args: max_history (int): Maximum number of past steps to include (-1 means unlimited). prompt_type (str): Prompt style ("chat" or "single"). """ self.max_history = max_history self.prompt_type = prompt_type self._events = [] self.admissible_actions = None self.step_count = 1 def update_step_count(self): """Increment the current step counter.""" self.step_count += 1 def update_instruction_prompt(self, instruction: str): """Set the instruction/system prompt used in chat mode.""" self.instruction = instruction def update_single_obs_template(self, single_obs_template_wo_his: str, single_obs_template: str): """ Set templates for single-prompt mode. Args: single_obs_template_wo_his (str): Template without history. single_obs_template (str): Template with history. """ self.single_obs_template_wo_his = single_obs_template_wo_his self.single_obs_template = single_obs_template def update_observation(self, obs: dict): """Append an observation to the event history.""" self._events.append( { "type": "observation", "text": obs, } ) def update_action(self, action: str): """Append an action to the event history.""" self._events.append( { "type": "action", "action": action, } ) def update_admissible_actions(self, admissible_actions): """Update the list of admissible actions for the current step.""" self.admissible_actions = admissible_actions def init(self, env): """ Initialize the prompt builder at the beginning of an episode. - Clears the event history - Loads prompt instructions or templates from the environment """ self._events.clear() if self.prompt_type == "chat": inst_prompt = env.get_instruction_prompt() self.update_instruction_prompt(inst_prompt) elif self.prompt_type == "single": template_wo_his, template = env.get_single_prompt_template() self.update_single_obs_template(template_wo_his, template) else: raise ValueError(f"Unsupported prompt_type: {self.prompt_type}") def get_chat_prompt(self): """ Construct a chat-style prompt from the event history. Returns: List[Message]: A sequence of User and Assistant messages. """ if self.max_history != -1: events = self._events[-(self.max_history * 2 + 1) :] else: events = self._events messages = [] for idx, event in enumerate(events): event_type = event.get("type") message = None if event_type == "observation": content = event.get("text", "") # Attach instruction prompt to the first observation if idx == 0 and self.instruction: content += "\n" + self.instruction message = UserMessage(source="user", content=content) elif event_type == "action": content = event.get("action", "") message = AssistantMessage(source="assistant", content=content) if message: messages.append(message) return messages def get_single_prompt(self): """ Construct a single formatted prompt using templates. Returns: List[Message]: A single User message. """ if self.max_history != -1: events = self._events[-(self.max_history * 2 + 1) :] else: events = self._events current_obs = events[-1]["text"] # Case 1: No history available if len(events) == 1: template = self.single_obs_template_wo_his kwargs = {"current_observation": current_obs} if "{admissible_actions}" in template: kwargs["admissible_actions"] = self.admissible_actions single_prompt = template.format(**kwargs) # Case 2: History exists else: template = self.single_obs_template history = "" obs_count = 0 for idx, event in enumerate(events): if events[idx]["type"] == "observation" and idx != len(events) - 1: next_event = events[idx + 1] history += f"[Observation {max(self.step_count-self.max_history+obs_count, 1)}: '{event['text']}', " history += ( f"Action {max(self.step_count-self.max_history+obs_count, 1)}: '{next_event['action']}']\n " ) obs_count += 1 kwargs = { "step_count": self.step_count - 1, "history_length": min(self.step_count - 1, self.max_history), "history": history, "current_step": self.step_count, "current_observation": current_obs, } if "{admissible_actions}" in template: kwargs["admissible_actions"] = self.admissible_actions single_prompt = template.format(**kwargs) return [UserMessage(source="user", content=single_prompt)] def get_prompt(self): """ Return the final prompt based on the configured prompt type. """ if self.prompt_type == "chat": prompt = self.get_chat_prompt() elif self.prompt_type == "single": prompt = self.get_single_prompt() return prompt