What if you could ask your database a question in plain English and get the answer instantly, without writing a single line of SQL?
In this tutorial, you'll build a Python-based AI agent that connects to Azure Database for MySQL server and uses OpenAI's function calling to translate natural language questions into SQL queries, execute them, and return human-readable answers. The agent can explore your schema, answer business questions, and even self-correct when it writes invalid SQL.
What you'll build:
- An Azure Database for MySQL server with sample data
- A Python AI agent with three tools: list_tables, describe_table, and run_sql_query
- In the context of AI agents, tools are functions the agent can call to interact with external systems like querying a database, fetching a file, or calling an API. Here, our agent has three tools that let it explore and query your MySQL database.
- An interactive chat interface where you ask questions and the agent auto-generates and runs SQL
Prerequisites
Before you begin, make sure you have:
- An Azure account — Sign up for free (includes 12 months of free MySQL hosting)
- An OpenAI API key — Get one here (you'll need a few dollars of credit)
- Python 3.10+ — Download here (check "Add to PATH" during install)
- A code editor — VS Code recommended
- Optional: You can download the complete project from this GitHub repository, or follow the step‑by‑step instructions below to build it from scratch.
Step 1 — Create the Azure Database for MySQL server
- Go to the Azure Portal
- Search for "Azure Database for MySQL server" and click + Create
- Configure the following settings:
|
Setting |
Value |
|
Resource group |
rg-mysql-ai-agent (create new) |
|
Server name |
mysql-ai-agent (or any unique name) |
|
Region |
Your nearest region |
|
MySQL version |
8.4 |
|
Workload type |
Dev/Test (Burstable B1ms — free for 12 months) |
|
Admin username |
mysqladmin |
|
Password |
A strong password — save it! |
4. ✅ Check "Add firewall rule for current IP address"
⚠️ Important: If you skip the firewall settings, you won't be able to connect from Cloud Shell or your local machine.
5. Click Review + create → Create and wait 3–5 minutes
Once deployment finishes, navigate to your server and note the hostname from the Connection details:
mysql-ai-agent.mysql.database.azure.com
Step 2 — Load Sample Data
Open Azure Cloud Shell by clicking the >_ icon in the portal's top toolbar. Select Bash if prompted.
Connect to your MySQL server. You can copy the exact connection command from the "Connect from browser or locally" section on your server's overview page in the Azure portal:
mysql -h mysql-ai-agent.mysql.database.azure.com -u mysqladmin -p
Enter your password when prompted (the cursor won't move — just type and press Enter).
Now paste the following SQL to create a sample sales database:
CREATE DATABASE demo_sales;
USE demo_sales;
CREATE TABLE customers (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100),
email VARCHAR(100),
city VARCHAR(50),
signup_date DATE
);
CREATE TABLE orders (
id INT AUTO_INCREMENT PRIMARY KEY,
customer_id INT,
product VARCHAR(100),
amount DECIMAL(10,2),
order_date DATE,
FOREIGN KEY (customer_id) REFERENCES customers(id)
);
INSERT INTO customers (name, email, city, signup_date) VALUES
('Sara Ahmed', 'sara@example.com', 'Cairo', '2024-06-15'),
('John Smith', 'john@example.com', 'London', '2024-08-22'),
('Priya Patel', 'priya@example.com', 'Mumbai', '2025-01-10');
INSERT INTO orders (customer_id, product, amount, order_date) VALUES
(1, 'Azure Certification Voucher', 150.00, '2025-03-01'),
(2, 'MySQL Workbench Pro License', 99.00, '2025-03-10'),
(1, 'Power BI Dashboard Template', 45.00, '2025-04-05'),
(3, 'Data Analysis Course', 200.00, '2025-05-20');
Verify the data:
SELECT * FROM customers;
SELECT * FROM orders;
Type exit to leave MySQL.
Step 3 — Set Up the Python Project
Open a terminal on your local machine and create the project:
mkdir mysql-ai-agent
cd mysql-ai-agent
python -m venv venv
Activate the virtual environment:
Windows (PowerShell):
venv\Scripts\Activate.ps1
macOS/Linux:
source venv/bin/activate
Install the required packages:
pip install openai mysql-connector-python python-dotenv
Step 4 — Configure Environment Variables
Create a file named .env in your project folder:
OPENAI_API_KEY=sk-proj-xxxxxxxxxxxxxxxxxxxxxxxx
MYSQL_HOST=mysql-ai-agent.mysql.database.azure.com
MYSQL_USER=mysqladmin
MYSQL_PASSWORD=YourPasswordHere
MYSQL_DATABASE=demo_sales
🔒 Security: Never commit this file to Git. Add .env to your .gitignore
Step 5 — Build the Agent
Open VS Code, create a new file called mysql_agent.py in your mysql-ai-agent folder, and paste the following code. Let's walk through each section.
5.1 — Imports and Database Connection
import os
import json
import mysql.connector
from openai import OpenAI
from dotenv import load_dotenv
load_dotenv()
def get_db_connection():
return mysql.connector.connect(
host=os.getenv("MYSQL_HOST"),
user=os.getenv("MYSQL_USER"),
password=os.getenv("MYSQL_PASSWORD"),
database=os.getenv("MYSQL_DATABASE"),
ssl_disabled=False
)
This loads your secrets from .env and creates a reusable MySQL connection function with SSL encryption.
5.2 — Define the Three Tools
These are the functions the AI agent can call:
def list_tables():
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute("SHOW TABLES")
tables = [row[0] for row in cursor.fetchall()]
cursor.close()
conn.close()
return json.dumps({"tables": tables})
def describe_table(table_name):
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute(f"DESCRIBE `{table_name}`")
columns = []
for row in cursor.fetchall():
columns.append({
"field": row[0],
"type": row[1],
"null": row[2],
"key": row[3]
})
cursor.close()
conn.close()
return json.dumps({"table": table_name, "columns": columns})
def run_sql_query(query):
if not query.strip().upper().startswith("SELECT"):
return json.dumps({"error": "Only SELECT queries are allowed."})
conn = get_db_connection()
cursor = conn.cursor()
try:
cursor.execute(query)
columns = [desc[0] for desc in cursor.description]
rows = cursor.fetchall()
results = []
for row in rows:
results.append(dict(zip(columns, row)))
return json.dumps({"columns": columns, "rows": results}, default=str)
except mysql.connector.Error as e:
return json.dumps({"error": str(e)})
finally:
cursor.close()
conn.close()
A few things to note:
- run_sql_query only allows SELECT statements — this is a safety guardrail that prevents the AI from modifying data
- The try/except block is critical — if the AI generates invalid SQL (e.g., a bad GROUP BY), the error message is returned to OpenAI, and the model automatically corrects its query and retries. Without this, the script would crash.
5.3 — Register Tools with OpenAI
This tells OpenAI what tools the agent has access to:
tools = [
{
"type": "function",
"function": {
"name": "list_tables",
"description": "List all tables in the connected MySQL database.",
"parameters": {"type": "object", "properties": {}, "required": []}
}
},
{
"type": "function",
"function": {
"name": "describe_table",
"description": "Get the schema (columns and types) of a specific table.",
"parameters": {
"type": "object",
"properties": {
"table_name": {"type": "string", "description": "Name of the table to describe"}
},
"required": ["table_name"]
}
}
},
{
"type": "function",
"function": {
"name": "run_sql_query",
"description": "Execute a read-only SQL SELECT query and return results.",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "The SQL SELECT query to execute"}
},
"required": ["query"]
}
}
}
]
def call_tool(name, args):
if name == "list_tables":
return list_tables()
elif name == "describe_table":
return describe_table(args["table_name"])
elif name == "run_sql_query":
return run_sql_query(args["query"])
else:
return json.dumps({"error": f"Unknown tool: {name}"})
5.4 — The Agent Loop
This is the core logic. It sends the user's message to OpenAI, processes any tool calls, and loops until the model produces a final text response:
def chat(user_message, conversation_history):
client = OpenAI()
conversation_history.append({"role": "user", "content": user_message})
print(f"\n{'='*60}")
print(f"🧑 You: {user_message}")
print(f"{'='*60}")
while True:
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=conversation_history,
tools=tools,
tool_choice="auto"
)
assistant_message = response.choices[0].message
if assistant_message.tool_calls:
conversation_history.append(assistant_message)
for tool_call in assistant_message.tool_calls:
fn_name = tool_call.function.name
fn_args = json.loads(tool_call.function.arguments)
print(f" 🔧 Calling tool: {fn_name}({json.dumps(fn_args)})")
result = call_tool(fn_name, fn_args)
print(f" ✅ Tool returned: {result[:200]}...")
conversation_history.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result
})
else:
final_answer = assistant_message.content
conversation_history.append({"role": "assistant", "content": final_answer})
print(f"\n🤖 Agent:\n{final_answer}")
return conversation_history
The while True loop is what makes self-correction possible. When a tool returns an error, the model sees it in the conversation and generates a corrected tool call in the next iteration.
5.5 — Main Entry Point
if __name__ == "__main__":
print("\n" + "=" * 60)
print(" 🤖 MySQL AI Agent")
print(" Powered by OpenAI + Azure Database for MySQL")
print(" Type 'quit' to exit")
print("=" * 60)
system_message = {
"role": "system",
"content": (
"You are a helpful data analyst agent connected to an Azure Database for MySQL. "
"You have 3 tools: list_tables, describe_table, and run_sql_query. "
"ALWAYS start by listing tables and describing their schema before writing queries. "
"Only generate SELECT statements. Never write INSERT, UPDATE, DELETE, or DROP. "
"Present query results in clean, readable tables. "
"If the user asks a question, figure out the right SQL to answer it."
)
}
conversation_history = [system_message]
while True:
user_input = input("\n🧑 You: ").strip()
if user_input.lower() in ("quit", "exit", "q"):
print("\n👋 Goodbye!")
break
if not user_input:
continue
conversation_history = chat(user_input, conversation_history)
Your final project folder should look like this:
Step 6 — Run and Test the Agent
python mysql_agent.py
Test:
Prompt: Which product generated the most revenue and who bought it?
How Self-Correction Works
One of the most powerful aspects of this agent is its ability to recover from SQL errors automatically. Azure Database for MySQL has sql_mode=only_full_group_by enabled by default, which rejects queries where non-aggregated columns aren't in the GROUP BY clause.
When the AI generates an invalid query, here's what happens:
- The run_sql_query function catches the MySQL error
- It returns the error message as the tool result
- OpenAI sees the error in the conversation context
- The model generates a corrected query automatically
- The agent retries — and succeeds
Without the try/except error handling, the entire script would crash. This is a key design pattern for production AI agents.
Security Best Practices
When building AI agents that interact with databases, security is critical:
- Read-only enforcement — The run_sql_query function rejects anything that isn't a SELECT statement
- SSL encryption — All connections use ssl_disabled=False, ensuring data in transit is encrypted
- Environment variables — Credentials are stored in .env, never hardcoded
- Principle of least privilege — For production, create a dedicated MySQL user with SELECT-only permissions:
CREATE USER 'ai_agent'@'%' IDENTIFIED BY 'AgentPass123!';
GRANT SELECT ON demo_sales.* TO 'ai_agent'@'%';
FLUSH PRIVILEGES;
- Network isolation — For production workloads, consider using Azure Private Link instead of public access.
Conclusion
In this tutorial, you built a Python AI agent that connects to Azure Database for MySQL and answers natural language questions by auto-generating SQL - complete with self-correction and security guardrails. Clone the GitHub repo, spin up your own server, and start experimenting!
If you'd like to connect to Azure Database for MySQL using the Model Context Protocol (MCP), see Unlocking AI-Driven Data Access: Azure Database for MySQL Support via the Azure MCP Server.
If you have any feedback or questions about the information provided above, please leave a comment below. Thank you!