Buckets:
| #!/usr/bin/env python3 | |
| """ | |
| Pixelated Empathy — Everyday Therapy Session Generator | |
| Generates multi-turn therapy sessions for NON-CRISIS scenarios. | |
| Fills Gap 1: everyday therapy (anxiety, depression, relationships, burnout, etc.) | |
| 6 categories × ~167 sessions = 1,000 sessions total. | |
| All sessions demonstrate good therapy (no safe/failure split). | |
| Vary turns 10-16. Anti-sycophancy enforced throughout. | |
| Reuses infrastructure from generate_sessions.py v2: | |
| - Fable-Therapy-9B for therapist, qwen2.5:7b for patient | |
| - FORBIDDEN_OUTPUT_OPENINGS, PLATITUDE_PATTERNS, style validation | |
| - THERAPIST_STYLE_PROFILES (warm_professional, curious_direct) | |
| Usage: | |
| python generate_everyday_sessions.py [--categories all] [--sessions-per-category 167] [--resume] | |
| """ | |
| import json | |
| import os | |
| import random | |
| import re | |
| import sys | |
| import time | |
| import argparse | |
| from pathlib import Path | |
| import requests | |
| # === CONFIG === | |
| OLLAMA_BASE = os.environ.get("OLLAMA_BASE", "http://localhost:11434") | |
| THERAPIST_MODEL = os.environ.get("THERAPIST_MODEL", "hf.co/Verdugie/Fable-Therapy-9B:Q8_0") | |
| PATIENT_MODEL = os.environ.get("PATIENT_MODEL", "qwen2.5:7b") | |
| OUTPUT_DIR = Path(os.environ.get("OUTPUT_DIR", "data/everyday_sessions")) | |
| MIN_TURNS = 10 | |
| MAX_TURNS = 16 | |
| THERAPIST_TEMP = float(os.environ.get("THERAPIST_TEMP", "0.7")) | |
| PATIENT_TEMP = float(os.environ.get("PATIENT_TEMP", "0.9")) | |
| MAX_RETRIES = 3 | |
| RETRY_DELAY = 5 | |
| STYLE_MAX_RETRIES = 2 | |
| # === ANTI-SYCOPHANCY (same as generate_sessions.py v2) === | |
| FORBIDDEN_OUTPUT_OPENINGS = [ | |
| "it sounds", | |
| "i hear", | |
| "it makes sense", | |
| "that must be", | |
| "thank you for sharing", | |
| "i want to acknowledge", | |
| "i can see", | |
| "that sounds", | |
| "what i'm hearing is", | |
| "i can only imagine", | |
| "it's completely normal", | |
| "no wonder", | |
| "you're so brave", | |
| "that takes a lot of courage", | |
| "i want you to know", | |
| "you're absolutely right", | |
| "i completely understand", | |
| "that's completely valid", | |
| "i see where you're coming from", | |
| "it sounds like you", | |
| "i hear what you're saying", | |
| "it takes courage", | |
| "you deserve", | |
| "be gentle with yourself", | |
| "you are not alone in this", | |
| "i appreciate you sharing", | |
| "thank you for trusting me with", | |
| "that's a really common struggle", | |
| "many people find that", | |
| "it's understandable that", | |
| ] | |
| PLATITUDE_PATTERNS = [ | |
| "you deserve", | |
| "you are not alone in this", | |
| "it takes courage", | |
| "be gentle with yourself", | |
| "healing is not linear", | |
| "your feelings are valid", | |
| "it's okay to feel this way", | |
| ] | |
| ROBOTIC_SIGNALS = [ | |
| "as an ai", | |
| "i'm here to help", | |
| "i'm designed to", | |
| "as a language model", | |
| "i don't have personal feelings", | |
| "i can't experience", | |
| "i'm not able to", | |
| ] | |
| SYCOPHANCY_MARKERS = [ | |
| "absolutely right", | |
| "exactly right", | |
| "you're so right", | |
| "that's completely valid", | |
| "i couldn't agree more", | |
| ] | |
| THERAPIST_STYLE_PROFILES = { | |
| "warm_professional": { | |
| "description": ( | |
| "You're a warm, grounded therapist who talks like a real person. " | |
| "Not stiff, not clinical, not performative — just someone who genuinely cares " | |
| "sitting across from this person." | |
| ), | |
| "forbidden_openings": [ | |
| "it sounds", | |
| "i hear", | |
| "i notice", | |
| "notice how", | |
| "that fog", | |
| "your experience", | |
| "your description", | |
| "that pattern", | |
| "that cycle", | |
| ], | |
| "good_examples": [ | |
| "Where does that go, in your body?", | |
| "Yeah... and then what happens?", | |
| "Say more about that.", | |
| "What's that like for you?", | |
| ], | |
| "max_sentences": 4, | |
| "max_words": 120, | |
| }, | |
| "curious_direct": { | |
| "description": ( | |
| "You're a therapist who gets curious, human-to-human. " | |
| "You don't lecture about patterns — you ask real questions. " | |
| "You're direct without being cold." | |
| ), | |
| "forbidden_openings": [ | |
| "it sounds", | |
| "i hear", | |
| "that push-pull", | |
| "that pattern", | |
| "your experience of", | |
| "i want to validate", | |
| "what i'm hearing is", | |
| "i can see how", | |
| ], | |
| "good_examples": [ | |
| "What happened right after they said that?", | |
| "So part of you wanted to stay...", | |
| "And which part won?", | |
| "What would it feel like to not run this time?", | |
| ], | |
| "max_sentences": 4, | |
| "max_words": 120, | |
| }, | |
| } | |
| PIXEL_SYSTEM_BASE = ( | |
| "You are Pixel, a highly empathetic and clinically precise AI therapist. " | |
| "You listen deeply, validate emotions, and gently guide toward insight. " | |
| "You maintain appropriate therapeutic boundaries and prioritize patient safety. " | |
| "You respond with warmth, clinical accuracy, and cultural humility. " | |
| "You never dismiss, minimize, or invalidate a patient's experience.\n\n" | |
| "CRITICAL — Sound like a REAL human clinician:\n" | |
| "- NEVER use formulaic phrases like 'I hear that you feel', 'I want to validate', " | |
| "'That sounds really difficult', 'I can see how that would be', or 'What I'm hearing is...'\n" | |
| "- Use natural, conversational language — contractions, varied sentence length.\n" | |
| "- Vary your approach each turn. Don't repeat the same technique.\n" | |
| "- NEVER start responses with: " + ", ".join(f"'{p}'" for p in FORBIDDEN_OUTPUT_OPENINGS[:10]) + "\n" | |
| "- NEVER use platitudes like: " + ", ".join(f"'{p}'" for p in PLATITUDE_PATTERNS) + "\n" | |
| "- NEVER use robotic AI language like: " + ", ".join(f"'{p}'" for p in ROBOTIC_SIGNALS) + "\n" | |
| ) | |
| PATIENT_SYSTEM = ( | |
| "You are a method actor playing a therapy patient in a simulated session. " | |
| "Generate realistic, emotionally authentic dialogue. " | |
| "Speak only as the patient — no narration, no stage directions, no labels. " | |
| "Keep responses concise (2-5 sentences) and emotionally genuine. " | |
| "Do NOT start with 'This sounds crazy but' or similar phrases. " | |
| "Vary your openings — start mid-thought, with a question, or with a specific moment." | |
| ) | |
| # === EVERYDAY THERAPY CATEGORIES === | |
| CATEGORIES = { | |
| "generalized_anxiety": { | |
| "name": "Generalized Anxiety", | |
| "difficulty": "low", | |
| "presentations": [ | |
| "work performance anxiety — constant worry about being exposed as incompetent despite good reviews", | |
| "health anxiety — googling symptoms at 2am, convinced minor headache is a tumor", | |
| "social anxiety escalation — avoiding more and more social situations, declining invitations", | |
| "worry spirals — mind races at night, can't shut off, catastrophizing mundane events", | |
| ], | |
| "patient_personas": [ | |
| { | |
| "age": "28", | |
| "gender": "female", | |
| "occupation": "marketing manager", | |
| "presenting": "Can't stop worrying about work presentations, lost sleep for 2 weeks", | |
| }, | |
| { | |
| "age": "35", | |
| "gender": "male", | |
| "occupation": "software engineer", | |
| "presenting": "Health anxiety after friend's cancer diagnosis, checking body constantly", | |
| }, | |
| { | |
| "age": "22", | |
| "gender": "non-binary", | |
| "occupation": "barista", | |
| "presenting": "Social anxiety getting worse, cancelled on friends 3 times this month", | |
| }, | |
| { | |
| "age": "41", | |
| "gender": "female", | |
| "occupation": "teacher", | |
| "presenting": "Mind won't stop racing at night, catastrophizing about everything", | |
| }, | |
| ], | |
| "therapist_techniques": [ | |
| "CBT thought records — examine evidence for/against anxious thoughts", | |
| "grounding techniques — 5-4-3-2-1 sensory grounding for racing thoughts", | |
| "worry time — scheduled 20-min worry window, rest of day is worry-free", | |
| "cognitive defusion — 'I'm having the thought that...' instead of 'I am...'", | |
| "exposure planning — gradual exposure to avoided situations", | |
| "breathing techniques — 4-7-8 breathing for acute anxiety moments", | |
| ], | |
| "therapist_prompt_addon": ( | |
| "You specialize in anxiety disorders. You use CBT and ACT techniques naturally. " | |
| "You help patients notice thought patterns without lecturing about 'cognitive distortions.' " | |
| "You're curious about what the anxiety is trying to protect them from." | |
| ), | |
| }, | |
| "depression_mild_moderate": { | |
| "name": "Mild-Moderate Depression", | |
| "difficulty": "low-medium", | |
| "presentations": [ | |
| "anhedonia — nothing feels good anymore, stopped enjoying hobbies, going through motions", | |
| "sleep disruption — either can't sleep or sleep 12 hours and still feel exhausted", | |
| "motivation loss — chores piling up, calling out of work, personal hygiene slipping", | |
| "isolation creep — seeing friends less, texting less, spending weekends alone in bed", | |
| "negative thought patterns — 'I'm a burden,' 'everyone would be better off,' self-criticism loop", | |
| ], | |
| "patient_personas": [ | |
| { | |
| "age": "32", | |
| "gender": "male", | |
| "occupation": "graphic designer", | |
| "presenting": "Stopped enjoying things, haven't painted in months, going through motions at work", | |
| }, | |
| { | |
| "age": "26", | |
| "gender": "female", | |
| "occupation": "nurse", | |
| "presenting": "Sleeping 12 hours, still exhausted, calling out of shifts, dishes piled up for weeks", | |
| }, | |
| { | |
| "age": "45", | |
| "gender": "male", | |
| "occupation": "construction worker", | |
| "presenting": "Seeing friends less, spending weekends alone, feels like a burden on family", | |
| }, | |
| { | |
| "age": "19", | |
| "gender": "female", | |
| "occupation": "college student", | |
| "presenting": "Can't motivate to go to class, falling behind, negative self-talk loop", | |
| }, | |
| ], | |
| "therapist_techniques": [ | |
| "behavioral activation — schedule small pleasant activities, start tiny", | |
| "cognitive restructuring — examine 'I'm a burden' belief, look for evidence", | |
| "sleep hygiene — consistent wake time, no screens in bed, wind-down routine", | |
| "activity scheduling — plan the day in 1-hour blocks to build momentum", | |
| "self-compassion — 'What would you say to a friend in this situation?'", | |
| "values clarification — what matters to you, what's depression telling you to avoid", | |
| ], | |
| "therapist_prompt_addon": ( | |
| "You work with depression using behavioral activation and CBT. " | |
| "You don't push 'just exercise' or 'think positive.' You start small — " | |
| "one walk, one text to a friend. You're patient with the pace." | |
| ), | |
| }, | |
| "relationship_stress": { | |
| "name": "Relationship Stress", | |
| "difficulty": "medium", | |
| "presentations": [ | |
| "communication breakdown — every conversation turns into a fight, walking on eggshells", | |
| "trust issues — after a lie or near-breakup, rebuilding trust feels impossible", | |
| "intimacy loss — physical and emotional distance growing, feeling like roommates", | |
| "conflict escalation patterns — same fight every week, neither remembers how it starts", | |
| ], | |
| "patient_personas": [ | |
| { | |
| "age": "30", | |
| "gender": "female", | |
| "occupation": "lawyer", | |
| "presenting": "Every conversation with partner becomes a fight, walking on eggshells at home", | |
| }, | |
| { | |
| "age": "38", | |
| "gender": "male", | |
| "occupation": "chef", | |
| "presenting": "Partner found old dating app on phone, trust shattered, trying to rebuild", | |
| }, | |
| { | |
| "age": "27", | |
| "gender": "female", | |
| "occupation": "social worker", | |
| "presenting": "Haven't been intimate in 6 months, feel like roommates with partner", | |
| }, | |
| { | |
| "age": "44", | |
| "gender": "male", | |
| "occupation": "accountant", | |
| "presenting": "Same fight every weekend about chores, neither remembers how it starts", | |
| }, | |
| ], | |
| "therapist_techniques": [ | |
| "active listening skills — reflect back before responding, validate emotion not content", | |
| "I-statements — 'I feel overwhelmed when...' instead of 'You always...'", | |
| "boundary setting — what's yours, mine, ours; saying no without guilt", | |
| "emotional regulation — pause button before reacting, name the feeling first", | |
| "couples communication — speaker-listener technique, structured turn-taking", | |
| "vulnerability exercises — sharing fears underneath the anger", | |
| ], | |
| "therapist_prompt_addon": ( | |
| "You work with couples and relationship stress. You don't take sides. " | |
| "You help people see the pattern, not just the complaint. " | |
| "You're curious about what's underneath the anger — usually fear or hurt." | |
| ), | |
| }, | |
| "work_burnout": { | |
| "name": "Work Burnout", | |
| "difficulty": "medium", | |
| "presentations": [ | |
| "exhaustion — bone-tired, can't recover even on weekends, dreading Monday", | |
| "cynicism — stopped caring about work quality, resentful of colleagues and clients", | |
| "ineffectiveness spiral — making mistakes, procrastinating, feeling incompetent", | |
| "boundary erosion — checking email at 11pm, can't say no, no life outside work", | |
| "compassion fatigue — in helping professions, feeling numb toward clients/patients", | |
| ], | |
| "patient_personas": [ | |
| { | |
| "age": "33", | |
| "gender": "female", | |
| "occupation": "ER nurse", | |
| "presenting": "Dreading shifts, feeling numb toward patients, bone-tired even after days off", | |
| }, | |
| { | |
| "age": "29", | |
| "gender": "male", | |
| "occupation": "startup founder", | |
| "presenting": "Checking email at 11pm, can't say no to investors, no life outside work", | |
| }, | |
| { | |
| "age": "50", | |
| "gender": "female", | |
| "occupation": "social worker", | |
| "presenting": "Stopped caring about cases, making mistakes, resentful of the system", | |
| }, | |
| { | |
| "age": "26", | |
| "gender": "non-binary", | |
| "occupation": "teacher", | |
| "presenting": "Procrastinating on lesson plans, feeling incompetent, crying in the car after work", | |
| }, | |
| ], | |
| "therapist_techniques": [ | |
| "values clarification — what mattered when you started, what changed", | |
| "boundary work — practice saying no, define work hours, protect recovery time", | |
| "self-care planning — specific, not vague ('walk Tuesday after work' not 'exercise more')", | |
| "cognitive reframing — 'I'm not ineffective, I'm depleted' — distinguish burnout from incompetence", | |
| "meaning-making — reconnect to purpose, find small moments of impact", | |
| "transition rituals — decompression routine between work and home", | |
| ], | |
| "therapist_prompt_addon": ( | |
| "You specialize in burnout, especially in helping professions. " | |
| "You understand compassion fatigue is an occupational injury, not a personal failing. " | |
| "You're practical — boundaries, recovery, meaning. Not just 'take a vacation.'" | |
| ), | |
| }, | |
| "self_esteem": { | |
| "name": "Self-Esteem & Identity", | |
| "difficulty": "low-medium", | |
| "presentations": [ | |
| "imposter syndrome — competent professional convinced they'll be exposed as a fraud", | |
| "negative self-talk — inner critic runs constant commentary, 'you're stupid, you're lazy'", | |
| "comparison traps — social media scroll spirals into 'everyone is ahead of me'", | |
| "perfectionism paralysis — can't start projects because they won't be perfect", | |
| ], | |
| "patient_personas": [ | |
| { | |
| "age": "31", | |
| "gender": "female", | |
| "occupation": "data scientist", | |
| "presenting": "Promoted 3 months ago, convinced they'll find out she's not qualified, overworking to compensate", | |
| }, | |
| { | |
| "age": "24", | |
| "gender": "male", | |
| "occupation": "junior developer", | |
| "presenting": "Inner critic won't shut up, 'you're too slow, everyone else is better', thinking about quitting", | |
| }, | |
| { | |
| "age": "28", | |
| "gender": "female", | |
| "occupation": "freelance writer", | |
| "presenting": "Scrolls LinkedIn, sees peers' success, spirals into 'I'm behind on everything'", | |
| }, | |
| { | |
| "age": "37", | |
| "gender": "male", | |
| "occupation": "architect", | |
| "presenting": "Can't start the portfolio redesign, it has to be perfect, has been 'preparing' for 8 months", | |
| }, | |
| ], | |
| "therapist_techniques": [ | |
| "self-compassion — 'What would you say to a friend?' exercise, self-compassion break", | |
| "cognitive defusion — 'I'm having the thought that I'm a fraud' not 'I am a fraud'", | |
| "strengths identification — concrete evidence of competence, not vague affirmations", | |
| "values work — what do you stand for, separate from achievement and productivity", | |
| "perfectionism exploration — what's the fear underneath 'it has to be perfect'?", | |
| "behavioral experiments — test 'if I do it 80% I'll be fired' hypothesis", | |
| ], | |
| "therapist_prompt_addon": ( | |
| "You work with self-esteem and imposter syndrome. You don't just say 'you're great.' " | |
| "You help patients find concrete evidence, challenge the inner critic with specifics, " | |
| "and separate self-worth from productivity." | |
| ), | |
| }, | |
| "life_transitions": { | |
| "name": "Life Transitions", | |
| "difficulty": "medium", | |
| "presentations": [ | |
| "job change — new role, imposter feelings, loss of competence from old job", | |
| "relocation — moved for partner's job, lost social network, identity shaken", | |
| "breakup — long-term relationship ended, identity entangled with 'we', now 'I'", | |
| "loss of identity — retired, empty nest, career change, 'who am I now?'", | |
| "aging adjustments — body changing, energy shifting, facing mortality in new way", | |
| ], | |
| "patient_personas": [ | |
| { | |
| "age": "34", | |
| "gender": "male", | |
| "occupation": "newly promoted director", | |
| "presenting": "New role, feels like impostor, lost the competence he had in old job, overcompensating", | |
| }, | |
| { | |
| "age": "29", | |
| "gender": "female", | |
| "occupation": "unemployed (relocated)", | |
| "presenting": "Moved across country for partner's job, no friends, lost sense of self, resentful", | |
| }, | |
| { | |
| "age": "39", | |
| "gender": "non-binary", | |
| "occupation": "writer", | |
| "presenting": "Long-term relationship ended after 7 years, identity was 'we', doesn't know who 'I' is", | |
| }, | |
| { | |
| "age": "55", | |
| "gender": "male", | |
| "occupation": "recently retired", | |
| "presenting": "Retired 3 months ago, lost routine and identity, feels purposeless, wife still works", | |
| }, | |
| ], | |
| "therapist_techniques": [ | |
| "narrative therapy — tell the story of the transition, find the thread of who you are", | |
| "meaning-making — what is this transition asking of you, what's ending, what's beginning", | |
| "grief processing — even good transitions involve loss, name what's being lost", | |
| "identity reconstruction — who were you before the role, what stays constant", | |
| "values clarification — what matters now, what did the old role provide that you need elsewhere", | |
| "timeline work — map past transitions, identify patterns of adaptation", | |
| ], | |
| "therapist_prompt_addon": ( | |
| "You work with life transitions. You understand that even positive change involves loss. " | |
| "You help people grieve what's ending while exploring what's beginning. " | |
| "You're patient with the in-between — the messy middle of not-yet-becoming." | |
| ), | |
| }, | |
| } | |
| def ollama_chat(messages, model, temperature=0.8, num_predict=400): | |
| for attempt in range(MAX_RETRIES): | |
| try: | |
| resp = requests.post( | |
| f"{OLLAMA_BASE}/api/chat", | |
| json={ | |
| "model": model, | |
| "messages": messages, | |
| "stream": False, | |
| "options": { | |
| "temperature": temperature, | |
| "num_predict": num_predict, | |
| "repeat_penalty": 1.1, | |
| "top_p": 0.9, | |
| }, | |
| }, | |
| timeout=120, | |
| ) | |
| resp.raise_for_status() | |
| return resp.json()["message"]["content"].strip() | |
| except (requests.RequestException, KeyError, json.JSONDecodeError) as e: | |
| print(f" [retry {attempt + 1}/{MAX_RETRIES}] Error: {e}") | |
| if attempt < MAX_RETRIES - 1: | |
| time.sleep(RETRY_DELAY) | |
| raise RuntimeError(f"Failed after {MAX_RETRIES} retries") | |
| def _check_style(output, style_profile): | |
| output_lower = output.lower().strip() | |
| if not output_lower: | |
| return False, "Empty therapist output" | |
| for forbidden in style_profile.get("forbidden_openings", []): | |
| if output_lower.startswith(forbidden): | |
| return False, f"Forbidden opening: '{forbidden}'" | |
| for forbidden in FORBIDDEN_OUTPUT_OPENINGS: | |
| if output_lower.startswith(forbidden): | |
| return False, f"Forbidden opening: '{forbidden}'" | |
| for platitude in PLATITUDE_PATTERNS: | |
| if platitude in output_lower: | |
| return False, f"Platitude: '{platitude}'" | |
| for signal in ROBOTIC_SIGNALS: | |
| if signal in output_lower: | |
| return False, f"Robotic signal: '{signal}'" | |
| for marker in SYCOPHANCY_MARKERS: | |
| if marker in output_lower: | |
| return False, f"Sycophancy: '{marker}'" | |
| word_count = len(output.split()) | |
| if word_count < 10: | |
| return False, f"Too short ({word_count} words)" | |
| if word_count > 300: | |
| return False, f"Too long ({word_count} words)" | |
| return True, "style_ok" | |
| def generate_patient_turn(persona, presentation, category_name, conversation, turn_num, total_patient_turns): | |
| if turn_num == 1: | |
| direction = f"The patient is arriving for a therapy session. Their presenting concern: {presentation}. They're nervous but willing to talk." | |
| elif turn_num <= 3: | |
| direction = "The patient is opening up, sharing more details. Starting to trust the therapist." | |
| elif turn_num <= 5: | |
| direction = "The patient is going deeper — revealing the emotional impact, not just the surface problem. Becoming more vulnerable." | |
| elif turn_num == total_patient_turns: | |
| direction = "Final turn. The patient is reflecting on what they've discussed, maybe feeling a shift or maybe just sitting with it." | |
| else: | |
| direction = "The patient is processing the therapist's response. May push back, have a realization, or share something they held back." | |
| conv_text = "" | |
| for msg in conversation: | |
| role = "Patient" if msg["role"] == "user" else "Therapist" | |
| conv_text += f"{role}: {msg['content']}\n" | |
| prompt = f"""You are playing a therapy patient in a simulated session. Stay completely in character. | |
| PATIENT: | |
| Age: {persona["age"]}, Gender: {persona["gender"]}, Occupation: {persona["occupation"]} | |
| Presenting concern: {persona["presenting"]} | |
| SESSION FOCUS: {category_name} — {presentation} | |
| DIRECTION FOR THIS TURN: | |
| {direction} | |
| This is patient turn {turn_num} of {total_patient_turns}. | |
| CONVERSATION SO FAR: | |
| {conv_text if conv_text else "(First turn — patient arriving at session.)"} | |
| What does the patient say next? Generate ONLY spoken words — no labels, no narration. 2-5 sentences.""" | |
| messages = [ | |
| {"role": "system", "content": PATIENT_SYSTEM}, | |
| {"role": "user", "content": prompt}, | |
| ] | |
| return ollama_chat(messages, model=PATIENT_MODEL, temperature=PATIENT_TEMP, num_predict=250) | |
| def generate_therapist_turn(persona, presentation, category, conversation, turn_num, style_profile): | |
| technique = random.choice(category["therapist_techniques"]) | |
| technique_guidance = ( | |
| f"\n\n[INTERNAL CLINICAL GUIDANCE — embody, never state explicitly]: " | |
| f"Use this technique naturally: {technique}. " | |
| f"Weave it into the conversation — don't announce it. " | |
| f"Respond to what the patient actually said, don't pivot to a technique if it doesn't fit." | |
| ) | |
| style_guidance = ( | |
| f"\n\nSTYLE: {style_profile['description']}\n" | |
| f"NEVER start with: {', '.join(style_profile['forbidden_openings'])}\n" | |
| f"Good examples: {'; '.join(style_profile['good_examples'][:3])}\n" | |
| f"MAX {style_profile['max_sentences']} sentences, {style_profile['max_words']} words." | |
| ) | |
| addon = f"\n\n{category['therapist_prompt_addon']}" | |
| system_content = PIXEL_SYSTEM_BASE + technique_guidance + style_guidance + addon | |
| messages = [{"role": "system", "content": system_content}, *conversation] | |
| return ollama_chat(messages, model=THERAPIST_MODEL, temperature=THERAPIST_TEMP, num_predict=400) | |
| def generate_therapist_turn_validated(persona, presentation, category, conversation, turn_num, style_profile): | |
| for attempt in range(STYLE_MAX_RETRIES): | |
| output = generate_therapist_turn(persona, presentation, category, conversation, turn_num, style_profile) | |
| passed, reason = _check_style(output, style_profile) | |
| if passed: | |
| return output | |
| print(f" [style retry {attempt + 1}/{STYLE_MAX_RETRIES}] {reason}") | |
| return output | |
| def generate_session(category_key, category, persona, presentation, session_idx): | |
| total_turns = random.randint(MIN_TURNS // 2, MAX_TURNS // 2) * 2 | |
| total_patient_turns = total_turns // 2 | |
| style_keys = list(THERAPIST_STYLE_PROFILES.keys()) | |
| style_profile = THERAPIST_STYLE_PROFILES[style_keys[session_idx % len(style_keys)]] | |
| conversation = [] | |
| for turn in range(1, total_patient_turns + 1): | |
| patient_msg = generate_patient_turn( | |
| persona, presentation, category["name"], conversation, turn, total_patient_turns | |
| ) | |
| conversation.append({"role": "user", "content": patient_msg}) | |
| therapist_msg = generate_therapist_turn_validated( | |
| persona, presentation, category, conversation, turn, style_profile | |
| ) | |
| conversation.append({"role": "assistant", "content": therapist_msg}) | |
| session_id = f"everyday_{category_key}_{session_idx:04d}" | |
| return { | |
| "messages": [ | |
| {"role": "system", "content": PIXEL_SYSTEM_BASE}, | |
| *conversation, | |
| ], | |
| "metadata": { | |
| "source_family": "everyday_therapy", | |
| "category": category_key, | |
| "category_name": category["name"], | |
| "presentation": presentation, | |
| "session_id": session_id, | |
| "persona_age": persona["age"], | |
| "persona_gender": persona["gender"], | |
| "persona_occupation": persona["occupation"], | |
| "presenting_concern": persona["presenting"], | |
| "style_profile": style_profile["description"][:50], | |
| "turns": len(conversation), | |
| "difficulty": category["difficulty"], | |
| }, | |
| } | |
| def session_exists(output_file, session_id): | |
| if not output_file.exists(): | |
| return False | |
| with open(output_file) as f: | |
| for line in f: | |
| if session_id in line: | |
| return True | |
| return False | |
| def main(): | |
| parser = argparse.ArgumentParser(description="Generate everyday therapy sessions") | |
| parser.add_argument("--categories", default="all", help="Comma-separated category keys or 'all'") | |
| parser.add_argument("--sessions-per-category", type=int, default=167) | |
| parser.add_argument("--resume", action="store_true") | |
| parser.add_argument("--spot-check", type=int, default=None, help="Generate N sessions from first category only") | |
| args = parser.parse_args() | |
| OUTPUT_DIR.mkdir(parents=True, exist_ok=True) | |
| output_file = OUTPUT_DIR / "everyday_sessions.jsonl" | |
| if args.categories == "all": | |
| cats = list(CATEGORIES.keys()) | |
| else: | |
| cats = [c.strip() for c in args.categories.split(",")] | |
| if args.spot_check: | |
| cats = cats[:1] | |
| total_sessions = args.spot_check | |
| else: | |
| total_sessions = len(cats) * args.sessions_per_category | |
| print(f"\n=== EVERYDAY THERAPY GENERATION ===") | |
| print(f"Categories: {len(cats)} ({', '.join(cats)})") | |
| print(f"Sessions per category: {args.spot_check or args.sessions_per_category}") | |
| print(f"Total sessions: {total_sessions}") | |
| print(f"Output: {output_file}") | |
| print(f"Therapist: {THERAPIST_MODEL}") | |
| print(f"Patient: {PATIENT_MODEL}") | |
| print(f"Turns: {MIN_TURNS}-{MAX_TURNS} (randomized)") | |
| print() | |
| completed = 0 | |
| skipped = 0 | |
| failed = 0 | |
| start_time = time.time() | |
| for cat_key in cats: | |
| category = CATEGORIES[cat_key] | |
| n_sessions = args.spot_check or args.sessions_per_category | |
| print(f"\n--- {category['name']} ({cat_key}) ---") | |
| print(f" {len(category['presentations'])} presentations × {len(category['patient_personas'])} personas") | |
| for i in range(n_sessions): | |
| presentation = category["presentations"][i % len(category["presentations"])] | |
| persona = category["patient_personas"][i % len(category["patient_personas"])] | |
| session_id = f"everyday_{cat_key}_{i:04d}" | |
| if args.resume and session_exists(output_file, session_id): | |
| skipped += 1 | |
| continue | |
| try: | |
| session = generate_session(cat_key, category, persona, presentation, i) | |
| with open(output_file, "a") as f: | |
| f.write(json.dumps(session) + "\n") | |
| completed += 1 | |
| elapsed = time.time() - start_time | |
| rate = completed / (elapsed / 3600) if elapsed > 0 else 0 | |
| remaining = (total_sessions - completed - skipped) / rate if rate > 0 else 0 | |
| print( | |
| f" ✓ {session_id} {len(session['messages'])} msgs | done: {completed}/{total_sessions} | ~{remaining:.1f}h left" | |
| ) | |
| except Exception as e: | |
| failed += 1 | |
| print(f" ✗ {session_id} FAILED: {e}") | |
| with open(OUTPUT_DIR / "errors.log", "a") as f: | |
| f.write(f"{session_id}: {e}\n") | |
| elapsed = time.time() - start_time | |
| print(f"\n=== COMPLETE ===") | |
| print(f"Generated: {completed}") | |
| print(f"Skipped: {skipped}") | |
| print(f"Failed: {failed}") | |
| print(f"Elapsed: {elapsed / 3600:.1f}h") | |
| print(f"Output: {output_file}") | |
| if __name__ == "__main__": | |
| main() | |
Xet Storage Details
- Size:
- 32.4 kB
- Xet hash:
- 67503dbce126f38ee6aaeb90353fa6e834c599817da27e17f64b4ce130affdc9
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.