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()