QFQueryForge Create your config file here

QueryForge

Replace the whole filter panel with one sentence.

Your users stop translating what they want into dropdowns, checkboxes and date pickers — they just say it, in their own words. QueryForge turns that into a query you can trust.

The model never writes the query. It fills in a typed Query AST your config constrains, and deterministic Go compiles that to whatever database you run — Postgres SQL and MongoDB today, and a new backend is a generator, not a rewrite.

GitHub Go Reference License

See it replace a filter panel → Configuration reference → Build a config → View on GitHub

Read-only by design. Every output is a SELECT or a find. There is no operator or config option that can write, update, or delete — and the library never connects to your database. It hands you the query; running it stays yours.

What it does

A user asks a question in their own words:

"orders over 500 dollars in the last 30 days that were not cancelled, newest first, top 20"

You get a parameterized query you can trust — values bound, never pasted in:

SELECT status, created_at, amount, customer_name FROM orders
WHERE (status <> $1 AND created_at >= $2 AND amount > $3)
ORDER BY created_at DESC LIMIT 20

args: ["CANCELLED", "2026-06-29T08:49:06Z", 500]

The same question, against MongoDB, compiled from the identical intermediate representation:

{ amount: {$gt: 500}, createdAt: {$gte: "2026-06-29T08:49:06Z"}, status: {$ne: "CANCELLED"} }

Why not just ask an LLM for SQL?

Because you cannot check what comes back. Ask a model for SQL directly and it can invent a column, invent a table, quietly widen a filter, or hand you a statement that is only probably right — and you have no way to tell before you run it.

QueryForge never lets the model near a query string. The model fills in a typed form — the Query AST — constrained to a vocabulary your config registers. Ordinary deterministic Go does everything after that.

Natural language  →  [ AI planner ]  →  Query AST  →  [ generators ]  →  SQL / Mongo
   (unbounded)        model + config     (typed)       pure code, no AI

Everything that must be guaranteed lives on the right-hand side, where it can be tested offline.

Invented a field?

Rejected by the validator before anything compiles, with "did you mean" suggestions.

SQL injection?

The model emits structure, never a string. Values are bound; only config-supplied identifiers reach the statement.

Asked to delete something?

Not expressible. The AST has no mutation node, so the guarantee holds by construction.

Asked about a hidden field?

returnable: false is enforced on the default projection too, not only explicit select.

Asked something impossible?

You get a typed refusal, not a plausible query built on a lookalike field.

Asked for another tenant's rows?

Scope filters are AND-ed on after the model has answered — it never learns the field exists.

Changed your mind on models?

Gemini, Groq, Anthropic, local Ollama — a config change, never a code change. Fallback chains included.

Install

go get github.com/awsaman-ai/queryforge
cfg, _ := qf.LoadConfig("orders.config.json")
engine := qf.New(cfg)

res, err := engine.Translate(ctx, "cancelled orders over 200 dollars", "sql", nil)
fmt.Println(res.Query.SQL)   // SELECT … WHERE (status = $1 AND amount > $2) …
fmt.Println(res.Query.Args)  // [CANCELLED 200]
fmt.Println(res.Explain)     // plain-English readback of what it understood

Core has no third-party dependencies — standard library only.

That last argument is the scope — filters your application imposes on every query, whatever the user asked. Pass nil for none, or a map for multi-tenancy:

res, err := engine.Translate(ctx, "cancelled orders over 200 dollars", "sql", qf.Scope{
    "subscriptionId": session.SubscriptionID,
    "userId":         session.UserID,
})
// … WHERE (subscriptionId = $1 AND userId = $2 AND status = $3 AND amount > $4)

Next

How well does the model actually understand?

The deterministic half of the pipeline — AST → validate → generate — is covered by this library's own test suite, with no API key and no network. The half that cannot be covered that way is comprehension: whether the model reads a real sentence the way your user meant it. That is a statistical property of a remote service, so it needs a corpus and a pass rate rather than an assertion.

qfeval is a separate tool for exactly that. You give it a CSV of sentences and the queries they should compile to; it runs them through the real engine and grades each one — matched exactly, matched in a different spelling, correctly refused, or wrong. On the shipped 25-case example corpus, gemini-3.1-flash-lite scores 24 / 25, and the single failure is a genuinely ambiguous sentence rather than a misread one. Point it at your own config and your own sentences before you decide which model to pay for.

Apache-2.0 licensed. Phase 1: SQL (Postgres dialect) and MongoDB.