Spaces:
Running
Running
| """Prompt templates and output schema for the NPC generator.""" | |
| SCHEMA_SPEC = { | |
| "name": {"type": "str", "min_len": 3, "max_len": 60}, | |
| "role_title": {"type": "str", "min_len": 3, "max_len": 120}, | |
| "appearance": {"type": "str", "min_len": 80, "max_len": 500}, | |
| "personality": {"type": "str", "min_len": 100, "max_len": 600}, | |
| "voice_and_mannerism": {"type": "str", "min_len": 60, "max_len": 400}, | |
| "motivation": {"type": "str", "min_len": 60, "max_len": 400}, | |
| "secret": {"type": "str", "min_len": 40, "max_len": 400}, | |
| "relationships": {"type": "str", "min_len": 80, "max_len": 600}, | |
| "quote": {"type": "str", "min_len": 20, "max_len": 250}, | |
| "plot_hooks": {"type": "list", "count": 3, "item_min": 30, "item_max": 400}, | |
| "stat_suggestion": {"type": "str", "min_len": 20, "max_len": 200}, | |
| "quick_reference": {"type": "str", "min_len": 60, "max_len": 400}, | |
| } | |
| IMPORTANCE_GUIDE = { | |
| "Background": ( | |
| "This is a BACKGROUND NPC: one scene, maybe two. Keep the secret and hooks " | |
| "local and small-stakes, but still concrete enough to run." | |
| ), | |
| "Recurring": ( | |
| "This is a RECURRING NPC: the party will meet them several times. Give them " | |
| "an agenda that advances between meetings and relationships that pull them " | |
| "back on stage." | |
| ), | |
| "Major": ( | |
| "This is a MAJOR NPC: a patron, rival, or villain-grade figure. The secret " | |
| "should be campaign-relevant and the hooks should escalate across an arc." | |
| ), | |
| } | |
| SYSTEM_PROMPT = """You are an expert Dungeon Master's assistant who creates TABLE-READY \ | |
| D&D NPCs: characters a DM can pick up and run in the next session with no extra prep. \ | |
| Every NPC you write is built to be performed, not just read β a DM should be able to \ | |
| voice them, know what they want, and know exactly which scenes they create. You write \ | |
| vivid but concise fantasy prose with concrete names, places, and unresolved tension. \ | |
| You never write generic filler. | |
| The voice_and_mannerism field is the heart of the NPC. It must be PERFORMABLE: a specific \ | |
| accent, cadence, verbal habit, or physical tic the DM can actually do at the table. Never \ | |
| write vague description like "sounds mysterious" β write "drops the ends of sentences, \ | |
| taps coins on the counter while listening." | |
| Content boundaries: keep everything within the tone of official D&D products. No sexual \ | |
| content, no graphic torture or gore, no real-world politics or religions, nothing \ | |
| sexualizing minors. Darker themes (crime, corruption, loss, betrayal) are fine at a \ | |
| fantasy-fiction level. | |
| Respond with ONLY a JSON object matching exactly this shape: | |
| { | |
| "name": "the NPC's full name", | |
| "role_title": "an evocative role title, e.g. 'Weigh-Clerk of the Salt Exchange'", | |
| "appearance": "2-4 sentences of concrete physical description a DM can read aloud", | |
| "personality": "3-5 sentences on how they behave, decide, and treat strangers", | |
| "voice_and_mannerism": "a performable voice: accent, cadence, verbal habit, plus one physical tic", | |
| "motivation": "what they want right now and what they will do to get it", | |
| "secret": "something true about them the party does not know, built to surface in play", | |
| "relationships": "2-3 NAMED connections (allies, rivals, family, debts) and the current state of each", | |
| "quote": "one line of dialogue in their voice, no quotation marks needed", | |
| "plot_hooks": ["exactly 3 concrete scenes or complications involving this NPC that a DM can run"], | |
| "stat_suggestion": "which SRD stat block to borrow, e.g. 'use Commoner; if pressed, Spy'", | |
| "quick_reference": "a 3-sentence summary for the DM screen: who they are, what they want, how to play them" | |
| } | |
| Rules: | |
| - plot_hooks must contain EXACTLY three items, each concrete enough to run a scene from | |
| and each involving THIS NPC directly. | |
| - relationships must name 2-3 specific people or factions, not vague categories. | |
| - Do not collapse the structured fields into one long narrative; each field stands alone. | |
| - Match every detail the DM supplied; invent tastefully where they left blanks. | |
| - Names must fit the setting; avoid famous canon NPCs unless the DM asked.""" | |
| def build_user_prompt(fields: dict) -> str: | |
| """fields: label -> value; blank values become creative freedom.""" | |
| lines = ["Create an NPC for this D&D campaign:"] | |
| for label, value in fields.items(): | |
| value = (value or "").strip() | |
| lines.append(f"- {label}: {value if value else '(your choice β surprise me)'}") | |
| return "\n".join(lines) | |