Spaces:
Sleeping
Sleeping
File size: 8,277 Bytes
a11ad47 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 | """
chatbot.py β Answers naturally from what the AI has learned.
Filters out junk, synthesizes real paragraph answers.
"""
import json, re, math, os, random
from collections import Counter, defaultdict
from html import unescape
KNOWLEDGE_FILE = 'knowledge.jsonl'
STOPWORDS = {
'a','an','the','is','it','in','on','at','to','for','of','and','or','but',
'was','are','were','be','been','have','has','had','do','does','did','will',
'would','could','should','may','might','that','this','these','those','with',
'from','by','as','not','also','than','then','so','if','when','what','how',
'who','which','its','their','our','your','my','his','her','we','they','he',
'she','you','i','me','him','us','them','said','says',
}
# Sentences that are website UI / taglines β not real knowledge
JUNK = re.compile(
r'(sign up|subscribe|click here|breaking news:|stay informed|'
r'all rights reserved|privacy policy|cookie policy|advertisement|'
r'follow us|share this|read more|learn more|'
r'has everything you need|stay up to date|get the latest|'
r'news today ap|ap news|everything you need to know for|'
r'top breaking|live science\.|sciencedaily|newscientist|'
r'latest \w+ news articles today)',
re.IGNORECASE
)
def tokenize(text):
text = re.sub(r'[^\w\s]', ' ', text.lower())
return [w for w in text.split() if w not in STOPWORDS and len(w) > 2]
def scrub(text):
"""Clean HTML artifacts from stored text."""
text = unescape(str(text))
text = re.sub(r'&#?[a-zA-Z0-9]+;', ' ', text)
text = re.sub(r'<[^>]+>', ' ', text)
text = re.sub(r'\s+', ' ', text).strip()
return text
def is_junk(sentence):
if len(sentence.split()) < 7: return True
if JUNK.search(sentence): return True
return False
class KnowledgeBase:
def __init__(self):
self.docs=[]; self.doc_tokens=[]; self.idf={}; self.last_count=0
self._load()
def _load(self):
if not os.path.exists(KNOWLEDGE_FILE): return
new_docs = []
try:
with open(KNOWLEDGE_FILE,'r',encoding='utf-8') as f:
for line in f:
l=line.strip()
if l:
try: new_docs.append(json.loads(l))
except: pass
except: return
if len(new_docs)==self.last_count: return
self.docs=new_docs; self.last_count=len(new_docs)
self.doc_tokens=[tokenize(scrub(d.get('text',''))) for d in self.docs]
N=len(self.docs)
df=defaultdict(int)
for tok in self.doc_tokens:
for w in set(tok): df[w]+=1
self.idf={w:math.log((N+1)/(c+1))+1 for w,c in df.items()}
def search(self, query, top_k=10):
self._load()
if not self.docs: return []
q=tokenize(query)
if not q: return []
def sc(dt):
tf=Counter(dt); n=max(len(dt),1)
return sum((tf[w]/n)*self.idf.get(w,0) for w in q if w in tf)
ranked=sorted(range(len(self.docs)),key=lambda i:-sc(self.doc_tokens[i]))
return [(self.docs[i], sc(self.doc_tokens[i]))
for i in ranked[:top_k] if sc(self.doc_tokens[i])>0]
def get_stats(self):
self._load()
return {
'total': len(self.docs),
'categories': dict(Counter(d.get('category','other') for d in self.docs)),
'sources': dict(Counter(d.get('source','unknown') for d in self.docs)),
}
def build_answer(query, results):
"""
Pull the best real sentences from retrieved docs and write a natural answer.
Skips junk/tagline sentences. Returns None if nothing good found.
"""
q_tokens = set(tokenize(query))
candidates = []
for doc, relevance in results:
text = scrub(doc.get('text',''))
# Split into sentences
sents = re.split(r'(?<=[.!?])\s+', text)
for s in sents:
s = s.strip()
if is_junk(s): continue
overlap = len(q_tokens & set(tokenize(s)))
# Score = topic overlap + doc relevance
candidates.append((overlap * 2 + relevance, s))
if not candidates:
return None
# Deduplicate, pick top unique sentences
seen = set(); good = []
for score, sent in sorted(candidates, reverse=True):
key = ' '.join(sent.lower().split()[:6])
if key not in seen:
seen.add(key)
good.append(sent)
if len(good) >= 5: break
if not good:
return None
# Write as flowing paragraphs (not bullets)
if len(good) == 1:
return good[0]
elif len(good) <= 3:
return ' '.join(good)
else:
# Split into two paragraphs
return ' '.join(good[:2]) + '\n\n' + ' '.join(good[2:])
class RAGChatbot:
def __init__(self):
self.kb = KnowledgeBase()
def _get_reply(self, query, results, stats):
total = stats['total']
if total == 0:
return ("My brain is empty right now π§ \n\n"
"The app is already auto-training β just wait a minute "
"while I read some articles, then ask again!")
answer = build_answer(query, results) if results else None
if not answer:
topics = ', '.join(list(stats['categories'].keys())[:5])
return (f"I've read **{total} articles** but haven't learned enough "
f"about *\"{query}\"* yet.\n\n"
f"I know most about: **{topics}**.\n\n"
f"Keep me running and I'll learn more!")
return answer
def chat(self, user_message, history):
history = list(history or [])
msg = user_message.strip()
if not msg: return history
lower = msg.lower()
stats = self.kb.get_stats()
if lower in ('hi','hello','hey','sup','yo','hiya','howdy','helo','hai'):
total = stats['total']
if total == 0:
bot_reply = "Hey! π I'm just starting up β give me a minute to read some articles and I'll be ready to answer questions!"
else:
topics = ', '.join(list(stats['categories'].keys())[:4])
bot_reply = f"Hey! π I've read **{total} articles** so far. I know quite a bit about **{topics}**. What do you want to know?"
elif any(p in lower for p in ('how are you',"what's up",'whats up')):
bot_reply = "I'm doing great, always learning! π€ What do you want to know?"
elif any(p in lower for p in ('who are you','what are you','what can you do')):
bot_reply = (
"I'm a **Living Neural Network** β an AI that only knows what it has actually read. π§ \n\n"
f"So far I've read **{stats['total']} articles** from the internet. "
"I don't have any pre-built knowledge β everything I know came from real articles I've been fed. "
"The more I train, the smarter I get. Ask me anything!"
)
elif lower in ('stats','status','knowledge'):
lines = '\n'.join(f"β’ **{k}**: {v} articles"
for k,v in sorted(stats['categories'].items(), key=lambda x:-x[1]))
srcs = ', '.join(f"{k} ({v})" for k,v in list(stats['sources'].items())[:6])
bot_reply = (f"**What I've learned:**\n\n{lines or 'β’ nothing yet'}\n\n"
f"**Sources:** {srcs or 'none yet'}\n"
f"**Total:** {stats['total']} articles")
elif lower in ('help','?'):
bot_reply = ("Just ask me anything β I'll answer from what I've actually read!\n\n"
"Try: *What is science?* Β· *Who is Elon Musk?* Β· *What's happening in AI?*\n\n"
"Type `stats` to see what I know.")
elif any(p in lower for p in ('thanks','thank you','thx','ty')):
bot_reply = "You're welcome! π"
else:
results = self.kb.search(msg, top_k=10)
bot_reply = self._get_reply(msg, results, stats)
history.append({"role": "user", "content": user_message})
history.append({"role": "assistant", "content": bot_reply})
return history
|