AI Architecture for Module Developers

How to add AI capabilities to your LaraDashboard module using the Capability/Action/Context pattern — register actions, provide context, and integrate with the AI command processor.

AI Architecture for Module Developers

LaraDashboard provides a modular AI system that any module can extend. Modules register capabilities (groups of AI actions), actions (executable AI operations), and context providers (domain data for the AI). The core AI engine handles provider communication, intent matching, and execution.

Architecture Overview

User Command ("Score this contact")
       │
       ▼
┌──────────────────┐
│ AiCommandProcessor│ ← Matches command to registered action
└──────┬───────────┘
       │
       ▼
┌──────────────────┐     ┌───────────────────┐
│  ActionRegistry   │ ←── │ Module registers   │
│  (finds action)   │     │ actions on boot    │
└──────┬───────────┘     └───────────────────┘
       │
       ▼
┌──────────────────┐     ┌───────────────────┐
│ ContextRegistry   │ ←── │ Module provides    │
│ (gathers context) │     │ domain context     │
└──────┬───────────┘     └───────────────────┘
       │
       ▼
┌──────────────────────────┐
│ AiContentGeneratorService │ ← Calls OpenAI/Claude/Gemini/Ollama
└──────┬───────────────────┘
       │
       ▼
┌──────────────────┐
│    AiResult       │ ← Returned to UI
└──────────────────┘

Core Contracts

AiCapabilityInterface

Groups related AI actions into a named capability with enable/disable control.

namespace App\Ai\Contracts;

interface AiCapabilityInterface
{
    public function name(): string;          // "CRM Contact Intelligence"
    public function description(): string;   // Human-readable description
    public function actions(): array;        // Array of AiActionInterface classes
    public function isEnabled(): bool;       // Feature flag check
}

AiActionInterface

A single executable AI operation with command patterns and permission checks.

namespace App\Ai\Contracts;

interface AiActionInterface
{
    public function name(): string;          // "contacts.score"
    public function description(): string;   // "Score a contact based on engagement"
    public function patterns(): array;       // Regex patterns to match commands
    public function payloadSchema(): array;  // Expected input structure
    public function requiredPermission(): ?string; // "contact.view"
    public function handle(array $payload): AiResult;
    public function handleWithProgress(array $payload, callable $onProgress): AiResult;
}

AiContextProviderInterface

Provides domain-specific data to enrich AI prompts.

namespace App\Ai\Contracts;

interface AiContextProviderInterface
{
    public function key(): string;           // "crm_contacts"
    public function context(): array;        // Aggregated domain data
}

Adding AI to Your Module

Step 1: Create a Context Provider

Context providers give the AI system knowledge about your module's domain.

<?php

declare(strict_types=1);

namespace Modules\Inventory\Ai\Context;

use App\Ai\Contracts\AiContextProviderInterface;
use Modules\Inventory\Models\Product;
use Modules\Inventory\Models\Warehouse;

class InventoryContextProvider implements AiContextProviderInterface
{
    public function key(): string
    {
        return 'inventory';
    }

    public function context(): array
    {
        return [
            'total_products' => Product::count(),
            'low_stock_count' => Product::where('quantity', '<', 10)->count(),
            'warehouses' => Warehouse::pluck('name')->toArray(),
            'categories' => Product::distinct('category')->pluck('category')->toArray(),
            'total_value' => Product::sum(\DB::raw('quantity * unit_price')),
        ];
    }
}

Step 2: Create an Action

Actions define what the AI can do. Each action matches user commands via regex patterns.

<?php

declare(strict_types=1);

namespace Modules\Inventory\Ai\Actions;

use App\Ai\Contracts\AiActionInterface;
use App\Ai\AiResult;
use App\Services\AiContentGeneratorService;
use Modules\Inventory\Models\Product;

class PredictRestockAction implements AiActionInterface
{
    public function __construct(
        private readonly AiContentGeneratorService $aiService,
    ) {}

    public function name(): string
    {
        return 'inventory.predict_restock';
    }

    public function description(): string
    {
        return 'Predict which products need restocking based on sales velocity and current stock levels.';
    }

    public function patterns(): array
    {
        return [
            '/predict\s+restock/i',
            '/which\s+products?\s+need\s+restock/i',
            '/low\s+stock\s+predict/i',
        ];
    }

    public function payloadSchema(): array
    {
        return [
            'days_ahead' => ['type' => 'integer', 'default' => 30],
            'warehouse_id' => ['type' => 'integer', 'optional' => true],
        ];
    }

    public function requiredPermission(): ?string
    {
        return 'product.view';
    }

    public function handle(array $payload): AiResult
    {
        return $this->handleWithProgress($payload, fn () => null);
    }

    public function handleWithProgress(array $payload, callable $onProgress): AiResult
    {
        $onProgress('Analyzing inventory levels...');

        $daysAhead = $payload['days_ahead'] ?? 30;

        $lowStockProducts = Product::query()
            ->where('quantity', '<', 20)
            ->with('salesHistory')
            ->get();

        if ($lowStockProducts->isEmpty()) {
            return AiResult::success('All products are well-stocked.', [
                'products_analyzed' => Product::count(),
                'recommendations' => [],
            ]);
        }

        $onProgress('Generating restock predictions...');

        $context = $lowStockProducts->map(fn ($p) => [
            'name' => $p->name,
            'current_stock' => $p->quantity,
            'avg_daily_sales' => $p->averageDailySales(),
            'days_of_stock' => $p->daysOfStockRemaining(),
        ])->toArray();

        $prompt = "Analyze these products and predict restock needs for the next {$daysAhead} days. "
            . "Return JSON with 'recommendations' array, each having 'product', 'urgency' (high/medium/low), "
            . "'restock_quantity', and 'reason'. Products:\n" . json_encode($context);

        $response = $this->aiService->generateContent($prompt);

        $recommendations = json_decode($response, true)['recommendations'] ?? [];

        return AiResult::success(
            "Analyzed {$lowStockProducts->count()} low-stock products.",
            [
                'recommendations' => $recommendations,
                'days_ahead' => $daysAhead,
            ]
        );
    }
}

Step 3: Create a Capability

Capabilities group related actions and control feature flags.

<?php

declare(strict_types=1);

namespace Modules\Inventory\Ai\Capabilities;

use App\Ai\Contracts\AiCapabilityInterface;
use Modules\Inventory\Ai\Actions\PredictRestockAction;
use Modules\Inventory\Ai\Actions\CategorizePr oductAction;
use Modules\Inventory\Ai\Actions\GenerateProductDescriptionAction;

class InventoryAiCapability implements AiCapabilityInterface
{
    public function name(): string
    {
        return 'Inventory Intelligence';
    }

    public function description(): string
    {
        return 'AI-powered inventory predictions, product categorization, and description generation.';
    }

    public function actions(): array
    {
        return [
            PredictRestockAction::class,
            CategorizeProductAction::class,
            GenerateProductDescriptionAction::class,
        ];
    }

    public function isEnabled(): bool
    {
        $settings = app('inventory.settings');

        return ($settings['ai_restock_prediction'] ?? false)
            || ($settings['ai_categorization'] ?? false)
            || ($settings['ai_descriptions'] ?? false);
    }
}

Step 4: Register in Service Provider

<?php

declare(strict_types=1);

namespace Modules\Inventory\Providers;

use Illuminate\Support\ServiceProvider;
use App\Ai\Registry\CapabilityRegistry;
use App\Ai\Registry\ContextRegistry;
use Modules\Inventory\Ai\Capabilities\InventoryAiCapability;
use Modules\Inventory\Ai\Context\InventoryContextProvider;

class InventoryServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        // ... other registrations ...

        $this->registerAiCapabilities();
    }

    protected function registerAiCapabilities(): void
    {
        // Check if core AI system exists (graceful fallback)
        if (! $this->app->bound(CapabilityRegistry::class)) {
            return;
        }

        $this->app->booted(function () {
            $this->app->make(CapabilityRegistry::class)
                ->register(InventoryAiCapability::class);

            $this->app->make(ContextRegistry::class)
                ->register(InventoryContextProvider::class);
        });
    }
}

Directory Structure

Follow this structure in your module:

modules/{Module}/app/Ai/
├── Capabilities/
│   └── {Module}AiCapability.php       ← Groups actions, feature flags
├── Context/
│   └── {Resource}ContextProvider.php   ← Provides domain data
└── Actions/
    └── {Resource}/
        ├── ScoreAction.php            ← Individual AI operations
        ├── PredictAction.php
        └── SummarizeAction.php

AiResult

Actions return AiResult objects:

use App\Ai\AiResult;

// Success
return AiResult::success('Operation completed.', ['key' => 'value']);

// Partial success (some steps completed)
return AiResult::partial('Completed 3 of 5 items.', $data, $completedSteps);

// Failure
return AiResult::failed('Could not process: insufficient data.');

AI Providers

LaraDashboard supports multiple AI providers via AiContentGeneratorService:

Provider Default Model Env Variable Best For
OpenAI gpt-4o-mini OPENAI_API_KEY General purpose, image generation
Claude claude-3-haiku ANTHROPIC_API_KEY Long-form content, analysis
Gemini gemini-2.0-flash GEMINI_API_KEY Fast responses
Ollama llama3.2 OLLAMA_BASE_URL Local/private, no API key needed

Using the Service

use App\Services\AiContentGeneratorService;

class MyService
{
    public function __construct(
        private readonly AiContentGeneratorService $ai,
    ) {}

    public function analyze(string $data): ?string
    {
        return $this->ai->generateContent(
            "Analyze this data and provide insights:\n{$data}"
        );
    }

    public function generateJson(string $prompt): ?array
    {
        $response = $this->ai->generateContent($prompt);

        // Service handles JSON parsing from markdown code blocks
        return json_decode($response, true);
    }
}

Provider Configuration

Providers are configured in config/ai.php and can be overridden via database settings (ai_* keys in the settings table):

// config/ai.php
return [
    'default_provider' => env('AI_DEFAULT_PROVIDER', 'openai'),

    'openai' => [
        'api_key' => env('OPENAI_API_KEY'),
        'model' => env('OPENAI_MODEL', 'gpt-4o-mini'),
        'max_tokens' => (int) env('OPENAI_MAX_TOKENS', 2000),
    ],

    'anthropic' => [
        'api_key' => env('ANTHROPIC_API_KEY'),
        'model' => env('ANTHROPIC_MODEL', 'claude-3-haiku-20240307'),
    ],

    'gemini' => [
        'api_key' => env('GEMINI_API_KEY'),
        'model' => env('GEMINI_MODEL', 'gemini-2.0-flash'),
    ],

    'ollama' => [
        'base_url' => env('OLLAMA_BASE_URL', 'http://localhost:11434'),
        'model' => env('OLLAMA_MODEL', 'llama3.2'),
    ],
];

CRM Module AI — Reference Implementation

The CRM module is the most complete AI integration. Study it as the reference:

Capabilities (6)

Capability Actions Purpose
ContactAiCapability 5 Scoring, enrichment, duplicates, segmentation, summaries
DealAiCapability 5 Scoring, summaries, pipeline forecasting, stale detection
TicketAiCapability 3 Categorization, sentiment analysis, reply suggestions
CampaignAiCapability 2 Subject line generation, email body improvement
InvoiceAiCapability 3 Payment prediction, health scoring, follow-up emails
DashboardAiCapability 3 Daily digest, dashboard insights, follow-up suggestions

Context Providers (5)

Provider Data Provided
ContactContextProvider Contact stats, types, groups, tags, recent activity
DealContextProvider Pipeline metrics, deal stages, revenue data
TicketContextProvider Support queue stats, categories, response times
CampaignContextProvider Campaign performance, open/click rates
InvoiceContextProvider Revenue metrics, payment patterns

CrmAiService Pattern

The CRM wraps the core AiContentGeneratorService with domain-specific prompt building:

class CrmAiService
{
    public function __construct(
        private readonly AiContentGeneratorService $aiService,
    ) {}

    public function scoreContact(Contact $contact): ?array
    {
        $prompt = $this->buildContactScoringPrompt($contact);
        $response = $this->aiService->generateContent($prompt);

        return $this->parseJsonResponse($response);
    }

    private function buildContactScoringPrompt(Contact $contact): string
    {
        return "Score this contact on a scale of 0-100 based on engagement...\n"
            . json_encode($contact->toArray());
    }
}

Progress Streaming

For long-running AI operations, use progress callbacks:

public function handleWithProgress(array $payload, callable $onProgress): AiResult
{
    $onProgress('Step 1: Loading data...');
    $data = $this->loadData($payload);

    $onProgress('Step 2: Analyzing with AI...');
    $analysis = $this->ai->generateContent($prompt);

    $onProgress('Step 3: Processing results...');
    $results = $this->processResults($analysis);

    return AiResult::success('Analysis complete.', $results);
}

Command Logging

All AI command executions are logged to the ai_command_logs table:

Column Type Description
user_id FK Who ran the command
command string Original user input
intent JSON Parsed intent with confidence
plan JSON Execution plan
result JSON AiResult data
status string success / partial / failed
execution_time_ms integer How long it took

Best Practices

  1. Always check isEnabled() — Respect feature flags in capabilities
  2. Use progress callbacks — Give users feedback during long operations
  3. Parse JSON carefully — AI responses may include markdown code blocks; the service handles this
  4. Handle failures gracefully — Return AiResult::failed() with a helpful message
  5. Check permissions — Set requiredPermission() to enforce authorization
  6. Keep prompts focused — Smaller, specific prompts produce better results than large vague ones
  7. Provide rich context — Context providers help the AI understand the domain
  8. Check if AI system exists — Use $this->app->bound(CapabilityRegistry::class) before registering

Next Steps

/