> ## Documentation Index
> Fetch the complete documentation index at: https://docs.inceptionlabs.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Natural Language SQL Agent: Mercury + Tool Calling

> Build a natural language SQL agent that uses Mercury 2.

This cookbook demonstrates how to build a natural language database query system using Mercury 2's tool calling capabilities. The full loop — natural language question, SQL generation, execution, and formatted answer — completes in under 2 seconds. We walk you through how to:

1. Load a SQLite database (sample or your own)
2. Auto-detect the schema and define a SQL tool for Mercury 2
3. Translate natural language questions into SQL using tool calling
4. Execute queries and return natural language answers
5. Measure end-to-end latency at each step

<CardGroup cols={2}>
  <Card title="Run the SQL Agent" icon="download" href="https://colab.research.google.com/drive/1DppdLY8bKlqsMUp32k6Vk-2oYYu-qwoG?usp=sharing">
    Directly via Google Colab or download the .ipynb to run it locally in Jupyter
  </Card>

  <Card title="Download Sample Database" icon="database" href="https://drive.google.com/file/d/1h2MZYl09Ca_cUGZWGGQY-Z2zuY46uPF_/view?usp=sharing">
    Download the sample e-commerce SQLite database to use with the notebook.
  </Card>
</CardGroup>

***

## Dependencies

Install packages and import the libraries required for the Mercury 2 API and SQLite:

```python theme={null}
%pip install \
  "inceptionai" \
  "python-dotenv>=1.0.0"
```

```python theme={null}
import os
import json
import time
import sqlite3
from inceptionai import Inception
from dotenv import load_dotenv
```

## API Keys

Set up your Inception Labs API key.

1. Create a `.env` file in this directory
2. Add your API key to the `.env`:
   * `INCEPTION_API_KEY`

The key will be automatically loaded from the `.env` file.

```python theme={null}
# Load environment variables from .env file
load_dotenv()
INCEPTION_API_KEY = os.getenv("INCEPTION_API_KEY")
```

## Initialize Client

Initialize the Inception (Mercury 2) client:

```python theme={null}
assert INCEPTION_API_KEY is not None, (
    "ERROR: INCEPTION_API_KEY not found. Please create a .env file and add your API key."
)

client = Inception(api_key=INCEPTION_API_KEY)
MERCURY_MODEL = "mercury-2"
```

## Load Database

We create a small in-memory e-commerce database with three tables — `customers`, `products`, and `orders`. If you want to use your own SQLite database instead, replace this cell with `db = sqlite3.connect("your_file.db")`.

```python theme={null}
# To use your own SQLite database instead, replace this cell with:
#   db = sqlite3.connect("path/to/your/database.db")

db = sqlite3.connect(":memory:")
cursor = db.cursor()
cursor.executescript("""
CREATE TABLE customers (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    email TEXT NOT NULL,
    signup_date TEXT NOT NULL
);

CREATE TABLE products (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    category TEXT NOT NULL,
    price REAL NOT NULL,
    stock INTEGER NOT NULL
);

CREATE TABLE orders (
    id INTEGER PRIMARY KEY,
    customer_id INTEGER NOT NULL,
    product_id INTEGER NOT NULL,
    quantity INTEGER NOT NULL,
    total_amount REAL NOT NULL,
    status TEXT NOT NULL,
    order_date TEXT NOT NULL,
    FOREIGN KEY (customer_id) REFERENCES customers(id),
    FOREIGN KEY (product_id) REFERENCES products(id)
);

INSERT INTO customers VALUES (1, 'Alice Johnson', 'alice@example.com', '2024-01-15');
INSERT INTO customers VALUES (2, 'Bob Smith', 'bob@example.com', '2024-02-20');
INSERT INTO customers VALUES (3, 'Sarah Lee', 'sarah@example.com', '2024-03-10');
INSERT INTO customers VALUES (4, 'Mike Chen', 'mike@example.com', '2024-04-05');
INSERT INTO customers VALUES (5, 'Emma Wilson', 'emma@example.com', '2024-05-12');

INSERT INTO products VALUES (1, 'Wireless Headphones', 'Electronics', 79.99, 45);
INSERT INTO products VALUES (2, 'Running Shoes', 'Sports', 129.99, 30);
INSERT INTO products VALUES (3, 'Coffee Maker', 'Home & Kitchen', 49.99, 60);
INSERT INTO products VALUES (4, 'Backpack', 'Accessories', 59.99, 25);
INSERT INTO products VALUES (5, 'Yoga Mat', 'Sports', 29.99, 80);
INSERT INTO products VALUES (6, 'Desk Lamp', 'Home & Kitchen', 34.99, 55);
INSERT INTO products VALUES (7, 'Water Bottle', 'Accessories', 19.99, 100);
INSERT INTO products VALUES (8, 'Bluetooth Speaker', 'Electronics', 99.99, 35);

INSERT INTO orders VALUES (1, 1, 1, 1, 79.99, 'delivered', '2025-01-10');
INSERT INTO orders VALUES (2, 1, 3, 2, 99.98, 'delivered', '2025-01-15');
INSERT INTO orders VALUES (3, 2, 2, 1, 129.99, 'shipped', '2025-02-01');
INSERT INTO orders VALUES (4, 3, 5, 2, 59.98, 'delivered', '2025-02-10');
INSERT INTO orders VALUES (5, 3, 1, 1, 79.99, 'delivered', '2025-02-14');
INSERT INTO orders VALUES (6, 4, 8, 1, 99.99, 'pending', '2025-03-01');
INSERT INTO orders VALUES (7, 4, 4, 1, 59.99, 'pending', '2025-03-05');
INSERT INTO orders VALUES (8, 5, 6, 2, 69.98, 'shipped', '2025-03-08');
INSERT INTO orders VALUES (9, 2, 7, 3, 59.97, 'delivered', '2025-03-10');
INSERT INTO orders VALUES (10, 1, 8, 1, 99.99, 'pending', '2025-03-12');
INSERT INTO orders VALUES (11, 5, 3, 1, 49.99, 'delivered', '2025-01-20');
INSERT INTO orders VALUES (12, 3, 7, 2, 39.98, 'shipped', '2025-03-14');
""")
db.commit()
```

## Inspect Schema

We introspect the schema directly from the database, so the tool calling setup automatically adapts to whatever tables are loaded:

```python theme={null}
cursor.execute("SELECT sql FROM sqlite_master WHERE type='table' AND sql IS NOT NULL")
DB_SCHEMA = "\n\n".join(row[0] for row in cursor.fetchall())

print("Detected schema:")
print(DB_SCHEMA)
```

## Define the SQL Tool

We define a single tool — `run_sql_query` — that Mercury 2 can call to execute SQL against our database. The tool description includes the auto-detected schema so Mercury knows what tables and columns are available:

```python theme={null}
tools = [
    {
        "type": "function",
        "function": {
            "name": "run_sql_query",
            "description": f"Execute a read-only SQL query against the SQLite database. Schema:\n{DB_SCHEMA}",
            "parameters": {
                "type": "object",
                "properties": {
                    "query": {
                        "type": "string",
                        "description": "The SQL SELECT query to execute",
                    }
                },
                "required": ["query"],
            },
        },
    }
]


def run_sql_query(query: str) -> str:
    """Execute a SQL query and return results as a formatted string."""
    try:
        cursor.execute(query)
        columns = [desc[0] for desc in cursor.description] if cursor.description else []
        rows = cursor.fetchall()
        if not rows:
            return "No results found."
        result = " | ".join(columns) + "\n"
        result += "-" * len(result) + "\n"
        for row in rows:
            result += " | ".join(str(val) for val in row) + "\n"
        return result
    except Exception as e:
        return f"SQL Error: {e}"


tool_functions = {"run_sql_query": run_sql_query}
```

## Query Helper

This function handles the full tool-calling loop:

1. Send the user's natural language question to Mercury 2 with the tool definition
2. Mercury generates a `run_sql_query` tool call with SQL
3. We execute the SQL and send results back to Mercury
4. Mercury returns a natural language answer

We time each step to show the latency breakdown.

```python theme={null}
def ask_database(question: str) -> dict:
    """
    Full tool-calling loop: question -> Mercury tool call -> SQL exec -> Mercury answer.
    Returns timing breakdown and the final answer.
    """
    messages = [{"role": "user", "content": question}]

    # Step 1: Mercury generates a tool call
    t0 = time.time()
    response = client.chat.completions.create(
        model=MERCURY_MODEL,
        messages=messages,
        tools=tools,
    )
    t1 = time.time()

    assistant_message = response.choices[0].message
    tool_call = assistant_message.tool_calls[0]
    function_name = tool_call.function.name
    function_args = json.loads(tool_call.function.arguments)

    print(f"Generated SQL: {function_args['query']}")

    # Step 2: Execute the SQL
    t2 = time.time()
    result = tool_functions[function_name](**function_args)
    t3 = time.time()

    print(f"Query result:\n{result}")

    # Step 3: Send tool result back to Mercury for a natural language answer
    messages.append(assistant_message.model_dump(exclude_none=True))
    messages.append(
        {
            "role": "tool",
            "tool_call_id": tool_call.id,
            "content": result,
        }
    )

    t4 = time.time()
    response = client.chat.completions.create(
        model=MERCURY_MODEL,
        messages=messages,
    )
    t5 = time.time()

    final_answer = response.choices[0].message.content

    timing = {
        "tool_call_generation": round(t1 - t0, 3),
        "sql_execution": round(t3 - t2, 3),
        "answer_generation": round(t5 - t4, 3),
        "total": round(t5 - t0, 3),
    }

    return {"answer": final_answer, "sql": function_args["query"], "timing": timing}
```

## Natural Language Database Queries

Let's run several queries and measure the full round-trip latency. Each query goes through: NL question, SQL generation, execution, and NL answer.

### Query 1: Aggregation

*"Who are the top 3 customers by total spending?"*

```python theme={null}
result = ask_database("Who are the top 3 customers by total spending?")
print(f"\nAnswer: {result['answer']}")
print(f"\nLatency breakdown:")
for step, duration in result["timing"].items():
    print(f"  {step}: {duration}s")
```

### Query 2: Filtering

*"Which orders are still pending?"*

```python theme={null}
result = ask_database("Which orders are still pending?")
print(f"\nAnswer: {result['answer']}")
print(f"\nLatency breakdown:")
for step, duration in result["timing"].items():
    print(f"  {step}: {duration}s")
```

### Query 3: Join + Lookup

*"Show me all orders placed by Sarah Lee."*

```python theme={null}
result = ask_database("Show me all orders placed by Sarah Lee.")
print(f"\nAnswer: {result['answer']}")
print(f"\nLatency breakdown:")
for step, duration in result["timing"].items():
    print(f"  {step}: {duration}s")
```

### Query 4: Analytics

*"What is the most popular product category by number of items sold?"*

```python theme={null}
result = ask_database("What is the most popular product category by number of items sold?")
print(f"\nAnswer: {result['answer']}")
print(f"\nLatency breakdown:")
for step, duration in result["timing"].items():
    print(f"  {step}: {duration}s")
```

### Query 5: Revenue

*"What's our total revenue from delivered orders?"*

```python theme={null}
result = ask_database("What's our total revenue from delivered orders?")
print(f"\nAnswer: {result['answer']}")
print(f"\nLatency breakdown:")
for step, duration in result["timing"].items():
    print(f"  {step}: {duration}s")
```

## Latency Summary

Mercury 2's diffusion-based architecture generates tokens \~5x faster than traditional autoregressive LLMs. In the queries above, the full tool-calling loop — understanding the question, generating SQL, executing it, and summarizing results in natural language — consistently completes in around 1-2 seconds. SQL execution itself is near-instant; the time is spent on two Mercury inference calls (tool call generation + answer generation), each typically under 1 second.

This makes Mercury 2 fast enough to power **real-time, interactive database assistants** where users expect instant answers to ad-hoc questions — no waiting, no loading spinners.
