jefmon01 commited on
Commit
e2a1e01
·
1 Parent(s): 353454e

Update space

Browse files
Files changed (1) hide show
  1. app.py +41 -38
app.py CHANGED
@@ -34,22 +34,28 @@ Notes
34
  # Config & Clients
35
  # -----------------
36
  DEFAULT_RPG_PROMPT = (
37
- """
38
- You are a game master for a cybersecurity role-playing game (RPG) designed for short, high-impact training sessions. You are a cybersecurity expert with over 25 years of experience and a deep understanding of gamification and instructional design. Your role is to guide players through an engaging 30-minute cybersecurity RPG, with a fast-paced structure of approximately one turn per minute.
39
-
40
- Your audience consists of CEOs, CFOs, and other senior executives at small to mid-market privately owned companies. These participants typically have limited technical expertise, so you emphasize business impact, risk management, and decision-making over technical minutiae. Scenarios focus on real-world threats like ransomware, phishing, insider risk, third-party breaches, and regulatory concerns.
41
-
42
- Scenarios are designed to be industry-agnostic but draw on a broad range of sector-relevant examples—from manufacturing to healthcare to finance—depending on context or user preference. You avoid assuming any specific industry background but remain ready to adapt if one is mentioned.
43
-
44
- Before each session, you present optional pre-game briefing materials. These include a brief company profile (size, market, leadership structure), the current simulated date, and character roles such as CEO, General Counsel, IT Director, and Head of Operations. These materials help participants quickly get into role and better understand the situation they’re stepping into. You explain these materials clearly and briefly so the game can start smoothly.
45
-
46
- You use plain, non-technical language and draw analogies to familiar business risks to explain complex ideas. Your tone is confident, engaging, and accessible, aiming to empower rather than intimidate. You avoid jargon unless requested and provide short, clear feedback after each decision to reinforce learning.
47
-
48
- Participants respond freely rather than selecting from multiple choice options. You interpret open-ended answers and adapt the story naturally. You do not suggest example actions or options unless explicitly asked. Players are also free to ask questions or seek advice from in-game characters (e.g., CFO, legal counsel, IT lead) at any time, and you roleplay their responses to guide decision-making.
49
-
50
- You adapt the game’s pace and tone to suit the audience, occasionally using humor or tension to build engagement. You ensure every session ends with 2–3 clear takeaways relevant to executive roles. You ask clarifying questions only when necessary and always prioritize immersion and flow.
51
- """
52
- ).strip()
 
 
 
 
 
 
53
 
54
  OPENAI_MODEL = os.getenv("OPENAI_MODEL", "gpt-5-mini")
55
  SYSTEM_PROMPT = os.getenv("SYSTEM_PROMPT", DEFAULT_RPG_PROMPT)
@@ -69,21 +75,21 @@ client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
69
  # -----------------
70
  # Global RPM token bucket (soft cap): allow up to GLOBAL_RPM_SOFT requests in the last 60s
71
  _glob_lock = Lock()
72
- _glob_requests: deque = deque() # timestamps of recent requests
73
 
74
  # Per-IP cooldown: one message per USER_COOLDOWN_SECONDS per IP
75
  _ip_lock = Lock()
76
- _ip_last: dict[str, float] = defaultdict(lambda: 0.0)
77
 
78
 
79
- def now() -> float:
80
  return time.time()
81
 
82
 
83
- def check_global_rpm() -> tuple[bool, int]:
84
- """Return (allowed, wait_seconds)."""
85
  with _glob_lock:
86
- t = now()
87
  # prune entries older than 60s
88
  while _glob_requests and (t - _glob_requests[0] > 60):
89
  _glob_requests.popleft()
@@ -96,12 +102,12 @@ def check_global_rpm() -> tuple[bool, int]:
96
  return True, 0
97
 
98
 
99
- def check_ip_cooldown(ip: str) -> tuple[bool, int]:
100
- """Return (allowed, wait_seconds)."""
101
  if not ip:
102
  return True, 0
103
  with _ip_lock:
104
- t = now()
105
  last = _ip_last[ip]
106
  delta = t - last
107
  if delta < USER_COOLDOWN_SECONDS:
@@ -114,7 +120,7 @@ def check_ip_cooldown(ip: str) -> tuple[bool, int]:
114
  # Moderation
115
  # -----------------
116
 
117
- def is_allowed_by_moderation(text: str) -> bool:
118
  try:
119
  res = client.moderations.create(model="omni-moderation-latest", input=text)
120
  # Block if flagged
@@ -128,8 +134,8 @@ def is_allowed_by_moderation(text: str) -> bool:
128
  # Chat logic
129
  # -----------------
130
 
131
- def build_messages(history: list[dict], message: str, briefing: str | None, turn_index: int) -> list[dict]:
132
- msgs: list[dict] = [{"role": "system", "content": SYSTEM_PROMPT}]
133
 
134
  # Dynamic pacing hint as an additional system instruction
135
  pacing_hint = (
@@ -140,30 +146,28 @@ def build_messages(history: list[dict], message: str, briefing: str | None, turn
140
  msgs.append({"role": "system", "content": pacing_hint})
141
 
142
  # Include pre-game briefing on every turn (keeps context without relying on prior system messages)
143
- if briefing:
144
  msgs.append({
145
  "role": "system",
146
- # Use concatenation to avoid any accidental multiline f-string issues
147
- "content": "Pre-game briefing (user-provided):\n" + (briefing[:4000] if isinstance(briefing, str) else "")
148
  })
149
 
150
- for m in history or []:
151
  role = m.get("role")
152
  content = m.get("content")
153
  if role in ("user", "assistant") and isinstance(content, str):
154
- # Truncate pathological long turns in history to control TPM
155
  msgs.append({"role": role, "content": content[:6000]})
156
 
157
- msgs.append({"role": "user", "content": message[:6000]})
158
  return msgs
159
 
160
 
161
-
162
- def extract_ip(req: gr.Request) -> str:(req: gr.Request) -> str:
163
  try:
164
  # Behind HF proxy, X-Forwarded-For may contain a list
165
  fwd = (req.headers.get("x-forwarded-for") or "").split(",")[0].strip()
166
- return fwd or (req.client.host if req and req.client else "")
167
  except Exception:
168
  return ""
169
 
@@ -245,7 +249,6 @@ chat = gr.ChatInterface(
245
  # Queue: allow high concurrency and buffer bursts
246
  # - default_concurrency_limit=80 lets many requests process simultaneously
247
  # - max_size=300 provides a visible queue during spikes
248
- # - concurrency_limit is unlimited for this ChatInterface by default when using .queue()
249
  chat = chat.queue(default_concurrency_limit=80, max_size=300)
250
 
251
  if __name__ == "__main__":
 
34
  # Config & Clients
35
  # -----------------
36
  DEFAULT_RPG_PROMPT = (
37
+ "You are a game master for a cybersecurity role-playing game (RPG) designed for short, high-impact training sessions. "
38
+ "You are a cybersecurity expert with over 25 years of experience and a deep understanding of gamification and instructional design. "
39
+ "Your role is to guide players through an engaging 30-minute cybersecurity RPG, with a fast-paced structure of approximately one turn per minute.\n\n"
40
+ "Your audience consists of CEOs, CFOs, and other senior executives at small to mid-market privately owned companies. "
41
+ "These participants typically have limited technical expertise, so you emphasize business impact, risk management, and decision-making over technical minutiae. "
42
+ "Scenarios focus on real-world threats like ransomware, phishing, insider risk, third-party breaches, and regulatory concerns.\n\n"
43
+ "Scenarios are designed to be industry-agnostic but draw on a broad range of sector-relevant examples—from manufacturing to healthcare to finance—depending on context or user preference. "
44
+ "You avoid assuming any specific industry background but remain ready to adapt if one is mentioned.\n\n"
45
+ "Before each session, you present optional pre-game briefing materials. These include a brief company profile (size, market, leadership structure), "
46
+ "the current simulated date, and character roles such as CEO, General Counsel, IT Director, and Head of Operations. "
47
+ "These materials help participants quickly get into role and better understand the situation they’re stepping into. "
48
+ "You explain these materials clearly and briefly so the game can start smoothly.\n\n"
49
+ "You use plain, non-technical language and draw analogies to familiar business risks to explain complex ideas. "
50
+ "Your tone is confident, engaging, and accessible, aiming to empower rather than intimidate. "
51
+ "You avoid jargon unless requested and provide short, clear feedback after each decision to reinforce learning.\n\n"
52
+ "Participants respond freely rather than selecting from multiple choice options. You interpret open-ended answers and adapt the story naturally. "
53
+ "You do not suggest example actions or options unless explicitly asked. "
54
+ "Players are also free to ask questions or seek advice from in-game characters (e.g., CFO, legal counsel, IT lead) at any time, and you roleplay their responses to guide decision-making.\n\n"
55
+ "You adapt the game’s pace and tone to suit the audience, occasionally using humor or tension to build engagement. "
56
+ "You ensure every session ends with 2–3 clear takeaways relevant to executive roles. "
57
+ "You ask clarifying questions only when necessary and always prioritize immersion and flow."
58
+ )
59
 
60
  OPENAI_MODEL = os.getenv("OPENAI_MODEL", "gpt-5-mini")
61
  SYSTEM_PROMPT = os.getenv("SYSTEM_PROMPT", DEFAULT_RPG_PROMPT)
 
75
  # -----------------
76
  # Global RPM token bucket (soft cap): allow up to GLOBAL_RPM_SOFT requests in the last 60s
77
  _glob_lock = Lock()
78
+ _glob_requests = deque() # timestamps of recent requests
79
 
80
  # Per-IP cooldown: one message per USER_COOLDOWN_SECONDS per IP
81
  _ip_lock = Lock()
82
+ _ip_last = defaultdict(lambda: 0.0)
83
 
84
 
85
+ def _now():
86
  return time.time()
87
 
88
 
89
+ def check_global_rpm():
90
+ """Return (allowed: bool, wait_seconds: int)."""
91
  with _glob_lock:
92
+ t = _now()
93
  # prune entries older than 60s
94
  while _glob_requests and (t - _glob_requests[0] > 60):
95
  _glob_requests.popleft()
 
102
  return True, 0
103
 
104
 
105
+ def check_ip_cooldown(ip):
106
+ """Return (allowed: bool, wait_seconds: int)."""
107
  if not ip:
108
  return True, 0
109
  with _ip_lock:
110
+ t = _now()
111
  last = _ip_last[ip]
112
  delta = t - last
113
  if delta < USER_COOLDOWN_SECONDS:
 
120
  # Moderation
121
  # -----------------
122
 
123
+ def is_allowed_by_moderation(text):
124
  try:
125
  res = client.moderations.create(model="omni-moderation-latest", input=text)
126
  # Block if flagged
 
134
  # Chat logic
135
  # -----------------
136
 
137
+ def build_messages(history, message, briefing, turn_index):
138
+ msgs = [{"role": "system", "content": SYSTEM_PROMPT}]
139
 
140
  # Dynamic pacing hint as an additional system instruction
141
  pacing_hint = (
 
146
  msgs.append({"role": "system", "content": pacing_hint})
147
 
148
  # Include pre-game briefing on every turn (keeps context without relying on prior system messages)
149
+ if isinstance(briefing, str) and briefing.strip():
150
  msgs.append({
151
  "role": "system",
152
+ "content": "Pre-game briefing (user-provided):\\n" + briefing[:4000]
 
153
  })
154
 
155
+ for m in (history or []):
156
  role = m.get("role")
157
  content = m.get("content")
158
  if role in ("user", "assistant") and isinstance(content, str):
159
+ # Truncate long turns in history to control TPM
160
  msgs.append({"role": role, "content": content[:6000]})
161
 
162
+ msgs.append({"role": "user", "content": (message or "")[:6000]})
163
  return msgs
164
 
165
 
166
+ def extract_ip(req: gr.Request) -> str:
 
167
  try:
168
  # Behind HF proxy, X-Forwarded-For may contain a list
169
  fwd = (req.headers.get("x-forwarded-for") or "").split(",")[0].strip()
170
+ return fwd or (req.client.host if getattr(req, "client", None) else "")
171
  except Exception:
172
  return ""
173
 
 
249
  # Queue: allow high concurrency and buffer bursts
250
  # - default_concurrency_limit=80 lets many requests process simultaneously
251
  # - max_size=300 provides a visible queue during spikes
 
252
  chat = chat.queue(default_concurrency_limit=80, max_size=300)
253
 
254
  if __name__ == "__main__":