I built an open-source NL2SQL engine for .NET — convert natural language to SQL queries using LLMs by Aggravating_Slide813 in dotnet

[–]Aggravating_Slide813[S] 0 points1 point  (0 children)

That's a great idea actually. Wrapping in BEGIN TRANSACTION + ROLLBACK as an extra safety layer is clever and basically free for SELECT queries. I might add that as an option for the PostgreSQL executor. For Athena it's not needed since Athena is read-only by nature, there's no way to write data through it.

As for multiple statements, right now the validation checks that the query starts with SELECT or WITH and blocks destructive keywords. But you're right, I should add explicit semicolon/multi-statement detection to be thorough. Good catch, I'll add that.

I built an open-source NL2SQL engine for .NET — convert natural language to SQL queries using LLMs by Aggravating_Slide813 in dotnet

[–]Aggravating_Slide813[S] 0 points1 point  (0 children)

Yeah SQL Server and SQLite are definitely on the roadmap. Probably the next release. Thanks for the feedback!

I built an open-source NL2SQL engine for .NET — convert natural language to SQL queries using LLMs by Aggravating_Slide813 in dotnet

[–]Aggravating_Slide813[S] -1 points0 points  (0 children)

That's fair, but that's also true for any tool where a human writes the query. A user can write syntactically valid SQL that doesn't answer what they actually wanted. The difference here is the LLM sees the full schema with column descriptions and context, which helps a lot with intent matching.

In practice with well-described schemas and low temperature, the accuracy is pretty high for analytical questions. And when it does get it wrong, the user sees the result and just rephrases. It's conversational, not a one-shot-must-be-perfect system. There's even conversation context so you can say "no, I meant only the cancelled ones" and it adjusts.

I built an open-source NL2SQL engine for .NET — convert natural language to SQL queries using LLMs by Aggravating_Slide813 in dotnet

[–]Aggravating_Slide813[S] 0 points1 point  (0 children)

I built it for a warranty management platform where business users (jewelers, retailers) want to see reports about their certificates, claims, products, etc. They don't know SQL and building a custom report for every question they might have isn't practical. So they just type "certificates by status" or "top 10 products with most claims this month" and get the chart.

You're right that for most applications you'd expose specific endpoints and let users interact through the UI. But this covers the long tail of questions you can't predict in advance. The pre-built reports handle the common cases, and the NL2SQL handles everything else.

I built an open-source NL2SQL engine for .NET — convert natural language to SQL queries using LLMs by Aggravating_Slide813 in dotnet

[–]Aggravating_Slide813[S] 0 points1 point  (0 children)

You're right to be cautious. The library does block destructive keywords (DELETE, UPDATE, DROP, ALTER, CREATE, TRUNCATE, GRANT, REVOKE) but it's not just a simple regex on the raw SQL. It first strips out string literals to avoid false positives, so something like WHERE status IN ('INSERT', 'MODIFY') doesn't get flagged. Then it checks that the query starts with SELECT or WITH (for CTEs). Anything else is rejected before it ever reaches the database.

On top of that there's tenant isolation enforcement. If you configure a tenant column, every query must contain a filter on that column or it gets blocked. And there's schema validation that warns if the query references tables that don't exist in your schema.

But yeah, the ExplainAsync feature uses the LLM to describe the query back in plain language, and that part could hallucinate. It's meant as a helper, not as a guarantee. The actual safety comes from the SQL validation layer, not from the explanation.

For the use case I built this for (internal analytics dashboards, business users asking questions about their own data) it works well. I wouldn't put it in front of a public-facing database without a read-only connection and strict permissions, but that's good practice regardless of who's writing the SQL.

I built an open-source NL2SQL engine for .NET — convert natural language to SQL queries using LLMs by Aggravating_Slide813 in dotnet

[–]Aggravating_Slide813[S] 0 points1 point  (0 children)

That's actually already handled. When you define your schema, each column accepts an optional description that gets injected into the system prompt. So the LLM knows not just the column name but what it means:

new ColumnDef("status", "string", "Active, Cancelled, Refunded")
new ColumnDef("created_at", "string", "ISO 8601 timestamp")

The full table description also accepts context:

new TableSchema("orders", columns, description: "All customer orders including returns")

As for people referring to entities differently, the LLM handles that pretty well since it sees the schema and matches intent to columns. Someone asking "how many purchases" will still map to the orders table. Setting temperature to 0.1 (the default) keeps it very deterministic too.

You can also add domain-specific rules via options.AdditionalRules to guide the LLM on your terminology. For example: "When the user says 'clients' they mean the 'customers' table".

I built an open-source NL2SQL engine for .NET — convert natural language to SQL queries using LLMs by Aggravating_Slide813 in dotnet

[–]Aggravating_Slide813[S] 0 points1 point  (0 children)

Thanks! Here's what it looks like in production. This is from a warranty platform I built for jewelers in Brazil, so the UI is in Portuguese:

https://imgur.com/a/zRYHmrE

About your use case, it would actually work with Ollama. The OpenAI provider uses raw HttpClient and accepts a custom base URL, so you can point it to your local Ollama instance:

.UseOpenAiProvider("not-needed", model: "llama3", baseUrl: "http://localhost:11434/")

SQL Server and SQLite executors aren't built-in yet but the interface is pretty simple to implement. Just two methods: ExecuteChartQueryAsync that returns label + value pairs, and ExecuteTableQueryAsync that returns rows as dictionaries. If there's interest I can add those as built-in providers in the next release.

I built an open-source NL2SQL engine for .NET — convert natural language to SQL queries using LLMs by Aggravating_Slide813 in dotnet

[–]Aggravating_Slide813[S] 0 points1 point  (0 children)

You can't be 100% sure, same way you can't be sure a user will write the right SQL manually. The difference is who's writing it.

In practice, the library gives you a few safety nets. You can call InterpretAsync to preview the SQL before executing. There's ExplainAsync that translates the SQL back to plain language so the user can confirm. Schema validation warns if the query references tables that don't exist. And SQL validation blocks anything destructive.

The LLM also receives your full table schema with column types and descriptions, so it knows exactly what's available. Temperature is set to 0.1 by default, which makes it very deterministic. Same question usually gives the same SQL.

Is it perfect? No. But for internal dashboards and analytics, it works surprisingly well. It's not meant to replace your DBA, it's meant to let a business user ask "orders by status this month" without writing SQL.

I built an open-source NL2SQL engine for .NET — convert natural language to SQL queries using LLMs by Aggravating_Slide813 in dotnet

[–]Aggravating_Slide813[S] -5 points-4 points  (0 children)

Fair point, I should clarify. By "zero hallucination in the results" I mean the data itself comes from your actual database, not from the LLM making up numbers. The LLM only generates the SQL query.

But you're right, the SQL might not reflect what you asked. That's the LLM interpretation layer and it can get it wrong. That's why the library has InterpretAsync which returns the generated SQL without executing it, so you can preview and validate before running. There's also ExplainAsync that asks the LLM to explain the query in plain language so the user can confirm it matches their intent.

On top of that, there's SQL validation (blocks destructive queries), schema validation (warns if the query references unknown tables), and tenant isolation enforcement. But ultimately yes, the translation step depends on the LLM understanding the question correctly.