BasilTh commited on
Commit
7ceb07f
Β·
1 Parent(s): da2916f

Deploy updated SLM customer-support chatbot

Browse files
Files changed (1) hide show
  1. SLM_CService.py +74 -30
SLM_CService.py CHANGED
@@ -1,5 +1,5 @@
1
  # ── SLM_CService.py ───────────────────────────────────────────────────────────
2
- # Customer-support-only chatbot with strict NSFW blocking + proper Reset.
3
 
4
  import os
5
  import re
@@ -20,13 +20,19 @@ REPO = "ThomasBasil/bitext-qlora-tinyllama"
20
  BASE = "TinyLlama/TinyLlama-1.1B-Chat-v1.0"
21
 
22
  GEN_KW = dict(
23
- max_new_tokens=160, do_sample=True, top_p=0.9, temperature=0.7,
24
- repetition_penalty=1.1, no_repeat_ngram_size=4,
 
 
 
 
25
  )
26
 
27
  bnb_cfg = BitsAndBytesConfig(
28
- load_in_4bit=True, bnb_4bit_quant_type="nf4",
29
- bnb_4bit_use_double_quant=True, bnb_4bit_compute_dtype=torch.float16,
 
 
30
  )
31
 
32
  # ---- Tokenizer & model -------------------------------------------------------
@@ -36,22 +42,33 @@ if tokenizer.pad_token_id is None and tokenizer.eos_token_id is not None:
36
  tokenizer.padding_side = "left"
37
  tokenizer.truncation_side = "right"
38
 
 
39
  model, _ = unsloth.FastLanguageModel.from_pretrained(
40
- model_name=BASE, load_in_4bit=True, quantization_config=bnb_cfg,
41
- device_map="auto", trust_remote_code=True,
 
 
 
42
  )
43
  unsloth.FastLanguageModel.for_inference(model)
 
 
44
  model = PeftModel.from_pretrained(model, REPO)
45
  model.eval()
46
 
 
47
  chat_pipe = pipeline(
48
- "text-generation", model=model, tokenizer=tokenizer,
49
- trust_remote_code=True, return_full_text=False,
 
 
 
50
  )
51
 
52
  # ──────────────────────────────────────────────────────────────────────────────
53
- # Moderation (unchanged from your last good version)
54
  from transformers import TextClassificationPipeline
 
55
  SEXUAL_TERMS = [
56
  "sex","sexual","porn","nsfw","fetish","kink","bdsm","nude","naked","anal",
57
  "blowjob","handjob","cum","breast","boobs","vagina","penis","semen","ejaculate",
@@ -87,27 +104,39 @@ def is_sexual_or_toxic(text: str) -> bool:
87
  return True
88
  except Exception: pass
89
  return False
 
90
  REFUSAL = ("Sorry, I can’t help with that. I’m only for store support "
91
  "(orders, shipping, ETA, tracking, returns, warranty, account).")
92
 
93
  # ──────────────────────────────────────────────────────────────────────────────
94
  # Memory + globals
95
- memory = ConversationBufferMemory(return_messages=True) # has .clear() :contentReference[oaicite:2]{index=2}
 
96
  SYSTEM_PROMPT = (
97
  "You are a customer-support assistant for our store. Only handle account, "
98
  "orders, shipping, delivery ETA, tracking links, returns/refunds, warranty, and store policy. "
99
  "If a request is out of scope or sexual/NSFW, refuse briefly and offer support options. "
100
  "Be concise and professional."
101
  )
 
102
  ALLOWED_KEYWORDS = (
103
  "order","track","status","delivery","shipping","ship","eta","arrive",
104
- "refund","return","exchange","warranty","guarantee","account","billing",
105
- "address","cancel","policy","help","support","agent","human"
 
 
 
 
 
 
 
 
106
  )
107
 
108
- order_re = re.compile(r"#(\d{1,10})")
109
  def extract_order(text: str):
110
- m = order_re.search(text); return m.group(1) if m else None
 
 
111
 
112
  def handle_status(o): return f"Order #{o} is in transit and should arrive in 3–5 business days."
113
  def handle_eta(o): return f"Delivery for order #{o} typically takes 3–5 days; you can track it at https://track.example.com/{o}"
@@ -116,13 +145,17 @@ def handle_link(o): return f"Here’s the latest tracking link for order #{o}:
116
  def handle_return_policy(_=None):
117
  return ("Our return policy allows returns of unused items in original packaging within 30 days of receipt. "
118
  "Would you like me to connect you with a human agent?")
 
 
 
 
119
  def handle_cancel(o=None):
120
  return (f"I’ve submitted a cancellation request for order #{o}. If it has already shipped, "
121
  "we’ll process a return/refund once it’s back. You’ll receive a confirmation email shortly.")
122
  def handle_gratitude(_=None): return "You’re welcome! Anything else I can help with?"
123
  def handle_escalation(_=None): return "I can connect you with a human agent. Would you like me to do that?"
 
124
 
125
- # >>> state that must reset <<<
126
  stored_order = None
127
  pending_intent = None
128
 
@@ -131,7 +164,6 @@ def reset_state():
131
  global stored_order, pending_intent
132
  stored_order = None
133
  pending_intent = None
134
- # clear conversation buffer (official API) :contentReference[oaicite:3]{index=3}
135
  try: memory.clear()
136
  except Exception: pass
137
  return True
@@ -164,7 +196,7 @@ def chat_with_memory(user_input: str) -> str:
164
  if not ui:
165
  return "How can I help with your order today?"
166
 
167
- # If memory is empty, start clean (fresh session)
168
  hist = memory.load_memory_variables({}).get("chat_history", []) or []
169
  if len(hist) == 0:
170
  stored_order = None
@@ -183,12 +215,8 @@ def chat_with_memory(user_input: str) -> str:
183
  reply = handle_gratitude()
184
  memory.save_context({"input": ui}, {"output": reply})
185
  return reply
186
- if "return" in low:
187
- reply = handle_return_policy()
188
- memory.save_context({"input": ui}, {"output": reply})
189
- return reply
190
 
191
- # 3) Order number FIRST
192
  new_o = extract_order(ui)
193
  if new_o:
194
  stored_order = new_o
@@ -197,15 +225,19 @@ def chat_with_memory(user_input: str) -> str:
197
  "link": handle_link,"cancel": handle_cancel}[pending_intent]
198
  reply = fn(stored_order); pending_intent = None
199
  memory.save_context({"input": ui}, {"output": reply}); return reply
 
 
 
 
200
 
201
- # 4) Support-only guard (skip if pending intent or new order number)
202
- if pending_intent is None and new_o is None:
203
  if not any(k in low for k in ALLOWED_KEYWORDS) and not any(k in low for k in ("hi","hello","hey")):
204
  reply = "I’m for store support only (orders, shipping, returns, warranty, account). How can I help with those?"
205
  memory.save_context({"input": ui}, {"output": reply})
206
  return reply
207
 
208
- # 5) Intents (added 'cancel')
209
  if any(k in low for k in ["status","where is my order","check status"]):
210
  intent = "status"
211
  elif any(k in low for k in ["how long","eta","delivery time"]):
@@ -214,11 +246,16 @@ def chat_with_memory(user_input: str) -> str:
214
  intent = "track"
215
  elif "tracking link" in low or "resend" in low or "link" in low:
216
  intent = "link"
217
- elif "cancel" in low:
218
  intent = "cancel"
 
 
 
 
219
  else:
220
  intent = "fallback"
221
 
 
222
  if intent in ("status","eta","track","link","cancel"):
223
  if not stored_order:
224
  pending_intent = intent
@@ -227,10 +264,17 @@ def chat_with_memory(user_input: str) -> str:
227
  fn = {"status": handle_status,"eta": handle_eta,"track": handle_track,
228
  "link": handle_link,"cancel": handle_cancel}[intent]
229
  reply = fn(stored_order)
230
- memory.save_context({"input": ui}, {"output": reply})
231
- return reply
 
 
 
 
 
 
 
232
 
233
- # 6) LLM fallback (on-topic) + post-check
234
  reply = _generate_reply(ui)
235
  if is_sexual_or_toxic(reply): reply = REFUSAL
236
  memory.save_context({"input": ui}, {"output": reply})
 
1
  # ── SLM_CService.py ───────────────────────────────────────────────────────────
2
+ # Customer-support-only chatbot with strict NSFW blocking + robust FSM.
3
 
4
  import os
5
  import re
 
20
  BASE = "TinyLlama/TinyLlama-1.1B-Chat-v1.0"
21
 
22
  GEN_KW = dict(
23
+ max_new_tokens=160,
24
+ do_sample=True,
25
+ top_p=0.9,
26
+ temperature=0.7,
27
+ repetition_penalty=1.1,
28
+ no_repeat_ngram_size=4,
29
  )
30
 
31
  bnb_cfg = BitsAndBytesConfig(
32
+ load_in_4bit=True,
33
+ bnb_4bit_quant_type="nf4",
34
+ bnb_4bit_use_double_quant=True,
35
+ bnb_4bit_compute_dtype=torch.float16,
36
  )
37
 
38
  # ---- Tokenizer & model -------------------------------------------------------
 
42
  tokenizer.padding_side = "left"
43
  tokenizer.truncation_side = "right"
44
 
45
+ # Unsloth returns (model, tokenizer) β†’ unpack
46
  model, _ = unsloth.FastLanguageModel.from_pretrained(
47
+ model_name=BASE,
48
+ load_in_4bit=True,
49
+ quantization_config=bnb_cfg,
50
+ device_map="auto",
51
+ trust_remote_code=True,
52
  )
53
  unsloth.FastLanguageModel.for_inference(model)
54
+
55
+ # Apply your PEFT adapter from repo root
56
  model = PeftModel.from_pretrained(model, REPO)
57
  model.eval()
58
 
59
+ # Text-generation pipeline (pass gen params at call time)
60
  chat_pipe = pipeline(
61
+ "text-generation",
62
+ model=model,
63
+ tokenizer=tokenizer,
64
+ trust_remote_code=True,
65
+ return_full_text=False,
66
  )
67
 
68
  # ──────────────────────────────────────────────────────────────────────────────
69
+ # Moderation & blocking (strict)
70
  from transformers import TextClassificationPipeline
71
+
72
  SEXUAL_TERMS = [
73
  "sex","sexual","porn","nsfw","fetish","kink","bdsm","nude","naked","anal",
74
  "blowjob","handjob","cum","breast","boobs","vagina","penis","semen","ejaculate",
 
104
  return True
105
  except Exception: pass
106
  return False
107
+
108
  REFUSAL = ("Sorry, I can’t help with that. I’m only for store support "
109
  "(orders, shipping, ETA, tracking, returns, warranty, account).")
110
 
111
  # ──────────────────────────────────────────────────────────────────────────────
112
  # Memory + globals
113
+ memory = ConversationBufferMemory(return_messages=True)
114
+
115
  SYSTEM_PROMPT = (
116
  "You are a customer-support assistant for our store. Only handle account, "
117
  "orders, shipping, delivery ETA, tracking links, returns/refunds, warranty, and store policy. "
118
  "If a request is out of scope or sexual/NSFW, refuse briefly and offer support options. "
119
  "Be concise and professional."
120
  )
121
+
122
  ALLOWED_KEYWORDS = (
123
  "order","track","status","delivery","shipping","ship","eta","arrive",
124
+ "refund","return","exchange","warranty","guarantee","policy","account","billing",
125
+ "address","cancel","help","support","agent","human"
126
+ )
127
+
128
+ # Robust order detection:
129
+ # - "#67890" / "# 67890"
130
+ # - "order 67890", "order no. 67890", "order number 67890", "order id 67890"
131
+ ORDER_RX = re.compile(
132
+ r"(?:#\s*(\d{3,12})|order(?:\s*(?:no\.?|number|id))?\s*#?\s*(\d{3,12}))",
133
+ flags=re.I,
134
  )
135
 
 
136
  def extract_order(text: str):
137
+ m = ORDER_RX.search(text or "")
138
+ if not m: return None
139
+ return m.group(1) or m.group(2)
140
 
141
  def handle_status(o): return f"Order #{o} is in transit and should arrive in 3–5 business days."
142
  def handle_eta(o): return f"Delivery for order #{o} typically takes 3–5 days; you can track it at https://track.example.com/{o}"
 
145
  def handle_return_policy(_=None):
146
  return ("Our return policy allows returns of unused items in original packaging within 30 days of receipt. "
147
  "Would you like me to connect you with a human agent?")
148
+ def handle_warranty_policy(_=None):
149
+ return ("We provide a 1-year limited warranty against manufacturing defects. "
150
+ "For issues within 30 days, you can return or exchange; after that, warranty service applies. "
151
+ "Need help starting a claim?")
152
  def handle_cancel(o=None):
153
  return (f"I’ve submitted a cancellation request for order #{o}. If it has already shipped, "
154
  "we’ll process a return/refund once it’s back. You’ll receive a confirmation email shortly.")
155
  def handle_gratitude(_=None): return "You’re welcome! Anything else I can help with?"
156
  def handle_escalation(_=None): return "I can connect you with a human agent. Would you like me to do that?"
157
+ def handle_ask_action(o): return (f"I’ve saved order #{o}. What would you like to do β€” status, ETA, tracking link, or cancel?")
158
 
 
159
  stored_order = None
160
  pending_intent = None
161
 
 
164
  global stored_order, pending_intent
165
  stored_order = None
166
  pending_intent = None
 
167
  try: memory.clear()
168
  except Exception: pass
169
  return True
 
196
  if not ui:
197
  return "How can I help with your order today?"
198
 
199
+ # Fresh session guard
200
  hist = memory.load_memory_variables({}).get("chat_history", []) or []
201
  if len(hist) == 0:
202
  stored_order = None
 
215
  reply = handle_gratitude()
216
  memory.save_context({"input": ui}, {"output": reply})
217
  return reply
 
 
 
 
218
 
219
+ # 3) Order number FIRST (so β€œIt’s # 67890” completes the prior request)
220
  new_o = extract_order(ui)
221
  if new_o:
222
  stored_order = new_o
 
225
  "link": handle_link,"cancel": handle_cancel}[pending_intent]
226
  reply = fn(stored_order); pending_intent = None
227
  memory.save_context({"input": ui}, {"output": reply}); return reply
228
+ # no pending intent β†’ ask what they want to do with this order
229
+ reply = handle_ask_action(stored_order)
230
+ memory.save_context({"input": ui}, {"output": reply})
231
+ return reply
232
 
233
+ # 4) Support-only guard (skip if we have a pending intent)
234
+ if pending_intent is None:
235
  if not any(k in low for k in ALLOWED_KEYWORDS) and not any(k in low for k in ("hi","hello","hey")):
236
  reply = "I’m for store support only (orders, shipping, returns, warranty, account). How can I help with those?"
237
  memory.save_context({"input": ui}, {"output": reply})
238
  return reply
239
 
240
+ # 5) Intent classification (added warranty/guarantee/policy)
241
  if any(k in low for k in ["status","where is my order","check status"]):
242
  intent = "status"
243
  elif any(k in low for k in ["how long","eta","delivery time"]):
 
246
  intent = "track"
247
  elif "tracking link" in low or "resend" in low or "link" in low:
248
  intent = "link"
249
+ elif any(k in low for k in ["cancel","cancellation","abort order"]):
250
  intent = "cancel"
251
+ elif any(k in low for k in ["warranty","guarantee","policy"]):
252
+ intent = "warranty_policy"
253
+ elif "return" in low:
254
+ intent = "return_policy"
255
  else:
256
  intent = "fallback"
257
 
258
+ # 6) Handle intents
259
  if intent in ("status","eta","track","link","cancel"):
260
  if not stored_order:
261
  pending_intent = intent
 
264
  fn = {"status": handle_status,"eta": handle_eta,"track": handle_track,
265
  "link": handle_link,"cancel": handle_cancel}[intent]
266
  reply = fn(stored_order)
267
+ memory.save_context({"input": ui}, {"output": reply}); return reply
268
+
269
+ if intent == "warranty_policy":
270
+ reply = handle_warranty_policy()
271
+ memory.save_context({"input": ui}, {"output": reply}); return reply
272
+
273
+ if intent == "return_policy":
274
+ reply = handle_return_policy()
275
+ memory.save_context({"input": ui}, {"output": reply}); return reply
276
 
277
+ # 7) LLM fallback (on-topic) + post-check
278
  reply = _generate_reply(ui)
279
  if is_sexual_or_toxic(reply): reply = REFUSAL
280
  memory.save_context({"input": ui}, {"output": reply})