maestroerror/laragent
LarAgent is an open-source Laravel framework for building and maintaining AI agents. Define agents, tools, memories, and workflows with an Eloquent-style API, structured outputs, pluggable context/memory, multi-agent orchestration with queues, and MCP tool support.
Full Changelog: https://github.com/MaestroError/LarAgent/compare/1.2.2...1.3.0
Full Changelog: https://github.com/MaestroError/LarAgent/compare/1.2.1...1.2.2
Full Changelog: https://github.com/MaestroError/LarAgent/compare/1.2.0...1.2.1
Version 1.2.0 introduces powerful new features for improved reliability, flexibility, and developer experience in agent tool development.
Automatically switch between AI providers when one fails, ensuring your agents stay operational even when a provider is down or rate-limited.
Simple array configuration:
class MyAgent extends Agent
{
// First is primary, others are fallbacks in order
protected $provider = ['openai', 'gemini', 'claude'];
public function instructions()
{
return 'You are a helpful assistant.';
}
}
With per-provider config overrides:
class MyAgent extends Agent
{
protected $provider = [
'openai',
'gemini' => ['model' => 'gemini-2.0-flash'],
'claude',
];
public function instructions()
{
return 'You are a helpful assistant.';
}
}
Global configuration in config/laragent.php
'default_providers' => [
'openai',
'gemini' => ['model' => 'gemini-2.0-flash'],
'claude',
],
NEW: Class-based tools now use handle() instead of execute() for automatic DataModel and Enum conversion.
⚠️ Tool stubs now generate handle() instead of execute()
When you run php artisan laragent:tool MyTool, the generated stub now uses:
protected function handle(array|DataModel $input): mixed
datamodel::class as TypeDirectly use DataModel classes as property types for cleaner, more intuitive tool definitions.
use LarAgent\Tool;
$tool = Tool::create('schedule_meeting', 'Schedule a meeting')
->addProperty('title', 'string', 'Meeting title')
->addProperty('attendee', PersonDataModel::class) // ✨ NEW!
->addProperty('location', AddressDataModel::class) // ✨ NEW!
->setRequired(['title', 'attendee', 'location'])
->setCallback(function (string $title, PersonDataModel $attendee, AddressDataModel $location) {
return "Meeting '{$title}' scheduled with {$attendee->name} at {$location->city}";
});
addDataModelAsProperties() MethodUse an entire DataModel as your tool's input schema:
class TaskDataModel extends DataModel
{
public string $title;
public int $estimatedHours;
public ?string $description = null;
}
$tool = Tool::create('create_task', 'Create a task')
->addDataModelAsProperties(TaskDataModel::class)
->setCallback(function (TaskDataModel $task) {
return "Task '{$task->title}' created with {$task->estimatedHours} hours.";
});
$dataModelClass PropertyDefine your entire tool schema using a DataModel:
class CreateTaskTool extends Tool
{
protected string $name = 'create_task';
protected string $description = 'Create a new task';
// ✨ Automatically populates all properties from TaskDataModel
protected ?string $dataModelClass = TaskDataModel::class;
protected function handle(array|DataModel $input): mixed
{
// $input is already a TaskDataModel instance!
$task = $input;
return "Task '{$task->title}' created with {$task->estimatedHours} hours.";
}
}
$properties ArrayMix DataModel classes directly in your properties array:
class ScheduleMeetingTool extends Tool
{
protected string $name = 'schedule_meeting';
protected string $description = 'Schedule a meeting';
protected array $properties = [
'title' => ['type' => 'string'],
'attendee' => PersonDataModel::class, // ✨ Auto-expanded!
'location' => AddressDataModel::class, // ✨ Auto-expanded!
];
protected array $required = ['title', 'attendee', 'location'];
protected function handle(array|DataModel $input): mixed
{
// DataModel properties are automatically converted!
$attendee = $input['attendee']; // PersonDataModel instance
$location = $input['location']; // AddressDataModel instance
return "Meeting '{$input['title']}' scheduled with {$attendee->name}";
}
}
NEW: Override identity for chat history and usage tracking independently for advanced scenarios like multi-tenant applications.
Custom history identity (group-based chat history):
class TeamAgent extends Agent
{
protected function createHistoryIdentity(): SessionIdentity
{
return new SessionIdentity(
agentName: $this->name(),
chatName: $this->getTeamId(), // Share history across team
);
}
}
Dual identity (different scopes for history and usage):
class MultiTenantAgent extends Agent
{
protected function createHistoryIdentity(): SessionIdentity
{
return new SessionIdentity(
agentName: $this->name(),
chatName: 'team-' . $this->getTeamId(),
);
}
protected function createUsageIdentity(): SessionIdentity
{
return new SessionIdentity(
agentName: $this->name(),
chatName: 'org-' . $this->getOrganizationId(),
);
}
}
$provider configuration with per-provider overridesdefault_providers config option for global fallback setupaddDataModelAsProperties() method for unified DataModel tool inputaddDataModelProperty() method for individual DataModel propertiesdatamodel::class as property type in addProperty()$dataModelClass property for class-based tools$properties arrayhandle() method for class-based tools with automatic type conversioncreateHistoryIdentity() override for custom chat history scopingcreateUsageIdentity() override for custom usage trackinggetProviderSequence() and getActiveProviderName() for debuggingInvalidDataModelException for better error messageshandle() instead of execute()$provider property now accepts string, array, or nullexecute() now delegates to handle() for better extensibilityfallback_provider config (use default_providers or array-based $provider instead)changeProvider() method (kept for backward compatibility)respond() callshandle()Old pattern (still works):
class MyTool extends Tool
{
public function execute(array $input): mixed
{
return 'result';
}
}
New pattern (recommended):
class MyTool extends Tool
{
protected function handle(array|DataModel $input): mixed
{
return 'result';
}
}
Old (deprecated):
// config/laragent.php
'fallback_provider' => 'gemini',
New (recommended):
// config/laragent.php
'default_providers' => ['openai', 'gemini', 'claude'],
// or in your agent:
protected $provider = ['openai', 'gemini', 'claude'];
Full Changelog: https://github.com/MaestroError/LarAgent/compare/1.1.0...1.2.0
Release Date: February 2, 2026
LarAgent now supports structured output (response schema) for Claude models! This brings feature parity with OpenAI and Gemini drivers, enabling schema-validated JSON responses from Claude.
Usage Example:
class PersonExtractor extends Agent
{
protected $provider = 'claude';
protected $responseSchema = PersonData::class;
}
$data = PersonExtractor::make()->respond('John Smith is 35 and lives in NYC');
$data->toArray();
// Returns: ['name' => 'John Smith', 'age' => 35, 'city' => 'NYC']
agent:chat CommandThe agent:chat Artisan command now displays response completion time after each agent response, providing visibility into latency and performance.
Features:
$agent->respond() and tool call display using microtime(true) measurementsformatElapsedTime() method for consistent time formattingExample Output:
TestAgent:
Hello! How can I help you today?
Response completed in 1.35 seconds.
You:
Fixed a critical serialization bug with ToolResultMessage where the tool_name field could be lost during serialization/deserialization. This particularly affected persisted chat histories and caused failures with the Gemini driver.
What was fixed:
tool_name is now always preserved at the top level of the message array in toArray()fromArray() to extract tool_name from nested content for backward compatibility with older data formatsCleaned up the repository by removing example scripts and outdated internal documentation.
Removed Files:
examples/example-agent-class.phpexamples/example-streaming-driver.phpexamples/example-streaming-structured-laragent.phpexamples/example-streaming-tools-laragent.phpexamples/example-structured-output.phpinternalDocs/DataModel.mdConfiguration Update:
.github/ISSUE_TEMPLATE/config.yml to enable blank issuesdependabot/fetch-metadata from 2.4.0 to 2.5.0Full Changelog: https://github.com/MaestroError/LarAgent/compare/1.0.1...1.1.0
Full Changelog: https://github.com/MaestroError/LarAgent/compare/1.0.0...1.0.1
We're excited to announce LarAgent v1.0 - a major release that brings powerful new features for building AI agents in Laravel. This release focuses on enhanced developer experience, improved context management, and production-ready tooling.
Whole storage abstraction layer and DataModel which gives type-safe support to tool inputs, structured output and storage.
It comes with breaking changes, please check the migration guide
make:agent:toolCreating custom tools for your agents is now easier than ever with the new make:agent:tool artisan command.
php artisan make:agent:tool WeatherTool
This command:
AgentTools directory in your app/ folder if it doesn't existExample generated tool:
// app/AgentTools/WeatherTool.php
namespace App\AgentTools;
use LarAgent\Tool;
class WeatherTool extends Tool
{
protected string $name = 'weather_tool';
protected string $description = 'Describe what this tool does';
public function handle(string $location): string
{
// Your tool implementation here
return "Weather data for {$location}";
}
}
agent:chatThe agent:chat command now displays tool calls in the console, providing real-time visibility into your agent's decision-making process.
You: Search for Laravel documentation
Tool call: web_search
Tool call: extract_content
AgentName:
Here is the information I found...
This enhancement makes debugging and testing agents significantly easier by showing which tools are being called during conversations.
MCP (Model Context Protocol) tools now support automatic caching, dramatically improving agent initialization performance. Since MCP tools require network calls to fetch definitions from MCP servers, caching eliminates this latency for subsequent requests.
Configuration:
MCP_TOOL_CACHE_ENABLED=true
MCP_TOOL_CACHE_TTL=3600
MCP_TOOL_CACHE_STORE=redis # Optional: use dedicated store
How it works:
Clear cache when needed:
php artisan agent:tool-clear
The cache clearing command is production-safe for Redis (uses SCAN instead of KEYS).
Track token consumption across your agents with the new usage tracking system.
Enable in your agent:
class MyAgent extends Agent
{
protected $trackUsage = true;
}
Access usage data:
$agent = MyAgent::for('user-123');
$usage = $agent->usageStorage();
// Get all usage records
$records = $usage->getRecords();
// Get total tokens
$totalTokens = $usage->getTotalTokens();
$promptTokens = $usage->getTotalPromptTokens();
$completionTokens = $usage->getTotalCompletionTokens();
// Get records by date range
$recentRecords = $usage->getRecordsSince(now()->subDays(7));
Learn more: https://docs.laragent.ai/v1/context/usage-tracking
LarAgent now provides intelligent strategies for managing conversation length when approaching context window limits.
Available Strategies:
Configuration:
class MyAgent extends Agent
{
protected $enableTruncation = true;
protected $truncationThreshold = 50000; // tokens
// Use custom strategy
protected function truncationStrategy()
{
return new SummarizationStrategy();
}
}
Learn more: https://docs.laragent.ai/v1/context/history#truncation-strategies
Built-in Eloquent and SimpleEloquent drivers for persistent chat history storage.
Using Eloquent storage:
class MyAgent extends Agent
{
protected $history = 'eloquent';
}
Publish and run the migration:
php artisan vendor:publish --tag=laragent-migrations
php artisan migrate
learn more: https://docs.laragent.ai/v1/context/storage-drivers
Define type-safe response schemas using DataModel classes for predictable, structured agent responses.
Create a DataModel:
use LarAgent\Core\Abstractions\DataModel;
use LarAgent\Attributes\Desc;
class WeatherResponse extends DataModel
{
#[Desc('Current temperature in Celsius')]
public float $temperature;
#[Desc('Weather condition (sunny, cloudy, rainy, etc.)')]
public string $condition;
#[Desc('Humidity percentage')]
public int $humidity;
}
Use in your agent:
class WeatherAgent extends Agent
{
protected $responseSchema = WeatherResponse::class;
}
// Response is automatically typed
$response = WeatherAgent::ask('What\'s the weather in London?');
$response->temperature; // 18.5
$response->condition; // "cloudy"
$response->humidity; // 72
DataModel supports:
oneOf schema generationLearn more: https://docs.laragent.ai/v1/responses/structured-output
A new fluent API for managing agent context from anywhere in your application.
use LarAgent\Facades\Context;
use App\AiAgents\MyAgent;
// Get all chat keys for an agent
$chatKeys = Context::of(MyAgent::class)->getChatKeys();
// Filter by user
$userChats = Context::of(MyAgent::class)
->forUser('user-123')
->getChatIdentities();
// Clear all chats for a user
Context::of(MyAgent::class)
->forUser('user-123')
->clearAllChats();
// Iterate with full agent access
Context::of(MyAgent::class)
->forUser('user-123')
->each(function ($identity, $agent) {
$agent->chatHistory()->clear();
});
// Lightweight access without agent initialization
$keys = Context::named('MyAgent')->getChatKeys();
Learn more: https://docs.laragent.ai/v1/context/facade
Enhanced session management with support for identity based context management, including user, chat name, agent and groups
// Session-based (existing)
$agent = MyAgent::for('session-123');
// User-based
$agent = MyAgent::forUser(auth()->id());
// With grouping
$agent = MyAgent::for('support-chat')
->group('customer-support')
->forUser(auth()->id());
// Access identity information
$sessionKey = $agent->getSessionKey(); // 'support-chat'
$userId = $agent->getUserId(); // auth()->id()
$group = $agent->group(); // 'customer-support'
$fullKey = $agent->getSessionId(); // Full storage key
Learn more: https://docs.laragent.ai/v1/context/identity
Tools now support advanced parameter types including:
class BookFlightTool extends Tool
{
protected string $name = 'book_flight';
public function handle(
FlightDetails $details, // DataModel parameter
TravelClass $class, // Enum parameter
string|int $passengers // Union type
): BookingConfirmation {
// Implementation
}
}
Learn more: https://docs.laragent.ai/v1/tools/attribute-tools
Please refer to the MIGRATION guide for detailed migration instructions. Key changes include:
Message::create() and Message::fromArray() removed - use typed factory methodsToolResultMessage constructor now requires toolName parameterChatHistory::getMessages() now returns MessageArray instead of arraychat_history renamed to history in provider configcontextWindowSize property renamed to truncationThresholdcomposer update maestroerror/laragent
Happy building with LarAgent v1.0! 🚀
Full Changelog: https://github.com/MaestroError/LarAgent/compare/0.8.0...1.0.0
First things first! If you were using BeforeToolExecution or AfterToolExecution events, you should update them 👇
What Changed:
BeforeToolExecution and AfterToolExecution events now include the ToolCall objectbeforeToolExecution() and afterToolExecution() receive additional parametersMigration Required:
Event Listeners:
// Before (v0.7)
protected function afterToolExecution(ToolInterface $tool, &$result)
protected function beforeToolExecution(ToolInterface $tool)
// After (v0.8)
protected function afterToolExecution(ToolInterface $tool, ToolCallInterface $toolCall, &$result)
protected function beforeToolExecution(ToolInterface $tool, ToolCallInterface $toolCall)
Hook Callbacks:
// Before (v0.7)
$agent->beforeToolExecution(function($agent, $tool) {
// 2 parameters
});
$agent->afterToolExecution(function($agent, $tool, &$result) {
// 3 parameters
});
// After (v0.8)
$agent->beforeToolExecution(function($agent, $tool, $toolCall) {
// 3 parameters - $toolCall added
});
$agent->afterToolExecution(function($agent, $tool, $toolCall, &$result) {
// 4 parameters - $toolCall added
});
Benefits:
BeforeToolExecution eventAfterToolExecution eventBeforeToolExecution event constructor signature updatedAfterToolExecution event constructor signature updatedprocessBeforeToolExecution() method signature in Hooks trait updatedprocessAfterToolExecution() method signature in Hooks trait updatedA native implementation of Google's Gemini API has been added, providing better integration and performance compared to the OpenAI-compatible wrapper.
Wrapper is still in place to keep it backward-compatible, but it's recommended to use "gemini_native" for new implementations!
Configuration:
// config/laragent.php
'gemini_native' => [
'label' => 'gemini',
'api_key' => env('GEMINI_API_KEY'),
'driver' => \LarAgent\Drivers\Gemini\GeminiDriver::class,
'default_context_window' => 1000000,
'default_max_completion_tokens' => 10000,
'default_temperature' => 1,
'model' => 'gemini-2.0-flash-latest',
],
Features:
Example:
use LarAgent\Agent;
class MyGeminiAgent extends Agent
{
protected $provider = 'gemini_native';
protected $model = 'gemini-2.0-flash-latest';
public function instructions()
{
return 'You are a helpful assistant.';
}
}
You can now set custom configuration values on agents that will be passed through to the driver.
New Methods:
// Set individual config
$agent->setConfig('custom_key', 'value');
// Get individual config
$value = $agent->getConfig('custom_key');
// Set multiple configs (merge)
$agent->withConfigs(['key1' => 'value1', 'key2' => 'value2']);
// Set multiple configs (replace)
$agent->setConfigs(['key1' => 'value1']);
// Check if config exists
if ($agent->hasConfig('custom_key')) {
// ...
}
// Remove config
$agent->removeConfig('custom_key');
// Clear all custom configs
$agent->clearConfigs();
// Get all custom configs
$configs = $agent->getConfigs();
Example:
$agent = MyAgent::for('chat-123')
->withConfigs([
'custom_header' => 'value',
'timeout' => 60,
]);
Or set it via Agent
protected $configs = [
"reasoning_effort" => "minimal"
]
The MCP (Model Context Protocol) server configuration now supports additional options: "headers", "id_type", "startup_delay", "poll_interval"
// config/laragent.php
'mcp_servers' => [
'github' => [
'type' => \Redberry\MCPClient\Enums\Transporters::SSE,
'base_url' => 'https://api.githubcopilot.com/mcp',
'timeout' => 30,
'token' => env('GITHUB_API_TOKEN'),
'headers' => [
// Add custom headers here
],
// 'string' or 'int' - controls JSON-RPC id type (default: 'int')
'id_type' => 'int',
],
'mcp_server_memory' => [
'type' => \Redberry\MCPClient\Enums\Transporters::STDIO,
'command' => 'npx',
'args' => [
'-y',
'[@modelcontextprotocol](https://github.com/modelcontextprotocol)/server-memory',
],
'timeout' => 30,
'cwd' => base_path(),
// milliseconds - delay after process start (default: 100)
'startup_delay' => 100,
// milliseconds - polling interval for response (default: 20)
'poll_interval' => 20,
],
],
MCP resource tool descriptions have been improved for clarity:
PHP
// Before: "Reads the resource"
// After: "Read the resource"
Fixed HTTP transporter in MCP client to support broader list of MCP servers Add "header" array in config allowing to pass arbitrary headers to the client
The configuration initialization has been refactored to be more robust:
PHP
// Excluded keys (won't be added to custom configs)
- api_key
- api_url
// Standard keys (set as properties)
- contextWindowSize
- maxCompletionTokens
- temperature
- reinjectInstructionsPer
- model
- n
- topP
- frequencyPenalty
- presencePenalty
- parallelToolCalls
- toolChoice
- modalities
- audio
// All other keys are stored as custom configs
redberry/mcp-client-laravel from ^1.0 to ^1.1New tests added:
BeforeToolExecution eventAfterToolExecution eventManual test suites added:
GeminiAgentTest.php: Comprehensive agent-level testing for GeminiGeminiDriverTest.php: Low-level driver testing for GeminiFull Changelog: https://github.com/MaestroError/LarAgent/compare/0.7.0...0.8.0
Welcome to LarAgent v0.7! This release focuses on fast, stateless interactions, dynamic capability via MCP, and deep Laravel-native observability through events.
ask() and make()Two new developer-friendly ways to interact with agents without maintaining persistent chat histories.
ask() — one‑liner interactionUse for quick, stateless prompts:
echo WeatherAgent::ask('What is the weather like?');
make() — chainable, configurable, statelessGives you finer control while staying expressive:
echo WeatherAgent::make()
->message('What is the weather like?') // Set the message
->temperature(0.7) // Optional: override temperature
->respond(); // Get the response
Use make() when you need a temporary agent instance with custom temperature, model, or configuration but no stored chat history.
MCP enables agents to discover and use tools and resources from external MCP servers—no custom tool implementation required.
MCP support is provided through the
redberry/mcp-client-laravelpackage (bundled as a dependency).
LarAgent supports HTTP and STDIO transport types via config/laragent.php:
// config/laragent.php
'mcp_servers' => [
'github' => [
'type' => \Redberry\MCPClient\Enums\Transporters::HTTP,
'base_url' => 'https://api.githubcopilot.com/mcp',
'timeout' => 30,
'token' => env('GITHUB_API_TOKEN', null),
],
'mcp_server_memory' => [
'type' => \Redberry\MCPClient\Enums\Transporters::STDIO,
'command' => ['npx', '-y', '[@modelcontextprotocol](https://github.com/modelcontextprotocol)/server-memory'],
'timeout' => 30,
'cwd' => base_path(),
],
],
Use a property for simplicity, or a method for dynamic control.
use LarAgent\Agent;
class MyAgent extends Agent
{
protected $mcpServers = [
'github',
'mcp_server_memory',
];
}
Or with filters:
public function registerMcpServers()
{
return [
'github:tools|only:search_repositories,get_issues',
'mcp_server_memory:tools|except:delete_entities,delete_observations,delete_relations',
'mcp_everything:resources|only:Resource 1,Resource 2',
];
}
Filter syntax tips
:tools or :resources to select fetch typeonly: and except: with comma‑separated items (no spaces)Resources are not fetched by default—request them explicitly or access manually via $this->mcpClient.
class MyAgent extends Agent
{
protected $mcpServers = ['mcp_everything'];
public function instructions()
{
$resourceData = $this->mcpClient
->connect('mcp_everything')
->readResource('test://static/resource/1');
$context = $resourceData['contents'][0]['text'] ?? '';
return "You are a helpful assistant. Context: {$context}";
}
}
Resource response:
[
'contents' => [
[
'uri' => 'test://static/resource/1',
'name' => 'Resource 1',
'mimeType' => 'text/plain',
'text' => 'Resource 1: This is a plaintext resource',
]
]
]
You can call tools directly and hard‑code safe defaults to reduce LLM error surface:
#[Tool('Get issue details from LarAgent repository')]
public function readTheIssue(int $issue_number)
{
$args = [
'issue_number' => $issue_number,
'owner' => 'maestroerror',
'repo' => 'LarAgent',
];
return $this->mcpClient
->connect('github')
->callTool('get_issue', $args);
}
Performance note: Initialization → fetching → registration is intensive. Using >3 MCP servers per Agent can significantly increase response time.
Observe, log, and customize agent behavior with first‑class Laravel events.
AgentInitializedConversationStartedConversationEnded (contains final message; includes token usage via toArrayWithMeta())ToolChanged (with added bool flag)AgentClearedEngineError (with exception)BeforeReinjectingInstructionsBeforeSend / AfterSendBeforeSaveHistoryBeforeResponse / AfterResponseBeforeToolExecution / AfterToolExecutionBeforeStructuredOutputExample handler:
public function handle(ConversationEnded $event): void
{
$response = $event->message; // includes meta/usage
// Log, compute analytics, etc.
}
If you don’t specify for/forUser, LarAgent uses a random key (10 chars via Str). You can customize by overriding generateRandomKey():
// Example: per‑user key
protected static function generateRandomKey(): string {
$user = auth()->user();
return $user->type.'-'.$user->id;
}
// Example: per‑topic key
protected static function generateRandomKey(): string {
return class_basename(static::class).'-'.Session::get('latest_topic');
}
Full Changelog: https://github.com/MaestroError/LarAgent/compare/0.6.0...0.7.0
New Drivers:
Bug Fixes & Other Enhancements:
null by defaultstatic::class)Full Changelog: https://github.com/MaestroError/LarAgent/compare/0.5.0...0.6.0
Welcome to LarAgent v0.5! This release makes it easier to turn your Laravel agents into OpenAI‑compatible APIs and gives you more control over how agents behave. It also adds multimodal inputs (images & audio), new drivers, flexible tool usage and better management of chat histories and structured output. Read on for the highlights.
!! Important: Check the upgrade guide: https://blog.laragent.ai/laragent-v0-4-to-v0-5-upgrade-guide/
Expose agents via API -- A new LarAgent\API\Completions class lets you serve an OpenAI‑style /v1/chat/completions endpoint from your Laravel app. You simply call the static make() method with a Request and your agent class to get a response:
use LarAgent\API\Completions;
public function completion(Request $request)
{
$response = Completions::make($request, MyAgent::class);
// Your custom code
}
Laravel controllers ready to use -- Completions::make is useful for custom implementations, but for common use cases we already implemented The new SingleAgentController and MultiAgentController let you expose one or many agents through REST endpoints without writing boiler‑plate. In your controller, set the $agentClass (for a single agent) or $agents (for multiple agents) and optionally $models to restrict models:
use LarAgent\API\Completions\Controllers\SingleAgentController;
class MyAgentApiController extends SingleAgentController
{
protected ?string $agentClass = \App\AiAgents\MyAgent::class;
protected ?array $models = ['gpt‑4o‑mini', 'gpt-4.1-mini'];
}
For multiple agents:
use LarAgent\API\Completions\Controllers\MultiAgentController;
class AgentsController extends MultiAgentController
{
protected ?array $agents = [
\App\AiAgents\ChatAgent::class,
\App\AiAgents\SupportAgent::class,
];
protected ?array $models = [
'chatAgent/gpt‑4.1‑mini',
'chatAgent/gpt‑4.1‑nano',
'supportAgent', // will use default model defined in agent class
];
}
Both controllers support completion and models endpoints to make it compatible with any OpenAI client, such as OpenWebUI. Example of routes registration:
Route::post('/v1/chat/completions', [MyAgentApiController::class, 'completion']);
Route::get('/v1/models', [MyAgentApiController::class, 'models']);
For more details, check the documentation
Server‑Sent Events (SSE) streaming -- When the client sets "stream": true, the API returns text/event-stream responses. Each event contains a JSON chunk that mirrors OpenAI's streaming format. Usage data appears only in the final chunk.
Custom controllers -- For full control, call Completions::make() directly in your own controller and stream the chunks manually.
Image input -- Agents can now accept publicly‑accessible image URLs using the chainable withImages() method:
$images = [
'https://example.com/image1.jpg',
'https://example.com/image2.jpg',
];
$response = WeatherAgent::for('test_chat')
->withImages($images)
->respond();
Audio input -- Send base64‑encoded audio clips via withAudios(). Supported formats include wav, mp3, ogg, flac, m4a and webm:
$audios = [
[
'format' => 'mp3',
'data' => $base64Audio,
],
];
echo WeatherAgent::for('test_chat')->withAudios($audios)->respond();
Custom UserMessage instances -- Instead of passing a plain string, you can build a UserMessage with metadata (e.g. user ID or request ID). When using a UserMessage, the agent skips the prompt() method:
$userMessage = Message::user($finalPrompt, ['userRequest' => $userRequestId]);
$response = WeatherAgent::for('test_chat')
->message($userMessage)
->respond();
Return message objects -- Call returnMessage() or set the $returnMessage property to true to receive a MessageInterface instance instead of a plain string. This is useful when you need the model's raw assistant message.
Check "Using an Agent" section
A new GroqDriver works with the Groq Platform API. Add GROQ_API_KEY to your .env file and set the provider to groq to use it. The configuration example in the quick‑start has been updated accordingly.
Contributed by @john-ltc
Tool selection methods -- You can now control whether tools are used on a per‑call basis:
toolNone() disables tools for the current call.
toolRequired() makes at least one tool call mandatory.
forceTool('toolName') forces the agent to call a specific tool. After the first call, the choice automatically resets to avoid infinite loops.
withTool() and removeTool().Parallel tool calls -- The new parallelToolCalls(true) method enables or disables parallel tool execution. You can also set the tool choice manually via setToolChoice('none') or similar methods.
Phantom tools -- You can define Phantom Tools that are registered with the agent but not executed by LarAgent; instead they return a ToolCallMessage, allowing you to handle the execution externally. Phantom tools are useful for dynamic integration with external services or when tool execution happens elsewhere.
use LarAgent\PhantomTool;
$phantomTool = PhantomTool::create('phantom_tool', 'Get the current weather in a location')
->addProperty('location', 'string', 'City and state, e.g. San Francisco, CA')
->setRequired('location')
->setCallback('PhantomTool');
// Register with the agent
$agent->withTool($phantomTool);
Chat session IDs -- Model names are no longer included in the chat session ID by default. To retain the old AgentName_ModelName_UserId format, set $includeModelInChatSessionId to true.
Usage metadata keys changed -- Keys in usage data are now snake‑case (prompt_tokens, completion_tokens, etc.) instead of camel‑case (promptTokens). Update any custom code that reads these keys.
Gemini streaming limitation -- The Gemini driver currently does not support streaming responses.
Check the upgrade guide
Full Changelog: https://github.com/MaestroError/LarAgent/compare/0.4.1...0.5.0
Full Changelog: https://github.com/MaestroError/LarAgent/compare/0.4.0...0.4.1
Highlights:
peter-evans/create-pull-request GitHub Action from version 5 to 7.See the full changelog and code diff
Full Changelog: https://github.com/MaestroError/LarAgent/compare/0.3.1...0.4.0
Full Changelog: https://github.com/MaestroError/LarAgent/compare/0.3.0...0.3.1
agent:chat:remove provides a way to completely remove chat histories and their associated keys for a specific agent.agent:chat command: Now you can test your agent with structured outputCheck examples below 👇
We will:
Join the fresh new LarAgent community server on Discord: https://discord.gg/NAczq2T9F8
OpenAiCompatible driver
Support for reasoning models like o1 & o3
Structured output in console for agent:chat command
Full Changelog: https://github.com/MaestroError/LarAgent/compare/0.2.2...0.3.0
What's new in LarAgent?
withModel and overridable model methodsphp artisan agent:chat:clear AgentName$saveChatKeys property to control whether store or not the chat keys for future managementgetChatKeys command for AgentRelease notes 👇
Full Changelog: https://github.com/MaestroError/LarAgent/compare/0.1.1...0.2.0
Bring the power of AI Agents to your Laravel projects with unparalleled ease! 🎉
We're thrilled to announce the first release of LarAgent, the easiest way to create and maintain AI agents in your Laravel applications.
Imagine building an AI assistant with the same elegance as creating an Eloquent model!
With LarAgent, you can:
✨ Create Agents with Artisan: php artisan make:agent YourAgentName
🛠️ Define Agent Behavior: Customize instructions, models, and more directly in your Agent class.
🧰 Add Tools Effortlessly: Use the #[Tool] attribute to turn methods into powerful agent tools.
🗣️ Manage Chat History: Built-in support for in-memory, cache, session, file, and JSON storage.
Check out the documentation
How can I help you explore Laravel packages today?