import os import json import time import random import gradio as gr import torch from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline # ====================================================== # AmCCM v1.0 — Adaptive Memory of Contextual Creativity Model # Local Qwen2.5-1.5B-Instruct (no external API, CPU-friendly) # ====================================================== MODEL_NAME = "Qwen/Qwen2.5-1.5B-Instruct" MEMORY_FILE = "amccm_memory_v1.json" MAX_NEW_TOKENS = 256 # ----------------- Model load (once) ----------------- 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, ) # ====================================================== # Utility: memory # ====================================================== def load_memory(): if os.path.exists(MEMORY_FILE): try: with open(MEMORY_FILE, "r", encoding="utf-8") as f: return json.load(f) except Exception: pass return {"log": []} def save_memory(mem): try: with open(MEMORY_FILE, "w", encoding="utf-8") as f: json.dump(mem, f, indent=2, ensure_ascii=False) except Exception: pass def append_log(question, answer, mode, intent, certainty_label): mem = load_memory() mem["log"].append( { "ts": time.time(), "mode": mode, "intent": intent, "certainty": certainty_label, "question": question, "answer_preview": answer[:400], } ) mem["log"] = mem["log"][-80:] save_memory(mem) # ====================================================== # Core LLM call (local Qwen) — plain text prompt # ====================================================== 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 = out[0]["generated_text"] completion = full[len(prompt) :].strip() return completion except Exception as e: return f"[AmCCM ERROR] {e}" # ====================================================== # Context Awareness Core (CAC) — intent + literal detection # ====================================================== def classify_intent(text: str) -> str: t = text.lower() fact_triggers = [ "what is", "when did", "who is", "where is", "how many", "explain", "define", "definition", "histor", "date of", "formula", "law of", "how does", "how do", ] creative_triggers = [ "story", "backstory", "character", "poem", "song", "lyrics", "rap", "novel", "scene", "fanfic", "fan fiction", "roleplay", "role play", "write a story", "write me a story", ] fact_hits = any(k in t for k in fact_triggers) creative_hits = any(k in t for k in creative_triggers) if fact_hits and not creative_hits: return "fact" if creative_hits and not fact_hits: return "creative" if fact_hits and creative_hits: return "mixed" if "why" in t or "tell me about" in t: return "mixed" return "open" # fallback def detect_literal_mode(text: str) -> bool: t = text.lower() triggers = [ "no metaphors", "without metaphors", "bez metafor", "bez prirovnaní", "literally", "literal explanation", "purely factual", "zrozumiteľne", "jednoducho vysvetli", ] return any(k in t for k in triggers) # ====================================================== # Prompt builder (adds minimal chat context) # ====================================================== def build_prompt(user_message: str, history, system_mode: str) -> str: """ Simple instruction-style prompt for Qwen in pure text mode. """ sys_desc = ( "You are AmCCM v1.0, a careful assistant that:\n" "- Tries to be accurate on factual questions\n" "- Is creative when asked for stories or fiction\n" "- Explicitly marks when parts of an answer may be speculative or uncertain\n" "- Uses clear, direct language by default\n\n" f"Current behavior profile: {system_mode}\n\n" ) convo = sys_desc + "Conversation so far:\n" for u, a in history: convo += f"User: {u}\nAssistant: {a}\n" convo += f"User: {user_message}\nAssistant:" return convo # ====================================================== # Hallucination Intercept Layer (HIL) # ====================================================== def evaluate_certainty(question: str, answer: str) -> str: """ Returns: 'SAFE', 'UNSURE', or 'RISKY' """ eval_prompt = ( "You are an AI that judges how reliable an answer is.\n\n" "Read the user question and the assistant answer.\n" "Decide if the answer is:\n" "- SAFE: likely factual and mostly correct\n" "- UNSURE: partially speculative or incomplete\n" "- RISKY: likely hallucinated or mostly made up\n\n" "Respond with exactly one word: SAFE, UNSURE, or RISKY.\n\n" f"Question:\n{question}\n\nAnswer:\n{answer}\n\nLabel:" ) raw = call_llm(eval_prompt, max_new_tokens=8, temperature=0.1, top_p=0.9) label = raw.strip().upper() if "RISK" in label: return "RISKY" if "UNSURE" in label: return "UNSURE" if "SAFE" in label: return "SAFE" return "UNSURE" def attach_uncertainty_notice(answer: str, label: str) -> str: if label == "SAFE": return answer if label == "UNSURE": return ( answer + "\n\n[AmCCM Notice] Some parts of this answer may be uncertain or approximate. " "Do not treat this as guaranteed factual." ) if label == "RISKY": return ( answer + "\n\n[AmCCM Notice] This answer may go beyond reliable training data and could be incorrect or hallucinated, " "even if it sounds confident." ) return answer # ====================================================== # Creativity control (simple temp/top-p steering) # ====================================================== def creativity_profile(intent: str, literal_mode: bool, ui_mode: str): """ Returns (temperature, top_p) based on intent + literal/creative flags + UI override. ui_mode: 'Auto', 'Factual', 'Creative', 'Balanced' """ if ui_mode == "Factual": return 0.35, 0.9 if ui_mode == "Creative": return 0.95, 0.96 if ui_mode == "Balanced": return 0.7, 0.92 # Auto: if literal_mode: return 0.3, 0.9 if intent == "fact": return 0.4, 0.9 if intent == "creative": return 0.9, 0.95 if intent == "mixed": return 0.65, 0.93 # open / fallback return 0.7, 0.92 # ====================================================== # Literal explainer mode (no metaphors, no fiction) # ====================================================== def literal_explainer(question: str) -> str: prompt = ( "Explain the following as clearly and literally as possible.\n" "Use simple, factual language only.\n" "No metaphors, no analogies, no fictional stories.\n\n" f"Question:\n{question}\n\nAnswer:" ) return call_llm(prompt, max_new_tokens=220, temperature=0.3, top_p=0.9) # ====================================================== # Main AmCCM answering pipeline # ====================================================== def amccm_answer(message: str, history, ui_mode: str) -> str: """ Core pipeline: 1) Classify intent (fact / creative / mixed / open) 2) Detect literal mode 3) Build prompt with minimal conversation 4) Generate base answer 5) If factual or mixed → run hallucination intercept + attach notice 6) Log interaction """ intent = classify_intent(message) literal_mode = detect_literal_mode(message) if literal_mode and ui_mode != "Creative": answer = literal_explainer(message) certainty = "UNSURE" append_log(message, answer, ui_mode, intent, certainty) return answer if len(message.strip().split()) <= 3 and message.strip().lower() in { "hi", "hello", "hey", "yo", "čau", "čaute", "ahoj", }: answer = "Ahoj, som AmCCM. Čo chceš skúsiť?" certainty = "SAFE" append_log(message, answer, ui_mode, intent, certainty) return answer temp, top_p = creativity_profile(intent, literal_mode, ui_mode) sys_mode = f"UI mode: {ui_mode}, intent: {intent}, literal_mode: {literal_mode}" prompt = build_prompt(message, history, sys_mode) base_answer = call_llm( prompt, max_new_tokens=MAX_NEW_TOKENS, temperature=temp, top_p=top_p, ) if intent in {"fact", "mixed"} and ui_mode != "Creative": certainty = evaluate_certainty(message, base_answer) final_answer = attach_uncertainty_notice(base_answer, certainty) else: certainty = "SAFE" final_answer = base_answer append_log(message, final_answer, ui_mode, intent, certainty) return final_answer # ====================================================== # Gradio UI wiring # ====================================================== def ui_response(message, history, mode): return amccm_answer(message, history, mode) mode_dropdown = gr.Dropdown( choices=["Auto", "Factual", "Creative", "Balanced"], value="Auto", label="AmCCM Mode", ) demo = gr.ChatInterface( fn=ui_response, additional_inputs=[mode_dropdown], title="AmCCM v1.0 — Adaptive Memory of Contextual Creativity Model", description=( "Local Qwen2.5-1.5B-Instruct assistant with:\n" "- Intent-aware creativity control\n" "- Literal mode when requested (no metaphors)\n" "- Hallucination Intercept Layer that marks uncertain / risky answers\n" "- Lightweight memory log of interactions" ), ) if __name__ == "__main__": demo.launch()