fix: new versions of send_message_to_agent that are async (#725)

Co-authored-by: Matt Zhou <mattzh1314@gmail.com>
This commit is contained in:
Charles Packer
2025-01-27 17:11:44 -08:00
committed by GitHub
parent 7eb44280c1
commit ec6e5d153c
12 changed files with 354 additions and 87 deletions

View File

@@ -1,80 +1,86 @@
import asyncio
from typing import TYPE_CHECKING, List, Optional
from typing import TYPE_CHECKING, List
from letta.constants import MULTI_AGENT_SEND_MESSAGE_MAX_RETRIES, MULTI_AGENT_SEND_MESSAGE_TIMEOUT
from letta.functions.helpers import async_send_message_with_retries
from letta.orm.errors import NoResultFound
from letta.functions.helpers import async_send_message_with_retries, execute_send_message_to_agent, fire_and_forget_send_to_agent
from letta.schemas.enums import MessageRole
from letta.schemas.message import MessageCreate
from letta.server.rest_api.utils import get_letta_server
if TYPE_CHECKING:
from letta.agent import Agent
def send_message_to_specific_agent(self: "Agent", message: str, other_agent_id: str) -> Optional[str]:
def send_message_to_agent_and_wait_for_reply(self: "Agent", message: str, other_agent_id: str) -> str:
"""
Send a message to a specific Letta agent within the same organization.
Sends a message to a specific Letta agent within the same organization and waits for a response. The sender's identity is automatically included, so no explicit introduction is needed in the message. This function is designed for two-way communication where a reply is expected.
Args:
message (str): The message to be sent to the target Letta agent.
other_agent_id (str): The identifier of the target Letta agent.
message (str): The content of the message to be sent to the target agent.
other_agent_id (str): The unique identifier of the target Letta agent.
Returns:
Optional[str]: The response from the Letta agent. It's possible that the agent does not respond.
str: The response from the target agent.
"""
server = get_letta_server()
messages = [MessageCreate(role=MessageRole.user, content=message, name=self.agent_state.name)]
return execute_send_message_to_agent(
sender_agent=self,
messages=messages,
other_agent_id=other_agent_id,
log_prefix="[send_message_to_agent_and_wait_for_reply]",
)
# Ensure the target agent is in the same org
try:
server.agent_manager.get_agent_by_id(agent_id=other_agent_id, actor=self.user)
except NoResultFound:
raise ValueError(
f"The passed-in agent_id {other_agent_id} either does not exist, "
f"or does not belong to the same org ({self.user.organization_id})."
)
# Async logic to send a message with retries and timeout
async def async_send_single_agent():
return await async_send_message_with_retries(
server=server,
sender_agent=self,
target_agent_id=other_agent_id,
message_text=message,
max_retries=MULTI_AGENT_SEND_MESSAGE_MAX_RETRIES, # or your chosen constants
timeout=MULTI_AGENT_SEND_MESSAGE_TIMEOUT, # e.g., 1200 for 20 min
logging_prefix="[send_message_to_specific_agent]",
)
def send_message_to_agent_async(self: "Agent", message: str, other_agent_id: str) -> str:
"""
Sends a message to a specific Letta agent within the same organization. The sender's identity is automatically included, so no explicit introduction is required in the message. This function does not expect a response from the target agent, making it suitable for notifications or one-way communication.
# Run in the current event loop or create one if needed
try:
return asyncio.run(async_send_single_agent())
except RuntimeError:
# e.g., in case there's already an active loop
loop = asyncio.get_event_loop()
if loop.is_running():
return loop.run_until_complete(async_send_single_agent())
else:
raise
Args:
message (str): The content of the message to be sent to the target agent.
other_agent_id (str): The unique identifier of the target Letta agent.
Returns:
str: A confirmation message indicating the message was successfully sent.
"""
message = (
f"[Incoming message from agent with ID '{self.agent_state.id}' - to reply to this message, "
f"make sure to use the 'send_message_to_agent_async' tool, or the agent will not receive your message] "
f"{message}"
)
messages = [MessageCreate(role=MessageRole.system, content=message, name=self.agent_state.name)]
# Do the actual fire-and-forget
fire_and_forget_send_to_agent(
sender_agent=self,
messages=messages,
other_agent_id=other_agent_id,
log_prefix="[send_message_to_agent_async]",
use_retries=False, # or True if you want to use async_send_message_with_retries
)
# Immediately return to caller
return "Successfully sent message"
def send_message_to_agents_matching_all_tags(self: "Agent", message: str, tags: List[str]) -> List[str]:
"""
Send a message to all agents in the same organization that match ALL of the given tags.
Messages are sent in parallel for improved performance, with retries on flaky calls and timeouts for long-running requests.
This function does not use a cursor (pagination) and enforces a limit of 100 agents.
Sends a message to all agents within the same organization that match all of the specified tags. Messages are dispatched in parallel for improved performance, with retries to handle transient issues and timeouts to ensure responsiveness. This function enforces a limit of 100 agents and does not support pagination (cursor-based queries). Each agent must match all specified tags (`match_all_tags=True`) to be included.
Args:
message (str): The message to be sent to each matching agent.
tags (List[str]): The list of tags that each agent must have (match_all_tags=True).
message (str): The content of the message to be sent to each matching agent.
tags (List[str]): A list of tags that an agent must possess to receive the message.
Returns:
List[str]: A list of responses from the agents that match all tags.
Each response corresponds to one agent.
List[str]: A list of responses from the agents that matched all tags. Each
response corresponds to a single agent. Agents that do not respond will not
have an entry in the returned list.
"""
server = get_letta_server()
# Retrieve agents that match ALL specified tags
matching_agents = server.agent_manager.list_agents(actor=self.user, tags=tags, match_all_tags=True, limit=100)
messages = [MessageCreate(role=MessageRole.user, content=message, name=self.agent_state.name)]
async def send_messages_to_all_agents():
tasks = [
@@ -82,7 +88,7 @@ def send_message_to_agents_matching_all_tags(self: "Agent", message: str, tags:
server=server,
sender_agent=self,
target_agent_id=agent_state.id,
message_text=message,
messages=messages,
max_retries=MULTI_AGENT_SEND_MESSAGE_MAX_RETRIES,
timeout=MULTI_AGENT_SEND_MESSAGE_TIMEOUT,
logging_prefix="[send_message_to_agents_matching_all_tags]",