diff --git a/python/packages/autogen-core/docs/src/user-guide/core-user-guide/framework/tools.ipynb b/python/packages/autogen-core/docs/src/user-guide/core-user-guide/framework/tools.ipynb index 0214b0286..183d878e4 100644 --- a/python/packages/autogen-core/docs/src/user-guide/core-user-guide/framework/tools.ipynb +++ b/python/packages/autogen-core/docs/src/user-guide/core-user-guide/framework/tools.ipynb @@ -59,6 +59,13 @@ "print(code_execution_tool.return_value_as_string(result))" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, { "cell_type": "markdown", "metadata": {}, @@ -82,6 +89,11 @@ "To create a custom function tool, you just need to create a Python function\n", "and use the {py:class}`~autogen_core.components.tools.FunctionTool` class to wrap it.\n", "\n", + "The {py:class}`~autogen_core.components.tools.FunctionTool` class uses descriptions and type annotations\n", + "to inform the LLM when and how to use a given function. The description provides context\n", + "about the function’s purpose and intended use cases, while type annotations inform the LLM about\n", + "the expected parameters and return type.\n", + "\n", "For example, a simple tool to obtain the stock price of a company might look like this:" ] }, @@ -296,7 +308,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.9" + "version": "3.12.7" } }, "nbformat": 4, diff --git a/python/packages/autogen-core/src/autogen_core/components/tools/_function_tool.py b/python/packages/autogen-core/src/autogen_core/components/tools/_function_tool.py index e537deeba..462374116 100644 --- a/python/packages/autogen-core/src/autogen_core/components/tools/_function_tool.py +++ b/python/packages/autogen-core/src/autogen_core/components/tools/_function_tool.py @@ -13,6 +13,52 @@ from ._base import BaseTool class FunctionTool(BaseTool[BaseModel, BaseModel]): + """ + Create custom tools by wrapping standard Python functions. + + `FunctionTool` offers an interface for executing Python functions either asynchronously or synchronously. + Each function must include type annotations for all parameters and its return type. These annotations + enable `FunctionTool` to generate a schema necessary for input validation, serialization, and for informing + the LLM about expected parameters. When the LLM prepares a function call, it leverages this schema to + generate arguments that align with the function's specifications. + + .. note:: + + It is the user's responsibility to verify that the tool's output type matches the expected type. + + Args: + func (Callable[..., ReturnT | Awaitable[ReturnT]]): The function to wrap and expose as a tool. + description (str): A description to inform the model of the function's purpose, specifying what + it does and the context in which it should be called. + name (str, optional): An optional custom name for the tool. Defaults to + the function's original name if not provided. + + Example: + + .. code-block:: python + + import random + from autogen_core.base import CancellationToken + from autogen_core.components.tools import FunctionTool + from typing_extensions import Annotated + + + async def get_stock_price(ticker: str, date: Annotated[str, "Date in YYYY/MM/DD"]) -> float: + # Simulates a stock price retrieval by returning a random float within a specified range. + return random.uniform(10, 200) + + + # Initialize a FunctionTool instance for retrieving stock prices. + stock_price_tool = FunctionTool(get_stock_price, description="Fetch the stock price for a given ticker.") + + # Execute the tool with cancellation support. + cancellation_token = CancellationToken() + result = await stock_price_tool.run_json({"ticker": "AAPL", "date": "2021/01/01"}, cancellation_token) + + # Output the result as a formatted string. + print(stock_price_tool.return_value_as_string(result)) + """ + def __init__(self, func: Callable[..., Any], description: str, name: str | None = None) -> None: self._func = func signature = get_typed_signature(func) @@ -46,6 +92,4 @@ class FunctionTool(BaseTool[BaseModel, BaseModel]): cancellation_token.link_future(future) result = await future - if not isinstance(result, self.return_type()): - raise ValueError(f"Expected return type {self.return_type()}, got {type(result)}") return result diff --git a/python/packages/autogen-core/tests/test_tools.py b/python/packages/autogen-core/tests/test_tools.py index 2a0c82b8f..f41b5edd4 100644 --- a/python/packages/autogen-core/tests/test_tools.py +++ b/python/packages/autogen-core/tests/test_tools.py @@ -1,5 +1,5 @@ import inspect -from typing import Annotated +from typing import Annotated, List import pytest from autogen_core.base import CancellationToken @@ -324,3 +324,15 @@ def test_convert_tools_accepts_both_tool_and_schema() -> None: assert len(converted_tool_schema) == 2 assert converted_tool_schema[0] == converted_tool_schema[1] + + +@pytest.mark.asyncio +async def test_func_tool_return_list() -> None: + def my_function() -> List[int]: + return [1, 2] + + tool = FunctionTool(my_function, description="Function tool.") + result = await tool.run_json({}, CancellationToken()) + assert isinstance(result, list) + assert result == [1, 2] + assert tool.return_value_as_string(result) == "[1, 2]"