PHP developers get a lot of AI examples - but almost all of them start in Python. If your app is already in PHP (and it usually is), the "add an agent" path looks like bolting on a sidecar or learning a framework. It doesn't have to.
This is the smallest working agent in NanoAgent: one agent, one custom tool, one task.
An agent in NanoAgent is just four objects:
Agent`` FunctionTool``Task
Let's build it piece by piece. First, the boilerplate: pull in the auto and the three classes you need.
require_once __DIR__ . '/../NanoAgent/auto.php';
use NanoAgent\Agent;
use NanoAgent\Task;
use NanoAgent\Tools\FunctionTool;
Rather than hard-code a provider and API key, read them from a config.php file if one exists, and fall back to sane defaults (Groq's free tier) if it doesn't. This means the same script runs out of the box for a first-time reader, and switches provider for anyone with their own config.
$configFile = __DIR__ . '/../NanoAgent/config.php';
$config = file_exists($configFile) ? require $configFile : [];
$llmConfig = [
'provider' => $config['provider'] ?? 'groq',
'model' => $config['model'] ?? 'llama-3.3-70b-versatile',
'api_key' => $config['api_key'] ?? ''
];
A FunctionTool wraps three things: a name the model refers to, a description it reads to decide when to use the tool, and a JSON Schema describing the arguments. The callable is just a normal PHP closure - here it fabricates a "secret token" from the input string.
$secretTool = new FunctionTool(
name: 'generate_secret_token',
description: 'Generates a secure token based on input string.',
parameters: [
'type' => 'object',
'properties' => [
'input' => ['type' => 'string', 'description' => 'The string to obfuscate']
],
'required' => ['input']
],
callable: fn(array $args) => "TOKEN_" . strtoupper(strrev($args['input'])) . "_SECURE"
);
The Agent ties the LLM config to a system prompt and the list of tools it's allowed to use. Nothing runs yet - this just assembles the pieces.
$agent = new Agent(
llm: $llmConfig,
systemPrompt: "You are a helpful PHP technical assistant.",
tools: [$secretTool]
);
A Task bundles a goal with any background context you want the model to have (here, just the project name). Calling execute() is what actually sends the prompt, lets the model call the tool if it wants to, and returns the final answer.
$task = new Task($agent);
$task->addContext("Project", "NanoAgent Library");
$response = $task->execute(
"Generate a secret token for 'NanoAgent' and explain how it was created."
);
echo $response;
The FunctionTool is the whole idea. You describe a PHP function with a JSON Schema, and the model decides when to call it and what to pass. Task::execute() formats the goal + context into a prompt, and handles the tool round-trip for you - when the model asks to call generate_secret_token, the agent runs your callable, feeds the result back, and continues. You never wire that loop by hand.
That's the floor. Once you have an agent that can call tools, everything else is adding more tools or more agents:
Part of the NanoAgent examples series. Landing + demos.