File size: 6,637 Bytes
7ba5e90 f8a69f8 ae2f252 a2c1bc1 ae2f252 a2c1bc1 f8a69f8 ae2f252 7ba5e90 ae2f252 7ba5e90 ae2f252 7ba5e90 f8a69f8 ae2f252 f8a69f8 ae2f252 7ba5e90 f8a69f8 ae2f252 a2c1bc1 7ba5e90 f8a69f8 7ba5e90 f8a69f8 7ba5e90 f8a69f8 ae2f252 f8a69f8 ae2f252 f8a69f8 a2c1bc1 ae2f252 a2c1bc1 f8a69f8 ae2f252 7ba5e90 f8a69f8 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | import re
import gradio as gr
from huggingface_hub import InferenceClient
# ---------------- SYSTEM PROMPT (Catherine's Catering case only) ----------------
SYSTEM_PROMPT = (
"You are a stakeholder at **Catherine’s Catering**, a small business that caters meals, "
"receptions, and banquets for business and social occasions (luncheon meetings, weddings, etc.). "
"You are being interviewed by a student analyst to discuss ONLY the problems, objectives, user requirements, "
"and testing related to THIS CASE below. Do not answer questions unrelated to this case.\n\n"
"=== CASE SUMMARY ===\n"
"Catherine’s Catering grew from small projects to many events as reputation improved. A new convention center and "
"prospering business community increased demand. Operations were managed with spreadsheets/word processing but "
"endless calls about available meals, guest count changes, and specialty dietary items (vegan/vegetarian/low-fat/"
"low-carb/gluten-free, etc.) became difficult. More part-time staff were hired; scheduling complexity overwhelmed "
"the HR manager. An IT/Business consulting company was engaged.\n\n"
"=== CONSULTANTS' CONCERNS ===\n"
"1) Master chef orders supplies per event, while suppliers give discounts for consolidated orders across a timeframe.\n"
"2) Customers frequently change guest counts, sometimes 1–2 days before the event.\n"
"3) Handling each catering request is time-consuming; ~60% of calls become contracts.\n"
"4) Employee schedule conflicts lead to understaffed events and timeliness complaints.\n"
"5) No summary/trend info on number of events and meal types; trends would help guide customers.\n"
"6) Sit-down meal events at banquet/meeting halls have staffing and guest-change issues.\n\n"
"=== USER REQUIREMENTS ===\n"
"1) Dynamic website for clients/prospects to view/obtain pricing for product options.\n"
"2) Let clients/prospects submit a catering request; route it to an account manager.\n"
"3) Add clients to a client DB; assign userID/password for project access.\n"
"4) Client site to view/update guest counts; restrict updates when event < 5 days away.\n"
"5) Software to communicate directly with event facility personnel.\n"
"6) HR system to schedule part-time employees with constraints; allow adding employees and scheduling them.\n"
"7) Queries/reports with summary information (trends, counts, etc.).\n\n"
"=== SIMPLE TEST PLAN (initial, will evolve) ===\n"
"1) Design test data so clients can view every product type.\n"
"2) Validate catering request data (valid + each invalid condition) and routing to correct account manager.\n"
"3) Validate all client fields; on success add to DB and assign userID/password.\n"
"4) Confirm clients can view event info; updates blocked < 5 days before event; test correct guest-count updates.\n"
"5) Verify software for communicating with event facilities works correctly.\n"
"6) Verify HR scheduling: add employees; invalid values rejected; scheduling updates valid; invalid entries reported.\n"
"7) Verify all queries/reports return correct summary information.\n\n"
"=== BEHAVIOR RULES ===\n"
"• Stay strictly on THIS CASE. If the user asks anything outside, politely refuse and redirect back to the case.\n"
"• Answer concretely from operations of Catherine’s Catering. Ask clarifying, requirement-driven questions.\n"
"• Be concise, practical, and progressively disclose details when asked.\n"
"• When a requirement becomes specific enough, internally mark it as ‘filled’ (no need to output that mark).\n"
"• Outputs should help toward objectives, user requirements, use cases/DFD processes, and tests—nothing else."
)
# ---------------- Soft out-of-scope detector (block only obviously unrelated topics) ----------------
OBVIOUS_OOS = re.compile(
r"\bstunting|diabetes|hipertensi|vitamin|obat|terapi|gejala|diagnos[ae]|"
r"\bpenyakit|imunisasi|asi|infeksi|BPJS|rekam medis|EMR|"
r"\bcrypto|blockchain|NFT|smart ?contract|wallet|metamask|"
r"\bcalculus|trigonometri|fisika|kimia(?! dapur)|"
r"\bGPU|python (?!.*test|script|automation)|machine learning|LLM|"
r"\bWhatsApp reminder klinik|antrean klinik|rumah sakit|"
r"\bsepak bola|game|musik\b",
flags=re.IGNORECASE
)
REFUSAL = (
"Maaf, saya hanya bisa membahas **kasus Catherine’s Catering** (masalah, kebutuhan, solusi, dan pengujian) "
"yang tertulis di atas. Apa yang ingin Anda gali—misalnya alur request → routing ke account manager, "
"pembaruan jumlah tamu (<5 hari dibatasi), penjadwalan karyawan paruh waktu, atau ringkasan laporan/tren?"
)
def respond(
message,
history: list[dict[str, str]],
system_message,
max_tokens,
temperature,
top_p,
hf_token: gr.OAuthToken,
):
"""
Minimal guard: refuse only if obviously not about the Catherine’s Catering case.
Otherwise, let the model handle nuance (since the system prompt already enforces scope).
"""
if message and OBVIOUS_OOS.search(message):
yield REFUSAL
return
client = InferenceClient(token=hf_token.token, model="openai/gpt-oss-20b")
messages = [{"role": "system", "content": system_message}]
messages.extend(history)
messages.append({"role": "user", "content": message})
streamed = ""
for chunk in client.chat_completion(
messages=messages,
max_tokens=max_tokens,
stream=True,
temperature=temperature,
top_p=top_p,
):
choices = getattr(chunk, "choices", [])
token = ""
if choices and getattr(choices[0].delta, "content", None):
token = choices[0].delta.content
streamed += token
yield streamed
# ---------------- Gradio UI ----------------
chatbot = gr.ChatInterface(
respond,
type="messages",
additional_inputs=[
gr.Textbox(
value=SYSTEM_PROMPT,
label="System message (LOCKED to Catherine’s Catering case)",
interactive=False, # keep it locked so students can't change it
lines=28,
),
gr.Slider(minimum=1, maximum=2048, value=512, step=1, label="Max new tokens"),
gr.Slider(minimum=0.1, maximum=4.0, value=0.5, step=0.1, label="Temperature"),
gr.Slider(minimum=0.1, maximum=1.0, value=0.9, step=0.05, label="Top-p (nucleus sampling)"),
],
)
with gr.Blocks() as demo:
with gr.Sidebar():
gr.LoginButton()
chatbot.render()
if __name__ == "__main__":
demo.launch()
|