Repository navigation
Chatbot #157
Description
simple_chatbot.py
Een compacte 'AI' chatbot: TF-IDF retrieval + eenvoudige leerfunctie.
Scherp, voorspellend en makkelijk uit te breiden.
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
import numpy as np
import readline # handig voor commandline historie op unix-achtige systemen
class SimpleChatBot:
def init(self, pairs=None):
"""
pairs: lijst van (user_tekst, bot_antwoord) tuples
De bot zoekt in de user_tekst voorbeelden naar de meest vergelijkbare zin
en geeft het corresponderende bot_antwoord terug.
"""
self.pairs = pairs or []
self._build_vectorizer()
def _build_vectorizer(self):
# Corpus = alle user-zinnen in de dataset
self.user_texts = [p[0] for p in self.pairs]
if self.user_texts:
self.vectorizer = TfidfVectorizer(ngram_range=(1,2), min_df=1)
self.user_tfidf = self.vectorizer.fit_transform(self.user_texts)
else:
# lege vectorizer (voorkomt errors voor nog geen data)
self.vectorizer = TfidfVectorizer(ngram_range=(1,2), min_df=1)
self.user_tfidf = None
def respond(self, text, top_k=1):
"""
Geef een antwoord op 'text'. Als er geen dataset is, antwoord simpele fallback.
top_k bepaalt hoeveel beste matches worden overwogen voor randomisatie (optioneel).
"""
text = text.strip()
if not text:
return "Zeg iets, of zwijg plechtig. (Maar zeg iets.)"
# Als we nog geen voorbeelden hebben:
if not self.user_texts:
return "Ik ben nog leerling. Leer me iets door 'learn: jouw zin => mijn antwoord' te typen."
# Transformeer en vergelijk
x = self.vectorizer.transform([text])
sims = cosine_similarity(x, self.user_tfidf).flatten()
best_idx = np.argmax(sims)
best_score = sims[best_idx]
# Drempel: als geen goede match, geef fallback
if best_score < 0.25:
return ("Dat is intrigerend maar ik heb daar geen goede zet voor. "
"Vertel me wat het betekent of leer me met: learn: vraag => antwoord")
# soms wil je variatie: kies uit de top_k best overeenkomende antwoorden
if top_k > 1:
top_indices = np.argsort(sims)[-top_k:]
chosen = np.random.choice(top_indices)
return self.pairs[chosen][1]
else:
return self.pairs[best_idx][1]
def learn_from_pair(self, user_text, bot_answer):
"""Voeg een nieuw voorbeeld toe en rebuild de vectorizer."""
self.pairs.append((user_text.strip(), bot_answer.strip()))
self._build_vectorizer()
def save_to_file(self, path):
"""Sla de dataset op (eenvoudig formaat)."""
with open(path, "w", encoding="utf-8") as f:
for u, b in self.pairs:
# tab-gescheiden, simpele escaping
u_escaped = u.replace("\t", " ").replace("\n", " ")
b_escaped = b.replace("\t", " ").replace("\n", " ")
f.write(u_escaped + "\t" + b_escaped + "\n")
def load_from_file(self, path):
"""Laad dataset van disk."""
new_pairs = []
try:
with open(path, "r", encoding="utf-8") as f:
for line in f:
if not line.strip(): continue
parts = line.rstrip("\n").split("\t")
if len(parts) >= 2:
new_pairs.append((parts[0], "\t".join(parts[1:])))
self.pairs = new_pairs
self._build_vectorizer()
except FileNotFoundError:
print(f"Geen dataset gevonden op {path}; start met lege dataset.")
def demo():
# Voorbeeld dataset: voeg hier je eigen moeilijke, schaakachtige zinnen toe.
initial_pairs = [
("hallo", "Salve. Wat wens je te weten?"),
("hoe gaat het", "Adequaat. En bij jou?"),
("wat is schaak", "Schaak is een strategisch bordspel waarin anticipatie essentieel is."),
("leer me schaken", "Begin met de basisprincipes: ontwikkel stukken, controleer centrum, en vermijd vroegtijdige koningszwakte."),
("vertel een mop", "Twee torens lopen naar het bord... de rest is vertelling."),
("bedankt", "Graag gedaan. Mijn circuits glimmen van voldoening.")
]
bot = SimpleChatBot(initial_pairs)
bot.load_from_file("my_bot_memory.txt") # optioneel: laad eerdere leerdata
print("Slimme ChatBot (TF-IDF retrieval). Type 'quit' om te stoppen.")
print("Leer de bot nieuw gedrag met: learn: jouw vraag => mijn antwoord")
print("Sla geheugen op met: save")
print("Laad geheugen met: load")
print("-" * 60)
while True:
try:
user = input("Jij: ").strip()
except (KeyboardInterrupt, EOFError):
print("\nEinde. Memory wordt automatisch opgeslagen.")
bot.save_to_file("my_bot_memory.txt")
break
if not user:
continue
if user.lower() in ("quit", "exit", "stop"):
print("Bot: Tot ziens. Je zet was goed, je eindezelfde mind ook.")
bot.save_to_file("my_bot_memory.txt")
break
# leer commando: learn: vraag => antwoord
if user.lower().startswith("learn:"):
payload = user[6:].strip()
if "=>" in payload:
q, a = payload.split("=>", 1)
bot.learn_from_pair(q.strip(), a.strip())
print("Bot: Acknowledged. Ik heb die zet opgeslagen.")
else:
print("Bot: Foutief leerformaat. Gebruik: learn: vraag => antwoord")
continue
if user.lower() == "save":
bot.save_to_file("my_bot_memory.txt")
print("Bot: geheugen opgeslagen naar my_bot_memory.txt")
continue
if user.lower() == "load":
bot.load_from_file("my_bot_memory.txt")
print("Bot: geheugen geladen (my_bot_memory.txt)")
continue
# normaal gesprek
reply = bot.respond(user, top_k=3)
print("Bot:", reply)
if name == "main":
demo()
Activity
iamnotgoodatprogrammingplshelp commentedon Nov 19, 2025
Subject: Small Python syntax fixes to get your bot running
This is a really solid concept for a lightweight chatbot! However, it currently won't run because of three specific syntax errors regarding Python's "magic methods" and indentation.
Here is how to fix it:
Fix the Constructor (The Double Underscores) Python classes require double underscores before and after init to recognize it as the setup function.
Change: def init(self, pairs=None):
To: def init(self, pairs=None):
PatelNikhil666 commentedon Feb 3, 2026
Clean separation of concerns (respond, learn_from_pair, persistence).
Good fallback handling when confidence is low.
top_k option adds response variation
Safe handling of empty datasets.
Rebuilding the vectorizer on every learn_from_pair is fine for small datasets, but may become slow at scale — consider batching or delayed rebuilds.
Random selection from top_k could sometimes return weaker matches; you might want to restrict randomization to scores above a higher threshold.
The similarity threshold (0.25) may need tuning as the dataset grows.
iamnotgoodatprogrammingplshelp commentedon Feb 3, 2026
so what your problem is is that you have a problem with o(n^2) growth you can use an incremental learner or a Vector Database (like Chroma, Pinecone, or FAISS).
Hardikrepo commentedon Jul 1, 2026
Following up on the O(n^2) growth point — here's a minimal-diff fix that avoids refitting the whole TF-IDF vectorizer on every
learn_from_paircall, usingscipy.sparse.vstackto append incrementally and a periodic rebuild to resync vocabulary:What changed and why:
learn_from_pairno longer calls_build_vectorizer()(a fullfit_transformover the whole corpus) on every call — that's what made each call cost O(n) and a full session cost O(n²).vectorizer.transform()(reuses existing vocab, no refit) and appends the new row withvstack— O(1) amortized per call.rebuild_every(default 20) bounds how stale that gets — tune based on how bursty learning sessions are.cosine_similarityscan inrespond()— this is just the smallest change that addresses the O(n²) issue directly.Hardikrepo commentedon Jul 1, 2026
Summary of the fix for the O(n^2) growth issue above:
The root cause is that
learn_from_paircalls_build_vectorizer(), which refits the TF-IDF vectorizer over the entire dataset on every single new example — so each call costs O(n), and a full learning session costs O(n²).The fix: instead of refitting on every call, reuse the already-fitted vectorizer's
transform()to encode just the new example, and append it to the existing TF-IDF matrix withscipy.sparse.vstack— O(1) amortized per call. A full rebuild only runs periodically (e.g. every 20 additions) to resync the vocabulary/IDF weights with any new terms, bounding the staleness trade-off.Full code for the change is in my earlier comment above. For larger-scale use, a vector DB (FAISS/Chroma) is still the better long-term path since it also avoids the linear
cosine_similarityscan inrespond()— this fix targets just the specific O(n²) concern with a minimal change.