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