Imagine: a commercial director wants to know the conversion rate from pending to delivered over the last 30 days by customer segment. Instead of writing an SQL query or waiting for a BI specialist's report, they ask a question in natural language. Three minutes later, the answer is ready. This is not science fiction but a real result of deploying an AI agent with database access (Text-to-SQL).
We develop such agents — Text-to-SQL solutions that turn employee questions into correct SQL queries, execute them securely, and return understandable results. Below is how it works in practice, with a focus on security, accuracy, and performance.
How the AI Agent Converts Natural Language to SQL
Text-to-SQL (see Wikipedia) is the task of automatically generating SQL queries from natural language questions. At its core is a combination of an LLM and database interaction tools. We use LangChain and the SQLDatabaseToolkit library. The agent connects to PostgreSQL via a read-only user, retrieves the schema description (tables, columns, relationships), and generates SQL based on the question.
from langchain_openai import ChatOpenAI
from langchain_community.utilities import SQLDatabase
from langchain_community.agent_toolkits import SQLDatabaseToolkit
from langchain.agents import create_sql_agent
# Connect to PostgreSQL
db = SQLDatabase.from_uri(
"postgresql://user:password@localhost:5432/company_db",
include_tables=["orders", "customers", "products", "inventory"],
sample_rows_in_table_info=3,
)
llm = ChatOpenAI(model="gpt-4o", temperature=0)
toolkit = SQLDatabaseToolkit(db=db, llm=llm)
# SQL agent with automatic error correction
agent = create_sql_agent(
llm=llm,
toolkit=toolkit,
verbose=True,
handle_parsing_errors=True,
max_iterations=10,
)
# Example queries
result = agent.invoke({"input": "What are the top 5 customers by revenue over the last 3 months?"})
result = agent.invoke({"input": "Show products with stock less than 10 units"})
Why Security Is Critical for an AI Agent with Database Access
An error in SQL generation can lead to data leaks or corruption. Therefore, we build multi-layered protection. As stated in the LangChain documentation, a read-only user is the minimum standard.
from langchain_community.utilities import SQLDatabase
from sqlalchemy import create_engine, text
# PostgreSQL read-only user
READ_ONLY_USER_URI = "postgresql://readonly_user:pass@localhost:5432/db"
# Additional validation: forbid DML operations
def validate_sql_query(query: str) -> bool:
"""Check that the query is only SELECT"""
forbidden_keywords = ["INSERT", "UPDATE", "DELETE", "DROP", "CREATE", "ALTER", "TRUNCATE"]
query_upper = query.upper()
for keyword in forbidden_keywords:
if keyword in query_upper:
return False
return True
class SafeSQLTool:
def __init__(self, db_uri: str):
self.engine = create_engine(db_uri)
def execute_query(self, query: str) -> str:
if not validate_sql_query(query):
return "ERROR: Only SELECT queries are allowed"
# Limit number of rows
if "LIMIT" not in query.upper():
query = f"{query.rstrip(';')} LIMIT 100"
with self.engine.connect() as conn:
result = conn.execute(text(query))
rows = result.fetchall()
columns = result.keys()
return str([dict(zip(columns, row)) for row in rows])
Additionally, we enforce a read-only user at the DBMS level, limit rows (default LIMIT 100), and log every query for auditing. This approach guarantees that accidental or malicious data modification is impossible.
Few-Shot Learning for Higher Accuracy
Few-shot examples are samples of correct SQL queries provided in the prompt along with the question. They critically boost accuracy on complex queries: from 60-70% to 85-95%. Without them, the LLM might misinterpret business logic (e.g., counting revenue across all statuses instead of only 'delivered').
FEW_SHOT_EXAMPLES = """
Examples of correct queries:
Question: Top 10 products by margin for the last quarter
SQL:
SELECT p.name, p.sku,
SUM(oi.quantity * (p.price_rub - p.cost_rub)) AS margin_rub
FROM order_items oi
JOIN products p ON oi.sku = p.sku
JOIN orders o ON oi.order_id = o.id
WHERE o.status = 'delivered'
AND o.created_at >= DATE_TRUNC('quarter', CURRENT_DATE) - INTERVAL '3 months'
GROUP BY p.name, p.sku
ORDER BY margin_rub DESC
LIMIT 10;
Question: Average order value by month for the current year
SQL:
SELECT DATE_TRUNC('month', created_at) AS month,
ROUND(AVG(total_amount), 0) AS average_order_value,
COUNT(*) AS order_count
FROM orders
WHERE status = 'delivered'
AND o.created_at >= DATE_TRUNC('year', CURRENT_DATE)
GROUP BY 1
ORDER BY 1;
"""
We adapt these examples to your schema and typical business questions.
Practical Case Study: BI Agent for E-commerce (From Our Practice)
Goal: an analytics assistant for the commercial director — sales analysis, ABC assortment analysis, order funnel, cohort retention.
Database: PostgreSQL, 15 tables, 3M orders.
Example dialogues:
The commercial director asks: "What is the conversion rate from pending to delivered over the last 30 days by customer segment?"
The agent generates:
SELECT
c.segment AS segment,
COUNT(*) FILTER (WHERE o.status = 'pending') AS pending,
COUNT(*) FILTER (WHERE o.status = 'delivered') AS delivered,
ROUND(
COUNT(*) FILTER (WHERE o.status = 'delivered')::decimal /
NULLIF(COUNT(*), 0) * 100, 1
) AS conversion_pct
FROM orders o
JOIN customers c ON o.customer_id = c.id
WHERE o.created_at >= NOW() - INTERVAL '30 days'
GROUP BY c.segment
ORDER BY conversion_pct DESC
LIMIT 100;
Results:
- Time to get analytics: 2 days → 3 minutes
- SQL accuracy (questions → correct SQL): 87%
- Typical errors: incorrect JOINs for complex queries (solved via few-shot examples in the prompt)
Database Schema Context for LLM
The quality of Text-to-SQL critically depends on the quality of the schema description. We provide not only table names but also sample data, relationship descriptions, and business rules in the prompt.
SCHEMA_CONTEXT = """
Database tables:
1. orders
- id: PK, INTEGER
- customer_id: FK -> customers.id
- status: VARCHAR (pending, confirmed, shipped, delivered, cancelled)
- total_amount: DECIMAL(12,2) — order amount in rubles
- created_at: TIMESTAMP
- shipped_at: TIMESTAMP (NULL if not shipped)
2. customers
- id: PK
- name: VARCHAR — company name or full name
- inn: VARCHAR(12) — TIN of legal entity/sole proprietor
- segment: VARCHAR (enterprise, mid, small) — customer segment
- manager_id: FK -> employees.id — responsible manager
3. products
- sku: VARCHAR — SKU
- name: VARCHAR
- category: VARCHAR
- price_rub: DECIMAL
- cost_rub: DECIMAL — cost price
IMPORTANT: Order statuses: 'delivered' = successfully completed. 'cancelled' = canceled.
Revenue = sum of total_amount for orders with status 'delivered'.
"""
system_prompt = f"""You are a data analyst. Translate questions into SQL queries.
Use the following database schema:
{SCHEMA_CONTEXT}
Rules:
- Only SELECT queries
- Always add LIMIT (max 1000)
- Use English aliases for readability
- When aggregating, add ORDER BY"""
What's Included in the Work
| Stage | Duration | Result |
|---|---|---|
| Database schema audit | 2–3 days | Report on structure and access rights |
| Text-to-SQL core development | 2–3 weeks | Agent with basic queries |
| Prompt and few-shot tuning | 1 week | Accuracy increase to 85%+ |
| Integration and testing | 1–2 weeks | Production-ready agent |
| Documentation and training | 2–3 days | User guide and API |
Additional services
We also offer post-release support: query quality monitoring, few-shot retraining, adaptation to new tables. 1-month warranty after launch.Comparison: Off-the-Shelf Solutions vs Custom Development
Custom development delivers 85-95% accuracy — twice as high as off-the-shelf solutions (60-70%). Other advantages:
| Criteria | Off-the-Shelf Solutions | Custom Development |
|---|---|---|
| Schema adaptation | Limited | Full, for any database |
| Security | Basic | Multi-layered (read-only, validation, audit) |
| Accuracy on complex queries | 60–70% | 85–95% (2x higher) |
| Integration with business software | Difficult | Flexible (REST, WebSocket, 1C integration) |
| Implementation cost | Fixed | Transparent, under your budget |
Custom development pays off within 3–4 months due to analyst time savings. In one project, the agent helped identify a 10% conversion increase. Analyst time savings reach up to 80%: depends on scope. Contact us for a free project evaluation.
How to Order AI Agent Development
- Submit a consultation request — we'll discuss your database and business tasks.
- We'll perform a schema and access rights audit (2–3 days).
- Develop the Text-to-SQL core with basic queries.
- Tune prompts and few-shot examples for your domain.
- Test with real data and hand over the finished agent.
Get a consultation — we'll explain how an AI agent with database access can accelerate your team's work.







