ZENLLC commited on
Commit
72693ca
·
verified ·
1 Parent(s): a8da885

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +123 -50
app.py CHANGED
@@ -1,85 +1,158 @@
1
  """
2
- A key-free Hugging Face Space chatbot built with:
3
- • microsoft/DialoGPT-small (356 MB causal-LM, perfect for free CPU)
4
- • gradio.ChatInterface (simple two-arg callback)
5
-
6
- Paste this file + requirements.txt into a new Gradio Space and press ⏵ Run.
 
 
 
 
7
  """
8
 
 
9
  import gradio as gr
10
- import torch
11
  from transformers import AutoTokenizer, AutoModelForCausalLM
12
 
13
- MODEL_NAME = "microsoft/DialoGPT-small" # swap to any open-weights causal LM
14
-
15
  # ---------------------------------------------------------------------
16
- # 1 · Load model & tokenizer
17
  # ---------------------------------------------------------------------
 
 
18
  tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
19
  model = AutoModelForCausalLM.from_pretrained(MODEL_NAME)
20
 
21
  # ---------------------------------------------------------------------
22
- # 2 · Chat callback must return *only* the reply string
23
  # ---------------------------------------------------------------------
24
- MAX_CONTEXT = 1024 # DialoGPT’s context window
25
- GEN_KWARGS = dict( # tweak to taste
26
- max_new_tokens = 120,
27
- do_sample = False, # deterministic ⇒ fewer “nonsense” tokens
28
- pad_token_id = tokenizer.eos_token_id,
29
- )
30
 
31
- def respond(message: str, history: list[list[str, str]]) -> str:
32
  """
33
- Parameters
34
- ----------
35
- message : str
36
- Latest user message.
37
- history : list[(user, bot), …]
38
- Passed in by gr.ChatInterface.
39
-
40
- Returns
41
- -------
42
- str
43
- Bot's reply (ChatInterface handles updating history UI).
44
  """
45
- # --- Build a single token sequence using DialoGPT’s EOS delimiter
46
- sequence = ""
47
- for usr, bot in history:
48
- sequence += usr + tokenizer.eos_token
49
- sequence += bot + tokenizer.eos_token
50
- sequence += message + tokenizer.eos_token
51
 
52
- input_ids = tokenizer(sequence, return_tensors="pt").input_ids
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
- # Keep only the last MAX_CONTEXT tokens so we never overflow
55
- if input_ids.shape[-1] > MAX_CONTEXT:
56
- input_ids = input_ids[:, -MAX_CONTEXT:]
 
 
 
57
 
58
- output_ids = model.generate(input_ids, **GEN_KWARGS)
 
 
 
 
 
 
 
 
 
59
 
60
- # Everything *after* the original input is the new reply
 
 
 
 
 
 
 
61
  reply_ids = output_ids[0, input_ids.shape[-1]:]
62
- reply = tokenizer.decode(reply_ids, skip_special_tokens=True).strip()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
- return reply or "…"
 
 
 
 
 
 
 
 
 
 
 
 
65
 
66
  # ---------------------------------------------------------------------
67
- # 3 · Launch UI
68
  # ---------------------------------------------------------------------
69
  demo = gr.ChatInterface(
70
  fn = respond,
71
- title = "🤖 Key-Free DialoGPT Chatbot",
72
  description = (
73
- "Runs entirely on open weights (no API keys). "
74
- "Swap `MODEL_NAME` to try any other causal-LM that fits CPU RAM."
 
 
 
 
75
  ),
 
 
76
  examples = [
77
- "Hi there!",
78
- "Give me a fun fact about Jupiter.",
79
- "Tell me a short robot joke.",
 
80
  ],
81
- theme = "soft",
82
  )
83
 
84
  if __name__ == "__main__":
85
  demo.launch()
 
 
 
1
  """
2
+ Advanced, key-free chatbot for Hugging Face Spaces
3
+ -------------------------------------------------
4
+ Features
5
+ • Natural chat with TinyLlama-1.1B-Chat (open weights, ~1 GB)
6
+ /math – secure calculator (basic math + trig/log)
7
+ • /summarize – 2-sentence TL;DR
8
+ • /translate_es – English → Spanish
9
+ • Remembers user's name inside the session
10
+ Everything runs through ONE language model, so it stays within the free CPU tier.
11
  """
12
 
13
+ import ast, math, re, gc
14
  import gradio as gr
 
15
  from transformers import AutoTokenizer, AutoModelForCausalLM
16
 
 
 
17
  # ---------------------------------------------------------------------
18
+ # 1 · Model & tokenizer (fits HF free CPU - ~1 GB RAM)
19
  # ---------------------------------------------------------------------
20
+ MODEL_NAME = "TinyLlama/TinyLlama-1.1B-Chat" # swap to any causal-LM if desired
21
+
22
  tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
23
  model = AutoModelForCausalLM.from_pretrained(MODEL_NAME)
24
 
25
  # ---------------------------------------------------------------------
26
+ # 2 · Safe-eval utility for /math
27
  # ---------------------------------------------------------------------
28
+ _ALLOWED_NAMES = {k: getattr(math, k) for k in dir(math) if not k.startswith("__")}
29
+ _ALLOWED_NAMES.update({"abs": abs, "round": round})
 
 
 
 
30
 
31
+ def safe_math_eval(expr: str) -> str:
32
  """
33
+ Evaluate math expression safely using ast.
 
 
 
 
 
 
 
 
 
 
34
  """
35
+ try:
36
+ node = ast.parse(expr, mode="eval")
 
 
 
 
37
 
38
+ def _check(node):
39
+ if isinstance(node, ast.Num): # numbers
40
+ return True
41
+ if isinstance(node, ast.BinOp): # +, -, *, /, **, etc.
42
+ return _check(node.left) and _check(node.right)
43
+ if isinstance(node, ast.UnaryOp): # -1
44
+ return _check(node.operand)
45
+ if isinstance(node, ast.Call): # sin(0.5)
46
+ return (
47
+ isinstance(node.func, ast.Name)
48
+ and node.func.id in _ALLOWED_NAMES
49
+ and all(_check(arg) for arg in node.args)
50
+ )
51
+ return False
52
 
53
+ if not _check(node.body):
54
+ return "⛔️ Expression not allowed."
55
+ result = eval(compile(node, filename="<math>", mode="eval"), {"__builtins__": {}}, _ALLOWED_NAMES)
56
+ return str(result)
57
+ except Exception as e:
58
+ return f"⚠️ Error: {e}"
59
 
60
+ # ---------------------------------------------------------------------
61
+ # 3 · Generation helper
62
+ # ---------------------------------------------------------------------
63
+ MAX_NEW_TOKENS = 160
64
+ TOKEN_LIMIT = 1024 # truncate long histories
65
+
66
+ def generate(prompt: str) -> str:
67
+ input_ids = tokenizer(prompt, return_tensors="pt").input_ids
68
+ if input_ids.shape[-1] > TOKEN_LIMIT:
69
+ input_ids = input_ids[:, -TOKEN_LIMIT:]
70
 
71
+ output_ids = model.generate(
72
+ input_ids,
73
+ max_new_tokens=MAX_NEW_TOKENS,
74
+ do_sample=True,
75
+ top_p=0.92,
76
+ temperature=0.7,
77
+ pad_token_id=tokenizer.eos_token_id,
78
+ )
79
  reply_ids = output_ids[0, input_ids.shape[-1]:]
80
+ return tokenizer.decode(reply_ids, skip_special_tokens=True).strip()
81
+
82
+ # ---------------------------------------------------------------------
83
+ # 4 · Chat callback with command routing + simple memory
84
+ # ---------------------------------------------------------------------
85
+ session_memory = {} # {session_hash: {"name": str}}
86
+
87
+ COMMAND_PAT = re.compile(r"^/(math|summarize|translate_es)\s+(.*)", re.S | re.I)
88
+
89
+ def respond(message: str, history: list[list[str, str]], session: gr.Request) -> str:
90
+ sess_id = session.session_hash or "anon"
91
+ mem = session_memory.setdefault(sess_id, {})
92
+
93
+ # -------- handle special commands --------
94
+ m = COMMAND_PAT.match(message.strip())
95
+ if m:
96
+ cmd, payload = m.group(1).lower(), m.group(2).strip()
97
+ if cmd == "math":
98
+ return safe_math_eval(payload)
99
+ elif cmd == "summarize":
100
+ prompt = (
101
+ "Summarize the following text in 2 concise sentences:\n\n"
102
+ f"{payload}\n\nSummary:"
103
+ )
104
+ return generate(prompt)
105
+ elif cmd == "translate_es":
106
+ prompt = (
107
+ "Translate the following text from English to Spanish (keep it natural):\n\n"
108
+ f"{payload}\n\nSpanish:"
109
+ )
110
+ return generate(prompt)
111
+
112
+ # -------- name capture (very lightweight memory) --------
113
+ name_match = re.search(r"\bmy name is (\w+)", message, re.I)
114
+ if name_match:
115
+ mem["name"] = name_match.group(1).capitalize()
116
 
117
+ # -------- regular chat --------
118
+ system_prompt = (
119
+ "You are ZEN-Bot, a kind, concise AI assistant for young tech pioneers."
120
+ )
121
+ if "name" in mem:
122
+ system_prompt += f" The user's name is {mem['name']}."
123
+
124
+ dialogue = system_prompt + "\n\n"
125
+ for u, b in history:
126
+ dialogue += f"User: {u}\nAssistant: {b}\n"
127
+ dialogue += f"User: {message}\nAssistant:"
128
+
129
+ return generate(dialogue)
130
 
131
  # ---------------------------------------------------------------------
132
+ # 5 · Launch Gradio ChatInterface
133
  # ---------------------------------------------------------------------
134
  demo = gr.ChatInterface(
135
  fn = respond,
136
+ title = "🛠️ ZEN-Bot Pro (Key-Free)",
137
  description = (
138
+ "**Skills**\n"
139
+ " Chat naturally\n"
140
+ "• `/math 1+2*3` – calculator\n"
141
+ "• `/summarize <text>` – 2-sentence TL;DR\n"
142
+ "• `/translate_es <text>` – English→Spanish\n\n"
143
+ "Runs on open weights (TinyLlama-1.1B-Chat) – no API keys needed."
144
  ),
145
+ fill_height = True,
146
+ theme = "soft",
147
  examples = [
148
+ "Hi, my name is Alex!",
149
+ "/math sin(0.5) ** 2 + cos(0.5) ** 2",
150
+ "/summarize The James Webb Space Telescope is the most powerful…",
151
+ "/translate_es Artificial intelligence will change the world.",
152
  ],
 
153
  )
154
 
155
  if __name__ == "__main__":
156
  demo.launch()
157
+ # Cleanup when Space shuts down
158
+ gc.collect()