217 lines
9.2 KiB
Python
217 lines
9.2 KiB
Python
#!/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()
|