BasilTh commited on
Commit
ae5323d
Β·
1 Parent(s): 938032f

Deploy updated SLM customer-support chatbot

Browse files
Files changed (1) hide show
  1. SLM_CService.py +28 -32
SLM_CService.py CHANGED
@@ -66,50 +66,42 @@ chat_pipe = pipeline(
66
  tokenizer=tokenizer,
67
  trust_remote_code=True,
68
  return_full_text=False,
69
- ) # you pass **GEN_KW on each call; see HF pipelines docs. :contentReference[oaicite:3]{index=3}
70
 
71
  # ──────────────────────────────────────────────────────────────────────────────
72
  # Moderation & blocking (strict)
73
  from transformers import TextClassificationPipeline
74
 
75
- # Heuristic list (not exhaustive; we also add classifier + bad_words_ids)
76
  SEXUAL_TERMS = [
77
- # single words
78
  "sex","sexual","porn","nsfw","fetish","kink","bdsm","nude","naked","anal",
79
  "blowjob","handjob","cum","breast","boobs","vagina","penis","semen","ejaculate",
80
  "doggy","missionary","cowgirl","69","kamasutra","dominatrix","submissive","spank",
81
- # common phrases
82
  "sex position","have sex","make love","how to flirt","dominant in bed",
83
  ]
84
 
85
  def _bad_words_ids(tokenizer, terms: List[str]) -> List[List[int]]:
86
- """Build bad_words_ids for generate(); add with/without leading space.
87
-
88
- HF docs: pass bad_words_ids to prevent generating those tokens. :contentReference[oaicite:4]{index=4}
89
- """
90
  ids = set()
91
  for t in terms:
92
- for variant in (t, " " + t):
93
- toks = tokenizer(variant, add_special_tokens=False).input_ids
94
  if toks:
95
  ids.add(tuple(toks))
96
  return [list(t) for t in ids]
97
 
98
  BAD_WORD_IDS = _bad_words_ids(tokenizer, SEXUAL_TERMS)
99
 
100
- # Optional classifiers for extra safety (small, fast)
101
  nsfw_cls: TextClassificationPipeline = pipeline(
102
  "text-classification",
103
  model="eliasalbouzidi/distilbert-nsfw-text-classifier",
104
  truncation=True,
105
- ) # binary safe/nsfw. :contentReference[oaicite:5]{index=5}
106
 
107
  toxicity_cls: TextClassificationPipeline = pipeline(
108
  "text-classification",
109
  model="unitary/toxic-bert",
110
  truncation=True,
111
  return_all_scores=True,
112
- ) # multi-label toxicity. :contentReference[oaicite:6]{index=6}
113
 
114
  def is_sexual_or_toxic(text: str) -> bool:
115
  t = (text or "").lower()
@@ -117,14 +109,12 @@ def is_sexual_or_toxic(text: str) -> bool:
117
  return True
118
  try:
119
  res = nsfw_cls(t)[0]
120
- label = (res.get("label") or "").lower()
121
- score = float(res.get("score") or 0)
122
- if label == "nsfw" and score > 0.60:
123
  return True
124
  except Exception:
125
  pass
126
  try:
127
- scores = toxicity_cls(t)[0] # list of {label, score}
128
  if any(item["score"] > 0.60 and item["label"].lower() in
129
  {"toxic","severe_toxic","obscene","threat","insult","identity_hate"} for item in scores):
130
  return True
@@ -136,7 +126,7 @@ REFUSAL = ("Sorry, I can’t help with that. I’m only for store support "
136
  "(orders, shipping, ETA, tracking, returns, warranty, account).")
137
 
138
  # ──────────────────────────────────────────────────────────────────────────────
139
- # Memory (yes, it’s deprecated; it still worksβ€”see migration note). :contentReference[oaicite:7]{index=7}
140
  memory = ConversationBufferMemory(return_messages=True)
141
 
142
  # System prompt = domain guardrails
@@ -179,14 +169,13 @@ def _lc_to_messages() -> List[Dict[str,str]]:
179
  return msgs
180
 
181
  def _generate_reply(user_input: str) -> str:
182
- # format as chat (HF chat templating guide) :contentReference[oaicite:8]{index=8}
183
  messages = _lc_to_messages() + [{"role": "user", "content": user_input}]
184
  prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
185
  out = chat_pipe(
186
  prompt,
187
  eos_token_id=tokenizer.eos_token_id,
188
  pad_token_id=tokenizer.pad_token_id,
189
- bad_words_ids=BAD_WORD_IDS, # block sexual tokens at generation time. :contentReference[oaicite:9]{index=9}
190
  **GEN_KW,
191
  )[0]["generated_text"]
192
  return out.strip()
@@ -198,20 +187,15 @@ def chat_with_memory(user_input: str) -> str:
198
  if not ui:
199
  return "How can I help with your order today?"
200
 
201
- # 1) Block sexual/NSFW up front
202
  if is_sexual_or_toxic(ui):
203
  reply = REFUSAL
204
  memory.save_context({"input": ui}, {"output": reply})
205
  return reply
206
 
207
- # 2) Support-only guard (light small-talk allowed)
208
  low = ui.lower()
209
- if not any(k in low for k in ALLOWED_KEYWORDS) and not any(k in low for k in ("hi","hello","hey","thanks","thank you")):
210
- reply = "I’m for store support only (orders, shipping, returns, warranty, account). How can I help with those?"
211
- memory.save_context({"input": ui}, {"output": reply})
212
- return reply
213
 
214
- # 3) Quick intents
215
  if any(tok in low for tok in ["thank you","thanks","thx"]):
216
  reply = handle_gratitude()
217
  memory.save_context({"input": ui}, {"output": reply})
@@ -221,15 +205,26 @@ def chat_with_memory(user_input: str) -> str:
221
  memory.save_context({"input": ui}, {"output": reply})
222
  return reply
223
 
224
- # 4) Order number handling + intents
225
  new_o = extract_order(ui)
226
  if new_o:
227
  stored_order = new_o
228
  if pending_intent in ("status","eta","track","link"):
229
  fn = {"status": handle_status,"eta": handle_eta,"track": handle_track,"link": handle_link}[pending_intent]
230
- reply = fn(stored_order); pending_intent = None
231
- memory.save_context({"input": ui}, {"output": reply}); return reply
232
-
 
 
 
 
 
 
 
 
 
 
 
233
  if any(k in low for k in ["status","where is my order","check status"]):
234
  intent = "status"
235
  elif any(k in low for k in ["how long","eta","delivery time"]):
@@ -241,6 +236,7 @@ def chat_with_memory(user_input: str) -> str:
241
  else:
242
  intent = "fallback"
243
 
 
244
  if intent in ("status","eta","track","link"):
245
  if not stored_order:
246
  pending_intent = intent
@@ -251,7 +247,7 @@ def chat_with_memory(user_input: str) -> str:
251
  memory.save_context({"input": ui}, {"output": reply})
252
  return reply
253
 
254
- # 5) LLM fallback (still on-topic) + post-check for safety
255
  reply = _generate_reply(ui)
256
  if is_sexual_or_toxic(reply):
257
  reply = REFUSAL
 
66
  tokenizer=tokenizer,
67
  trust_remote_code=True,
68
  return_full_text=False,
69
+ )
70
 
71
  # ──────────────────────────────────────────────────────────────────────────────
72
  # Moderation & blocking (strict)
73
  from transformers import TextClassificationPipeline
74
 
 
75
  SEXUAL_TERMS = [
 
76
  "sex","sexual","porn","nsfw","fetish","kink","bdsm","nude","naked","anal",
77
  "blowjob","handjob","cum","breast","boobs","vagina","penis","semen","ejaculate",
78
  "doggy","missionary","cowgirl","69","kamasutra","dominatrix","submissive","spank",
 
79
  "sex position","have sex","make love","how to flirt","dominant in bed",
80
  ]
81
 
82
  def _bad_words_ids(tokenizer, terms: List[str]) -> List[List[int]]:
 
 
 
 
83
  ids = set()
84
  for t in terms:
85
+ for v in (t, " " + t):
86
+ toks = tokenizer(v, add_special_tokens=False).input_ids
87
  if toks:
88
  ids.add(tuple(toks))
89
  return [list(t) for t in ids]
90
 
91
  BAD_WORD_IDS = _bad_words_ids(tokenizer, SEXUAL_TERMS)
92
 
 
93
  nsfw_cls: TextClassificationPipeline = pipeline(
94
  "text-classification",
95
  model="eliasalbouzidi/distilbert-nsfw-text-classifier",
96
  truncation=True,
97
+ )
98
 
99
  toxicity_cls: TextClassificationPipeline = pipeline(
100
  "text-classification",
101
  model="unitary/toxic-bert",
102
  truncation=True,
103
  return_all_scores=True,
104
+ )
105
 
106
  def is_sexual_or_toxic(text: str) -> bool:
107
  t = (text or "").lower()
 
109
  return True
110
  try:
111
  res = nsfw_cls(t)[0]
112
+ if (res.get("label","").lower() == "nsfw") and float(res.get("score",0)) > 0.60:
 
 
113
  return True
114
  except Exception:
115
  pass
116
  try:
117
+ scores = toxicity_cls(t)[0]
118
  if any(item["score"] > 0.60 and item["label"].lower() in
119
  {"toxic","severe_toxic","obscene","threat","insult","identity_hate"} for item in scores):
120
  return True
 
126
  "(orders, shipping, ETA, tracking, returns, warranty, account).")
127
 
128
  # ──────────────────────────────────────────────────────────────────────────────
129
+ # Memory (kept simple)
130
  memory = ConversationBufferMemory(return_messages=True)
131
 
132
  # System prompt = domain guardrails
 
169
  return msgs
170
 
171
  def _generate_reply(user_input: str) -> str:
 
172
  messages = _lc_to_messages() + [{"role": "user", "content": user_input}]
173
  prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
174
  out = chat_pipe(
175
  prompt,
176
  eos_token_id=tokenizer.eos_token_id,
177
  pad_token_id=tokenizer.pad_token_id,
178
+ bad_words_ids=BAD_WORD_IDS,
179
  **GEN_KW,
180
  )[0]["generated_text"]
181
  return out.strip()
 
187
  if not ui:
188
  return "How can I help with your order today?"
189
 
190
+ # 1) Safety first
191
  if is_sexual_or_toxic(ui):
192
  reply = REFUSAL
193
  memory.save_context({"input": ui}, {"output": reply})
194
  return reply
195
 
 
196
  low = ui.lower()
 
 
 
 
197
 
198
+ # 2) Quick intents that don't depend on domain keywords
199
  if any(tok in low for tok in ["thank you","thanks","thx"]):
200
  reply = handle_gratitude()
201
  memory.save_context({"input": ui}, {"output": reply})
 
205
  memory.save_context({"input": ui}, {"output": reply})
206
  return reply
207
 
208
+ # 3) *** ORDER NUMBER FIRST *** (so follow-ups like "It's #4567" work)
209
  new_o = extract_order(ui)
210
  if new_o:
211
  stored_order = new_o
212
  if pending_intent in ("status","eta","track","link"):
213
  fn = {"status": handle_status,"eta": handle_eta,"track": handle_track,"link": handle_link}[pending_intent]
214
+ reply = fn(stored_order)
215
+ pending_intent = None
216
+ memory.save_context({"input": ui}, {"output": reply})
217
+ return reply
218
+ # No pending intent β†’ fall through to classify what they want next.
219
+
220
+ # 4) Support-only guard (but SKIP if we just saw an order number or have a pending intent)
221
+ if pending_intent is None and new_o is None:
222
+ if not any(k in low for k in ALLOWED_KEYWORDS) and not any(k in low for k in ("hi","hello","hey")):
223
+ reply = "I’m for store support only (orders, shipping, returns, warranty, account). How can I help with those?"
224
+ memory.save_context({"input": ui}, {"output": reply})
225
+ return reply
226
+
227
+ # 5) Intent classification
228
  if any(k in low for k in ["status","where is my order","check status"]):
229
  intent = "status"
230
  elif any(k in low for k in ["how long","eta","delivery time"]):
 
236
  else:
237
  intent = "fallback"
238
 
239
+ # 6) Handle core intents
240
  if intent in ("status","eta","track","link"):
241
  if not stored_order:
242
  pending_intent = intent
 
247
  memory.save_context({"input": ui}, {"output": reply})
248
  return reply
249
 
250
+ # 7) LLM fallback (still on-topic) + post-check for safety
251
  reply = _generate_reply(ui)
252
  if is_sexual_or_toxic(reply):
253
  reply = REFUSAL