Field notes from working through example 15 of Ardan Labs’ Ultimate AI course by Bill Kennedy and Florin Pățan (Apache 2.0). My fork: PratikDhanave/ai-training. Thank you Bill and Florin for teaching this material — the patterns in this post are derived from the course; the production reflections at the end are mine.
What the example teaches
The minimum-viable NL→SQL tool that doesn’t drop your tables.
What it looks like
func runSQL(ctx context.Context, query string) (Rows, error) {
// Parse — refuse anything that isn't a single SELECT.
parsed, err := pgparser.Parse(query)
if err != nil {
return nil, fmt.Errorf("invalid SQL: %w", err)
}
if len(parsed.Stmts) != 1 {
return nil, ErrMultipleStatements
}
if !isSelectStmt(parsed.Stmts[0]) {
return nil, ErrNotSelect
}
// Allow-listed table check
for _, table := range tablesIn(parsed.Stmts[0]) {
if !allowedTables[table] {
return nil, fmt.Errorf("table %s not in allow-list", table)
}
}
// Strict timeout
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
return db.QueryContext(ctx, query)
}
What I learned
SQL parsing in the tool is more robust than prompt-instructing the LLM. Telling the LLM “only generate SELECT” works most of the time. Refusing non-SELECT after parsing works all of the time.
Table allow-list is the second line. Even with SELECT-only, an LLM might join against the users table when you don’t want it touching PII. Allow-listing tables to a domain-appropriate subset narrows the blast radius.
Production connection
For one Searce client we built a “chat with your sales data” feature. The SQL tool was 90% of the safety story. The other 10% was per-user RLS at the database — a user’s chat agent could only see rows for that user’s territory. Pattern from Ardan; production layering from Genie.
Credit & reference. This post is field notes on example 15 from Ardan Labs’ Ultimate AI by Bill Kennedy + Florin Pățan, licensed Apache 2.0. The original example: cmd/examples/example15-sql-tool/. My fork with notes: PratikDhanave/ai-training. Highly recommend the course for anyone building AI applications in Go — the material is rigorous and the Kronk + yzma + llama.cpp pipeline gives you hardware-accelerated local inference end-to-end. Thank you, Bill and Florin.