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.
strict: true and additionalProperties: false on input_schema, enabling guaranteed schema validation on tool names and inputs. This ensures Claude always returns correctly-typed tool parameters, eliminating the need for validation retries.$defs/definitions support in schema processing: The ensureAdditionalPropertiesFalse method now recursively processes $defs and definitions blocks in JSON schemas, ensuring schemas using $ref references are fully compliant with Claude's structured output requirements.refusal stop reason handling: The driver now handles Claude's refusal stop reason (returned when Claude declines a request for safety reasons). A descriptive exception is thrown instead of an "Unexpected stop reason" error.Provider config key "chat_history" replaced with "history"
Deprecated Agent property includeModelInChatSessionId and all related methods:
withoutModelInChatSessionId
withModelInChatSessionId
What Changed:
DriverConfig DTO internally instead of plain arraysLlmDriver constructor now accepts DriverConfig|array (backward compatible)LlmDriver::sendMessage() and sendMessageStreamed() accept DriverConfig|array for override settingsapiKey, apiUrl, maxCompletionTokens, etc.config/laragent.php still use snake_case (api_key, api_url) - mapped automaticallyMigration Required for Custom Drivers:
If you have custom LLM drivers extending LlmDriver, update the constructor:
// Before (v0.8)
class MyCustomDriver extends LlmDriver
{
public function __construct(array $settings = [])
{
$this->settings = $settings;
// custom initialization
}
}
// After (v1.0)
use LarAgent\Core\DTO\DriverConfig;
class MyCustomDriver extends LlmDriver
{
public function __construct(DriverConfig|array $settings = [])
{
parent::__construct($settings); // Required - initializes $this->driverConfig
// custom initialization
}
}
Accessing Configuration in Custom Drivers:
// Array access (still works - backward compatible)
$model = $this->getSettings()['model'];
$apiKey = $this->getSettings()['apiKey'];
// Typed access (new, recommended)
$model = $this->getDriverConfig()->model;
$apiKey = $this->getDriverConfig()->apiKey;
$temperature = $this->getDriverConfig()->temperature;
// Check if property is set
if ($this->getDriverConfig()->has('temperature')) {
// ...
}
// Get with default
$temp = $this->getDriverConfig()->get('temperature', 0.7);
// Access extra configs
$custom = $this->getDriverConfig()->getExtra('customOption');
No Changes Required If:
What Changed:
Message::create() static method removedMessage::fromArray() static method removedMessage class is now a pure factory with only typed factory methodsMigration Required:
// Before (v0.8)
$message = Message::create('user', 'Hello');
$message = Message::fromArray(['role' => 'user', 'content' => 'Hello']);
// After (v1.0)
$message = Message::user('Hello');
$message = Message::assistant('Hi there');
$message = Message::system('You are helpful');
$message = Message::toolResult($content, $toolCallId, $toolName);
What Changed:
ToolResultMessage constructor now requires toolName as third parameterfunction_response.name fieldMigration Required:
// Before (v0.8)
new ToolResultMessage($result, $toolCallId, $metadata);
// After (v1.0)
new ToolResultMessage($result, $toolCallId, $toolName, $metadata);
// Or use the Message facade (recommended)
Message::toolResult($result, $toolCallId, $toolName, $metadata);
What Changed:
ToolCall class now extends DataModel for proper serializationToolCallFunction DataModel for function detailstoArray() and fromArray() methodsMigration Required:
No changes needed if using ToolCall through the interface. Direct property access may need updates:
// Before (v0.8) - direct properties
$name = $toolCall->name;
$args = $toolCall->arguments;
// After (v1.0) - use interface methods (unchanged)
$name = $toolCall->getToolName();
$args = $toolCall->getArguments();
What Changed:
$id property (auto-generated UUID)$extras array for driver-specific/unknown fields$content removed from base class - each child defines its own typed contentTextContent, MessageContent, ToolResultContent)Migration Required:
// Accessing message ID (new feature)
$id = $message->getId(); // e.g., 'msg_abc123...'
// Accessing extras (new feature)
$message->setExtra('custom_field', 'value');
$value = $message->getExtra('custom_field');
What Changed:
The following methods are deprecated in all drivers:
toolCallsToMessage() → Use MessageFormatter::formatToolCallMessage()toolResultToMessage() → Use MessageFormatter::formatToolResultMessage()No immediate action required - methods still work but will be removed in future.
Replace Message::create() calls:
// Use typed factory methods instead
Message::user($content)
Message::assistant($content)
Message::system($content)
Message::developer($content)
Message::toolCall($toolCalls)
Message::toolResult($content, $toolCallId, $toolName)
Update ToolResultMessage instantiation:
new ToolResultMessage($result, $toolCallId, $toolName);
Update custom drivers (if you have custom LLM drivers):
MessageFormatter for your driverOpenAiMessageFormatter as referenceOpenAiResponsesDriver for the OpenAI Responses API (/v1/responses), required for newer models (e.g. gpt-5.4-mini) with reasoning_effort supportOpenAiResponsesMessageFormatter for Responses API input/output format conversionOpenAiResponsesCompatible driver for third-party providers offering a Responses-API-compatible endpointopenai_responses provider entry in default configDriverConfig DTO for type-safe driver configurationDriverConfig::fromArray(), ::wrap(), ::merge(), ::withExtra() methodsDriverConfig::set(), ::get(), ::has(), ::getExtra(), ::getExtras() methodsLlmDriver::getDriverConfig() for typed access to driver configurationMessageFormatter interface for driver message transformationOpenAiMessageFormatter, ClaudeMessageFormatter, GeminiMessageFormatter implementationsToolCall now extends DataModel with proper serializationToolCallFunction DataModel for nested function detailsToolCallArray DataModelArray for collections of tool callsToolResultContent::$tool_name property for Gemini compatibilityMessage::$id - unique identifier for each message (auto-generated)Message::$extras - storage for driver-specific/unknown fieldsMessage::getExtras(), setExtras(), getExtra(), setExtra(), hasExtra(), removeExtra()DataModelArray::findItem(), getItem(), setItem(), hasItem(), removeItem() methodsLlmDriver constructor now accepts DriverConfig|array (backward compatible)LlmDriver::sendMessage() and sendMessageStreamed() accept DriverConfig|array for override settingsLarAgent class now stores DriverConfig internally instead of individual propertiesMessage base class is now abstract with no $content propertyTextContent, MessageContent, etc.)Message facade is now a pure factory (static methods only)ToolCall extends DataModel instead of being a plain classMessageFormatter for message/response transformationToolResultMessage constructor requires toolName parameterLlmDriver::toolCallsToMessage() - Use MessageFormatter insteadLlmDriver::toolResultToMessage() - Use MessageFormatter insteadMessage::create() - Use typed factory methods (Message::user(), etc.)Message::fromArray() - Use specific message class fromArray() or MessageArrayMessage::fromJSON() - Use specific message class methodsWhat Changed:
BeforeToolExecution and AfterToolExecution events now include the ToolCall objectbeforeToolExecution() and afterToolExecution() receive additional parametersMigration Required:
Event Listeners:
// Before (v0.7)
Event::listen(BeforeToolExecution::class, function ($event) {
// $event->tool available
// $event->toolCall NOT available
});
// After (v0.8)
Event::listen(BeforeToolExecution::class, function ($event) {
// $event->tool available
// $event->toolCall NOW available - contains ID, name, arguments
Log::info('Tool call', [
'id' => $event->toolCall->getId(),
'tool' => $event->toolCall->getToolName(),
'args' => json_decode($event->toolCall->getArguments(), true),
]);
});
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
logger()->info("Executing: {$toolCall->getToolName()}", [
'call_id' => $toolCall->getId(),
]);
});
$agent->afterToolExecution(function($agent, $tool, $toolCall, &$result) {
// 4 parameters - $toolCall added
logger()->info("Completed: {$toolCall->getToolName()}", [
'call_id' => $toolCall->getId(),
'result' => $result,
]);
});
How can I help you explore Laravel packages today?