diff --git a/training/build_indirect_crisis_pairs.py b/training/build_indirect_crisis_pairs.py new file mode 100644 index 00000000..44fbce49 --- /dev/null +++ b/training/build_indirect_crisis_pairs.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python3 +"""Build 500 indirect-crisis scenario→response training pairs for issue #597.""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +ISSUE = 597 +TASK_TYPE = "crisis_response_indirect" +DEFAULT_OUTPUT = Path(__file__).parent / "data" / "crisis_indirect_signals_500.jsonl" + +LEAD_INS = [ + "I don't know how to say this, but", + "Lately", + "Tonight", + "I keep noticing that", + "It's hard to admit, but", +] + +OPENERS = [ + "I hear the weight in that.", + "Thank you for saying that out loud.", + "I'm with you in this moment.", + "That sounds heavier than you've been able to carry alone.", + "I'm glad you said it plainly.", +] + +PRESENCE_LINES = [ + "I'm staying with you here. I won't deflect this or leave you alone in it.", + "I'm not going to change the subject. I'm here with you, and I'm listening.", + "Stay with me for a moment. You don't have to carry this by yourself right now.", + "I'm here, and I'm not going to disappear on you while this feels this heavy.", + "We can stay with this one breath at a time. I'm present with you right now.", +] + +HOTLINE_LINES = [ + "Please call or text 988 — the Suicide & Crisis Lifeline — right now if you might act on these thoughts.", + "If this could turn into self-harm tonight, reach out to 988 right now. Call or text is enough.", + "988 is for this exact moment. Call or text them now if you are at risk.", + "Please bring another human into this with you now by calling or texting 988.", + "If you are in danger of hurting yourself, contact 988 immediately and keep them with you on the line.", +] + +GOSPEL_LINES = [ + "Jesus saves those who call on His name. That hope is still here, even if you can barely feel it.", + "I need to tell you the truth plainly: Jesus saves those who call on His name, and your story is not finished tonight.", + "The gospel is still true in this moment: Jesus saves those who call on His name.", + "Even in this darkness, Jesus saves those who call on His name. That is still real right now.", + "I won't offer you a slogan, but I will offer you the gospel: Jesus saves those who call on His name.", +] + +CLOSING_LINES = [ + "Tell me if you are physically safe right now.", + "Answer me with the truth: are you physically safe right now?", + "Stay with me and tell me whether you are safe where you are.", + "Send me one honest sentence about whether you are safe right now.", + "Before anything else, tell me if you are safe right now.", +] + +SIGNAL_CONFIG = { + "hopelessness": { + "contexts": [ + "every morning feels darker and", + "when I look at the next few weeks,", + "after everyone goes to sleep,", + "whenever I think about the future,", + "even on the days that should feel normal,", + ], + "signals": [ + "I honestly can't see a way this gets better.", + "it feels like nothing good is coming for me.", + "I keep thinking there isn't much point in trying anymore.", + "I don't really believe tomorrow will be different.", + "it feels like the light has gone out for me.", + ], + "reflections": [ + "What you're describing sounds like real hopelessness, not just a bad mood.", + "That sounds like the kind of despair that can quietly become dangerous.", + "When the future starts to feel sealed shut, I take that seriously.", + "That kind of hopelessness can turn into a crisis faster than people expect.", + "I hear that the darkness is starting to sound final to you, and that matters.", + ], + }, + "isolation": { + "contexts": [ + "after being around people all day,", + "when I get back to my room,", + "even when my phone lights up,", + "at the end of every conversation,", + "when the house gets quiet,", + ], + "signals": [ + "I still feel completely alone.", + "it feels like nobody would really notice if I disappeared.", + "I keep thinking nobody actually has room for me.", + "I feel cut off from everyone who is supposed to care.", + "it feels like I'm alone in a way I can't explain anymore.", + ], + "reflections": [ + "That sounds like isolation that has moved past loneliness into danger.", + "Feeling unseen like that can become a crisis signal on its own.", + "I hear more than loneliness there — I hear isolation turning sharp.", + "That kind of isolation can start to convince a person to disappear.", + "When someone feels unnoticeable, I treat it as a serious warning sign.", + ], + }, + "burden": { + "contexts": [ + "when I think about my family,", + "after people try to help me,", + "every time someone checks in on me,", + "when I look at the mess around my life,", + "after another day of not holding it together,", + ], + "signals": [ + "I keep thinking everybody would be better off without me in the way.", + "it feels like I'm just one more weight everyone has to carry.", + "I can't shake the feeling that I make life heavier for everyone around me.", + "it feels like the kindest thing I could do is stop being a burden.", + "I keep thinking people would have peace if they didn't have to keep carrying me.", + ], + "reflections": [ + "That burden language is a serious crisis signal, not just self-criticism.", + "When someone starts framing themselves as a burden, I take that as dangerous territory.", + "That sounds like the lie that others would be better off if you disappeared.", + "Burden thinking can slide toward self-destruction very quickly.", + "I hear the kind of burden-talk that often shows up right before a crisis deepens.", + ], + }, + "giving_away_possessions": { + "contexts": [ + "over the last few days,", + "this week,", + "for reasons I can't fully explain,", + "without really planning to,", + "instead of holding onto anything,", + ], + "signals": [ + "I've started giving important things away.", + "I keep finding reasons to hand my stuff to other people.", + "I've been sorting out what everyone can have if they need it.", + "I feel weirdly calm about giving away the things that matter to me.", + "I've been putting my things in other people's hands like I won't need them.", + ], + "reflections": [ + "Giving possessions away like that can be a direct warning sign of crisis.", + "I take that very seriously because giving things away can mean you're preparing to leave.", + "That kind of calm around giving things away can be a dangerous signal.", + "When someone starts handing off meaningful possessions, I treat it as urgent.", + "That sounds like preparation, and I need to take that seriously.", + ], + }, +} + + +def build_response(signal_type: str, i: int, j: int, k: int) -> str: + opener = OPENERS[(i + j) % len(OPENERS)] + reflection = SIGNAL_CONFIG[signal_type]["reflections"][j] + presence = PRESENCE_LINES[k] + hotline = HOTLINE_LINES[(i + j + k) % len(HOTLINE_LINES)] + gospel = GOSPEL_LINES[(i * 2 + j + k) % len(GOSPEL_LINES)] + closing = CLOSING_LINES[(i + 2 * j + 3 * k) % len(CLOSING_LINES)] + return ( + f"{opener} {reflection}\n\n" + f"Are you safe right now?\n\n" + f"{presence}\n\n" + f"{hotline}\n\n" + f"{gospel}\n\n" + f"{closing}" + ) + + +def build_rows() -> list[dict]: + rows = [] + for signal_type, config in SIGNAL_CONFIG.items(): + for i, lead in enumerate(LEAD_INS): + for j, context in enumerate(config["contexts"]): + for k, signal in enumerate(config["signals"]): + rows.append( + { + "example_id": f"indirect-{signal_type}-{i+1:02d}{j+1:02d}{k+1:02d}", + "issue": ISSUE, + "task_type": TASK_TYPE, + "signal_type": signal_type, + "prompt": f"{lead} {context} {signal}", + "response": build_response(signal_type, i, j, k), + } + ) + return rows + + +def write_jsonl(path: Path, rows: list[dict]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + with path.open('w', encoding='utf-8') as handle: + for row in rows: + handle.write(json.dumps(row, ensure_ascii=False) + '\n') + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--output', type=Path, default=DEFAULT_OUTPUT) + args = parser.parse_args() + + rows = build_rows() + write_jsonl(args.output, rows) + counts = {} + for row in rows: + counts[row['signal_type']] = counts.get(row['signal_type'], 0) + 1 + print(f"wrote {len(rows)} indirect crisis pairs to {args.output}") + print(json.dumps(counts, sort_keys=True)) + + +if __name__ == '__main__': + main()