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);
}
}
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');
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
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
$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'];
}
}