chatBPR / app.py
pandion's picture
Update app.py
ae2f252 verified
Raw
History Blame Contribute Delete
6.64 kB
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()