import os import json import re from openai import OpenAI # --- Configuration & Client Setup --- api_key = os.environ.get("GAPGPT_API_KEY") base_url = "https://api.gapgpt.app/v1" if not api_key: print("⚠️ Warning: 'GAPGPT_API_KEY' not found. Checking for 'OPENAI_API_KEY'...") api_key = os.environ.get("OPENAI_API_KEY") if api_key: print("ℹ️ Using 'OPENAI_API_KEY' instead. Switching base_url to default.") base_url = None else: print("❌ CRITICAL ERROR: No API Key found. LLM calls will fail.") if base_url: client = OpenAI(api_key=api_key, base_url=base_url) else: client = OpenAI(api_key=api_key) def get_llm_response(prompt): """Helper to call LLM and handle basic errors.""" if not client.api_key: return "{}" try: response = client.chat.completions.create( model="gpt-4o", messages=[ {"role": "system", "content": "You are a JSON generator. Output ONLY valid JSON. No markdown, no explanations."}, {"role": "user", "content": prompt} ], temperature=0.3, max_tokens=1000 ) content = response.choices[0].message.content if not content: print("⚠️ LLM returned empty content.") return "{}" return content except Exception as e: print(f"❌ Error calling LLM: {e}") return "{}" def clean_json_text(text): """ Robustly extracts JSON object from text using Regex. Finds the substring starting with '{' and ending with '}'. """ if not text: return "" # First, try to remove markdown code blocks if present text = text.replace('```json', '').replace('```', '') # Use Regex to find the outer-most JSON object match = re.search(r'\{.*\}', text, re.DOTALL) if match: return match.group(0) return text.strip() def update_episodic_memory_only(memory_data, user_name): """ Analyzes the LATEST session to extract: Topics, Emotions, Insights. """ print(f"⚡ Processing Episodic Memory for session end: {user_name}") if user_name not in memory_data: return memory_data user_data = memory_data[user_name] sessions = user_data.get("sessions", []) if not sessions: return memory_data last_session = sessions[-1] if last_session.get("analyzed", False): print(f" Session {last_session.get('session_id')} already analyzed.") return memory_data conversation = last_session.get("conversation", []) if not conversation: return memory_data print(" ... ⚡ Generating Episodic Memory (Calling LLM) ...") conversation_text = json.dumps(conversation, ensure_ascii=False) prompt = f""" Analyze this chat session between AI (Emma) and user ({user_name}). Conversation: {conversation_text} Return a JSON object with these exact keys: {{ "topics": ["list", "of", "topics"], "emotion": "single string emotion", "key_insights": ["list", "of", "insights"] }} """ response_text = get_llm_response(prompt) cleaned_text = clean_json_text(response_text) try: analysis_result = json.loads(cleaned_text) except json.JSONDecodeError as e: print(f" ⚠️ JSON Error: {e}") print(f" 📝 Raw LLM Response was: {response_text}") # Debug print analysis_result = {"topics": [], "emotion": "Unknown", "key_insights": []} episode_entry = { "session_id": last_session.get("session_id"), "date": last_session.get("date"), "topics": analysis_result.get("topics", []), "emotion": analysis_result.get("emotion", "Neutral"), "insights": analysis_result.get("key_insights", []) } if "episodic_memory" not in user_data: user_data["episodic_memory"] = [] user_data["episodic_memory"].append(episode_entry) last_session["analyzed"] = True print(f" ✅ Episodic memory successfully updated.") return memory_data def update_semantic_memory_only(memory_data, user_name): """ Analyzes ALL episodic memories to update the Semantic Profile. """ print(f"🧠 Processing Semantic Memory for: {user_name}") if user_name not in memory_data: return memory_data user_data = memory_data[user_name] episodes = user_data.get("episodic_memory", []) if not episodes: print(" No episodic memory found.") return memory_data episodes_text = json.dumps(episodes[-10:], ensure_ascii=False) prompt = f""" Based on these past session analyses for user '{user_name}', update their profile. History: {episodes_text} Current Profile: {user_data.get('semantic_memory', 'None')} Return JSON: {{ "big_five": {{ "openness": "Low/Medium/High", "conscientiousness": "Low/Medium/High", "extraversion": "Low/Medium/High", "agreeableness": "Low/Medium/High", "neuroticism": "Low/Medium/High" }}, "core_values": ["value1", "value2"], "summary": "Concise personality summary." }} """ response_text = get_llm_response(prompt) cleaned_text = clean_json_text(response_text) try: semantic_result = json.loads(cleaned_text) if "summary" in semantic_result: user_data["semantic_profile"] = semantic_result user_data["semantic_memory"] = semantic_result["summary"] print(f" ✅ Semantic memory updated.") else: print(" ⚠️ Semantic update failed: 'summary' key missing.") except json.JSONDecodeError as e: print(f" ⚠️ JSON Error: {e}") print(f" 📝 Raw LLM Response was: {response_text}") return memory_data def summarize_memory(memory_data, user_name): data = update_episodic_memory_only(memory_data, user_name) data = update_semantic_memory_only(data, user_name) return data