Tech · Build

Build a chatbot that answers from your own files and database

October 2026 · 9 min read

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:

The same pattern works for any simple website: a clinic, a school, a restaurant or a small business.

Download the full working code: shopbot.zip (Python, SQLite, Flask).

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

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:

  1. Local files: load every file in docs/, split it into paragraphs and find the ones that share the most words with the question.
  2. 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.
  3. Tool descriptions: tell the AI what each tool does and what inputs it needs. Clear descriptions matter; the model chooses tools based on them.
  4. 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:

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:

Adapting it to any website

The pattern stays the same whatever the site. Only the tools change:

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.

Key takeaway: never let the AI be the source of truth. Let it be the friendly voice that reads your truth back to the customer.
← All Tech posts