import json from typing import Dict, Optional, Union from letta.agents.letta_agent import LettaAgent from letta.interface import AgentInterface from letta.orm.group import Group from letta.orm.user import User from letta.schemas.agent import AgentState from letta.schemas.group import ManagerType from letta.schemas.letta_message_content import ImageContent, ReasoningContent, TextContent from letta.schemas.message import Message from letta.services.mcp.base_client import AsyncBaseMCPClient def load_multi_agent( group: Group, agent_state: Optional[AgentState], actor: User, interface: Union[AgentInterface, None] = None, mcp_clients: Optional[Dict[str, AsyncBaseMCPClient]] = None, ) -> LettaAgent: if len(group.agent_ids) == 0: raise ValueError("Empty group: group must have at least one agent") if not agent_state: raise ValueError("Empty manager agent state: manager agent state must be provided") match group.manager_type: case ManagerType.round_robin: from letta.groups.round_robin_multi_agent import RoundRobinMultiAgent return RoundRobinMultiAgent( agent_state=agent_state, interface=interface, user=actor, group_id=group.id, agent_ids=group.agent_ids, description=group.description, max_turns=group.max_turns, ) case ManagerType.dynamic: from letta.groups.dynamic_multi_agent import DynamicMultiAgent return DynamicMultiAgent( agent_state=agent_state, interface=interface, user=actor, group_id=group.id, agent_ids=group.agent_ids, description=group.description, max_turns=group.max_turns, termination_token=group.termination_token, ) case ManagerType.supervisor: from letta.groups.supervisor_multi_agent import SupervisorMultiAgent return SupervisorMultiAgent( agent_state=agent_state, interface=interface, user=actor, group_id=group.id, agent_ids=group.agent_ids, description=group.description, ) case ManagerType.sleeptime: if not agent_state.enable_sleeptime: return LettaAgent( agent_state=agent_state, interface=interface, user=actor, mcp_clients=mcp_clients, ) from letta.groups.sleeptime_multi_agent import SleeptimeMultiAgent return SleeptimeMultiAgent( agent_state=agent_state, interface=interface, user=actor, group_id=group.id, agent_ids=group.agent_ids, description=group.description, sleeptime_agent_frequency=group.sleeptime_agent_frequency, ) case _: raise ValueError(f"Type {group.manager_type} is not supported.") 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 = [] for content in message.content: if isinstance(content, TextContent): messages.append(f"{message.name or 'user'}: {content.text}") elif isinstance(content, ImageContent): messages.append(f"{message.name or 'user'}: [Image Here]") return "\n".join(messages) except Exception: 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: 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}") if message.tool_calls: 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) if str(content["message"]) != "None": return f"{assistant_name}: Tool call returned {content['message']}" return None elif message.role == "system": return None else: 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