diff --git a/letta/groups/helpers.py b/letta/groups/helpers.py
index 3b7f70d0..ea1503d7 100644
--- a/letta/groups/helpers.py
+++ b/letta/groups/helpers.py
@@ -87,6 +87,7 @@ def load_multi_agent(
def stringify_message(message: Message, use_assistant_name: bool = False) -> str | None:
assistant_name = message.name or "assistant" if use_assistant_name else "assistant"
+
if message.role == "user":
try:
messages = []
@@ -97,7 +98,9 @@ def stringify_message(message: Message, use_assistant_name: bool = False) -> str
messages.append(f"{message.name or 'user'}: [Image Here]")
return "\n".join(messages)
except:
- return f"{message.name or 'user'}: {message.content[0].text}"
+ if message.content and len(message.content) > 0:
+ return f"{message.name or 'user'}: {message.content[0].text}"
+ return None
elif message.role == "assistant":
messages = []
if message.content:
@@ -105,13 +108,42 @@ def stringify_message(message: Message, use_assistant_name: bool = False) -> str
if isinstance(content, TextContent):
messages.append(f"{assistant_name}: {content.text}")
elif isinstance(content, ReasoningContent):
- messages.append(f"{assistant_name}: *thinking* {content.reasoning}")
+ messages.append(f"{assistant_name}: {content.reasoning}")
if message.tool_calls:
- if message.tool_calls[0].function.name == "send_message":
- messages.append(f"{assistant_name}: {json.loads(message.tool_calls[0].function.arguments)['message']}")
- else:
- messages.append(f"{assistant_name}: Calling tool {message.tool_calls[0].function.name}")
- return "\n".join(messages)
+ for tool_call in message.tool_calls:
+ if tool_call.function.name == "send_message":
+ messages.append(f"{assistant_name}: {json.loads(tool_call.function.arguments)['message']}")
+ else:
+ messages.append(f"{assistant_name}: Calling tool {tool_call.function.name}")
+ return "\n".join(messages) if messages else None
+ elif message.role == "approval":
+ # role == "approval" has two cases:
+ # 1. Approval REQUEST: has tool_calls (assistant calling tool that needs HITL)
+ # 2. Approval RESPONSE: no tool_calls, has approve field (user's decision)
+
+ # Check if this is an approval request (has tool_calls)
+ if hasattr(message, "tool_calls") and message.tool_calls:
+ # Treat like assistant message calling a tool
+ messages = []
+ if message.content:
+ for content in message.content:
+ if isinstance(content, TextContent):
+ messages.append(f"{assistant_name}: {content.text}")
+ elif isinstance(content, ReasoningContent):
+ messages.append(f"{assistant_name}: {content.reasoning}")
+ for tool_call in message.tool_calls:
+ if tool_call.function.name == "send_message":
+ messages.append(f"{assistant_name}: {json.loads(tool_call.function.arguments)['message']}")
+ else:
+ messages.append(f"{assistant_name}: Calling tool {tool_call.function.name}")
+ return "\n".join(messages) if messages else None
+ else:
+ # Approval response - user approved/rejected
+ if hasattr(message, "approve") and message.approve is not None:
+ status = "approved" if message.approve else "rejected"
+ reason = f": {message.denial_reason}" if hasattr(message, "denial_reason") and message.denial_reason else ""
+ return f"[User {status}{reason}]"
+ return None
elif message.role == "tool":
if message.content:
content = json.loads(message.content[0].text)
@@ -121,4 +153,13 @@ def stringify_message(message: Message, use_assistant_name: bool = False) -> str
elif message.role == "system":
return None
else:
- return f"{message.name or 'user'}: {message.content[0].text}"
+ if message.content and len(message.content) > 0:
+ # Handle different content types
+ content_item = message.content[0]
+ if isinstance(content_item, TextContent):
+ return f"{message.name or 'user'}: {content_item.text}"
+ elif isinstance(content_item, ReasoningContent):
+ return f"{message.name or 'user'}: {content_item.reasoning}"
+ elif isinstance(content_item, ImageContent):
+ return f"{message.name or 'user'}: [Image Here]"
+ return None