Socrates_docker / Prompt_building.py
alesamodio's picture
improve further prompt building
b2b23da
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