Grounding
Configure database introspection to provide rich context for SQL generation
Grounding is Text2SQL's database introspection system. It extracts schema metadata, statistics, and relationships from your database to give the AI rich context for generating accurate SQL queries.
Why Grounding Matters
Without grounding, the AI would need to guess table names, column types, and relationships. Grounding provides:
- Schema awareness - Tables, columns, and their types
- Relationship mapping - Foreign keys and join paths
- Performance hints - Indexes, row counts, and size classifications
- Data patterns - Low cardinality values (enums) and column statistics
Quick Start
Each database adapter (PostgreSQL, SQLite, SQL Server) exports grounding factory functions. Import them and pass to your adapter:
import { Text2Sql, InMemoryHistory } from '@deepagents/text2sql';
import { Postgres } from '@deepagents/text2sql/postgres';
import * as postgres from '@deepagents/text2sql/postgres';
const text2sql = new Text2Sql({
version: 'v1',
adapter: new Postgres({
execute: async (sql) => pool.query(sql).then(r => r.rows),
grounding: [
postgres.tables(),
postgres.views(),
postgres.info(),
postgres.indexes(),
postgres.constraints(),
postgres.rowCount(),
postgres.columnStats(),
postgres.lowCardinality(),
],
}),
history: new InMemoryHistory(),
});import { Text2Sql, InMemoryHistory } from '@deepagents/text2sql';
import { Sqlite } from '@deepagents/text2sql/sqlite';
import * as sqlite from '@deepagents/text2sql/sqlite';
const text2sql = new Text2Sql({
version: 'v1',
adapter: new Sqlite({
execute: (sql) => db.prepare(sql).all(),
grounding: [
sqlite.tables(),
sqlite.views(),
sqlite.info(),
sqlite.indexes(),
sqlite.constraints(),
sqlite.rowCount(),
sqlite.columnStats(),
sqlite.lowCardinality(),
],
}),
history: new InMemoryHistory(),
});import { Text2Sql, InMemoryHistory } from '@deepagents/text2sql';
import { SqlServer } from '@deepagents/text2sql/sqlserver';
import * as sqlserver from '@deepagents/text2sql/sqlserver';
const text2sql = new Text2Sql({
version: 'v1',
adapter: new SqlServer({
execute: async (sql) => {
const result = await pool.request().query(sql);
return result.recordset;
},
grounding: [
sqlserver.tables(),
sqlserver.views(),
sqlserver.info(),
sqlserver.indexes(),
sqlserver.constraints(),
sqlserver.rowCount(),
sqlserver.columnStats(),
sqlserver.lowCardinality(),
],
}),
history: new InMemoryHistory(),
});Available Groundings
Core Groundings
These groundings discover your database structure:
tables(config?)
Discovers tables and their relationships via foreign key traversal. This is the foundational grounding that other groundings depend on.
tables({
// Filter tables - array of names, regex, or predicate function
filter: ['users', 'orders', 'products'],
// or
filter: /^app_/,
// or
filter: (name) => !name.startsWith('_'),
// Traverse forward (child → parent) following FK direction
// true = unlimited depth, number = max depth
forward: 2,
// Traverse backward (parent → child) finding referencing tables
backward: 1,
})| Option | Type | Description |
|---|---|---|
filter | string[] | RegExp | (name: string) => boolean | Select which tables to include |
forward | boolean | number | Follow FKs from child to parent tables |
backward | boolean | number | Find tables that reference these tables |
views(config?)
Discovers database views and their columns.
views({
// Same filter options as tables()
filter: ['active_users', 'sales_summary'],
})info()
Collects database dialect, version, and connection info. Helps the AI understand database-specific SQL syntax.
info()
// Returns: { dialect: 'postgresql', version: '15.0', database: 'mydb' }Metadata Groundings
These groundings enhance the schema with performance-relevant metadata. They run after tables() and annotate the discovered tables.
indexes()
Marks which columns have indexes. Helps the AI generate queries that use indexed columns for filtering and sorting.
indexes()
// Annotates columns with isIndexed: trueconstraints()
Collects CHECK, UNIQUE, NOT NULL, DEFAULT, PRIMARY KEY, and FOREIGN KEY constraints.
constraints()
// Annotates tables with constraint informationrowCount()
Counts rows per table and classifies by size. Helps the AI avoid expensive full table scans on large tables.
rowCount()
// Annotates tables with rowCount and sizeHintSize classifications:
| Size Hint | Row Count |
|---|---|
| tiny | < 100 |
| small | 100 - 999 |
| medium | 1,000 - 9,999 |
| large | 10,000 - 99,999 |
| huge | >= 100,000 |
columnStats()
Collects min/max values and null fraction for numeric and date columns.
columnStats()
// Annotates columns with stats: { min, max, nullFraction }lowCardinality()
Identifies columns with few distinct values (like status, type, or category columns) and caches their values.
lowCardinality()
// Annotates columns with kind: 'LowCardinality' and values: ['active', 'inactive', ...]Advanced Groundings
report(config)
Uses an LLM agent to analyze your database and generate a business context report. The agent queries each table's row count and sample data to understand what the database represents.
import { groq } from '@ai-sdk/groq';
report({
model: groq('llama-3.3-70b-versatile'),
// Optional: cache the report
cache: {
get: async () => localStorage.getItem('db-report'),
set: async (value) => localStorage.setItem('db-report', value),
},
// Optional: regenerate even if cached
forceRefresh: false,
})| Option | Type | Description |
|---|---|---|
model | AgentModel | LLM model for generating the report |
cache | { get(): Promise<string | null>, set(value: string): Promise<void> } | Optional cache interface |
forceRefresh | boolean | Force regeneration even if cached |
Execution Order
Groundings execute in the order you specify. Some groundings depend on others:
tables()must run first - discovers the base schemaviews()can run independentlyinfo()can run independentlyindexes(),constraints(),rowCount()- run aftertables(), annotate discovered tablescolumnStats(),lowCardinality()- run aftertables(), annotate columnsreport()- typically runs last, uses the full context
Filtering Tables
All filter options work the same way across tables() and views():
// Include specific tables by name
tables({ filter: ['users', 'orders', 'products'] })
// Include tables matching a pattern
tables({ filter: /^public\./ })
// Custom predicate
tables({ filter: (name) => !name.includes('_archive') })Relationship Traversal
The forward and backward options control how tables() discovers related tables:
// Start with 'orders' and follow FKs to find 'users', 'products', etc.
tables({
filter: ['orders'],
forward: 2, // Follow FKs up to 2 levels deep
})
// Start with 'users' and find all tables that reference it
tables({
filter: ['users'],
backward: 1, // Find immediate children only
})
// Both directions
tables({
filter: ['orders'],
forward: true, // Unlimited forward traversal
backward: true, // Unlimited backward traversal
})Minimal Configuration
For simple use cases, you can use a minimal grounding setup:
import * as postgres from '@deepagents/text2sql/postgres';
grounding: [
postgres.tables(),
postgres.info(),
]import * as sqlite from '@deepagents/text2sql/sqlite';
grounding: [
sqlite.tables(),
sqlite.info(),
]import * as sqlserver from '@deepagents/text2sql/sqlserver';
grounding: [
sqlserver.tables(),
sqlserver.info(),
]Empty Grounding
Pass an empty array to disable all automatic introspection:
grounding: []This is useful when providing custom introspection via the adapter's introspect option.
Next Steps
- PostgreSQL Adapter - PostgreSQL-specific configuration
- SQLite Adapter - SQLite-specific configuration
- SQL Server Adapter - SQL Server-specific configuration
- Teach the System - Add domain knowledge