Spaces:
Running
Running
| 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 | |