Releases: MaestroError/LarAgent
1.2.2: Gemini streaming and empty tool_use fixes
What's Changed
- Fix empty tool_use input serialized as JSON array instead of object by @ricardo in #145
- Update openai-php/client requirement from ^0.18.0 to ^0.19.0 by @dependabot[bot] in #144
- Fix issue with Gemini and streaming when tools are called. by @jamestimbrell in #146
New Contributors
- @ricardo made their first contribution in #145
- @jamestimbrell made their first contribution in #146
Full Changelog: 1.2.1...1.2.2
Fix: sessionId back in agent DTO
What's Changed
- Add sessionId to AgentDTO and include in event dispatching tests by @MaestroError in #143
Full Changelog: 1.2.0...1.2.1
1.2.0 - Multi provider fallback, DataModel in tool class and overridable identity
🎉 What's New
Version 1.2.0 introduces powerful new features for improved reliability, flexibility, and developer experience in agent tool development.
🔄 Multi-Provider Fallback with Automatic Failover
Automatically switch between AI providers when one fails, ensuring your agents stay operational even when a provider is down or rate-limited.
Usage Examples
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',
],🛠️ Enhanced Tool Development with DataModel Support
NEW: Class-based tools now use handle() instead of execute() for automatic DataModel and Enum conversion.
Breaking Changes
handle() instead of execute()
When you run php artisan laragent:tool MyTool, the generated stub now uses:
protected function handle(array|DataModel $input): mixed1 AddProperty Now Supports datamodel::class as Type
Directly 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}";
});2 New addDataModelAsProperties() Method
Use 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.";
});3 Class-Based Tools with $dataModelClass Property
Define 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.";
}
}4 Class-Based Tools with DataModel in $properties Array
Mix 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}";
}
}🔐 Custom Storage Identity Overrides
NEW: Override identity for chat history and usage tracking independently for advanced scenarios like multi-tenant applications.
Usage Examples
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(),
);
}
}📋 Complete Changelog
Added
- ✨ Multi-provider fallback with automatic failover
- ✨ Array-based
$providerconfiguration with per-provider overrides - ✨
default_providersconfig option for global fallback setup - ✨
addDataModelAsProperties()method for unified DataModel tool input - ✨
addDataModelProperty()method for individual DataModel properties - ✨ Support for
datamodel::classas property type inaddProperty() - ✨
$dataModelClassproperty for class-based tools - ✨ Automatic DataModel expansion in
$propertiesarray - ✨
handle()method for class-based tools with automatic type conversion - ✨
createHistoryIdentity()override for custom chat history scoping - ✨
createUsageIdentity()override for custom usage tracking - ✨
getProviderSequence()andgetActiveProviderName()for debugging - ✨
InvalidDataModelExceptionfor better error messages
Changed
- 🔄 Tool stub now generates
handle()instead ofexecute() - 🔄
$providerproperty now acceptsstring,array, ornull - 🔄
execute()now delegates tohandle()for better extensibility
Deprecated
⚠️ fallback_providerconfig (usedefault_providersor array-based$providerinstead)⚠️ changeProvider()method (kept for backward compatibility)
Fixed
- 🐛 Provider-specific config resolution in multi-provider scenarios
- 🐛 Agent-defined property preservation during provider fallback
- 🐛 Provider reset on subsequent
respond()calls
🔧 Migration Guide (optional)
Updating Tools to Use handle()
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';
}
}Updating to Multi-Provider Fallback
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'];What's Changed
- Implement array-based multi-provider fallback for Agent API providers by @Copilot in #139
- Add overridable identity creation methods for built-in storages by @Copilot in #140
- Unify DataModel property support for all Tool creation mechanisms by @Copilot in #141
Full Changelog: 1.1.0...1.2.0
v1.1 - Structured output for Claude, completion time in agent:chat and fixes
LarAgent v1.1.0 Release Notes
Release Date: February 2, 2026
✨ New Features
Structured Output Support for ClaudeDriver
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']Completion Time Display in agent:chat Command
The agent:chat Artisan command now displays response completion time after each agent response, providing visibility into latency and performance.
Features:
- Timer wraps
$agent->respond()and tool call display usingmicrotime(true)measurements - Smart formatting: sub-second times shown as milliseconds, otherwise as seconds with 2 decimal places
- New
formatElapsedTime()method for consistent time formatting
Example Output:
TestAgent:
Hello! How can I help you today?
Response completed in 1.35 seconds.
You:🐛 Bug Fixes
Gemini Driver Function Response Fix
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_nameis now always preserved at the top level of the message array intoArray()- Updated
fromArray()to extracttool_namefrom nested content for backward compatibility with older data formats - Added comprehensive test suite covering creation, serialization, deserialization, backward compatibility, and round-trip preservation
- Added manual integration test script for real-world Gemini API verification
🧹 Codebase Cleanup
Cleaned 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.md
Configuration Update:
- Updated
.github/ISSUE_TEMPLATE/config.ymlto enable blank issues
📦 Dependencies
- Bumped
dependabot/fetch-metadatafrom 2.4.0 to 2.5.0
What's Changed
- Cleanup and fixes by @MaestroError in #129
- Gemini driver function response fix by @MaestroError in #136
- Bump dependabot/fetch-metadata from 2.4.0 to 2.5.0 by @dependabot[bot] in #135
- Add structured output support for ClaudeDriver by @Copilot in #137
- Add completion time display to agent:chat command by @Copilot in #138
Full Changelog: 1.0.1...1.1.0
Fixes: Gemini driver, Data model, Ollama driver, signature, etc
What's Changed
- Implement thought signature support for Gemini 3 models by @MaestroError in #123
- fix: DTO parameter in openAI drivers by @jcbertoli in #125
- Data model fixes by @MaestroError in #126
New Contributors
- @jcbertoli made their first contribution in #125
Full Changelog: 1.0.0...1.0.1
v1.0 - Release Notes 🚀
LarAgent v1.0 Release Notes
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
🎉 Highlights
🛠️ New Artisan Command: make:agent:tool
Creating custom tools for your agents is now easier than ever with the new make:agent:tool artisan command.
php artisan make:agent:tool WeatherToolThis command:
- Creates an
AgentToolsdirectory in yourapp/folder if it doesn't exist - Generates a ready-to-customize tool class with all the necessary boilerplate
- Returns the full path for easy IDE navigation (Ctrl+Click support)
Example 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}";
}
}🔧 Tool Call Debugging in agent:chat
The 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 Tool Caching
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 storeHow it works:
- First request fetches tools from MCP server and caches them
- Subsequent requests load from cache instantly (no network call)
- Cache is shared across all users/agents for efficiency
Clear cache when needed:
php artisan agent:tool-clearThe cache clearing command is production-safe for Redis (uses SCAN instead of KEYS).
📊 Usage Tracking
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
✂️ Automatic Conversation Truncation
LarAgent now provides intelligent strategies for managing conversation length when approaching context window limits.
Available Strategies:
- Sliding Window (Default) - Removes oldest messages to stay within limits
- Summarization - Summarizes older messages using AI before removing them
- Symbolization - Replaces messages with symbolic representations
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
🗄️ Database Storage Drivers
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 migratelearn more: https://docs.laragent.ai/v1/context/storage-drivers
📋 Structured Output with DataModel
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; // 72DataModel supports:
- Union types with
oneOfschema generation - Nested DataModels for complex structures
- Arrays of DataModels
- Enums for constrained values
- Polymorphic discriminator resolution
Learn more: https://docs.laragent.ai/v1/responses/structured-output
🎯 Context Facade
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
🆔 Context with Identities
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 keyLearn more: https://docs.laragent.ai/v1/context/identity
🧩 Enhanced Tool Parameter Types
Tools now support advanced parameter types including:
- DataModel parameters - Complex structured input
- Enum parameters - Constrained choice values
- Union types - Multiple allowed types with smart resolution
- Arrays of typed items - Collections with type validation
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
📚 Documentation Updates
- Migration Guide: Comprehensive guide for upgrading from v0.8 to v1.0
- Internal Documentation: Detailed docs for Chat History, Context, Truncation Strategies, Usage Tracking, and more
- DataModel Guide: Complete documentation for structured output features
⚠️ Breaking Changes
Please refer to the MIGRATION guide for detailed migration instructions. Key changes include:
Message::create()andMessage::fromArray()removed - use typed factory methodsToolResultMessageconstructor now requirestoolNameparameterChatHistory::getMessages()now returnsMessageArrayinstead of array- Config key
chat_historyrenamed tohistoryin provider config contextWindowSizeproperty renamed totruncationThreshold
📦 Upgrading
composer update maestroerror/laragent🔗 Resources
Happy building with LarAgent v1.0! 🚀
What's Changed
- Add tool call logging to agent:chat command by @Copilot in #100
- Update openai-php/client requirement from ^0.17.1 to ^0.18.0 by @dependabot[bot] in #96
- feat: add make:agent:tool command by @Yalasev903 in #103
- Bump stefanzweifel/git-auto-commit-action from 6 to 7 by @dep...
0.8.0 - Native Gemini driver, ToolCall events, Arbitrary configs & more!
🚀 LarAgent v0.8.0 Release Notes
First things first! If you were using BeforeToolExecution or AfterToolExecution events, you should update them 👇
⚠️ Breaking Changes (v0.7 → v0.8)
Tool Execution Events Now Include ToolCall Object
What Changed:
BeforeToolExecutionandAfterToolExecutionevents now include theToolCallobject- Hook callbacks for
beforeToolExecution()andafterToolExecution()receive additional parameters
Migration 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:
- Full tool call tracing with unique IDs
- Access to exact arguments passed to each tool
- Better correlation between tool calls and results
- Enhanced debugging and audit capabilities
Added
- ToolCall object now passed to
BeforeToolExecutionevent - ToolCall object now passed to
AfterToolExecutionevent - ToolCall parameter added to tool execution hook callbacks
- New tests for ToolCall presence in events
Changed
BeforeToolExecutionevent constructor signature updatedAfterToolExecutionevent constructor signature updatedprocessBeforeToolExecution()method signature in Hooks trait updatedprocessAfterToolExecution()method signature in Hooks trait updated- Hook callbacks now receive ToolCall parameter for better logging capabilities
✨ New Features
1. Native Gemini Driver
A 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:
- Normal text generation
- Streaming support
- Tool/function calling
- Structured output
- System instructions
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.';
}
}2. Arbitrary Configuration Support
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"
]3. Enhanced MCP Server Configuration
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/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,
],
],🔧 Improvements
Streaming Enhancements
- Anthropic (Claude): Improved handling of delta chunks, resetting last chunk when no recognized delta type is present
- Groq: Fixed streaming to always yield final message, improved handling of tool calls vs normal messages
- Gemini Native: Full streaming support with proper chunk handling
MCP Resource Descriptions
MCP resource tool descriptions have been improved for clarity:
PHP
// Before: "Reads the resource"
// After: "Read the resource"
MCP HTTP Transporter enhancement
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
Configuration Handling
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
Error Handling
- Fixed Gemini OpenAI-compatible driver to properly check for API key existence
- Improved null safety in MCP client initialization
📦 Dependencies
- Updated
redberry/mcp-client-laravelfrom^1.0to^1.1
🧪 Testing
New tests added:
- ToolCall object presence in
BeforeToolExecutionevent - ToolCall object presence in
AfterToolExecutionevent - Arbitrary configuration setting and retrieval
- Configuration chaining behavior
- Configuration initialization with standard vs custom keys
Manual test suites added:
GeminiAgentTest.php: Comprehensive agent-level testing for GeminiGeminiDriverTest.php: Low-level driver testing for Gemini
What's Changed
- Add ToolCall object to tool execution events for enhanced logging by @Copilot in #93
- feat: adds ability to set arbitrary configs for an agent by @crathgeb in #94
- Upgraded RDBR MCP client to v1.1 & updated example configuration by @MaestroError in #97
- Feature/gemini native driver by @Yalasev903 in #86
- Gemini driver update by @MaestroError in #98
New Contributors
- @crathgeb made their first contribution in #94
- @Yalasev903 made their first contribution in #86
Full Changelog: 0.7.0...0.8.0
v0.7 - MCP & Events
LarAgent v0.7 — Release Notes
Welcome to LarAgent v0.7! This release focuses on fast, stateless interactions, dynamic capability via MCP, and deep Laravel-native observability through events.
🚀 New Methods: ask() and make()
Two new developer-friendly ways to interact with agents without maintaining persistent chat histories.
🔹 ask() — one‑liner interaction
Use for quick, stateless prompts:
echo WeatherAgent::ask('What is the weather like?');🔹 make() — chainable, configurable, stateless
Gives 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 responseUse make() when you need a temporary agent instance with custom temperature, model, or configuration but no stored chat history.
🧩 Model Context Protocol (MCP) Integration
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).
⚙️ Configure MCP Servers
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/server-memory'],
'timeout' => 30,
'cwd' => base_path(),
],
],🧠 Register Servers in an Agent
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
- Use
:toolsor:resourcesto select fetch type - Use
only:andexcept:with comma‑separated items (no spaces)
📚 Working with Resources
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',
]
]
]🪄 Call MCP Tools Manually
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.
🔔 Laravel‑Native Events
Observe, log, and customize agent behavior with first‑class Laravel events.
🔹 Lifecycle Events
AgentInitializedConversationStartedConversationEnded(contains finalmessage; includes token usage viatoArrayWithMeta())ToolChanged(withaddedbool flag)AgentClearedEngineError(withexception)
🔹 Hook Events (Before/After)
BeforeReinjectingInstructionsBeforeSend/AfterSendBeforeSaveHistoryBeforeResponse/AfterResponseBeforeToolExecution/AfterToolExecutionBeforeStructuredOutput
Example handler:
public function handle(ConversationEnded $event): void
{
$response = $event->message; // includes meta/usage
// Log, compute analytics, etc.
}🗂️ Chat‑History Improvements
Random Chat Histories by Default
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');
}What's Changed
- Add Laravel Events with automatic dispatching via callEvent system by @Copilot in #85
- Add new methods to the agent for one off questions by @vakhtangaA in #90
- Add PEST tests for newly added MCP methods in Agent class by @Copilot in #92
- MCPClient integrated in Agent class by @MaestroError in #91
- Bump actions/checkout from 4 to 5 by @dependabot[bot] in #88
- Update openai-php/client requirement from ^0.14.0 to ^0.16.1 by @dependabot[bot] in #89
New Contributors
- @Copilot made their first contribution in #85
- @vakhtangaA made their first contribution in #90
Full Changelog: 0.6.0...0.7.0
v0.6 - Drivers Expansion
LarAgent v0.6 Release Note 🎉
-
New Drivers:
- Openrouter driver: Added support for Openrouter
- Anthropic (Claude) driver: Added a driver for Anthropic Claude models
- Ollama driver: Added support for Ollama
- Groq driver improvements: Fixes and enhancements for the Groq driver (PR #81).
-
Bug Fixes & Other Enhancements:
- Fixed ob_flush issue affecting streaming
- Update config including all supported drivers preconfigured
- Update config setting fallback_provider to
nullby default - Name property for Agent class, setting basename by default (
static::class) - Various dependency updates and minor fixes
What's Changed
- Ollama driver and fallback provider set to null by default by @MaestroError in #73
- Bump aglipanci/laravel-pint-action from 2.5 to 2.6 by @dependabot[bot] in #75
- [Feature]: Anthropic (Claude) driver by @john-ltc in #76
- Updated tests and default model to 3.7 by @MaestroError in #78
- fixes ob_flush issue #77 by @shininglove in #79
- fix groq driver and improvements for claude driver by @john-ltc in #81
- Openrouter driver by @MaestroError in #84
New Contributors
- @shininglove made their first contribution in #79
Full Changelog: 0.5.0...0.6.0
v0.5 - OpenAI‑compatible API endpoints, Multimodal inputs & more
LarAgent v0.5 Release Notes 🎉
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/
New features ✨
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.
Multimodal inputs
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();Agent usage enrichment
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
Groq driver
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 handling enhancements 🔧
-
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.- Dynamic tool management -- Tools can be added or removed at runtime using
withTool()andremoveTool().
- Dynamic tool management -- Tools can be added or removed at runtime using
-
Parallel tool calls -- The new
parallelToolCalls(true)method enables or disables parallel tool execution. You can also set the tool choice manually viasetToolChoice('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);Changes 🛠️
-
Chat session IDs -- Model names are no longer included in the chat session ID by default. To retain the old
AgentName_ModelName_UserIdformat, set$includeModelInChatSessionIdtotrue. -
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
What's Changed
- Bump dependabot/fetch-metadata from 2.3.0 to 2.4.0 by @dependabot[bot] in #33
- Fix toDTO to use agent model by @MaestroError in #40
- Fix docblock typo in Agent.php by @MaestroError in #42
- Fix reinject instructions methods typo by @MaestroError in #43
- Fix SSE stream accumulation by @MaestroError in #41
- Improve streaming response tests by @MaestroError in #44
- Add streaming fallback provider support by @MaestroError in #45
- Fix/store usage while streaming by @MaestroError in #46
- Add additional OpenAI configuration options by @MaestroError in #47
- Bump stefanzweifel/git-auto-commit-action from 5 to 6 by @dependabot[bot] in #53
- Update openai-php/client requirement from ^0.13.0 to ^0.14.0 by @dependabot[bot] in #52
- Extend API request validation rules by @MaestroError in #48
- Add tool choice configuration in Agent by @MaestroError in #54
- Add PseudoTool tests and finalize API tool call handling by @MaestroError in #57
- New setter methods for API and first basic response by @MaestroError in #56
- Add audio modality support for agents by @MaestroError in #55
- [Feature]: Groq driver by @john-ltc in #66
- Manual tests created, readme update by @MaestroError in #67
- Codex/add OpenAI compatible api layer by @MaestroError in #70
- Add comprehensive Completions controller tests by @MaestroError in #71
- Expose any Agent(s) as API endpoint compatible to OpenAI API by @MaestroError in #72
New Contributors
Full Changelog: 0.4.1...0.5.0