Tech · Build
Build a chatbot that answers from your own files and database
A step-by-step guide to a chatbot that answers customer questions from your own files and your own database, without making things up. About 200 lines of Python and one small web page.
A basic chatbot that only uses an AI model's general knowledge is not much use on a real website. It does not know your products, your prices or your return policy, so it guesses. And a guessing shop assistant is worse than none.
The fix is to connect the AI to your own information. In this guide we build a chatbot for a simple online shop that can:
- Answer policy questions (shipping, returns, support hours) from text files in a folder
- Search the product catalogue in a database, including price and stock
- Look up an order's status, safely, only when the customer gives the order number and email
- Say "I don't know" instead of inventing answers
The same pattern works for any simple website: a clinic, a school, a restaurant or a small business.
How it works
The key idea is called tool use (also known as function calling). You give the AI model a list of tools, with a short description of each. When a customer asks a question, the model decides which tool it needs, your code runs that tool against your real data, and the model writes the answer from the result.
Customer → Chat window → Your server → AI model
↑ │
│ "call search_products('shoes')"
│ ↓
Your database / files ←┘
│
result goes back to the model → friendly answer
Notice what the AI never gets: direct access to your database. It can only ask your code to run the specific, safe tools you wrote. This is the most important design choice in the whole project.
What you need
- Python 3.10 or newer
- An API key from an AI provider that supports tool use. This guide uses Anthropic's Claude API; OpenAI, Google Gemini and local models through Ollama all support the same idea with slightly different code.
- About an hour
Project layout:
shopbot/
├── setup_db.py # creates a sample database
├── bot.py # tools + AI conversation loop
├── app.py # small web server
├── docs/ # your policy files (.md or .txt)
│ ├── shipping.md
│ ├── returns.md
│ └── faq.md
└── static/
└── index.html # your website with the chat widget
Install the two libraries and set your API key:
pip install anthropic flask
export ANTHROPIC_API_KEY="your-key-here" # Windows: set ANTHROPIC_API_KEY=your-key-here
Step 1: Create a sample database
Most small sites already have a database. For this guide we create a tiny SQLite one with products and orders. Replace this with your real database later.
import sqlite3
db = sqlite3.connect("shop.db")
db.executescript("""
DROP TABLE IF EXISTS products;
DROP TABLE IF EXISTS orders;
CREATE TABLE products (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
category TEXT,
price REAL,
stock INTEGER,
description TEXT
);
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
email TEXT NOT NULL,
status TEXT,
total REAL,
placed_on TEXT
);
INSERT INTO products (name, category, price, stock, description) VALUES
('Cotton T-shirt', 'Clothing', 19.99, 40, 'Soft 100% cotton tee, sizes S to XL'),
('Running Shoes', 'Footwear', 79.00, 12, 'Lightweight shoes with cushioned sole'),
('Steel Water Bottle', 'Accessories', 24.50, 0, 'Keeps drinks cold for 24 hours'),
('Yoga Mat', 'Fitness', 35.00, 8, 'Non-slip 6 mm mat with carry strap'),
('Wireless Earbuds', 'Electronics', 59.99, 25, '8-hour battery, noise reduction');
INSERT INTO orders (id, email, status, total, placed_on) VALUES
(1001, 'asha@example.com', 'Shipped', 98.99, '2026-10-20'),
(1002, 'ravi@example.com', 'Processing', 35.00, '2026-10-26');
""")
db.commit()
db.close()
print("shop.db created")
Run it once with python setup_db.py.
Step 2: Add your files
Put your policies, FAQs and other help text as plain .md or .txt files in the docs folder. Short paragraphs work best, because the bot searches paragraph by paragraph. For example, docs/shipping.md:
# Shipping
We ship to all US states. Standard shipping takes 3 to 5 business days and is free on orders over $50. Express shipping takes 1 to 2 business days and costs $12.
Orders placed before 2 pm Pacific time on a business day ship the same day.
Step 3: Build the tools and the conversation loop
This is the heart of the chatbot. Read it in four parts:
- Local files: load every file in
docs/, split it into paragraphs and find the ones that share the most words with the question. - Database: two read-only functions, one to search products and one to look up an order. Both use parameterised queries (the
?placeholders), which protects you from SQL injection. - Tool descriptions: tell the AI what each tool does and what inputs it needs. Clear descriptions matter; the model chooses tools based on them.
- The loop: send the conversation to the model. If it asks for a tool, run it and send back the result. Repeat until it gives a final answer.
import json
import re
import sqlite3
from pathlib import Path
import anthropic
MODEL = "claude-sonnet-5-5" # or any model that supports tool use
DB_PATH = "shop.db"
DOCS_DIR = Path("docs")
client = anthropic.Anthropic() # reads ANTHROPIC_API_KEY from the environment
SYSTEM_PROMPT = """You are the friendly shopping assistant for Your Shop.
Answer only questions about our products, orders and store policies.
Always use the tools to look up facts. Never invent products, prices,
stock levels, order details or policies. If the tools do not give you
the answer, say you don't know and suggest emailing support@yourshop.example.
To check an order, you need BOTH the order number and the email used to
place it. Keep answers short and friendly."""
# ---------- Part 1: your local files ----------
def load_docs():
"""Split every .md/.txt file in docs/ into paragraphs."""
chunks = []
for path in DOCS_DIR.glob("*"):
if path.suffix.lower() not in (".md", ".txt"):
continue
for para in path.read_text(encoding="utf-8").split("\n\n"):
if para.strip():
chunks.append({"source": path.name, "text": para.strip()})
return chunks
DOC_CHUNKS = load_docs()
def words(text):
return set(re.findall(r"[a-z0-9]+", text.lower()))
def search_docs(query, top_k=3):
"""Return the paragraphs that share the most words with the query."""
q = words(query)
scored = [(len(q & words(c["text"])), c) for c in DOC_CHUNKS]
scored = [s for s in scored if s[0] > 0]
scored.sort(key=lambda s: s[0], reverse=True)
return [c for _, c in scored[:top_k]] or [{"text": "No matching policy found."}]
# ---------- Part 2: your database ----------
def db():
# Read-only connection: the bot can look, but never change data.
conn = sqlite3.connect(f"file:{DB_PATH}?mode=ro", uri=True)
conn.row_factory = sqlite3.Row
return conn
def search_products(query="", max_price=None):
sql = """SELECT name, category, price, stock, description FROM products
WHERE (name LIKE ? OR category LIKE ? OR description LIKE ?)"""
like = f"%{query}%"
params = [like, like, like]
if max_price is not None:
sql += " AND price <= ?"
params.append(max_price)
with db() as conn:
rows = conn.execute(sql + " LIMIT 10", params).fetchall()
return [dict(r) for r in rows] or [{"result": "No matching products."}]
def get_order_status(order_id, email):
with db() as conn:
row = conn.execute(
"SELECT id, status, total, placed_on FROM orders WHERE id = ? AND lower(email) = lower(?)",
(order_id, email),
).fetchone()
return dict(row) if row else {"result": "No order found with that number and email."}
# ---------- Part 3: tell the AI what tools it has ----------
TOOLS = [
{
"name": "search_products",
"description": "Search the product catalogue by keyword (name, category or description). "
"Returns name, category, price, stock and description.",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "Keyword, e.g. 'shoes' or 'electronics'"},
"max_price": {"type": "number", "description": "Optional maximum price in USD"},
},
"required": ["query"],
},
},
{
"name": "get_order_status",
"description": "Look up an order. Requires the order number and the customer's email.",
"input_schema": {
"type": "object",
"properties": {
"order_id": {"type": "integer"},
"email": {"type": "string"},
},
"required": ["order_id", "email"],
},
},
{
"name": "search_docs",
"description": "Search store policies and FAQs: shipping, returns, refunds, support, gift wrap.",
"input_schema": {
"type": "object",
"properties": {"query": {"type": "string"}},
"required": ["query"],
},
},
]
TOOL_FUNCTIONS = {
"search_products": search_products,
"get_order_status": get_order_status,
"search_docs": search_docs,
}
def run_tool(name, args):
try:
return TOOL_FUNCTIONS[name](**args)
except Exception as e: # never crash the chat because of one bad call
return {"error": str(e)}
# ---------- Part 4: the conversation loop ----------
def chat(history):
"""history: list of {"role": "user"|"assistant", "content": "text"}"""
messages = list(history)
for _ in range(5): # safety limit on tool rounds
response = client.messages.create(
model=MODEL,
max_tokens=1024,
system=SYSTEM_PROMPT,
tools=TOOLS,
messages=messages,
)
messages.append({"role": "assistant", "content": response.content})
if response.stop_reason != "tool_use":
return "".join(b.text for b in response.content if b.type == "text")
results = []
for block in response.content:
if block.type == "tool_use":
output = run_tool(block.name, block.input)
results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": json.dumps(output),
})
messages.append({"role": "user", "content": results})
return "Sorry, I couldn't finish that. Please try rephrasing your question."
Two details are worth pointing out. The system prompt tells the model to always use the tools and to admit when it does not know. And the database connection opens in read-only mode (mode=ro), so even a confused or manipulated model cannot change or delete your data.
Step 4: A small web server
The browser must never talk to the AI provider directly, or your secret API key would be visible to anyone. Instead, the page talks to your own server, which holds the key.
from flask import Flask, jsonify, request, send_from_directory
from bot import chat
app = Flask(__name__, static_folder="static")
@app.get("/")
def home():
return send_from_directory("static", "index.html")
@app.post("/chat")
def chat_endpoint():
history = request.get_json().get("messages", [])[-20:] # keep the last 20 turns
if not history or history[-1].get("role") != "user":
return jsonify({"reply": "Please type a question."}), 400
return jsonify({"reply": chat(history)})
if __name__ == "__main__":
app.run(port=5000, debug=True)
Step 5: Add the chat widget to your site
This is a plain HTML page with a floating "Chat with us" button. Copy the button, chat box, styles and script into your own site's pages.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Your Shop</title>
<style>
body { font-family: system-ui, sans-serif; margin: 0; padding: 24px; }
#chat-btn { position: fixed; right: 20px; bottom: 20px; border: 0; border-radius: 999px;
padding: 14px 20px; background: #1f6feb; color: #fff; font-size: 16px; cursor: pointer; }
#chat-box { position: fixed; right: 20px; bottom: 80px; width: min(360px, calc(100vw - 40px));
height: 460px; display: none; flex-direction: column; background: #fff;
border: 1px solid #ddd; border-radius: 14px; box-shadow: 0 8px 30px rgba(0,0,0,.15); }
#log { flex: 1; overflow-y: auto; padding: 12px; display: flex; flex-direction: column; gap: 8px; }
.msg { padding: 8px 12px; border-radius: 12px; max-width: 85%; white-space: pre-wrap; }
.user { align-self: flex-end; background: #1f6feb; color: #fff; }
.bot { align-self: flex-start; background: #f0f2f5; }
form { display: flex; gap: 8px; padding: 10px; border-top: 1px solid #eee; }
input { flex: 1; padding: 10px; border: 1px solid #ccc; border-radius: 8px; font-size: 15px; }
</style>
</head>
<body>
<h1>Welcome to Your Shop</h1>
<p>Your normal website content goes here.</p>
<button id="chat-btn">Chat with us</button>
<div id="chat-box">
<div id="log"><div class="msg bot">Hi! Ask me about products, orders, shipping or returns.</div></div>
<form id="chat-form">
<input id="chat-input" placeholder="Type your question…" autocomplete="off">
<button>Send</button>
</form>
</div>
<script>
const box = document.getElementById("chat-box");
const log = document.getElementById("log");
const input = document.getElementById("chat-input");
const history = [];
document.getElementById("chat-btn").onclick = () => {
box.style.display = box.style.display === "flex" ? "none" : "flex";
input.focus();
};
function add(text, who) {
const div = document.createElement("div");
div.className = "msg " + who;
div.textContent = text; // textContent, never innerHTML, to avoid script injection
log.appendChild(div);
log.scrollTop = log.scrollHeight;
return div;
}
document.getElementById("chat-form").onsubmit = async (e) => {
e.preventDefault();
const text = input.value.trim();
if (!text) return;
input.value = "";
add(text, "user");
history.push({ role: "user", content: text });
const thinking = add("…", "bot");
try {
const res = await fetch("/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ messages: history }),
});
const data = await res.json();
thinking.textContent = data.reply;
history.push({ role: "assistant", content: data.reply });
} catch {
thinking.textContent = "Sorry, something went wrong. Please try again.";
history.pop();
}
};
</script>
</body>
</html>
Step 6: Run and test it
python setup_db.py
python app.py
Open http://localhost:5000, click the chat button and try questions like these:
- "Do you have running shoes?" (uses the database)
- "Anything under $30?" (database with a price filter)
- "How long does express shipping take?" (uses your files)
- "Where is order 1001? My email is asha@example.com" (order lookup)
- "Where is order 1001?" with the wrong email (should politely refuse)
- "Do you sell laptops?" (should say no, not invent one)
The last two tests matter most. A good shop bot is judged by what it refuses to make up.
Making it production-ready
The code above is deliberately simple. Before you put it on a live site, work through this list:
- Better file search. The word-matching search is fine for a few pages. For hundreds of documents, switch to embeddings and a vector store, which find paragraphs by meaning rather than exact words. This is what people call RAG (retrieval-augmented generation).
- Protect customer data. Only reveal order details after verifying the customer. For logged-in users, use their session instead of asking for an email.
- Limit costs and abuse. Add rate limits per visitor, cap message length, and set a monthly spending limit with your AI provider.
- Hosting. Static hosts such as GitHub Pages cannot run Python. Host the server on a service that can (for example Render, Railway, Azure App Service or a small VPS) and point the widget's
fetchcall at that address. - Logging and review. Log questions and answers (without personal data) and read them weekly. They show you which policies are missing from your docs.
- A human handoff. Always give customers a way to reach a person.
Adapting it to any website
The pattern stays the same whatever the site. Only the tools change:
- Clinic: opening hours and services from files; appointment availability from the booking database.
- School: admission rules and fees from files; event calendar from the database.
- Restaurant: menu and allergen info from the database; booking policy from files.
Write small, safe, read-only tools for the data you want to expose, describe them clearly, and let the model choose. That is all an "AI agent" on a website really is: a model, a loop, and well-guarded tools.