Skip to content

Releases: MaestroError/LarAgent

1.2.2: Gemini streaming and empty tool_use fixes

11 Mar 09:05
58cfe08

Choose a tag to compare

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

Full Changelog: 1.2.1...1.2.2

Fix: sessionId back in agent DTO

09 Feb 11:09
8180ba3

Choose a tag to compare

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

05 Feb 11:17
590be4b

Choose a tag to compare

🎉 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

⚠️ 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

1 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 $provider configuration with per-provider overrides
  • ✨ default_providers config option for global fallback setup
  • ✨ addDataModelAsProperties() method for unified DataModel tool input
  • ✨ addDataModelProperty() method for individual DataModel properties
  • ✨ Support for datamodel::class as property type in addProperty()
  • ✨ $dataModelClass property for class-based tools
  • ✨ Automatic DataModel expansion in $properties array
  • ✨ handle() method for class-based tools with automatic type conversion
  • ✨ createHistoryIdentity() override for custom chat history scoping
  • ✨ createUsageIdentity() override for custom usage tracking
  • ✨ getProviderSequence() and getActiveProviderName() for debugging
  • ✨ InvalidDataModelException for better error messages

Changed

  • 🔄 Tool stub now generates handle() instead of execute()
  • 🔄 $provider property now accepts stringarray, or null
  • 🔄 execute() now delegates to handle() for better extensibility

Deprecated

  • ⚠️ fallback_provider config (use default_providers or array-based $provider instead)
  • ⚠️ 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

02 Feb 15:29
31a3414

Choose a tag to compare

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 using microtime(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_name is now always preserved at the top level of the message array in toArray()
  • Updated fromArray() to extract tool_name from 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.php
  • examples/example-streaming-driver.php
  • examples/example-streaming-structured-laragent.php
  • examples/example-streaming-tools-laragent.php
  • examples/example-structured-output.php
  • internalDocs/DataModel.md

Configuration Update:

  • Updated .github/ISSUE_TEMPLATE/config.yml to enable blank issues

📦 Dependencies

  • Bumped dependabot/fetch-metadata from 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

15 Jan 19:44
b2f5a4c

Choose a tag to compare

What's Changed

New Contributors

Full Changelog: 1.0.0...1.0.1

v1.0 - Release Notes 🚀

02 Jan 17:03
4a40c73

Choose a tag to compare

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 WeatherTool

This command:

  • Creates an AgentTools directory in your app/ 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 store

How 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-clear

The 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:

  1. Sliding Window (Default) - Removes oldest messages to stay within limits
  2. Summarization - Summarizes older messages using AI before removing them
  3. 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 migrate

learn 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;     // 72

DataModel supports:

  • Union types with oneOf schema 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 key

Learn 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

Full Documentation


⚠️ Breaking Changes

Please refer to the MIGRATION guide for detailed migration instructions. Key changes include:

  • Message::create() and Message::fromArray() removed - use typed factory methods
  • ToolResultMessage constructor now requires toolName parameter
  • ChatHistory::getMessages() now returns MessageArray instead of array
  • Config key chat_history renamed to history in provider config
  • contextWindowSize property renamed to truncationThreshold

📦 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...
Read more

0.8.0 - Native Gemini driver, ToolCall events, Arbitrary configs & more!

07 Nov 18:05
f71a4bc

Choose a tag to compare

🚀 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:

  • BeforeToolExecution and AfterToolExecution events now include the ToolCall object
  • Hook callbacks for beforeToolExecution() and afterToolExecution() 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 BeforeToolExecution event
  • ToolCall object now passed to AfterToolExecution event
  • ToolCall parameter added to tool execution hook callbacks
  • New tests for ToolCall presence in events

Changed

  • BeforeToolExecution event constructor signature updated
  • AfterToolExecution event constructor signature updated
  • processBeforeToolExecution() method signature in Hooks trait updated
  • processAfterToolExecution() 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-laravel from ^1.0 to ^1.1

🧪 Testing

New tests added:

  • ToolCall object presence in BeforeToolExecution event
  • ToolCall object presence in AfterToolExecution event
  • 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 Gemini
  • GeminiDriverTest.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

Full Changelog: 0.7.0...0.8.0

v0.7 - MCP & Events

22 Oct 16:32
722b9bb

Choose a tag to compare

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 response

Use 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-laravel package (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 :tools or :resources to select fetch type
  • Use only: and except: 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

  • AgentInitialized
  • ConversationStarted
  • ConversationEnded (contains final message; includes token usage via toArrayWithMeta())
  • ToolChanged (with added bool flag)
  • AgentCleared
  • EngineError (with exception)

🔹 Hook Events (Before/After)

  • BeforeReinjectingInstructions
  • BeforeSend / AfterSend
  • BeforeSaveHistory
  • BeforeResponse / AfterResponse
  • BeforeToolExecution / AfterToolExecution
  • BeforeStructuredOutput

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

01 Sep 15:00
8116dc2

Choose a tag to compare

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 null by default
    • Name property for Agent class, setting basename by default (static::class)
    • Various dependency updates and minor fixes

What's Changed

New Contributors

Full Changelog: 0.5.0...0.6.0

v0.5 - OpenAI‑compatible API endpoints, Multimodal inputs & more

27 Jul 13:53
34ccb1f

Choose a tag to compare

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() 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);

Changes 🛠️

  • 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

What's Changed

New Contributors

Full Changelog: 0.4.1...0.5.0