EMMA commited on
Commit
7f91cdf
·
1 Parent(s): ff9480b

Fix gradio version conflict

Browse files
Backup code/app_21.py ADDED
@@ -0,0 +1,942 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding:utf-8 -*-
2
+
3
+ import os
4
+ import sys
5
+ import json
6
+ import time
7
+ import datetime
8
+ import copy
9
+ import shutil
10
+ import signal
11
+ import logging
12
+ import platform
13
+ import re
14
+
15
+ import gradio as gr
16
+ import nltk
17
+ import torch
18
+ import torch.nn.functional as F
19
+ import tiktoken
20
+ from transformers import AutoTokenizer, AutoModelForSequenceClassification
21
+
22
+ # ==========================================
23
+ # Hugging Face Dataset Integration
24
+ # ==========================================
25
+ from huggingface_hub import hf_hub_download, HfApi
26
+
27
+ from openai import OpenAI as OpenAIClient
28
+ from llama_index.embeddings.openai import OpenAIEmbedding
29
+ from llama_index.llms.openai import OpenAI as LlamaIndexOpenAI
30
+ from llama_index.core import Settings
31
+ from llama_index.core.indices.prompt_helper import PromptHelper
32
+
33
+ prompt_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../')
34
+ sys.path.append(prompt_path)
35
+
36
+ # Local module imports
37
+ from utils.sys_args import data_args, model_args
38
+ from utils.app_modules.utils import *
39
+ from utils.app_modules.presets import *
40
+ from utils.app_modules.overwrites import *
41
+ from utils.prompt_utils import *
42
+ from utils.memory_utils import (
43
+ enter_name_llamaindex,
44
+ summarize_memory_event_personality,
45
+ save_local_memory,
46
+ extract_session_summary,
47
+ extract_semantic_memory,
48
+ )
49
+
50
+ # Ensure NLTK data path
51
+ nltk.data.path = [os.path.join(os.path.dirname(__file__), "nltk_data")] + nltk.data.path
52
+
53
+ tokenizer = tiktoken.get_encoding("cl100k_base")
54
+
55
+ GAPGPT_BASE_URL = os.getenv("GAPGPT_BASE_URL", "https://api.gapgpt.app/v1")
56
+ openai_client_cache = {}
57
+
58
+ HF_MODEL_ID = "Keyvan1986/Emma-Classification_Model"
59
+ HF_TOKEN = os.getenv("HF_TOKEN") # در صورت Private بودن مدل
60
+
61
+ print(f"Loading classifier model from Hugging Face: {HF_MODEL_ID}")
62
+
63
+ try:
64
+ _tokenizer = AutoTokenizer.from_pretrained(
65
+ HF_MODEL_ID,
66
+ token=HF_TOKEN
67
+ )
68
+ _classifier_model = AutoModelForSequenceClassification.from_pretrained(
69
+ HF_MODEL_ID,
70
+ token=HF_TOKEN
71
+ )
72
+ _classifier_model.eval()
73
+ print("Classifier model loaded successfully from Hugging Face Hub.")
74
+ except Exception as e:
75
+ print(f"Error loading classifier from Hugging Face: {e}")
76
+ _tokenizer = None
77
+ _classifier_model = None
78
+
79
+
80
+
81
+ def get_gapgpt_client(api_key: str) -> OpenAIClient:
82
+ if not api_key:
83
+ raise ValueError("API key is None while attempting to create a GapGPT client.")
84
+ if api_key not in openai_client_cache:
85
+ openai_client_cache[api_key] = OpenAIClient(api_key=api_key, base_url=GAPGPT_BASE_URL)
86
+ return openai_client_cache[api_key]
87
+
88
+
89
+ os_name = platform.system()
90
+ clear_command = 'cls' if os_name == 'Windows' else 'clear'
91
+ stop_stream = False
92
+
93
+
94
+ def signal_handler(signal_number, frame):
95
+ global stop_stream
96
+ stop_stream = True
97
+
98
+
99
+ VECTOR_SEARCH_TOP_K = 2
100
+
101
+ # Update this path to your actual file location
102
+ api_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data", "api_key_list.txt")
103
+
104
+
105
+ def read_apis(path):
106
+ api_keys_local = []
107
+ if os.path.exists(path):
108
+ with open(path, 'r', encoding='utf8') as f:
109
+ for line in f:
110
+ line = line.strip()
111
+ if line:
112
+ api_keys_local.append(line)
113
+ return api_keys_local
114
+
115
+
116
+ # ==========================================
117
+ # پیکربندی حافظه و اتصال به Hugging Face Dataset
118
+ # ==========================================
119
+ HF_TOKEN = os.getenv("HF_TOKEN") # توکن Write هاگینگ‌فیس
120
+ HF_DATASET_REPO = os.getenv("HF_DATASET_REPO", "YOUR_HF_USERNAME/YOUR_DATASET_NAME") # نام دیتاست خود را وارد کنید
121
+ HF_MEMORY_FILENAME = "update_memory_0512_eng.json"
122
+
123
+ base_data_dir = os.getenv("EMMA_DATA_DIR", os.path.join(os.path.dirname(os.path.abspath(__file__)), "data"))
124
+ memory_dir = os.path.join(base_data_dir, "memories", HF_MEMORY_FILENAME)
125
+ os.makedirs(os.path.dirname(memory_dir), exist_ok=True)
126
+
127
+ # ۱. دانلود فایل از دیتاست در ابتدای اجرای برنامه
128
+ try:
129
+ print(f"Fetching {HF_MEMORY_FILENAME} from Hugging Face Dataset: {HF_DATASET_REPO}...")
130
+ hf_hub_download(
131
+ repo_id=HF_DATASET_REPO,
132
+ filename=HF_MEMORY_FILENAME,
133
+ repo_type="dataset",
134
+ local_dir=os.path.dirname(memory_dir),
135
+ token=HF_TOKEN
136
+ )
137
+ print("Memory file successfully downloaded from Hugging Face Dataset.")
138
+ except Exception as e:
139
+ print(f"Warning: Could not fetch memory from Hugging Face ({e}). Checking local fallback...")
140
+ if not os.path.exists(memory_dir):
141
+ with open(memory_dir, "w", encoding="utf-8") as f:
142
+ json.dump({}, f)
143
+
144
+ # لود کردن حافظه در رم
145
+ global memory
146
+ try:
147
+ with open(memory_dir, "r", encoding="utf-8") as f:
148
+ memory = json.load(f)
149
+ except Exception as e:
150
+ print(f"Error parsing memory JSON: {e}. Initializing empty memory.")
151
+ memory = {}
152
+
153
+
154
+ def sync_memory_to_hf():
155
+ """ذخیره لوکال و آپلود همزمان به دیتاست Hugging Face"""
156
+ try:
157
+ # ۱. ذخیره روی دیسک کانتینر
158
+ with open(memory_dir, "w", encoding="utf-8") as f:
159
+ json.dump(memory, f, ensure_ascii=False, indent=4)
160
+
161
+ # ۲. آپلود به دیتاست
162
+ if HF_TOKEN and HF_DATASET_REPO:
163
+ api = HfApi()
164
+ api.upload_file(
165
+ path_or_fileobj=memory_dir,
166
+ path_in_repo=HF_MEMORY_FILENAME,
167
+ repo_id=HF_DATASET_REPO,
168
+ repo_type="dataset",
169
+ token=HF_TOKEN,
170
+ commit_message=f"Update user memory - {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
171
+ )
172
+ print("Memory successfully synced to Hugging Face Dataset.")
173
+ except Exception as e:
174
+ print(f"Error syncing memory to HF Dataset: {e}")
175
+
176
+
177
+ language = 'en'
178
+ user_keyword = generate_user_keyword()[language]
179
+ ai_keyword = generate_ai_keyword()[language]
180
+ boot_name = boot_name_dict[language]
181
+ boot_actual_name = boot_actual_name_dict[language]
182
+ meta_prompt = generate_meta_prompt_dict_chatgpt()[language]
183
+ meta_prompt_semantic = generate_meta_prompt_dict_semantic_chatgpt()[language]
184
+ meta_prompt_semantic_episodic = generate_meta_prompt_dict_semantic_episodic_chatgpt()[language]
185
+ new_user_meta_prompt = generate_new_user_meta_prompt_dict_chatgpt()[language]
186
+
187
+ api_keys = read_apis(api_path)
188
+ if not api_keys:
189
+ print(f"Warning: No API keys found in {api_path}.")
190
+
191
+ new_conversation = False
192
+ chatgpt_config = {
193
+ "model": "gpt-4o",
194
+ "temperature": 1,
195
+ "max_tokens": 1024,
196
+ "top_p": 0.95,
197
+ "frequency_penalty": 0.4,
198
+ "presence_penalty": 0.2,
199
+ "n": 1,
200
+ }
201
+
202
+ deactivated_keys = []
203
+ logging.basicConfig(
204
+ level=logging.DEBUG,
205
+ format="%(asctime)s [%(levelname)s] [%(filename)s:%(lineno)d] %(message)s",
206
+ )
207
+
208
+
209
+ def chatgpt_chat(prompt, system, history, gpt_config, api_index=0):
210
+ retry_times, count = 5, 0
211
+ response = None
212
+
213
+ while response is None and count < retry_times:
214
+ try:
215
+ request = copy.deepcopy(gpt_config)
216
+
217
+ if data_args.language == 'en':
218
+ message = [
219
+ {"role": "system", "content": system.strip()},
220
+ {"role": "user", "content": "Hi!"},
221
+ {"role": "assistant", "content": f"Hi! I'm {boot_actual_name}! I will give you warm companion!"},
222
+ ]
223
+ else:
224
+ message = [
225
+ {"role": "system", "content": system.strip()},
226
+ {"role": "user", "content": "Hi"},
227
+ {"role": "assistant", "content": f"Hi! I'm {boot_actual_name}! I will give you warm companion!"},
228
+ ]
229
+
230
+ if history:
231
+ if isinstance(history[0], list):
232
+ for q, a in history:
233
+ message.append({"role": "user", "content": str(q)})
234
+ message.append({"role": "assistant", "content": str(a)})
235
+ elif isinstance(history[0], dict):
236
+ for msg in history:
237
+ if msg.get('role') in ['user', 'assistant']:
238
+ message.append(msg)
239
+
240
+ message.append({"role": "user", "content": f"{prompt}"})
241
+
242
+ if not api_keys:
243
+ return "Error: No API Keys available."
244
+
245
+ client = get_gapgpt_client(api_keys[api_index])
246
+ response = client.chat.completions.create(messages=message, **request)
247
+
248
+ except Exception as e:
249
+ print(f"Chat Error: {e}")
250
+ if 'This key is associated with a deactivated account' in str(e):
251
+ deactivated_keys.append(api_keys[api_index])
252
+
253
+ if api_keys:
254
+ api_index = api_index + 1 if api_index < len(api_keys) - 1 else 0
255
+ loop_check = 0
256
+ while api_keys[api_index] in deactivated_keys and loop_check < len(api_keys):
257
+ api_index = api_index + 1 if api_index < len(api_keys) - 1 else 0
258
+ loop_check += 1
259
+ count += 1
260
+
261
+ if response:
262
+ response = response.choices[0].message.content
263
+ else:
264
+ response = ''
265
+ return response
266
+
267
+
268
+ def classify_query_local(text):
269
+ id2label_map = {
270
+ 0: "episodic_memory",
271
+ 1: "semantic_memory",
272
+ 2: "semantic-episodic",
273
+ 3: "unknown"
274
+ }
275
+
276
+ try:
277
+ inputs = _tokenizer(
278
+ text,
279
+ return_tensors="pt",
280
+ truncation=True,
281
+ padding=True,
282
+ max_length=512
283
+ )
284
+
285
+ with torch.no_grad():
286
+ outputs = _classifier_model(**inputs)
287
+
288
+ logits = outputs.logits
289
+ probabilities = F.softmax(logits, dim=-1)
290
+ predicted_class_id = torch.argmax(probabilities, dim=-1).item()
291
+
292
+ category = id2label_map.get(predicted_class_id, "unknown")
293
+ print(f"Local Classifier: '{text}' -> {category} (Class ID: {predicted_class_id})")
294
+ return category
295
+
296
+ except Exception as e:
297
+ print(f"Error in local classification: {e}")
298
+ return "unknown"
299
+
300
+
301
+ def classify_query_openai(text, gpt_config, api_index=0, retry_times=5):
302
+ response = None
303
+ count = 0
304
+ local_deactivated = []
305
+ print("text********:", text)
306
+
307
+ system_prompt = """
308
+ You are an AI that classifies user queries into one of the following memory types:
309
+ - 'episodic_memory': Queries about past personal events, daily life logs, or specific experiences the user has shared (e.g., "What did I eat yesterday?", "Tell me about my trip").
310
+ - 'semantic_memory': Queries about facts, preferences, general knowledge the user has taught you, or summaries of their personality (e.g., "What is my favorite color?", "Do I like sci-fi movies?").
311
+ - 'semantic-episodic': Complex queries requiring both specific past events and general facts/preferences (e.g., "Based on my food preferences, did I enjoy the dinner last night?").
312
+
313
+ Output ONLY one of these three strings: 'episodic_memory', 'semantic_memory', or 'semantic-episodic'. If unsure, output 'episodic_memory'.
314
+ """.strip()
315
+
316
+ if not api_keys:
317
+ return "unknown"
318
+
319
+ while response is None and count < retry_times:
320
+ try:
321
+ client = get_gapgpt_client(api_keys[api_index])
322
+ response = client.chat.completions.create(
323
+ messages=[
324
+ {"role": "system", "content": system_prompt},
325
+ {"role": "user", "content": text.strip()},
326
+ ],
327
+ **copy.deepcopy(gpt_config),
328
+ )
329
+ except Exception as e:
330
+ print(f"Classify Error: {e}")
331
+ if "This key is associated with a deactivated account" in str(e):
332
+ local_deactivated.append(api_keys[api_index])
333
+
334
+ api_index = api_index + 1 if api_index < len(api_keys) - 1 else 0
335
+ while api_keys[api_index] in local_deactivated:
336
+ api_index = api_index + 1 if api_index < len(api_keys) - 1 else 0
337
+ count += 1
338
+
339
+ if response:
340
+ category = response.choices[0].message.content.strip().lower()
341
+ if "semantic_memory" in category: category = "semantic_memory"
342
+ elif "semantic-episodic" in category: category = "semantic-episodic"
343
+ elif "episodic_memory" in category: category = "episodic_memory"
344
+ else: category = "unknown"
345
+ else:
346
+ category = "unknown"
347
+
348
+ return category
349
+
350
+
351
+ def predict_new(
352
+ text,
353
+ history,
354
+ top_p,
355
+ temperature,
356
+ max_length_tokens,
357
+ max_context_length_tokens,
358
+ user_name,
359
+ user_memory,
360
+ user_memory_index,
361
+ service_context,
362
+ api_index,
363
+ semantic_memory_text,
364
+ query_category,
365
+ user_profile=None
366
+ ):
367
+ chatgpt_cfg = {
368
+ "model": "gpt-4o",
369
+ "temperature": temperature,
370
+ "max_tokens": max_length_tokens,
371
+ "top_p": top_p,
372
+ "frequency_penalty": 0.4,
373
+ "presence_penalty": 0.2,
374
+ "n": 1,
375
+ }
376
+
377
+ if text == "":
378
+ return history, history, "Empty context."
379
+
380
+ if history is None:
381
+ history = []
382
+
383
+ system_prompt, related_memos = build_prompt_with_search_memory_llamaindex(
384
+ history=history,
385
+ query=text,
386
+ user_memory=user_memory,
387
+ user_name=user_name,
388
+ user_memory_index=user_memory_index,
389
+ service_context=service_context,
390
+ api_keys=api_keys,
391
+ api_index=api_index,
392
+ meta_prompt=meta_prompt,
393
+ new_user_meta_prompt=new_user_meta_prompt,
394
+ data_args=data_args,
395
+ boot_actual_name=boot_actual_name,
396
+ semantic_memory_text=semantic_memory_text,
397
+ query_category=query_category,
398
+ meta_prompt_semantic=meta_prompt_semantic,
399
+ meta_prompt_semantic_episodic=meta_prompt_semantic_episodic,
400
+ )
401
+
402
+ if user_profile:
403
+ profile_str = (
404
+ f"\n\n[User Profile Information]:\n"
405
+ f"- Age: {user_profile.get('age', 'Unknown')}\n"
406
+ f"- Gender: {user_profile.get('gender', 'Unknown')}\n"
407
+ f"- Occupation: {user_profile.get('occupation', 'Unknown')}\n"
408
+ f"- Residence: {user_profile.get('residence', 'Unknown')}\n"
409
+ f"Use this context subtly when talking to the user."
410
+ )
411
+ system_prompt += profile_str
412
+
413
+ current_history_for_llm = history
414
+ if len(history) > data_args.max_history * 2:
415
+ current_history_for_llm = history[-(data_args.max_history * 2):]
416
+
417
+ response = chatgpt_chat(
418
+ prompt=text,
419
+ system=system_prompt,
420
+ history=current_history_for_llm,
421
+ gpt_config=chatgpt_cfg,
422
+ api_index=api_index,
423
+ )
424
+
425
+ torch.cuda.empty_cache()
426
+
427
+ new_history = history + [
428
+ {"role": "user", "content": text},
429
+ {"role": "assistant", "content": response}
430
+ ]
431
+
432
+ if user_name:
433
+ save_local_memory(memory, new_history, user_name, data_args)
434
+ # همگام‌سازی بلافاصله با دیتاست Hugging Face
435
+ sync_memory_to_hf()
436
+
437
+ return new_history, new_history, "Generating..."
438
+
439
+
440
+ def create_gradio_interface(service_context, api_keys):
441
+ custom_css = """
442
+ .send-btn {
443
+ border: 3px solid #0066cc !important;
444
+ border-radius: 12px !important;
445
+ background-color: white !important;
446
+ color: black !important;
447
+ font-weight: bold !important;
448
+ font-size: 16px !important;
449
+ transition: all 0.3s ease !important;
450
+ }
451
+ .send-btn:hover {
452
+ background-color: #0066cc !important;
453
+ color: white !important;
454
+ }
455
+
456
+ .bottom-actions-row {
457
+ background-color: #e5e7eb !important;
458
+ border-radius: 8px !important;
459
+ padding: 5px !important;
460
+ margin-top: 10px !important;
461
+ }
462
+
463
+ .action-btn {
464
+ border: none !important;
465
+ background: transparent !important;
466
+ color: black !important;
467
+ font-weight: bold !important;
468
+ box-shadow: none !important;
469
+ transition: all 0.3s ease !important;
470
+ }
471
+
472
+ .action-btn:hover {
473
+ background-color: #d1d5db !important;
474
+ border-radius: 8px !important;
475
+ }
476
+
477
+ .progress-level, .progress-text, .progress-level svg, .progress-level img {
478
+ display: none !important;
479
+ }
480
+
481
+ body:has(.progress-level) {
482
+ pointer-events: none !important;
483
+ }
484
+
485
+ body:has(.progress-level)::after {
486
+ content: "";
487
+ position: fixed;
488
+ top: 0; left: 0;
489
+ width: 100vw; height: 100vh;
490
+ background-color: rgba(255, 255, 255, 0.7);
491
+ z-index: 9998;
492
+ pointer-events: all;
493
+ cursor: wait;
494
+ }
495
+
496
+ body:has(.progress-level)::before {
497
+ content: "";
498
+ position: fixed;
499
+ top: 50%; left: 50%;
500
+ transform: translate(-50%, -50%);
501
+ width: 60px; height: 60px;
502
+ border: 6px solid #e5e7eb;
503
+ border-top-color: #0066cc;
504
+ border-radius: 50%;
505
+ animation: spin 1s linear infinite;
506
+ z-index: 9999;
507
+ }
508
+
509
+ @keyframes spin {
510
+ 0% { transform: translate(-50%, -50%) rotate(0deg); }
511
+ 100% { transform: translate(-50%, -50%) rotate(360deg); }
512
+ }
513
+
514
+ .typing-dots::after {
515
+ content: '';
516
+ animation: typing 1.5s infinite;
517
+ }
518
+ @keyframes typing {
519
+ 0% { content: ''; }
520
+ 25% { content: ' .'; }
521
+ 50% { content: ' . .'; }
522
+ 75% { content: ' . . .'; }
523
+ 100% { content: ''; }
524
+ }
525
+ """
526
+
527
+ with gr.Blocks(title="EMMA") as demo:
528
+ gr.HTML(f"<style>{custom_css}</style>")
529
+
530
+ state = gr.State({
531
+ "history": [],
532
+ "user_name": None,
533
+ "memory": memory,
534
+ "data_args": data_args,
535
+ "service_context": service_context,
536
+ "api_keys": api_keys,
537
+ "api_index": 0,
538
+ "semantic_memory_text": "",
539
+ "new_conversation": True,
540
+ "initialized": False
541
+ })
542
+
543
+ with gr.Row():
544
+ # ==========================================
545
+ # 1. LOGIN PAGE (Left Sidebar)
546
+ # ==========================================
547
+ with gr.Sidebar(open=True) as login_page:
548
+ gr.Markdown("<h1 style='text-align: center;'>🧠 EMMA</h1><h3 style='text-align: center;'>Your Empathetic Mental Health Assistant</h3><br><p style='text-align: center;'>Please login or register to begin.</p>")
549
+
550
+ username_input = gr.Textbox(label="Your Name (Required)", placeholder="e.g., Alex")
551
+ password_input = gr.Textbox(label="Password (Required)", placeholder="Enter your password", type="password")
552
+
553
+ login_status = gr.Textbox(
554
+ visible=False,
555
+ label="Status",
556
+ interactive=False,
557
+ lines=1,
558
+ max_lines=10
559
+ )
560
+
561
+ check_user_btn = gr.Button("🔍 Login / Check", variant="primary", elem_classes=["login-btn"])
562
+
563
+ with gr.Column(visible=False) as registration_fields:
564
+ gr.Markdown("### ✨ New User Registration")
565
+ age_input = gr.Textbox(label="Age", placeholder="e.g., 28")
566
+ gender_input = gr.Dropdown(label="Gender", choices=["Male", "Female", "Other"])
567
+ occupation_input = gr.Textbox(label="Occupation", placeholder="e.g., Student...")
568
+ residence_input = gr.Textbox(label="Place of Residence", placeholder="e.g., Berlin")
569
+ register_btn = gr.Button("🎯 Complete Registration", variant="primary", elem_classes=["login-btn"])
570
+
571
+ # ==========================================
572
+ # 2. CHAT PAGE (Main Column)
573
+ # ==========================================
574
+ with gr.Column(scale=3, elem_classes=["chat-box"]) as chat_page:
575
+ active_header = gr.Markdown("<h2 style='text-align: center; color: #333;'>🧠 EMMA: Session</h2>")
576
+
577
+ with gr.Group():
578
+ chatbot = gr.Chatbot(label="💬 EMMA Conversation", height=350)
579
+
580
+ with gr.Row():
581
+ user_input = gr.Textbox(placeholder="Type your message here...", show_label=False, scale=6)
582
+ submit_btn = gr.Button("📤 Send", elem_classes=["send-btn"], scale=1)
583
+
584
+ with gr.Row(equal_height=True, elem_classes=["bottom-actions-row"]):
585
+ clear_btn = gr.Button("🧹 Clear", elem_classes=["action-btn"])
586
+ new_session_btn = gr.Button("🔄 New Session", elem_classes=["action-btn"])
587
+ switch_user_btn = gr.Button("👥 Logout", elem_classes=["action-btn"])
588
+ system_msg = gr.Textbox(label="🔔 System Messages", interactive=False, max_lines=2)
589
+
590
+ # -------------------------------------------------------
591
+ # Internal Functions
592
+ # -------------------------------------------------------
593
+ def handle_login_check(name, password, state):
594
+ if not name.strip() or not password.strip():
595
+ return (gr.update(), gr.update(), gr.update(), gr.update(visible=True, value="⚠️ Please enter both name and password."), gr.update(), gr.update(), state)
596
+
597
+ if name in memory:
598
+ stored_password = memory[name].get("profile", {}).get("password")
599
+
600
+ if stored_password and stored_password != password:
601
+ return (gr.update(), gr.update(), gr.update(), gr.update(visible=True, value="⚠️ Incorrect password!"), gr.update(), gr.update(), state)
602
+
603
+ hello_msg, user_memory, sessions_memory, episodic_memory, semantic_memory = enter_name_llamaindex(name, memory, data_args)
604
+ user_memory = summarize_memory_event_personality(data_args, memory, name)
605
+
606
+ new_state = state.copy()
607
+ new_state["user_name"] = name
608
+ new_state["memory"] = memory
609
+ new_state["initialized"] = True
610
+ new_state["semantic_memory_text"] = user_memory.get("semantic_memory", {})
611
+ new_state["semantic_memory_index"] = semantic_memory
612
+ new_state["user_memory_index"] = sessions_memory
613
+ new_state["episodic_memory_index"] = episodic_memory
614
+ new_state["history"] = []
615
+
616
+ welcome_msg = hello_msg if hello_msg else f"Welcome back, {name}!"
617
+
618
+ return (
619
+ gr.update(open=False),
620
+ gr.update(visible=False),
621
+ gr.update(visible=True),
622
+ gr.update(visible=False, value=""),
623
+ gr.update(value=f"<h2 style='text-align: center; color: #333;'>🧠 EMMA: Session for {name}</h2>"),
624
+ gr.update(value=welcome_msg),
625
+ new_state
626
+ )
627
+ else:
628
+ return (
629
+ gr.update(),
630
+ gr.update(visible=True),
631
+ gr.update(visible=False),
632
+ gr.update(visible=True, value="📝 New user detected. Please fill the details below to register with this password."),
633
+ gr.update(),
634
+ gr.update(),
635
+ state
636
+ )
637
+
638
+ def handle_register(name, password, age, gender, occupation, residence, state):
639
+ name = str(name).strip()
640
+
641
+ if not name or not password.strip():
642
+ raise gr.Error("Please enter a valid user name and password.")
643
+
644
+ if name in memory:
645
+ raise gr.Error(f"User '{name}' already exists. Please use the login form.")
646
+
647
+ memory[name] = {
648
+ "profile": {
649
+ "password": password,
650
+ "age": age,
651
+ "gender": gender,
652
+ "occupation": occupation,
653
+ "residence": residence,
654
+ },
655
+ "sessions": [],
656
+ "episodic_memory": [],
657
+ "semantic_memory": {},
658
+ }
659
+
660
+ try:
661
+ (
662
+ _,
663
+ user_memory,
664
+ sessions_memory,
665
+ episodic_memory,
666
+ semantic_memory,
667
+ ) = enter_name_llamaindex(
668
+ name=name,
669
+ memory=memory,
670
+ data_args=data_args,
671
+ update_memory_index=True,
672
+ )
673
+ except Exception:
674
+ memory.pop(name, None)
675
+ raise
676
+
677
+ # همگام‌سازی کاربر جدید با HF Dataset
678
+ sync_memory_to_hf()
679
+
680
+ new_state = dict(state or {})
681
+ new_state.update(
682
+ {
683
+ "user_name": name,
684
+ "memory": memory,
685
+ "initialized": True,
686
+ "history": [],
687
+ "semantic_memory_text": user_memory.get("semantic_memory", {}),
688
+ "user_memory_index": sessions_memory,
689
+ "episodic_memory_index": episodic_memory,
690
+ "semantic_memory_index": semantic_memory,
691
+ }
692
+ )
693
+
694
+ welcome_msg = f"Welcome {name}! Registration complete."
695
+
696
+ return (
697
+ gr.update(open=False),
698
+ gr.update(visible=False),
699
+ gr.update(visible=True),
700
+ gr.update(visible=False, value=""),
701
+ gr.update(
702
+ value=(
703
+ "<h2 style='text-align: center; color: #333;'>"
704
+ f"🧠 EMMA: Session for {name}</h2>"
705
+ )
706
+ ),
707
+ gr.update(value=welcome_msg),
708
+ new_state,
709
+ )
710
+
711
+ def switch_user(state):
712
+ if state["initialized"] and state["user_name"] in state["memory"]:
713
+ if state["memory"][state["user_name"]]["sessions"]:
714
+ previous_session = state["memory"][state["user_name"]]["sessions"][-1]
715
+ summary = extract_session_summary(previous_session["conversation"], previous_session["date"], len(state["memory"][state["user_name"]]["sessions"]) - 1)
716
+ state["memory"][state["user_name"]]["episodic_memory"].append(summary)
717
+
718
+ try:
719
+ user_mem = state["memory"][state["user_name"]]
720
+ existing_semantic = user_mem.get("semantic_memory", {})
721
+ updated_semantic = extract_semantic_memory(summary, existing_semantic)
722
+ state["memory"][state["user_name"]]["semantic_memory"] = updated_semantic
723
+ except Exception as e:
724
+ print(f"Error updating semantic memory on logout: {e}")
725
+
726
+ # آپلود تغییرات ایجادشده در زمان لاگ‌اوت به HF Dataset
727
+ sync_memory_to_hf()
728
+
729
+ new_state = state.copy()
730
+ new_state.update({"history": [], "user_name": None, "semantic_memory_text": "", "initialized": False})
731
+
732
+ return (
733
+ gr.update(open=True),
734
+ gr.update(visible=False),
735
+ gr.update(visible=False),
736
+ gr.update(value="Logged out successfully.", visible=True),
737
+ gr.update(value=""),
738
+ gr.update(value=""),
739
+ new_state,
740
+ gr.update(value=[]),
741
+ gr.update(value=""),
742
+ gr.update(value=""),
743
+ gr.update(value=""),
744
+ gr.update(value=""),
745
+ gr.update(value="")
746
+ )
747
+
748
+ def handle_chat(user_message, state):
749
+ if not user_message.strip():
750
+ yield gr.update(), state.get("history", []), state
751
+ return
752
+
753
+ current_history = state.get("history", [])
754
+ temp_history = current_history + [
755
+ {"role": "user", "content": user_message},
756
+ {"role": "assistant", "content": "<span class='typing-dots'>Generating</span>"}
757
+ ]
758
+
759
+ yield gr.update(value=""), temp_history, state
760
+
761
+ user_name = state.get("user_name")
762
+ user_memory_index = state.get("user_memory_index")
763
+ user_memory = state.get("user_memory", {})
764
+ semantic_memory_text = state.get("semantic_memory_text", "")
765
+ service_context = state.get("service_context")
766
+ api_index = state.get("api_index", 0)
767
+
768
+ user_profile = {}
769
+ if user_name and state.get("memory") and user_name in state["memory"]:
770
+ user_profile = state["memory"][user_name].get("profile", {})
771
+
772
+ query_category = classify_query_local(user_message)
773
+
774
+ new_history, _, status_msg = predict_new(
775
+ text=user_message,
776
+ history=current_history,
777
+ top_p=0.95,
778
+ temperature=1.0,
779
+ max_length_tokens=1024,
780
+ max_context_length_tokens=4096,
781
+ user_name=user_name,
782
+ user_memory=user_memory,
783
+ user_memory_index=user_memory_index,
784
+ service_context=service_context,
785
+ api_index=api_index,
786
+ semantic_memory_text=semantic_memory_text,
787
+ query_category=query_category,
788
+ user_profile=user_profile
789
+ )
790
+
791
+ state["history"] = new_history
792
+ yield gr.update(), new_history, state
793
+
794
+ def clear_history(state):
795
+ state["history"] = []
796
+ return [], state
797
+
798
+ def handle_new_session(state):
799
+ user_name = state.get("user_name")
800
+ if not user_name or "memory" not in state or user_name not in state["memory"]:
801
+ print("Error: User not logged in or memory None.")
802
+ return [], state, gr.update()
803
+
804
+ user_data = state["memory"][user_name]
805
+
806
+ if user_data.get("sessions") and len(user_data["sessions"]) > 0:
807
+ last_session = user_data["sessions"][-1]
808
+
809
+ if len(last_session.get("conversation", [])) > 0:
810
+ session_date = last_session.get("date", "")
811
+ session_id = last_session.get("session_id", 0)
812
+
813
+ ep_summary = extract_session_summary(last_session["conversation"], session_date, session_id)
814
+
815
+ if "episodic_memory" not in user_data:
816
+ user_data["episodic_memory"] = []
817
+ user_data["episodic_memory"].append(ep_summary)
818
+
819
+ try:
820
+ existing_semantic = user_data.get("semantic_memory", {})
821
+ updated_semantic = extract_semantic_memory(ep_summary, existing_semantic)
822
+ user_data["semantic_memory"] = updated_semantic
823
+ except Exception as e:
824
+ print(f"Error updating semantic memory: {e}")
825
+
826
+ new_session_id = len(user_data.get("sessions", []))
827
+ new_session = {
828
+ "session_id": new_session_id,
829
+ "date": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
830
+ "conversation": []
831
+ }
832
+
833
+ if "sessions" not in user_data:
834
+ user_data["sessions"] = []
835
+
836
+ user_data["sessions"].append(new_session)
837
+
838
+ # ذخیره و سینک با شروع سشن جدید
839
+ sync_memory_to_hf()
840
+
841
+ state["history"] = []
842
+ session_number = len(user_data["sessions"])
843
+ new_header_text = f"<h2 style='text-align: center; color: #333;'>🧠 EMMA: Session {session_number} for {user_name}</h2>"
844
+
845
+ return [], state, gr.update(value=new_header_text)
846
+
847
+ check_user_btn.click(
848
+ handle_login_check,
849
+ inputs=[username_input, password_input, state],
850
+ outputs=[login_page, registration_fields, chat_page, login_status, active_header, system_msg, state],
851
+ )
852
+
853
+ register_btn.click(
854
+ handle_register,
855
+ inputs=[username_input, password_input, age_input, gender_input, occupation_input, residence_input, state],
856
+ outputs=[login_page, registration_fields, chat_page, login_status, active_header, system_msg, state],
857
+ )
858
+
859
+ switch_user_btn.click(
860
+ switch_user,
861
+ inputs=[state],
862
+ outputs=[login_page, registration_fields, chat_page, login_status, username_input, password_input, state, chatbot, age_input, gender_input, occupation_input, residence_input, system_msg],
863
+ )
864
+
865
+ submit_btn.click(
866
+ handle_chat,
867
+ inputs=[user_input, state],
868
+ outputs=[user_input, chatbot, state],
869
+ )
870
+
871
+ user_input.submit(
872
+ handle_chat,
873
+ inputs=[user_input, state],
874
+ outputs=[user_input, chatbot, state],
875
+ )
876
+
877
+ clear_btn.click(
878
+ clear_history,
879
+ inputs=[state],
880
+ outputs=[chatbot, state],
881
+ )
882
+
883
+ new_session_btn.click(
884
+ handle_new_session,
885
+ inputs=[state],
886
+ outputs=[chatbot, state, active_header],
887
+ )
888
+
889
+ return demo
890
+
891
+
892
+ def main():
893
+ global api_keys
894
+ gapgpt_key = os.getenv("GAPGPT_API_KEY")
895
+
896
+ if not gapgpt_key:
897
+ print("Warning: GAPGPT_API_KEY environment variable is not set. Proceeding with keys from file.")
898
+ else:
899
+ os.environ["OPENAI_API_KEY"] = gapgpt_key
900
+ os.environ["OPENAI_API_BASE"] = GAPGPT_BASE_URL
901
+ os.environ["OPENAI_BASE_URL"] = GAPGPT_BASE_URL
902
+
903
+ if gapgpt_key not in api_keys:
904
+ api_keys.insert(0, gapgpt_key)
905
+
906
+ llm = LlamaIndexOpenAI(
907
+ model="gpt-4o",
908
+ temperature=1,
909
+ max_tokens=1024,
910
+ top_p=0.95,
911
+ frequency_penalty=0.4,
912
+ presence_penalty=0.2,
913
+ api_key=gapgpt_key,
914
+ api_base=GAPGPT_BASE_URL,
915
+ )
916
+
917
+ embed_model = OpenAIEmbedding(
918
+ api_key=gapgpt_key,
919
+ api_base=GAPGPT_BASE_URL,
920
+ model="text-embedding-ada-002"
921
+ )
922
+
923
+ Settings.llm = llm
924
+ Settings.embed_model = embed_model
925
+
926
+ Settings.prompt_helper = PromptHelper(
927
+ context_window=4096,
928
+ num_output=256,
929
+ chunk_overlap_ratio=20 / 4096,
930
+ tokenizer=tokenizer,
931
+ )
932
+
933
+ demo = create_gradio_interface(Settings, api_keys)
934
+ demo.launch(
935
+ server_name="0.0.0.0",
936
+ server_port=7860,
937
+ share=False
938
+ )
939
+
940
+ if __name__ == "__main__":
941
+ signal.signal(signal.SIGINT, signal_handler)
942
+ main()
Backup code/build_memory_index_21.py ADDED
@@ -0,0 +1,259 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # Memory Indexing Utility for LlamaIndex
3
+ #
4
+ # This script provides functionalities to build and manage memory indices for a conversational AI
5
+ # using the LlamaIndex framework. It supports creating structured memory documents from raw
6
+ # conversation data and persisting them as vector indices for efficient retrieval.
7
+ #
8
+
9
+ # --- Standard Library Imports ---
10
+ import json
11
+ import os
12
+
13
+ # --- Third-Party Imports ---
14
+ import tiktoken
15
+ from llama_index.core import (
16
+ Document,
17
+ PromptHelper,
18
+ Settings, # Replaces the deprecated ServiceContext
19
+ StorageContext,
20
+ VectorStoreIndex,
21
+ load_index_from_storage,
22
+ )
23
+ from llama_index.llms.openai import OpenAI
24
+ # If a specific embedding model is needed (e.g., from OpenAI), uncomment the line below
25
+ # from llama_index.embeddings.openai import OpenAIEmbedding
26
+
27
+
28
+ # --- Global Variables ---
29
+ # A dictionary to hold the loaded or newly created indices in memory.
30
+ index_set = {}
31
+
32
+ _INDEX_BASE_DIR = os.getenv(
33
+ "EMMA_INDEX_DIR",
34
+ os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "data", "memories", "memory_index", "llamaindex")
35
+ )
36
+
37
+ # --- Core Functions ---
38
+
39
+ def setup_global_settings():
40
+ """
41
+ Configures global settings for LlamaIndex (v0.10+).
42
+
43
+ This function replaces the deprecated `build_service_context` pattern by directly
44
+ configuring the global `Settings` object for the LLM, tokenizer, and other components.
45
+ """
46
+ # 1. Set Tokenizer (to resolve potential tiktoken errors)
47
+ try:
48
+ # This encoding is commonly used by OpenAI models like GPT-4.
49
+ Settings.tokenizer = tiktoken.get_encoding("cl100k_base").encode
50
+ except Exception as e:
51
+ print(f"Warning: Could not set the tokenizer. This might lead to issues. Error: {e}")
52
+
53
+ # 2. Configure the Language Model (LLM)
54
+ # Use the LlamaIndex-specific OpenAI class to avoid conflicts with other libraries.
55
+ # It automatically reads the API key from the "OPENAI_API_KEY" environment variable.
56
+ Settings.llm = OpenAI(
57
+ model="gpt-4o",
58
+ temperature=1.0,
59
+ max_tokens=1024,
60
+ api_key=os.environ.get("GAPGPT_API_KEY"),
61
+ api_base="https://api.gapgpt.app/v1",
62
+ additional_kwargs={
63
+ "top_p": 0.95,
64
+ "frequency_penalty": 0.4,
65
+ "presence_penalty": 0.2
66
+ }
67
+ )
68
+
69
+ # 3. Configure the Prompt Helper (with updated parameter names)
70
+ # This helps manage context window size, number of outputs, etc.
71
+ Settings.prompt_helper = PromptHelper(
72
+ context_window=4096,
73
+ num_output=256,
74
+ chunk_overlap_ratio=0.1
75
+ )
76
+
77
+ # 4. Configure Chunk Size for document processing
78
+ Settings.chunk_size = 512
79
+ Settings.chunk_overlap = 20
80
+
81
+ # Optional: Configure the embedding model if needed.
82
+ # If not set, LlamaIndex defaults to a compatible model (e.g., OpenAI's text-embedding-ada-002).
83
+ # Settings.embed_model = OpenAIEmbedding(api_key=..., api_base=...)
84
+
85
+
86
+ def generate_memory_docs(data, language):
87
+ """
88
+ Generates structured LlamaIndex Document objects from a nested dictionary of user memories.
89
+
90
+ This function processes different types of memory (sessions, episodic, semantic) and
91
+ formats them into Document objects suitable for indexing.
92
+
93
+ Args:
94
+ data (dict): A dictionary where keys are user names and values contain their memories.
95
+ language (str): The language code (e.g., "en"), currently used for formatting.
96
+
97
+ Returns:
98
+ dict: A dictionary where keys are user names and values are dicts containing
99
+ lists of Document objects for each memory type.
100
+ """
101
+ all_user_memories = {}
102
+
103
+ for user_name, user_memory in data.items():
104
+ all_user_memories[user_name] = {
105
+ "sessions": [],
106
+ "episodic_memory": [],
107
+ "semantic_memory": [],
108
+ }
109
+
110
+ # Process session history
111
+ if "sessions" in user_memory:
112
+ for session in user_memory["sessions"]:
113
+ date = session["date"]
114
+ content = session["conversation"]
115
+
116
+ memory_str = f"Session on {date}:\n"
117
+ for dialog in content:
118
+ query = dialog.get("query", "")
119
+ response = dialog.get("response", "")
120
+ memory_str += f"\n{user_name}: {query.strip()}"
121
+ memory_str += f"\nAI: {response.strip()}"
122
+ memory_str += "\n"
123
+
124
+ if "summary" in user_memory and date in user_memory["summary"]:
125
+ summary = f'The summary of the conversation on {date} is: {user_memory["summary"][date]}'
126
+ memory_str += summary
127
+
128
+ # Use the 'text' argument for the Document object
129
+ all_user_memories[user_name]["sessions"].append(Document(text=memory_str))
130
+
131
+ # Process episodic memory
132
+ if "episodic_memory" in user_memory:
133
+ episodic_str = "Recent experiences:\n"
134
+ for event in user_memory["episodic_memory"]:
135
+ episodic_str += f"- {event}\n"
136
+ all_user_memories[user_name]["episodic_memory"].append(Document(text=episodic_str))
137
+
138
+ # Process semantic memory (long-term facts and personality)
139
+ if "semantic_memory" in user_memory:
140
+ semantic_str = "Long-term personality traits and facts:\n"
141
+ if isinstance(user_memory["semantic_memory"], dict):
142
+ for trait, value in user_memory["semantic_memory"].items():
143
+ semantic_str += f"- {trait}: {value}\n"
144
+ elif isinstance(user_memory["semantic_memory"], list):
145
+ for item in user_memory["semantic_memory"]:
146
+ semantic_str += f"- {item}\n"
147
+ else:
148
+ print(f"WARNING: semantic_memory for user '{user_name}' has an unexpected format: {type(user_memory['semantic_memory'])}")
149
+
150
+ all_user_memories[user_name]["semantic_memory"].append(Document(text=semantic_str))
151
+
152
+ return all_user_memories
153
+
154
+
155
+ def build_memory_index(all_user_memories, data_args, name=None):
156
+ """
157
+ Build and persist memory indices for each user and memory type.
158
+ """
159
+ structured_docs = generate_memory_docs(
160
+ all_user_memories, data_args.language
161
+ )
162
+
163
+ setup_global_settings()
164
+
165
+ for user_name, memories_by_type in structured_docs.items():
166
+ if name and user_name != name:
167
+ continue
168
+
169
+ print(f"Building indices for user '{user_name}'...")
170
+
171
+ base_path = os.path.join(_INDEX_BASE_DIR, user_name)
172
+
173
+ path_map = {
174
+ "sessions": os.path.join(base_path, "sessions"),
175
+ "episodic_memory": os.path.join(base_path, "episodic_memory"),
176
+ "semantic_memory": os.path.join(base_path, "semantic_memory"),
177
+ }
178
+
179
+ for memory_type, docs in memories_by_type.items():
180
+ if not docs:
181
+ print(f" -> Skipping '{memory_type}' index (no documents found).")
182
+ continue
183
+
184
+ if memory_type not in path_map:
185
+ print(f" -> Skipping unknown memory type: '{memory_type}'")
186
+ continue
187
+
188
+ print(f" -> Building '{memory_type}' index...")
189
+
190
+ cur_index = VectorStoreIndex.from_documents(docs)
191
+
192
+ index_dir = path_map[memory_type]
193
+ os.makedirs(index_dir, exist_ok=True)
194
+
195
+ cur_index.storage_context.persist(persist_dir=index_dir)
196
+ print(f" + Saved '{memory_type}' index to: {index_dir}")
197
+
198
+ index_set[f"{user_name}_{memory_type}"] = cur_index
199
+
200
+
201
+ # --- Deprecated Functions (Kept for reference) ---
202
+
203
+ def generate_memory_docs_old(data, language):
204
+ """
205
+ DEPRECATED: An older version of the document generation function.
206
+
207
+ This function had a simpler structure and contained Chinese string literals.
208
+ It is replaced by `generate_memory_docs`.
209
+ """
210
+ all_user_memories = {}
211
+ for user_name, user_memory in data.items():
212
+ all_user_memories[user_name] = []
213
+ if "history" not in user_memory:
214
+ continue
215
+ for date, content in user_memory["history"].items():
216
+ memory_str = f"Conversation on {date}:"
217
+
218
+ for dialog in content:
219
+ query = dialog.get("query", "")
220
+ response = dialog.get("response", "")
221
+ memory_str += f"\n{user_name}: {query.strip()}"
222
+ memory_str += f"\nAI: {response.strip()}"
223
+ memory_str += "\n"
224
+ if "summary" in user_memory and date in user_memory["summary"]:
225
+ summary = f'The summary of the conversation on {date} is: {user_memory["summary"][date]}'
226
+ memory_str += summary
227
+
228
+ all_user_memories[user_name].append(Document(text=memory_str))
229
+ return all_user_memories
230
+
231
+
232
+ def build_memory_index_old(all_user_memories, data_args, name=None):
233
+ """
234
+ DEPRECATED: An older version of the index building function.
235
+
236
+ This function used a flatter memory structure and is replaced by the more
237
+ comprehensive `build_memory_index`.
238
+ """
239
+ # The function name is a typo in the original code, corrected here for clarity.
240
+ all_user_memories_docs = generate_memory_docs(
241
+ all_user_memories, data_args.language
242
+ )
243
+
244
+ # Apply global settings
245
+ setup_global_settings()
246
+
247
+ for user_name, memories in all_user_memories_docs.items():
248
+ if name and user_name != name:
249
+ continue
250
+ print(f"Building index for user {user_name} (using old method)...")
251
+
252
+ # The service_context argument is removed in newer versions.
253
+ cur_index = VectorStoreIndex.from_documents(memories)
254
+ index_set[user_name] = cur_index
255
+
256
+ # Corrected save path; the new persist method requires a directory.
257
+ save_dir = f"../memories/memory_index/llamaindex/{user_name}_store"
258
+ os.makedirs(save_dir, exist_ok=True)
259
+ cur_index.storage_context.persist(persist_dir=save_dir)
Backup code/summarize_memory_21.py ADDED
@@ -0,0 +1,433 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+
3
+ import sys
4
+ import os
5
+ import json
6
+ import time
7
+ import copy
8
+ import argparse
9
+ from typing import Dict, Any
10
+ from openai import OpenAI
11
+ import re
12
+ import openai
13
+
14
+ # Add memory bank path
15
+ sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'memory_bank'))
16
+
17
+ # Configuration for OpenAI API
18
+ CHATGPT_CONFIG = {
19
+ "model": "gpt-4o",
20
+ "temperature": 0.7,
21
+ "max_tokens": 400,
22
+ "top_p": 1.0,
23
+ "frequency_penalty": 0.4,
24
+ "presence_penalty": 0.2,
25
+ "stop": ["<|im_end|>", "¬Human"]
26
+ }
27
+
28
+ # Default memory directory
29
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
30
+ MEMORY_DIR = os.path.join(BASE_DIR, "memories", "update_memory_0512_eng.json")
31
+
32
+ class LLMClientSimple:
33
+ def __init__(self, config, gen_config=None):
34
+ self.config = config
35
+ self.client = OpenAI(
36
+ api_key=self.config.get("api_key") or os.environ.get("GAPGPT_API_KEY"),
37
+ base_url=self.config.get("base_url") or os.environ.get(
38
+ "GAPGPT_BASE_URL", "https://api.gapgpt.app/v1"
39
+ ),
40
+ )
41
+ self.disable_tqdm = False
42
+ self.gen_config = copy.deepcopy(
43
+ gen_config if gen_config is not None else config
44
+ )
45
+
46
+ self.disable_tqdm = False
47
+ self.gen_config = gen_config
48
+
49
+ def generate_text_simple(self, prompt, prompt_num, language='en'):
50
+ self.gen_config['n'] = prompt_num
51
+ retry_times, count = 5, 0
52
+ response = None
53
+
54
+ while response is None and count < retry_times:
55
+ try:
56
+ request = copy.deepcopy(self.gen_config)
57
+
58
+ # System prompt setup based on language
59
+ if language == 'cn':
60
+ message = [
61
+ {"role": "system", "content": "The following is a transcript of a conversation between a human and a smart, psychology-savvy AI assistant."},
62
+ {"role": "user", "content": "Hello! Please help me summarize the conversation"},
63
+ {"role": "system", "content": "OK, I'll try to help you."},
64
+ {"role": "user", "content": f"{prompt}"}
65
+ ]
66
+ else:
67
+ message = [
68
+ {"role": "system", "content": "Below is a transcript of a conversation between a human and an AI assistant that is intelligent and knowledgeable in psychology."},
69
+ {"role": "user", "content": "Hello! Please help me summarize the content of the conversation."},
70
+ {"role": "system", "content": "Sure, I will do my best to assist you."},
71
+ {"role": "user", "content": f"{prompt}"}
72
+ ]
73
+
74
+ # Call the API method
75
+ response = self.client.chat.completions.create(**request, messages=message)
76
+
77
+ except Exception as e:
78
+ print(e)
79
+ # Check for context limit errors
80
+ if 'context' in str(e).lower() or 'maximum' in str(e).lower():
81
+ cut_length = 1800 - 200 * count
82
+ print('Max context length reached, cut to {}'.format(cut_length))
83
+ prompt = prompt[-cut_length:]
84
+ response = None
85
+ count += 1
86
+
87
+ if response:
88
+ # Get response content using dot notation
89
+ task_desc = response.choices[0].message.content
90
+ else:
91
+ task_desc = ''
92
+
93
+ return task_desc
94
+
95
+
96
+ # Lazy initialization instead of module-level
97
+ _llm_client = None
98
+
99
+ def get_llm_client():
100
+ global _llm_client
101
+ if _llm_client is None:
102
+ _llm_client = LLMClientSimple(CHATGPT_CONFIG, gen_config=CHATGPT_CONFIG)
103
+ return _llm_client
104
+
105
+
106
+
107
+ # --- Prompt Generation Functions ---
108
+
109
+ def summarize_content_prompt(content, user_name, boot_name, language='en'):
110
+ # Base prompt adjusted based on psychology
111
+ prompt = 'Please summarize the following dialogue from a psychological perspective, focusing on the user\'s emotional state, key concerns, and potential therapeutic insights. Dialogue content:\n'
112
+
113
+ for dialog in content:
114
+ query = dialog['query']
115
+ response = dialog['response']
116
+ prompt += f"\n{user_name}: {query.strip()}"
117
+ prompt += f"\n{boot_name}: {response.strip()}"
118
+
119
+ prompt += ('\nSummary:' if language == 'cn' else '\nSummarization:')
120
+ return prompt
121
+
122
+
123
+ def summarize_user_issues_prompt(content, user_name, boot_name, language='en'):
124
+ """
125
+ Analyze the conversation from a psychological perspective, identify user problems and their causes,
126
+ and organize them into a structured JSON format.
127
+ """
128
+ prompt = (
129
+ "Please analyze the following dialogue from a psychological perspective. Identify the user's key "
130
+ "problems and the underlying causes behind them. Structure the results in a clear and concise way. "
131
+ "Dialogue content:\n"
132
+ )
133
+
134
+ for dialog in content:
135
+ query = dialog['query']
136
+ response = dialog['response']
137
+ prompt += f"\n{user_name}: {query.strip()}"
138
+ prompt += f"\n{boot_name}: {response.strip()}"
139
+
140
+ prompt += '\n\nPlease list the user’s problems and their causes in JSON format, like this:\n'
141
+ prompt += '{\n "problems": [\n {\n "problem": "<problem_1>",\n "cause": "<cause_1>"\n },\n {\n "problem": "<problem_2>",\n "cause": "<cause_2>"\n }\n ]\n}'
142
+
143
+ return prompt
144
+
145
+
146
+ def summarize_overall_prompt(content, language='en'):
147
+ prompt = 'Please summarize the following events in a high-level manner, and try to be as concise as possible, summarizing and retaining the core key information:\n' if language == 'cn' else "Please provide a highly concise summary of the following event, capturing the essential key information as succinctly as possible. Summarize the event:\n"
148
+
149
+ for date, summary_dict in content:
150
+ summary = summary_dict['content']
151
+ prompt += (f"\nTime {date} The event that occurred was {summary.strip()}" if language == 'cn' else f"At {date}, the events are {summary.strip()}")
152
+
153
+ prompt += ('\nSummary:' if language == 'cn' else '\nSummarization:')
154
+ return prompt
155
+
156
+
157
+ def summarize_overall_personality(content, language='en'):
158
+ prompt = 'Below are the personality traits and moods of users in multiple conversations, as well as the appropriate response strategies at the time:\n' if language == 'cn' else "The following are the user's exhibited personality traits and emotions throughout multiple dialogues, along with appropriate response strategies for the current situation:"
159
+
160
+ for date, summary in content:
161
+ prompt += (f"\n in time {date} The analysis is {summary.strip()}" if language == 'cn' else f"At {date}, the analysis shows {summary.strip()}")
162
+
163
+ prompt += ('\nPlease give a general summary of the users personality and the most appropriate response strategy for the AI lover, and try to be concise and highly summarized. The summary is:' if language == 'cn' else "Please provide a highly concise and general summary of the user's personality and the most appropriate response strategy for the AI lover, summarized as:")
164
+ return prompt
165
+
166
+
167
+ def summarize_person_prompt(content, user_name, boot_name, language):
168
+ prompt = f"Based on the following dialogue, analyze {user_name}'s emotional state, cognitive patterns, and potential psychological needs. Suggest therapeutic strategies for the AI to respond empathetically and effectively. Dialogue content:\n"
169
+
170
+ for dialog in content:
171
+ query = dialog['query']
172
+ response = dialog['response']
173
+ prompt += f"\n{user_name}: {query.strip()}"
174
+ prompt += f"\n{boot_name}: {response.strip()}"
175
+
176
+ prompt += (f'\n{user_name} personality traits, mood, {boot_name} The response strategy is:' if language == 'cn' else f"\n{user_name}'s personality traits, emotions, and {boot_name}'s response strategy are:")
177
+ return prompt
178
+
179
+
180
+ # --- Memory Extraction Functions ---
181
+
182
+ def extract_session_summary(conversation_text, session_date, session_id):
183
+ """
184
+ Extracts session-level insights and creates an episodic memory entry.
185
+
186
+ Args:
187
+ conversation_text (str): The full conversation from the session.
188
+ session_date (str): The date of the session.
189
+ session_id (int): The index of the session.
190
+
191
+ Returns:
192
+ dict: Extracted episodic memory entry.
193
+ """
194
+ # Prompt to the LLM to extract insights
195
+ insight_prompt = f"""
196
+ Please analyze the following conversation and extract key information:
197
+
198
+ Conversation:
199
+ {conversation_text}
200
+
201
+ 1. Topics discussed (as a list)
202
+ 2. User's emotional state (e.g., happy, anxious, stressed)
203
+ 3. Key insights or takeaways (what the user is feeling or thinking)
204
+
205
+ Format response as JSON:
206
+ {{
207
+ "topics_discussed": [],
208
+ "emotional_state": "",
209
+ "insights": ""
210
+ }}
211
+ """
212
+
213
+ response = get_llm_client().generate_text_simple(
214
+ prompt=insight_prompt,
215
+ prompt_num=1,
216
+ language="en",
217
+ )
218
+
219
+ # Remove extra whitespace
220
+ response = response.strip()
221
+
222
+ # حذف تگ شروع بلاک کد مارک‌داون
223
+ if response.startswith("```json"):response = response[7:]
224
+ elif response.startswith("```"):response = response[3:]
225
+
226
+ # حذف تگ پایان بلاک کد مارک‌داون
227
+ if response.endswith("```"):
228
+ response = response[:-3]
229
+
230
+ response = response.strip()
231
+
232
+ try:
233
+ episodic_memory = json.loads(response)
234
+ except json.JSONDecodeError:
235
+ print("⚠️ Failed to parse episodic memory response!")
236
+ episodic_memory = {
237
+ "topics_discussed": ["N/A"],
238
+ "emotional_state": "unknown",
239
+ "insights": "No insights extracted.",
240
+ }
241
+
242
+ # Add session metadata
243
+ episodic_memory["session_date"] = session_date
244
+ episodic_memory["session_id"] = session_id
245
+
246
+ return episodic_memory
247
+
248
+
249
+
250
+ def extract_semantic_memory(latest_episodic_memory, existing_semantic_memory):
251
+ """
252
+ Updates semantic memory based on the latest episodic memory.
253
+ """
254
+ # Use the insights from episodic memory to infer stable traits and patterns
255
+ semantic_prompt = f"""
256
+ Please analyze the following session summary and infer long-term personality traits and stable characteristics:
257
+ Session Summary:
258
+ {json.dumps(latest_episodic_memory, indent=4)}
259
+
260
+ Update the user's semantic memory with:
261
+ 1. Evolving personality traits (Big Five).
262
+ 2. Core values and recurring motivations.
263
+ 3. Behavioral patterns and consistent emotional responses.
264
+ 4. Frequent topics and recurring themes.
265
+
266
+ Format response as JSON:
267
+ {{
268
+ "personality_traits": {{
269
+ "openness": 0.0,
270
+ "conscientiousness": 0.0,
271
+ "extraversion": 0.0,
272
+ "agreeableness": 0.0,
273
+ "neuroticism": 0.0
274
+ }},
275
+ "core_values": [],
276
+ "behavioral_patterns": [],
277
+ "recurring_themes": []
278
+ }}
279
+ """
280
+
281
+ response = get_llm_client().generate_text_simple(prompt=semantic_prompt, prompt_num=1, language='en')
282
+
283
+ # Safe JSON extraction using Regex
284
+ try:
285
+ # Find content between first { and last }
286
+ json_match = re.search(r'\{.*\}', response, re.DOTALL)
287
+ if json_match:
288
+ json_str = json_match.group(0)
289
+ new_semantic_data = json.loads(json_str)
290
+ else:
291
+ raise ValueError("No JSON object found in response.")
292
+
293
+ except Exception as e:
294
+ print(f"⚠️ Failed to parse semantic memory response! Error: {e}")
295
+ print(f"LLM Response was:\n{response}")
296
+ return existing_semantic_memory
297
+
298
+ # Merge new data into semantic memory
299
+ updated_semantic_memory = existing_semantic_memory if isinstance(existing_semantic_memory, dict) else {}
300
+
301
+ # Update or average personality traits
302
+ for trait, value in new_semantic_data.get("personality_traits", {}).items():
303
+ try:
304
+ val_float = float(value)
305
+ except (ValueError, TypeError):
306
+ val_float = 0.0
307
+
308
+ if trait in updated_semantic_memory.get("personality_traits", {}):
309
+ existing_value = float(updated_semantic_memory["personality_traits"].get(trait, 0.0))
310
+ updated_semantic_memory["personality_traits"][trait] = round((existing_value + val_float) / 2, 2)
311
+ else:
312
+ updated_semantic_memory.setdefault("personality_traits", {})[trait] = val_float
313
+
314
+ # Combine core values, avoiding duplicates
315
+ updated_semantic_memory["core_values"] = list(set(
316
+ updated_semantic_memory.get("core_values", []) + new_semantic_data.get("core_values", [])
317
+ ))
318
+
319
+ # Combine behavioral patterns
320
+ updated_semantic_memory["behavioral_patterns"] = list(set(
321
+ updated_semantic_memory.get("behavioral_patterns", []) + new_semantic_data.get("behavioral_patterns", [])
322
+ ))
323
+
324
+ # Combine recurring themes
325
+ updated_semantic_memory["recurring_themes"] = list(set(
326
+ updated_semantic_memory.get("recurring_themes", []) + new_semantic_data.get("recurring_themes", [])
327
+ ))
328
+
329
+ print("\n✅ Semantic memory updated successfully!")
330
+ return updated_semantic_memory
331
+
332
+ # Merge new data into semantic memory
333
+ updated_semantic_memory = existing_semantic_memory if isinstance(existing_semantic_memory, dict) else {}
334
+
335
+ # Update or average personality traits
336
+ for trait, value in new_semantic_data.get("personality_traits", {}).items():
337
+ try:
338
+ val_float = float(value)
339
+ except (ValueError, TypeError):
340
+ val_float = 0.0
341
+
342
+ if trait in updated_semantic_memory.get("personality_traits", {}):
343
+ existing_value = float(updated_semantic_memory["personality_traits"].get(trait, 0.0))
344
+ updated_semantic_memory["personality_traits"][trait] = round((existing_value + val_float) / 2, 2)
345
+ else:
346
+ updated_semantic_memory.setdefault("personality_traits", {})[trait] = val_float
347
+
348
+ # Combine core values, avoiding duplicates
349
+ updated_semantic_memory["core_values"] = list(set(
350
+ updated_semantic_memory.get("core_values", []) + new_semantic_data.get("core_values", [])
351
+ ))
352
+
353
+ # Combine behavioral patterns
354
+ updated_semantic_memory["behavioral_patterns"] = list(set(
355
+ updated_semantic_memory.get("behavioral_patterns", []) + new_semantic_data.get("behavioral_patterns", [])
356
+ ))
357
+
358
+ # Combine recurring themes
359
+ updated_semantic_memory["recurring_themes"] = list(set(
360
+ updated_semantic_memory.get("recurring_themes", []) + new_semantic_data.get("recurring_themes", [])
361
+ ))
362
+
363
+ print("\n✅ Semantic memory updated successfully!")
364
+ return updated_semantic_memory
365
+
366
+
367
+ # --- Main Logic ---
368
+
369
+ def summarize_memory(memory_dir, name=None, language='en'):
370
+ boot_name = 'AI'
371
+ gen_prompt_num = 1
372
+
373
+ with open(memory_dir, 'r', encoding='utf8') as f:
374
+ memory = json.loads(f.read())
375
+
376
+ for k, v in memory.items():
377
+ if name is not None and k != name:
378
+ continue
379
+
380
+ user_name = k
381
+ print(f'Updating memory for user {user_name}')
382
+
383
+ if v.get('history') is None:
384
+ continue
385
+
386
+ history = v['history']
387
+
388
+ if v.get('summary') is None:
389
+ memory[user_name]['summary'] = {}
390
+ if v.get('personality') is None:
391
+ memory[user_name]['personality'] = {}
392
+ if v.get('issues') is None:
393
+ memory[user_name]['issues'] = {}
394
+
395
+ for date, content in history.items():
396
+ # Check flags to see if update is needed
397
+ his_flag = False if (date in v['summary'].keys() and v['summary'][date]) else True
398
+ person_flag = False if (date in v['personality'].keys() and v['personality'][date]) else True
399
+ problem_flag = False if (date in v['issues'].keys() and v['issues'][date]) else True
400
+
401
+ hisprompt = summarize_content_prompt(content, user_name, boot_name, language)
402
+ issues_prompt = summarize_user_issues_prompt(content, user_name, boot_name, language)
403
+ person_prompt = summarize_person_prompt(content, user_name, boot_name, language)
404
+
405
+ if his_flag:
406
+ his_summary = get_llm_client().generate_text_simple(prompt=hisprompt, prompt_num=gen_prompt_num, language=language)
407
+ memory[user_name]['summary'][date] = {'content': his_summary}
408
+
409
+ if problem_flag:
410
+ issues_summary = get_llm_client().generate_text_simple(prompt=issues_prompt, prompt_num=gen_prompt_num, language=language)
411
+ memory[user_name]['issues'][date] = {'content': issues_summary}
412
+ print(issues_summary)
413
+
414
+ if person_flag:
415
+ person_summary = get_llm_client().generate_text_simple(prompt=person_prompt, prompt_num=gen_prompt_num, language=language)
416
+ memory[user_name]['personality'][date] = person_summary
417
+
418
+ overall_his_prompt = summarize_overall_prompt(list(memory[user_name]['summary'].items()), language=language)
419
+ overall_person_prompt = summarize_overall_personality(list(memory[user_name]['personality'].items()), language=language)
420
+
421
+ memory[user_name]['overall_history'] = get_llm_client().generate_text_simple(prompt=overall_his_prompt, prompt_num=gen_prompt_num, language=language)
422
+ memory[user_name]['overall_personality'] = get_llm_client().generate_text_simple(prompt=overall_person_prompt, prompt_num=gen_prompt_num, language=language)
423
+
424
+ with open(memory_dir, 'w', encoding='utf8') as f:
425
+ print(f'Successfully updated memory for {name}')
426
+ json.dump(memory, f, ensure_ascii=False)
427
+
428
+ return memory
429
+
430
+
431
+ if __name__ == '__main__':
432
+ target_memory = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'memories', 'eng_memory_cases.json')
433
+ summarize_memory(target_memory, language='en')
memory_bank/build_memory_index.py CHANGED
@@ -1,9 +1,7 @@
 
1
  #
2
  # Memory Indexing Utility for LlamaIndex
3
- #
4
- # This script provides functionalities to build and manage memory indices for a conversational AI
5
- # using the LlamaIndex framework. It supports creating structured memory documents from raw
6
- # conversation data and persisting them as vector indices for efficient retrieval.
7
  #
8
 
9
  # --- Standard Library Imports ---
@@ -15,50 +13,50 @@ import tiktoken
15
  from llama_index.core import (
16
  Document,
17
  PromptHelper,
18
- Settings, # Replaces the deprecated ServiceContext
19
  StorageContext,
20
  VectorStoreIndex,
21
  load_index_from_storage,
22
  )
23
  from llama_index.llms.openai import OpenAI
24
- # If a specific embedding model is needed (e.g., from OpenAI), uncomment the line below
25
- # from llama_index.embeddings.openai import OpenAIEmbedding
26
-
27
 
28
- # --- Global Variables ---
29
- # A dictionary to hold the loaded or newly created indices in memory.
30
  index_set = {}
31
 
 
 
 
 
32
  _INDEX_BASE_DIR = os.getenv(
33
- "EMMA_INDEX_DIR",
34
- os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "data", "memories", "memory_index", "llamaindex")
35
  )
 
36
 
37
- # --- Core Functions ---
 
38
 
39
  def setup_global_settings():
40
  """
41
  Configures global settings for LlamaIndex (v0.10+).
42
-
43
- This function replaces the deprecated `build_service_context` pattern by directly
44
- configuring the global `Settings` object for the LLM, tokenizer, and other components.
45
  """
46
- # 1. Set Tokenizer (to resolve potential tiktoken errors)
 
 
47
  try:
48
- # This encoding is commonly used by OpenAI models like GPT-4.
49
  Settings.tokenizer = tiktoken.get_encoding("cl100k_base").encode
50
  except Exception as e:
51
- print(f"Warning: Could not set the tokenizer. This might lead to issues. Error: {e}")
52
 
53
- # 2. Configure the Language Model (LLM)
54
- # Use the LlamaIndex-specific OpenAI class to avoid conflicts with other libraries.
55
- # It automatically reads the API key from the "OPENAI_API_KEY" environment variable.
56
  Settings.llm = OpenAI(
57
  model="gpt-4o",
58
  temperature=1.0,
59
  max_tokens=1024,
60
- api_key=os.environ.get("GAPGPT_API_KEY"),
61
- api_base="https://api.gapgpt.app/v1",
62
  additional_kwargs={
63
  "top_p": 0.95,
64
  "frequency_penalty": 0.4,
@@ -66,37 +64,30 @@ def setup_global_settings():
66
  }
67
  )
68
 
69
- # 3. Configure the Prompt Helper (with updated parameter names)
70
- # This helps manage context window size, number of outputs, etc.
 
 
 
 
 
 
71
  Settings.prompt_helper = PromptHelper(
72
  context_window=4096,
73
  num_output=256,
74
  chunk_overlap_ratio=0.1
75
  )
76
 
77
- # 4. Configure Chunk Size for document processing
78
  Settings.chunk_size = 512
79
  Settings.chunk_overlap = 20
80
 
81
- # Optional: Configure the embedding model if needed.
82
- # If not set, LlamaIndex defaults to a compatible model (e.g., OpenAI's text-embedding-ada-002).
83
- # Settings.embed_model = OpenAIEmbedding(api_key=..., api_base=...)
84
 
 
85
 
86
- def generate_memory_docs(data, language):
87
  """
88
- Generates structured LlamaIndex Document objects from a nested dictionary of user memories.
89
-
90
- This function processes different types of memory (sessions, episodic, semantic) and
91
- formats them into Document objects suitable for indexing.
92
-
93
- Args:
94
- data (dict): A dictionary where keys are user names and values contain their memories.
95
- language (str): The language code (e.g., "en"), currently used for formatting.
96
-
97
- Returns:
98
- dict: A dictionary where keys are user names and values are dicts containing
99
- lists of Document objects for each memory type.
100
  """
101
  all_user_memories = {}
102
 
@@ -107,11 +98,11 @@ def generate_memory_docs(data, language):
107
  "semantic_memory": [],
108
  }
109
 
110
- # Process session history
111
- if "sessions" in user_memory:
112
  for session in user_memory["sessions"]:
113
- date = session["date"]
114
- content = session["conversation"]
115
 
116
  memory_str = f"Session on {date}:\n"
117
  for dialog in content:
@@ -125,18 +116,17 @@ def generate_memory_docs(data, language):
125
  summary = f'The summary of the conversation on {date} is: {user_memory["summary"][date]}'
126
  memory_str += summary
127
 
128
- # Use the 'text' argument for the Document object
129
  all_user_memories[user_name]["sessions"].append(Document(text=memory_str))
130
 
131
- # Process episodic memory
132
- if "episodic_memory" in user_memory:
133
  episodic_str = "Recent experiences:\n"
134
  for event in user_memory["episodic_memory"]:
135
  episodic_str += f"- {event}\n"
136
  all_user_memories[user_name]["episodic_memory"].append(Document(text=episodic_str))
137
 
138
- # Process semantic memory (long-term facts and personality)
139
- if "semantic_memory" in user_memory:
140
  semantic_str = "Long-term personality traits and facts:\n"
141
  if isinstance(user_memory["semantic_memory"], dict):
142
  for trait, value in user_memory["semantic_memory"].items():
@@ -145,20 +135,21 @@ def generate_memory_docs(data, language):
145
  for item in user_memory["semantic_memory"]:
146
  semantic_str += f"- {item}\n"
147
  else:
148
- print(f"WARNING: semantic_memory for user '{user_name}' has an unexpected format: {type(user_memory['semantic_memory'])}")
149
 
150
  all_user_memories[user_name]["semantic_memory"].append(Document(text=semantic_str))
151
 
152
  return all_user_memories
153
 
154
 
155
- def build_memory_index(all_user_memories, data_args, name=None):
 
 
156
  """
157
- Build and persist memory indices for each user and memory type.
158
  """
159
- structured_docs = generate_memory_docs(
160
- all_user_memories, data_args.language
161
- )
162
 
163
  setup_global_settings()
164
 
@@ -198,62 +189,38 @@ def build_memory_index(all_user_memories, data_args, name=None):
198
  index_set[f"{user_name}_{memory_type}"] = cur_index
199
 
200
 
201
- # --- Deprecated Functions (Kept for reference) ---
202
-
203
- def generate_memory_docs_old(data, language):
204
  """
205
- DEPRECATED: An older version of the document generation function.
206
-
207
- This function had a simpler structure and contained Chinese string literals.
208
- It is replaced by `generate_memory_docs`.
209
  """
210
- all_user_memories = {}
211
- for user_name, user_memory in data.items():
212
- all_user_memories[user_name] = []
213
- if "history" not in user_memory:
214
- continue
215
- for date, content in user_memory["history"].items():
216
- memory_str = f"Conversation on {date}:"
217
-
218
- for dialog in content:
219
- query = dialog.get("query", "")
220
- response = dialog.get("response", "")
221
- memory_str += f"\n{user_name}: {query.strip()}"
222
- memory_str += f"\nAI: {response.strip()}"
223
- memory_str += "\n"
224
- if "summary" in user_memory and date in user_memory["summary"]:
225
- summary = f'The summary of the conversation on {date} is: {user_memory["summary"][date]}'
226
- memory_str += summary
227
-
228
- all_user_memories[user_name].append(Document(text=memory_str))
229
- return all_user_memories
230
-
231
-
232
- def build_memory_index_old(all_user_memories, data_args, name=None):
233
- """
234
- DEPRECATED: An older version of the index building function.
235
-
236
- This function used a flatter memory structure and is replaced by the more
237
- comprehensive `build_memory_index`.
238
- """
239
- # The function name is a typo in the original code, corrected here for clarity.
240
- all_user_memories_docs = generate_memory_docs(
241
- all_user_memories, data_args.language
242
- )
243
-
244
- # Apply global settings
245
  setup_global_settings()
 
 
 
 
246
 
247
- for user_name, memories in all_user_memories_docs.items():
248
- if name and user_name != name:
249
- continue
250
- print(f"Building index for user {user_name} (using old method)...")
 
 
 
 
 
 
 
 
251
 
252
- # The service_context argument is removed in newer versions.
253
- cur_index = VectorStoreIndex.from_documents(memories)
254
- index_set[user_name] = cur_index
255
 
256
- # Corrected save path; the new persist method requires a directory.
257
- save_dir = f"../memories/memory_index/llamaindex/{user_name}_store"
258
- os.makedirs(save_dir, exist_ok=True)
259
- cur_index.storage_context.persist(persist_dir=save_dir)
 
 
 
 
 
 
 
 
1
+ # -*- coding:utf-8 -*-
2
  #
3
  # Memory Indexing Utility for LlamaIndex
4
+ # Project: EMMA (Empathetic Memory-augmented Multi-layer Assistant)
 
 
 
5
  #
6
 
7
  # --- Standard Library Imports ---
 
13
  from llama_index.core import (
14
  Document,
15
  PromptHelper,
16
+ Settings,
17
  StorageContext,
18
  VectorStoreIndex,
19
  load_index_from_storage,
20
  )
21
  from llama_index.llms.openai import OpenAI
22
+ from llama_index.embeddings.openai import OpenAIEmbedding
 
 
23
 
24
+ # --- Global Variables & Paths ---
 
25
  index_set = {}
26
 
27
+ BASE_DATA_DIR = os.getenv(
28
+ "EMMA_DATA_DIR",
29
+ os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "data")
30
+ )
31
  _INDEX_BASE_DIR = os.getenv(
32
+ "EMMA_INDEX_DIR",
33
+ os.path.join(BASE_DATA_DIR, "memories", "memory_index", "llamaindex")
34
  )
35
+ GAPGPT_BASE_URL = os.getenv("GAPGPT_BASE_URL", "https://api.gapgpt.app/v1")
36
 
37
+
38
+ # --- Core Configuration Functions ---
39
 
40
  def setup_global_settings():
41
  """
42
  Configures global settings for LlamaIndex (v0.10+).
43
+ Sets LLM and Embedding models to route through GapGPT.
 
 
44
  """
45
+ GAPGPTMASKTOKENwdfn4ezolb9X0X = os.getenv("GAPGPT_API_KEY") or os.getenv("OPENAI_API_KEY")
46
+
47
+ # 1. Set Tokenizer
48
  try:
 
49
  Settings.tokenizer = tiktoken.get_encoding("cl100k_base").encode
50
  except Exception as e:
51
+ print(f"Warning: Could not set tokenizer: {e}")
52
 
53
+ # 2. Configure LLM for GapGPT
 
 
54
  Settings.llm = OpenAI(
55
  model="gpt-4o",
56
  temperature=1.0,
57
  max_tokens=1024,
58
+ api_key=GAPGPTMASKTOKENwdfn4ezolb9X1X,
59
+ api_base=GAPGPT_BASE_URL,
60
  additional_kwargs={
61
  "top_p": 0.95,
62
  "frequency_penalty": 0.4,
 
64
  }
65
  )
66
 
67
+ # 3. Configure Embedding Model for GapGPT
68
+ Settings.embed_model = OpenAIEmbedding(
69
+ model="text-embedding-ada-002",
70
+ api_key=GAPGPTMASKTOKENwdfn4ezolb9X2X,
71
+ api_base=GAPGPT_BASE_URL
72
+ )
73
+
74
+ # 4. Configure Prompt Helper
75
  Settings.prompt_helper = PromptHelper(
76
  context_window=4096,
77
  num_output=256,
78
  chunk_overlap_ratio=0.1
79
  )
80
 
81
+ # 5. Chunk Settings
82
  Settings.chunk_size = 512
83
  Settings.chunk_overlap = 20
84
 
 
 
 
85
 
86
+ # --- Document Generation Functions ---
87
 
88
+ def generate_memory_docs(data, language="en"):
89
  """
90
+ Generates structured LlamaIndex Document objects from nested user memory dictionary.
 
 
 
 
 
 
 
 
 
 
 
91
  """
92
  all_user_memories = {}
93
 
 
98
  "semantic_memory": [],
99
  }
100
 
101
+ # 1. Process session history
102
+ if "sessions" in user_memory and user_memory["sessions"]:
103
  for session in user_memory["sessions"]:
104
+ date = session.get("date", "")
105
+ content = session.get("conversation", [])
106
 
107
  memory_str = f"Session on {date}:\n"
108
  for dialog in content:
 
116
  summary = f'The summary of the conversation on {date} is: {user_memory["summary"][date]}'
117
  memory_str += summary
118
 
 
119
  all_user_memories[user_name]["sessions"].append(Document(text=memory_str))
120
 
121
+ # 2. Process episodic memory
122
+ if "episodic_memory" in user_memory and user_memory["episodic_memory"]:
123
  episodic_str = "Recent experiences:\n"
124
  for event in user_memory["episodic_memory"]:
125
  episodic_str += f"- {event}\n"
126
  all_user_memories[user_name]["episodic_memory"].append(Document(text=episodic_str))
127
 
128
+ # 3. Process semantic memory
129
+ if "semantic_memory" in user_memory and user_memory["semantic_memory"]:
130
  semantic_str = "Long-term personality traits and facts:\n"
131
  if isinstance(user_memory["semantic_memory"], dict):
132
  for trait, value in user_memory["semantic_memory"].items():
 
135
  for item in user_memory["semantic_memory"]:
136
  semantic_str += f"- {item}\n"
137
  else:
138
+ print(f"WARNING: semantic_memory for '{user_name}' has unexpected format: {type(user_memory['semantic_memory'])}")
139
 
140
  all_user_memories[user_name]["semantic_memory"].append(Document(text=semantic_str))
141
 
142
  return all_user_memories
143
 
144
 
145
+ # --- Indexing & Storage Functions ---
146
+
147
+ def build_memory_index(all_user_memories, data_args=None, name=None):
148
  """
149
+ Build and persist memory indices for each user and memory layer.
150
  """
151
+ language = getattr(data_args, 'language', 'en') if data_args else 'en'
152
+ structured_docs = generate_memory_docs(all_user_memories, language=language)
 
153
 
154
  setup_global_settings()
155
 
 
189
  index_set[f"{user_name}_{memory_type}"] = cur_index
190
 
191
 
192
+ def load_memory_index(user_name, memory_type):
 
 
193
  """
194
+ Loads a specific persisted VectorStoreIndex from disk.
 
 
 
195
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
  setup_global_settings()
197
+
198
+ key = f"{user_name}_{memory_type}"
199
+ if key in index_set:
200
+ return index_set[key]
201
 
202
+ index_dir = os.path.join(_INDEX_BASE_DIR, user_name, memory_type)
203
+ if not os.path.exists(index_dir):
204
+ return None
205
+
206
+ try:
207
+ storage_context = StorageContext.from_defaults(persist_dir=index_dir)
208
+ index = load_index_from_storage(storage_context)
209
+ index_set[key] = index
210
+ return index
211
+ except Exception as e:
212
+ print(f"Error loading index for {key} from {index_dir}: {e}")
213
+ return None
214
 
 
 
 
215
 
216
+ def load_all_user_indices(user_name):
217
+ """
218
+ Loads all available memory layers (sessions, episodic, semantic) for a given user.
219
+ """
220
+ memory_types = ["sessions", "episodic_memory", "semantic_memory"]
221
+ loaded = {}
222
+ for m_type in memory_types:
223
+ idx = load_memory_index(user_name, m_type)
224
+ if idx:
225
+ loaded[m_type] = idx
226
+ return loaded
memory_bank/summarize_memory.py CHANGED
@@ -11,8 +11,9 @@ from openai import OpenAI
11
  import re
12
  import openai
13
 
14
- # Add memory bank path
15
- sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'memory_bank'))
 
16
 
17
  # Configuration for OpenAI API
18
  CHATGPT_CONFIG = {
@@ -25,19 +26,18 @@ CHATGPT_CONFIG = {
25
  "stop": ["<|im_end|>", "¬Human"]
26
  }
27
 
28
- # Default memory directory
29
- BASE_DIR = os.path.dirname(os.path.abspath(__file__))
30
- MEMORY_DIR = os.path.join(BASE_DIR, "memories", "update_memory_0512_eng.json")
31
 
32
  class LLMClientSimple:
33
  def __init__(self, config, gen_config=None):
34
  self.config = config
35
  self.client = OpenAI(
36
- api_key=self.config.get("api_key") or os.environ.get("GAPGPT_API_KEY"),
37
- base_url=self.config.get("base_url") or os.environ.get(
38
- "GAPGPT_BASE_URL", "https://api.gapgpt.app/v1"
39
- ),
40
- )
41
  self.disable_tqdm = False
42
  self.gen_config = copy.deepcopy(
43
  gen_config if gen_config is not None else config
@@ -429,5 +429,9 @@ def summarize_memory(memory_dir, name=None, language='en'):
429
 
430
 
431
  if __name__ == '__main__':
432
- target_memory = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'memories', 'eng_memory_cases.json')
 
 
 
433
  summarize_memory(target_memory, language='en')
 
 
11
  import re
12
  import openai
13
 
14
+ # Add project root and memory bank path safely
15
+ PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
16
+ sys.path.append(os.path.join(PROJECT_ROOT, 'memory_bank'))
17
 
18
  # Configuration for OpenAI API
19
  CHATGPT_CONFIG = {
 
26
  "stop": ["<|im_end|>", "¬Human"]
27
  }
28
 
29
+ # Default memory directory using dynamic paths
30
+ BASE_DATA_DIR = os.environ.get("EMMA_DATA_DIR", PROJECT_ROOT)
31
+ MEMORY_DIR = os.path.join(BASE_DATA_DIR, "memories", "update_memory_0512_eng.json")
32
 
33
  class LLMClientSimple:
34
  def __init__(self, config, gen_config=None):
35
  self.config = config
36
  self.client = OpenAI(
37
+ api_key=os.environ.get("GAPGPT_API_KEY", "YOUR_API_KEY_HERE"),
38
+ base_url=os.environ.get("GAPGPT_BASE_URL", "https://api.gapgpt.app/v1"),
39
+ ),
40
+
 
41
  self.disable_tqdm = False
42
  self.gen_config = copy.deepcopy(
43
  gen_config if gen_config is not None else config
 
429
 
430
 
431
  if __name__ == '__main__':
432
+ # Use dynamic base directory for the target memory file
433
+ BASE_DATA_DIR = os.environ.get("EMMA_DATA_DIR", os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
434
+ target_memory = os.path.join(BASE_DATA_DIR, 'memories', 'eng_memory_cases.json')
435
+
436
  summarize_memory(target_memory, language='en')
437
+