text-to-sql-smolagent / index.html
devjas1
Add LICENSE.md and update license information in index.html to Apache 2.0
9fea9b9
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Build AI Agent Text-to-SQL</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
font-family: Arial, sans-serif;
margin: 2em;
background: #f9f9f9;
color: #222;
}
h1,
h2,
h3 {
color: #2a7ae2;
}
pre,
code {
background: #f4f4f4;
padding: 0.2em 0.4em;
border-radius: 4px;
}
.container {
max-width: 900px;
margin: auto;
background: #fff;
padding: 2em;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
ul,
ol {
margin-left: 2em;
}
a {
color: #2a7ae2;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
hr {
margin: 2em 0;
}
pre,
code {
background: #f4f4f4;
padding: 0.2em 0.4em;
border-radius: 4px;
white-space: pre-wrap;
/* Enables word wrap */
word-break: break-word;
/* Breaks long words */
}
</style>
</head>
<body>
<div class="container">
<h2 class="code-line" data-line-start=9 data-line-end=10><a
id="Intelligent_TexttoSQL_Agent_with_smolagents_9"></a>Intelligent Text-to-SQL Agent with
<code>smolagents</code>
</h2>
<p class="has-line-data" data-line-start="11" data-line-end="12">This project demonstrates building a robust
Text-to-SQL AI agent using the <code>smolagents</code> framework, capable of translating natural language
queries into SQL, executing them, and intelligently processing the results, including handling complex
scenarios
like table joins.</p>
<h2 id="table-of-contents">Table of Contents</h2>
<ul>
<li><a href="#intelligent-text-to-sql-agent-with-smolagents">Intelligent Text-to-SQL Agent with
<code>smolagents</code></a></li>
<li><a href="#table-of-contents">Table of Contents</a></li>
<li><a href="#why-an-ai-agent-for-text-to-sql">Why an AI Agent for Text-to-SQL?</a></li>
<li><a href="#features">Features</a></li>
<li><a href="#installation">Installation</a>
<ul>
<li><a href="#instantiating-the-agent-single-table">Instantiating the Agent (Single Table)</a></li>
<li><a href="#querying-the-agent-single-table">Querying the Agent: Single Table</a></li>
<li><a href="#extending-for-table-joins">Extending for Table Joins</a></li>
<li><a href="#querying-the-agent-multi-table">Querying the Agent: Multi-Table</a></li>
</ul>
</li>
<li><a href="#how-it-works">How it Works</a></li>
<li><a href="#key-concepts-demonstrated">Key Concepts Demonstrated</a></li>
<li><a href="#contributing">Contributing</a></li>
<li><a href="#license">License</a></li>
</ul>
<h2 id="why-an-ai-agent-for-text-to-sql" class="code-line" data-line-start=29 data-line-end=30><a
id="Why_an_AI_Agent_for_TexttoSQL_29"></a>Why an AI
Agent for Text-to-SQL?</h2>
<p class="has-line-data" data-line-start="31" data-line-end="32">Traditional Text-to-SQL pipelines often suffer
from
brittleness:</p>
<ul>
<li class="has-line-data" data-line-start="33" data-line-end="34"><strong>Syntactic Errors:</strong>
Generated
SQL queries might be invalid, leading to execution failures.</li>
<li class="has-line-data" data-line-start="34" data-line-end="36"><strong>Semantic Errors:</strong> Even if
syntactically correct, queries can produce incorrect or irrelevant results without explicit error
messages,
leading to silent failures and potentially misleading information.</li>
</ul>
<p class="has-line-data" data-line-start="36" data-line-end="37"><strong>An agent-based system overcomes these
limitations by:</strong></p>
<ul>
<li class="has-line-data" data-line-start="38" data-line-end="39"><strong>Critical Inspection:</strong>
Analyzing query outputs and execution logs.</li>
<li class="has-line-data" data-line-start="39" data-line-end="40"><strong>Self-Correction:</strong>
Identifying
errors or suboptimal results and iteratively refining the SQL query or subsequent processing steps.</li>
<li class="has-line-data" data-line-start="40" data-line-end="42"><strong>Enhanced Robustness:</strong>
Providing a more reliable and intelligent way to interact with databases from natural language.</li>
</ul>
<h2 id="features" class="code-line" data-line-start=42 data-line-end=43><a id="Features_42"></a>Features</h2>
<ul>
<li class="has-line-data" data-line-start="44" data-line-end="45"><strong>Natural Language to SQL:</strong>
Translates user questions into executable SQL queries.</li>
<li class="has-line-data" data-line-start="45" data-line-end="46"><strong>Database Interaction:</strong>
Executes SQL queries against an in-memory SQLite database.</li>
<li class="has-line-data" data-line-start="46" data-line-end="47"><strong>Intelligent Parsing:</strong>
Processes and extracts relevant information from SQL query results.</li>
<li class="has-line-data" data-line-start="47" data-line-end="48"><strong>Self-Correction:</strong> Learns
from
execution errors and refines its approach.</li>
<li class="has-line-data" data-line-start="48" data-line-end="49"><strong>Multi-Table Querying:</strong>
Supports questions requiring joins across multiple tables.</li>
<li class="has-line-data" data-line-start="49" data-line-end="51"><strong>LLM Flexibility:</strong>
Integrates
with various Large Language Models (LLMs) via <code>smolagents</code>.</li>
</ul>
<h2 id="installation" class="code-line" data-line-start=51 data-line-end=52><a
id="Installation_51"></a>Installation</h2>
<p class="has-line-data" data-line-start="53" data-line-end="54">To get started, clone this repository and
install
the required dependencies:</p>
<pre><code class="has-line-data" data-line-start="56" data-line-end="60" class="language-bash">git <span class="hljs-built_in">clone</span> https://github.com/your-username/text-to-sql-agent.git
<span class="hljs-built_in">cd</span> text-to-sql-agent
pip install smolagents python-dotenv sqlalchemy --upgrade -q
</code></pre>
<p class="has-line-data" data-line-start="4" data-line-end="5"><strong>Note:</strong> To interact with Large
Language Models via inference providers (e.g., Hugging Face Inference API), you’ll need a valid
authentication token set as an environment variable, typically <code>HF_TOKEN</code>.</p>
<h2 class="code-line" data-line-start=6 data-line-end=7><a id="Project_Structure_6"></a>Project Structure</h2>
<p class="has-line-data" data-line-start="8" data-line-end="9">The core logic of this project is encapsulated in
<code>text_to_sql.py</code>.
</p>
<pre><code class="has-line-data" data-line-start="11" data-line-end="15" class="language-text">.
├── README.md
└── text_to_sql.py
</code></pre>
<h2 class="code-line" data-line-start=16 data-line-end=17><a id="Usage_16"></a>Usage</h2>
<p class="has-line-data" data-line-start="18" data-line-end="19">This section walks through the
<code>text_to_sql.py</code> script, explaining each part of building and using the agent.
</p>
<h3 class="code-line" data-line-start=20 data-line-end=21><a id="Setup_and_Dependencies_20"></a>Setup and
Dependencies</h3>
<p class="has-line-data" data-line-start="22" data-line-end="23">First, load your environment variables,
including your LLM token.</p>
<pre><code class="has-line-data" data-line-start="25" data-line-end="45" class="language-python"><span class="hljs-comment"># text_to_sql.py</span>
<span class="hljs-keyword">from</span> dotenv <span class="hljs-keyword">import</span> load_dotenv
load_dotenv()
<span class="hljs-keyword">from</span> sqlalchemy <span class="hljs-keyword">import</span> (
create_engine,
MetaData,
Table,
Column,
String,
Integer,
Float,
insert,
inspect,
text,
)
<span class="hljs-keyword">from</span> smolagents <span class="hljs-keyword">import</span> tool, CodeAgent, InferenceClientModel
<span class="hljs-comment"># ... (rest of the code)</span>
</code></pre>
<h3 class="code-line" data-line-start=46 data-line-end=47><a id="Database_Initialization_46"></a>Database
Initialization</h3>
<p class="has-line-data" data-line-start="48" data-line-end="49">We set up an in-memory SQLite database using
SQLAlchemy, defining <code>receipts</code> and <code>waiters</code> tables and populating them with sample
data.</p>
<pre><code class="has-line-data" data-line-start="51" data-line-end="88" class="language-python"><span class="hljs-comment"># text_to_sql.py</span>
engine = create_engine(<span class="hljs-string">"sqlite:///:memory:"</span>)
metadata_obj = MetaData()
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">insert_rows_into_table</span><span class="hljs-params">(rows, table, engine=engine)</span>:</span>
<span class="hljs-keyword">for</span> row <span class="hljs-keyword">in</span> rows:
stmt = insert(table).values(**row)
<span class="hljs-keyword">with</span> engine.begin() <span class="hljs-keyword">as</span> connection:
connection.execute(stmt)
<span class="hljs-comment"># Define the 'receipts' table</span>
receipts = Table(
<span class="hljs-string">"receipts"</span>,
metadata_obj,
Column(<span class="hljs-string">"receipt_id"</span>, Integer, primary_key=<span class="hljs-keyword">True</span>),
Column(<span class="hljs-string">"customer_name"</span>, String(<span class="hljs-number">255</span>)), <span class="hljs-comment"># Adjusted from String(16) for longer names</span>
Column(<span class="hljs-string">"price"</span>, Float),
Column(<span class="hljs-string">"tip"</span>, Float),
)
metadata_obj.create_all(engine)
<span class="hljs-comment"># Sample data for 'receipts'</span>
rows = [
{<span class="hljs-string">"receipt_id"</span>: <span class="hljs-number">1</span>, <span class="hljs-string">"customer_name"</span>: <span class="hljs-string">"Alan Payne"</span>, <span class="hljs-string">"price"</span>: <span class="hljs-number">12.06</span>, <span class="hljs-string">"tip"</span>: <span class="hljs-number">1.20</span>},
{<span class="hljs-string">"receipt_id"</span>: <span class="hljs-number">2</span>, <span class="hljs-string">"customer_name"</span>: <span class="hljs-string">"Alex Mason"</span>, <span class="hljs-string">"price"</span>: <span class="hljs-number">23.86</span>, <span class="hljs-string">"tip"</span>: <span class="hljs-number">0.24</span>},
{<span class="hljs-string">"receipt_id"</span>: <span class="hljs-number">3</span>, <span class="hljs-string">"customer_name"</span>: <span class="hljs-string">"Woodrow Wilson"</span>, <span class="hljs-string">"price"</span>: <span class="hljs-number">53.43</span>, <span class="hljs-string">"tip"</span>: <span class="hljs-number">5.43</span>},
{<span class="hljs-string">"receipt_id"</span>: <span class="hljs-number">4</span>, <span class="hljs-string">"customer_name"</span>: <span class="hljs-string">"Margaret James"</span>, <span class="hljs-string">"price"</span>: <span class="hljs-number">21.11</span>, <span class="hljs-string">"tip"</span>: <span class="hljs-number">1.00</span>},
]
insert_rows_into_table(rows, receipts)
<span class="hljs-comment"># Print table schema (for LLM context)</span>
inspector = inspect(engine)
columns_info = [(col[<span class="hljs-string">"name"</span>], col[<span class="hljs-string">"type"</span>]) <span class="hljs-keyword">for</span> col <span class="hljs-keyword">in</span> inspector.get_columns(<span class="hljs-string">"receipts"</span>)]
table_description = <span class="hljs-string">"Columns:\n"</span> + <span class="hljs-string">"\n"</span>.join([f<span class="hljs-string">" - {name}: {col_type}"</span> <span class="hljs-keyword">for</span> name, col_type <span class="hljs-keyword">in</span> columns_info])
print(table_description)```
</code></pre>
<b>Output:</b>
<p class="has-line-data" data-line-start="0" data-line-end="1">Columns:</p>
<p class="has-line-data" data-line-start="2" data-line-end="6">receipt_id: INTEGER<br>
customer_name: VARCHAR(255)<br>
price: FLOAT<br>
tip: FLOAT</p>
<h3 class="code-line" data-line-start=7 data-line-end=8><a id="Creating_the_SQL_Tool_7"></a>Creating the SQL
Tool</h3>
<p class="has-line-data" data-line-start="9" data-line-end="10">The <code>sql_engine</code> function acts as the
agent’s interface to the database. Its detailed docstring provides the LLM with crucial information about
its functionality and the database schema.</p>
<pre><code class="has-line-data" data-line-start="12" data-line-end="37" class="language-python"><span class="hljs-comment"># text_to_sql.py</span>
<span class="hljs-decorator">@tool</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">sql_engine</span><span class="hljs-params">(query: str)</span> -&gt; <span class="hljs-title">str</span>:</span>
<span class="hljs-string">"""
Enables execution of SQL queries against the database.
Outputs the query results as a formatted string.
Known tables and their column structures:
Table 'receipts':
Columns:
- receipt_id: INTEGER (Primary Key)
- customer_name: VARCHAR(255)
- price: FLOAT
- tip: FLOAT
Args:
query: The precise SQL query string to be executed.
Example: "SELECT customer_name FROM receipts WHERE price &gt; 10.0;"
"""</span>
output = <span class="hljs-string">""</span>
<span class="hljs-keyword">with</span> engine.connect() <span class="hljs-keyword">as</span> con:
rows = con.execute(text(query))
<span class="hljs-keyword">for</span> row <span class="hljs-keyword">in</span> rows:
output += <span class="hljs-string">"\n"</span> + str(row)
<span class="hljs-keyword">return</span> output</code></pre>
<h3 id="instantiating-the-agent-single-table" class="code-line" data-line-start=190 data-line-end=191><a
id="Instantiating_the_Agent_Single_Table_190"></a>Instantiating the Agent (Single Table)</h3>
<p class="has-line-data" data-line-start="192" data-line-end="193">We create a <code>CodeAgent</code> and
provide it
with the <code>sql_engine</code> tool and an LLM (e.g., <code>meta-llama/Llama-3.1-8B-Instruct</code>).</p>
<pre><code class="has-line-data" data-line-start="195" data-line-end="201" class="language-python"><span class="hljs-comment"># text_to_sql.py</span>
agent = CodeAgent(
tools=[sql_engine],
model=InferenceClientModel(model_id=<span class="hljs-string">"meta-llama/Llama-3.1-8B-Instruct"</span>),
)
</code></pre>
<h3 id="querying-the-agent-single-table" class="code-line" data-line-start=202 data-line-end=203><a
id="Querying_the_Agent_Single_Table_202"></a>Querying
the Agent: Single Table</h3>
<p class="has-line-data" data-line-start="204" data-line-end="205">Now, we can ask the agent a question and
observe
its problem-solving process, including self-correction.</p>
<pre><code class="has-line-data" data-line-start="207" data-line-end="210" class="language-python"><span class="hljs-comment"># text_to_sql.py</span>
agent.run(<span class="hljs-string">"Can you give me the name of the client who got the most expensive receipt?"</span>)
</code></pre>
<p class="has-line-data" data-line-start="211" data-line-end="213"><strong>Expected Agent Output
(summarized):</strong><br>
The agent will attempt several SQL queries, potentially encountering syntax errors or parsing issues with
the
raw string output from <code>sql_engine</code>. Through iterative self-correction, it will eventually
generate
and execute <code>SELECT MAX(price), customer_name FROM receipts ORDER BY price DESC LIMIT 1</code>, parse
the
result <code>(53.43, 'Woodrow Wilson')</code>, and identify ‘Woodrow Wilson’.</p>
<h3 id="extending-for-table-joins" class="code-line" data-line-start=214 data-line-end=215><a
id="Extending_for_Table_Joins_214"></a>Extending
for
Table Joins</h3>
<p class="has-line-data" data-line-start="216" data-line-end="217">To handle more complex queries, we add a
<code>waiters</code> table and update the <code>sql_engine</code> tool’s description to include its schema.
</p>
<pre><code class="has-line-data" data-line-start="219" data-line-end="252" class="language-python"><span class="hljs-comment"># text_to_sql.py</span>
<span class="hljs-comment"># Define the 'waiters' table</span>
waiters = Table(
<span class="hljs-string">"waiters"</span>,
metadata_obj,
Column(<span class="hljs-string">"receipt_id"</span>, Integer, primary_key=<span class="hljs-keyword">True</span>),
Column(<span class="hljs-string">"waiter_name"</span>, String(<span class="hljs-number">16</span>), primary_key=<span class="hljs-keyword">True</span>),
)
metadata_obj.create_all(engine)
<span class="hljs-comment"># Sample data for 'waiters'</span>
rows = [
{<span class="hljs-string">"receipt_id"</span>: <span class="hljs-number">1</span>, <span class="hljs-string">"waiter_name"</span>: <span class="hljs-string">"Corey Johnson"</span>},
{<span class="hljs-string">"receipt_id"</span>: <span class="hljs-number">2</span>, <span class="hljs-string">"waiter_name"</span>: <span class="hljs-string">"Michael Watts"</span>},
{<span class="hljs-string">"receipt_id"</span>: <span class="hljs-number">3</span>, <span class="hljs-string">"waiter_name"</span>: <span class="hljs-string">"Michael Watts"</span>},
{<span class="hljs-string">"receipt_id"</span>: <span class="hljs-number">4</span>, <span class="hljs-string">"waiter_name"</span>: <span class="hljs-string">"Margaret James"</span>},
]
insert_rows_into_table(rows, waiters)
<span class="hljs-comment"># Update the tool's description to include the new table</span>
updated_description = <span class="hljs-string">"""This tool allows performing SQL queries on the database, returning results as a string.
It can access the following tables:"""</span>
inspector = inspect(engine)
<span class="hljs-keyword">for</span> table <span class="hljs-keyword">in</span> [<span class="hljs-string">"receipts"</span>, <span class="hljs-string">"waiters"</span>]:
columns_info = [(col[<span class="hljs-string">"name"</span>], col[<span class="hljs-string">"type"</span>]) <span class="hljs-keyword">for</span> col <span class="hljs-keyword">in</span> inspector.get_columns(table)]
table_description = f<span class="hljs-string">"Table '{table}':\n"</span>
table_description += <span class="hljs-string">" Columns:\n"</span> + <span class="hljs-string">"\n"</span>.join([f<span class="hljs-string">" - {name}: {col_type}"</span> <span class="hljs-keyword">for</span> name, col_type <span class="hljs-keyword">in</span> columns_info])
updated_description += <span class="hljs-string">"\n\n"</span> + table_description
print(updated_description)
sql_engine.description = updated_description <span class="hljs-comment"># Update the tool's description</span>
</code></pre>
<p class="has-line-data" data-line-start="253" data-line-end="254"><strong>Output:</strong></p>
<pre><code class="has-line-data" data-line-start="256" data-line-end="271">This tool allows performing SQL queries on the database, returning results as a string.
It can access the following tables:
Table 'receipts':
Columns:
- receipt_id: INTEGER
- customer_name: VARCHAR(255)
- price: FLOAT
- tip: FLOAT
Table 'waiters':
Columns:
- receipt_id: INTEGER
- waiter_name: VARCHAR(16)
</code></pre>
<h3 id="querying-the-agent-multi-table" class="code-line" data-line-start=272 data-line-end=273><a
id="Querying_the_Agent_MultiTable_272"></a>Querying
the Agent: Multi-Table</h3>
<p class="has-line-data" data-line-start="274" data-line-end="275">We switch to a more powerful LLM
(<code>Qwen/Qwen2.5-Coder-32B-Instruct</code>) for this harder task.</p>
<pre><code class="has-line-data" data-line-start="277" data-line-end="285" class="language-python"><span class="hljs-comment"># text_to_sql.py</span>
agent = CodeAgent(
tools=[sql_engine],
model=InferenceClientModel(model_id=<span class="hljs-string">"Qwen/Qwen2.5-Coder-32B-Instruct"</span>),
)
agent.run(<span class="hljs-string">"Which waiter received the highest total amount in tips?"</span>)
</code></pre>
<p class="has-line-data" data-line-start="286" data-line-end="288"><strong>Expected Agent Output
(summarized):</strong><br>
The agent will formulate a SQL query to join <code>waiters</code> and <code>receipts</code> tables (e.g.,
<code>SELECT w.waiter_name, r.tip FROM waiters w JOIN receipts r ON w.receipt_id = r.receipt_id</code>). It
will
then process the results in Python to sum tips per waiter and identify “Michael Watts” as having the highest
total tips.
</p>
<h2 id="how-it-works" class="code-line" data-line-start=289 data-line-end=290><a id="How_it_Works_289"></a>How
it Works</h2>
<p class="has-line-data" data-line-start="291" data-line-end="292">The <code>smolagents</code>
<code>CodeAgent</code> operates on the <strong>ReAct (Reasoning + Acting)</strong> framework:
</p>
<ol>
<li class="has-line-data" data-line-start="293" data-line-end="294"><strong>Reasoning (LLM as
Brain):</strong> A
Large Language Model (e.g., Llama-3.1, Qwen2.5) interprets the natural language prompt and decides on a
course of action.</li>
<li class="has-line-data" data-line-start="294" data-line-end="295"><strong>Acting (Tools as
Hands):</strong> If
an external interaction is needed (like querying a database), the LLM generates Python code to call a
registered <code>@tool</code> (e.g., <code>sql_engine(&quot;...&quot;)</code>). The tool’s
<code>docstring</code> (description) is critical for the LLM to understand its capabilities.
</li>
<li class="has-line-data" data-line-start="295" data-line-end="296"><strong>Observation &amp;
Feedback:</strong>
The generated code is executed. The output (e.g., database results, error messages) is fed back to the
LLM.
</li>
<li class="has-line-data" data-line-start="296" data-line-end="298"><strong>Self-Correction &amp;
Iteration:</strong> The LLM analyzes the feedback. If there’s an error or the result is
unsatisfactory,
it refines its reasoning and generates new code, iterating until the task is complete or deemed
unfeasible.
</li>
</ol>
<p class="has-line-data" data-line-start="298" data-line-end="299">This iterative process allows the agent to
solve
complex problems and recover from errors, making it more robust than traditional direct translation methods.
</p>
<h2 id="key-concepts-demonstrated" class="code-line" data-line-start=300 data-line-end=301><a
id="Key_Concepts_Demonstrated_300"></a>Key
Concepts
Demonstrated</h2>
<ul>
<li class="has-line-data" data-line-start="302" data-line-end="303"><strong>Agentic Frameworks:</strong>
Using
<code>smolagents</code> to orchestrate LLM interactions and tool use.
</li>
<li class="has-line-data" data-line-start="303" data-line-end="304"><strong>Tool Creation:</strong> Defining
custom Python functions as tools for agents, complete with detailed descriptions.</li>
<li class="has-line-data" data-line-start="304" data-line-end="305"><strong>Dynamic Tool
Descriptions:</strong>
Updating tool information to reflect changes in available data (e.g., new database tables).</li>
<li class="has-line-data" data-line-start="305" This will ensure that long lidata-line-end="306"><strong>LLM
Integration:</strong>
Leveraging
various LLMs for different levels of reasoning complexity.</li>
<li class="has-line-data" data-line-start="306" data-line-end="307"><strong>SQLAlchemy:</strong>
Programmatically interacting with databases in Python.</li>
<li class="has-line-data" data-line-start="307" data-line-end="309"><strong>ReAct Paradigm:</strong> The
iterative cycle of reasoning, acting, and observation that enables self-correction.</li>
</ul>
<h2 id="contibuting" class="code-line" data-line-start=309 data-line-end=310><a
id="Contributing_309"></a>Contributing</h2>
<p class="has-line-data" data-line-start="311" data-line-end="312">Feel free to open issues or submit pull
requests
if you have suggestions or improvements!</p>
<h2 id="license" class="code-line" data-line-start=313 data-line-end=314><a id="License_313"></a>License</h2>
<p class="has-line-data" data-line-start="315" data-line-end="316">This project is open-sourced Apache 2.0
License. See the <code>LICENSE</code> file for more details.</p>
</div>
</body>
</html>