Skip to content

gemini.tools

Classes for using tools with Google's Gemini API.

BaseTool

Bases: BaseModel, Generic[ToolCallT], ABC

A base class for easy use of tools with prompts.

BaseTool is an abstract class interface and should not be used directly. When implementing a class that extends BaseTool, you must include the original tool_call from which this till was instantiated. Make sure to skip tool_call when generating the schema by annotating it with SkipJsonSchema.

Source code in mirascope/base/tools.py
class BaseTool(BaseModel, Generic[ToolCallT], ABC):
    """A base class for easy use of tools with prompts.

    `BaseTool` is an abstract class interface and should not be used directly. When
    implementing a class that extends `BaseTool`, you must include the original
    `tool_call` from which this till was instantiated. Make sure to skip `tool_call`
    when generating the schema by annotating it with `SkipJsonSchema`.
    """

    tool_call: SkipJsonSchema[ToolCallT]

    model_config = ConfigDict(arbitrary_types_allowed=True)

    @classmethod
    def name(cls) -> str:
        """Returns the name of the tool."""
        return cls.__name__

    @classmethod
    def description(cls) -> str:
        """Returns the description of the tool."""
        return inspect.cleandoc(cls.__doc__) if cls.__doc__ else DEFAULT_TOOL_DOCSTRING

    @property
    def args(self) -> dict[str, Any]:
        """The arguments of the tool as a dictionary."""
        return {
            field: getattr(self, field)
            for field in self.model_fields
            if field != "tool_call"
        }

    @property
    def fn(self) -> Callable[..., str]:
        """Returns the function that the tool describes."""
        raise RuntimeError("Tool does not have an attached function.")

    def call(self) -> str:
        """Calls the tool's `fn` with the tool's `args`."""
        return self.fn(**self.args)

    @classmethod
    def tool_schema(cls) -> Any:
        """Constructs a JSON Schema tool schema from the `BaseModel` schema defined."""
        model_schema = cls.model_json_schema()
        model_schema.pop("title", None)
        model_schema.pop("description", None)

        fn = {"name": cls.name(), "description": cls.description()}
        if model_schema["properties"]:
            fn["parameters"] = model_schema  # type: ignore

        return fn

    @classmethod
    @abstractmethod
    def from_tool_call(cls, tool_call: ToolCallT) -> BaseTool:
        """Extracts an instance of the tool constructed from a tool call response."""
        ...  # pragma: no cover

    @classmethod
    @abstractmethod
    def from_model(cls, model: type[BaseModel]) -> type[BaseTool]:
        """Constructs a `BaseTool` type from a `BaseModel` type."""
        ...  # pragma: no cover

    @classmethod
    @abstractmethod
    def from_fn(cls, fn: Callable) -> type[BaseTool]:
        """Constructs a `BaseTool` type from a function."""
        ...  # pragma: no cover

    @classmethod
    @abstractmethod
    def from_base_type(cls, base_type: type[BaseType]) -> type[BaseTool]:
        """Constructs a `BaseTool` type from a `BaseType` type."""
        ...  # pragma: no cover

args: dict[str, Any] property

The arguments of the tool as a dictionary.

fn: Callable[..., str] property

Returns the function that the tool describes.

call()

Calls the tool's fn with the tool's args.

Source code in mirascope/base/tools.py
def call(self) -> str:
    """Calls the tool's `fn` with the tool's `args`."""
    return self.fn(**self.args)

description() classmethod

Returns the description of the tool.

Source code in mirascope/base/tools.py
@classmethod
def description(cls) -> str:
    """Returns the description of the tool."""
    return inspect.cleandoc(cls.__doc__) if cls.__doc__ else DEFAULT_TOOL_DOCSTRING

from_base_type(base_type) abstractmethod classmethod

Constructs a BaseTool type from a BaseType type.

Source code in mirascope/base/tools.py
@classmethod
@abstractmethod
def from_base_type(cls, base_type: type[BaseType]) -> type[BaseTool]:
    """Constructs a `BaseTool` type from a `BaseType` type."""
    ...  # pragma: no cover

from_fn(fn) abstractmethod classmethod

Constructs a BaseTool type from a function.

Source code in mirascope/base/tools.py
@classmethod
@abstractmethod
def from_fn(cls, fn: Callable) -> type[BaseTool]:
    """Constructs a `BaseTool` type from a function."""
    ...  # pragma: no cover

from_model(model) abstractmethod classmethod

Constructs a BaseTool type from a BaseModel type.

Source code in mirascope/base/tools.py
@classmethod
@abstractmethod
def from_model(cls, model: type[BaseModel]) -> type[BaseTool]:
    """Constructs a `BaseTool` type from a `BaseModel` type."""
    ...  # pragma: no cover

from_tool_call(tool_call) abstractmethod classmethod

Extracts an instance of the tool constructed from a tool call response.

Source code in mirascope/base/tools.py
@classmethod
@abstractmethod
def from_tool_call(cls, tool_call: ToolCallT) -> BaseTool:
    """Extracts an instance of the tool constructed from a tool call response."""
    ...  # pragma: no cover

name() classmethod

Returns the name of the tool.

Source code in mirascope/base/tools.py
@classmethod
def name(cls) -> str:
    """Returns the name of the tool."""
    return cls.__name__

tool_schema() classmethod

Constructs a JSON Schema tool schema from the BaseModel schema defined.

Source code in mirascope/base/tools.py
@classmethod
def tool_schema(cls) -> Any:
    """Constructs a JSON Schema tool schema from the `BaseModel` schema defined."""
    model_schema = cls.model_json_schema()
    model_schema.pop("title", None)
    model_schema.pop("description", None)

    fn = {"name": cls.name(), "description": cls.description()}
    if model_schema["properties"]:
        fn["parameters"] = model_schema  # type: ignore

    return fn

GeminiTool

Bases: BaseTool[FunctionCall]

A base class for easy use of tools with the Gemini API.

GeminiTool internally handles the logic that allows you to use tools with simple calls such as GeminiCompletion.tool or GeminiTool.fn, as seen in the examples below.

Example:

from mirascope.gemini import GeminiCall, GeminiCallParams, GeminiTool


class CurrentWeather(GeminiTool):
    """A tool for getting the current weather in a location."""

    location: str


class WeatherForecast(GeminiPrompt):
    prompt_template = "What is the current weather in {city}?"

    city: str

    call_params = GeminiCallParams(
        model="gemini-pro",
        tools=[CurrentWeather],
    )


prompt = WeatherPrompt()
forecast = WeatherForecast(city="Tokyo").call().tool
print(forecast.location)
#> Tokyo
Source code in mirascope/gemini/tools.py
class GeminiTool(BaseTool[FunctionCall]):
    '''A base class for easy use of tools with the Gemini API.

    `GeminiTool` internally handles the logic that allows you to use tools with simple
    calls such as `GeminiCompletion.tool` or `GeminiTool.fn`, as seen in the
    examples below.

    Example:

    ```python
    from mirascope.gemini import GeminiCall, GeminiCallParams, GeminiTool


    class CurrentWeather(GeminiTool):
        """A tool for getting the current weather in a location."""

        location: str


    class WeatherForecast(GeminiPrompt):
        prompt_template = "What is the current weather in {city}?"

        city: str

        call_params = GeminiCallParams(
            model="gemini-pro",
            tools=[CurrentWeather],
        )


    prompt = WeatherPrompt()
    forecast = WeatherForecast(city="Tokyo").call().tool
    print(forecast.location)
    #> Tokyo
    ```
    '''

    model_config = ConfigDict(arbitrary_types_allowed=True)

    @classmethod
    def tool_schema(cls) -> Tool:
        """Constructs a tool schema for use with the Gemini API.

        A Mirascope `GeminiTool` is deconstructed into a `Tool` schema for use with the
        Gemini API.

        Returns:
            The constructed `Tool` schema.
        """
        tool_schema = super().tool_schema()
        if "parameters" in tool_schema:
            if "$defs" in tool_schema["parameters"]:
                raise ValueError(
                    "Unfortunately Google's Gemini API cannot handle nested structures "
                    "with $defs."
                )
            tool_schema["parameters"]["properties"] = {
                prop: {
                    key: value for key, value in prop_schema.items() if key != "title"
                }
                for prop, prop_schema in tool_schema["parameters"]["properties"].items()
            }
        return Tool(function_declarations=[FunctionDeclaration(**tool_schema)])

    @classmethod
    def from_tool_call(cls, tool_call: FunctionCall) -> GeminiTool:
        """Extracts an instance of the tool constructed from a tool call response.

        Given a `GenerateContentResponse` from a Gemini chat completion response, this
        method extracts the tool call and constructs an instance of the tool.

        Args:
            tool_call: The `GenerateContentResponse` from which to extract the tool.

        Returns:
            An instance of the tool constructed from the tool call.

        Raises:
            ValueError: if the tool call doesn't have any arguments.
            ValidationError: if the tool call doesn't match the tool schema.
        """
        if not tool_call.args:
            raise ValueError("Tool call doesn't have any arguments.")
        model_json = {key: value for key, value in tool_call.args.items()}
        model_json["tool_call"] = tool_call
        return cls.model_validate(model_json)

    @classmethod
    def from_model(cls, model: Type[BaseModel]) -> Type[GeminiTool]:
        """Constructs a `GeminiTool` type from a `BaseModel` type."""
        return convert_base_model_to_tool(model, GeminiTool)

    @classmethod
    def from_fn(cls, fn: Callable) -> Type[GeminiTool]:
        """Constructs a `GeminiTool` type from a function."""
        return convert_function_to_tool(fn, GeminiTool)

    @classmethod
    def from_base_type(cls, base_type: Type[BaseType]) -> Type[GeminiTool]:
        """Constructs a `GeminiTool` type from a `BaseType` type."""
        return convert_base_type_to_tool(base_type, GeminiTool)

from_base_type(base_type) classmethod

Constructs a GeminiTool type from a BaseType type.

Source code in mirascope/gemini/tools.py
@classmethod
def from_base_type(cls, base_type: Type[BaseType]) -> Type[GeminiTool]:
    """Constructs a `GeminiTool` type from a `BaseType` type."""
    return convert_base_type_to_tool(base_type, GeminiTool)

from_fn(fn) classmethod

Constructs a GeminiTool type from a function.

Source code in mirascope/gemini/tools.py
@classmethod
def from_fn(cls, fn: Callable) -> Type[GeminiTool]:
    """Constructs a `GeminiTool` type from a function."""
    return convert_function_to_tool(fn, GeminiTool)

from_model(model) classmethod

Constructs a GeminiTool type from a BaseModel type.

Source code in mirascope/gemini/tools.py
@classmethod
def from_model(cls, model: Type[BaseModel]) -> Type[GeminiTool]:
    """Constructs a `GeminiTool` type from a `BaseModel` type."""
    return convert_base_model_to_tool(model, GeminiTool)

from_tool_call(tool_call) classmethod

Extracts an instance of the tool constructed from a tool call response.

Given a GenerateContentResponse from a Gemini chat completion response, this method extracts the tool call and constructs an instance of the tool.

Parameters:

Name Type Description Default
tool_call FunctionCall

The GenerateContentResponse from which to extract the tool.

required

Returns:

Type Description
GeminiTool

An instance of the tool constructed from the tool call.

Raises:

Type Description
ValueError

if the tool call doesn't have any arguments.

ValidationError

if the tool call doesn't match the tool schema.

Source code in mirascope/gemini/tools.py
@classmethod
def from_tool_call(cls, tool_call: FunctionCall) -> GeminiTool:
    """Extracts an instance of the tool constructed from a tool call response.

    Given a `GenerateContentResponse` from a Gemini chat completion response, this
    method extracts the tool call and constructs an instance of the tool.

    Args:
        tool_call: The `GenerateContentResponse` from which to extract the tool.

    Returns:
        An instance of the tool constructed from the tool call.

    Raises:
        ValueError: if the tool call doesn't have any arguments.
        ValidationError: if the tool call doesn't match the tool schema.
    """
    if not tool_call.args:
        raise ValueError("Tool call doesn't have any arguments.")
    model_json = {key: value for key, value in tool_call.args.items()}
    model_json["tool_call"] = tool_call
    return cls.model_validate(model_json)

tool_schema() classmethod

Constructs a tool schema for use with the Gemini API.

A Mirascope GeminiTool is deconstructed into a Tool schema for use with the Gemini API.

Returns:

Type Description
Tool

The constructed Tool schema.

Source code in mirascope/gemini/tools.py
@classmethod
def tool_schema(cls) -> Tool:
    """Constructs a tool schema for use with the Gemini API.

    A Mirascope `GeminiTool` is deconstructed into a `Tool` schema for use with the
    Gemini API.

    Returns:
        The constructed `Tool` schema.
    """
    tool_schema = super().tool_schema()
    if "parameters" in tool_schema:
        if "$defs" in tool_schema["parameters"]:
            raise ValueError(
                "Unfortunately Google's Gemini API cannot handle nested structures "
                "with $defs."
            )
        tool_schema["parameters"]["properties"] = {
            prop: {
                key: value for key, value in prop_schema.items() if key != "title"
            }
            for prop, prop_schema in tool_schema["parameters"]["properties"].items()
        }
    return Tool(function_declarations=[FunctionDeclaration(**tool_schema)])

convert_base_model_to_tool(schema, base)

Converts a BaseModel schema to a BaseToolT type.

By adding a docstring (if needed) and passing on fields and field information in dictionary format, a Pydantic BaseModel can be converted into an BaseToolT for performing extraction.

Parameters:

Name Type Description Default
schema type[BaseModel]

The BaseModel schema to convert.

required

Returns:

Type Description
type[BaseToolT]

The constructed BaseToolT type.

Source code in mirascope/base/tools.py
def convert_base_model_to_tool(
    schema: type[BaseModel], base: type[BaseToolT]
) -> type[BaseToolT]:
    """Converts a `BaseModel` schema to a `BaseToolT` type.

    By adding a docstring (if needed) and passing on fields and field information in
    dictionary format, a Pydantic `BaseModel` can be converted into an `BaseToolT` for
    performing extraction.

    Args:
        schema: The `BaseModel` schema to convert.

    Returns:
        The constructed `BaseToolT` type.
    """
    field_definitions = {
        field_name: (field_info.annotation, field_info)
        for field_name, field_info in schema.model_fields.items()
    }
    return create_model(
        f"{schema.__name__}",
        __base__=base,
        __doc__=schema.__doc__ if schema.__doc__ else DEFAULT_TOOL_DOCSTRING,
        **cast(dict[str, Any], field_definitions),
    )

convert_base_type_to_tool(schema, base)

Converts a BaseType to a BaseToolT type.

Source code in mirascope/base/tools.py
def convert_base_type_to_tool(
    schema: type[BaseType], base: type[BaseToolT]
) -> type[BaseToolT]:
    """Converts a `BaseType` to a `BaseToolT` type."""
    if get_origin(schema) == Annotated:
        schema.__name__ = get_args(schema)[0].__name__
    return create_model(
        f"{schema.__name__.title()}",
        __base__=base,
        __doc__=DEFAULT_TOOL_DOCSTRING,
        value=(schema, ...),
    )

convert_function_to_tool(fn, base)

Constructs a BaseToolT type from the given function.

This method expects all function parameters to be properly documented in identical order with identical variable names, as well as descriptions of each parameter. Errors will be raised if any of these conditions are not met.

Parameters:

Name Type Description Default
fn Callable

The function to convert.

required

Returns:

Type Description
type[BaseToolT]

The constructed BaseToolT type.

Raises:

Type Description
ValueError

if the given function doesn't have a docstring.

ValueError

if the given function's parameters don't have type annotations.

ValueError

if a given function's parameter is in the docstring args section but the name doesn't match the docstring's parameter name.

ValueError

if a given function's parameter is in the docstring args section but doesn't have a dosctring description.

Source code in mirascope/base/tools.py
def convert_function_to_tool(fn: Callable, base: type[BaseToolT]) -> type[BaseToolT]:
    """Constructs a `BaseToolT` type from the given function.

    This method expects all function parameters to be properly documented in identical
    order with identical variable names, as well as descriptions of each parameter.
    Errors will be raised if any of these conditions are not met.

    Args:
        fn: The function to convert.

    Returns:
        The constructed `BaseToolT` type.

    Raises:
        ValueError: if the given function doesn't have a docstring.
        ValueError: if the given function's parameters don't have type annotations.
        ValueError: if a given function's parameter is in the docstring args section but
            the name doesn't match the docstring's parameter name.
        ValueError: if a given function's parameter is in the docstring args section but
            doesn't have a dosctring description.
    """
    if not fn.__doc__:
        raise ValueError("Function must have a docstring.")

    docstring = parse(fn.__doc__)

    doc = ""
    if docstring.short_description:
        doc = docstring.short_description
    if docstring.long_description:
        doc += "\n\n" + docstring.long_description
    if docstring.returns and docstring.returns.description:
        doc += "\n\n" + "Returns:\n    " + docstring.returns.description

    field_definitions = {}
    hints = get_type_hints(fn)
    for i, parameter in enumerate(signature(fn).parameters.values()):
        if parameter.name == "self" or parameter.name == "cls":
            continue
        if parameter.annotation == Parameter.empty:
            raise ValueError("All parameters must have a type annotation.")

        docstring_description = None
        if i < len(docstring.params):
            docstring_param = docstring.params[i]
            if docstring_param.arg_name != parameter.name:
                raise ValueError(
                    f"Function parameter name {parameter.name} does not match docstring "
                    f"parameter name {docstring_param.arg_name}. Make sure that the "
                    "parameter names match exactly."
                )
            if not docstring_param.description:
                raise ValueError("All parameters must have a description.")
            docstring_description = docstring_param.description

        field_info = FieldInfo(annotation=hints[parameter.name])
        if parameter.default != Parameter.empty:
            field_info.default = parameter.default
        if docstring_description:  # we check falsy here because this comes from docstr
            field_info.description = docstring_description

        param_name = parameter.name
        if param_name.startswith("model_"):  # model_ is a BaseModel reserved namespace
            param_name = "aliased_" + param_name
            field_info.alias = parameter.name
            field_info.validation_alias = parameter.name
            field_info.serialization_alias = parameter.name

        field_definitions[param_name] = (
            hints[parameter.name],
            field_info,
        )

    model = create_model(
        fn.__name__,
        __base__=base,
        __doc__=doc,
        **cast(dict[str, Any], field_definitions),
    )
    return tool_fn(fn)(model)