Files
agent-framework/docs/design/tools.md
T
Eric Zhu 6089446f04 Design doc draft (#5)
* wip

* wip

* wip

* wip

* wip

* wip

* Update docs/design/main.md

Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>

* Update docs/design/main.md

Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* update

* update

* update

* wip

* wip

* wip

* wip

* address comment

* update

* add custom agent example

* address comment

* update code teaser

* Update docs/design/main.md

Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>

* update

* address comments

* update guardrails

* address some of mark's comments

* add new separate sections for agents and workflows

* update agent doc

* Update agent.md

Co-authored-by: Jack Gerrits <jackgerrits@users.noreply.github.com>

* add foundry agent doc

* wip

* refine the component registration interface with agent runtime

* update

* workflows

* update

* update

* Update

* Update

* update

* Update design doc to remove runtime

* Update

* Update

* Update

* update

* Add eval section notes (#9)

* add notes on eval

* remove duplicate title

* update docs

* update docs

* save updates before merge

* update evaluation script

* Update agents.md

* update workflows

* Update

Co-authored-by: Jack Gerrits <jackgerrits@users.noreply.github.com>

* update workflow

* Updated design doc

* Update

* Update

* update

* update

* Update

* update

* update

* Update

* update

* Update with agent abstraction alternatives

* Update discussion

* Update

* update

* Update

* Update

* Update

* Update

---------

Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
Co-authored-by: Jack Gerrits <jackgerrits@users.noreply.github.com>
Co-authored-by: Victor Dibia <chuvidi2003@gmail.com>
2025-05-29 19:36:54 +00:00

6.6 KiB

Tools

The design goal is to make it easy to create new tools and integrate existing APIs and make them available to agents.

A tool is a component that can be used to invoke procedure code and returns a well-defined result type to the caller.

The result type should indicate the success or failure of the invocation, as well as the output of the invocation in terms of the core data types. There may be other fields in the result type for things like side effects, etc.. We should address this when designing the tool interface.

A tool may have arguments for invocation. The arguments must be defined using JSON schema that language model supports.

A tool may have dependencies such as tokens, credentials, or output message channels that will be provided by through a context variable passed to the tool when it is invoked.

A tool may also have guardrails that are used to ensure the tool is invoked with proper arguments, or that the agent has the right context such as human approval to invoke the tool.

The framework provides a set of pre-built tools:

  • FunctionTool: a tool that wraps a function.
  • AzureAISearchTool: a tool that is backed by Azure AI Search Service.
  • OpenAPITool: a tool that is backed by a service that defines an OpenAPI spec.
  • Other tools backed by Foundry.

Tool base class

@dataclass
class ToolResult:
    """The result of running a tool."""
    is_error: bool
    output: List[ImageContent | TextContent] # The content types are defined as part of the core data types.
    ... # Other fields, could be extended to include more for application-specific needs.

class Tool(ABC):
    """The base class for all tools in the framework."""

    @property
    def name(self) -> str:
        """The name of the tool, used to identify it in the system."""
        ...
    
    @property
    def description(self) -> str:
        """The description of the tool, used to provide information about its
        functionality.
        """
        ...

    @property
    def schema(self) -> ToolSchema:
        """The schema of the tool, which defines the JSON schema of the input
        arguments."""
        ...

    @property
    def strict(self) -> bool:
        """Whether the JSON schema is in strict mode. If true, no optional
        arguments are allowed.
        """
        ...
    
    async def __call__(
        self,
        call: ToolCall,
        context: Context,
    ) -> ToolResult:
        """The method to call to run the tool with arguments and return the result.
        
        Args:
            call: The tool call containing the name and arguments to pass to the tool.
            context: The context for the current invocation of the tool, providing
                access to the event channel, and human-in-the-loop (HITL) features.

        Returns:
            The result of running the tool.
        """
        try:
            # Call the on_invoke method to allow for input guardrails to be applied
            # to the arguments before the tool is run.
            await self.on_invoke(args, context)
            # Call the run method to actually run the tool.
            result = await self.run(args, context)
            # Call the on_output method to handle the output of the tool.
            result = await self.on_output(result, context)
        except Exception as e:
            # If an error occurs, call the on_error method to handle it.
            result = await self.on_error(e, context)
        return result

    @abstractmethod
    async def run(
        self,
        calls: ToolCall,
        context: Context,
    ) -> ToolResult:
        """The method called by the tool itself to run the tool with arguments and return the result."""
        ...
    
    async def on_invoke(
        self,
        calls: ToolCall,
        context: Context,
    ) -> None:
        """The method called by the tool when is invoked but before it is run.

        This is useful for input guardrails to be applied to the arguments
        before the tool is run.
        """ 
        ...
    
    async def on_error(
        self,
        error: Exception,
        context: Context,
    ) -> ToolResult:
        """The method called by the tool when an error is raised."""
        ...
    
    async def on_output(
        self,
        output: ToolResult,
        context: Context,
    ) -> ToolResult:
        """The method called by the tool when the output is ready.

        This is where output guardrails can be applied to the result
        before it is returned to the caller.
        """
        ...
    
    def add_input_guardrails(
        self, 
        guardrails: list[InputGuardrail[ToolCall]]
    ) -> None:
        """Add input guardrails to the tool.

        Args:
            guardrails: The list of input guardrails to add.
        """
        ...
    
    def add_output_guardrails(
        self, 
        guardrails: list[OutputGuardrail[ToolResult]]
    ) -> None:
        """Add output guardrails to the tool.

        Args:
            guardrails: The list of output guardrails to add.
        """
        ...
    
    def add_on_error_func(
        self, 
        on_error_func: Callable[[Exception, Context], Awaitable[ToolResult]]
    ) -> None:
        """Add a function to call when an error is raised during the call to `run`.

        Args:
            on_error_func: The function to call when an error is raised.
        """
        ...

FunctionTool

The FunctionTool is a decorator that can be used to create a tool from a function.

@FunctionTool
def web_search(
    query: str,
    num_results: int = 10,
) -> str:
    """A tool that performs a web search and returns the results."""
    ...

FunctionTool supports customization of the following:

  • name
  • description
  • on_error_func: the function to call when an error is raised during the call to run.
  • strict: whether the JSON schema is in strict mode. If true, no optional arguments are allowed.
  • input_guardrails: a list of input guardrails to apply to the arguments before the tool is run.
  • output_guardrails: a list of output guardrails to apply to the result before it is returned.

AgentTool

The AgentTool is a wrapper around an agent that can be used as a tool.

agent = SomeAgent(...)
tool = AgentTool(
    agent=agent,
    name="SomeAgent",
    description="Some description of this agent tool.",
    output_extractor=..., # Optional, a function to extract a ToolResult from the agent's run Result.
    on_error_func=..., # Optional, a function to call when an error is raised during the call to `run`.
)

The argument to the AgentTool is a single string.

NOTE: Do we also need to support passing a thread to the agent tool?