Function Calling
Underneath every tool is a Function — a Pydantic model
(buddy.tools.function.Function) that captures what a callable is so the model
can decide when to call it. This page covers how a Function is built, how its
JSON schema is generated, and how a FunctionCall runs it.
The Function model
A Function stores the metadata sent to the model plus the callable to run and
its execution options. Key fields:
| Field | Type | Description |
|---|---|---|
name |
str |
Function name (a–z, A–Z, 0–9, _, -; max 64 chars). |
description |
Optional[str] |
What the function does; defaults to the docstring. |
parameters |
Dict[str, Any] |
JSON Schema describing the parameters. |
strict |
Optional[bool] |
Strict schema mode (all properties required). |
entrypoint |
Optional[Callable] |
The callable that is actually executed. |
instructions / add_instructions |
Usage instructions and whether to add them to the system message. | |
show_result |
bool |
Show the result alongside sending it to the model. |
stop_after_tool_call |
bool |
Stop the agent after this call. |
pre_hook / post_hook |
Optional[Callable] |
Run before / after the entrypoint. |
tool_hooks |
Optional[List[Callable]] |
Middleware around the call. |
requires_confirmation / requires_user_input / external_execution |
Human-in-the-loop and external-execution flags. | |
cache_results / cache_dir / cache_ttl |
Disk-caching configuration. |
The default parameters value describes a no-argument function:
Function.to_dict() returns only the model-facing subset — name,
description, parameters, strict, requires_confirmation, and
external_execution — which is what gets sent to the provider.
Building a Function from a callable
Function.from_callable(callable, name=None, strict=False) is how plain
functions become tools. It:
- Reads the signature and resolves type hints with
get_type_hints. - Drops
agent,team, andselffrom the parameters (injected at runtime). - Parses the docstring for per-parameter descriptions.
- Generates the JSON Schema via
buddy.utils.json_schema.get_json_schema. - Marks parameters as
requiredbased onstrictor the absence of defaults.
from buddy.tools import Function
def lookup(symbol: str, limit: int = 5) -> str:
"""Look up recent prices for a ticker.
Args:
symbol (str): The ticker symbol.
limit (int): How many rows to return.
"""
...
fn = Function.from_callable(lookup)
print(fn.name) # "lookup"
print(fn.parameters) # JSON schema with "symbol" required, "limit" optional
Schema generation rules
- Required vs optional. With
strict=False, a parameter is required only if it has no default. Withstrict=True, every property is required andadditionalPropertiesis set toFalse. - Descriptions. When a docstring documents a parameter's type, it is encoded
as
(type) description; otherwise just the description is used. - Injected parameters.
agent,team, andselfare always excluded from the schema.
For tools created via @tool or a Toolkit, the same logic runs through
Function.process_entrypoint(strict=...), which additionally honours
requires_user_input / user_input_fields by moving those fields into a
user_input_schema instead of the model-facing parameters.
Argument validation
Before execution, the entrypoint is wrapped with Pydantic's validate_call
(with arbitrary_types_allowed=True) so arguments are validated against the
type hints. Async generators — and coroutines on Pydantic < 2.10.0 — are
left unwrapped.
Calling a function: FunctionCall
A FunctionCall pairs a Function with the arguments the model produced and
performs one invocation.
| Field | Description |
|---|---|
function |
The Function to call. |
arguments |
The argument dict from the model. |
result |
The value returned by the entrypoint. |
call_id |
Provider-supplied call identifier. |
error |
Set if argument parsing or execution failed. |
from buddy.tools import FunctionCall
call = FunctionCall(function=fn, arguments={"symbol": "ACME", "limit": 3})
result = call.execute() # synchronous
# result = await call.aexecute() # asynchronous
execute() (and aexecute()) returns a FunctionExecutionResult:
class FunctionExecutionResult(BaseModel):
status: Literal["success", "failure"]
result: Optional[Any] = None
error: Optional[str] = None
Internally, execute():
- Runs
pre_hook(if any). - Builds the entrypoint args, injecting
agent/team/fcif requested. - Checks the disk cache when
cache_resultsis enabled (non-generators only). - Runs the entrypoint — directly, or wrapped in the
tool_hookschain. - Caches non-generator results, runs
post_hook, and returns the result.
get_call_str() produces a readable, terminal-width-aware representation such as
lookup(symbol=ACME, limit=3) used in show_tool_calls output.
AgentRunException propagates
If a hook or the entrypoint raises AgentRunException, it is re-raised
rather than swallowed — letting the agent loop handle control-flow signals.
Other exceptions are captured into a failure result.
See Tool Execution for how the agent drives these calls, and Custom Tools for building functions and toolkits.