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

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.

Get started

Pick how you want to call it. Every one of these needs the same two things: a config file describing your data, and your model API key in the environment. Nothing else — no server, no Docker, no database connection.

MCP Claude Desktop · Cursor

Install the server:

go install github.com/awsaman-ai/queryforge_mcp@latest

Add it to claude_desktop_config.json:

{
  "mcpServers": {
    "queryforge": {
      "command": "queryforge-mcp",
      "args": ["--config", "orders.config.json"]
    }
  }
}

Restart Claude and ask "cancelled orders over $200" — it comes back as a validated query. Docs →

Go the library

Install it:

go get github.com/awsaman-ai/queryforge

Ask a question:

cfg, _ := qf.LoadConfig("orders.config.json")
engine := qf.New(cfg)

res, _ := engine.Translate(ctx,
    "cancelled orders over 200 dollars", "sql", nil)

fmt.Println(res.Query.SQL)   // SELECT … WHERE (status = $1 …)
fmt.Println(res.Query.Args)  // [CANCELLED 200]

No third-party dependencies — standard library only. API docs →

Java Maven · Java 11+

Add two dependencies — the classes, and the engine binary for the platform you run on. Set queryforge.version to the latest on Maven Central:

<properties>
  <queryforge.version>LATEST</queryforge.version>
</properties>

<dependency>
  <groupId>io.github.awsaman-ai</groupId>
  <artifactId>queryforge</artifactId>
  <version>${queryforge.version}</version>
</dependency>
<dependency>
  <groupId>io.github.awsaman-ai</groupId>
  <artifactId>queryforge</artifactId>
  <version>${queryforge.version}</version>
  <classifier>linux-amd64</classifier>
</dependency>

Ask a question:

QueryForge forge = QueryForge.postgres(
        Paths.get("orders.config.json"));

String sql = forge.query("cancelled orders over $200").toSql();
List<Object> args = forge.query("…").toArgs();

Zero runtime dependencies, not even a JSON library. Docs →

Python pip

Install it:

pip install queryforge-ai

Ask a question:

from queryforge import QueryForge

qf = QueryForge.postgres("orders.config.json")
pending = qf.query("cancelled orders over $200")

print(pending.to_sql())    # SELECT … WHERE (status = $1 …)
print(pending.to_args())   # ('CANCELLED', 200)

The engine ships inside the wheel — no Go toolchain needed. Docs →

Whichever you pick, the answer is the same query: the model fills in an AST, your config validates it, and deterministic code compiles it. Swap postgres for mysql or mongo and the same sentence compiles for that backend instead.

The last argument to Translate 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

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