import os import json import random import gradio as gr import torch from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline # ====================================================== # ACE v6.0 — Lite-Core-Network (Phi-3 Mini local) # - No evaluator agent (fixes podcast/meta nonsense) # - Stronger meta filters # - Stronger literal mode # - Story-only narrative enforcement # - ACW + CBS kept as "creative control" core # ====================================================== MODEL_NAME = "microsoft/Phi-3-mini-4k-instruct" MEMORY_FILE = "ace_memory_v6_0.json" MAX_NEW_TOKENS = 220 # we keep this (problems 2/10 intentionally NOT solved) # -------- load model once (local, free) -------- device = 0 if torch.cuda.is_available() else -1 tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME) model = AutoModelForCausalLM.from_pretrained( MODEL_NAME, torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32, ) generator = pipeline( "text-generation", model=model, tokenizer=tokenizer, device=device, ) # ====================================================== # Prompt classifiers # ====================================================== def is_literal_mode(prompt: str) -> bool: p = prompt.lower() triggers = [ "no metaphors", "without metaphors", "bez metafor", "bez prirovnaní", "literal explanation", "explain how", "explain in simple language", "explain simply", "explain this", "how does", "how do", "technical explanation", "mechanical clock", "mechanický", "mechanical", ] return any(t in p for t in triggers) def is_short_greeting(prompt: str) -> bool: p = prompt.strip().lower() words = p.split() if len(words) <= 3 and any( w in p for w in ["hi", "hello", "hey", "yo", "sup", "čau", "čaute"] ): return True return False def is_story_mode(prompt: str) -> bool: p = prompt.lower() triggers = [ "story", "backstory", "character backstory", "short story", "scene", "narrative", "write a story", "sad but also has a good ending", "sad and ends hopeful", "tragic past", "origin story", "fantasy story", "sci-fi story", "horror story", "romantic story", "fairytale", "fairy tale", ] return any(t in p for t in triggers) def wants_hopeful_ending(prompt: str) -> bool: p = prompt.lower() triggers = [ "ends hopeful", "hopeful ending", "good ending", "ends with hope", "positive ending", ] return any(t in p for t in triggers) def contains_figurative(text: str) -> bool: t = text.lower() figurative_triggers = [ "like a", "as if", "as though", "seemed to", "resembled", "felt like", "heart of the", "soul of the", "spirit of the", "little people", "whispered to her soul", "whispered to his soul", ] return any(tr in t for tr in figurative_triggers) def has_metadata_patterns(text: str) -> bool: t = text patterns = [ "Title:", "Author:", "Publisher:", "[HOST]", "[GUEST]", "[AGENT]", "[USER]", "[ASSISTANT]", "ISBN", "Episode", "Podcast", "Top 10 tips", ] return any(pat in t for pat in patterns) def has_meta_writing(text: str) -> bool: t = text.lower() patterns = [ "to set the scene", "in this chapter", "in this story", "in your story", "as a writer", "you should", "the reader", "the audience", "this essay", "this chapter", "this section", "outline", "we will discuss", "top 10 tips", "in this episode", "in this podcast", "dear reader", "as a storyteller", "in the following story", ] return any(p in t for p in patterns) def looks_truncated(text: str) -> bool: t = text.strip() if not t: return True last = t[-1] return last not in [".", "!", "?"] def has_second_person_narration(text: str) -> bool: # catches "you / your" outside of quotes (rough heuristic) t = text.lower() return " you " in f" {t} " or " your " in f" {t} " # ====================================================== # Context Boundary Space (CBS) # ====================================================== def extract_context(prompt: str): words = [w.strip(".,!?").lower() for w in prompt.split()] keywords = {w for w in words if len(w) > 3} topic_hint = words[0] if words else "" sentiment = ( "positive" if any(w in prompt.lower() for w in ["hope", "dream", "love", "happy"]) else "negative" if any(w in prompt.lower() for w in ["fear", "pain", "loss", "sad", "hurt"]) else "neutral" ) return { "keywords": keywords, "topic_hint": topic_hint, "sentiment": sentiment, } def context_fit(text: str, ctx) -> float: penalty = 0.0 lowered = text.lower() if ctx["topic_hint"] and ctx["topic_hint"].lower() not in lowered: penalty += 0.15 # simple keyword anchor: at least some overlap if ctx["keywords"]: overlap = sum(1 for k in ctx["keywords"] if k in lowered) if overlap == 0: penalty += 0.35 elif overlap <= 2: penalty += 0.15 if ctx["sentiment"] == "positive" and any( w in lowered for w in ["kill", "die", "ruin"] ): penalty += 0.35 if ctx["sentiment"] == "negative" and any( w in lowered for w in ["cute", "joy", "happy", "celebrate"] ): penalty += 0.25 return max(0.0, 1.0 - penalty) # ====================================================== # ACW (Adaptive Creativity Window) # ====================================================== def acw_state( prompt: str, history_len: int, decay: float, literal_mode: bool, story_mode: bool ) -> int: if literal_mode: return 0 p = prompt.lower().strip() tokens = p.split() short = len(tokens) <= 4 creative_trigger = story_mode or any( kw in p for kw in [ "imagine", "poem", "invent", "creative", "world where", "dream", "weird idea", "make up", ] ) if short and not creative_trigger: return 0 entropy = random.uniform(0.45, 1.0) intensity = 0.85 if creative_trigger else 0.5 stability = 1 - decay s = 0.45 * entropy + 0.35 * intensity + 0.2 * stability if s < 0.35: return 0 elif s < 0.7: return 1 else: return 2 def mutation_settings(state: int, literal_mode: bool, story_mode: bool): if literal_mode: return {"temp": 0.3, "top_p": 0.9, "count": 1} if story_mode: if state == 0: return {"temp": 0.9, "top_p": 0.95, "count": 2} if state == 1: return {"temp": 1.05, "top_p": 0.97, "count": 3} return {"temp": 1.15, "top_p": 0.98, "count": 3} if state == 0: return {"temp": 0.55, "top_p": 0.9, "count": 2} if state == 1: return {"temp": 0.95, "top_p": 0.94, "count": 3} return {"temp": 1.1, "top_p": 0.96, "count": 3} # ====================================================== # Novelty + Twist scoring # ====================================================== def rarity(text: str) -> float: words = text.lower().split() rare = [w for w in words if len(w) > 9] return min(len(rare) / 25.0, 1.0) def metaphor_score(text: str) -> float: triggers = ["as if", "like a", "seemed to", "resembled", "as though"] return 1.0 if any(t in text.lower() for t in triggers) else 0.25 def twist(text: str, keywords) -> float: if not keywords: return 0.3 lowered = text.lower() missing = sum(1 for k in keywords if k not in lowered) return min(missing / max(1, len(keywords)), 1.0) def reward(text: str, ctx, literal_mode: bool, story_mode: bool) -> float: length_score = min(len(text) / 230.0, 1.0) novelty_score = rarity(text) imagery_score = metaphor_score(text) twist_score = twist(text, ctx["keywords"]) context_score = context_fit(text, ctx) structure_score = max(0.0, 1.0 - 0.15 * text.count("\n\n")) creativity = 0.25 * novelty_score + 0.2 * imagery_score + 0.35 * twist_score base = ( 0.2 * length_score + 0.25 * creativity + 0.35 * context_score + 0.2 * structure_score ) score = base if literal_mode: figurative_penalty = 0.9 if contains_figurative(text) else 0.0 literal_score = ( 0.4 * length_score + 0.35 * context_score + 0.25 * structure_score ) score = literal_score - figurative_penalty if story_mode: if has_meta_writing(text): score -= 1.2 if has_metadata_patterns(text): score -= 1.0 else: if has_metadata_patterns(text): score -= 0.8 # bonus: punish second-person "you"/"your" in story mode (outside dialogue) if story_mode and has_second_person_narration(text): score -= 0.6 return score # ====================================================== # Memory # ====================================================== def load_mem(): if os.path.exists(MEMORY_FILE): with open(MEMORY_FILE, "r") as f: return json.load(f) return {"scores": [], "params": []} def save_mem(mem): with open(MEMORY_FILE, "w") as f: json.dump(mem, f, indent=2) # ====================================================== # LLM call — local Phi-3 via transformers # ====================================================== def call_llm(prompt: str, max_new_tokens=MAX_NEW_TOKENS, temperature=0.7, top_p=0.9): try: out = generator( prompt, max_new_tokens=max_new_tokens, do_sample=True, temperature=temperature, top_p=top_p, pad_token_id=tokenizer.eos_token_id, ) full_text = out[0]["generated_text"] completion = full_text[len(prompt):].strip() return completion except Exception as e: return f"[ACE ERROR] {e}" # ====================================================== # Literal explainer # ====================================================== def literal_explainer(prompt: str) -> str: full_prompt = ( "Explain this as clearly, directly and literally as possible.\n" "No metaphors, no comparisons, no stories, no fictional books or podcasts.\n" "Use simple factual language only.\n\n" f"Question: {prompt}\n\nAnswer:" ) return call_llm(full_prompt, max_new_tokens=220, temperature=0.3, top_p=0.9) def sanitize_literal(text: str) -> str: if not contains_figurative(text): return text rewrite_prompt = ( "Rewrite this explanation to be 100% literal.\n" "Remove ALL metaphors, comparisons, and figurative language.\n" "Use direct, simple, factual sentences only.\n\n" f"Original explanation:\n{text}\n\nLiteral rewrite:" ) return call_llm(rewrite_prompt, max_new_tokens=200, temperature=0.25, top_p=0.9) # ====================================================== # Story prompt builder + sanitizer # ====================================================== def build_story_prompt(user_prompt: str, hopeful: bool) -> str: base = ( "You are ACE, a narrative engine.\n" "Write a continuous story as pure narrative from inside the character's world.\n" "Do NOT explain writing techniques.\n" "Do NOT mention 'the reader', 'audience', 'chapter', 'section', or 'outline'.\n" "Do NOT give advice, steps, or tips.\n" "Just tell the story.\n" ) if hopeful: base += ( "The story should begin sad but end with a clear sense of hope, healing, or a new beginning.\n" ) base += "\nStory prompt:\n" + user_prompt + "\n\nStory:\n" return base def sanitize_story(text: str) -> str: # Hard filter for meta-writing / podcast artifacts if not (has_meta_writing(text) or has_metadata_patterns(text)): return text # Try to chop off leading meta paragraphs and keep the narrative part paragraphs = [p.strip() for p in text.split("\n") if p.strip()] kept = [] for p in paragraphs: if has_meta_writing(p) or has_metadata_patterns(p): continue kept.append(p) if kept: return "\n\n".join(kept).strip() return text # ====================================================== # Explorer (single agent in v6 Lite-Core) # ====================================================== def explorer(prompt: str, temp: float, top_p: float) -> str: return call_llm(prompt, max_new_tokens=MAX_NEW_TOKENS, temperature=temp, top_p=top_p) # ====================================================== # ACE v6.0 core # ====================================================== def ace_generate(prompt: str, history, mode: str) -> str: ctx = extract_context(prompt) mem = load_mem() literal = is_literal_mode(prompt) short_greet = is_short_greeting(prompt) story_mode = is_story_mode(prompt) hopeful = wants_hopeful_ending(prompt) if mode == "Creative": literal = False if literal and mode != "Creative": base = literal_explainer(prompt) return sanitize_literal(base) if short_greet and mode != "Creative": return "Ahoj, som ACE v6.0 Lite-Core-Network. Čo chceš skúsiť?" decay = sum(mem["scores"][-5:]) / 5 if mem["scores"] else 0.0 state = acw_state(prompt, len(history), decay, literal, story_mode) if mode == "Instant": temp = 0.4 if literal else (1.0 if story_mode else 0.9) top_p = 0.95 if story_mode else 0.9 if story_mode: story_prompt = build_story_prompt(prompt, hopeful) raw = explorer(story_prompt, temp, top_p) cleaned = sanitize_story(raw) return cleaned else: return explorer(prompt, temp, top_p) if mode == "Full": settings = mutation_settings(state, literal, story_mode) use_memory = True elif mode == "Lite": settings = mutation_settings(state, literal, story_mode) settings["count"] = 1 use_memory = True elif mode == "Turbo": settings = mutation_settings(state, literal, story_mode) settings["count"] = 2 use_memory = True elif mode == "Creative": state = 2 settings = mutation_settings(state, False, True if story_mode else False) settings["count"] = max(3, settings["count"]) literal = False use_memory = True else: settings = mutation_settings(state, literal, story_mode) use_memory = True raw_candidates = [] for _ in range(settings["count"]): if story_mode: story_prompt = build_story_prompt(prompt, hopeful) text = explorer(story_prompt, settings["temp"], settings["top_p"]) else: text = explorer(prompt, settings["temp"], settings["top_p"]) raw_candidates.append(text) improved_candidates = [] for text in raw_candidates: if story_mode: text = sanitize_story(text) if literal: text = sanitize_literal(text) score = reward(text, ctx, literal, story_mode) improved_candidates.append((text, score)) if not improved_candidates: fallback_temp = 0.35 if literal else (1.0 if story_mode else 0.9) fallback_top_p = 0.95 if story_mode else 0.9 if story_mode: story_prompt = build_story_prompt(prompt, hopeful) best = explorer(story_prompt, fallback_temp, fallback_top_p) best = sanitize_story(best) else: best = explorer(prompt, fallback_temp, fallback_top_p) best_score = reward(best, ctx, literal, story_mode) else: best, best_score = max(improved_candidates, key=lambda x: x[1]) if use_memory: mem["scores"].append(best_score) mem["params"].append(settings) mem["scores"] = mem["scores"][-80:] mem["params"] = mem["params"][-80:] save_mem(mem) return best # ====================================================== # Gradio UI # ====================================================== def ui_response(msg, history, mode): return ace_generate(msg, history, mode) mode_dropdown = gr.Dropdown( choices=["Full", "Lite", "Turbo", "Instant", "Creative"], value="Full", label="ACE Mode", ) demo = gr.ChatInterface( ui_response, additional_inputs=[mode_dropdown], title="ACE v6.0 — Lite-Core-Network (Phi-3 Mini, local)", description=( "ACCF-based creative engine with CBS, ACW, Lite-Core candidate network, " "story-only narrative mode, literal mode, and meta-writing suppression. " "Running locally on microsoft/Phi-3-mini-4k-instruct (CPU)." ), ) if __name__ == "__main__": demo.launch()