Building Tools
This page walks through the two recommended paths for creating tools in the Agents package. Most projects only need one of these:FunctionTool::fromCallable()— wrap any callable and get typed parameters with auto-generated schema.BaseTool— extend a base class for tools that need access to agent state or custom behavior.
ContextAwareTool, SimpleTool, and custom descriptors, see Building Tools: Advanced Patterns.
FunctionTool (Recommended)
FunctionTool is the fastest path to a working tool. It uses PHP reflection to extract the tool name from the function name, the description from the #[Description] attribute, and the parameter schema from typed arguments. There is nothing to configure manually.
Basic Usage
get_weather, the description from the function-level #[Description] attribute, and a JSON schema with a required city string parameter documented with its own description.
Closures and Anonymous Functions
Closures work too, though the generated tool name will be less meaningful. You can use#[Description] on both the closure and its parameters:
Multiple Parameters and Types
FunctionTool supports all common PHP types. Optional parameters (those with default values) are not marked as required in the generated schema:
Using Static or Instance Methods
Any callable works — static methods, instance methods, and invokable objects:How Schema Generation Works
When you callFunctionTool::fromCallable(), the factory:
- Uses
CallableSchemaFactoryto extract the function name, description, and parameter types via reflection. - Converts the schema to a JSON Schema array via
SchemaFactory. - Caches the JSON schema on the
FunctionToolinstance so reflection only happens once. - Wraps the callable in a
Closurefor consistent invocation.
Accessing the Underlying Callable
If you need to retrieve the original callable (for example, for testing), use thefunction() method:
BaseTool (State-Aware Class Tool)
UseBaseTool when you need a class-based tool that can access the current AgentState during execution. This is the right choice when your tool needs to read conversation history, check execution metadata, or interact with other parts of the agent’s runtime context.
Basic Usage
EveryBaseTool subclass must implement __invoke(mixed ...$args). Because SimpleTool (the root of the hierarchy) declares __invoke with a variadic mixed signature, all subclasses must keep this exact signature. Use $this->arg() to extract named or positional parameters from the args array.
Why Override toToolSchema()?
BaseTool includes reflective schema support via the HasReflectiveSchema trait, which can auto-generate a schema from the __invoke method signature. However, because __invoke must use the mixed ...$args signature, the auto-generated schema will describe a single variadic mixed parameter — not useful for production prompts. You should almost always override toToolSchema() to declare the parameters the LLM should provide.
Defining Parameters with JsonSchema
TheJsonSchema class provides a fluent API for building parameter schemas without writing raw arrays. It supports all JSON Schema types:
JsonSchema factory methods include: string(), integer(), number(), boolean(), enum(), array(), object(), and any(). Each accepts a name, description, and optional configuration like nullability.
Extracting Arguments with $this->arg()
The arg() helper resolves arguments by trying three sources in order: named key, positional index, then default value. This means your tool works correctly whether the LLM passes arguments by name (the typical case) or by position in tests:
$args['query'] first, then $args[0], then the default ''.
Accessing Agent State
BaseTool extends StateAwareTool, so the current AgentState is available as $this->agentState during execution. The framework injects the state automatically before each invocation — you do not need to set it yourself:
Constructor Defaults
TheBaseTool constructor accepts optional name and description parameters. If name is omitted, it defaults to the fully qualified class name. If description is omitted, it defaults to an empty string:
Custom Metadata and Instructions
BaseTool provides default implementations of metadata() and instructions() that derive values from the tool name and description. Override them when your tool needs richer documentation for tool registries or browsing:
metadata() implementation supports automatic namespace extraction from dotted tool names (e.g., file.read extracts namespace file) and automatic summary extraction from the first sentence of the description. The instructions() method returns the full specification including the reflective parameter schema. This two-level design supports the ToolsTool registry pattern where agents can discover tools without loading their complete documentation.
The __invoke Signature Constraint
A common question is why BaseTool subclasses cannot declare typed parameters on __invoke. The answer is a PHP language constraint: SimpleTool (the abstract root of the hierarchy) declares abstract public function __invoke(mixed ...$args): mixed, and PHP does not allow child classes to narrow the parameter types of an inherited method signature.
This means you cannot write:
$this->arg() to extract named or positional parameters:
FunctionTool::fromCallable() instead.
Testing Your Tools
FakeTool for Loop Testing
When writing tests for agent behavior, useFakeTool to create tools with predetermined responses. This lets you test the agent loop without real tool implementations:
Testing FunctionTool Directly
You can invoke aFunctionTool directly without the agent loop:
Testing BaseTool Subclasses
Instantiate the tool and call it directly. If the tool reads$this->agentState, inject a state first:
Which Approach Should I Use?
| Approach | Use when | Schema strategy | State access |
|---|---|---|---|
FunctionTool | You have a callable with typed parameters | Auto-generated from reflection | No |
BaseTool | You need agent state access or class-based organization | Override toToolSchema() manually | Yes ($this->agentState) |
ContextAwareTool | You need raw ToolCall access for tracing | Override toToolSchema() manually | Yes (both) |
SimpleTool | You want full low-level control over everything | Override toToolSchema() manually | No |
FunctionTool is the right choice. Reach for BaseTool when you need AgentState access, and ContextAwareTool only when you also need the raw ToolCall for correlation or tracing.
Next Steps
- Tools — full reference for the tool system, contracts, and execution lifecycle
- Building Tools: Advanced Patterns —
ContextAwareTool,SimpleTool, custom descriptors, and schema strategies