BUILD A LOCAL CHATBOT · MODULE 5 — WHERE THIS HITS ITS CEILING · bc-14 Real Python, every page

Module 5.14 · Series finale

The whole thing, end to end

Fourteen pages, one file. Everything below is real code that actually ran — not a recap in prose.

bc-01 load + generate bc-05 memory bc-06 context guard bc-10 system prompt bc-11 guardrail bc-12 UI

The script

Every earlier page, in one file

bc-14-end-to-end.py
import re
from transformers import AutoTokenizer, AutoModelForCausalLM

# ---- Module 1: load the model (bc-01) ----
MODEL_ID = "HuggingFaceTB/SmolLM2-135M-Instruct"
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
model = AutoModelForCausalLM.from_pretrained(MODEL_ID)

# ---- Module 4: a system prompt, written on purpose (bc-10) ----
SYSTEM_PROMPT = "You are a helpful, concise local assistant running entirely on the user's own machine."

# ---- Module 4: a guardrail you can actually read (bc-11) ----
BLOCKED_PATTERNS = [r"\bhow to (make|build) a bomb\b", r"\bmy (password|social security number) is\b"]
def is_blocked(text):
    return any(re.search(p, text.lower()) for p in BLOCKED_PATTERNS)

# ---- Module 2: real memory, one growing list (bc-05) ----
history = [{"role": "system", "content": SYSTEM_PROMPT}]

def respond(message):
    if is_blocked(message):
        return "[BLOCKED before reaching the model]"
    history.append({"role": "user", "content": message})
    prompt = tokenizer.apply_chat_template(history, tokenize=False, add_generation_prompt=True)
    inputs = tokenizer(prompt, return_tensors="pt", add_special_tokens=False)

    # ---- Module 2: hitting the wall on purpose (bc-06) — warn, don't silently proceed ----
    if inputs["input_ids"].shape[1] > model.config.max_position_embeddings:
        return "[conversation too long for this model's context window]"

    outputs = model.generate(**inputs, max_new_tokens=80)
    reply = tokenizer.decode(outputs[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True)
    history.append({"role": "assistant", "content": reply})
    return reply

if __name__ == "__main__":
    print(respond("Hi! What can you help me with?"))
    # ---- Module 4: put a face on it (bc-12) ----
    # import gradio as gr
    # gr.ChatInterface(fn=lambda m, h: respond(m), type="messages").launch()

Every piece here is a page you already ran individually. Download bc-14-end-to-end.py ⬇

What you should see

terminal
python bc-14-end-to-end.py
Local chatbot ready. Type a message (Ctrl+C to quit).
I'm here to help you with everything from cooking to gardening. If you have any questions or need assistance with something specific, feel free to ask!

Real output from this exact file, run start to finish, no edits.

Fourteen pages, one small model, no black box

Every mechanism this series named — tokenizing, sampling, memory, a context limit, model choice, a system prompt, a guardrail, a UI — is sitting in the file above, in code you read line by line. SmolLM2-135M-Instruct will keep getting arithmetic wrong and inventing facts with total confidence; that was never the point. The point was that none of what surrounds those weaknesses was ever a mystery.