PHP code example of jtmcc / ai-query-builder

1. Go to this page and download the library: Download jtmcc/ai-query-builder library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

jtmcc / ai-query-builder example snippets


namespace App\AiQueries;

use App\Models\Invoice;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\JoinClause;
use JTMcC\AiQueryBuilder\Schema\ColumnDefinition;
use JTMcC\AiQueryBuilder\Schema\Contracts\DefinesQuerySchema;
use JTMcC\AiQueryBuilder\Schema\PivotDefinition;
use JTMcC\AiQueryBuilder\Schema\RelationDefinition;
use JTMcC\AiQueryBuilder\Schema\ResourceSchema;

final class InvoiceQuerySchema implements DefinesQuerySchema
{
    public function define(ResourceSchema $schema): ResourceSchema
    {
        return $schema
            ->for(Invoice::class)
            ->name('invoices')
            ->describe('Customer invoices, one row per invoice.')

            ->column('id', fn (ColumnDefinition $c) => $c->as('invoice_id')->sortable())

            // The value type comes from the model's casts. `issued_at` is cast
            // to `date`, so filter values are checked against that and the
            // column earns `within` for free.
            ->column('issued_at', fn (ColumnDefinition $c) => $c
                ->describe('Date the invoice was issued')
                ->filterable(['=', '>', '<', '>=', '<=', 'between'])
                ->groupableBy(['day', 'month', 'quarter', 'year'])
                ->sortable())

            // Nothing casts this one, so the type is declared. Without it,
            // nothing checks what an agent puts in a filter.
            ->column('due_at', fn (ColumnDefinition $c) => $c
                ->typed('datetime')
                ->filterable(['>', '<', 'between', 'is_null'])
                ->sortable())

            ->column('total', fn (ColumnDefinition $c) => $c
                ->describe('Invoice total, tax               ->filterable(['=', 'in'])
                        ->groupable())))

            // belongsToMany, with columns on the pivot table and a condition
            // that constrains the link rather than the tag.
            ->relation('tags', fn (RelationDefinition $t) => $t
                ->column('name', fn (ColumnDefinition $c) => $c
                    ->filterable(['=', 'in'])
                    ->groupable())
                ->pivot(fn (PivotDefinition $p) => $p
                    ->column('assigned_at', fn (ColumnDefinition $c) => $c
                        ->typed('date')
                        ->filterable(['=', 'between'])))
                ->alwaysPivotScope(fn (JoinClause $join, ?Authenticatable $user, string $alias) => $join
                    ->whereNull("{$alias}.revoked_at")))

            // Applied to every query, before anything in the plan.
            ->alwaysScope(fn (Builder $query, ?Authenticatable $user) => $query
                ->where('invoices.tenant_id', $user?->tenant_id))

            ->defaultLimit(100)
            ->maxLimit(1000)
            ->maxRelationDepth(2)
            ->maxFilterDepth(5)
            ->maxFilterNodes(50);
    }
}

// config/ai-query-builder.php
'resources' => [
    App\AiQueries\InvoiceQuerySchema::class,
],

use JTMcC\AiQueryBuilder\Ai\QueryDataTool;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Promptable;

class InvoiceAnalystAgent implements Agent, HasTools
{
    use Promptable;

    public function instructions(): string
    {
        return 'You answer billing questions. Use the query_invoices tool to read the '
            .'invoices resource — never estimate an invoice figure from memory.';
    }

    public function tools(): iterable
    {
        return [new QueryDataTool('invoices', auth()->user())];
    }
}

$response = InvoiceAnalystAgent::make()->prompt('What did we invoice last month?');

// config/ai-query-builder.php
'mcp' => [
    'resources' => ['invoices'],
],

// routes/ai.php
use JTMcC\AiQueryBuilder\Mcp\QueryServer;
use Laravel\Mcp\Facades\Mcp;

Mcp::oauthRoutes();

Mcp::web('/mcp/query', QueryServer::class)
    ->middleware('auth:api');

->relation('lines', fn (RelationDefinition $r) => $r
    ->column('quantity', fn (ColumnDefinition $c) => $c->aggregatable(['sum']))
    ->alwaysScope(fn (JoinClause $join, ?Authenticatable $user, string $alias) => $join
        ->where("{$alias}.quantity", '>', 0)))

->relation('tags', fn (RelationDefinition $t) => $t
    ->column('name', fn (ColumnDefinition $c) => $c->filterable(['=', 'in'])->groupable()))

->relation('tags', fn (RelationDefinition $t) => $t
    ->column('name')
    ->alwaysPivotScope(fn (JoinClause $join, ?Authenticatable $user, string $alias) => $join
        ->whereNull("{$alias}.revoked_at")))

->relation('tags', fn (RelationDefinition $t) => $t
    ->column('name', fn (ColumnDefinition $c) => $c->filterable(['=', 'in']))
    ->pivot(fn (PivotDefinition $p) => $p
        ->column('assigned_at', fn (ColumnDefinition $c) => $c
            ->typed('date')
            ->filterable(['=', 'between']))))

->relation('lines', fn (RelationDefinition $r) => $r
    ->withTrashed()
    ->column('quantity', fn (ColumnDefinition $c) => $c->aggregatable(['sum'])))

use JTMcC\AiQueryBuilder\Execution\QueryRunner;

$result = app(QueryRunner::class)
    ->as($user)
    ->connection('analytics_readonly')  // optional read-only replica
    ->timeout(5_000)                    // milliseconds
    ->maxRows(1_000)
    ->withPrompt($prompt)               // carried into audit events
    ->run($plan);

$result->rows;       // list of associative arrays
$result->columns;    // per-alias unit and description metadata
$result->truncated;  // true when the cap was hit

use JTMcC\AiQueryBuilder\Ai\QueryDataTool;

class AnalystAgent implements Agent, HasTools
{
    use Promptable;

    public function tools(): iterable
    {
        return [new QueryDataTool('invoices', auth()->user())];
    }
}

use JTMcC\AiQueryBuilder\Facades\AiQueryBuilder;

public function tools(): iterable
{
    return AiQueryBuilder::tools(['invoices', 'customers'], auth()->user());
}

new QueryDataTool('invoices', auth()->user(), detail: PlanSchemaDetail::Generic)

use Laravel\Ai\Contracts\HasProviderOptions;
use Laravel\Ai\Enums\Lab;

class AnalystAgent implements Agent, HasProviderOptions, HasTools
{
    public function providerOptions(Lab|string $provider): array
    {
        return match ($provider) {
            Lab::Anthropic => ['cache_control' => ['type' => 'ephemeral']],
            default => [],
        };
    }
}

str_replace('{NOW}', now()->toDateTimeString(), $prompt)   // invalidates every request
str_replace('{TODAY}', now()->toDateString(), $prompt)     // stable for a day

$plan = json_decode($whateverYourModelReturned, true);
$rows = app(QueryRunner::class)->as($user)->run($plan);

use JTMcC\AiQueryBuilder\Contract\SchemaContract;
use JTMcC\AiQueryBuilder\Schema\SchemaRegistry;

$contract = SchemaContract::for(app(SchemaRegistry::class)->get('invoices'), $user);

$contract->toPrompt();      // data dictionary text
$contract->toJsonSchema();  // JSON Schema for the plan

// config/ai-query-builder.php
'routes' => [
    'enabled' => true,
    'prefix' => 'ai-query',
    'middleware' => ['api', 'auth'],
],

$plan = $agent->generatePlan('revenue by status over the last 30 days');

SavedQuery::create(['user_id' => $user->id, 'name' => 'Revenue by status', 'plan' => $plan]);

// config/ai-query-builder.php
'mcp' => [
    'resources' => ['invoices'],
],

// routes/ai.php
use JTMcC\AiQueryBuilder\Mcp\QueryServer;
use Laravel\Mcp\Facades\Mcp;

Mcp::oauthRoutes();

Mcp::web('/mcp/query', QueryServer::class)
    ->middleware('auth:api');

use JTMcC\AiQueryBuilder\Mcp\QueryServer;

class AdminQueryServer extends QueryServer
{
    protected array|string $exposes = ['invoices', 'customers', 'audit_logs'];
}

// routes/ai.php
Mcp::web('/mcp/admin', AdminQueryServer::class)->middleware(['auth:api', 'can:view-admin']);

use Illuminate\Contracts\Auth\Authenticatable;
use JTMcC\AiQueryBuilder\Mcp\Contracts\ResolvesExposedResources;

class TenantResources implements ResolvesExposedResources
{
    public function resources(?Authenticatable $user): array
    {
        return $user?->isAdmin() ? ['invoices', 'customers'] : ['invoices'];
    }
}

$response = QueryServer::actingAs($user)->tool(QueryResourcesTool::class, [
    'resource' => 'invoices',
    'select' => [['column' => 'total', 'function' => 'sum', 'as' => 'revenue']],
]);

$response->assertOk()->assertSee('revenue');

Event::listen(QueryPlanRejected::class, function ($event) {
    Log::channel('ai-queries')->info('rejected', [
        'resource' => $event->resource,
        'codes' => $event->errorCodes(),
    ]);
});
bash
php artisan ai-query:make-schema "App\Models\Invoice"
shell
php artisan mcp:inspector mcp/query