beyondcode/laravel-ask-database
Ask your database questions in plain English. BeyondCode's package uses AI to translate natural language into SQL queries and returns the results, making data exploration easier without writing SQL yourself.
Install and publish (expect friction first).
composer require beyondcode/laravel-ask-database --with-all-dependencies
php artisan vendor:publish --provider="BeyondCode\AskDatabase\AskDatabaseServiceProvider"
The package was last released in February 2023, so the very first thing you do on a modern Laravel app is lose a fight with Composer over the openai-php/laravel / openai-php/client constraint and the php/laravel/framework pins. Budget an hour, not five minutes. If --with-all-dependencies can't resolve against your framework version, stop and fork: composer require beyondcode/laravel-ask-database --no-update then vendor it, or clone into packages/ with a path repository. Do not spend a day forcing a resolution on an archived package.
Configure a disposable target.
Set OPENAI_API_KEY in .env, and immediately point the package at a read-only connection — not your default one. Add a dedicated connection in config/database.php (a replica, a local docker DB, or a staging snapshot) backed by a MySQL/Postgres user with SELECT only. Then set that connection in config/ask-database.php. This is the first thing you do, not a hardening follow-up; the package executes model-authored SQL directly.
First query.
php artisan ask-database "How many users signed up last month?"
Where to look first. Read the source in this order: the service provider, the config file, the console command, and whatever class builds the schema summary and prompt. The whole loop — introspect → prompt → completion → DB::select() → output — is small enough to read end to end in ten minutes, and you need to know exactly what leaves your machine (table names, column names, possibly sample values, plus your question) and what comes back (raw SQL that gets executed).
First real use case. Point it at a non-production schema your team already understands and use it as a schema navigation aid: "which tables reference orders?", "what's the shape of the subscriptions table?". That establishes a baseline for accuracy before anyone trusts it with a business question.
Treat the command as a reference, not an API. The package ships one Artisan command and no first-class service facade you should depend on. The durable pattern is to keep the command for ad-hoc exploration and wrap the prompt→SQL→execute loop in your own service class if you need it programmatically — it's maybe 60 lines, and owning it means you control the allowlist, the row cap, and the logging.
final class NlQuery
{
public function ask(string $question): array
{
$sql = $this->toSql($question); // your prompt builder + client
Log::info('nlq', ['q' => $question, 'sql' => $sql, 'user' => auth()->id()]);
$this->assertAllowed($sql); // SELECT-only, allowlisted tables
return DB::connection('askdb')
->select($this->withLimit($sql, 500));
}
}
Scope the schema you expose. Don't hand the model your whole database. Two effective levers:
reporting schema of views with readable column names and comments, and point the connection at it. The model gets a smaller, better-named surface, your token bill drops, and accuracy goes up.users, sessions, jobs, failed_jobs, personal_access_tokens, anything with PII.Establish a "show me the SQL" norm. Make the generated statement visible before it runs — add a --dry-run flag to the command (or a dd($sql) in your fork) and default to using it when someone asks a question they actually care about. The dominant failure mode here is a plausible-but-wrong JOIN or GROUP BY, not an exception; the only cheap defense is reading the SQL.
Chain it into existing tooling. Realistic day-to-day integrations:
Artisan::call('ask-database', [...]) and posts the table back, so support doesn't file a ticket for one number.Log everything, in a table. Prompt, generated SQL, row count, latency, token usage, user. It's the only way to answer "who asked what, and was it right?" during a pilot, and it's the artifact you show during a security review.
Cache the schema summary, invalidate on deploy. Re-introspecting on every call is wasteful when the schema changes weekly. Key a cached schema blob on the connection name and flush it in your deploy/migrate hook.
composer update three months from now will otherwise silently break you when the pinned OpenAI SDK or model name is retired.text-davinci-003-era default will 404 from OpenAI. Overriding the model should be a config value; if it isn't, that's your first patch.LIMIT, no timeout. All of it is yours. Do all four:
SELECT-only DB user,INSERT|UPDATE|DELETE|DROP|ALTER|TRUNCATE|GRANT|;,LIMIT,SET SESSION max_execution_time / statement_timeout.OPENAI_API_KEY in .env only; never in the published config committed to the repo, and never in a multi-tenant config cache.-v for full output, tail storage/logs/laravel.log, and temporarily dump the exact prompt string — 90% of "it gave a dumb answer" cases are a truncated, noisy, or stale schema summary.How can I help you explore Laravel packages today?