How to Create AI Agents in Laravel with Attribute‑based Tool Registration (2025 Guide)

LarAgent brings the power of agentic AI to any Laravel 10+ project, letting you define agents using familiar PHP classes and register tools with a simple attribute.
This guide walks through the core concepts of LarAgent, shows how to install and configure it, and then builds a complete Travel Planner agent that fetches weather, converts currency and recommends attractions using attribute‑registered tools.
By the end, you’ll know how to create your own AI assistants and integrate them into your application. 🚀
Prerequisites and installation
- Laravel 10.x+ and PHP 8.3+ – the package requires a recent version of PHP and Laravel
- An API key for OpenAI or another LLM provider. LarAgent ships with drivers for OpenAI, Gemini and other providers; you simply supply the key in
.env
file.
Install LarAgent via Composer:
composer require maestroerror/laragent
# publish the config file to config/laragent.php
php artisan vendor:publish --tag="laragent-config"
Publishing the config creates config/laragent.php
, which defines the default driver, chat‑history store and provider settings. For OpenAI, all you need is an OPENAI_API_KEY
in your .env
You can also define custom providers with different API URLs, models or temperatures—useful when switching to Ollama, OpenRouter or Gemini.
Configuring chat history
LarAgent keeps track of conversations so your agent remembers past messages. You can choose how histories are stored by setting the $history
property in the agent class:
in_memory
(ephemeral, lost after request)session
(Laravel session)cache
(uses Laravel’s cache)file
or to persist JSON files understorage/app/private/chat‑histories
.
For more control you can provide a class (e.g. \LarAgent\History\SessionChatHistory
) or override createChatHistory()
method, check relevant documentation: https://docs.laragent.ai/core-concepts/chat-history#built-in-chat-histories
Understanding tools and attribute‑based registration
Tools extend an agent’s capabilities beyond pure text generation. They let the language model call PHP functions to fetch data, perform calculations or trigger side effects. LarAgent supports three ways to register tools:
- Attribute‑based tools – the simplest method. Apply the
#[Tool]
attribute to a method and LarAgent will expose it as a function. For example:
use LarAgent\Attributes\Tool;
#[Tool('Get the current weather in a given location')]
public function weatherTool($location, $unit = 'celsius')
{
return 'The weather in ' . $location . ' is 20 degrees ' . $unit;
}
When the agent sees a relevant user request, the language model can choose to call this function. LarAgent automatically extracts parameter names and types. It also supports PHP Enums as parameter types to give the model a finite set of choices. For example, defining a Unit
enum with celsius
and fahrenheit
and using it as a type in the tool’s signature.
registerTools()
method – useful when you need to create tools programmatically (e.g. based on user state). Inside this method you can return an array ofTool::create()
calls and set callbacks and properties.- Dedicated tool classes – for complex or reusable logic. Extend
LarAgent\Tool
, define the$name
,$description
,$properties
and implementexecute()
. You then list the class in the agent’s$tools
property.
Use‑case: Building a Travel Planner agent
To illustrate attribute‑based tool registration, we’ll build a TravelPlannerAgent that plans short trips. It will:
- Fetch the weather for a location.
- Convert currency amounts into the user’s preferred unit.
- Recommend attractions based on the traveller’s interests.
We’ll show how to define Enums for valid units, register the tools using the attribute, and return structured JSON output for easier integration with a frontend.
Setting up the agent
Generate the class via Artisan command:
php artisan make:agent TravelPlannerAgent
Then modify the class as follows:
<?php
namespace App\AiAgents;
use LarAgent\Agent;
use LarAgent\Attributes\Tool;
use App\Enums\TemperatureUnit;
use App\Enums\Currency;
use App\Enums\Interest;
class TravelPlannerAgent extends Agent
{
// Use GPT‑4o for better reasoning; store sessions in JSON files
protected $model = 'gpt-4o';
protected $history = 'json';
protected $provider = 'default';
protected $temperature = 0.8;
/**
* System instructions. These guide the LLM’s behaviour.
*/
public function instructions(): string
{
return "You are a friendly AI travel planner. Given a destination, dates, interests and budget, create a concise itinerary. Use tools to fetch weather, convert currency and suggest attractions.";
}
/**
* Optional: modify incoming messages before sending to the LLM.
* Or implement RAG, or add extra context like current date
*/
public function prompt(string $message): string
{
return $message;
}
/**
* Define a structured output schema. This tells LarAgent to return
* JSON matching this schema instead of free‑form text.
*/
protected $responseSchema = [
'name' => 'travel_itinerary',
'schema' => [
'type' => 'object',
'properties' => [
'summary' => [
'type' => 'string',
'description' => 'High‑level summary of the trip',
],
'days' => [
'type' => 'array',
'items' => [
'type' => 'object',
'properties' => [
'day' => ['type' => 'integer'],
'weather' => ['type' => 'string'],
'activities'=> [
'type' => 'array',
'items' => ['type' => 'string'],
],
'estimatedBudget' => ['type' => 'number'],
],
'required' => ['day','weather','activities'],
'additionalProperties' => false,
],
],
],
'required' => ['summary','days'],
'additionalProperties' => false,
],
'strict' => true,
];
// Tools will be defined below using attributes…
}
Creating Enums
To limit the model to valid options we define simple PHP Enums. They live in app/Enums
:
// app/Enums/TemperatureUnit.php
namespace App\Enums;
enum TemperatureUnit: string
{
case CELSIUS = 'celsius';
case FAHRENHEIT = 'fahrenheit';
}
// app/Enums/Currency.php
namespace App\Enums;
enum Currency: string
{
case USD = 'USD';
case EUR = 'EUR';
}
// app/Enums/Interest.php
namespace App\Enums;
enum Interest: string
{
case FOOD = 'food';
case CULTURE = 'culture';
case NATURE = 'nature';
case NIGHTLIFE= 'nightlife';
}
Enums are recommended for restricting the AI to specific values, preventing ambiguous requests like “use the best unit”.
Registering tools with attributes
Now we add methods decorated with #[Tool]
. The first argument to the attribute is the tool’s description; the second, optional argument is an array mapping parameter names to human‑readable descriptions:
Weather tool
#[Tool(
'Get the current weather in a given location',
[
'location' => 'City and country, e.g. Paris, France',
'unit' => 'Temperature unit (celsius or fahrenheit)'
]
)]
public function getWeather(string $location, TemperatureUnit $unit): string
{
// In a real application call a weather API here.
// Returning a stubbed value keeps the example self‑contained.
$weather = $unit === TemperatureUnit::CELSIUS ? '22 °C' : '72 °F';
return "The weather in $location is $weather.";
}
Currency conversion tool
#[Tool(
'Convert an amount between currencies',
[
'amount' => 'Amount to convert',
'from' => 'Currency code to convert from',
'to' => 'Currency code to convert to'
]
)]
public function convertCurrency(float $amount, Currency $from, Currency $to): float
{
// Dummy exchange rates; in production fetch live rates from a service.
$rates = [
'USD' => ['EUR' => 0.9, 'USD' => 1],
'EUR' => ['USD' => 1.1, 'EUR' => 1],
];
return round($amount * $rates[$from->value][$to->value], 2);
}
Because the tool uses the Currency
enum, the AI can only choose valid currencies. So your API call will always receive the correct currency codes.
Attractions recommendation tool
#[Tool(
'Recommend attractions based on your interests in a city',
[
'city' => 'Destination city',
'interest'=> 'Type of interest (food, culture, nature or nightlife)'
]
)]
public function findAttractions(string $city, Interest $interest): array
{
// A simple lookup table. Replace with a database or API call.
$db = [
'Paris' => [
'food' => ['Try fresh croissants at a local boulangerie', 'Dinner at a Michelin‑starred bistro'],
'culture' => ['Visit the Louvre', 'Explore the Musée d’Orsay'],
'nature' => ['Relax in the Luxembourg Gardens', 'Boat ride on the Seine'],
'nightlife'=> ['Enjoy cocktails in Le Marais', 'Attend a jazz club'],
],
'Tbilisi' => [
'food' => ['Taste khinkali at a traditional restaurant', 'Sample wines in a local wine cellar'],
'culture' => ['Tour the Old Town', 'Visit the Georgian National Museum'],
'nature' => ['Ride the cable car to Mtatsminda Park', 'Hike in Mtatsminda forest'],
'nightlife'=> ['Explore Rustaveli Avenue\'s bars', 'Listen to live music in a basement club'],
],
];
return $db[$city][$interest->value] ?? [];
}
Putting it together
After defining the tools, your TravelPlannerAgent
class might look like this (simplified):
class TravelPlannerAgent extends Agent
{
// properties and schema omitted…
#[Tool(...)]
public function getWeather(...) { /* code above */ }
#[Tool(...)]
public function convertCurrency(...) { /* code above */ }
#[Tool(...)]
public function findAttractions(...) { /* code above */ }
}
To generate an itinerary, call the agent with a descriptive message:
use App\AiAgents\TravelPlannerAgent;
// forUser method keeps chat histories separated per user
$agent = TravelPlannerAgent::forUser(auth()->user());
$response = $agent
->message('Plan a 3‑day trip to Paris focusing on food and culture with a budget of 500 EUR. Output JSON.')
->respond();
// Array adhering to given response Schema
print_r($response);
Because we defined a response schema, $response
will be an associative array containing summary
and a list of days
. LLM can call our tools to fetch the current weather, convert budgets and pick attractions. The schema ensures consistent keys, so we can present the returned $response
to user in structured format 💪
Tips and best practices
- Separate concerns: encapsulate reusable logic in dedicated tool classes rather than overloading the agent class
- Be explicit: give tools descriptive names and provide clear parameter descriptions. The AI relies on these strings to understand when to call them
- Restrict input: use Enums whenever possible so the model can only choose valid options (e.g.
TemperatureUnit
,Currency
) and avoid hallucinations. - Leverage structured output: define schemas for predictable JSON responses and set
strict
to true, to avoid extra properties - Test tool behaviour: run your agent locally using
php artisan agent:chat TravelPlannerAgent
or in your tests; watch how the LLM decides to call your tools and refine descriptions accordingly.
Conclusion
LarAgent makes building AI agents in Laravel feel familiar and elegant. With a few lines of PHP and the #[Tool]
attribute, you can expose functions to a language model, restrict arguments with enums and even define JSON schemas for structured responses.
Our imagined TravelPlannerAgent showed how to combine multiple tools—weather, currency conversion and attraction recommendations—into a cohesive assistant.
Using LarAgent’s fluent API, tool registration and configurable chat histories, you can integrate AI‑powered features into your Laravel applications today and be ready for the innovations coming in 2025 🚀
Your support is the best motivation for us to keep LarAgent development ❤️