Tool Calling Internals
The agent’s ability to use tools relies on two contracts and two drivers that implement them differently.Architecture
The Two Contracts
CanUseTools (Driver)
Sends state + tools to the LLM, gets back an updated state with tool call decisions:CanExecuteToolCalls (Executor)
Runs tool calls and returns execution results:ToolCallingDriver
Uses the LLM’s native function calling API. Flow:- Compile messages from state via
CanCompileMessages - Send messages + tool schemas to LLM via
Inference - Parse
InferenceResponsefor tool calls - Pass tool calls to
ToolExecutor - Format execution results as assistant/tool message pairs
- Return updated state with new
AgentStep
tool_calls in its response.
ReActDriver
Uses structured output to extract Thought/Action/Observation decisions. Flow:- Build a system prompt describing available tools and ReAct format
- Use
StructuredOutputto extract aReActDecisionfrom the LLM - Validate the decision (type, tool existence, arguments)
- If
call_tool: execute viaToolExecutor, format as Observation messages - If
final_answer: return the answer as the final response
type, tool, args, and thought fields.
ToolExecutor
The defaultCanExecuteToolCalls implementation. For each tool call:
- BeforeToolUse hook - can modify the call or block it
- Prepare tool - inject
AgentStateif tool implementsCanAccessAgentState - Validate args - check required parameters
- Execute - call
$tool->use(...$args) - AfterToolUse hook - can modify the result
- Emit events -
ToolCallStarted,ToolCallCompleted
ToolExecutor is created automatically by AgentLoop::default(). To customize it:
When to Use Which Driver
| ToolCallingDriver | ReActDriver | |
|---|---|---|
| Requires | LLM with function calling | Any LLM with JSON output |
| Tool selection | Native, reliable | Structured output extraction |
| Reasoning | Implicit | Explicit (Thought field) |
| Reliability | Higher (native API) | Lower (parsing required) |
| Flexibility | Standard tools only | Custom decision schemas |