EMMA commited on
Commit
36013e0
·
1 Parent(s): 792d769

update code

Browse files
Backup code/build_memory_index_25.py ADDED
@@ -0,0 +1,271 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ # پیکربندی دایرکتوری‌ها بر اساس ساختار استاندارد درخواستی
33
+ CURRENT_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
34
+ BASE_DIR = os.path.abspath(os.path.join(CURRENT_SCRIPT_DIR, ".."))
35
+
36
+ # <--- تغییر در اینجا: نام پوشه به Emma-memory-storage تغییر یافت --->
37
+ MEMORIES_DIR = os.path.join(BASE_DIR, "Emma-memory-storage")
38
+ INDEX_BASE_DIR = os.path.join(MEMORIES_DIR, "memory_index")
39
+
40
+ # تنظیم مسیر دقیق برای llamaindex
41
+ _LLAMAINDEX_BASE_DIR = os.getenv("EMMA_INDEX_DIR", os.path.join(INDEX_BASE_DIR, "llamaindex"))
42
+
43
+ # اطمینان از وجود دایرکتوری اصلی در صورت ساخت ایندکس جدید
44
+ os.makedirs(_LLAMAINDEX_BASE_DIR, exist_ok=True)
45
+
46
+
47
+
48
+ # --- Core Functions ---
49
+
50
+ def setup_global_settings():
51
+ """
52
+ Configures global settings for LlamaIndex (v0.10+).
53
+
54
+ This function replaces the deprecated `build_service_context` pattern by directly
55
+ configuring the global `Settings` object for the LLM, tokenizer, and other components.
56
+ """
57
+ # 1. Set Tokenizer (to resolve potential tiktoken errors)
58
+ try:
59
+ # This encoding is commonly used by OpenAI models like GPT-4.
60
+ Settings.tokenizer = tiktoken.get_encoding("cl100k_base").encode
61
+ except Exception as e:
62
+ print(f"Warning: Could not set the tokenizer. This might lead to issues. Error: {e}")
63
+
64
+ # 2. Configure the Language Model (LLM)
65
+ # Use the LlamaIndex-specific OpenAI class to avoid conflicts with other libraries.
66
+ # It automatically reads the API key from the "OPENAI_API_KEY" environment variable.
67
+ Settings.llm = OpenAI(
68
+ model="gpt-4o",
69
+ temperature=1.0,
70
+ max_tokens=1024,
71
+ api_key=os.environ.get("GAPGPT_API_KEY"),
72
+ api_base="https://api.gapgpt.app/v1",
73
+ additional_kwargs={
74
+ "top_p": 0.95,
75
+ "frequency_penalty": 0.4,
76
+ "presence_penalty": 0.2
77
+ }
78
+ )
79
+
80
+ # 3. Configure the Prompt Helper (with updated parameter names)
81
+ # This helps manage context window size, number of outputs, etc.
82
+ Settings.prompt_helper = PromptHelper(
83
+ context_window=4096,
84
+ num_output=256,
85
+ chunk_overlap_ratio=0.1
86
+ )
87
+
88
+ # 4. Configure Chunk Size for document processing
89
+ Settings.chunk_size = 512
90
+ Settings.chunk_overlap = 20
91
+
92
+ # Optional: Configure the embedding model if needed.
93
+ # If not set, LlamaIndex defaults to a compatible model (e.g., OpenAI's text-embedding-ada-002).
94
+ # Settings.embed_model = OpenAIEmbedding(api_key=GAPGPTMASKTOKENlgicihcumgiX0X, api_base=GAPGPTMASKTOKENlgicihcumgiX1X)
95
+
96
+
97
+ def generate_memory_docs(data, language):
98
+ """
99
+ Generates structured LlamaIndex Document objects from a nested dictionary of user memories.
100
+
101
+ This function processes different types of memory (sessions, episodic, semantic) and
102
+ formats them into Document objects suitable for indexing.
103
+
104
+ Args:
105
+ data (dict): A dictionary where keys are user names and values contain their memories.
106
+ language (str): The language code (e.g., "en"), currently used for formatting.
107
+
108
+ Returns:
109
+ dict: A dictionary where keys are user names and values are dicts containing
110
+ lists of Document objects for each memory type.
111
+ """
112
+ all_user_memories = {}
113
+
114
+ for user_name, user_memory in data.items():
115
+ all_user_memories[user_name] = {
116
+ "sessions": [],
117
+ "episodic_memory": [],
118
+ "semantic_memory": [],
119
+ }
120
+
121
+ # Process session history
122
+ if "sessions" in user_memory:
123
+ for session in user_memory["sessions"]:
124
+ date = session["date"]
125
+ content = session["conversation"]
126
+
127
+ memory_str = f"Session on {date}:\n"
128
+ for dialog in content:
129
+ query = dialog.get("query", "")
130
+ response = dialog.get("response", "")
131
+ memory_str += f"\n{user_name}: {query.strip()}"
132
+ memory_str += f"\nAI: {response.strip()}"
133
+ memory_str += "\n"
134
+
135
+ if "summary" in user_memory and date in user_memory["summary"]:
136
+ summary = f'The summary of the conversation on {date} is: {user_memory["summary"][date]}'
137
+ memory_str += summary
138
+
139
+ # Use the 'text' argument for the Document object
140
+ all_user_memories[user_name]["sessions"].append(Document(text=memory_str))
141
+
142
+ # Process episodic memory
143
+ if "episodic_memory" in user_memory:
144
+ episodic_str = "Recent experiences:\n"
145
+ for event in user_memory["episodic_memory"]:
146
+ episodic_str += f"- {event}\n"
147
+ all_user_memories[user_name]["episodic_memory"].append(Document(text=episodic_str))
148
+
149
+ # Process semantic memory (long-term facts and personality)
150
+ if "semantic_memory" in user_memory:
151
+ semantic_str = "Long-term personality traits and facts:\n"
152
+ if isinstance(user_memory["semantic_memory"], dict):
153
+ for trait, value in user_memory["semantic_memory"].items():
154
+ semantic_str += f"- {trait}: {value}\n"
155
+ elif isinstance(user_memory["semantic_memory"], list):
156
+ for item in user_memory["semantic_memory"]:
157
+ semantic_str += f"- {item}\n"
158
+ else:
159
+ print(f"WARNING: semantic_memory for user '{user_name}' has an unexpected format: {type(user_memory['semantic_memory'])}")
160
+
161
+ all_user_memories[user_name]["semantic_memory"].append(Document(text=semantic_str))
162
+
163
+ return all_user_memories
164
+
165
+
166
+ def build_memory_index(all_user_memories, data_args, name=None):
167
+ """
168
+ Build and persist memory indices for each user and memory type.
169
+ """
170
+ structured_docs = generate_memory_docs(
171
+ all_user_memories, data_args.language
172
+ )
173
+
174
+ setup_global_settings()
175
+
176
+ for user_name, memories_by_type in structured_docs.items():
177
+ if name and user_name != name:
178
+ continue
179
+
180
+ print(f"Building indices for user '{user_name}'")
181
+
182
+ base_path = os.path.join(_LLAMAINDEX_BASE_DIR, user_name)
183
+
184
+ path_map = {
185
+ "sessions": os.path.join(base_path, "sessions"),
186
+ "episodic_memory": os.path.join(base_path, "episodic_memory"),
187
+ "semantic_memory": os.path.join(base_path, "semantic_memory"),
188
+ }
189
+
190
+ for memory_type, docs in memories_by_type.items():
191
+ if not docs:
192
+ print(f" -> Skipping '{memory_type}' index (no documents found).")
193
+ continue
194
+
195
+ if memory_type not in path_map:
196
+ print(f" -> Skipping unknown memory type: '{memory_type}'")
197
+ continue
198
+
199
+ print(f" -> Building '{memory_type}' index...")
200
+
201
+ cur_index = VectorStoreIndex.from_documents(docs)
202
+
203
+ index_dir = path_map[memory_type]
204
+ os.makedirs(index_dir, exist_ok=True)
205
+
206
+ cur_index.storage_context.persist(persist_dir=index_dir)
207
+ print(f" + Saved '{memory_type}' index to: {index_dir}")
208
+
209
+ index_set[f"{user_name}_{memory_type}"] = cur_index
210
+
211
+
212
+ # --- Deprecated Functions (Kept for reference) ---
213
+
214
+ def generate_memory_docs_old(data, language):
215
+ """
216
+ DEPRECATED: An older version of the document generation function.
217
+
218
+ This function had a simpler structure and contained Chinese string literals.
219
+ It is replaced by `generate_memory_docs`.
220
+ """
221
+ all_user_memories = {}
222
+ for user_name, user_memory in data.items():
223
+ all_user_memories[user_name] = []
224
+ if "history" not in user_memory:
225
+ continue
226
+ for date, content in user_memory["history"].items():
227
+ memory_str = f"Conversation on {date}:"
228
+
229
+ for dialog in content:
230
+ query = dialog.get("query", "")
231
+ response = dialog.get("response", "")
232
+ memory_str += f"\n{user_name}: {query.strip()}"
233
+ memory_str += f"\nAI: {response.strip()}"
234
+ memory_str += "\n"
235
+ if "summary" in user_memory and date in user_memory["summary"]:
236
+ summary = f'The summary of the conversation on {date} is: {user_memory["summary"][date]}'
237
+ memory_str += summary
238
+
239
+ all_user_memories[user_name].append(Document(text=memory_str))
240
+ return all_user_memories
241
+
242
+
243
+ def build_memory_index_old(all_user_memories, data_args, name=None):
244
+ """
245
+ DEPRECATED: An older version of the index building function.
246
+
247
+ This function used a flatter memory structure and is replaced by the more
248
+ comprehensive `build_memory_index`.
249
+ """
250
+ # The function name is a typo in the original code, corrected here for clarity.
251
+ all_user_memories_docs = generate_memory_docs(
252
+ all_user_memories, data_args.language
253
+ )
254
+
255
+ # Apply global settings
256
+ setup_global_settings()
257
+
258
+ for user_name, memories in all_user_memories_docs.items():
259
+ if name and user_name != name:
260
+ continue
261
+ print(f"Building index for user {user_name} (using old method)")
262
+
263
+ # The service_context argument is removed in newer versions.
264
+ cur_index = VectorStoreIndex.from_documents(memories)
265
+ index_set[user_name] = cur_index
266
+
267
+ # استفاده از مسیر داینامیک و امن
268
+ save_dir = os.path.join(_LLAMAINDEX_BASE_DIR, f"{user_name}_store")
269
+ os.makedirs(save_dir, exist_ok=True)
270
+ cur_index.storage_context.persist(persist_dir=save_dir)
271
+
Backup code/memory_utils_25.py ADDED
@@ -0,0 +1,293 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import json
4
+ import time
5
+ import datetime
6
+ import shutil
7
+ import gradio as gr
8
+ from pprint import pprint
9
+ import traceback
10
+
11
+ # LlamaIndex Imports
12
+ from llama_index.core import StorageContext, load_index_from_storage, VectorStoreIndex
13
+
14
+ # Local Imports setup
15
+ # Assuming this file is in 'utils/', we step back to find 'memory_bank'
16
+ CURRENT_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
17
+ # تغییر نام BASE_DIR در اینجا برای جلوگیری از تداخل با BASE_DIR مسیرهای پایین
18
+ APP_ROOT_DIR = os.path.abspath(os.path.join(CURRENT_SCRIPT_DIR, ".."))
19
+ bank_path = os.path.join(APP_ROOT_DIR, 'memory_bank')
20
+ sys.path.append(bank_path)
21
+
22
+ # Import functions from sibling/child modules
23
+ # Ensure these files exist in the appended path
24
+ try:
25
+ from build_memory_index import build_memory_index
26
+ from summarize_memory import summarize_memory, extract_session_summary, extract_semantic_memory
27
+ except ImportError:
28
+ # Fallback or placeholder if running independently for testing
29
+ print("Warning: Could not import memory build/summary modules.")
30
+ def build_memory_index(*args, **kwargs): pass
31
+ def summarize_memory(*args, **kwargs): return {}
32
+ def extract_session_summary(*args, **kwargs): return {}
33
+ def extract_semantic_memory(*args, **kwargs): return {}
34
+
35
+
36
+ # --- یکپارچه‌سازی و استانداردسازی مسیرها (منطبق با پیکربندی مصوب جدید) ---
37
+ REPO_ID = "Keyvan1986/Emma-memory-storage"
38
+ REPO_TYPE = "dataset"
39
+ HF_TOKEN = os.environ.get("Emma-memory-storage")
40
+
41
+ # مسیرهای اصلی (Absolute Paths) برای جلوگیری از ساخته شدن پوشه در مسیرهای اشتباه
42
+ BASE_DIR = os.path.abspath(os.getcwd()) # معمولاً /app
43
+ MEMORIES_DIR = os.path.join(BASE_DIR, "memories")
44
+ MEMORY_INDEX_DIR_NAME = "memory_index"
45
+ MEMORY_INDEX_PATH = os.path.join(MEMORIES_DIR, MEMORY_INDEX_DIR_NAME)
46
+
47
+ _LLAMAINDEX_BASE_DIR = os.path.join(MEMORY_INDEX_PATH, "llamaindex")
48
+
49
+ # مسیر فایل json
50
+ MEMORY_FILE_NAME = "update_memory_0512_eng.json"
51
+ MEMORY_FILE_PATH = os.path.join(MEMORIES_DIR, MEMORY_FILE_NAME)
52
+
53
+ # اطمینان از وجود پوشه‌ها
54
+ os.makedirs(MEMORIES_DIR, exist_ok=True)
55
+ os.makedirs(MEMORY_INDEX_PATH, exist_ok=True)
56
+ os.makedirs(_LLAMAINDEX_BASE_DIR, exist_ok=True)
57
+
58
+ print(f"📂 Path Configuration:")
59
+ print(f" - App Base Dir: {BASE_DIR}")
60
+ print(f" - Memories Dir: {MEMORIES_DIR}")
61
+ print(f" - Memory Index Path: {MEMORY_INDEX_PATH}")
62
+ # ------------------------------------------------------------------------
63
+
64
+
65
+ def enter_name(name, memory, local_memory_qa, data_args, update_memory_index=True):
66
+ """
67
+ Legacy/Basic function to load user memory and initialize vector store.
68
+ """
69
+ cur_date = datetime.date.today().strftime("%Y-%m-%d")
70
+ user_memory_index = None
71
+
72
+ # Handle Gradio States
73
+ if isinstance(data_args, gr.State): data_args = data_args.value
74
+ if isinstance(memory, gr.State): memory = memory.value
75
+ if isinstance(local_memory_qa, gr.State): local_memory_qa = local_memory_qa.value
76
+
77
+ memory_dir = MEMORY_FILE_PATH
78
+
79
+ if name in memory.keys():
80
+ user_memory = memory[name]
81
+ memory_index_path = os.path.join(_LLAMAINDEX_BASE_DIR, name)
82
+ os.makedirs(memory_index_path, exist_ok=True)
83
+
84
+ if (not os.path.exists(memory_index_path)) or update_memory_index:
85
+ print(f'Initializing memory index {memory_index_path}...')
86
+
87
+ if os.path.exists(memory_index_path):
88
+ shutil.rmtree(memory_index_path)
89
+ # Initialize using the local QA object
90
+ memory_index_path, _ = local_memory_qa.init_memory_vector_store(
91
+ filepath=memory_dir,
92
+ vs_path=memory_index_path,
93
+ user_name=name,
94
+ cur_date=cur_date
95
+ )
96
+
97
+ user_memory_index = local_memory_qa.load_memory_index(memory_index_path) if memory_index_path else None
98
+ msg = f"Welcome back, {name}!"
99
+ return msg, user_memory, memory, name, user_memory_index
100
+ else:
101
+ memory[name] = {}
102
+ memory[name].update({"name": name})
103
+ msg = f"Welcome, new user {name}! I will remember your name, so next time we meet, I'll be able to call you by your name!"
104
+ return msg, memory[name], memory, name, user_memory_index
105
+
106
+
107
+ def enter_name_llamaindex(name, memory, data_args, update_memory_index=True):
108
+ """
109
+ Load the user's session, episodic, and semantic memory indices.
110
+ Compatible with LlamaIndex v0.10+.
111
+ """
112
+ sessions_memory = None
113
+ episodic_memory = None
114
+ semantic_memory = None
115
+
116
+ print(f"[DEBUG] enter_name_llamaindex called with name={name!r}")
117
+
118
+ if name not in memory:
119
+ print(f"[DEBUG] user {name!r} not found in memory")
120
+ return "User not found.", None, None, None, None
121
+
122
+ user_memory = memory[name]
123
+ # مسیر دقیق کاربر در پوشه llamaindex
124
+ base_path = os.path.join(_LLAMAINDEX_BASE_DIR, name)
125
+ sessions_path = os.path.join(base_path, "sessions")
126
+ episodic_path = os.path.join(base_path, "episodic_memory")
127
+ semantic_path = os.path.join(base_path, "semantic_memory")
128
+
129
+ print(f"[DEBUG] base_path: {base_path}")
130
+ print(f"[DEBUG] sessions_path: {sessions_path}")
131
+ print(f"[DEBUG] episodic_path: {episodic_path}")
132
+ print(f"[DEBUG] semantic_path: {semantic_path}")
133
+
134
+ indices_exist = (
135
+ os.path.isdir(sessions_path)
136
+ and os.path.isdir(episodic_path)
137
+ and os.path.isdir(semantic_path)
138
+ )
139
+
140
+ print(f"[DEBUG] indices_exist: {indices_exist}")
141
+ #print(f"[DEBUG] update_memory_index: {update_memory_index}")
142
+
143
+ if update_memory_index or not indices_exist:
144
+ print(f"[DEBUG] Initializing memory indices for {name}...")
145
+
146
+ # Important: build_memory_index must persist to these same absolute paths.
147
+ build_memory_index(memory, data_args, name=name)
148
+
149
+ print("[DEBUG] After build:")
150
+ print(f"[DEBUG] sessions_path exists: {os.path.isdir(sessions_path)}")
151
+ print(f"[DEBUG] episodic_path exists: {os.path.isdir(episodic_path)}")
152
+ print(f"[DEBUG] semantic_path exists: {os.path.isdir(semantic_path)}")
153
+
154
+ def load_index_safe(index_name, index_path):
155
+ if not os.path.isdir(index_path):
156
+ print(f"[DEBUG] {index_name} path does not exist: {index_path}")
157
+ return None
158
+
159
+ try:
160
+ storage_context = StorageContext.from_defaults(
161
+ persist_dir=index_path
162
+ )
163
+ index = load_index_from_storage(storage_context)
164
+ print(
165
+ f"[DEBUG] {index_name} loaded: "
166
+ f"{type(index).__name__}, is_none={index is None}"
167
+ )
168
+ return index
169
+ except Exception as exc:
170
+ print(f"[ERROR] Could not load {index_name}: {exc}")
171
+ traceback.print_exc()
172
+ return None
173
+
174
+ sessions_memory = load_index_safe("sessions_memory", sessions_path)
175
+ episodic_memory = load_index_safe("episodic_memory", episodic_path)
176
+ semantic_memory = load_index_safe("semantic_memory", semantic_path)
177
+
178
+ print("[DEBUG] RETURN VALUES:")
179
+ print(f" hello_msg: Welcome back, {name}!")
180
+ print(f" user_memory type: {type(user_memory).__name__}")
181
+ print(f" sessions_memory type: {type(sessions_memory).__name__}")
182
+ print(f" episodic_memory type: {type(episodic_memory).__name__}")
183
+ print(f" semantic_memory type: {type(semantic_memory).__name__}")
184
+
185
+ return (
186
+ f"Welcome back, {name}!",
187
+ user_memory,
188
+ sessions_memory,
189
+ episodic_memory,
190
+ semantic_memory,
191
+ )
192
+
193
+
194
+ def summarize_memory_event_personality(data_args, memory, user_name):
195
+ """
196
+ Summarizes the memory and returns the user-specific memory dict.
197
+ """
198
+ if isinstance(data_args, gr.State): data_args = data_args.value
199
+ if isinstance(memory, gr.State): memory = memory.value
200
+
201
+ memory_dir = MEMORY_FILE_PATH
202
+
203
+ # Note: Ensure summarize_memory handles the language argument correctly (passed 'en' or similar)
204
+ memory = summarize_memory(memory_dir, user_name, language=data_args.language)
205
+ user_memory = memory[user_name] if user_name in memory.keys() else {}
206
+ return user_memory
207
+
208
+
209
+ def save_local_memory(memory, history, user_name, data_args, new_conversation=False):
210
+ """
211
+ Saves user-model conversations into memory and adds episodic memory for each session.
212
+ Handles both list-of-lists (old Gradio) and list-of-dicts (new Gradio/OpenAI) formats.
213
+ """
214
+ if isinstance(data_args, gr.State): data_args = data_args.value
215
+ if isinstance(memory, gr.State): memory = memory.value
216
+
217
+ memory_dir = MEMORY_FILE_PATH
218
+
219
+ # 1. Initialize user memory with ALL required structures FIRST
220
+ if user_name not in memory:
221
+ memory[user_name] = {
222
+ "sessions": [],
223
+ "episodic_memory": [],
224
+ "semantic_memory": {}
225
+ }
226
+
227
+ # 2. Ensure all sub-structures exist and are correct type
228
+ memory[user_name].setdefault("sessions", [])
229
+ memory[user_name].setdefault("episodic_memory", [])
230
+ memory[user_name].setdefault("semantic_memory", {})
231
+
232
+ # 3. Now safely check types
233
+ if not isinstance(memory[user_name]["semantic_memory"], dict):
234
+ memory[user_name]["semantic_memory"] = {}
235
+
236
+ # Create new session or update existing one
237
+ if new_conversation or not memory[user_name]["sessions"]:
238
+ if new_conversation and memory[user_name]["sessions"]:
239
+ # Logic to summarize previous session before starting new one could go here
240
+ pass
241
+
242
+ # Create new session
243
+ session = {
244
+ "session_id": len(memory[user_name]["sessions"]),
245
+ "date": time.strftime("%Y-%m-%d", time.localtime()),
246
+ "conversation": []
247
+ }
248
+ memory[user_name]["sessions"].append(session)
249
+
250
+ current_session = memory[user_name]["sessions"][-1]
251
+
252
+ # --- Modified section to fix KeyError: 0 and handle History formats ---
253
+ if not new_conversation and history:
254
+ last_item = history[-1]
255
+
256
+ # Case 1: New Format (List of Dicts)
257
+ # In this format, history is linear. The last item is bot response, second to last is user query.
258
+ if isinstance(last_item, dict):
259
+ if len(history) >= 2:
260
+ user_query = history[-2].get('content', '')
261
+ bot_response = history[-1].get('content', '')
262
+
263
+ # Verify roles to ensure correct pairing
264
+ if history[-2].get('role') == 'user' and history[-1].get('role') == 'assistant':
265
+ current_session["conversation"].append({
266
+ 'query': user_query,
267
+ 'response': bot_response
268
+ })
269
+
270
+ # Case 2: Old Format (List of Lists/Tuples)
271
+ elif isinstance(last_item, (list, tuple)):
272
+ current_session["conversation"].append({
273
+ 'query': last_item[0],
274
+ 'response': last_item[1]
275
+ })
276
+ # ----------------------------------------------------------------------
277
+
278
+ # Optional: Update semantic memory in real-time
279
+ memory[user_name]["semantic_memory"] = extract_semantic_memory(
280
+ memory[user_name]["semantic_memory"],
281
+ current_session["conversation"]
282
+ )
283
+
284
+ semantic_memory_text = memory[user_name]["semantic_memory"]
285
+
286
+ # Save to file
287
+ # Ensure memory directory exists
288
+ os.makedirs(os.path.dirname(memory_dir), exist_ok=True)
289
+
290
+ with open(memory_dir, "w", encoding="utf-8") as f:
291
+ json.dump(memory, f, ensure_ascii=False, indent=4)
292
+
293
+ return memory, semantic_memory_text
memory_bank/build_memory_index.py CHANGED
@@ -1,4 +1,3 @@
1
- #
2
  # Memory Indexing Utility for LlamaIndex
3
  #
4
  # This script provides functionalities to build and manage memory indices for a conversational AI
@@ -9,6 +8,7 @@
9
  # --- Standard Library Imports ---
10
  import json
11
  import os
 
12
 
13
  # --- Third-Party Imports ---
14
  import tiktoken
@@ -24,25 +24,25 @@ 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
- # پیکربندی دایرکتوری‌ها بر اساس ساختار استاندارد درخواستی
33
  CURRENT_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
34
  BASE_DIR = os.path.abspath(os.path.join(CURRENT_SCRIPT_DIR, ".."))
35
 
36
- # <--- تغییر در اینجا: نام پوشه به Emma-memory-storage تغییر یافت --->
37
- MEMORIES_DIR = os.path.join(BASE_DIR, "Emma-memory-storage")
 
38
  INDEX_BASE_DIR = os.path.join(MEMORIES_DIR, "memory_index")
39
 
40
- # تنظیم مسیر دقیق برای llamaindex
41
- _LLAMAINDEX_BASE_DIR = os.getenv("EMMA_INDEX_DIR", os.path.join(INDEX_BASE_DIR, "llamaindex"))
 
42
 
43
- # اطمینان از وجود دایرکتوری اصلی در صورت ساخت ایندکس جدید
44
- os.makedirs(_LLAMAINDEX_BASE_DIR, exist_ok=True)
45
 
 
 
 
46
 
47
 
48
  # --- Core Functions ---
@@ -62,14 +62,16 @@ def setup_global_settings():
62
  print(f"Warning: Could not set the tokenizer. This might lead to issues. Error: {e}")
63
 
64
  # 2. Configure the Language Model (LLM)
65
- # Use the LlamaIndex-specific OpenAI class to avoid conflicts with other libraries.
66
- # It automatically reads the API key from the "OPENAI_API_KEY" environment variable.
 
 
67
  Settings.llm = OpenAI(
68
  model="gpt-4o",
69
  temperature=1.0,
70
  max_tokens=1024,
71
- api_key=os.environ.get("GAPGPT_API_KEY"),
72
- api_base="https://api.gapgpt.app/v1",
73
  additional_kwargs={
74
  "top_p": 0.95,
75
  "frequency_penalty": 0.4,
@@ -90,8 +92,7 @@ def setup_global_settings():
90
  Settings.chunk_overlap = 20
91
 
92
  # Optional: Configure the embedding model if needed.
93
- # If not set, LlamaIndex defaults to a compatible model (e.g., OpenAI's text-embedding-ada-002).
94
- # Settings.embed_model = OpenAIEmbedding(api_key=GAPGPTMASKTOKENlgicihcumgiX0X, api_base=GAPGPTMASKTOKENlgicihcumgiX1X)
95
 
96
 
97
  def generate_memory_docs(data, language):
@@ -165,47 +166,53 @@ def generate_memory_docs(data, language):
165
 
166
  def build_memory_index(all_user_memories, data_args, name=None):
167
  """
168
- Build and persist memory indices for each user and memory type.
 
 
 
 
 
 
 
 
169
  """
 
170
  structured_docs = generate_memory_docs(
171
  all_user_memories, data_args.language
172
  )
173
 
 
174
  setup_global_settings()
175
 
176
  for user_name, memories_by_type in structured_docs.items():
 
177
  if name and user_name != name:
178
  continue
179
 
180
- print(f"Building indices for user '{user_name}'")
181
-
182
- base_path = os.path.join(_LLAMAINDEX_BASE_DIR, user_name)
183
-
184
- path_map = {
185
- "sessions": os.path.join(base_path, "sessions"),
186
- "episodic_memory": os.path.join(base_path, "episodic_memory"),
187
- "semantic_memory": os.path.join(base_path, "semantic_memory"),
188
- }
189
 
190
  for memory_type, docs in memories_by_type.items():
191
  if not docs:
192
- print(f" -> Skipping '{memory_type}' index (no documents found).")
193
  continue
194
 
195
- if memory_type not in path_map:
196
- print(f" -> Skipping unknown memory type: '{memory_type}'")
197
- continue
198
-
199
- print(f" -> Building '{memory_type}' index...")
200
 
 
 
201
  cur_index = VectorStoreIndex.from_documents(docs)
202
 
203
- index_dir = path_map[memory_type]
 
 
 
 
204
  os.makedirs(index_dir, exist_ok=True)
205
 
206
  cur_index.storage_context.persist(persist_dir=index_dir)
207
- print(f" + Saved '{memory_type}' index to: {index_dir}")
208
 
 
209
  index_set[f"{user_name}_{memory_type}"] = cur_index
210
 
211
 
@@ -214,9 +221,6 @@ def build_memory_index(all_user_memories, data_args, name=None):
214
  def generate_memory_docs_old(data, language):
215
  """
216
  DEPRECATED: An older version of the document generation function.
217
-
218
- This function had a simpler structure and contained Chinese string literals.
219
- It is replaced by `generate_memory_docs`.
220
  """
221
  all_user_memories = {}
222
  for user_name, user_memory in data.items():
@@ -243,11 +247,7 @@ def generate_memory_docs_old(data, language):
243
  def build_memory_index_old(all_user_memories, data_args, name=None):
244
  """
245
  DEPRECATED: An older version of the index building function.
246
-
247
- This function used a flatter memory structure and is replaced by the more
248
- comprehensive `build_memory_index`.
249
  """
250
- # The function name is a typo in the original code, corrected here for clarity.
251
  all_user_memories_docs = generate_memory_docs(
252
  all_user_memories, data_args.language
253
  )
@@ -258,14 +258,13 @@ def build_memory_index_old(all_user_memories, data_args, name=None):
258
  for user_name, memories in all_user_memories_docs.items():
259
  if name and user_name != name:
260
  continue
261
- print(f"Building index for user {user_name} (using old method)")
262
 
263
- # The service_context argument is removed in newer versions.
264
  cur_index = VectorStoreIndex.from_documents(memories)
265
  index_set[user_name] = cur_index
266
 
267
- # استفاده از مسیر داینامیک و امن
268
- save_dir = os.path.join(_LLAMAINDEX_BASE_DIR, f"{user_name}_store")
269
- os.makedirs(save_dir, exist_ok=True)
270
  cur_index.storage_context.persist(persist_dir=save_dir)
271
 
 
 
1
  # Memory Indexing Utility for LlamaIndex
2
  #
3
  # This script provides functionalities to build and manage memory indices for a conversational AI
 
8
  # --- Standard Library Imports ---
9
  import json
10
  import os
11
+ import sys
12
 
13
  # --- Third-Party Imports ---
14
  import tiktoken
 
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
+ # --- PATH CONFIGURATION (CRITICAL UPDATE) ---
28
+ # پیدا کردن مسیر پایه بر اساس موقعیت فایل اسکریپت برای اطمینان از ذخیره‌سازی صحیح
29
+ # فرض بر این است که فایل در /app/utils/ یا /app/memory_bank/ است
 
 
 
30
  CURRENT_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
31
  BASE_DIR = os.path.abspath(os.path.join(CURRENT_SCRIPT_DIR, ".."))
32
 
33
+ # تنظیم مسیر دقیق برای ذخیره ایندکس‌ها
34
+ # این مسیر باید دقیقاً همان مسیری باشد که memory_utils.py آن را آپلود می‌کند
35
+ MEMORIES_DIR = os.path.join(BASE_DIR, "memories")
36
  INDEX_BASE_DIR = os.path.join(MEMORIES_DIR, "memory_index")
37
 
38
+ print(f"📊 Index Builder Path Config:")
39
+ print(f" - Script Dir: {CURRENT_SCRIPT_DIR}")
40
+ print(f" - Index Target Dir: {INDEX_BASE_DIR}")
41
 
 
 
42
 
43
+ # --- Global Variables ---
44
+ # A dictionary to hold the loaded or newly created indices in memory.
45
+ index_set = {}
46
 
47
 
48
  # --- Core Functions ---
 
62
  print(f"Warning: Could not set the tokenizer. This might lead to issues. Error: {e}")
63
 
64
  # 2. Configure the Language Model (LLM)
65
+ # Uses GAPGPT if available, otherwise falls back to OPENAI_API_KEY
66
+ api_key = os.environ.get("GAPGPT_API_KEY") or os.environ.get("OPENAI_API_KEY")
67
+ api_base = "https://api.gapgpt.app/v1" if os.environ.get("GAPGPT_API_KEY") else None
68
+
69
  Settings.llm = OpenAI(
70
  model="gpt-4o",
71
  temperature=1.0,
72
  max_tokens=1024,
73
+ api_key=api_key,
74
+ api_base=api_base,
75
  additional_kwargs={
76
  "top_p": 0.95,
77
  "frequency_penalty": 0.4,
 
92
  Settings.chunk_overlap = 20
93
 
94
  # Optional: Configure the embedding model if needed.
95
+ # Settings.embed_model = OpenAIEmbedding(api_key=..., api_base=...)
 
96
 
97
 
98
  def generate_memory_docs(data, language):
 
166
 
167
  def build_memory_index(all_user_memories, data_args, name=None):
168
  """
169
+ Main function for building and persisting memory indices for each user and memory type.
170
+
171
+ It orchestrates the process of generating documents, setting up the environment,
172
+ building vector indices, and saving them to disk.
173
+
174
+ Args:
175
+ all_user_memories (dict): The raw memory data loaded from a source like JSON.
176
+ data_args (object): An object or namespace containing arguments, like `language`.
177
+ name (str, optional): If specified, only build the index for this user. Defaults to None.
178
  """
179
+ # 1. Generate Document objects from raw memory data.
180
  structured_docs = generate_memory_docs(
181
  all_user_memories, data_args.language
182
  )
183
 
184
+ # 2. Apply the global settings (replaces the old service_context creation).
185
  setup_global_settings()
186
 
187
  for user_name, memories_by_type in structured_docs.items():
188
+ # If a specific user name is provided, skip others.
189
  if name and user_name != name:
190
  continue
191
 
192
+ print(f"Building indices for user '{user_name}'...")
 
 
 
 
 
 
 
 
193
 
194
  for memory_type, docs in memories_by_type.items():
195
  if not docs:
196
+ print(f" → Skipping '{memory_type}' index (no documents found).")
197
  continue
198
 
199
+ print(f" → Building '{memory_type}' index...")
 
 
 
 
200
 
201
+ # 3. Build the index from documents.
202
+ # It automatically uses the global `Settings` configured in setup_global_settings().
203
  cur_index = VectorStoreIndex.from_documents(docs)
204
 
205
+ # 4. Persist the index to disk using the new method.
206
+ # UPDATE: Using absolute path join instead of relative paths
207
+ index_dir = os.path.join(INDEX_BASE_DIR, "llamaindex", user_name, memory_type)
208
+
209
+ # Ensure directory exists
210
  os.makedirs(index_dir, exist_ok=True)
211
 
212
  cur_index.storage_context.persist(persist_dir=index_dir)
213
+ print(f" ✓ Saved '{memory_type}' index to: {index_dir}")
214
 
215
+ # Store the created index in the global set for runtime access.
216
  index_set[f"{user_name}_{memory_type}"] = cur_index
217
 
218
 
 
221
  def generate_memory_docs_old(data, language):
222
  """
223
  DEPRECATED: An older version of the document generation function.
 
 
 
224
  """
225
  all_user_memories = {}
226
  for user_name, user_memory in data.items():
 
247
  def build_memory_index_old(all_user_memories, data_args, name=None):
248
  """
249
  DEPRECATED: An older version of the index building function.
 
 
 
250
  """
 
251
  all_user_memories_docs = generate_memory_docs(
252
  all_user_memories, data_args.language
253
  )
 
258
  for user_name, memories in all_user_memories_docs.items():
259
  if name and user_name != name:
260
  continue
261
+ print(f"Building index for user {user_name} (using old method)...")
262
 
 
263
  cur_index = VectorStoreIndex.from_documents(memories)
264
  index_set[user_name] = cur_index
265
 
266
+ # Corrected save path with absolute path
267
+ save_dir = os.path.join(INDEX_BASE_DIR, "llamaindex", f"{user_name}_store")
268
+ o.makedirs(save_dir, exist_ok=True)
269
  cur_index.storage_context.persist(persist_dir=save_dir)
270
 
utils/memory_utils.py CHANGED
@@ -2,292 +2,275 @@ import os
2
  import sys
3
  import json
4
  import time
5
- import datetime
6
  import shutil
7
  import gradio as gr
8
- from pprint import pprint
9
- import traceback
10
 
11
  # LlamaIndex Imports
12
- from llama_index.core import StorageContext, load_index_from_storage, VectorStoreIndex
 
 
 
13
 
14
  # Local Imports setup
15
- # Assuming this file is in 'utils/', we step back to find 'memory_bank'
16
- CURRENT_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
17
- # تغییر نام BASE_DIR در اینجا برای جلوگیری از تداخل با BASE_DIR مسیرهای پایین
18
- APP_ROOT_DIR = os.path.abspath(os.path.join(CURRENT_SCRIPT_DIR, ".."))
19
- bank_path = os.path.join(APP_ROOT_DIR, 'memory_bank')
20
  sys.path.append(bank_path)
21
 
22
- # Import functions from sibling/child modules
23
- # Ensure these files exist in the appended path
24
  try:
25
  from build_memory_index import build_memory_index
26
- from summarize_memory import summarize_memory, extract_session_summary, extract_semantic_memory
27
  except ImportError:
28
- # Fallback or placeholder if running independently for testing
29
- print("Warning: Could not import memory build/summary modules.")
30
  def build_memory_index(*args, **kwargs): pass
31
  def summarize_memory(*args, **kwargs): return {}
32
- def extract_session_summary(*args, **kwargs): return {}
33
- def extract_semantic_memory(*args, **kwargs): return {}
34
 
 
 
 
35
 
36
- # --- یکپارچه‌سازی و استانداردسازی مسیرها (منطبق با پیکربندی مصوب جدید) ---
37
  REPO_ID = "Keyvan1986/Emma-memory-storage"
38
  REPO_TYPE = "dataset"
39
- HF_TOKEN = os.environ.get("Emma-memory-storage")
40
 
41
- # مسیرهای اصلی (Absolute Paths) برای جلوگیری از ساخته شدن پوشه در مسیرهای اشتباه
42
  BASE_DIR = os.path.abspath(os.getcwd()) # معمولاً /app
43
  MEMORIES_DIR = os.path.join(BASE_DIR, "memories")
44
  MEMORY_INDEX_DIR_NAME = "memory_index"
45
  MEMORY_INDEX_PATH = os.path.join(MEMORIES_DIR, MEMORY_INDEX_DIR_NAME)
46
 
47
- _LLAMAINDEX_BASE_DIR = os.path.join(MEMORY_INDEX_PATH, "llamaindex")
48
-
49
- # مسیر فایل json
50
  MEMORY_FILE_NAME = "update_memory_0512_eng.json"
51
  MEMORY_FILE_PATH = os.path.join(MEMORIES_DIR, MEMORY_FILE_NAME)
52
 
53
- # اطمینان از وجود پوشه‌ها
 
 
 
 
54
  os.makedirs(MEMORIES_DIR, exist_ok=True)
55
  os.makedirs(MEMORY_INDEX_PATH, exist_ok=True)
56
- os.makedirs(_LLAMAINDEX_BASE_DIR, exist_ok=True)
57
 
58
  print(f"📂 Path Configuration:")
59
- print(f" - App Base Dir: {BASE_DIR}")
60
- print(f" - Memories Dir: {MEMORIES_DIR}")
61
- print(f" - Memory Index Path: {MEMORY_INDEX_PATH}")
62
- # ------------------------------------------------------------------------
63
 
 
 
 
64
 
65
- def enter_name(name, memory, local_memory_qa, data_args, update_memory_index=True):
66
  """
67
- Legacy/Basic function to load user memory and initialize vector store.
 
68
  """
69
- cur_date = datetime.date.today().strftime("%Y-%m-%d")
70
- user_memory_index = None
71
-
72
- # Handle Gradio States
73
- if isinstance(data_args, gr.State): data_args = data_args.value
74
- if isinstance(memory, gr.State): memory = memory.value
75
- if isinstance(local_memory_qa, gr.State): local_memory_qa = local_memory_qa.value
76
-
77
- memory_dir = MEMORY_FILE_PATH
78
-
79
- if name in memory.keys():
80
- user_memory = memory[name]
81
- memory_index_path = os.path.join(_LLAMAINDEX_BASE_DIR, name)
82
- os.makedirs(memory_index_path, exist_ok=True)
83
-
84
- if (not os.path.exists(memory_index_path)) or update_memory_index:
85
- print(f'Initializing memory index {memory_index_path}...')
86
-
87
- if os.path.exists(memory_index_path):
88
- shutil.rmtree(memory_index_path)
89
- # Initialize using the local QA object
90
- memory_index_path, _ = local_memory_qa.init_memory_vector_store(
91
- filepath=memory_dir,
92
- vs_path=memory_index_path,
93
- user_name=name,
94
- cur_date=cur_date
95
- )
96
-
97
- user_memory_index = local_memory_qa.load_memory_index(memory_index_path) if memory_index_path else None
98
- msg = f"Welcome back, {name}!"
99
- return msg, user_memory, memory, name, user_memory_index
100
  else:
101
- memory[name] = {}
102
- memory[name].update({"name": name})
103
- msg = f"Welcome, new user {name}! I will remember your name, so next time we meet, I'll be able to call you by your name!"
104
- return msg, memory[name], memory, name, user_memory_index
105
-
106
-
107
- def enter_name_llamaindex(name, memory, data_args, update_memory_index=True):
108
- """
109
- Load the user's session, episodic, and semantic memory indices.
110
- Compatible with LlamaIndex v0.10+.
111
- """
112
- sessions_memory = None
113
- episodic_memory = None
114
- semantic_memory = None
115
 
116
- print(f"[DEBUG] enter_name_llamaindex called with name={name!r}")
 
 
117
 
118
- if name not in memory:
119
- print(f"[DEBUG] user {name!r} not found in memory")
120
- return "User not found.", None, None, None, None
121
 
122
- user_memory = memory[name]
123
- # مسیر دقیق کاربر در پوشه llamaindex
124
- base_path = os.path.join(_LLAMAINDEX_BASE_DIR, name)
125
- sessions_path = os.path.join(base_path, "sessions")
126
- episodic_path = os.path.join(base_path, "episodic_memory")
127
- semantic_path = os.path.join(base_path, "semantic_memory")
128
-
129
- print(f"[DEBUG] base_path: {base_path}")
130
- print(f"[DEBUG] sessions_path: {sessions_path}")
131
- print(f"[DEBUG] episodic_path: {episodic_path}")
132
- print(f"[DEBUG] semantic_path: {semantic_path}")
133
 
134
- indices_exist = (
135
- os.path.isdir(sessions_path)
136
- and os.path.isdir(episodic_path)
137
- and os.path.isdir(semantic_path)
138
- )
139
-
140
- print(f"[DEBUG] indices_exist: {indices_exist}")
141
- #print(f"[DEBUG] update_memory_index: {update_memory_index}")
142
-
143
- if update_memory_index or not indices_exist:
144
- print(f"[DEBUG] Initializing memory indices for {name}...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
 
146
- # Important: build_memory_index must persist to these same absolute paths.
147
- build_memory_index(memory, data_args, name=name)
148
 
149
- print("[DEBUG] After build:")
150
- print(f"[DEBUG] sessions_path exists: {os.path.isdir(sessions_path)}")
151
- print(f"[DEBUG] episodic_path exists: {os.path.isdir(episodic_path)}")
152
- print(f"[DEBUG] semantic_path exists: {os.path.isdir(semantic_path)}")
153
 
154
- def load_index_safe(index_name, index_path):
155
- if not os.path.isdir(index_path):
156
- print(f"[DEBUG] {index_name} path does not exist: {index_path}")
157
- return None
158
 
 
 
 
159
  try:
160
- storage_context = StorageContext.from_defaults(
161
- persist_dir=index_path
 
 
 
 
162
  )
163
- index = load_index_from_storage(storage_context)
164
- print(
165
- f"[DEBUG] {index_name} loaded: "
166
- f"{type(index).__name__}, is_none={index is None}"
167
- )
168
- return index
169
- except Exception as exc:
170
- print(f"[ERROR] Could not load {index_name}: {exc}")
171
- traceback.print_exc()
172
- return None
173
-
174
- sessions_memory = load_index_safe("sessions_memory", sessions_path)
175
- episodic_memory = load_index_safe("episodic_memory", episodic_path)
176
- semantic_memory = load_index_safe("semantic_memory", semantic_path)
177
-
178
- print("[DEBUG] RETURN VALUES:")
179
- print(f" hello_msg: Welcome back, {name}!")
180
- print(f" user_memory type: {type(user_memory).__name__}")
181
- print(f" sessions_memory type: {type(sessions_memory).__name__}")
182
- print(f" episodic_memory type: {type(episodic_memory).__name__}")
183
- print(f" semantic_memory type: {type(semantic_memory).__name__}")
184
-
185
- return (
186
- f"Welcome back, {name}!",
187
- user_memory,
188
- sessions_memory,
189
- episodic_memory,
190
- semantic_memory,
191
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
 
 
193
 
194
- def summarize_memory_event_personality(data_args, memory, user_name):
195
- """
196
- Summarizes the memory and returns the user-specific memory dict.
197
- """
198
- if isinstance(data_args, gr.State): data_args = data_args.value
199
- if isinstance(memory, gr.State): memory = memory.value
200
-
201
- memory_dir = MEMORY_FILE_PATH
202
-
203
- # Note: Ensure summarize_memory handles the language argument correctly (passed 'en' or similar)
204
- memory = summarize_memory(memory_dir, user_name, language=data_args.language)
205
- user_memory = memory[user_name] if user_name in memory.keys() else {}
206
- return user_memory
207
 
 
 
 
 
 
208
 
209
- def save_local_memory(memory, history, user_name, data_args, new_conversation=False):
210
- """
211
- Saves user-model conversations into memory and adds episodic memory for each session.
212
- Handles both list-of-lists (old Gradio) and list-of-dicts (new Gradio/OpenAI) formats.
213
- """
214
- if isinstance(data_args, gr.State): data_args = data_args.value
215
- if isinstance(memory, gr.State): memory = memory.value
216
-
217
- memory_dir = MEMORY_FILE_PATH
218
-
219
- # 1. Initialize user memory with ALL required structures FIRST
220
- if user_name not in memory:
221
- memory[user_name] = {
222
- "sessions": [],
223
- "episodic_memory": [],
224
- "semantic_memory": {}
225
- }
226
-
227
- # 2. Ensure all sub-structures exist and are correct type
228
- memory[user_name].setdefault("sessions", [])
229
- memory[user_name].setdefault("episodic_memory", [])
230
- memory[user_name].setdefault("semantic_memory", {})
231
-
232
- # 3. Now safely check types
233
- if not isinstance(memory[user_name]["semantic_memory"], dict):
234
- memory[user_name]["semantic_memory"] = {}
235
-
236
- # Create new session or update existing one
237
- if new_conversation or not memory[user_name]["sessions"]:
238
- if new_conversation and memory[user_name]["sessions"]:
239
- # Logic to summarize previous session before starting new one could go here
240
- pass
241
-
242
- # Create new session
243
- session = {
244
- "session_id": len(memory[user_name]["sessions"]),
245
- "date": time.strftime("%Y-%m-%d", time.localtime()),
246
- "conversation": []
247
- }
248
- memory[user_name]["sessions"].append(session)
249
-
250
- current_session = memory[user_name]["sessions"][-1]
251
-
252
- # --- Modified section to fix KeyError: 0 and handle History formats ---
253
- if not new_conversation and history:
254
- last_item = history[-1]
255
-
256
- # Case 1: New Format (List of Dicts)
257
- # In this format, history is linear. The last item is bot response, second to last is user query.
258
- if isinstance(last_item, dict):
259
- if len(history) >= 2:
260
- user_query = history[-2].get('content', '')
261
- bot_response = history[-1].get('content', '')
262
-
263
- # Verify roles to ensure correct pairing
264
- if history[-2].get('role') == 'user' and history[-1].get('role') == 'assistant':
265
- current_session["conversation"].append({
266
- 'query': user_query,
267
- 'response': bot_response
268
- })
269
-
270
- # Case 2: Old Format (List of Lists/Tuples)
271
- elif isinstance(last_item, (list, tuple)):
272
- current_session["conversation"].append({
273
- 'query': last_item[0],
274
- 'response': last_item[1]
275
- })
276
- # ----------------------------------------------------------------------
277
-
278
- # Optional: Update semantic memory in real-time
279
- memory[user_name]["semantic_memory"] = extract_semantic_memory(
280
- memory[user_name]["semantic_memory"],
281
- current_session["conversation"]
282
- )
283
-
284
- semantic_memory_text = memory[user_name]["semantic_memory"]
285
-
286
- # Save to file
287
- # Ensure memory directory exists
288
- os.makedirs(os.path.dirname(memory_dir), exist_ok=True)
289
-
290
- with open(memory_dir, "w", encoding="utf-8") as f:
291
- json.dump(memory, f, ensure_ascii=False, indent=4)
292
 
293
- return memory, semantic_memory_text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  import sys
3
  import json
4
  import time
 
5
  import shutil
6
  import gradio as gr
7
+ from huggingface_hub import HfApi, snapshot_download
 
8
 
9
  # LlamaIndex Imports
10
+ try:
11
+ from llama_index.core import StorageContext, load_index_from_storage
12
+ except ImportError:
13
+ pass
14
 
15
  # Local Imports setup
16
+ bank_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../memory_bank')
 
 
 
 
17
  sys.path.append(bank_path)
18
 
 
 
19
  try:
20
  from build_memory_index import build_memory_index
21
+ from summarize_memory import summarize_memory
22
  except ImportError:
 
 
23
  def build_memory_index(*args, **kwargs): pass
24
  def summarize_memory(*args, **kwargs): return {}
 
 
25
 
26
+ # ==========================================
27
+ # 1. تنظیمات مسیر و هاب (Cloud Config)
28
+ # ==========================================
29
 
 
30
  REPO_ID = "Keyvan1986/Emma-memory-storage"
31
  REPO_TYPE = "dataset"
32
+ HF_TOKEN = os.environ.get("HF_TOKEN")
33
 
34
+ # مسیرهای اصلی (Absolute Paths)
35
  BASE_DIR = os.path.abspath(os.getcwd()) # معمولاً /app
36
  MEMORIES_DIR = os.path.join(BASE_DIR, "memories")
37
  MEMORY_INDEX_DIR_NAME = "memory_index"
38
  MEMORY_INDEX_PATH = os.path.join(MEMORIES_DIR, MEMORY_INDEX_DIR_NAME)
39
 
 
 
 
40
  MEMORY_FILE_NAME = "update_memory_0512_eng.json"
41
  MEMORY_FILE_PATH = os.path.join(MEMORIES_DIR, MEMORY_FILE_NAME)
42
 
43
+ # --- مسیر "اشتباه" قدیمی که فایل‌ها آنجا ساخته می‌شوند ---
44
+ # لاگ شما نشان داد فایل‌ها اینجا می‌روند: ../memories
45
+ LEGACY_OUTSIDE_DIR = os.path.abspath(os.path.join(BASE_DIR, "..", "memories"))
46
+ LEGACY_INDEX_PATH = os.path.join(LEGACY_OUTSIDE_DIR, MEMORY_INDEX_DIR_NAME)
47
+
48
  os.makedirs(MEMORIES_DIR, exist_ok=True)
49
  os.makedirs(MEMORY_INDEX_PATH, exist_ok=True)
 
50
 
51
  print(f"📂 Path Configuration:")
52
+ print(f" - App Dir (Target): {MEMORY_INDEX_PATH}")
53
+ print(f" - Builder Dir (Source): {LEGACY_INDEX_PATH}")
 
 
54
 
55
+ # ==========================================
56
+ # 2. تابع انتقال فایل (حیاتی برای حل مشکل شما)
57
+ # ==========================================
58
 
59
+ def sync_legacy_files_to_app():
60
  """
61
+ این تابع بررسی می‌کند که آیا ایندکس‌ها در مسیر ../memories ساخته شده‌اند یا نه.
62
+ اگر آنجا باشند، آن‌ها را به داخل /app/memories کپی می‌کند تا آپلود شوند.
63
  """
64
+ if os.path.exists(LEGACY_INDEX_PATH):
65
+ print(f"🕵️ Detected files in outside path: {LEGACY_INDEX_PATH}")
66
+ try:
67
+ # کپی کردن تمام محتویات از بیرون به داخل پوشه قابل آپلود
68
+ shutil.copytree(LEGACY_INDEX_PATH, MEMORY_INDEX_PATH, dirs_exist_ok=True)
69
+ print(f"✅ MOVED files from '{LEGACY_INDEX_PATH}' to '{MEMORY_INDEX_PATH}' for upload.")
70
+ except Exception as e:
71
+ print(f"⚠️ Error moving legacy files: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  else:
73
+ # شاید فایل‌ها همین الان در جای درست باشند
74
+ pass
 
 
 
 
 
 
 
 
 
 
 
 
75
 
76
+ # ==========================================
77
+ # 3. توابع همگام‌سازی ابری
78
+ # ==========================================
79
 
80
+ def push_to_hub():
81
+ if not HF_TOKEN:
82
+ return
83
 
84
+ try:
85
+ # قبل از آپلود، مطمئن می‌شویم فایل‌ها در جای درست هستند
86
+ sync_legacy_files_to_app()
 
 
 
 
 
 
 
 
87
 
88
+ api = HfApi(token=HF_TOKEN)
89
+
90
+ # 1. آپلود JSON
91
+ if os.path.exists(MEMORY_FILE_PATH):
92
+ api.upload_file(
93
+ path_or_fileobj=MEMORY_FILE_PATH,
94
+ path_in_repo=MEMORY_FILE_NAME,
95
+ repo_id=REPO_ID,
96
+ repo_type=REPO_TYPE,
97
+ commit_message=f"Auto-save JSON: {time.strftime('%Y-%m-%d %H:%M:%S')}"
98
+ )
99
+
100
+ # 2. آپلود پوشه ایندکس
101
+ if os.path.exists(MEMORY_INDEX_PATH):
102
+ # بررسی تعداد فایل‌های واقعی
103
+ files_count = sum([len(files) for r, d, files in os.walk(MEMORY_INDEX_PATH)])
104
+ print(f"📂 Preparing upload. Found {files_count} files in {MEMORY_INDEX_PATH}")
105
+
106
+ # ترفند Force Update
107
+ with open(os.path.join(MEMORY_INDEX_PATH, "last_sync_log.txt"), "w") as f:
108
+ f.write(f"Sync triggered at: {time.time()}")
109
+
110
+ print("☁️ Uploading Index folder...")
111
+ api.upload_folder(
112
+ folder_path=MEMORY_INDEX_PATH,
113
+ path_in_repo=MEMORY_INDEX_DIR_NAME,
114
+ repo_id=REPO_ID,
115
+ repo_type=REPO_TYPE,
116
+ commit_message=f"Auto-save Indices: {time.strftime('%Y-%m-%d %H:%M:%S')}",
117
+ ignore_patterns=[".DS_Store", "*.git*"]
118
+ )
119
+ print(f"✅ Sync Complete.")
120
 
121
+ except Exception as e:
122
+ print(f"❌ Cloud Sync Error: {e}")
123
 
 
 
 
 
124
 
125
+ def pull_from_hub():
126
+ if not HF_TOKEN: return
 
 
127
 
128
+ try:
129
+ print(f"📥 Pulling data from Hub...")
130
+ # دانلود JSON
131
  try:
132
+ snapshot_download(
133
+ repo_id=REPO_ID,
134
+ repo_type=REPO_TYPE,
135
+ local_dir=MEMORIES_DIR,
136
+ allow_patterns=[MEMORY_FILE_NAME],
137
+ force_download=True
138
  )
139
+ except: pass
140
+
141
+ # دانلود پوشه ایندکس
142
+ snapshot_download(
143
+ repo_id=REPO_ID,
144
+ repo_type=REPO_TYPE,
145
+ local_dir=MEMORIES_DIR,
146
+ allow_patterns=f"{MEMORY_INDEX_DIR_NAME}/**",
147
+ force_download=True
148
+ )
149
+ print("✅ Data Downloaded.")
150
+
151
+ # برعکس: اگر برنامه در ../memories دنبال فایل می‌گردد، باید فایل‌های دانلود شده را آنجا هم کپی کنیم
152
+ # تا LlamaIndex بتواند آن‌ها را بخواند (اگر مسیرش نسبی است)
153
+ if os.path.exists(MEMORY_INDEX_PATH):
154
+ try:
155
+ os.makedirs(LEGACY_INDEX_PATH, exist_ok=True)
156
+ shutil.copytree(MEMORY_INDEX_PATH, LEGACY_INDEX_PATH, dirs_exist_ok=True)
157
+ print(f"✅ Mirroring downloaded files to legacy path: {LEGACY_INDEX_PATH}")
158
+ except Exception as e:
159
+ print(f"⚠️ Mirroring error: {e}")
160
+
161
+ except Exception as e:
162
+ print(f"⚠️ Cloud Download Error: {e}")
163
+
164
+ # ==========================================
165
+ # 4. توابع اصلی I/O
166
+ # ==========================================
167
+
168
+ def save_memory(data):
169
+ try:
170
+ os.makedirs(MEMORIES_DIR, exist_ok=True)
171
+ temp_file = MEMORY_FILE_PATH + ".tmp"
172
+ with open(temp_file, "w", encoding="utf-8") as f:
173
+ json.dump(data, f, ensure_ascii=False, indent=4)
174
+ os.replace(temp_file, MEMORY_FILE_PATH)
175
+ push_to_hub()
176
+ except Exception as e:
177
+ print(f"❌ Save Error: {e}")
178
+
179
+ def load_memory():
180
+ pull_from_hub()
181
+ if not os.path.exists(MEMORY_FILE_PATH):
182
+ return {}
183
+ try:
184
+ with open(MEMORY_FILE_PATH, 'r', encoding='utf-8') as f:
185
+ return json.loads(f.read())
186
+ except:
187
+ return {}
188
+
189
+ # ==========================================
190
+ # 5. منطق حافظه (Memory Logic)
191
+ # ==========================================
192
+
193
+ def enter_name_llamaindex(name, memory_data, data_args, update_memory_index=True):
194
+ is_new_user = False
195
+ if name not in memory_data:
196
+ memory_data[name] = {"name": name, "sessions": [], "episodic_memory": [], "semantic_memory": {}, "profile": {}}
197
+ is_new_user = True
198
+ save_memory(memory_data)
199
 
200
+ user_memory = memory_data[name]
201
 
202
+ # سعی می‌کنیم فایل‌ها را در هر دو مسیر (جدید و قدیم) چک کنیم
203
+ # مسیر صحیح (که دانلود شده)
204
+ correct_base_dir = os.path.join(MEMORY_INDEX_PATH, "llamaindex", name)
205
+ # مسیر قدیمی (که ممکن است بیلدر آنجا بسازد)
206
+ legacy_base_dir = os.path.join(LEGACY_INDEX_PATH, "llamaindex", name)
 
 
 
 
 
 
 
 
207
 
208
+ # بررسی وجود فایل در مسیر صحیح
209
+ indices_exist = (
210
+ os.path.exists(os.path.join(correct_base_dir, "sessions_index.json")) and
211
+ os.path.exists(os.path.join(correct_base_dir, "episodic_memory_index.json"))
212
+ )
213
 
214
+ if update_memory_index or not indices_exist:
215
+ try:
216
+ print(f"⚙️ Building indices for {name}...")
217
+ # این تابع احتمالا در ../memories می‌سازد
218
+ build_memory_index(memory_data, data_args, name=name)
219
+
220
+ # بلافاصله فایل‌ها را به مسیر درست منتقل می‌کنیم و آپلود می‌کنیم
221
+ sync_legacy_files_to_app()
222
+ push_to_hub()
223
+
224
+ except Exception as e:
225
+ print(f"Warning building index: {e}")
226
+
227
+ # بارگذاری (لودینگ)
228
+ # اولویت با مسیری است که فایل دارد. اگر دانلود شده باشد، در correct_base_dir است.
229
+ # اگر همین الان ساخته شده باشد و کپی شده باشد، باز هم در correct_base_dir است.
230
+ load_dir = correct_base_dir if os.path.exists(correct_base_dir) else legacy_base_dir
231
+
232
+ sessions_memory = None
233
+ episodic_memory = None
234
+ semantic_memory = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
235
 
236
+ def load_idx(path):
237
+ try:
238
+ return load_index_from_storage(StorageContext.from_defaults(persist_dir=path))
239
+ except: return None
240
+
241
+ if os.path.exists(load_dir):
242
+ sessions_memory = load_idx(os.path.join(load_dir, "sessions_index")) # ممکن است نام پوشه فرق کند، کد اصلی چک شود
243
+ # معمولا json ذخیره نمیشود، بلکه پوشه است. اگر فایل json است:
244
+ if not sessions_memory:
245
+ # LlamaIndex معمولا پوشه میسازد. اگر فایل json است لاجیک فرق میکند
246
+ # اما طبق لاگ شما: Saved ... to .../episodic_memory (بدون پسوند، یعنی پوشه)
247
+ pass
248
+
249
+ # تلاش استاندارد برای لود:
250
+ # توجه: در لاگ شما مسیرها .../episodic_memory بود (پوشه).
251
+ s_path = os.path.join(load_dir, "sessions_index")
252
+ e_path = os.path.join(load_dir, "episodic_memory") # طبق لاگ شما
253
+ m_path = os.path.join(load_dir, "semantic_memory") # طبق لاگ شما
254
+
255
+ # چک کردن فایل json اگر پوشه نبود (fallback)
256
+ if not os.path.exists(e_path): e_path += "_index.json"
257
+ if not os.path.exists(m_path): m_path += "_index.json"
258
+
259
+ if os.path.exists(s_path): sessions_memory = load_idx(s_path)
260
+ if os.path.exists(e_path): episodic_memory = load_idx(e_path)
261
+ if os.path.exists(m_path): semantic_memory = load_idx(m_path)
262
+
263
+ return f"Welcome {name}!", user_memory, sessions_memory, episodic_memory, semantic_memory
264
+
265
+
266
+ def summarize_memory_event_personality(data_args, memory_data, user_name):
267
+ save_memory(memory_data)
268
+ try:
269
+ updated_memory = summarize_memory(MEMORY_FILE_PATH, user_name, language='en')
270
+ # بعد از خلاصه سازی هم ممکن است ایندکس تغییر کند
271
+ sync_legacy_files_to_app()
272
+ push_to_hub()
273
+ return updated_memory.get(user_name, {})
274
+ except Exception as e:
275
+ print(f"Error summarize: {e}")
276
+ return memory_data.get(user_name, {})