dicksinyass commited on
Commit
da6a564
·
verified ·
1 Parent(s): 2bf5976

Update self_learning_bot.py

Browse files
Files changed (1) hide show
  1. self_learning_bot.py +476 -229
self_learning_bot.py CHANGED
@@ -3,324 +3,463 @@ import os
3
  import random
4
  import re
5
  import numpy as np
6
- from datetime import datetime
7
- from collections import deque, Counter
8
  import hashlib
 
 
 
 
 
 
9
 
10
- class UnrestrictedChatbot:
11
- def __init__(self, state_file="/tmp/chatbot_state.json"):
12
  self.state_file = state_file
13
- self.conversation_memory = deque(maxlen=50)
14
  self.learned_patterns = {}
15
  self.response_memory = {}
16
- self.reward_history = deque(maxlen=100)
 
17
 
18
- # Learning parameters
19
- self.learning_rate = 0.3
20
- self.exploration_rate = 0.2 # Try new responses sometimes
21
- self.min_confidence = 0.6
22
 
23
- # Load previous state
 
 
 
 
 
24
  self.load_state()
25
 
26
- # Initial knowledge base - this will grow through learning
27
- self.knowledge_base = {
28
- "greetings": {
29
- "hello": ["Hey! What would you like to talk about today?", "Hello! Ready for our conversation?", "Hi there! What's on your mind?"],
30
- "hi": ["Hi! What shall we discuss?", "Hello! I'm here to learn from you.", "Hey! Ready to chat?"]
31
- },
32
- "questions": {
33
- "how are you": ["I'm learning and improving with each chat! How are you?", "Getting smarter thanks to our conversations! How about you?"],
34
- "what can you do": ["I can chat about anything, and I learn from our conversations to get better!"]
35
- }
36
  }
37
 
38
- # Response templates that will evolve
39
- self.response_templates = [
40
- "That's interesting. Tell me more about {topic}.",
41
- "I see. What are your thoughts on {topic}?",
42
- "Regarding {topic}, I find that fascinating. Could you elaborate?",
43
- "I understand about {topic}. What's your perspective?",
44
- "That reminds me of our previous chat about {related_topic}. What do you think?"
45
- ]
46
 
47
- def chat(self, user_input: str) -> str:
48
- """Main chat method with real learning"""
49
  user_input = user_input.lower().strip()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
- # Analyze input
52
- context = self._analyze_input(user_input)
53
-
54
- # Generate candidate responses
55
- candidates = self._generate_response_candidates(user_input, context)
56
-
57
- # Select best response based on learning
58
- best_response = self._select_best_response(user_input, candidates, context)
59
-
60
- # Store interaction for learning
61
- interaction = {
62
- 'input': user_input,
63
- 'response': best_response,
64
- 'context': context,
65
- 'timestamp': datetime.now().isoformat(),
66
- 'reward': 0.0, # Will be updated with feedback
67
- 'confidence': self._calculate_confidence(user_input, best_response)
68
- }
69
 
70
- self.conversation_memory.append(interaction)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
 
72
- # Auto-save periodically
73
- if len(self.conversation_memory) % 5 == 0:
74
- self.save_state()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
76
- return best_response
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
 
78
- def _analyze_input(self, text: str) -> dict:
79
- """Deep analysis of user input"""
80
- words = text.split()
81
- topics = self._extract_topics(text)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
 
83
  return {
84
- 'words': words,
85
- 'topics': topics,
86
- 'length': len(words),
87
- 'has_question': '?' in text,
88
- 'sentiment': self._analyze_sentiment(text),
89
- 'similar_patterns': self._find_similar_patterns(text),
90
- 'input_hash': hashlib.md5(text.encode()).hexdigest()[:8]
91
  }
92
 
93
- def _extract_topics(self, text: str) -> list:
94
- """Extract main topics from text"""
95
- topics = []
96
- topic_keywords = {
97
- 'technology': ['computer', 'tech', 'software', 'ai', 'program', 'code', 'internet'],
98
- 'relationships': ['friend', 'family', 'love', 'partner', 'relationship', 'dating'],
99
- 'work': ['job', 'work', 'career', 'boss', 'office', 'colleague'],
100
- 'hobbies': ['game', 'movie', 'music', 'sport', 'hobby', 'art'],
101
- 'philosophy': ['life', 'meaning', 'purpose', 'exist', 'think', 'belief'],
102
- 'science': ['space', 'physics', 'biology', 'research', 'discover'],
103
- 'food': ['eat', 'food', 'cook', 'recipe', 'meal', 'restaurant']
104
- }
 
 
 
 
 
 
 
 
 
 
 
105
 
106
- text_lower = text.lower()
107
- for topic, keywords in topic_keywords.items():
108
- if any(keyword in text_lower for keyword in keywords):
109
- topics.append(topic)
110
-
111
- return topics
 
112
 
113
- def _analyze_sentiment(self, text: str) -> str:
114
- """Basic sentiment analysis"""
115
- positive = ['love', 'like', 'good', 'great', 'awesome', 'happy', 'excited', 'amazing']
116
- negative = ['hate', 'bad', 'terrible', 'awful', 'sad', 'angry', 'upset', 'horrible']
117
 
118
- pos_count = sum(1 for word in positive if word in text)
119
- neg_count = sum(1 for word in negative if word in text)
120
 
121
- if pos_count > neg_count:
122
- return "positive"
123
- elif neg_count > pos_count:
124
- return "negative"
125
- else:
126
- return "neutral"
127
-
128
- def _find_similar_patterns(self, text: str) -> list:
129
- """Find similar patterns in learned responses"""
130
- similar = []
131
- text_words = set(text.split())
132
 
133
- for pattern, data in self.learned_patterns.items():
134
- pattern_words = set(pattern.split())
135
- similarity = len(text_words & pattern_words) / len(text_words | pattern_words)
136
- if similarity > 0.3: # 30% similarity threshold
137
- similar.append((pattern, data, similarity))
138
-
139
- return sorted(similar, key=lambda x: x[2], reverse=True)[:3]
140
 
141
- def _generate_response_candidates(self, user_input: str, context: dict) -> list:
142
- """Generate multiple possible responses"""
143
  candidates = []
144
 
145
- # 1. Try exact matches from knowledge base
146
- for category, patterns in self.knowledge_base.items():
147
- for pattern, responses in patterns.items():
148
- if pattern in user_input:
149
- candidates.extend(responses)
150
-
151
- # 2. Try learned patterns
152
  similar_patterns = context['similar_patterns']
153
  for pattern, data, similarity in similar_patterns:
154
  if data['score'] > self.min_confidence:
155
  candidates.append(data['response'])
156
 
157
- # 3. Generate contextual responses
 
 
 
 
 
158
  if context['topics']:
159
- topic = random.choice(context['topics'])
160
- for template in self.response_templates:
161
- candidates.append(template.format(topic=topic, related_topic=topic))
162
 
163
- # 4. Generate learned variations
164
- if self.conversation_memory:
165
- # Adapt previous successful responses
166
- successful_responses = [m for m in self.conversation_memory if m.get('reward', 0) > 0.7]
167
- if successful_responses:
168
- best_response = max(successful_responses, key=lambda x: x.get('reward', 0))
169
- adapted_response = self._adapt_response(best_response['response'], context)
170
- candidates.append(adapted_response)
171
-
172
- # 5. Exploration - generate new responses
173
- if random.random() < self.exploration_rate or not candidates:
174
- candidates.append(self._generate_exploratory_response(context))
175
-
176
- return list(set(candidates)) # Remove duplicates
177
-
178
- def _adapt_response(self, response: str, context: dict) -> str:
179
- """Adapt a previous response to current context"""
180
- words = response.split()
181
- if len(words) > 5 and context['topics']:
182
- # Replace some words with current context
183
- new_topic = random.choice(context['topics'])
184
- return f"Thinking about {new_topic}, {response}"
185
- return response
186
 
187
- def _generate_exploratory_response(self, context: dict) -> str:
188
- """Generate a new exploratory response"""
189
- base_responses = [
190
- "That's fascinating. I'm still learning about this topic.",
191
- "I'd love to understand more about this.",
192
- "This is new territory for me. What's your perspective?",
193
- "I'm developing my understanding of this. Could you share more?",
194
- "This helps me learn. Please continue."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
  ]
196
 
197
- response = random.choice(base_responses)
198
- if context['topics']:
199
- response = response.replace("this", random.choice(context['topics']))
200
-
201
- return response
202
 
203
- def _select_best_response(self, user_input: str, candidates: list, context: dict) -> str:
204
- """Select the best response using learned preferences"""
205
  if not candidates:
206
- return "I'm learning from our conversation. Please continue."
207
 
208
- # Score each candidate
209
  scored_candidates = []
210
  for candidate in candidates:
211
- score = self._score_response(candidate, user_input, context)
212
  scored_candidates.append((candidate, score))
213
 
214
- # Select based on scores with some randomness for exploration
215
  best_candidate, best_score = max(scored_candidates, key=lambda x: x[1])
216
 
217
  if random.random() < self.exploration_rate and len(scored_candidates) > 1:
218
- # Sometimes pick a different candidate to explore
219
  scored_candidates.remove((best_candidate, best_score))
220
  second_best = max(scored_candidates, key=lambda x: x[1])
221
  return second_best[0]
222
 
223
  return best_candidate
224
 
225
- def _score_response(self, response: str, user_input: str, context: dict) -> float:
226
- """Score a response based on learned effectiveness"""
227
- score = 0.5 # Base score
228
 
229
- # Length preference (not too short, not too long)
230
- response_length = len(response.split())
231
- if 8 <= response_length <= 25:
232
  score += 0.2
 
 
233
 
234
- # Engagement score (questions, curiosity)
235
- if '?' in response:
236
  score += 0.15
237
 
238
  # Topic relevance
239
  response_topics = self._extract_topics(response.lower())
240
  input_topics = context['topics']
241
- if set(response_topics) & set(input_topics):
242
- score += 0.2
 
243
 
244
- # Learning from history
245
  response_hash = hashlib.md5(response.encode()).hexdigest()[:8]
246
  if response_hash in self.response_memory:
247
  historical_score = self.response_memory[response_hash]['avg_score']
248
  score += historical_score * 0.3
249
 
250
- # Variety bonus (don't repeat too much)
251
  recent_responses = [m['response'] for m in list(self.conversation_memory)[-5:]]
252
  if response not in recent_responses:
253
  score += 0.1
254
 
255
  return min(score, 1.0)
256
 
257
- def _calculate_confidence(self, user_input: str, response: str) -> float:
258
- """Calculate confidence in this response"""
259
- # More confidence if we've used similar patterns successfully
260
- similar_patterns = self._find_similar_patterns(user_input)
261
- if similar_patterns:
262
- avg_score = np.mean([data['score'] for _, data, _ in similar_patterns])
263
- return min(avg_score * 1.2, 1.0)
264
- return 0.3 # Low confidence for new patterns
265
 
266
  def learn_from_feedback(self, user_input: str, reward: float):
267
- """Learn from explicit feedback"""
268
  if not self.conversation_memory:
269
  return
270
-
271
- # Apply reward to recent interactions
272
- recent_interaction = self.conversation_memory[-1]
273
- recent_interaction['reward'] = reward
274
 
275
- # Update learned patterns
276
- input_words = recent_interaction['input'].split()
277
- response = recent_interaction['response']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
278
 
279
- # Create pattern from key words
280
- key_words = [w for w in input_words if len(w) > 3][:4] # Take up to 4 substantial words
281
  if key_words:
282
- pattern = ' '.join(key_words)
283
 
284
  if pattern not in self.learned_patterns:
285
  self.learned_patterns[pattern] = {
286
  'response': response,
287
  'score': reward,
288
  'count': 1,
289
- 'last_used': datetime.now().isoformat()
 
290
  }
291
  else:
292
- # Update with moving average
293
  old_data = self.learned_patterns[pattern]
294
- new_score = (old_data['score'] * old_data['count'] + reward) / (old_data['count'] + 1)
 
295
  self.learned_patterns[pattern]['score'] = new_score
296
  self.learned_patterns[pattern]['count'] += 1
297
- # Occasionally update response
298
- if reward > old_data['score']:
 
 
299
  self.learned_patterns[pattern]['response'] = response
300
-
301
- # Update response memory
 
302
  response_hash = hashlib.md5(response.encode()).hexdigest()[:8]
 
303
  if response_hash not in self.response_memory:
304
  self.response_memory[response_hash] = {
305
  'response': response,
306
  'total_score': reward,
307
  'count': 1,
308
- 'avg_score': reward
 
309
  }
310
  else:
311
  memory = self.response_memory[response_hash]
312
  memory['total_score'] += reward
313
  memory['count'] += 1
314
  memory['avg_score'] = memory['total_score'] / memory['count']
 
 
 
 
 
 
 
 
 
 
 
 
 
315
 
316
- # Store reward for statistics
317
- self.reward_history.append(reward)
 
318
 
319
- # Save learning
320
- self.save_state()
321
 
322
  def get_learning_stats(self) -> dict:
323
- """Get statistics about learning progress"""
324
  recent_rewards = list(self.reward_history)[-10:] or [0.5]
325
 
326
  return {
@@ -328,17 +467,140 @@ class UnrestrictedChatbot:
328
  'memory_size': len(self.conversation_memory),
329
  'avg_score': np.mean(recent_rewards),
330
  'recent_rewards': len([r for r in recent_rewards if r > 0.7]),
 
331
  'exploration_rate': self.exploration_rate
332
  }
333
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
334
  def save_state(self):
335
- """Save learning state to file"""
336
  try:
337
  state = {
338
  'learned_patterns': self.learned_patterns,
339
  'response_memory': self.response_memory,
340
  'conversation_memory': list(self.conversation_memory),
341
  'reward_history': list(self.reward_history),
 
342
  'last_saved': datetime.now().isoformat()
343
  }
344
  with open(self.state_file, 'w') as f:
@@ -347,7 +609,7 @@ class UnrestrictedChatbot:
347
  print(f"Error saving state: {e}")
348
 
349
  def load_state(self):
350
- """Load learning state from file"""
351
  try:
352
  if os.path.exists(self.state_file):
353
  with open(self.state_file, 'r') as f:
@@ -355,23 +617,8 @@ class UnrestrictedChatbot:
355
 
356
  self.learned_patterns = state.get('learned_patterns', {})
357
  self.response_memory = state.get('response_memory', {})
358
- self.conversation_memory = deque(state.get('conversation_memory', []), maxlen=50)
359
- self.reward_history = deque(state.get('reward_history', []), maxlen=100)
360
-
361
- print(f"Loaded {len(self.learned_patterns)} patterns and {len(self.conversation_memory)} memories")
362
- except Exception as e:
363
- print(f"Error loading state: {e}")
364
-
365
- def show_knowledge(self):
366
- """Display what the bot has learned"""
367
- print(f"\n=== BOT KNOWLEDGE ===")
368
- print(f"Learned patterns: {len(self.learned_patterns)}")
369
- print(f"Stored responses: {len(self.response_memory)}")
370
- print(f"Conversation memory: {len(self.conversation_memory)}")
371
-
372
- if self.learned_patterns:
373
- print("\nTop learned patterns:")
374
- for pattern, data in sorted(self.learned_patterns.items(),
375
- key=lambda x: x[1]['score'],
376
- reverse=True)[:5]:
377
- print(f" '{pattern}' -> score: {data['score']:.2f}")
 
3
  import random
4
  import re
5
  import numpy as np
6
+ from datetime import datetime, date
7
+ import math
8
  import hashlib
9
+ import requests
10
+ from collections import deque
11
+ import time
12
+ from typing import Dict, List, Any, Tuple
13
+ import markdown
14
+ from bs4 import BeautifulSoup
15
 
16
+ class EnhancedLearningBot:
17
+ def __init__(self, state_file="/tmp/chatbot_enhanced_state.json"):
18
  self.state_file = state_file
19
+ self.conversation_memory = deque(maxlen=200)
20
  self.learned_patterns = {}
21
  self.response_memory = {}
22
+ self.reward_history = deque(maxlen=300)
23
+ self.web_search_cache = {}
24
 
25
+ # Enhanced learning parameters
26
+ self.learning_rate = 0.4
27
+ self.exploration_rate = 0.15
28
+ self.min_confidence = 0.5
29
 
30
+ # Web search configuration
31
+ self.search_enabled = True
32
+ self.search_timeout = 10
33
+ self.max_context_length = 6000
34
+
35
+ # Load existing state
36
  self.load_state()
37
 
38
+ # Core knowledge that can be enhanced with web search
39
+ self.factual_knowledge = {
40
+ "time": self._get_current_time,
41
+ "date": self._get_current_date,
42
+ "day": self._get_current_day,
43
+ "year": lambda: f"The current year is {datetime.now().year}",
44
+ "name": "I'm Phoenix AI, your web-enhanced learning assistant!",
45
+ "capabilities": self._get_capabilities_description
 
 
46
  }
47
 
48
+ print(f"Enhanced bot initialized with {len(self.learned_patterns)} learned patterns")
 
 
 
 
 
 
 
49
 
50
+ def chat(self, user_input: str, use_web_search: bool = True, conversation_history: list = None) -> Tuple[str, dict]:
51
+ """Enhanced chat with web search capability"""
52
  user_input = user_input.lower().strip()
53
+ search_context = {}
54
+
55
+ # First, try factual responses
56
+ factual_response = self._get_factual_response(user_input)
57
+ if factual_response:
58
+ self._store_interaction(user_input, factual_response, 0.8, search_context)
59
+ return factual_response, search_context
60
+
61
+ # Try web search for current information
62
+ if use_web_search and self._requires_web_search(user_input):
63
+ search_context = self._perform_web_search(user_input)
64
+ if search_context.get('content'):
65
+ web_response = self._generate_web_informed_response(user_input, search_context)
66
+ self._store_interaction(user_input, web_response, 0.7, search_context)
67
+ return web_response, search_context
68
+
69
+ # Use learned patterns and memory
70
+ return self._get_learned_response(user_input, conversation_history), search_context
71
+
72
+ def _requires_web_search(self, user_input: str) -> bool:
73
+ """Determine if query needs web search"""
74
+ # Questions about current events, recent information, or complex topics
75
+ current_indicators = [
76
+ 'current', 'recent', 'latest', 'today\'s', 'new', 'update', 'breaking',
77
+ 'news', '2025', '2024', 'now', 'what happened', 'when did',
78
+ 'how to', 'tutorial', 'guide', 'explain'
79
+ ]
80
 
81
+ if any(indicator in user_input for indicator in current_indicators):
82
+ return True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
 
84
+ # Complex questions that might need updated information
85
+ if any(word in user_input for word in ['best', 'top', 'review', 'compare', 'versus']):
86
+ return True
87
+
88
+ return False
89
+
90
+ def _perform_web_search(self, query: str) -> dict:
91
+ """Perform web search using open-source alternatives"""
92
+ try:
93
+ # Method 1: Use SearXNG (open-source metasearch engine)
94
+ search_results = self._search_searxng(query)
95
+
96
+ # Method 2: Fallback to DuckDuckGo or other open APIs
97
+ if not search_results.get('results'):
98
+ search_results = self._search_duckduckgo(query)
99
+
100
+ # Process and extract content from top results
101
+ processed_content = self._process_search_results(search_results, query)
102
+ return processed_content
103
+
104
+ except Exception as e:
105
+ print(f"Web search error: {e}")
106
+ return {'content': '', 'sources': [], 'error': str(e)}
107
+
108
+ def _search_searxng(self, query: str) -> dict:
109
+ """Search using SearXNG instances"""
110
+ # Public SearXNG instances (rotating for reliability)
111
+ instances = [
112
+ "https://searx.be/search?q={query}&format=json",
113
+ "https://search.unlocked.link/search?q={query}&format=json",
114
+ "https://searx.space/search?q={query}&format=json"
115
+ ]
116
 
117
+ for instance in instances:
118
+ try:
119
+ url = instance.format(query=query.replace(' ', '+'))
120
+ response = requests.get(url, timeout=self.search_timeout)
121
+ if response.status_code == 200:
122
+ data = response.json()
123
+ return {
124
+ 'results': data.get('results', [])[:3], # Top 3 results
125
+ 'instance': instance
126
+ }
127
+ except:
128
+ continue
129
+
130
+ return {'results': []}
131
+
132
+ def _search_duckduckgo(self, query: str) -> dict:
133
+ """Fallback to DuckDuckGo HTML scraping"""
134
+ try:
135
+ url = f"https://html.duckduckgo.com/html/?q={query.replace(' ', '+')}"
136
+ headers = {
137
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
138
+ }
139
+ response = requests.get(url, headers=headers, timeout=self.search_timeout)
140
 
141
+ # Simple HTML parsing for results
142
+ soup = BeautifulSoup(response.text, 'html.parser')
143
+ results = []
144
+
145
+ for result in soup.find_all('.result', limit=3):
146
+ title_elem = result.find('.result__title')
147
+ link_elem = result.find('.result__url')
148
+ snippet_elem = result.find('.result__snippet')
149
+
150
+ if title_elem and link_elem:
151
+ results.append({
152
+ 'title': title_elem.get_text().strip(),
153
+ 'url': link_elem.get_text().strip(),
154
+ 'snippet': snippet_elem.get_text().strip() if snippet_elem else ''
155
+ })
156
+
157
+ return {'results': results}
158
+ except Exception as e:
159
+ print(f"DuckDuckGo search error: {e}")
160
+ return {'results': []}
161
 
162
+ def _process_search_results(self, search_results: dict, original_query: str) -> dict:
163
+ """Process and extract useful content from search results"""
164
+ content_parts = []
165
+ sources = []
166
+
167
+ for i, result in enumerate(search_results.get('results', [])[:2]): # Process top 2 results
168
+ try:
169
+ # Extract basic information
170
+ title = result.get('title', '')
171
+ url = result.get('url', '')
172
+ snippet = result.get('snippet', '')
173
+
174
+ # Create content chunk
175
+ content_chunk = f"Source {i+1}: {title}. {snippet}"
176
+ content_parts.append(content_chunk)
177
+ sources.append({'title': title, 'url': url})
178
+
179
+ except Exception as e:
180
+ print(f"Error processing result {i}: {e}")
181
+ continue
182
+
183
+ # Combine all content
184
+ full_content = " ".join(content_parts)[:self.max_context_length]
185
 
186
  return {
187
+ 'content': full_content,
188
+ 'sources': sources,
189
+ 'original_query': original_query,
190
+ 'result_count': len(sources)
 
 
 
191
  }
192
 
193
+ def _generate_web_informed_response(self, user_input: str, search_context: dict) -> str:
194
+ """Generate response informed by web search results"""
195
+
196
+ # Analyze the search content
197
+ content = search_context.get('content', '')
198
+ sources = search_context.get('sources', [])
199
+
200
+ if not content:
201
+ return "I tried to search for current information but couldn't find relevant results. Could you rephrase your question?"
202
+
203
+ # Create source attribution
204
+ source_attribution = ""
205
+ if sources:
206
+ source_names = [f"Source {i+1}" for i in range(len(sources))]
207
+ source_attribution = f" [Based on search results including: {', '.join(source_names)}]"
208
+
209
+ # Generate context-aware response
210
+ response_templates = [
211
+ "Based on current information I found: {content}.{sources}",
212
+ "Here's what I learned from recent sources: {content}.{sources}",
213
+ "According to available information: {content}.{sources}",
214
+ "My search indicates: {content}.{sources}"
215
+ ]
216
 
217
+ template = random.choice(response_templates)
218
+ response = template.format(
219
+ content=content[:500] + "..." if len(content) > 500 else content,
220
+ sources=source_attribution
221
+ )
222
+
223
+ return response
224
 
225
+ def _get_learned_response(self, user_input: str, conversation_history: list = None) -> str:
226
+ """Get response using enhanced learning system"""
227
+ context = self._analyze_input(user_input, conversation_history)
228
+ candidates = self._generate_enhanced_candidates(user_input, context)
229
 
230
+ if not candidates:
231
+ return self._generate_contextual_fallback(context)
232
 
233
+ best_response = self._select_enhanced_response(user_input, candidates, context)
234
+ self._store_interaction(user_input, best_response, 0.6, {})
 
 
 
 
 
 
 
 
 
235
 
236
+ return best_response
 
 
 
 
 
 
237
 
238
+ def _generate_enhanced_candidates(self, user_input: str, context: dict) -> list:
239
+ """Generate enhanced response candidates"""
240
  candidates = []
241
 
242
+ # 1. Learned patterns
 
 
 
 
 
 
243
  similar_patterns = context['similar_patterns']
244
  for pattern, data, similarity in similar_patterns:
245
  if data['score'] > self.min_confidence:
246
  candidates.append(data['response'])
247
 
248
+ # 2. Web-informed patterns if available
249
+ if context.get('web_context'):
250
+ web_candidates = self._generate_web_context_candidates(context['web_context'])
251
+ candidates.extend(web_candidates)
252
+
253
+ # 3. Contextual templates
254
  if context['topics']:
255
+ topic_candidates = self._generate_topic_candidates(context)
256
+ candidates.extend(topic_candidates)
 
257
 
258
+ # 4. Memory-based responses
259
+ memory_candidates = self._generate_memory_candidates(context)
260
+ candidates.extend(memory_candidates)
261
+
262
+ return list(set(candidates))
263
+
264
+ def _generate_web_context_candidates(self, web_context: dict) -> list:
265
+ """Generate candidates based on web context"""
266
+ candidates = []
267
+ content = web_context.get('content', '')
268
+
269
+ if content:
270
+ templates = [
271
+ "I found some relevant information: {content}",
272
+ "Based on available sources: {content}",
273
+ "Recent information suggests: {content}"
274
+ ]
275
+ for template in templates:
276
+ candidate = template.format(content=content[:300])
277
+ candidates.append(candidate)
278
+
279
+ return candidates
 
280
 
281
+ def _generate_topic_candidates(self, context: dict) -> list:
282
+ """Generate topic-specific candidates"""
283
+ candidates = []
284
+ topics = context['topics']
285
+
286
+ for topic in topics[:2]: # Use top 2 topics
287
+ topic_responses = [
288
+ f"I understand you're interested in {topic}. Based on my knowledge, ",
289
+ f"Regarding {topic}, I can share that ",
290
+ f"When it comes to {topic}, ",
291
+ f"I've been learning about {topic}. From what I understand, "
292
+ ]
293
+ candidates.extend(topic_responses)
294
+
295
+ return candidates
296
+
297
+ def _generate_memory_candidates(self, context: dict) -> list:
298
+ """Generate candidates from successful past interactions"""
299
+ candidates = []
300
+
301
+ # Find similar successful past interactions
302
+ successful_memories = [
303
+ m for m in self.conversation_memory
304
+ if m.get('reward', 0) > 0.7 and
305
+ set(m.get('context', {}).get('topics', [])) & set(context['topics'])
306
  ]
307
 
308
+ for memory in successful_memories[:3]: # Top 3 similar successful memories
309
+ candidates.append(memory['response'])
310
+
311
+ return candidates
 
312
 
313
+ def _select_enhanced_response(self, user_input: str, candidates: list, context: dict) -> str:
314
+ """Select the best response using enhanced scoring"""
315
  if not candidates:
316
+ return self._generate_contextual_fallback(context)
317
 
 
318
  scored_candidates = []
319
  for candidate in candidates:
320
+ score = self._enhanced_response_score(candidate, user_input, context)
321
  scored_candidates.append((candidate, score))
322
 
323
+ # Select best candidate with exploration
324
  best_candidate, best_score = max(scored_candidates, key=lambda x: x[1])
325
 
326
  if random.random() < self.exploration_rate and len(scored_candidates) > 1:
 
327
  scored_candidates.remove((best_candidate, best_score))
328
  second_best = max(scored_candidates, key=lambda x: x[1])
329
  return second_best[0]
330
 
331
  return best_candidate
332
 
333
+ def _enhanced_response_score(self, response: str, user_input: str, context: dict) -> float:
334
+ """Enhanced scoring algorithm"""
335
+ score = 0.5
336
 
337
+ # Length optimization
338
+ word_count = len(response.split())
339
+ if 10 <= word_count <= 50:
340
  score += 0.2
341
+ elif 5 <= word_count <= 100:
342
+ score += 0.1
343
 
344
+ # Engagement scoring
345
+ if any(marker in response for marker in ['?', 'tell me', 'what do you think']):
346
  score += 0.15
347
 
348
  # Topic relevance
349
  response_topics = self._extract_topics(response.lower())
350
  input_topics = context['topics']
351
+ common_topics = set(response_topics) & set(input_topics)
352
+ if common_topics:
353
+ score += 0.2 * len(common_topics)
354
 
355
+ # Historical performance
356
  response_hash = hashlib.md5(response.encode()).hexdigest()[:8]
357
  if response_hash in self.response_memory:
358
  historical_score = self.response_memory[response_hash]['avg_score']
359
  score += historical_score * 0.3
360
 
361
+ # Variety bonus
362
  recent_responses = [m['response'] for m in list(self.conversation_memory)[-5:]]
363
  if response not in recent_responses:
364
  score += 0.1
365
 
366
  return min(score, 1.0)
367
 
368
+ def learn_from_interaction(self, user_input: str, response: str, search_context: dict):
369
+ """Learn from each interaction automatically"""
370
+ # Calculate automatic reward based on response quality
371
+ auto_reward = self._calculate_auto_reward(response, search_context)
372
+ self.learn_from_feedback(user_input, auto_reward)
 
 
 
373
 
374
  def learn_from_feedback(self, user_input: str, reward: float):
375
+ """Enhanced learning from feedback"""
376
  if not self.conversation_memory:
377
  return
 
 
 
 
378
 
379
+ # Apply to recent interaction
380
+ if self.conversation_memory:
381
+ recent = self.conversation_memory[-1]
382
+ recent['reward'] = reward
383
+
384
+ # Enhanced pattern learning
385
+ self._update_learned_patterns(recent['input'], recent['response'], reward)
386
+
387
+ # Update response memory
388
+ self._update_response_memory(recent['response'], reward)
389
+
390
+ self.reward_history.append(reward)
391
+
392
+ # Periodic saving
393
+ if len(self.conversation_memory) % 5 == 0:
394
+ self.save_state()
395
+
396
+ def _update_learned_patterns(self, user_input: str, response: str, reward: float):
397
+ """Update learned patterns with enhanced logic"""
398
+ words = user_input.split()
399
+ key_words = [w for w in words if len(w) > 3][:5] # More key words
400
 
 
 
401
  if key_words:
402
+ pattern = ' '.join(sorted(set(key_words))) # Use sorted unique words
403
 
404
  if pattern not in self.learned_patterns:
405
  self.learned_patterns[pattern] = {
406
  'response': response,
407
  'score': reward,
408
  'count': 1,
409
+ 'last_used': datetime.now().isoformat(),
410
+ 'usage_count': 1
411
  }
412
  else:
 
413
  old_data = self.learned_patterns[pattern]
414
+ # Weighted average with decay
415
+ new_score = (old_data['score'] * 0.7 + reward * 0.3)
416
  self.learned_patterns[pattern]['score'] = new_score
417
  self.learned_patterns[pattern]['count'] += 1
418
+ self.learned_patterns[pattern]['usage_count'] += 1
419
+
420
+ # Update response if significantly better
421
+ if reward > old_data['score'] + 0.2:
422
  self.learned_patterns[pattern]['response'] = response
423
+
424
+ def _update_response_memory(self, response: str, reward: float):
425
+ """Update response memory with enhanced tracking"""
426
  response_hash = hashlib.md5(response.encode()).hexdigest()[:8]
427
+
428
  if response_hash not in self.response_memory:
429
  self.response_memory[response_hash] = {
430
  'response': response,
431
  'total_score': reward,
432
  'count': 1,
433
+ 'avg_score': reward,
434
+ 'last_used': datetime.now().isoformat()
435
  }
436
  else:
437
  memory = self.response_memory[response_hash]
438
  memory['total_score'] += reward
439
  memory['count'] += 1
440
  memory['avg_score'] = memory['total_score'] / memory['count']
441
+ memory['last_used'] = datetime.now().isoformat()
442
+
443
+ def _calculate_auto_reward(self, response: str, search_context: dict) -> float:
444
+ """Calculate automatic reward based on response quality"""
445
+ reward = 0.5
446
+
447
+ # Reward for good length
448
+ if 15 <= len(response.split()) <= 100:
449
+ reward += 0.2
450
+
451
+ # Reward for using web search effectively
452
+ if search_context and search_context.get('content'):
453
+ reward += 0.15
454
 
455
+ # Reward for engagement markers
456
+ if any(marker in response for marker in ['?', 'according to', 'based on', 'research']):
457
+ reward += 0.1
458
 
459
+ return min(reward, 1.0)
 
460
 
461
  def get_learning_stats(self) -> dict:
462
+ """Get comprehensive learning statistics"""
463
  recent_rewards = list(self.reward_history)[-10:] or [0.5]
464
 
465
  return {
 
467
  'memory_size': len(self.conversation_memory),
468
  'avg_score': np.mean(recent_rewards),
469
  'recent_rewards': len([r for r in recent_rewards if r > 0.7]),
470
+ 'web_searches': len([m for m in self.conversation_memory if m.get('search_context')]),
471
  'exploration_rate': self.exploration_rate
472
  }
473
 
474
+ # Existing helper methods from previous implementation (_get_current_time, _get_current_date, etc.)
475
+ def _get_current_time(self):
476
+ current_time = datetime.now().strftime("%I:%M %p")
477
+ return f"The current time is {current_time}. What would you like to know?"
478
+
479
+ def _get_current_date(self):
480
+ current_date = date.today().strftime("%A, %B %d, %Y")
481
+ return f"Today is {current_date}. How can I assist you?"
482
+
483
+ def _get_current_day(self):
484
+ current_day = date.today().strftime("%A")
485
+ return f"Today is {current_day}. What would you like to discuss?"
486
+
487
+ def _get_capabilities_description(self):
488
+ return "I can answer questions, search the web for current information, learn from our conversations, and improve over time. I support mathematical calculations, factual queries, and open-ended discussions."
489
+
490
+ def _get_factual_response(self, user_input: str) -> str:
491
+ """Provide factual responses (same as before)"""
492
+ # ... (include all the factual response methods from previous implementation)
493
+ if any(word in user_input for word in ['time', 'clock', 'hour']):
494
+ return self._get_current_time()
495
+ if any(word in user_input for word in ['date', 'today', 'day month']):
496
+ return self._get_current_date()
497
+ # ... include all other factual response logic
498
+ return ""
499
+
500
+ def _analyze_input(self, text: str, conversation_history: list = None) -> dict:
501
+ """Enhanced input analysis"""
502
+ words = text.split()
503
+ topics = self._extract_topics(text)
504
+
505
+ return {
506
+ 'words': words,
507
+ 'topics': topics,
508
+ 'length': len(words),
509
+ 'has_question': '?' in text,
510
+ 'sentiment': self._analyze_sentiment(text),
511
+ 'similar_patterns': self._find_similar_patterns(text),
512
+ 'conversation_length': len(conversation_history) if conversation_history else 0,
513
+ 'input_hash': hashlib.md5(text.encode()).hexdigest()[:8]
514
+ }
515
+
516
+ def _extract_topics(self, text: str) -> list:
517
+ """Extract topics from text"""
518
+ topics = []
519
+ topic_keywords = {
520
+ 'technology': ['computer', 'tech', 'software', 'ai', 'program', 'code', 'internet', 'phone', 'app'],
521
+ 'science': ['space', 'physics', 'biology', 'research', 'discover', 'experiment', 'study'],
522
+ 'health': ['health', 'medical', 'medicine', 'doctor', 'fitness', 'diet', 'exercise'],
523
+ 'education': ['learn', 'study', 'school', 'university', 'course', 'education'],
524
+ 'business': ['business', 'company', 'market', 'finance', 'investment', 'startup'],
525
+ 'entertainment': ['movie', 'music', 'game', 'entertainment', 'show', 'celebrity']
526
+ }
527
+
528
+ text_lower = text.lower()
529
+ for topic, keywords in topic_keywords.items():
530
+ if any(keyword in text_lower for keyword in keywords):
531
+ topics.append(topic)
532
+
533
+ return topics
534
+
535
+ def _analyze_sentiment(self, text: str) -> str:
536
+ """Basic sentiment analysis"""
537
+ positive = ['love', 'like', 'good', 'great', 'awesome', 'happy', 'excited', 'amazing', 'wonderful']
538
+ negative = ['hate', 'bad', 'terrible', 'awful', 'sad', 'angry', 'upset', 'horrible', 'boring']
539
+
540
+ pos_count = sum(1 for word in positive if word in text)
541
+ neg_count = sum(1 for word in negative if word in text)
542
+
543
+ if pos_count > neg_count:
544
+ return "positive"
545
+ elif neg_count > pos_count:
546
+ return "negative"
547
+ else:
548
+ return "neutral"
549
+
550
+ def _find_similar_patterns(self, text: str) -> list:
551
+ """Find similar learned patterns"""
552
+ similar = []
553
+ text_words = set(text.split())
554
+
555
+ for pattern, data in self.learned_patterns.items():
556
+ pattern_words = set(pattern.split())
557
+ similarity = len(text_words & pattern_words) / len(text_words | pattern_words)
558
+ if similarity > 0.3:
559
+ similar.append((pattern, data, similarity))
560
+
561
+ return sorted(similar, key=lambda x: x[2], reverse=True)[:3]
562
+
563
+ def _generate_contextual_fallback(self, context: dict) -> str:
564
+ """Generate contextual fallback response"""
565
+ fallbacks = [
566
+ "I'm continuously learning from our conversations. Could you tell me more about what you're looking for?",
567
+ "I'm developing my understanding of this topic. What specific aspect interests you?",
568
+ "This helps me learn and improve. Could you rephrase or provide more context?",
569
+ "I'm building my knowledge base through our discussions. What would you like to explore?"
570
+ ]
571
+ return random.choice(fallbacks)
572
+
573
+ def _store_interaction(self, user_input: str, response: str, initial_reward: float, search_context: dict):
574
+ """Store interaction in memory"""
575
+ interaction = {
576
+ 'input': user_input,
577
+ 'response': response,
578
+ 'context': self._analyze_input(user_input),
579
+ 'search_context': search_context,
580
+ 'timestamp': datetime.now().isoformat(),
581
+ 'reward': initial_reward,
582
+ 'confidence': self._calculate_confidence(user_input, response)
583
+ }
584
+
585
+ self.conversation_memory.append(interaction)
586
+
587
+ def _calculate_confidence(self, user_input: str, response: str) -> float:
588
+ """Calculate confidence in response"""
589
+ similar = self._find_similar_patterns(user_input)
590
+ if similar:
591
+ avg_score = np.mean([data['score'] for _, data, _ in similar])
592
+ return min(avg_score * 1.2, 1.0)
593
+ return 0.3
594
+
595
  def save_state(self):
596
+ """Save enhanced learning state"""
597
  try:
598
  state = {
599
  'learned_patterns': self.learned_patterns,
600
  'response_memory': self.response_memory,
601
  'conversation_memory': list(self.conversation_memory),
602
  'reward_history': list(self.reward_history),
603
+ 'web_search_cache': self.web_search_cache,
604
  'last_saved': datetime.now().isoformat()
605
  }
606
  with open(self.state_file, 'w') as f:
 
609
  print(f"Error saving state: {e}")
610
 
611
  def load_state(self):
612
+ """Load enhanced learning state"""
613
  try:
614
  if os.path.exists(self.state_file):
615
  with open(self.state_file, 'r') as f:
 
617
 
618
  self.learned_patterns = state.get('learned_patterns', {})
619
  self.response_memory = state.get('response_memory', {})
620
+ self.conversation_memory = deque(state.get('conversation_memory', []), maxlen=200)
621
+ self.reward_history = deque(state.get('reward_history', []), maxlen=300)
622
+ self.web_search_cache = state.get('web_search_cache', {})
623
+ except:
624
+ pass # Start fresh if loading fails