Spaces:
Running
Running
devjas1
commited on
Commit
·
a111000
1
Parent(s):
986fb2a
(FEAT): Revise README to enhance tutorial clarity and detail on AI agent development with SQL interaction
Browse files
README.md
CHANGED
|
@@ -1,138 +1,128 @@
|
|
| 1 |
-
#
|
| 2 |
|
| 3 |
-
|
|
|
|
| 4 |
|
| 5 |
-
|
| 6 |
|
| 7 |
-
|
| 8 |
|
| 9 |
-
|
| 10 |
|
| 11 |
-
|
| 12 |
|
| 13 |
-
-
|
| 14 |
-
- Defines two database tables: receipts (with columns for receipt_id, customer_name, price, tip) and waiters (with receipt_id, waiter_name).
|
| 15 |
-
- Populates these tables with sample data.
|
| 16 |
|
| 17 |
-
|
| 18 |
|
| 19 |
-
|
| 20 |
-
- The `sql_engine` tool takes a SQL query string as input, executes it against the database, and returns the results as a string. Crucially, the tool's description is vital, as it tells the AI agent how and when to use it, including the available tables and their columns.
|
| 21 |
|
| 22 |
-
|
| 23 |
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
4. Problem Solving and Self-Correction:
|
| 29 |
-
|
| 30 |
-
- The output shows how the first agent (Llama-3.1) iteratively attempts to solve the problem. When its initial SQL generation or result parsing fails, it analyzes the error messages (e.g., "syntax error," "could not convert string to float") and revises its approach in subsequent steps. This demonstrates a form of self-correction.
|
| 31 |
-
- The second agent (Qwen2.5) successfully performs a SQL JOIN operation and then processes the results in Python to find the answer.
|
| 32 |
-
|
| 33 |
-
## How Does This Code Work? (The Mechanics of an AI Agent)
|
| 34 |
-
|
| 35 |
-
This code leverages the power of Large Language Models (LLMs) combined with tool-use capabilities to create an AI agent. Here's the "how":
|
| 36 |
-
|
| 37 |
-
1. **The LLM as the "Brain":** The `CodeAgent` uses an LLM (like Llama-3.1 or Qwen2.5) as its core reasoning engine. The LLM's extensive training on vast amounts of text enables it to:
|
| 38 |
-
|
| 39 |
-
- Understand natural language prompts (e.g., "most expensive receipt").
|
| 40 |
-
- Generate code (in this case, SQL queries and Python logic for parsing/calculation) to achieve a goal.
|
| 41 |
-
- Reason about execution errors and potentially self-correct its generated code.
|
| 42 |
-
|
| 43 |
-
2. **Tools as "Hands" or "Senses":** The `@tool` decorator from smolagents (or similar frameworks) allows the LLM to interact with the external world beyond just generating text.
|
| 44 |
-
|
| 45 |
-
- The `sql_engine` function acts as a specialized tool that encapsulates the logic for database interaction.s
|
| 46 |
-
- When the LLM determines that a database query is needed to answer a user's question, it "calls" the `sql_engine` tool, generating the appropriate SQL query string as an argument.
|
| 47 |
-
- The LLM relies heavily on the description attribute of the tool. This description tells the LLM what the tool does, what arguments it expects, and importantly, the schema of the database it can query. A clear and accurate description is paramount for the agent to use the tool correctly.
|
| 48 |
-
|
| 49 |
-
3. **The Agentic Loop:** The `CodeAgent` operates in a loop:
|
| 50 |
-
|
| 51 |
-
- It receives a prompt.
|
| 52 |
-
- It uses the LLM to think about the problem and decide if a tool is needed.
|
| 53 |
-
- If a tool is needed, the LLM generates the code (e.g., sql_engine("SELECT ...")) to use it.
|
| 54 |
-
- This generated code is then executed.
|
| 55 |
-
- The output of the code (e.g., database results, error messages) is fed back to the LLM.
|
| 56 |
-
- The LLM then uses this feedback to either refine its approach (self-correction), generate more code, or formulate a final answer.
|
| 57 |
-
|
| 58 |
-
4. **SQLAlchemy for Database Abstraction:**
|
| 59 |
-
|
| 60 |
-
- `SQLAlchemy` is used here to define the database schema (tables and columns) in a Pythonic way. This makes it easier to manage the database structure programmatically.
|
| 61 |
-
- It also provides an Object Relational Mapper (ORM) and a SQL Expression Language, which simplifies executing SQL queries and handling database connections (though in our sql_engine tool, we're directly using raw SQL for simplicity).
|
| 62 |
-
|
| 63 |
-
## Why Is This Approach Useful for AI Agents?
|
| 64 |
-
|
| 65 |
-
This method of combining LLMs with tools is a powerful paradigm for building sophisticated AI agents for several key reasons:
|
| 66 |
-
|
| 67 |
-
- **Extending LLM Capabilities:** LLMs are great at text understanding and generation, but they don't inherently "know" how to interact with a database, browse the internet, or perform complex calculations. Tools provide these external capabilities, essentially giving the LLM "skills" beyond just language.
|
| 68 |
-
- **Grounding in Factual Data:** By providing a sql_engine tool, the LLM can access up-to-date and specific information stored in a database, preventing hallucinations and ensuring factual accuracy, which LLMs alone sometimes struggle with.
|
| 69 |
-
- **Complex Task Execution:** Agents can break down complex problems into smaller steps, using different tools as needed. For example, in the second question, the agent first used SQL to fetch raw data and then used Python code (generated by the LLM) to perform an aggregation.
|
| 70 |
-
- **Reduced Development Effort:** Instead of hand-coding every possible interaction or query, you define the tools and let the LLM figure out how to use them to achieve the desired outcome from natural language. This abstracts away much of the conditional logic usually required in traditional programming.
|
| 71 |
-
- **Adaptability and Flexibility:** If your database schema changes, you primarily update the tool's description. The LLM, given the updated description, can often adapt its SQL generation without needing extensive code changes.
|
| 72 |
-
- **Self-Correction and Robustness:** The ability of the agent to analyze `Execution logs` and correct its own errors makes the system more robust and capable of handling unexpected issues.
|
| 73 |
-
|
| 74 |
-
## How to Create Your Own AI Agents (Step-by-Step Tutorial)
|
| 75 |
-
|
| 76 |
-
Let's break down the process of creating an AI agent similar to the one demonstrated:
|
| 77 |
|
| 78 |
-
|
| 79 |
|
| 80 |
-
|
|
|
|
|
|
|
|
|
|
| 81 |
|
| 82 |
-
|
| 83 |
|
| 84 |
-
|
| 85 |
-
- Use a library like SQLAlchemy to define your database schema (tables, columns, data types, primary keys).
|
| 86 |
-
- Populate your database with initial data.
|
| 87 |
|
| 88 |
```python
|
| 89 |
-
from sqlalchemy import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
|
| 91 |
-
|
|
|
|
| 92 |
metadata_obj = MetaData()
|
| 93 |
|
| 94 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
|
|
|
|
|
|
|
| 96 |
receipts = Table(
|
| 97 |
-
|
| 98 |
-
metadata_obj,
|
| 99 |
-
Column("receipt_id", Integer, primary_key=True),
|
| 100 |
-
Column("customer_name", String(255)),
|
| 101 |
-
Column("price", Float),
|
| 102 |
-
Column("tip", Float),
|
| 103 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
|
| 105 |
-
|
| 106 |
|
| 107 |
-
|
| 108 |
|
| 109 |
-
|
| 110 |
|
| 111 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
```
|
| 113 |
|
| 114 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
|
| 116 |
-
|
| 117 |
|
| 118 |
-
-
|
| 119 |
-
-
|
| 120 |
-
-
|
| 121 |
-
- A high-level explanation of the tool's purpose.
|
| 122 |
-
- A detailed description of the data it can access or modify (e.g., table schemas, API endpoints).
|
| 123 |
-
- Examples if necessary.
|
| 124 |
|
| 125 |
```python
|
| 126 |
from smolagents import tool
|
| 127 |
-
from sqlalchemy import text # Make sure to import text for raw SQL execution
|
| 128 |
|
| 129 |
-
|
| 130 |
def sql_engine(query: str) -> str:
|
| 131 |
-
"""
|
| 132 |
-
|
| 133 |
-
|
| 134 |
|
| 135 |
-
|
| 136 |
Table 'receipts':
|
| 137 |
Columns:
|
| 138 |
- receipt_id: INTEGER (Primary Key)
|
|
@@ -140,61 +130,125 @@ Returns a string representation of the query results.
|
|
| 140 |
- price: FLOAT
|
| 141 |
- tip: FLOAT
|
| 142 |
|
| 143 |
-
Table 'waiters':
|
| 144 |
-
Columns:
|
| 145 |
-
- receipt_id: INTEGER (Primary Key)
|
| 146 |
-
- waiter_name: VARCHAR(16) (Primary Key)
|
| 147 |
-
|
| 148 |
Args:
|
| 149 |
-
query: The SQL query string to
|
| 150 |
-
Example: "SELECT customer_name FROM receipts WHERE price > 10.0"
|
| 151 |
"""
|
| 152 |
output = ""
|
| 153 |
with engine.connect() as con:
|
| 154 |
-
|
|
|
|
| 155 |
for row in rows:
|
| 156 |
-
output += "\n" + str(row)
|
| 157 |
return output
|
| 158 |
```
|
| 159 |
|
| 160 |
-
|
| 161 |
|
| 162 |
-
|
| 163 |
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
- **Choose your LLM:** Select a suitable language model. Different models have different capabilities and cost implications.
|
| 167 |
-
- **Initialize `CodeAgent`:** Pass your list of tools and your chosen LLM.
|
| 168 |
|
| 169 |
```python
|
| 170 |
-
from smolagents import
|
| 171 |
|
| 172 |
-
agent =
|
| 173 |
-
tools=[sql_engine], # Provide the
|
| 174 |
-
model=InferenceClientModel(model_id="meta-llama/Llama-3.1-8B-Instruct"), #
|
| 175 |
)
|
| 176 |
```
|
| 177 |
|
| 178 |
-
### Step 4:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 179 |
|
| 180 |
-
|
| 181 |
|
| 182 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
|
| 184 |
```python
|
| 185 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 186 |
```
|
| 187 |
|
| 188 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 189 |
|
| 190 |
-
|
| 191 |
|
| 192 |
-
|
| 193 |
|
| 194 |
-
- **
|
| 195 |
-
- **
|
| 196 |
-
- **
|
| 197 |
-
- **Consider Model Choice:** Different LLMs excel at different tasks. If one model struggles, experimenting with another might yield better results.
|
| 198 |
-
- **Handle Complex Output Parsing:** As seen in the example, parsing the string output from tools can be tricky. The agent might need to generate additional Python code to properly extract information from the tool's raw string output.
|
| 199 |
|
| 200 |
-
|
|
|
|
| 1 |
+
# Unlocking Database Intelligence with AI Agents: A `smolagents` Tutorial
|
| 2 |
|
| 3 |
+
[Open In Colab](https://colab.research.google.com/github/huggingface/smolagents/blob/main/notebooks/text_to_sql.ipynb)
|
| 4 |
+
[Open In Studio Lab](https://studiolab.sagemaker.aws/import/github/huggingface/smolagents/blob/main/notebooks/text_to_sql.ipynb)
|
| 5 |
|
| 6 |
+
This guide explores how to develop an intelligent agent using the `smolagents` framework, specifically enabling it to interact with a SQL database.
|
| 7 |
|
| 8 |
+
---
|
| 9 |
|
| 10 |
+
## Beyond Simple Text-to-SQL: The Agent Advantage
|
| 11 |
|
| 12 |
+
Why opt for an advanced agent system instead of a straightforward text-to-SQL pipeline?
|
| 13 |
|
| 14 |
+
Traditional text-to-SQL solutions are often quite rigid. A direct translation from natural language to a database query can easily lead to syntactical errors, causing the database to reject the query. More insidiously, a query might execute without error but produce entirely incorrect or irrelevant results, providing no indication of its inaccuracy. This "silent failure" can be detrimental for critical applications.
|
|
|
|
|
|
|
| 15 |
|
| 16 |
+
👉 An agent-based system, conversely, possesses the crucial capability to **critically evaluate outputs and execution logs**. It can identify when a query has failed or yielded unexpected results, and then iteratively refine its strategy or reformulate the query. This inherent capacity for self-correction significantly boosts performance and reliability.
|
| 17 |
|
| 18 |
+
Let's dive into building such an agent! 💪
|
|
|
|
| 19 |
|
| 20 |
+
First, ensure all necessary libraries are installed by running the command below:
|
| 21 |
|
| 22 |
+
```bash
|
| 23 |
+
!pip install smolagents python-dotenv sqlalchemy --upgrade -q
|
| 24 |
+
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
+
To enable interaction with Large Language Models (LLMs) via inference providers, you'll need an authentication token, such as an `HF_TOKEN` from Hugging Face. We'll use `python-dotenv` to load this from your environment variables.
|
| 27 |
|
| 28 |
+
```python
|
| 29 |
+
from dotenv import load_dotenv
|
| 30 |
+
load_dotenv()
|
| 31 |
+
```
|
| 32 |
|
| 33 |
+
### Step 1: Database Initialization
|
| 34 |
|
| 35 |
+
We begin by setting up our in-memory SQLite database using `SQLAlchemy`. This involves defining our table structures and populating them with initial data.
|
|
|
|
|
|
|
| 36 |
|
| 37 |
```python
|
| 38 |
+
from sqlalchemy import (
|
| 39 |
+
create_engine,
|
| 40 |
+
MetaData,
|
| 41 |
+
Table,
|
| 42 |
+
Column,
|
| 43 |
+
String,
|
| 44 |
+
Integer,
|
| 45 |
+
Float,
|
| 46 |
+
insert,
|
| 47 |
+
inspect,
|
| 48 |
+
text, # Essential for executing raw SQL expressions
|
| 49 |
+
)
|
| 50 |
|
| 51 |
+
# Establish an in-memory SQLite database connection
|
| 52 |
+
engine = create_engine("sqlite:///:memory:")
|
| 53 |
metadata_obj = MetaData()
|
| 54 |
|
| 55 |
+
# Utility function for bulk data insertion
|
| 56 |
+
def insert_rows_into_table(rows, table, engine=engine):
|
| 57 |
+
for row in rows:
|
| 58 |
+
stmt = insert(table).values(**row)
|
| 59 |
+
with engine.begin() as connection:
|
| 60 |
+
connection.execute(stmt)
|
| 61 |
|
| 62 |
+
# Define the 'receipts' table schema
|
| 63 |
+
table_name = "receipts"
|
| 64 |
receipts = Table(
|
| 65 |
+
table_name,
|
| 66 |
+
metadata_obj,
|
| 67 |
+
Column("receipt_id", Integer, primary_key=True), # Unique identifier for each transaction
|
| 68 |
+
Column("customer_name", String(255)), # Full name of the patron
|
| 69 |
+
Column("price", Float), # Total cost of the receipt
|
| 70 |
+
Column("tip", Float), # Gratuity amount
|
| 71 |
)
|
| 72 |
+
# Create the defined table within our database
|
| 73 |
+
metadata_obj.create_all(engine)
|
| 74 |
+
|
| 75 |
+
# Sample transaction data
|
| 76 |
+
rows = [
|
| 77 |
+
{"receipt_id": 1, "customer_name": "Alan Payne", "price": 12.06, "tip": 1.20},
|
| 78 |
+
{"receipt_id": 2, "customer_name": "Alex Mason", "price": 23.86, "tip": 0.24},
|
| 79 |
+
{"receipt_id": 3, "customer_name": "Woodrow Wilson", "price": 53.43, "tip": 5.43},
|
| 80 |
+
{"receipt_id": 4, "customer_name": "Margaret James", "price": 21.11, "tip": 1.00},
|
| 81 |
+
]
|
| 82 |
+
# Populate the 'receipts' table
|
| 83 |
+
insert_rows_into_table(rows, receipts)
|
| 84 |
+
```
|
| 85 |
|
| 86 |
+
### Step 2: Crafting the Agent's Database Tool
|
| 87 |
|
| 88 |
+
For an AI agent to interact with a database, it requires specialized **tools**. Our `sql_engine` function will serve as this tool, allowing the agent to execute SQL queries.
|
| 89 |
|
| 90 |
+
The tool's docstring plays a critical role, as its content (the `description` attribute) is presented to the LLM by the agent system. This description guides the LLM on _how_ and _when_ to utilize the tool, including details about available tables and their column structures.
|
| 91 |
|
| 92 |
+
First, let's extract the schema details for our `receipts` table:
|
| 93 |
+
|
| 94 |
+
```python
|
| 95 |
+
inspector = inspect(engine)
|
| 96 |
+
columns_info = [(col["name"], col["type"]) for col in inspector.get_columns("receipts")]
|
| 97 |
+
|
| 98 |
+
table_description = "Columns:\n" + "\n".join([f" - {name}: {col_type}" for name, col_type in columns_info])
|
| 99 |
+
print(table_description)
|
| 100 |
```
|
| 101 |
|
| 102 |
+
```
|
| 103 |
+
Columns:
|
| 104 |
+
- receipt_id: INTEGER
|
| 105 |
+
- customer_name: VARCHAR(255)
|
| 106 |
+
- price: FLOAT
|
| 107 |
+
- tip: FLOAT
|
| 108 |
+
```
|
| 109 |
|
| 110 |
+
Now, we'll construct our `sql_engine` tool. Key elements include:
|
| 111 |
|
| 112 |
+
- The `@tool` decorator from `smolagents` to designate it as an agent capability.
|
| 113 |
+
- A comprehensive docstring, complete with an `Args:` section, to inform the LLM about the tool's purpose and expected inputs.
|
| 114 |
+
- Type hints for both input and output parameters, enhancing clarity and guiding the LLM's code generation.
|
|
|
|
|
|
|
|
|
|
| 115 |
|
| 116 |
```python
|
| 117 |
from smolagents import tool
|
|
|
|
| 118 |
|
| 119 |
+
@tool
|
| 120 |
def sql_engine(query: str) -> str:
|
| 121 |
+
"""
|
| 122 |
+
Enables execution of SQL queries against the database.
|
| 123 |
+
Outputs the query results as a formatted string.
|
| 124 |
|
| 125 |
+
Known tables and their column structures:
|
| 126 |
Table 'receipts':
|
| 127 |
Columns:
|
| 128 |
- receipt_id: INTEGER (Primary Key)
|
|
|
|
| 130 |
- price: FLOAT
|
| 131 |
- tip: FLOAT
|
| 132 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 133 |
Args:
|
| 134 |
+
query: The precise SQL query string to be executed.
|
| 135 |
+
Example: "SELECT customer_name FROM receipts WHERE price > 10.0;"
|
| 136 |
"""
|
| 137 |
output = ""
|
| 138 |
with engine.connect() as con:
|
| 139 |
+
# Utilize text() to safely execute raw SQL within SQLAlchemy
|
| 140 |
+
rows = con.execute(text(query))
|
| 141 |
for row in rows:
|
| 142 |
+
output += "\n" + str(row) # Converts each row of results into a string representation
|
| 143 |
return output
|
| 144 |
```
|
| 145 |
|
| 146 |
+
### Step 3: Assembling the AI Agent
|
| 147 |
|
| 148 |
+
With our database and tool ready, we now instantiate the `CodeAgent`. This is `smolagents’` flagship agent class, designed to generate and execute code, and to iteratively refine its actions based on the ReAct (Reasoning + Acting) framework.
|
| 149 |
|
| 150 |
+
The `model` parameter links our agent to a Large Language Model. `InferenceClientModel` facilitates access to LLMs via Hugging Face's Inference API, supporting both Serverless and Dedicated endpoints. Alternatively, you could integrate other proprietary LLM APIs.
|
|
|
|
|
|
|
|
|
|
| 151 |
|
| 152 |
```python
|
| 153 |
+
from smolagents import CodeAgent, InferenceClientModel
|
| 154 |
|
| 155 |
+
agent = CodeAgent(
|
| 156 |
+
tools=[sql_engine], # Provide the 'sql_engine' tool to our agent
|
| 157 |
+
model=InferenceClientModel(model_id="meta-llama/Llama-3.1-8B-Instruct"), # Selecting our LLM
|
| 158 |
)
|
| 159 |
```
|
| 160 |
|
| 161 |
+
### Step 4: Posing a Query to the Agent
|
| 162 |
+
|
| 163 |
+
Our agent is now configured. Let's challenge it with a natural language question. The agent will then leverage its LLM and `sql_engine` tool to find the answer.
|
| 164 |
+
|
| 165 |
+
```python
|
| 166 |
+
agent.run("Can you give me the name of the client who got the most expensive receipt?")
|
| 167 |
+
```
|
| 168 |
+
|
| 169 |
+
**Understanding the Agent's Iterative Solution Process:**
|
| 170 |
+
|
| 171 |
+
The `CodeAgent` employs a self-correcting, cyclical approach:
|
| 172 |
+
|
| 173 |
+
1. **Intent Comprehension:** The LLM interprets the request, identifying the need to find the "most expensive receipt."
|
| 174 |
+
2. **Tool Selection:** It recognizes that the `sql_engine` tool is necessary for database interaction.
|
| 175 |
+
3. **Initial Code Generation:** The agent generates its first attempt at a SQL query (e.g., `SELECT MAX(price) FROM receipts`) to get the maximum price. It then tries to use this result in a follow-up query.
|
| 176 |
+
4. **Execution and Feedback:** The `sql_engine` executes the query. However, the output is a string like `\n(53.43,)`. If the agent naively tries to embed this string directly into another SQL query (e.g., `WHERE price = (53.43,)`), it will encounter a `syntax error`.
|
| 177 |
+
5. **Adaptive Self-Correction:** Upon receiving an `OperationalError` (e.g., "syntax error" or "could not convert string to float"), the LLM analyzes the error. It understands that the string-formatted output needs to be correctly parsed into a numeric type before being used in subsequent SQL or Python logic. Previous attempts might fail due to unexpected characters (like newlines) or incorrect string manipulation.
|
| 178 |
+
6. **Refined Strategy:** Learning from its previous attempts, the agent eventually generates a more efficient, consolidated SQL query: `SELECT MAX(price), customer_name FROM receipts ORDER BY price DESC LIMIT 1`. This effectively retrieves both the highest price and the corresponding customer name in a single database call.
|
| 179 |
+
7. **Result Parsing and Finalization:** Finally, the LLM generates Python code to accurately parse the `\n(53.43, 'Woodrow Wilson')` string output from the `sql_engine`, extracting the customer name. It then provides the `final_answer`.
|
| 180 |
+
|
| 181 |
+
This continuous cycle of **reasoning, acting via tools, observing outcomes (including errors), and self-correction** is fundamental to the robustness and adaptability of agent-based systems.
|
| 182 |
+
|
| 183 |
+
---
|
| 184 |
+
|
| 185 |
+
### Level 2: Inter-Table Queries (Table Joins)
|
| 186 |
+
|
| 187 |
+
Let's elevate the complexity! Our goal now is to enable the agent to handle questions that require combining data from multiple tables using SQL joins.
|
| 188 |
|
| 189 |
+
To achieve this, we'll define a second table, `waiters`, which records the names of waiters associated with each `receipt_id`.
|
| 190 |
|
| 191 |
+
```python
|
| 192 |
+
# Define the 'waiters' table schema
|
| 193 |
+
table_name = "waiters"
|
| 194 |
+
waiters = Table(
|
| 195 |
+
table_name,
|
| 196 |
+
metadata_obj,
|
| 197 |
+
Column("receipt_id", Integer, primary_key=True), # Links to 'receipts' table
|
| 198 |
+
Column("waiter_name", String(16), primary_key=True), # Name of the assigned waiter
|
| 199 |
+
)
|
| 200 |
+
# Create the 'waiters' table in the database
|
| 201 |
+
metadata_obj.create_all(engine)
|
| 202 |
+
|
| 203 |
+
# Sample data for the 'waiters' table
|
| 204 |
+
rows = [
|
| 205 |
+
{"receipt_id": 1, "waiter_name": "Corey Johnson"},
|
| 206 |
+
{"receipt_id": 2, "waiter_name": "Michael Watts"},
|
| 207 |
+
{"receipt_id": 3, "waiter_name": "Michael Watts"},
|
| 208 |
+
{"receipt_id": 4, "waiter_name": "Margaret James"},
|
| 209 |
+
]
|
| 210 |
+
# Populate the 'waiters' table
|
| 211 |
+
insert_rows_into_table(rows, waiters)
|
| 212 |
+
```
|
| 213 |
+
|
| 214 |
+
With the introduction of a new table, it's crucial to **update the `sql_engine` tool's description**. This ensures the LLM is aware of the `waiters` table and its schema, allowing it to construct queries that span both tables.
|
| 215 |
|
| 216 |
```python
|
| 217 |
+
updated_description = """This tool allows performing SQL queries on the database, returning results as a string.
|
| 218 |
+
It can access the following tables:"""
|
| 219 |
+
|
| 220 |
+
inspector = inspect(engine)
|
| 221 |
+
for table in ["receipts", "waiters"]:
|
| 222 |
+
columns_info = [(col["name"], col["type"]) for col in inspector.get_columns(table)]
|
| 223 |
+
|
| 224 |
+
table_description = f"Table '{table}':\n"
|
| 225 |
+
|
| 226 |
+
table_description += " Columns:\n" + "\n".join([f" - {name}: {col_type}" for name, col_type in columns_info])
|
| 227 |
+
updated_description += "\n\n" + table_description
|
| 228 |
+
|
| 229 |
+
print(updated_description)
|
| 230 |
```
|
| 231 |
|
| 232 |
+
For more intricate requests like this, switching to a more powerful LLM can significantly enhance the agent's reasoning capabilities. Here, we'll upgrade to `Qwen/Qwen2.5-Coder-32B-Instruct`.
|
| 233 |
+
|
| 234 |
+
```python
|
| 235 |
+
# Assign the updated description to the tool
|
| 236 |
+
sql_engine.description = updated_description
|
| 237 |
+
|
| 238 |
+
agent = CodeAgent(
|
| 239 |
+
tools=[sql_engine],
|
| 240 |
+
model=InferenceClientModel(model_id="Qwen/Qwen2.5-Coder-32B-Instruct"),
|
| 241 |
+
)
|
| 242 |
+
|
| 243 |
+
agent.run("Which waiter received the highest total amount in tips?")
|
| 244 |
+
```
|
| 245 |
|
| 246 |
+
The agent successfully addresses this challenge, often directly formulating the correct SQL query involving a `JOIN` operation, and then performing the necessary calculations in Python. The simplicity of setup versus the complexity of the task handled demonstrates the power of this agentic approach!
|
| 247 |
|
| 248 |
+
This tutorial covered several key concepts:
|
| 249 |
|
| 250 |
+
- **Constructing custom tools** for agents.
|
| 251 |
+
- **Dynamically updating a tool's description** to reflect changes in available data or functionalities.
|
| 252 |
+
- **Leveraging stronger LLMs** to empower an agent's reasoning for more complex tasks.
|
|
|
|
|
|
|
| 253 |
|
| 254 |
+
✅ You are now equipped to start building your own advanced text-to-SQL systems! ✨
|