import html
import json
import re
from datetime import datetime
from typing import List, Union
from pydantic import BaseModel, Field
from letta.helpers.json_helpers import json_dumps
from letta.schemas.enums import JobStatus, MessageStreamStatus
from letta.schemas.letta_message import LettaMessage, LettaMessageUnion
from letta.schemas.letta_stop_reason import LettaStopReason
from letta.schemas.message import Message
from letta.schemas.usage import LettaUsageStatistics
# TODO: consider moving into own file
class LettaResponse(BaseModel):
"""
Response object from an agent interaction, consisting of the new messages generated by the agent and usage statistics.
The type of the returned messages can be either `Message` or `LettaMessage`, depending on what was specified in the request.
Attributes:
messages (List[Union[Message, LettaMessage]]): The messages returned by the agent.
usage (LettaUsageStatistics): The usage statistics
"""
messages: List[LettaMessageUnion] = Field(
...,
description="The messages returned by the agent.",
json_schema_extra={
"items": {
"$ref": "#/components/schemas/LettaMessageUnion",
}
},
)
stop_reason: LettaStopReason = Field(
...,
description="The stop reason from Letta indicating why agent loop stopped execution.",
)
usage: LettaUsageStatistics = Field(
...,
description="The usage statistics of the agent.",
)
def __str__(self):
return json_dumps(
{
"messages": [message.model_dump() for message in self.messages],
# Assume `Message` and `LettaMessage` have a `dict()` method
"usage": self.usage.model_dump(), # Assume `LettaUsageStatistics` has a `dict()` method
},
indent=4,
)
def _repr_html_(self):
def get_formatted_content(msg):
if msg.message_type == "internal_monologue":
return f'
{html.escape(msg.internal_monologue)}
'
if msg.message_type == "reasoning_message":
return f'{html.escape(msg.reasoning)}
'
elif msg.message_type == "function_call":
args = format_json(msg.function_call.arguments)
return f'{html.escape(msg.function_call.name)}({args})
'
elif msg.message_type == "tool_call_message":
args = format_json(msg.tool_call.arguments)
return f'{html.escape(msg.tool_call.name)}({args})
'
elif msg.message_type == "function_return":
return_value = format_json(msg.function_return)
# return f'Status: {html.escape(msg.status)}
{return_value}
'
return f'{return_value}
'
elif msg.message_type == "tool_return_message":
return_value = format_json(msg.tool_return)
# return f'Status: {html.escape(msg.status)}
{return_value}
'
return f'{return_value}
'
elif msg.message_type == "user_message":
if is_json(msg.message):
return f'{format_json(msg.message)}
'
else:
return f'{html.escape(msg.message)}
'
elif msg.message_type in ["assistant_message", "system_message"]:
return f'{html.escape(msg.message)}
'
else:
return f'{html.escape(str(msg))}
'
def is_json(string):
try:
json.loads(string)
return True
except ValueError:
return False
def format_json(json_str):
try:
parsed = json.loads(json_str)
formatted = json.dumps(parsed, indent=2, ensure_ascii=False)
formatted = formatted.replace("&", "&").replace("<", "<").replace(">", ">")
formatted = formatted.replace("\n", "
").replace(" ", " ")
formatted = re.sub(r'(".*?"):', r'\1:', formatted)
formatted = re.sub(r': (".*?")', r': \1', formatted)
formatted = re.sub(r": (\d+)", r': \1', formatted)
formatted = re.sub(r": (true|false)", r': \1', formatted)
return formatted
except json.JSONDecodeError:
return html.escape(json_str)
html_output = """
"""
for msg in self.messages:
content = get_formatted_content(msg)
title = msg.message_type.replace("_", " ").upper()
html_output += f"""
"""
html_output += "
"
# Formatting the usage statistics
usage_html = json.dumps(self.usage.model_dump(), indent=2)
html_output += f"""
USAGE STATISTICS
{format_json(usage_html)}
"""
return html_output
# The streaming response is either [DONE], [DONE_STEP], [DONE], an error, or a LettaMessage
LettaStreamingResponse = Union[LettaMessage, MessageStreamStatus, LettaStopReason, LettaUsageStatistics]
class LettaBatchResponse(BaseModel):
letta_batch_id: str = Field(..., description="A unique identifier for the Letta batch request.")
last_llm_batch_id: str = Field(..., description="A unique identifier for the most recent model provider batch request.")
status: JobStatus = Field(..., description="The current status of the batch request.")
agent_count: int = Field(..., description="The number of agents in the batch request.")
last_polled_at: datetime = Field(..., description="The timestamp when the batch was last polled for updates.")
created_at: datetime = Field(..., description="The timestamp when the batch request was created.")
class LettaBatchMessages(BaseModel):
messages: List[Message]