# Copyright (c) Meta Platforms, Inc. and affiliates.
# All rights reserved.
#
# This source code is licensed under the terms described in the LICENSE file in
# the root directory of this source tree.
import json
import re
import uuid
from abc import ABC, abstractmethod
from dataclasses import asdict, dataclass, field
from typing import Any
from are.simulation.agents.multimodal import Attachment
from are.simulation.utils import make_serializable
[docs]
@dataclass
class BaseAgentLog(ABC):
timestamp: float
id: str = field(init=False)
agent_id: str
def __post_init__(self) -> None:
self.id = str(uuid.uuid4().hex)
[docs]
@abstractmethod
def get_content_for_llm(self) -> str | None: ...
[docs]
def get_attachments_for_llm(self) -> list[Attachment]:
"""
Contains attachments that should be sent to the LLM
:return: Attachments to include in LLM message for this log entry.
:rtype: list[Attachment]
"""
return []
[docs]
@abstractmethod
def get_type(self) -> str: ...
[docs]
def to_dict(self) -> dict[str, Any]:
data = asdict(self)
data["log_type"] = self.get_type()
return data
[docs]
def serialize(self) -> str:
return json.dumps(make_serializable(self.to_dict()))
[docs]
@classmethod
def from_dict(cls, d: dict[str, Any]) -> "BaseAgentLog":
log_type = d.pop("log_type", None)
id = d.pop("id", str(uuid.uuid4().hex))
if log_type is None:
raise ValueError("Log type is not specified")
log_type_map = {
"system_prompt": SystemPromptLog,
"task": TaskLog,
"llm_input": LLMInputLog,
"llm_output": LLMOutputThoughtActionLog,
"llm_output_thought_action": LLMOutputThoughtActionLog,
"rationale": RationaleLog,
"tool_call": ToolCallLog,
"observation": ObservationLog,
"step": StepLog,
"subagent": SubagentLog,
"final_answer": FinalAnswerLog,
"error": ErrorLog,
"thought": ThoughtLog,
"plan": PlanLog,
"facts": FactsLog,
"replan": ReplanLog,
"refacts": RefactsLog,
"stop": StopLog,
"action": ActionLog,
"end_task": EndTaskLog,
"raw_plan": LLMOutputPlanLog,
"llm_output_plan": LLMOutputPlanLog,
"raw_facts": LLMOutputFactsLog,
"llm_output_facts": LLMOutputFactsLog,
"agent_user_interface": AgentUserInterfaceLog,
"environment_notifications": EnvironmentNotificationLog,
"hint": HintLog,
"task_reminder": TaskReminderLog,
}
log_class = log_type_map.get(log_type)
if log_class is not None:
log = log_class(**d)
if log_type == "subagent":
log.children = [
BaseAgentLog.from_dict(child) for child in d["children"]
]
log.id = id
return log
raise ValueError(f"Unknown agent log type: {log_type}")
[docs]
@dataclass
class SystemPromptLog(BaseAgentLog):
content: str
[docs]
def get_content_for_llm(self) -> str | None:
return self.content
[docs]
def get_type(self) -> str:
return "system_prompt"
[docs]
@dataclass
class TaskLog(BaseAgentLog):
content: str
attachments: list[Attachment] = field(default_factory=list)
[docs]
def get_content_for_llm(self) -> str | None:
return self.content
[docs]
def get_content_for_llm_no_attachment(self) -> str | None:
content = re.sub(r"<\|attachment:(\d+)\|>", "", self.content)
return content
[docs]
def get_attachments_for_llm(self) -> list[Attachment]:
return self.attachments
[docs]
def get_type(self) -> str:
return "task"
[docs]
@dataclass
class LLMOutputThoughtActionLog(BaseAgentLog):
content: str
prompt_tokens: int = 0
completion_tokens: int = 0
total_tokens: int = 0
reasoning_tokens: int = 0
completion_duration: float = 0.0
[docs]
def get_content_for_llm(self) -> str | None:
return self.content
[docs]
def get_type(self) -> str:
return "llm_output"
[docs]
@dataclass
class RationaleLog(BaseAgentLog):
content: str
[docs]
def get_content_for_llm(self) -> str | None:
return self.content
[docs]
def get_type(self) -> str:
return "rationale"
[docs]
@dataclass
class ObservationLog(BaseAgentLog):
content: str
attachments: list[Attachment] = field(default_factory=list)
[docs]
def get_content_for_llm(self) -> str | None:
return self.content
[docs]
def get_content_for_llm_no_attachment(self) -> str | None:
content = re.sub(r"<\|attachment:(\d+)\|>", "", self.content)
return content
[docs]
def get_attachments_for_llm(self) -> list[Attachment]:
return self.attachments
[docs]
def get_type(self) -> str:
return "observation"
[docs]
@dataclass
class StepLog(BaseAgentLog):
iteration: int
[docs]
def get_content_for_llm(self) -> str | None:
return None
[docs]
def get_type(self) -> str:
return "step"
[docs]
@dataclass
class SubagentLog(BaseAgentLog):
group_id: str
children: list[BaseAgentLog]
name: str | None = None
[docs]
def get_content_for_llm(self) -> str | None:
result = []
for child in self.children:
content_for_llm = child.get_content_for_llm()
if content_for_llm is not None:
result.append(content_for_llm)
if len(result) == 0:
return None
else:
return "\n".join(result)
[docs]
def get_type(self) -> str:
return "subagent"
[docs]
def to_dict(self) -> dict[str, Any]:
data = super().to_dict()
data["children"] = [child.to_dict() for child in self.children]
return data
[docs]
@dataclass
class FinalAnswerLog(BaseAgentLog):
content: str
attachments: list[Attachment] = field(default_factory=list)
[docs]
def get_content_for_llm(self) -> str | None:
return self.content
[docs]
def get_content_for_llm_no_attachment(self) -> str | None:
content = re.sub(r"<\|attachment:(\d+)\|>", "", self.content)
return content
[docs]
def get_attachments_for_llm(self) -> list[Attachment]:
return self.attachments
[docs]
def get_type(self) -> str:
return "final_answer"
[docs]
@dataclass
class ErrorLog(BaseAgentLog):
error: str
exception: str
category: str
agent: str
[docs]
def get_content_for_llm(self) -> str | None:
return f"Error: {self.error}\nException: {self.exception}\nCategory: {self.category}"
[docs]
def get_type(self) -> str:
return "error"
[docs]
@dataclass
class ThoughtLog(BaseAgentLog):
content: str
[docs]
def get_content_for_llm(self) -> str | None:
return None
[docs]
def get_type(self) -> str:
return "thought"
[docs]
@dataclass
class PlanLog(BaseAgentLog):
content: str
[docs]
def get_content_for_llm(self) -> str | None:
return self.content
[docs]
def get_type(self) -> str:
return "plan"
[docs]
@dataclass
class FactsLog(BaseAgentLog):
content: str
[docs]
def get_content_for_llm(self) -> str | None:
return self.content
[docs]
def get_type(self) -> str:
return "facts"
[docs]
@dataclass
class ReplanLog(BaseAgentLog):
content: str
[docs]
def get_content_for_llm(self) -> str | None:
return self.content
[docs]
def get_type(self) -> str:
return "replan"
[docs]
@dataclass
class RefactsLog(BaseAgentLog):
content: str
[docs]
def get_content_for_llm(self) -> str | None:
return self.content
[docs]
def get_type(self) -> str:
return "refacts"
[docs]
@dataclass
class StopLog(BaseAgentLog):
content: str
[docs]
def get_content_for_llm(self) -> str | None:
return None
[docs]
def get_type(self) -> str:
return "stop"
[docs]
@dataclass
class ActionLog(BaseAgentLog):
content: str
input: dict[str, Any]
event_type: str
output: Any
action_name: str
app_name: str
exception: str | None
exception_stack_trace: str | None
[docs]
def get_content_for_llm(self) -> str | None:
return f"Action: {self.action_name}\nInput: {self.input}\nOutput: {self.output}"
[docs]
def get_type(self) -> str:
return "action"
[docs]
@dataclass
class EndTaskLog(BaseAgentLog):
[docs]
def get_content_for_llm(self) -> str | None:
return None
[docs]
def get_type(self) -> str:
return "end_task"
[docs]
@dataclass
class LLMOutputPlanLog(BaseAgentLog):
content: str
prompt_tokens: int = 0
completion_tokens: int = 0
total_tokens: int = 0
reasoning_tokens: int = 0
completion_duration: float = 0.0
[docs]
def get_content_for_llm(self) -> str | None:
return self.content
[docs]
def get_type(self) -> str:
return "llm_output_plan"
[docs]
@dataclass
class LLMOutputFactsLog(BaseAgentLog):
content: str
prompt_tokens: int = 0
completion_tokens: int = 0
total_tokens: int = 0
reasoning_tokens: int = 0
completion_duration: float = 0.0
[docs]
def get_content_for_llm(self) -> str | None:
return self.content
[docs]
def get_type(self) -> str:
return "llm_output_facts"
[docs]
@dataclass
class AgentUserInterfaceLog(BaseAgentLog):
content: str
[docs]
def get_content_for_llm(self) -> str | None:
return self.content
[docs]
def get_type(self) -> str:
return "agent_user_interface"
[docs]
@dataclass
class EnvironmentNotificationLog(BaseAgentLog):
content: str
[docs]
def get_content_for_llm(self) -> str | None:
return self.content
[docs]
def get_type(self) -> str:
return "environment_notifications"
[docs]
@dataclass
class HintLog(BaseAgentLog):
content: str
[docs]
def get_content_for_llm(self) -> str | None:
return self.content
[docs]
def get_type(self) -> str:
return "hint"
[docs]
@dataclass
class TaskReminderLog(BaseAgentLog):
content: str
[docs]
def get_content_for_llm(self) -> str | None:
return self.content
[docs]
def get_type(self) -> str:
return "task_reminder"