PHPackages                             jake142/querai - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. jake142/querai

ActiveLibrary[Database &amp; ORM](/categories/database)

jake142/querai
==============

Ask questions about your database in natural language. AI generates safe read-only SQL and formats the results.

v0.1.0(3w ago)10MITPHPPHP ^8.2

Since May 15Pushed 3w agoCompare

[ Source](https://github.com/jake142/querai)[ Packagist](https://packagist.org/packages/jake142/querai)[ RSS](/packages/jake142-querai/feed)WikiDiscussions master Synced 1w ago

READMEChangelogDependencies (10)Versions (2)Used By (0)

Querai
======

[](#querai)

**Let anyone on your team ask your database questions in plain language — safely.**

Querai is a Laravel package that turns natural language into **read-only SQL**, runs it against your database, and returns answers a human can actually use. No SQL client, no BI tool, no ad-hoc queries in production.

### What you can use it for

[](#what-you-can-use-it-for)

- **Admin &amp; support** — "How many orders did customer X place last month?" without pinging a developer.
- **Internal analytics** — quick counts, lists, and breakdowns from real data, not exports.
- **Multi-tenant SaaS** — one isolated client per customer DB, with its own AI config and domain hints.
- **Embedded in your app** — use the facade in controllers, jobs, or the built-in chat UI behind your existing auth.

### Why Querai

[](#why-querai)

- **Safe by default** — only `SELECT`, blocked DDL/DML, row limits, optional table exclusions.
- **Your schema, your rules** — domain hints in config teach the AI how *your* tables relate.
- **Production-ready** — retries on bad SQL, paginated answers for large result sets, conversation context.
- **Provider-agnostic** — OpenAI, Azure OpenAI, Gemini, or Anthropic.

Ask in Swedish, English, or any language — answers follow the question.

MIT licensed.

How it works
------------

[](#how-it-works)

```
You: "How many orders did we get last month?"
  → AI generates SELECT … (schema-aware)
  → SqlGuard validates (SELECT only, no DDL/DML)
  → Query runs on your DB (with row limit)
  → AI formats the answer for humans

```

If the SQL fails, Querai sends the error back to the AI and retries (configurable).

Without **domain hints**, the AI only sees table/column names and will often guess wrong JOINs (e.g. linking `orders` to the wrong `user_id`). Hints in `config/querai.php` tell it how your app actually works.

Requirements
------------

[](#requirements)

- PHP 8.2+
- Laravel 11 or 12
- A database connection Laravel already uses
- An AI API key (OpenAI, Azure OpenAI, Gemini, or Anthropic)

Installation
------------

[](#installation)

```
composer require jake142/querai
```

Publish config:

```
php artisan vendor:publish --tag=querai-config
```

**Configure domain hints** in `config/querai.php` — this is the most important step for accurate SQL:

```
'hints' => [
    'enabled' => true,
    'text' => answer;      // Human-readable reply
echo $result->sql;         // SQL that was executed
echo $result->rowCount;    // Number of rows returned
echo $result->attempts;    // Query attempts (retries on error)
```

### Conversation context

[](#conversation-context)

Pass the same `conversation_id` across calls to keep follow-up context (cached, TTL configurable):

```
$id = 'user-session-abc';

Querai::ask('How many orders last month?', $id);
Querai::ask('Break that down by country', $id);
```

### Large results — continue / load more

[](#large-results--continue--load-more)

When a query returns more rows than `response.threshold` (default 15), the full result set is cached and the answer is split into pages.

```
$result = Querai::ask('List all products with stock below 10');

if ($result->hasMore) {
    $next = Querai::continue($result->responseId);
    echo $next->answer; // next batch, human-readable
}
```

Response fields:

FieldDescription`response_id`Use with `continue()` while `has_more` is true`has_more`More pages available`page` / `total_pages`Current page of the formatted answer```
QUERAI_RESPONSE_CACHE_ENABLED=true
QUERAI_RESPONSE_THRESHOLD=15
QUERAI_RESPONSE_PAGE_SIZE=15
QUERAI_RESPONSE_TTL=60
```

UI: a **Load more** button appears automatically when `has_more` is true.

### Controller / API

[](#controller--api)

```
use Querai\Facades\Querai;

$result = Querai::ask($request->input('question'));

return response()->json($result->toArray());
```

### Domain hints

[](#domain-hints)

Hints are sent to the AI on every question, together with a compact schema. They prevent wrong guesses when column names are ambiguous (e.g. `users` vs `customers`, or several foreign keys to the same table).

**What to include**

TopicExample hintLookupsfind a customer by `users.email`Relationships`orders.user_id` → `users.id`Business terms"sale" = `orders` row with `status = 'completed'`Anti-patternsdo not use `legacy_orders` — data lives in `orders`**What to avoid**

- Long prose or full API docs — keep under `hints.max_chars` (default 8000)
- Secrets or PII in hints

**Per-tenant** (different DB + hints per customer):

```
$client = Querai::configure()
    ->namespace('tenant:'.$tenant->id)
    ->connection('tenant_'.$tenant->id)
    ->hints("customer: users.email\norders: orders.user_id = users.id")
    ->make();
```

After changing hints: `php artisan config:clear`. No need to re-run `querai:schema`.

Isolated clients (multi-tenant / per-customer DB)
-------------------------------------------------

[](#isolated-clients-multi-tenant--per-customer-db)

The default facade uses `config/querai.php` (one DB + one AI setup). For apps where **each customer has their own database**, build a dedicated client per tenant:

```
use Querai\Facades\Querai;

$client = Querai::configure()
    ->namespace('tenant:'.$tenant->id)   // isolates conversation/response/schema cache
    ->database([                          // register tenant DB for this request
        'driver' => 'mysql',
        'host' => $tenant->db_host,
        'database' => $tenant->db_name,
        'username' => $tenant->db_user,
        'password' => $tenant->db_password,
    ])
    ->ai([
        'provider' => 'openai',
        'openai' => [
            'api_key' => $tenant->openai_api_key ?? config('querai.ai.openai.api_key'),
            'model' => 'gpt-4o-mini',
        ],
    ])
    ->make();

$result = $client->ask('How many orders this week?');
```

**Use an existing Laravel connection** (if you already register tenant connections in your app):

```
$client = Querai::configure()
    ->namespace('tenant:'.$tenant->id)
    ->connection('tenant_'.$tenant->id)
    ->ai([/* ... */])
    ->make();
```

Store the client on a service, request attribute, or resolve per request — each instance only talks to **its** DB and **its** AI config. Cache keys are namespaced so tenants never share conversation or paginated results.

Optional per-tenant overrides:

```
->security(['excluded_tables' => ['internal_audit']])
->conversation(['ttl_minutes' => 30])
```

AI providers
------------

[](#ai-providers)

Set `QUERAI_AI_PROVIDER` to one of: `openai`, `azure`, `gemini`, `anthropic`.

ProviderEnv varsOpenAI`OPENAI_API_KEY`, optional `QUERAI_OPENAI_MODEL`, `OPENAI_BASE_URL`Azure`AZURE_OPENAI_API_KEY`, `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_DEPLOYMENT`Gemini`GEMINI_API_KEY`, optional `QUERAI_GEMINI_MODEL`Anthropic`ANTHROPIC_API_KEY`, optional `QUERAI_ANTHROPIC_MODEL`Security (important)
--------------------

[](#security-important)

Querai is designed for **read-only analytics**, not arbitrary SQL execution.

### Built-in protections

[](#built-in-protections)

1. **SqlGuard** – Only `SELECT` is allowed. Blocks `INSERT`, `UPDATE`, `DELETE`, `DROP`, `TRUNCATE`, `ALTER`, multiple statements, etc.
2. **Row limit** – Appends `LIMIT` if missing (`QUERAI_MAX_ROWS`, default 200).
3. **Excluded tables** – Tables never appear in schema or queries:

```
// config/querai.php
'security' => [
    'excluded_tables' => ['users', 'password_reset_tokens', 'sessions'],
    'excluded_column_patterns' => [
        '/password/i',
        '/secret/i',
        '/token/i',
    ],
],
```

### Recommended hardening

[](#recommended-hardening)

- Use a **read-only database user** for the Querai connection (MySQL: `GRANT SELECT ON db.* TO 'querai'@'%'`).
- Point `QUERAI_DB_CONNECTION` at a **replica** if you have one.
- Never expose the UI without authentication (see below).
- Add **rate limiting** on `/querai/ask` in your app if the UI or API is used heavily.
- Monitor AI API costs; each question uses at least two AI calls (SQL + formatting).

Admin UI
--------

[](#admin-ui)

A minimal chat UI is included at `/querai` (prefix configurable).

It is **not** public by default. Routes use Laravel middleware from config:

```
'ui' => [
    'enabled' => true,
    'prefix' => 'querai',
    'middleware' => ['web', 'auth'],  // your admin guard
    'gate' => 'viewQuerai',           // optional ability
],
```

### Protect with your existing auth

[](#protect-with-your-existing-auth)

**Option A – middleware only** (default `web` + `auth`):

```
'middleware' => ['web', 'auth:admin'],
```

**Option B – Gate / policy** (fine-grained):

In `App\Providers\AuthServiceProvider`:

```
Gate::define('viewQuerai', fn ($user) => $user->is_admin);
```

Set in config:

```
'gate' => 'viewQuerai',
```

The package registers a placeholder gate if missing; **override it in your app**.

Disable UI entirely:

```
QUERAI_UI_ENABLED=false
```

Publish views to customize:

```
php artisan vendor:publish --tag=querai-views
```

Configuration reference
-----------------------

[](#configuration-reference)

KeyDescription`connection`Laravel DB connection name`ai.provider``openai`, `azure`, `gemini`, `anthropic``security.excluded_tables`Tables hidden from AI`security.excluded_column_patterns`Regex patterns for sensitive columns`security.max_rows`Max rows per query`security.blocked_keywords`Extra blocked SQL keywords`retries.max_attempts`SQL generation retries on error`conversation.enabled`Multi-turn context cache`conversation.ttl_minutes`Conversation cache TTL`response.threshold`Row count before paginating answers`response.page_size`Rows per page sent to AI`response.ttl_minutes`Cached result session TTL`schema.cache`Cache extracted schema`schema.compact`Compact schema format (fewer tokens)`schema.path`JSON schema file path`hints.enabled`Send hints to AI (default true)`hints.text`Domain knowledge for SQL generation — **configure this**`hints.max_chars`Max characters sent to AI`ui.middleware`Route middleware stack`ui.gate`Optional Gate abilityArtisan commands
----------------

[](#artisan-commands)

CommandDescription`php artisan querai:schema`Extract DB schema to cache`php artisan querai:schema --fresh`Force re-extractionError handling &amp; retries
----------------------------

[](#error-handling--retries)

When a generated query fails (syntax, unknown column, etc.), Querai:

1. Captures the database error message
2. Asks the AI to fix the SQL
3. Retries up to `retries.max_attempts` (default 3)

Disable retries:

```
'retries' => ['enabled' => false],
```

Conversation cache
------------------

[](#conversation-cache)

When enabled, user/assistant messages are stored in Laravel cache so follow-up questions understand prior context.

```
QUERAI_CONVERSATION_ENABLED=true
QUERAI_CONVERSATION_TTL=60
QUERAI_CONVERSATION_CACHE_STORE=redis
```

Things to consider in production
--------------------------------

[](#things-to-consider-in-production)

TopicSuggestionDB accessRead-only DB user + optional replicaSecretsExclude `users`, tokens, payment tablesAuth`auth` middleware + Gate for UIRate limits`throttle` middleware on ask endpointSchema driftRun `querai:schema --fresh` after migrationsAI costsCache schema; limit UI to adminsComplianceLog questions/SQL if your policy requires audit trails (add in your app)License
-------

[](#license)

MIT. See [LICENSE](LICENSE).

###  Health Score

35

—

LowBetter than 77% of packages

Maintenance95

Actively maintained with recent releases

Popularity2

Limited adoption so far

Community2

Small or concentrated contributor base

Maturity36

Early-stage or recently created project

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

25d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/6a6a9a8792905b18b2f694e7c6ca4455081041b24e20f467ec77a94ed65885a4?d=identicon)[jake142](/maintainers/jake142)

---

Tags

laraveldatabaseaisqlopenaiGemininatural-language

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/jake142-querai/health.svg)

```
[![Health](https://phpackages.com/badges/jake142-querai/health.svg)](https://phpackages.com/packages/jake142-querai)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3325.1M337](/packages/psalm-plugin-laravel)[laravel/pulse

Laravel Pulse is a real-time application performance monitoring tool and dashboard for your Laravel application.

1.7k14.1M120](/packages/laravel-pulse)[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9732.3M121](/packages/roots-acorn)[laravel/cashier

Laravel Cashier provides an expressive, fluent interface to Stripe's subscription billing services.

2.5k28.4M134](/packages/laravel-cashier)[laravel/mcp

Rapidly build MCP servers for your Laravel applications.

76318.2M110](/packages/laravel-mcp)[api-platform/laravel

API Platform support for Laravel

59156.3k10](/packages/api-platform-laravel)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
