parselynk/laravel-ai-attributes
Bring AI-ready metadata to your Laravel code with native PHP attributes. Annotate classes and methods to describe their intent and expose structured hints for AI agents, tools, and LLM integrations—declarative, discoverable, and colocated with your code.
composer require parselynk/laravel-ai-attributes:^2.0
This pulls in laravel/ai automatically. Confirm you have the required runtime: PHP 8.3+, Laravel 12.62+ or 13.x. Laravel 11 is no longer supported.
php artisan vendor:publish --provider="Laravel\AI\AIServiceProvider"
Set your provider and credentials in .env:
AI_ATTRIBUTES_PROVIDER=openai
OPENAI_API_KEY=sk-...
use Parselynk\AiAttributes\Traits\HasAIAttributes;
class Article extends Model
{
use HasAIAttributes;
protected array $aiAttributes = [
'ai_summary' => [
'provider' => 'openai',
'prompt' => 'Summarize this article in two sentences.',
'format' => 'text',
],
];
}
$article = Article::find(1);
echo $article->ai_summary;
The first access generates and persists the value. Subsequent accesses for the same content hash return the cached result.
Where to look first: start with the package README for $aiAttributes examples, then inspect laravel/ai’s config/ai.php for provider/model defaults. The first practical use case is summarising or classifying existing Eloquent content without writing custom prompt plumbing.
Keep AI-generated fields beside the model they belong to. The package’s main value is that $article->ai_summary, $product->ai_category, and $ticket->ai_sentiment behave like normal Eloquent attributes:
protected array $aiAttributes = [
'ai_summary' => [
'provider' => 'anthropic',
'prompt' => 'Summarize the following article for a technical audience.',
'format' => 'text',
],
'ai_tags' => [
'provider' => 'openai',
'prompt' => 'Return 5 short tags for this article as JSON.',
'format' => 'json',
],
'ai_is_actionable' => [
'provider' => 'openai',
'prompt' => 'Is this support ticket actionable? Answer true or false.',
'format' => 'bool',
],
];
Formats handle casting automatically:
text returns a string.json returns a decoded array/object.number returns a numeric value.bool returns a boolean.This lets you use the values directly in Blade, API resources, filters, and validation without extra decoding code.
Direct property access is best for short prompts and low-latency use cases:
$summary = $article->ai_summary;
$tags = $article->ai_tags; // already an array
Because generation happens on first access, avoid using this inside large collection loops unless you intentionally want to generate for every model. For existing records, consider an explicit backfill or regeneration pass.
For longer content, batch jobs, or user-facing requests where latency matters, use the queued job:
use Parselynk\AiAttributes\Jobs\GenerateAiAttributeJob;
GenerateAiAttributeJob::dispatch($article, 'ai_summary');
This is the safer default for production workflows. Generate asynchronously, then expose only populated attributes. A common pattern is to dispatch the job after model creation/update and display the attribute only once it exists.
Use the peek helpers when you need to avoid accidental generation:
if ($article->hasAiAttribute('ai_summary')) {
echo $article->ai_summary;
}
$summary = $article->aiAttributeOrNull('ai_summary');
This is especially useful in API resources and Blade views, where merely accessing $article->ai_summary would otherwise trigger a remote call.
Personas are useful when the same model field needs different tone or audience in different contexts:
$marketingSummary = $article->aiPersona('marketing')->ai_summary;
$technicalSummary = $article->aiPersona('technical')->ai_summary;
Treat personas as an extension point for context-specific generation rather than hard-coding multiple near-identical attributes. Be explicit about whether the persona affects cache identity; if the same content and attribute produce different outputs under different personas, validate that the generated values are stored/cached as expected in your workflow.
When prompts or upstream content change, use the command to regenerate:
php artisan ai:regenerate
Check the command’s options in the package for targeting models or attributes. In day-to-day work, this is the tool for backfills and for recovering from stale generated values after a prompt change.
Because the package delegates provider calls to laravel/ai, tests should use AnonymousAgent::fake():
use Laravel\AI\AnonymousAgent;
AnonymousAgent::fake([
'summary response',
]);
$article->ai_summary; // returns faked response
Assert that your code reads the attribute correctly, handles cached values, and dispatches queued jobs. Avoid testing provider-specific HTTP details; that coverage now belongs to laravel/ai.
This version is a rebase onto laravel/ai, not a drop-in patch. Upgrade work includes:
config/ai.php published by laravel/ai, not config/ai-attributes.php.driver to provider in every $aiAttributes definition.max_tokens and temperature from attribute definitions. Those concerns now belong to laravel/ai.AI_ATTRIBUTES_PROVIDER in .env.Do a project-wide search for driver, max_tokens, temperature, and ai-attributes.php before upgrading.
The magic property is convenient, but it is not lazy in the “cheap” sense. If the attribute has not been generated or cached, $article->ai_summary will trigger generation. In Blade loops, API resources, and serialization, prefer:
$article->aiAttributeOrNull('ai_summary')
or check hasAiAttribute() first. This avoids surprising latency and cost.
Same input should mean free repeat calls. That is excellent for cost control, but it also means prompt or model changes may not invalidate cached values unless part of the hashed input changes. When changing prompts or personas, explicitly regenerate the affected attributes with ai:regenerate or a targeted job dispatch.
If you dispatch GenerateAiAttributeJob, the attribute may not exist immediately after the request. Design your UI and API around that:
aiAttributeOrNull() until generated.Do not assume an attribute is populated just because a record was saved.
json format gives you decoded values, but the model may still return malformed JSON depending on the prompt. Keep prompts explicit about output shape. For bool and number, validate that provider responses match expectations in your own application tests, since the package cannot guarantee model output.
The package now supports all providers available through laravel/ai, including OpenAI, Anthropic, Gemini, Groq, Mistral, DeepSeek, xAI, Ollama, Azure OpenAI, Cohere, OpenRouter, Jina, VoyageAI, and ElevenLabs. You can set a global default with AI_ATTRIBUTES_PROVIDER, but per-attribute provider overrides are useful when one attribute needs a cheaper/faster model and another needs higher quality.
The practical extension points are the $aiAttributes definitions, runtime personas, queued jobs, and the ai:regenerate command. If you need custom provider behaviour, configure it through laravel/ai rather than expecting the old package-level driver system. The old AIManager, AIDriver contract, and HttpRetrier are gone.
When an attribute is not behaving as expected:
$aiAttributes.driver to provider.config/ai.php has valid provider credentials.hasAiAttribute().How can I help you explore Laravel packages today?