Skip to content

tools

langroid/agent/tools/init.py

AddRecipientTool

Bases: ToolMessage

Used by LLM to add a recipient to the previous message, when it has forgotten to specify a recipient. This avoids having to re-generate the previous message (and thus saves token-cost and time).

response(agent)

Returns:

Type Description
ChatDocument

with content set to self.content and metadata.recipient set to self.recipient.

Source code in langroid/agent/tools/recipient_tool.py
def response(self, agent: ChatAgent) -> ChatDocument:
    """
    Returns:
        (ChatDocument): with content set to self.content and
            metadata.recipient set to self.recipient.
    """
    print(
        "[red]RecipientTool: "
        f"Added recipient {self.intended_recipient} to message."
    )
    if self.__class__._saved_content == "":
        recipient_request_name = RecipientTool.default_value("request")
        content = f"""
            Recipient specified but content is empty!
            This could be because the `{self.request}` tool/function was used 
            before using `{recipient_request_name}` tool/function.
            Resend the message using `{recipient_request_name}` tool/function.
            """
    else:
        content = self.__class__._saved_content  # use class-level attrib value
        # erase content since we just used it.
        self.__class__._saved_content = ""
    return ChatDocument(
        content=content,
        metadata=ChatDocMetaData(
            recipient=self.intended_recipient,
            # we are constructing this so it looks as it msg is from LLM
            sender=Entity.LLM,
        ),
    )

RecipientTool

Bases: ToolMessage

Used by LLM to send a message to a specific recipient.

Useful in cases where an LLM is talking to 2 or more agents (or an Agent and human user), and needs to specify which agent (task) its message is intended for. The recipient name should be the name of a task (which is normally the name of the agent that the task wraps, although the task can have its own name).

To use this tool/function-call, LLM must generate a JSON structure with these fields: { "request": "recipient_message", # also the function name when using fn-calling "intended_recipient": , "content": } The effect of this is that content will be sent to the intended_recipient task.

create(recipients, default='') classmethod

Create a restricted version of RecipientTool that only allows certain recipients, and possibly sets a default recipient.

Source code in langroid/agent/tools/recipient_tool.py
@classmethod
def create(cls, recipients: List[str], default: str = "") -> Type["RecipientTool"]:
    """Create a restricted version of RecipientTool that
    only allows certain recipients, and possibly sets a default recipient."""

    class RecipientToolRestricted(cls):  # type: ignore
        allowed_recipients = recipients
        default_recipient = default

    return RecipientToolRestricted

instructions() classmethod

Generate instructions for using this tool/function. These are intended to be appended to the system message of the LLM.

Source code in langroid/agent/tools/recipient_tool.py
@classmethod
def instructions(cls) -> str:
    """
    Generate instructions for using this tool/function.
    These are intended to be appended to the system message of the LLM.
    """
    recipients = []
    if has_field(cls, "allowed_recipients"):
        recipients = cls.default_value("allowed_recipients")
    if len(recipients) > 0:
        recipients_str = ", ".join(recipients)
        return f"""
        Since you will be talking to multiple recipients, 
        you must clarify who your intended recipient is, using 
        the `{cls.default_value("request")}` tool/function-call, by setting the 
        'intended_recipient' field to one of the following:
        {recipients_str},
        and setting the 'content' field to your message.
        """
    else:
        return f"""
        Since you will be talking to multiple recipients, 
        you must clarify who your intended recipient is, using 
        the `{cls.default_value("request")}` tool/function-call, by setting the 
        'intended_recipient' field to the name of the recipient, 
        and setting the 'content' field to your message.
        """

response(agent)

When LLM has correctly used this tool, construct a ChatDocument with an explicit recipient, and make it look like it is from the LLM.

Returns:

Type Description
ChatDocument

with content set to self.content and metadata.recipient set to self.intended_recipient.

Source code in langroid/agent/tools/recipient_tool.py
def response(self, agent: ChatAgent) -> str | ChatDocument:
    """
    When LLM has correctly used this tool,
    construct a ChatDocument with an explicit recipient,
    and make it look like it is from the LLM.

    Returns:
        (ChatDocument): with content set to self.content and
            metadata.recipient set to self.intended_recipient.
    """
    default_recipient = self.__class__.default_value("default_recipient")
    if self.intended_recipient == "" and default_recipient not in ["", None]:
        self.intended_recipient = default_recipient
    elif self.intended_recipient == "":
        # save the content as a class-variable, so that
        # we can construct the ChatDocument once the LLM specifies a recipient.
        # This avoids having to re-generate the entire message, saving time + cost.
        AddRecipientTool._saved_content = self.content
        agent.enable_message(AddRecipientTool)
        return ChatDocument(
            content="""
            Empty recipient field!
            Please use the 'add_recipient' tool/function-call to specify who your 
            message is intended for.
            DO NOT REPEAT your original message; ONLY specify the recipient via this
            tool/function-call.
            """,
            metadata=ChatDocMetaData(
                sender=Entity.AGENT,
                recipient=Entity.LLM,
            ),
        )

    print("[red]RecipientTool: Validated properly addressed message")

    return ChatDocument(
        content=self.content,
        metadata=ChatDocMetaData(
            recipient=self.intended_recipient,
            # we are constructing this so it looks as if msg is from LLM
            sender=Entity.LLM,
        ),
    )

handle_message_fallback(agent, msg) staticmethod

Response of agent if this tool is not used, e.g. the LLM simply sends a message without using this tool. This method has two purposes: (a) Alert the LLM that it has forgotten to specify a recipient, and prod it to use the add_recipient tool to specify just the recipient (and not re-generate the entire message). (b) Save the content of the message in the agent's content field, so the agent can construct a ChatDocument with this content once LLM later specifies a recipient using the add_recipient tool.

This method is used to set the agent's handle_message_fallback() method.

Returns:

Type Description
str

reminder to LLM to use the add_recipient tool.

Source code in langroid/agent/tools/recipient_tool.py
@staticmethod
def handle_message_fallback(
    agent: ChatAgent, msg: str | ChatDocument
) -> str | ChatDocument | None:
    """
    Response of agent if this tool is not used, e.g.
    the LLM simply sends a message without using this tool.
    This method has two purposes:
    (a) Alert the LLM that it has forgotten to specify a recipient, and prod it
        to use the `add_recipient` tool to specify just the recipient
        (and not re-generate the entire message).
    (b) Save the content of the message in the agent's `content` field,
        so the agent can construct a ChatDocument with this content once LLM
        later specifies a recipient using the `add_recipient` tool.

    This method is used to set the agent's handle_message_fallback() method.

    Returns:
        (str): reminder to LLM to use the `add_recipient` tool.
    """
    # Note: once the LLM specifies a missing recipient, the task loop
    # mechanism will not allow any of the "native" responders to respond,
    # since the recipient will differ from the task name.
    # So if this method is called, we can be sure that the recipient has not
    # been specified.
    if (
        isinstance(msg, str)
        or msg.metadata.sender != Entity.LLM
        or msg.metadata.recipient != ""  # there IS an explicit recipient
    ):
        return None
    content = msg if isinstance(msg, str) else msg.content
    # save the content as a class-variable, so that
    # we can construct the ChatDocument once the LLM specifies a recipient.
    # This avoids having to re-generate the entire message, saving time + cost.
    AddRecipientTool._saved_content = content
    agent.enable_message(AddRecipientTool)
    print("[red]RecipientTool: Recipient not specified, asking LLM to clarify.")
    return ChatDocument(
        content="""
        Please use the 'add_recipient' tool/function-call to specify who your 
        `intended_recipient` is.
        DO NOT REPEAT your original message; ONLY specify the 
        `intended_recipient` via this tool/function-call.
        """,
        metadata=ChatDocMetaData(
            sender=Entity.AGENT,
            recipient=Entity.LLM,
        ),
    )

RewindTool

Bases: ToolMessage

Used by LLM to rewind (i.e. backtrack) to the nth Assistant message and replace with a new msg.

response(agent)

Define the tool-handler method for this tool here itself, since it is a generic tool whose functionality should be the same for any agent.

When LLM has correctly used this tool, rewind this agent's message_history to the nth assistant msg, and replace it with content. We need to mock it as if the LLM is sending this message.

Within a multi-agent scenario, this also means that any other messages dependent on this message will need to be invalidated -- so go down the chain of child messages and clear each agent's history back to the msg_idx corresponding to the child message.

Returns:

Type Description
ChatDocument

with content set to self.content.

Source code in langroid/agent/tools/rewind_tool.py
def response(self, agent: ChatAgent) -> str | ChatDocument:
    """
    Define the tool-handler method for this tool here itself,
    since it is a generic tool whose functionality should be the
    same for any agent.

    When LLM has correctly used this tool, rewind this agent's
    `message_history` to the `n`th assistant msg, and replace it with `content`.
    We need to mock it as if the LLM is sending this message.

    Within a multi-agent scenario, this also means that any other messages dependent
    on this message will need to be invalidated --
    so go down the chain of child messages and clear each agent's history
    back to the `msg_idx` corresponding to the child message.

    Returns:
        (ChatDocument): with content set to self.content.
    """
    idx = agent.nth_message_idx_with_role(lm.Role.ASSISTANT, self.n)
    if idx < 0:
        # set up a corrective message from AGENT
        msg = f"""
            Could not rewind to {self.n}th Assistant message!
            Please check the value of `n` and try again.
            Or it may be too early to use the `rewind_tool`.
            """
        return agent.create_agent_response(msg)

    parent = prune_messages(agent, idx)

    # create ChatDocument with new content, to be returned as result of this tool
    result_doc = agent.create_llm_response(self.content)
    result_doc.metadata.parent_id = "" if parent is None else parent.id()
    result_doc.metadata.agent_id = agent.id
    result_doc.metadata.msg_idx = idx

    # replace the message at idx with this new message
    agent.message_history.extend(ChatDocument.to_LLMMessage(result_doc))

    # set the replaced doc's parent's child to this result_doc
    if parent is not None:
        # first remove the this parent's child from registry
        ChatDocument.delete_id(parent.metadata.child_id)
        parent.metadata.child_id = result_doc.id()
    return result_doc

AgentDoneTool

Bases: ToolMessage

Tool for AGENT entity (i.e. agent_response or downstream tool handling fns) to signal the current task is done.

DoneTool

Bases: ToolMessage

Tool for Agent Entity (i.e. agent_response) or LLM entity (i.e. llm_response) to signal the current task is done, with some content as the result.

ForwardTool

Bases: PassTool

Tool for forwarding the received msg (ChatDocument) to another agent or entity. Similar to PassTool, but with a specified recipient agent.

response(agent, chat_doc)

When this tool is enabled for an Agent, this will result in a method added to the Agent with signature: forward_tool(self, tool: ForwardTool, chat_doc: ChatDocument) -> ChatDocument:

Source code in langroid/agent/tools/orchestration.py
def response(self, agent: ChatAgent, chat_doc: ChatDocument) -> ChatDocument:
    """When this tool is enabled for an Agent, this will result in a method
    added to the Agent with signature:
    `forward_tool(self, tool: ForwardTool, chat_doc: ChatDocument) -> ChatDocument:`
    """
    # if chat_doc contains ForwardTool, then we forward its parent ChatDocument;
    # else forward chat_doc itself
    new_doc = PassTool.response(self, agent, chat_doc)
    new_doc.metadata.recipient = self.agent
    return new_doc

PassTool

Bases: ToolMessage

Tool for "passing" on the received msg (ChatDocument), so that an as-yet-unspecified agent can handle it. Similar to ForwardTool, but without specifying the recipient agent.

response(agent, chat_doc)

When this tool is enabled for an Agent, this will result in a method added to the Agent with signature: forward_tool(self, tool: PassTool, chat_doc: ChatDocument) -> ChatDocument:

Source code in langroid/agent/tools/orchestration.py
def response(self, agent: ChatAgent, chat_doc: ChatDocument) -> ChatDocument:
    """When this tool is enabled for an Agent, this will result in a method
    added to the Agent with signature:
    `forward_tool(self, tool: PassTool, chat_doc: ChatDocument) -> ChatDocument:`
    """
    # if PassTool is in chat_doc, pass its parent, else pass chat_doc itself
    doc = chat_doc
    while True:
        tools = agent.get_tool_messages(doc)
        if not any(isinstance(t, type(self)) for t in tools):
            break
        if doc.parent is None:
            break
        doc = doc.parent
    assert doc is not None, "PassTool: parent of chat_doc must not be None"
    new_doc = ChatDocument.deepcopy(doc)
    new_doc.metadata.sender = Entity.AGENT
    return new_doc

SendTool

Bases: ToolMessage

Tool for agent or LLM to send content to a specified agent. Similar to RecipientTool.

AgentSendTool

Bases: ToolMessage

Tool for Agent (i.e. agent_response) to send content or tool_messages to a specified agent. Similar to SendTool except that AgentSendTool is only usable by agent_response (or handler of another tool), to send content or tools to another agent. SendTool does not allow sending tools.

DonePassTool

Bases: PassTool

Tool to signal DONE, AND Pass incoming/current msg as result. Similar to PassTool, except we append a DoneTool to the result tool_messages.

ResultTool

Bases: ToolMessage

Class to use as a wrapper for sending arbitrary results from an Agent's agent_response or tool handlers, to: (a) trigger completion of the current task (similar to (Agent)DoneTool), and (b) be returned as the result of the current task, i.e. this tool would appear in the resulting ChatDocument's tool_messages list. See test_tool_handlers_and_results in test_tool_messages.py, and examples/basic/tool-extract-short-example.py.

Note
  • when defining a tool handler or agent_response, you can directly return ResultTool(field1 = val1, ...), where the values can be arbitrary data structures, including nested Pydantic objs, or you can define a subclass of ResultTool with the fields you want to return.
  • This is a special ToolMessage that is NOT meant to be used or handled by an agent.
  • AgentDoneTool is more restrictive in that you can only send a content or tools in the result.

FinalResultTool

Bases: ToolMessage

Class to use as a wrapper for sending arbitrary results from an Agent's agent_response or tool handlers, to: (a) trigger completion of the current task as well as all parent tasks, and (b) be returned as the final result of the root task, i.e. this tool would appear in the final ChatDocument's tool_messages list. See test_tool_handlers_and_results in test_tool_messages.py, and examples/basic/chat-tool-function.py.

Note
  • when defining a tool handler or agent_response, you can directly return FinalResultTool(field1 = val1, ...), where the values can be arbitrary data structures, including nested Pydantic objs, or you can define a subclass of FinalResultTool with the fields you want to return.
  • This is a special ToolMessage that is NOT meant to be used by an agent's llm_response, but only by agent_response or tool handlers.
  • A subclass of this tool can be defined, with specific fields, and with _allow_llm_use = True, to allow the LLM to generate this tool, and have the effect of terminating the current and all parent tasks, with the tool appearing in the final ChatDocument's tool_messages list. See examples/basic/multi-agent-return-result.py.