1+ # agents/conversation_memory.py
2+ from .base import BaseAgent
3+ import json
4+ import os
5+ import time
6+ from datetime import datetime
7+ import re
8+ from collections import defaultdict
9+
10+ class ConversationMemoryAgent (BaseAgent ):
11+ """Agent responsible for maintaining conversation context and user preferences."""
12+
13+ def __init__ (self , memory_file = "conversation_memory.json" ):
14+ """Initialize the conversation memory agent."""
15+ self .memory_file = memory_file
16+ self .current_session_id = None
17+ self .session_memory = {}
18+ self .global_memory = self ._load_memory ()
19+ self .conversation_state = "greeting" # greeting, exploration, learning, wrap-up
20+ self .rapport_level = 0 # 0-10 scale of relationship development
21+
22+ def _load_memory (self ):
23+ """Load memory from file if available."""
24+ if os .path .exists (self .memory_file ):
25+ try :
26+ with open (self .memory_file , 'r' ) as f :
27+ return json .load (f )
28+ except Exception as e :
29+ print (f"Error loading conversation memory: { e } " )
30+ return {
31+ "global_topics" : {},
32+ "session_history" : {},
33+ "user_preferences" : {
34+ "explanation_style" : "balanced" , # detailed, balanced, concise
35+ "interests" : [],
36+ "difficulty_level" : "medium" , # basic, medium, advanced
37+ "previously_understood_concepts" : []
38+ }
39+ }
40+
41+ def _save_memory (self ):
42+ """Save memory to file."""
43+ try :
44+ with open (self .memory_file , 'w' ) as f :
45+ json .dump (self .global_memory , f , indent = 2 )
46+ except Exception as e :
47+ print (f"Error saving conversation memory: { e } " )
48+
49+ def start_new_session (self , session_id = None ):
50+ """Start a new conversation session."""
51+ if not session_id :
52+ session_id = f"session_{ int (time .time ())} "
53+
54+ self .current_session_id = session_id
55+ self .conversation_state = "greeting"
56+ self .rapport_level = 0
57+
58+ # Initialize session memory
59+ self .session_memory = {
60+ "messages" : [],
61+ "topics_discussed" : [],
62+ "concepts_explained" : [],
63+ "user_questions" : [],
64+ "session_start" : datetime .now ().isoformat (),
65+ "interaction_count" : 0
66+ }
67+
68+ # Add to global memory
69+ if session_id not in self .global_memory ["session_history" ]:
70+ self .global_memory ["session_history" ][session_id ] = {
71+ "start_time" : datetime .now ().isoformat (),
72+ "topics" : [],
73+ "summary" : ""
74+ }
75+
76+ return session_id
77+
78+ def add_message (self , sender , message , metadata = None ):
79+ """Add a message to the current conversation history."""
80+ if not self .current_session_id :
81+ self .start_new_session ()
82+
83+ if not metadata :
84+ metadata = {}
85+
86+ # Add message to session memory
87+ message_entry = {
88+ "sender" : sender ,
89+ "message" : message ,
90+ "timestamp" : datetime .now ().isoformat (),
91+ "metadata" : metadata
92+ }
93+
94+ self .session_memory ["messages" ].append (message_entry )
95+ self .session_memory ["interaction_count" ] += 1
96+
97+ # Update conversation state
98+ if len (self .session_memory ["messages" ]) <= 2 :
99+ self .conversation_state = "greeting"
100+ elif len (self .session_memory ["messages" ]) >= 8 :
101+ self .conversation_state = "learning"
102+ else :
103+ self .conversation_state = "exploration"
104+
105+ # Update rapport level based on interaction count
106+ if self .rapport_level < 10 :
107+ self .rapport_level = min (10 , self .session_memory ["interaction_count" ] // 2 )
108+
109+ # Analyze user messages for topics and concepts
110+ if sender == "user" :
111+ # Extract topics from user message
112+ self ._extract_topics (message )
113+
114+ # Track as a question for follow-up management
115+ if self ._is_question (message ):
116+ self .session_memory ["user_questions" ].append ({
117+ "question" : message ,
118+ "timestamp" : datetime .now ().isoformat ()
119+ })
120+
121+ # Save after updates
122+ self ._save_memory ()
123+
124+ return self .conversation_state
125+
126+ def _extract_topics (self , message ):
127+ """Extract potential historical topics from a message."""
128+ # Simple topic extraction using keywords
129+ historical_topics = [
130+ "industrial revolution" , "world war" , "cold war" , "civil war" ,
131+ "french revolution" , "american revolution" , "sri lanka" , "colonization" ,
132+ "independence" , "monarchy" , "democracy" , "empire" , "ancient" , "medieval" ,
133+ "renaissance" , "enlightenment" , "modern history" , "war" , "treaty" ,
134+ "civilization" , "culture" , "economy" , "politics" , "society"
135+ ]
136+
137+ found_topics = []
138+ message_lower = message .lower ()
139+
140+ for topic in historical_topics :
141+ if topic in message_lower :
142+ found_topics .append (topic )
143+
144+ # Add to global topics tracking
145+ if topic not in self .global_memory ["global_topics" ]:
146+ self .global_memory ["global_topics" ][topic ] = 0
147+ self .global_memory ["global_topics" ][topic ] += 1
148+
149+ if found_topics :
150+ self .session_memory ["topics_discussed" ].extend (found_topics )
151+ # Remove duplicates
152+ self .session_memory ["topics_discussed" ] = list (set (self .session_memory ["topics_discussed" ]))
153+
154+ def _is_question (self , message ):
155+ """Determine if a message is likely a question."""
156+ # Check for question marks
157+ if "?" in message :
158+ return True
159+
160+ # Check for question words
161+ question_starters = ["what" , "why" , "how" , "when" , "where" , "who" , "which" , "can" , "do" , "did" , "is" , "are" , "was" , "were" ]
162+ message_words = message .lower ().split ()
163+ if message_words and message_words [0 ] in question_starters :
164+ return True
165+
166+ return False
167+
168+ def record_explained_concept (self , concept ):
169+ """Record that a concept has been explained to the user."""
170+ if not self .current_session_id :
171+ return
172+
173+ if concept not in self .session_memory ["concepts_explained" ]:
174+ self .session_memory ["concepts_explained" ].append (concept )
175+
176+ # Add to global user preferences
177+ if concept not in self .global_memory ["user_preferences" ]["previously_understood_concepts" ]:
178+ self .global_memory ["user_preferences" ]["previously_understood_concepts" ].append (concept )
179+
180+ self ._save_memory ()
181+
182+ def has_concept_been_explained (self , concept ):
183+ """Check if a concept has already been explained in this session or before."""
184+ if not self .current_session_id :
185+ return False
186+
187+ # Check current session
188+ if concept in self .session_memory ["concepts_explained" ]:
189+ return True
190+
191+ # Check global memory
192+ return concept in self .global_memory ["user_preferences" ]["previously_understood_concepts" ]
193+
194+ def update_user_preference (self , preference_type , value ):
195+ """Update a user preference."""
196+ if preference_type in self .global_memory ["user_preferences" ]:
197+ if isinstance (self .global_memory ["user_preferences" ][preference_type ], list ):
198+ if value not in self .global_memory ["user_preferences" ][preference_type ]:
199+ self .global_memory ["user_preferences" ][preference_type ].append (value )
200+ else :
201+ self .global_memory ["user_preferences" ][preference_type ] = value
202+
203+ self ._save_memory ()
204+
205+ def get_recurring_topics (self ):
206+ """Get topics the user seems most interested in based on frequency."""
207+ if not self .global_memory ["global_topics" ]:
208+ return []
209+
210+ # Sort topics by frequency
211+ sorted_topics = sorted (
212+ self .global_memory ["global_topics" ].items (),
213+ key = lambda x : x [1 ],
214+ reverse = True
215+ )
216+
217+ return [topic for topic , count in sorted_topics if count > 1 ][:5 ]
218+
219+ def get_recent_questions (self , count = 3 ):
220+ """Get the most recent user questions."""
221+ if not self .current_session_id :
222+ return []
223+
224+ questions = self .session_memory .get ("user_questions" , [])
225+ return [q ["question" ] for q in questions [- count :]]
226+
227+ def generate_personalized_context (self ):
228+ """Generate conversation context for personalization."""
229+ context = {
230+ "conversation_state" : self .conversation_state ,
231+ "rapport_level" : self .rapport_level ,
232+ "topics_of_interest" : self .get_recurring_topics (),
233+ "recent_questions" : self .get_recent_questions (),
234+ "concepts_already_explained" : self .session_memory .get ("concepts_explained" , []),
235+ "user_preferences" : self .global_memory ["user_preferences" ],
236+ "interaction_count" : self .session_memory .get ("interaction_count" , 0 )
237+ }
238+
239+ return context
240+
241+ def get_chat_history (self , max_messages = 10 ):
242+ """Get recent chat history."""
243+ if not self .current_session_id or not self .session_memory .get ("messages" ):
244+ return []
245+
246+ return self .session_memory ["messages" ][- max_messages :]
247+
248+ def reset (self ):
249+ """Reset the current session memory."""
250+ self .current_session_id = None
251+ self .session_memory = {}
252+ self .conversation_state = "greeting"
253+ self .rapport_level = 0
254+
255+ def run (self , action = "update" , ** kwargs ):
256+ """Run the agent with specified action and parameters."""
257+ if action == "start_session" :
258+ session_id = kwargs .get ("session_id" )
259+ return self .start_new_session (session_id )
260+ elif action == "add_message" :
261+ sender = kwargs .get ("sender" )
262+ message = kwargs .get ("message" )
263+ metadata = kwargs .get ("metadata" )
264+ return self .add_message (sender , message , metadata )
265+ elif action == "get_context" :
266+ return self .generate_personalized_context ()
267+ elif action == "get_history" :
268+ max_messages = kwargs .get ("max_messages" , 10 )
269+ return self .get_chat_history (max_messages )
270+ elif action == "record_concept" :
271+ concept = kwargs .get ("concept" )
272+ return self .record_explained_concept (concept )
273+ elif action == "update_preference" :
274+ preference_type = kwargs .get ("preference_type" )
275+ value = kwargs .get ("value" )
276+ return self .update_user_preference (preference_type , value )
277+ else :
278+ return {"error" : "Unknown action" }
0 commit comments