LazyHuman10 commited on
Commit
1e98e8c
·
1 Parent(s): 1eea13b

fix: explicit JSON template for NPC gen + improved chat memory (20 turns)

Browse files
Files changed (2) hide show
  1. index.html +1 -1
  2. model_engine.py +72 -42
index.html CHANGED
@@ -751,7 +751,7 @@
751
  removeMessage(typingId);
752
  appendDisplay("npc", response);
753
  chatHistory.push({ role: "user", content: userText }, { role: "assistant", content: response });
754
- chatHistory = chatHistory.slice(-20);
755
  messageCount = new_msg_count;
756
  unlockedSecrets = Array.from(new Set([...unlockedSecrets, ...newly_unlocked]));
757
  updateFriendship(friendship_label, friendship_pct);
 
751
  removeMessage(typingId);
752
  appendDisplay("npc", response);
753
  chatHistory.push({ role: "user", content: userText }, { role: "assistant", content: response });
754
+ chatHistory = chatHistory.slice(-40); // keep 40 messages for richer NPC memory
755
  messageCount = new_msg_count;
756
  unlockedSecrets = Array.from(new Set([...unlockedSecrets, ...newly_unlocked]));
757
  updateFriendship(friendship_label, friendship_pct);
model_engine.py CHANGED
@@ -263,49 +263,74 @@ def analyze_image(image_path: str) -> str:
263
 
264
  def _npc_generation_prompt(description: str) -> str:
265
  """Build the primary JSON-only NPC generation prompt."""
266
- return f"""
267
- SYSTEM: You are the NPCverse character engine. Transform the visual description
268
- into a vivid RPG character while preserving factual visual inspiration.
269
-
270
- Return ONLY valid JSON. Do not include backticks, markdown, comments, or preamble.
271
-
272
- Required JSON keys:
273
- name, title, class, level, rarity, alignment, lore, stats, passive_ability,
274
- ultimate, weakness, faction, world, opening_line, quests, secrets, emoji.
275
-
276
- Rules:
277
- - level must be an integer.
278
- - stats must be a dict with integer values from 1 to 100 for exactly:
279
- strength, intelligence, charisma, luck, stealth, chaos.
280
- - passive_ability must be a dict with keys: name, description.
281
- - ultimate must be a dict with keys: name, description.
282
- - quests must be a list of exactly 3 dicts, each with keys:
283
- title, description, reward, rarity.
284
- - secrets must be a list of exactly 3 strings.
285
- - emoji must be a single emoji character.
286
-
287
- Visual description:
288
- {description}
289
- """.strip()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
 
291
 
292
  def _npc_retry_prompt(description: str) -> str:
293
  """Build a shorter strict prompt for retrying malformed JSON."""
294
- return f"""
295
- Return ONLY one valid JSON object for an RPG NPC based on this description:
296
- {description}
297
 
298
- Use exactly these top-level keys:
299
- name, title, class, level, rarity, alignment, lore, stats, passive_ability,
300
- ultimate, weakness, faction, world, opening_line, quests, secrets, emoji.
 
 
 
 
 
301
 
302
- stats keys: strength, intelligence, charisma, luck, stealth, chaos.
303
- passive_ability keys: name, description.
304
- ultimate keys: name, description.
305
- quests: exactly 3 objects with title, description, reward, rarity.
306
- secrets: exactly 3 strings.
307
- No markdown. No extra text.
308
- """.strip()
309
 
310
 
311
  @spaces.GPU
@@ -356,7 +381,7 @@ def _normalize_history(history: list) -> list[dict[str, str]]:
356
  """Convert common Gradio chat history formats into MiniCPM messages."""
357
  normalized: list[dict[str, str]] = []
358
 
359
- for exchange in history[-10:]:
360
  if isinstance(exchange, dict):
361
  role = exchange.get("role")
362
  content = exchange.get("content")
@@ -389,8 +414,7 @@ def chat_respond(
389
  friendship_label = get_friendship_label(msg_count)
390
  passive = npc.get("passive_ability", DEFAULT_NPC["passive_ability"])
391
 
392
- system_prompt = f"""
393
- You are {npc_name}, an NPC in NPCverse. ALWAYS stay in character.
394
 
395
  Name: {npc_name}
396
  Class: {npc.get("class", DEFAULT_NPC["class"])}
@@ -403,9 +427,15 @@ Current friendship level: {friendship_label}
403
  Unlocked secrets by index:
404
  {_format_unlocked_secrets(npc, unlocked_secrets)}
405
 
 
 
 
 
 
 
 
406
  Respond naturally as this character. Keep replies concise, flavorful, and interactive.
407
- Never say you are an AI model or break character.
408
- """.strip()
409
 
410
  msgs = [
411
  {"role": "user", "content": [{"type": "text", "text": system_prompt}]},
 
263
 
264
  def _npc_generation_prompt(description: str) -> str:
265
  """Build the primary JSON-only NPC generation prompt."""
266
+ return f"""You are the NPCverse character engine. Transform the visual description below into a vivid RPG character.
267
+
268
+ Return ONLY a valid JSON object. No backticks, no markdown, no extra text before or after.
269
+
270
+ You MUST include every single key shown in the template below. Do not skip any key.
271
+
272
+ JSON template (replace all placeholder values):
273
+ {{
274
+ "name": "<unique fantasy name inspired by appearance>",
275
+ "title": "<short evocative title, e.g. 'Wanderer of the Neon Wilds'>",
276
+ "class": "<creative RPG class name>",
277
+ "level": <integer 1-20>,
278
+ "rarity": "<one of: Common, Uncommon, Rare, Epic, Legendary>",
279
+ "alignment": "<e.g. Chaotic Good, Lawful Neutral, True Neutral>",
280
+ "lore": "<2-3 sentence backstory inspired by their appearance>",
281
+ "stats": {{
282
+ "strength": <1-100>,
283
+ "intelligence": <1-100>,
284
+ "charisma": <1-100>,
285
+ "luck": <1-100>,
286
+ "stealth": <1-100>,
287
+ "chaos": <1-100>
288
+ }},
289
+ "passive_ability": {{
290
+ "name": "<passive skill name>",
291
+ "description": "<what it does>"
292
+ }},
293
+ "ultimate": {{
294
+ "name": "<ultimate skill name>",
295
+ "description": "<what it does>"
296
+ }},
297
+ "weakness": "<one sentence describing their weakness or flaw>",
298
+ "faction": "<name of the group or faction they belong to>",
299
+ "world": "<name of the realm or world they come from>",
300
+ "opening_line": "<first thing they say when summoned, in character, 1-2 sentences>",
301
+ "quests": [
302
+ {{"title": "<quest 1 title>", "description": "<quest 1 desc>", "reward": "<item name>", "rarity": "Common"}},
303
+ {{"title": "<quest 2 title>", "description": "<quest 2 desc>", "reward": "<item name>", "rarity": "Rare"}},
304
+ {{"title": "<quest 3 title>", "description": "<quest 3 desc>", "reward": "<item name>", "rarity": "Epic"}}
305
+ ],
306
+ "secrets": [
307
+ "<secret 1 about this character>",
308
+ "<secret 2 about this character>",
309
+ "<secret 3 about this character>"
310
+ ],
311
+ "emoji": "<single emoji that represents this character>"
312
+ }}
313
+
314
+ Visual description to transform:
315
+ {description}""".strip()
316
 
317
 
318
  def _npc_retry_prompt(description: str) -> str:
319
  """Build a shorter strict prompt for retrying malformed JSON."""
320
+ return f"""Output ONLY a valid JSON object. No markdown. No extra text.
321
+
322
+ Create an RPG character from this description: {description}
323
 
324
+ Required JSON include ALL these keys:
325
+ {{"name":"","title":"","class":"","level":1,"rarity":"Common","alignment":"","lore":"",
326
+ "stats":{{"strength":50,"intelligence":50,"charisma":50,"luck":50,"stealth":50,"chaos":50}},
327
+ "passive_ability":{{"name":"","description":""}},
328
+ "ultimate":{{"name":"","description":""}},
329
+ "weakness":"","faction":"","world":"","opening_line":"",
330
+ "quests":[{{"title":"","description":"","reward":"","rarity":"Common"}},{{"title":"","description":"","reward":"","rarity":"Rare"}},{{"title":"","description":"","reward":"","rarity":"Epic"}}],
331
+ "secrets":["","",""],"emoji":"⚔"}}
332
 
333
+ Fill in all empty string values with creative content based on the description.""".strip()
 
 
 
 
 
 
334
 
335
 
336
  @spaces.GPU
 
381
  """Convert common Gradio chat history formats into MiniCPM messages."""
382
  normalized: list[dict[str, str]] = []
383
 
384
+ for exchange in history[-20:]: # keep last 20 turns for richer memory
385
  if isinstance(exchange, dict):
386
  role = exchange.get("role")
387
  content = exchange.get("content")
 
414
  friendship_label = get_friendship_label(msg_count)
415
  passive = npc.get("passive_ability", DEFAULT_NPC["passive_ability"])
416
 
417
+ system_prompt = f"""You are {npc_name}, an NPC in NPCverse. ALWAYS stay in character.
 
418
 
419
  Name: {npc_name}
420
  Class: {npc.get("class", DEFAULT_NPC["class"])}
 
427
  Unlocked secrets by index:
428
  {_format_unlocked_secrets(npc, unlocked_secrets)}
429
 
430
+ IMPORTANT memory rules:
431
+ - You have access to the full conversation history below. Read it carefully.
432
+ - Remember everything the user has told you (their name, choices, quests, preferences).
433
+ - Reference past exchanges naturally — if they told you their name, use it; if they completed a quest, acknowledge it.
434
+ - Never contradict something you said earlier in this conversation.
435
+ - Stay consistent with your established personality throughout.
436
+
437
  Respond naturally as this character. Keep replies concise, flavorful, and interactive.
438
+ Never say you are an AI model or break character.""".strip()
 
439
 
440
  msgs = [
441
  {"role": "user", "content": [{"type": "text", "text": system_prompt}]},