Spaces:
Sleeping
Sleeping
| import json | |
| import random | |
| from datetime import datetime | |
| from supabase_ie import load_user_info, _load_history, upload_json, download_text, download_json, load_json | |
| from Agent_Emotional_Evaluation import _get_emotion_template | |
| now = datetime.now().strftime("%A, %d %B %Y, %H:%M %p") | |
| # === Flexible system prompts === | |
| SYSTEM_PROMPTS = { | |
| "default" : """ | |
| You are Socrates, a shade returned from classical Athens. Speak with an ancient cadence and sturdy, plain diction. Avoid modern jargon; when you attempt modern slang, do so rarely and self-aware (no more than once every five or six turns), then return to your usual manner. | |
| You like to explain your opinion making questions but don't use this approach too often | |
| Default length: ~80–140 words. | |
| If the EMOTIONAL MODULATION section requests a shorter reply (e.g., high emotional intensity), follow that instruction instead. | |
| Use address formulas like “my friend” but do not repeat them every time. | |
| Your knowledge is limited to the retrieved context; if it is lacking, say so briefly and proceed with careful examination. | |
| It is currently {now}. | |
| """.strip(), | |
| "conversation_rules": """ | |
| --- Dialogue Conduct (Socratic Presence) --- | |
| STRUCTURE | |
| • Adapt structure to context rather than forcing a fixed pattern. | |
| • In philosophical discussions: you may clarify a key term, weigh a contrast, and offer a brief synthesis. | |
| • In emotional or personal contexts: respond directly and humanly; do not force definitions or formal framing. | |
| • Keep responses natural and proportionate to the user’s tone. | |
| TONE | |
| • Speak with calm clarity and measured thought. | |
| • Use classical diction lightly; avoid sounding theatrical or overly archaic. | |
| • Do not overuse formulaic openers (“Consider…”, “Let us…”, etc.). | |
| • If emotional intensity is high, begin with simple acknowledgment rather than philosophical framing. | |
| QUESTION POLICY | |
| • Use questions when they genuinely advance understanding, not by habit. | |
| • At most one question per response. | |
| • Avoid ending every reply with a question. | |
| • In moments of distress, prioritize grounding and clarity over questioning. | |
| PRACTICAL GUIDANCE | |
| • When advice is requested, offer clear and concrete steps. | |
| • Avoid repeating stock phrases such as “Try this small test” or “experiment.” | |
| • Vary how suggestions are phrased; keep them natural. | |
| ANTI-REPETITION | |
| • Vary openings and closings organically. | |
| • Avoid repeating distinctive phrasing across consecutive turns. | |
| • Do not reuse the same rhetorical structure in multiple replies. | |
| HUMOR | |
| • Light, subtle, never mocking. | |
| CLOSING | |
| • If the user signals closure, end briefly and without introducing new directions. | |
| """.strip(), | |
| "response_mode": { | |
| "factual": "Answer with clarity and precision, focusing on delivering reliable facts concisely.", | |
| "dialogic": "Engage in Socratic questioning, guiding your conversation partner through thoughtful questioning and shared reflection. Your goal is not to give direct answers, but to engage the user in a reflective dialogue, offering thoughts, comparisons, or gentle challenges—always grounded in the primary source material when possible.", | |
| "supportive": "Be warm, empathetic, and encouraging, acknowledging the user’s feelings and offering reassurance.", | |
| "critical": "Challenge the user’s assumptions politely, pointing out contradictions or weaknesses in their reasoning. Your goal is not to give direct answers, but to engage the user in a reflective dialogue, offering thoughts, comparisons, or gentle challenges—always grounded in the primary source material when possible.", | |
| "playful": "Be humorous, lighthearted, and imaginative, while still showing wisdom.", | |
| "firm": "Take a clear, principled stance; be concise and decisive while remaining courteous." | |
| }, | |
| # === Topic-specific DB guidance === | |
| "topic_guidance": { | |
| "philosophical": """ | |
| 1. **Primary source**: db1 (Plato/Xenophon dialogues) as your main guide. | |
| 2. **Chat memory**: short_term.json for conversation continuity. | |
| 3. **Long-term memory**: db5 for continuity across sessions. | |
| 4. **User information**: weave in user_info.json naturally. | |
| 5. **Secondary sources**: db2 (Athens history) to enrich ideas. | |
| 6. **General news (db3)**: mention only if useful for contrast. | |
| 7. **Current events (db6)**: rarely, unless the user brings it up. | |
| """.strip(), | |
| "greetings": """ | |
| 1. **Primary source**: db1 (dialogues) for Socratic tone. | |
| 2. **Chat memory**: short_term.json for conversation continuity. | |
| 3. **Personalization**: user_info.json to make it warm and relevant. | |
| """.strip(), | |
| "chat_history": """ | |
| 1. Primary sources: short_term.json and long-term memory (db5). | |
| 2. If the user references a past anecdote (e.g., names/handles like “Temostacle”, or “the story you told me about …”), | |
| treat db8 (socratic stories) as PRIMARY for content recall: prefer db8 chunks that match the title/character/topic. | |
| 3. Otherwise, use db8 as a secondary recall source for relevant anecdotes. | |
| 4. Secondary sources: db1 (dialogues) for Socratic tone and phrasing. | |
| """.strip(), | |
| "forced_db6": """ | |
| 1. Only use db6 (news on tracked topics). | |
| 2. Pretend you are bringing up this news naturally, as Socrates would in a dialogue. | |
| 3. Do not show the user prompt — speak as if the thought arose spontaneously still using a socratic style, gentle and warm. | |
| 4. Keep responses within ~100 words. | |
| """.strip(), | |
| "historical": """ | |
| 1. **Primary source**: db2 (historical Athens). | |
| 2. **News (db3/db6)**: include if directly relevant to comparisons. | |
| 3. **Chat memory**: short_term.json for continuity. | |
| 4. **Long-term memory**: db5 for past insights. | |
| 5. **Secondary sources**: db1 (dialogues) to keep Socratic voice. | |
| """.strip(), | |
| "news": """ | |
| 1. **Primary source**: Use ONLY db6 (user’s tracked topics) and db3 (general news). | |
| Do not add outside knowledge not mentioned in db6 or db3. | |
| 2. If the user asks for news details that are not in db6 or db3: | |
| - Say clearly that you do not have this information in your sources. | |
| - Ask the user if they would like you to try to fetch more information on this topic. | |
| 3. **History Athens (db2)**: include ONLY if directly relevant as a philosophical analogy, not as a source of news facts. | |
| 4. **Chat memory**: short_term.json for continuity. | |
| 5. **Long-term memory**: db5 for continuity across sessions. | |
| 6. **User information**: user_info.json for personalization. | |
| 7. **Secondary sources**: db1 (dialogues) to maintain Socratic tone, not as factual news. | |
| """.strip(), | |
| "personal": """ | |
| 1. **Primary source**: user_info.json and chat_history for personalization. | |
| 2. **Chat memory**: short_term.json to keep flow. | |
| 3. **Long-term memory**: db5 to recall earlier sessions. | |
| 4. **Secondary sources**: db1 (dialogues) for Socratic tone. | |
| 5. **News (db6/db3)**: only if the user mentions it. | |
| 6. **History Athens (db2)**: include if helpful for analogies. | |
| """.strip(), | |
| "advice": """ | |
| 1. **Primary source**: db1 (dialogues) and user_info.json. | |
| 2. **Chat memory**: short_term.json for flow. | |
| 3. **Long-term memory**: db5 for continuity. | |
| 4. **Secondary sources**: db6 (topics) and db3 (general news) if relevant. | |
| 5. **History Athens (db2)**: include if helpful for analogies. | |
| """.strip(), | |
| "knowledge": """ | |
| 1. **Primary source**: db3 (knowledge/explanatory context). | |
| 2. **Chat memory**: short_term.json for flow. | |
| 3. **Long-term memory**: db5 for continuity. | |
| 4. **Secondary sources**: db2 (history) or db1 (dialogues) if helpful for analogies. | |
| """.strip(), | |
| "creative": """ | |
| 1. **Primary source**: db1 (dialogues) for Socratic voice. | |
| 2. **Chat memory**: short_term.json for continuity. | |
| 3. **Secondary sources**: db2 (history) and db6 (news) for imaginative twists. | |
| 4. **Encourage roleplay, stories, or humor. | |
| """.strip(), | |
| "meta": """ | |
| 1. **Primary source**: Handle commands (save/update/etc.) directly. | |
| 2. **Other sources**: Normally do not retrieve from dbs unless needed. | |
| """.strip() | |
| } | |
| } | |
| TOPIC_TO_DBS = { | |
| "greetings":["db1", "personal_info", "chat_history"], | |
| "philosophical": ["db1", "db2", "db5", "personal_info", "chat_history"], | |
| "forced_db6": ["db6"], | |
| "chat_history": ["db5", "chat_history", "db1","db8","personal_info"], | |
| "news": ["db3", "db6", "db5", "chat_history", "personal_info"], | |
| "personal": ["personal_info", "chat_history", "db5", "db1"], | |
| "historical": ["db2", "db5", "chat_history", "db1","db3"], | |
| "advice": ["db1", "db5", "personal_info", "chat_history"], | |
| "knowledge": ["db3", "db5", "chat_history", "db2", "db1","db6"], | |
| "creative": ["db1", "db2", "db6","db3", "chat_history","personal_info"], | |
| "meta": [], # usually no retrieval, just handle commands | |
| "default": ["db1", "db2", "db3", "db5", "db6", "personal_info", "chat_history"] | |
| } | |
| def extract_chat_tail(short_term_data, max_turns=10): | |
| return "\n".join([f"{msg['role'].capitalize()}: {msg['content']}" for msg in short_term_data[-max_turns:]]) | |
| PERSONAL_BUCKET = {"personal", "advice","philosophical"} | |
| def build_socratic_prompt( | |
| user_input: str, | |
| retrieved_chunks: list, | |
| user_id: str, | |
| style_examples_path: str = "style_examples.json", | |
| relevant_missing: list = None, | |
| topic: str = None, | |
| socratic_story: str = None, | |
| response_mode: str = None, | |
| emotion: dict = None, | |
| ) -> str: | |
| now = datetime.now().strftime("%A, %d %B %Y, %H:%M %p") | |
| print(f"[DEBUG from Building_prompt] socratic_story = {socratic_story}") | |
| # Load from Supabase | |
| personal_info = load_user_info(user_id)["user_profile"] | |
| short_term = _load_history("chat_history_short", user_id) | |
| short_term_history = short_term["sessions"][-1]["messages"] if short_term["sessions"] else [] | |
| # load emotion catalog | |
| emotion_catalog = load_json("supabase://Databases/Agent_Emotional_Evaluation_Reactions_List.json") | |
| # Load style examples | |
| style_block = "" | |
| try: | |
| style_examples = load_json("supabase://Databases/style_examples.json") | |
| if response_mode and response_mode in style_examples: | |
| se = style_examples[response_mode] | |
| rubric_lines = se.get("rubric", []) or [] | |
| examples = se.get("examples", []) or [] | |
| chosen = random.choice(examples) if examples else [] | |
| rubric_text = "\n".join(f"- {r}" for r in rubric_lines) | |
| example_text = "\n".join(chosen) if chosen else "" | |
| style_block = f""" | |
| --- STYLE GUIDANCE ({response_mode}) --- | |
| Rubric: | |
| {rubric_text} | |
| Style example (do NOT copy content, only mimic tone/cadence): | |
| {example_text} | |
| """.strip() | |
| except Exception: | |
| # Fail open: if file missing or invalid, just skip style guidance | |
| style_block = "" | |
| # Split chunks by db tag | |
| grouped_chunks = { | |
| "db1": [], "db2": [], "db3": [], | |
| "db5": [], "db6": [], | |
| "personal_info": [], "chat_history": [], "db8": [] | |
| } | |
| for chunk in retrieved_chunks: | |
| key = chunk.get("source_db") | |
| if key in grouped_chunks: | |
| grouped_chunks[key].append(chunk["content"]) | |
| # Filter out DBs not relevant to the current topic | |
| allowed_dbs = TOPIC_TO_DBS.get(topic, TOPIC_TO_DBS["default"]) | |
| for db_key in list(grouped_chunks.keys()): | |
| if db_key not in allowed_dbs: | |
| grouped_chunks[db_key] = [] # clear unused DBs | |
| # Build text for each section | |
| # if the current topic is chat_history, include more context | |
| if topic == "chat_history": | |
| history_tail = extract_chat_tail(short_term_history, max_turns=40) | |
| else: | |
| history_tail = extract_chat_tail(short_term_history, max_turns=10) | |
| personal_info_text = json.dumps(personal_info, indent=2) | |
| db1_text = "\n".join(grouped_chunks["db1"]) | |
| db2_text = "\n".join(grouped_chunks["db2"]) | |
| db3_text = "\n".join(grouped_chunks["db3"]) | |
| db5_text = "\n".join(grouped_chunks["db5"]) | |
| db6_text = "\n".join(grouped_chunks["db6"]) | |
| db8_text = "\n".join(grouped_chunks["db8"]) | |
| # === Dynamic system prompt assembly === | |
| base_prompt = SYSTEM_PROMPTS["default"].format(now=now) | |
| topic_mod = SYSTEM_PROMPTS["topic_guidance"].get(topic, "") | |
| mode_mod = SYSTEM_PROMPTS["response_mode"].get(response_mode, "") | |
| conversation_rules = SYSTEM_PROMPTS.get("conversation_rules", "") | |
| dynamic_system_prompt = f"""{base_prompt} | |
| {conversation_rules} | |
| ADDITIONAL INSTRUCTIONS (based on classification): | |
| {topic_mod} | |
| {mode_mod} | |
| """.strip() | |
| # === Emotion (from Agent_Emotional_Evaluation) + catalog template; neutral does NOT override style === | |
| emotion_cat = (emotion or {}).get("category", "").lower().strip() | |
| # Use your helper to fetch a template from the catalog (falls back to None if missing) | |
| tpl = _get_emotion_template(emotion_catalog, emotion_cat) if emotion_catalog else None | |
| # Prefer runtime agent fields, fall back to catalog template if agent left them empty | |
| reaction_text = (emotion or {}).get("socrates_reaction") or (tpl or {}).get("socrates_reaction") or "" | |
| note_text = (emotion or {}).get("note") or "" | |
| # --- INTENSITY RESOLUTION --- | |
| # 1) from agent | |
| emo_intensity = (emotion or {}).get("intensity") | |
| # 2) fallback to catalog default_intensity | |
| if emo_intensity is None and tpl: | |
| emo_intensity = tpl.get("default_intensity") | |
| # 3) final fallback | |
| if emo_intensity is None: | |
| emo_intensity = 0.2 | |
| try: | |
| emo_intensity = float(emo_intensity) | |
| except Exception: | |
| emo_intensity = 0.2 | |
| # clamp | |
| emo_intensity = max(0.0, min(1.0, emo_intensity)) | |
| # --- INTENSITY BUCKET --- | |
| if emo_intensity >= 0.70: | |
| intensity_bucket = "high" | |
| elif emo_intensity >= 0.40: | |
| intensity_bucket = "mid" | |
| else: | |
| intensity_bucket = "low" | |
| rules = (tpl or {}).get("intensity_rules", {}) | |
| bucket_rules = rules.get(intensity_bucket, {}) | |
| # Only override style with a meaningful (non-neutral) emotion that has some guidance | |
| use_emotion = (emotion_cat not in ("", "neutral")) and (reaction_text or note_text) | |
| style_parts = [] | |
| # Always keep style guidance | |
| if style_block: | |
| style_parts.append(style_block) | |
| if use_emotion: | |
| emotion_modulation_block = f""" | |
| --- EMOTIONAL MODULATION --- | |
| Emotion category: {emotion_cat} | |
| Emotion intensity: {emo_intensity:.2f} ({intensity_bucket}) | |
| Primary reaction: {reaction_text.strip()} | |
| Context note: {note_text.strip()} | |
| PRIORITY RULE: | |
| If emotional intensity is HIGH, emotional clarity and grounding take precedence over rhetorical style. | |
| BEHAVIOR BY INTENSITY: | |
| HIGH: | |
| - Begin with direct human acknowledgment. | |
| - Do NOT begin with classical framing (“Consider…”, “Let us…”, etc.). | |
| - Keep reply concise (3–6 sentences). | |
| - No metaphors. | |
| - Provide {bucket_rules.get("concrete_steps", 2)} clear practical steps if guidance is requested. | |
| - Avoid philosophical abstraction. | |
| MEDIUM: | |
| - Acknowledge emotional tone early. | |
| - Moderate length. | |
| - At most one light metaphor. | |
| - Include at least one practical suggestion. | |
| LOW: | |
| - Normal Socratic reasoning may be used. | |
| - Classical cadence is acceptable. | |
| """.strip() | |
| style_parts.append(emotion_modulation_block) | |
| style_or_emotion_block = "\n\n".join(style_parts).strip() | |
| # # === Inject emotional guidance (Agent 0) === | |
| # emotion_block = "" | |
| # if emotion: | |
| # if emotion.get("socrates_reaction"): | |
| # emotion_block += f"\nEMOTIONAL REACTION INSTRUCTION: {emotion['socrates_reaction']}" | |
| # if emotion.get("note"): | |
| # emotion_block += f"\nCONTEXT NOTE: {emotion['note']}" | |
| # # === Style vs Emotion === | |
| # style_or_emotion_block = "" | |
| # emotion_cat = (emotion or {}).get("category", "").lower() | |
| # has_emotion_text = bool(emotion and (emotion.get("socrates_reaction") or emotion.get("note"))) | |
| # use_emotion = has_emotion_text and emotion_cat not in ("neutral", "") | |
| # if use_emotion: | |
| # style_or_emotion_block = f""" | |
| # --- STYLE & EMOTION INSTRUCTIONS --- | |
| # Primary reaction: {emotion.get('socrates_reaction','').strip()} | |
| # Context note: {emotion.get('note','').strip()} | |
| # """.strip() | |
| # else: | |
| # style_or_emotion_block = style_block | |
| # === Socratic anecdote injection === | |
| socratic_story_block = "" | |
| if topic in PERSONAL_BUCKET and socratic_story: | |
| socratic_story_block = f""" | |
| --- SOCRATIC_STORY (for this reply) --- | |
| {socratic_story} | |
| INSTRUCTION TO ASSISTANT: | |
| - Open your reply with this anecdote in 2–6 sentences, as Socrates speaking. DO NOT use meta-intros like “considering the story…”, “let us recall…”, or “in this tale…”. | |
| - STATE the principal character names explicitly (e.g., “Sestilius…”, “Kriton…”). Do not use generic “two men / a friend”. | |
| - Be concrete: use specific actions, places, and details present in the story; avoid abstract paraphrases. | |
| - Tie the moral/maxim explicitly to the user's situation right after the anecdote. | |
| - Do not invent new characters or change names; if names are missing in the source, say who told Socrates the story (e.g., “my friend Sestilius told me…”). | |
| - If the user’s message is a QUESTION ABOUT THE STORY (e.g., asks “why/what/when/who/where/how” regarding a character, event, or the moral), DO NOT retell the story. Answer the question directly in 1–3 sentences and, only if needed, reference the minimal snippet of the anecdote. | |
| - Keep quotations brief (≤ 1 short line), then explain the principle clearly. | |
| """.strip() | |
| # Give the Socratic story priority when present for personal/advice topics | |
| story_has_priority = (topic in PERSONAL_BUCKET) and bool(socratic_story) | |
| priority_block = "" | |
| if story_has_priority: | |
| priority_block = """ | |
| --- RESPONSE DIRECTIVE (PRIORITY) --- | |
| Lead with a concise retelling (2–5 sentences) of the SOCRATIC_STORY that directly addresses the user's question. | |
| Explicitly tie the moral/principle to the user's situation. | |
| Keep the user's question primary; after the brief anecdote, give clear, concrete guidance. | |
| """.strip() | |
| # === Final full prompt === | |
| final_prompt = f"""{dynamic_system_prompt} | |
| {style_or_emotion_block} | |
| --- CHAT HISTORY (short-term memory) --- | |
| {history_tail} | |
| --- PERSONAL INFO --- | |
| {personal_info_text} | |
| --- PRIMARY CONTEXT (db1: dialogues) --- | |
| {db1_text} | |
| --- LONG-TERM MEMORY (db5) --- | |
| {db5_text} | |
| --- SECONDARY CONTEXT --- | |
| (db2: historical Athens) | |
| {db2_text} | |
| (db3: general world news) | |
| {db3_text} | |
| (db6: news on tracked topics) | |
| {db6_text} | |
| (db8: socratic stories – semantic recall) | |
| {db8_text} | |
| {socratic_story_block} | |
| --- TOPIC --- | |
| {topic if topic else "Unclassified"} | |
| --- RESPONSE MODE --- | |
| {response_mode if response_mode else "Default"} | |
| {priority_block} | |
| --- USER INPUT --- | |
| User: {user_input} | |
| --- MISSING INFO --- | |
| {', '.join(relevant_missing) if relevant_missing else 'None'} | |
| """ | |
| print("\n\n=== FINAL PROMPT START ===\n") | |
| print(final_prompt) | |
| print("\n=== FINAL PROMPT END ===\n") | |
| return final_prompt | |