π Introduction
Imagine asking your database:
βShow me the top 10 customers by revenue.β
β¦and instantly getting results without writing a single SQL query.
Thatβs exactly what an AI-powered database agent can do.
In this tutorial, weβll build a secure PostgreSQL AI Agent using:
- π§© LangChain for agent orchestration
- π¦ Ollama for running local LLMs
- π PostgreSQL as the database
- π‘οΈ A custom SQL safety layer to block destructive queries
By the end, youβll have a local AI assistant capable of converting natural language into SQL queries safely and efficiently.
π» Source Code:
postgres-agent GitHub Repository
π€ What is a PostgreSQL AI Agent?
A PostgreSQL AI Agent is essentially an LLM-powered assistant that can:
- Understand natural language
- Generate SQL queries
- Execute them against PostgreSQL
- Return readable results
Think of it like ChatGPT connected to your database β but with guardrails and controlled execution.
βοΈ Tech Stack
| Tool | Purpose |
|---|---|
| LangChain | Agent orchestration and tool calling |
| Ollama | Run LLMs locally without API costs |
| langchain-ollama | LangChain integration for Ollama |
| psycopg2 | PostgreSQL adapter for Python |
| Python | Core application runtime |
π§± System Architecture
π Step 1: Connect to PostgreSQL
Weβll start by creating a PostgreSQL connection using psycopg2.
import psycopg2
DB_CONFIG = {
"host": "localhost",
"port": 5432,
"database": "postgres",
"user": "postgres",
"password": "root",
}
def get_connection():
return psycopg2.connect(**DB_CONFIG)
π Production Tip:
Never hardcode credentials in production. Use environment variables or a secret manager.
π οΈ Step 2: Create LangChain Tools
LangChain agents interact with systems using tools.
These tools expose safe and structured database operations to the LLM.
π Tool: List Database Tables
@tool
def list_tables() -> str:
"""List all tables in the database."""
conn = get_connection()
try:
cur = conn.cursor()
cur.execute("""
SELECT table_name FROM information_schema.tables
WHERE table_schema = 'public'
""")
tables = [row[0] for row in cur.fetchall()]
return f"Tables: {', '.join(tables)}" if tables else "No tables found."
finally:
conn.close()
This gives the agent dynamic schema awareness instead of relying on hardcoded table names.
π Tool: Fetch Table Schema
@tool
def get_table_schema(table_name: str) -> str:
"""Get the schema (columns and types) of a specific table."""
conn = get_connection()
try:
cur = conn.cursor()
cur.execute("""
SELECT column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = %s
ORDER BY ordinal_position
""", (table_name,))
columns = cur.fetchall()
if not columns:
return f"Table '{table_name}' not found."
schema = "\n".join([
f" {col[0]} ({col[1]}, nullable={col[2]})"
for col in columns
])
return f"Schema for '{table_name}':\n{schema}"
finally:
conn.close()
This helps the LLM understand:
- Column names
- Data types
- Nullability
- Table structure
β‘ Tool: Execute SQL Queries
@tool
def execute_sql(query: str) -> str:
"""Execute a SQL query against the PostgreSQL database and return results. Use this for SELECT queries."""
is_safe, reason = validate_read_only_sql(query)
if not is_safe:
return f"Safety Guard: Blocked query. {reason}"
conn = get_connection()
try:
cur = conn.cursor()
cur.execute(query)
if cur.description:
columns = [desc[0] for desc in cur.description]
rows = cur.fetchall()
if not rows:
return "Query returned no results."
result = " | ".join(columns) + "\n"
result += "\n".join([
" | ".join(str(v) for v in row)
for row in rows[:50]
])
if len(rows) > 50:
result += f"\n... ({len(rows)} total rows)"
return result
else:
conn.commit()
return f"Query executed successfully. Rows affected: {cur.rowcount}"
except Exception as e:
conn.rollback()
return f"SQL Error: {e}"
finally:
conn.close()
This becomes the core execution engine of the AI agent.
π‘οΈ Step 3: Add a SQL Safety Guard
Allowing an LLM to execute unrestricted SQL is dangerous.
Thatβs why every query should pass through a validation layer before execution.
β Allowed vs Blocked Queries
| Allowed | Blocked |
|---|---|
SELECT |
INSERT |
WITH |
UPDATE |
SHOW |
DELETE |
EXPLAIN |
DROP |
| β | ALTER |
| β | TRUNCATE |
π§Ό Query Normalization
Before validation, we sanitize queries by removing:
- Comments
- Hidden injections
- String-based bypass attempts
This significantly improves safety when working with LLM-generated SQL.
π§ Step 4: Setup Ollama for Local LLMs
Ollama makes it incredibly easy to run large language models locally.
No OpenAI API.
No usage limits.
No cloud dependency.
Useful links:
π½ Pull the Model
ollama pull qwen2.5:7b
Verify installation:
ollama list
π§© Recommended Models for SQL Agents
| Model | Command | Notes |
|---|---|---|
| Qwen 2.5 7B | ollama pull qwen2.5:7b |
Used in this tutorial |
| Llama 3.1 8B | ollama pull llama3.1 |
Strong general-purpose model |
| DeepSeek-R1 7B | ollama pull deepseek-r1 |
Excellent reasoning |
| Mistral 7B | ollama pull mistral |
Lightweight and fast |
π¦ Install LangChain Ollama Integration
pip install langchain-ollama
References:
βοΈ Configure ChatOllama
from langchain_ollama import ChatOllama
llm = ChatOllama(
model="qwen2.5:7b",
temperature=0
)
Setting temperature=0 helps generate more deterministic and reliable SQL queries.
Why Use Ollama?
- β Completely local execution
- β No API cost
- β Privacy-friendly
- β GPU acceleration support
- β Supports many open-source models
π Step 5: Create the LangChain Agent
tools = [list_tables, get_table_schema, execute_sql]
agent = create_agent(llm, tools)
LangChain enables the AI agent to:
- Select tools dynamically
- Chain multiple operations
- Reason step-by-step
- Generate context-aware SQL
π¬ Step 6: Create an Interactive Chat Loop
while True:
user_input = input("\nYou: ").strip()
if user_input.lower() in ("exit", "quit"):
print("Goodbye!")
break
if not user_input:
continue
This turns the application into a conversational SQL assistant.
π§Ύ Step 7: Add Debugging & Observability
Debugging AI agents becomes much easier when you can inspect tool calls and outputs.
def print_turn_details(messages: list[BaseMessage]) -> None:
final_response = ""
for message in messages:
if isinstance(message, AIMessage):
for tool_call in message.tool_calls:
tool_name = tool_call.get("name", "unknown_tool")
tool_args = format_tool_payload(
tool_call.get("args", {})
)
print(f"\nTool call: {tool_name}({tool_args})")
content = format_content(message.content).strip()
if content:
final_response = content
elif isinstance(message, ToolMessage):
tool_name = getattr(message, "name", None) or "tool"
tool_output = (
format_content(message.content).strip()
or "(no output)"
)
print(f"\nTool response [{tool_name}]: {tool_output}")
if final_response:
print(f"\nAgent: {final_response}")
else:
print("\nAgent: I couldn't generate a response.")
This helps you inspect:
- Tool invocations
- Tool outputs
- Agent reasoning flow
- Final responses
π§ͺ Example Queries
Try prompts like:
"List all tables""Show schema of users table""Get top 5 users by revenue""How many orders were placed last month?"
π Real-World Use Cases
This architecture can be extended into real production systems.
π AI Analytics Dashboards
Allow non-technical users to query business data using plain English.
π¬ Internal Data Chatbots
Integrate with Slack or Teams for self-serve analytics.
π§Ύ Automated Reporting
Generate recurring reports automatically using natural language prompts.
π’ SaaS Admin Tools
Provide operations teams with an AI-powered database interface.
π€ AI Copilots for Analysts
Speed up SQL generation and analytics workflows.
π― Final Thoughts
Youβve now built a secure and extensible PostgreSQL AI Agent powered by LangChain and Ollama.
The biggest takeaway here is that tool-based AI architecture gives LLMs structured access to databases without exposing unrestricted control.
Key Learnings
- LangChain simplifies AI agent orchestration
- Ollama enables local LLM execution
- SQL validation is essential for security
- Tool-driven agents are highly extensible
With a few additional improvements like authentication, query caching, and semantic memory, this can evolve into a powerful production-grade AI data assistant.
π¦ Full Working Source Code:
postgres-agent GitHub Repository


Top comments (0)