Blog Post

Azure Database for MySQL Blog
8 MIN READ

Tutorial: Building AI Agents That Talk to Your Azure Database for MySQL

FarahAbdou's avatar
FarahAbdou
Brass Contributor
Apr 15, 2026

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:

  1. An Azure Database for MySQL server with sample data
  2. 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.
  3. 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

  1. Go to the Azure Portal
  2. Search for "Azure Database for MySQL server" and click + Create
  3. 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:

  1. The run_sql_query function catches the MySQL error
  2. It returns the error message as the tool result
  3. OpenAI sees the error in the conversation context
  4. The model generates a corrected query automatically
  5. 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! 

Updated Apr 10, 2026
Version 1.0
No CommentsBe the first to comment