Saltar a contenido

API reference

Generated from the apogee-ai-tools source with mkdocstrings. Every symbol below is exported from apogee_ai_tools, so it is part of the supported public surface.

Application · DTOs

BenchDTO dataclass

Python
BenchDTO(iterations: int = 200)

iterations class-attribute instance-attribute

Python
iterations: int = 200

CallDTO dataclass

Python
CallDTO(name: str, arguments: dict[str, Any] = dict(), timeout_s: float | None = None, max_attempts: int = 1)

name instance-attribute

Python
name: str

arguments class-attribute instance-attribute

Python
arguments: dict[str, Any] = field(default_factory=dict)

timeout_s class-attribute instance-attribute

Python
timeout_s: float | None = None

max_attempts class-attribute instance-attribute

Python
max_attempts: int = 1

TranslateDTO dataclass

Python
TranslateDTO(provider: str = 'openai', tags: tuple[str, ...] = ())

provider class-attribute instance-attribute

Python
provider: str = 'openai'

tags class-attribute instance-attribute

Python
tags: tuple[str, ...] = ()

Application · Use cases

BenchCallsUseCase

Synthetic load: call two tools alternately and report rate.

execute async

Python
execute(iterations: int) -> dict[str, float]
Source code in apogee_ai_tools/application/use_cases/bench_calls_use_case.py
Python
async def execute(self, iterations: int) -> dict[str, float]:
    if iterations <= 0:
        raise ValueError("iterations must be positive")
    registry = ToolRegistry([_bench_add, _bench_echo])
    executor = AsyncExecutor(registry)
    ok = 0
    start = time.perf_counter()
    for i in range(iterations):
        if i % 2 == 0:
            result = await executor.execute("add", {"a": i, "b": 1})
        else:
            result = await executor.execute("echo", {"message": f"hi-{i}"})
        if result.succeeded:
            ok += 1
    elapsed = (time.perf_counter() - start) * 1000.0
    return {
        "iterations": float(iterations),
        "successful": float(ok),
        "elapsed_ms": elapsed,
        "calls_per_second": (iterations / elapsed * 1000.0) if elapsed > 0 else 0.0,
        "success_rate": ok / iterations,
    }

CallToolUseCase

Python
CallToolUseCase(registry, validator=None)
Source code in apogee_ai_tools/application/use_cases/call_tool_use_case.py
Python
def __init__(self, registry, validator=None) -> None:
    self._registry = registry
    self._validator = validator

execute async

Python
execute(name: str, arguments: dict[str, Any], timeout_s: float | None = None, max_attempts: int = 1) -> ToolResult
Source code in apogee_ai_tools/application/use_cases/call_tool_use_case.py
Python
async def execute(
    self,
    name: str,
    arguments: dict[str, Any],
    timeout_s: float | None = None,
    max_attempts: int = 1,
) -> ToolResult:
    executor = AsyncExecutor(
        self._registry, validator=self._validator, timeout_s=timeout_s,
    )
    if max_attempts > 1:
        executor = RetryExecutor(executor, max_attempts=max_attempts)
    return await executor.execute(name, arguments)

DescribeToolUseCase

Python
DescribeToolUseCase(registry)
Source code in apogee_ai_tools/application/use_cases/describe_tool_use_case.py
Python
def __init__(self, registry) -> None:
    self._registry = registry

execute async

Python
execute(name: str) -> dict
Source code in apogee_ai_tools/application/use_cases/describe_tool_use_case.py
Python
async def execute(self, name: str) -> dict:
    tool = self._registry.get(name)
    return {
        "name": tool.name,
        "description": tool.description,
        "tags": list(tool.tags),
        "is_async": tool.is_async,
        "schema": tool.to_json_schema(),
    }

ListToolsUseCase

Python
ListToolsUseCase(registry)
Source code in apogee_ai_tools/application/use_cases/list_tools_use_case.py
Python
def __init__(self, registry) -> None:
    self._registry = registry

execute async

Python
execute(tag: str | None = None) -> list
Source code in apogee_ai_tools/application/use_cases/list_tools_use_case.py
Python
async def execute(self, tag: str | None = None) -> list:
    if tag:
        return self._registry.find_by_tag(tag)
    return self._registry.all()

TranslateToolsUseCase

Python
TranslateToolsUseCase(registry)
Source code in apogee_ai_tools/application/use_cases/translate_tools_use_case.py
Python
def __init__(self, registry) -> None:
    self._registry = registry

execute async

Python
execute(provider: str, tag: str | None = None) -> list[dict]
Source code in apogee_ai_tools/application/use_cases/translate_tools_use_case.py
Python
async def execute(self, provider: str, tag: str | None = None) -> list[dict]:
    tools = self._registry.find_by_tag(tag) if tag else self._registry.all()
    if provider == "openai":
        return OpenAIToolsTranslator().translate(tools)
    if provider == "anthropic":
        return AnthropicToolsTranslator().translate(tools)
    raise ToolError(f"unknown provider: {provider!r}")

Domain

ToolCall dataclass

Python
ToolCall(name: str, arguments: dict[str, Any] = dict(), call_id: str | None = None)

name instance-attribute

Python
name: str

arguments class-attribute instance-attribute

Python
arguments: dict[str, Any] = field(default_factory=dict)

call_id class-attribute instance-attribute

Python
call_id: str | None = None

ToolCallStatus

Bases: str, Enum

SUCCESS class-attribute instance-attribute

Python
SUCCESS = 'success'

FAILED class-attribute instance-attribute

Python
FAILED = 'failed'

TIMEOUT class-attribute instance-attribute

Python
TIMEOUT = 'timeout'

VALIDATION_FAILED class-attribute instance-attribute

Python
VALIDATION_FAILED = 'validation_failed'

ToolDefinition dataclass

Python
ToolDefinition(name: str, description: str, parameters: tuple[ToolParameter, ...] = (), func: Callable[..., Any] | None = None, tags: tuple[str, ...] = (), metadata: dict[str, str] = dict())

name instance-attribute

Python
name: str

description instance-attribute

Python
description: str

parameters class-attribute instance-attribute

Python
parameters: tuple[ToolParameter, ...] = ()

func class-attribute instance-attribute

Python
func: Callable[..., Any] | None = None

tags class-attribute instance-attribute

Python
tags: tuple[str, ...] = ()

metadata class-attribute instance-attribute

Python
metadata: dict[str, str] = field(default_factory=dict)

is_async property

Python
is_async: bool

to_json_schema

Python
to_json_schema() -> dict[str, Any]
Source code in apogee_ai_tools/domain/entities/tool_definition.py
Python
def to_json_schema(self) -> dict[str, Any]:
    properties = {p.name: p.to_json_schema() for p in self.parameters}
    required = [p.name for p in self.parameters if p.required]
    return {
        "name": self.name,
        "description": self.description,
        "parameters": {
            "type": "object",
            "properties": properties,
            "required": required,
        },
    }

ToolParameter dataclass

Python
ToolParameter(name: str, kind: ParameterKind, description: str = '', required: bool = True, default: Any = None, enum: tuple[Any, ...] = (), items_kind: ParameterKind | None = None, properties: dict[str, 'ToolParameter'] = dict())

name instance-attribute

Python
name: str

kind instance-attribute

Python
kind: ParameterKind

description class-attribute instance-attribute

Python
description: str = ''

required class-attribute instance-attribute

Python
required: bool = True

default class-attribute instance-attribute

Python
default: Any = None

enum class-attribute instance-attribute

Python
enum: tuple[Any, ...] = ()

items_kind class-attribute instance-attribute

Python
items_kind: ParameterKind | None = None

properties class-attribute instance-attribute

Python
properties: dict[str, 'ToolParameter'] = field(default_factory=dict)

to_json_schema

Python
to_json_schema() -> dict[str, Any]
Source code in apogee_ai_tools/domain/value_objects/tool_parameter.py
Python
def to_json_schema(self) -> dict[str, Any]:
    schema: dict[str, Any] = {"type": self.kind.value}
    if self.description:
        schema["description"] = self.description
    if self.enum:
        schema["enum"] = list(self.enum)
    if self.kind is ParameterKind.ARRAY and self.items_kind is not None:
        schema["items"] = {"type": self.items_kind.value}
    if self.kind is ParameterKind.OBJECT and self.properties:
        schema["properties"] = {
            k: v.to_json_schema() for k, v in self.properties.items()
        }
        schema["required"] = [k for k, v in self.properties.items() if v.required]
    if self.default is not None and not self.required:
        schema["default"] = self.default
    return schema

ToolResult dataclass

Python
ToolResult(name: str, status: ToolCallStatus = SUCCESS, value: Any = None, error_message: str = '', latency_ms: float = 0.0, attempts: int = 1, call_id: str | None = None, metadata: dict[str, str] = dict())

name instance-attribute

Python
name: str

status class-attribute instance-attribute

Python
status: ToolCallStatus = SUCCESS

value class-attribute instance-attribute

Python
value: Any = None

error_message class-attribute instance-attribute

Python
error_message: str = ''

latency_ms class-attribute instance-attribute

Python
latency_ms: float = 0.0

attempts class-attribute instance-attribute

Python
attempts: int = 1

call_id class-attribute instance-attribute

Python
call_id: str | None = None

metadata class-attribute instance-attribute

Python
metadata: dict[str, str] = field(default_factory=dict)

succeeded property

Python
succeeded: bool

Domain · Enums

ParameterKind

Bases: str, Enum

STRING class-attribute instance-attribute

Python
STRING = 'string'

INTEGER class-attribute instance-attribute

Python
INTEGER = 'integer'

NUMBER class-attribute instance-attribute

Python
NUMBER = 'number'

BOOLEAN class-attribute instance-attribute

Python
BOOLEAN = 'boolean'

ARRAY class-attribute instance-attribute

Python
ARRAY = 'array'

OBJECT class-attribute instance-attribute

Python
OBJECT = 'object'

NULL class-attribute instance-attribute

Python
NULL = 'null'

Domain · Exceptions

ToolError

Bases: Exception

Base for apogee-ai-tools errors.

ToolExecutionException

Python
ToolExecutionException(name: str, original: BaseException)

Bases: ToolError

Source code in apogee_ai_tools/domain/exceptions/tool_exceptions.py
Python
def __init__(self, name: str, original: BaseException) -> None:
    super().__init__(f"Execution of {name!r} failed: {original}")
    self.name = name
    self.original = original

name instance-attribute

Python
name = name

original instance-attribute

Python
original = original

ToolNotFoundException

Python
ToolNotFoundException(name: str)

Bases: ToolError

Source code in apogee_ai_tools/domain/exceptions/tool_exceptions.py
Python
def __init__(self, name: str) -> None:
    super().__init__(f"Tool not found: {name!r}")
    self.name = name

name instance-attribute

Python
name = name

ToolTimeoutException

Python
ToolTimeoutException(name: str, timeout_s: float)

Bases: ToolError

Source code in apogee_ai_tools/domain/exceptions/tool_exceptions.py
Python
def __init__(self, name: str, timeout_s: float) -> None:
    super().__init__(f"Tool {name!r} timed out after {timeout_s}s")
    self.name = name
    self.timeout_s = timeout_s

name instance-attribute

Python
name = name

timeout_s instance-attribute

Python
timeout_s = timeout_s

ToolValidationException

Python
ToolValidationException(name: str, reason: str)

Bases: ToolError

Source code in apogee_ai_tools/domain/exceptions/tool_exceptions.py
Python
def __init__(self, name: str, reason: str) -> None:
    super().__init__(f"Validation failed for {name!r}: {reason}")
    self.name = name
    self.reason = reason

name instance-attribute

Python
name = name

reason instance-attribute

Python
reason = reason

Domain · Protocols (ports)

IExecutor

Bases: Protocol

name instance-attribute

Python
name: str

execute async

Python
execute(tool_name: str, arguments: dict[str, Any]) -> ToolResult
Source code in apogee_ai_tools/domain/services/i_executor.py
Python
async def execute(self, tool_name: str, arguments: dict[str, Any]) -> ToolResult: ...

ISchemaValidator

Bases: Protocol

validate

Python
validate(tool: ToolDefinition, arguments: dict[str, Any]) -> None

Raise ToolValidationException on failure; otherwise return None.

Source code in apogee_ai_tools/domain/services/i_schema_validator.py
Python
def validate(
    self,
    tool: ToolDefinition,
    arguments: dict[str, Any],
) -> None:
    """Raise ToolValidationException on failure; otherwise return None."""

IToolRegistry

Bases: Protocol

register

Python
register(tool: ToolDefinition) -> None
Source code in apogee_ai_tools/domain/services/i_tool_registry.py
Python
def register(self, tool: ToolDefinition) -> None: ...

unregister

Python
unregister(name: str) -> None
Source code in apogee_ai_tools/domain/services/i_tool_registry.py
Python
def unregister(self, name: str) -> None: ...

get

Python
get(name: str) -> ToolDefinition
Source code in apogee_ai_tools/domain/services/i_tool_registry.py
Python
def get(self, name: str) -> ToolDefinition: ...

list

Python
list() -> list[str]
Source code in apogee_ai_tools/domain/services/i_tool_registry.py
Python
def list(self) -> list[str]: ...

all

Python
all() -> Iterable[ToolDefinition]
Source code in apogee_ai_tools/domain/services/i_tool_registry.py
Python
def all(self) -> Iterable[ToolDefinition]: ...

find_by_tag

Python
find_by_tag(tag: str) -> list[ToolDefinition]
Source code in apogee_ai_tools/domain/services/i_tool_registry.py
Python
def find_by_tag(self, tag: str) -> list[ToolDefinition]: ...

Infrastructure

AnthropicToolsTranslator

Outputs tools in Anthropic Messages API tools format.

name class-attribute instance-attribute

Python
name = 'anthropic'

translate

Python
translate(tools: Iterable[ToolDefinition]) -> list[dict]
Source code in apogee_ai_tools/infrastructure/translators/anthropic_translator.py
Python
def translate(self, tools: Iterable[ToolDefinition]) -> list[dict]:
    out: list[dict] = []
    for t in tools:
        schema = t.to_json_schema()
        out.append({
            "name": t.name,
            "description": t.description,
            "input_schema": schema["parameters"],
        })
    return out

AsyncExecutor

Python
AsyncExecutor(registry, validator=None, timeout_s: float | None = None)
Source code in apogee_ai_tools/infrastructure/executors/async_executor.py
Python
def __init__(
    self,
    registry,
    validator=None,
    timeout_s: float | None = None,
) -> None:
    self._registry = registry
    self._validator = validator or BuiltinSchemaValidator()
    self._timeout = timeout_s

name class-attribute instance-attribute

Python
name = 'async'

execute async

Python
execute(tool_name: str, arguments: dict[str, Any]) -> ToolResult
Source code in apogee_ai_tools/infrastructure/executors/async_executor.py
Python
async def execute(
    self, tool_name: str, arguments: dict[str, Any]
) -> ToolResult:
    tool = self._registry.get(tool_name)
    try:
        self._validator.validate(tool, arguments)
    except ToolValidationException as exc:
        return ToolResult(
            name=tool_name,
            status=ToolCallStatus.VALIDATION_FAILED,
            error_message=str(exc),
        )
    if tool.func is None:
        return ToolResult(
            name=tool_name,
            status=ToolCallStatus.FAILED,
            error_message="tool has no callable",
        )

    start = time.perf_counter()
    try:
        if inspect.iscoroutinefunction(tool.func):
            coro = tool.func(**arguments)
            if self._timeout is not None:
                value = await asyncio.wait_for(coro, timeout=self._timeout)
            else:
                value = await coro
        else:
            value = tool.func(**arguments)
    except asyncio.TimeoutError:
        return ToolResult(
            name=tool_name,
            status=ToolCallStatus.TIMEOUT,
            error_message=str(ToolTimeoutException(tool_name, self._timeout or 0.0)),
            latency_ms=(time.perf_counter() - start) * 1000.0,
        )
    except Exception as exc:  # noqa: BLE001
        return ToolResult(
            name=tool_name,
            status=ToolCallStatus.FAILED,
            error_message=str(ToolExecutionException(tool_name, exc)),
            latency_ms=(time.perf_counter() - start) * 1000.0,
        )
    return ToolResult(
        name=tool_name,
        status=ToolCallStatus.SUCCESS,
        value=value,
        latency_ms=(time.perf_counter() - start) * 1000.0,
    )

BuiltinSchemaValidator

Lightweight validator for ToolDefinition.parameters.

validate

Python
validate(tool: ToolDefinition, arguments: dict[str, Any]) -> None
Source code in apogee_ai_tools/infrastructure/schema/builtin_validator.py
Python
def validate(
    self, tool: ToolDefinition, arguments: dict[str, Any]
) -> None:
    param_index: dict[str, ToolParameter] = {p.name: p for p in tool.parameters}

    for p in tool.parameters:
        if p.required and p.name not in arguments:
            raise ToolValidationException(
                tool.name, f"missing required parameter: {p.name!r}"
            )
    for name, value in arguments.items():
        if name not in param_index:
            raise ToolValidationException(
                tool.name, f"unknown parameter: {name!r}"
            )
        p = param_index[name]
        if not _matches(value, p.kind):
            raise ToolValidationException(
                tool.name,
                f"{name!r} expected {p.kind.value}, got {type(value).__name__}",
            )
        if p.enum and value not in p.enum:
            raise ToolValidationException(
                tool.name,
                f"{name!r}={value!r} not in enum {list(p.enum)}",
            )
        if p.kind is ParameterKind.ARRAY and p.items_kind is not None:
            for i, item in enumerate(value):
                if not _matches(item, p.items_kind):
                    raise ToolValidationException(
                        tool.name,
                        f"{name}[{i}] expected {p.items_kind.value}",
                    )

JsonSchemaValidator

Python
JsonSchemaValidator()

Lazy adapter for jsonschema. Install via extras=jsonschema.

Source code in apogee_ai_tools/infrastructure/schema/jsonschema_validator.py
Python
def __init__(self) -> None:
    self._validator_cls = None

validate

Python
validate(tool: ToolDefinition, arguments: dict[str, Any]) -> None
Source code in apogee_ai_tools/infrastructure/schema/jsonschema_validator.py
Python
def validate(
    self, tool: ToolDefinition, arguments: dict[str, Any]
) -> None:
    self._ensure()
    schema = tool.to_json_schema()["parameters"]
    validator = self._validator_cls(schema)  # type: ignore[misc]
    errors = list(validator.iter_errors(arguments))
    if errors:
        raise ToolValidationException(
            tool.name, "; ".join(e.message for e in errors)
        )

OpenAIToolsTranslator

Outputs tools in OpenAI Chat Completions tools format.

name class-attribute instance-attribute

Python
name = 'openai'

translate

Python
translate(tools: Iterable[ToolDefinition]) -> list[dict]
Source code in apogee_ai_tools/infrastructure/translators/openai_translator.py
Python
def translate(self, tools: Iterable[ToolDefinition]) -> list[dict]:
    out: list[dict] = []
    for t in tools:
        schema = t.to_json_schema()
        out.append({
            "type": "function",
            "function": {
                "name": t.name,
                "description": t.description,
                "parameters": schema["parameters"],
            },
        })
    return out

RetryExecutor

Python
RetryExecutor(inner, max_attempts: int = 3)

Wraps another executor with N retries on FAILED/TIMEOUT.

VALIDATION_FAILED is never retried — bad arguments will keep failing.

Source code in apogee_ai_tools/infrastructure/executors/retry_executor.py
Python
def __init__(self, inner, max_attempts: int = 3) -> None:
    if max_attempts <= 0:
        raise ValueError("max_attempts must be positive")
    self._inner = inner
    self._max_attempts = max_attempts

name class-attribute instance-attribute

Python
name = 'retry'

execute async

Python
execute(tool_name: str, arguments: dict[str, Any]) -> ToolResult
Source code in apogee_ai_tools/infrastructure/executors/retry_executor.py
Python
async def execute(
    self, tool_name: str, arguments: dict[str, Any]
) -> ToolResult:
    last: ToolResult | None = None
    for attempt in range(1, self._max_attempts + 1):
        result = await self._inner.execute(tool_name, arguments)
        last = result
        if result.status is ToolCallStatus.SUCCESS:
            return replace(result, attempts=attempt)
        if result.status is ToolCallStatus.VALIDATION_FAILED:
            return replace(result, attempts=attempt)
    assert last is not None
    return replace(last, attempts=self._max_attempts)

SyncExecutor

Python
SyncExecutor(registry, validator=None)
Source code in apogee_ai_tools/infrastructure/executors/sync_executor.py
Python
def __init__(self, registry, validator=None) -> None:
    self._inner = AsyncExecutor(registry, validator=validator)

name class-attribute instance-attribute

Python
name = 'sync'

execute

Python
execute(tool_name: str, arguments: dict[str, Any]) -> ToolResult
Source code in apogee_ai_tools/infrastructure/executors/sync_executor.py
Python
def execute(self, tool_name: str, arguments: dict[str, Any]) -> ToolResult:
    return asyncio.run(self._inner.execute(tool_name, arguments))

ToolRegistry

Python
ToolRegistry(tools: Iterable[ToolDefinition] | None = None)
Source code in apogee_ai_tools/infrastructure/registries/tool_registry.py
Python
def __init__(self, tools: Iterable[ToolDefinition] | None = None) -> None:
    self._tools: dict[str, ToolDefinition] = {}
    for t in tools or []:
        self._tools[t.name] = t

register

Python
register(tool: ToolDefinition) -> None
Source code in apogee_ai_tools/infrastructure/registries/tool_registry.py
Python
def register(self, tool: ToolDefinition) -> None:
    self._tools[tool.name] = tool

unregister

Python
unregister(name: str) -> None
Source code in apogee_ai_tools/infrastructure/registries/tool_registry.py
Python
def unregister(self, name: str) -> None:
    self._tools.pop(name, None)

get

Python
get(name: str) -> ToolDefinition
Source code in apogee_ai_tools/infrastructure/registries/tool_registry.py
Python
def get(self, name: str) -> ToolDefinition:
    if name not in self._tools:
        raise ToolNotFoundException(name)
    return self._tools[name]

list

Python
list() -> list[str]
Source code in apogee_ai_tools/infrastructure/registries/tool_registry.py
Python
def list(self) -> list[str]:
    return list(self._tools.keys())

all

Python
all() -> list[ToolDefinition]
Source code in apogee_ai_tools/infrastructure/registries/tool_registry.py
Python
def all(self) -> list[ToolDefinition]:
    return list(self._tools.values())

find_by_tag

Python
find_by_tag(tag: str) -> list[ToolDefinition]
Source code in apogee_ai_tools/infrastructure/registries/tool_registry.py
Python
def find_by_tag(self, tag: str) -> list[ToolDefinition]:
    return [t for t in self._tools.values() if tag in t.tags]

function_to_tool

Python
function_to_tool(func: Callable[..., Any], name: str | None = None, description: str | None = None, tags: tuple[str, ...] = ()) -> ToolDefinition

Inspects a function's type hints + docstring to build a ToolDefinition.

Source code in apogee_ai_tools/infrastructure/tools/_introspection.py
Python
def function_to_tool(
    func: Callable[..., Any],
    name: str | None = None,
    description: str | None = None,
    tags: tuple[str, ...] = (),
) -> ToolDefinition:
    """Inspects a function's type hints + docstring to build a ToolDefinition."""

    signature = inspect.signature(func)
    summary, doc_params = _parse_docstring(inspect.getdoc(func))
    final_description = (description or summary or "").strip()

    # Resolve forward references / PEP-563 string annotations.
    try:
        hints = typing.get_type_hints(func)
    except Exception:  # noqa: BLE001
        hints = {}

    parameters: list[ToolParameter] = []
    for pname, param in signature.parameters.items():
        if pname in {"self", "cls"}:
            continue
        annotation = hints.get(pname, param.annotation)
        kind = _kind_from_annotation(annotation)
        items = _items_kind(annotation) if kind is ParameterKind.ARRAY else None
        parameters.append(
            ToolParameter(
                name=pname,
                kind=kind,
                description=doc_params.get(pname, ""),
                required=param.default is inspect._empty,  # noqa: SLF001
                default=None if param.default is inspect._empty else param.default,  # noqa: SLF001
                items_kind=items,
            )
        )

    return ToolDefinition(
        name=name or func.__name__,
        description=final_description,
        parameters=tuple(parameters),
        func=func,
        tags=tags,
    )

tool

Python
tool(name: str | None = None, description: str | None = None, tags: tuple[str, ...] = ()) -> Callable[[Callable[..., Any]], ToolDefinition]

Decorator that turns a Python function into a ToolDefinition.

The function is preserved as definition.func so executors can call it. The decorator returns the ToolDefinition (not the function).

Source code in apogee_ai_tools/infrastructure/tools/decorator.py
Python
def tool(
    name: str | None = None,
    description: str | None = None,
    tags: tuple[str, ...] = (),
) -> Callable[[Callable[..., Any]], ToolDefinition]:
    """Decorator that turns a Python function into a ToolDefinition.

    The function is preserved as ``definition.func`` so executors can call
    it. The decorator returns the ToolDefinition (not the function).
    """

    def decorate(func: Callable[..., Any]) -> ToolDefinition:
        return function_to_tool(func, name=name, description=description, tags=tags)

    return decorate