decodingdatascience commited on
Commit
437a1c7
·
verified ·
1 Parent(s): 8363825

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +393 -0
app.py ADDED
@@ -0,0 +1,393 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import traceback
3
+ from typing import Any
4
+
5
+ import gradio as gr
6
+ from openai import OpenAI
7
+
8
+
9
+ GENERATION_MODELS = [
10
+ "gpt-4.1-mini",
11
+ "gpt-4.1",
12
+ "gpt-4o-mini",
13
+ "gpt-5.5",
14
+ ]
15
+
16
+ REASONING_MODELS = [
17
+ "gpt-5.5",
18
+ "o4-mini",
19
+ "o3-mini",
20
+ ]
21
+
22
+
23
+ def get_client() -> OpenAI | None:
24
+ """
25
+ Hugging Face Spaces exposes Secrets as environment variables.
26
+ Add your OpenAI key in Space Settings as OPENAI_API_KEY.
27
+ The lowercase fallback is included only to help during local testing.
28
+ """
29
+ api_key = os.getenv("OPENAI_API_KEY") or os.getenv("openai_api_key")
30
+ if not api_key:
31
+ return None
32
+ return OpenAI(api_key=api_key)
33
+
34
+
35
+ def extract_output_text(response: Any) -> str:
36
+ """Robustly extract text from an OpenAI Responses API response."""
37
+ output_text = getattr(response, "output_text", None)
38
+ if output_text:
39
+ return output_text.strip()
40
+
41
+ chunks: list[str] = []
42
+ for item in getattr(response, "output", []) or []:
43
+ content = getattr(item, "content", None)
44
+ if content is None and isinstance(item, dict):
45
+ content = item.get("content", [])
46
+
47
+ for part in content or []:
48
+ if isinstance(part, dict):
49
+ text = part.get("text") or part.get("output_text")
50
+ else:
51
+ text = getattr(part, "text", None) or getattr(part, "output_text", None)
52
+ if text:
53
+ chunks.append(str(text))
54
+
55
+ return "\n".join(chunks).strip() if chunks else str(response)
56
+
57
+
58
+ def is_gpt5_family(model: str) -> bool:
59
+ """
60
+ GPT-5 family models may reject custom sampling controls such as temperature.
61
+ To avoid the common 400 error, this app does not send those controls to GPT-5.x models.
62
+ """
63
+ return model.strip().lower().startswith("gpt-5")
64
+
65
+
66
+ def format_settings(title: str, settings: dict[str, Any]) -> str:
67
+ lines = [f"--- {title} ---"]
68
+ for key, value in settings.items():
69
+ lines.append(f"{key}: {value}")
70
+ lines.append("------------------------\n")
71
+ return "\n".join(lines)
72
+
73
+
74
+ def run_generation(
75
+ prompt: str,
76
+ model: str,
77
+ system_message: str,
78
+ temperature: float,
79
+ top_p: float,
80
+ max_output_tokens: int,
81
+ frequency_penalty: float,
82
+ presence_penalty: float,
83
+ show_settings: bool,
84
+ ) -> str:
85
+ client = get_client()
86
+ if client is None:
87
+ return (
88
+ "Missing API key.\n\n"
89
+ "In Hugging Face Spaces, go to Settings → Secrets and add:\n"
90
+ "Name: OPENAI_API_KEY\n"
91
+ "Value: your OpenAI API key"
92
+ )
93
+
94
+ if not prompt or not prompt.strip():
95
+ return "Please enter a prompt."
96
+
97
+ params: dict[str, Any] = {
98
+ "model": model,
99
+ "instructions": system_message or "You are a helpful assistant.",
100
+ "input": prompt,
101
+ "max_output_tokens": int(max_output_tokens),
102
+ }
103
+
104
+ settings_note = ""
105
+ if is_gpt5_family(model):
106
+ settings_note = (
107
+ "Note: GPT-5 family models can reject custom sampling controls. "
108
+ "Temperature, top_p, frequency_penalty, and presence_penalty were not sent.\n\n"
109
+ )
110
+ else:
111
+ params.update(
112
+ {
113
+ "temperature": float(temperature),
114
+ "top_p": float(top_p),
115
+ "frequency_penalty": float(frequency_penalty),
116
+ "presence_penalty": float(presence_penalty),
117
+ }
118
+ )
119
+
120
+ try:
121
+ response = client.responses.create(**params)
122
+ text = extract_output_text(response)
123
+
124
+ if show_settings:
125
+ settings = {
126
+ "model": model,
127
+ "system_message": system_message,
128
+ "max_output_tokens": max_output_tokens,
129
+ }
130
+ if is_gpt5_family(model):
131
+ settings.update(
132
+ {
133
+ "sampling_controls": "not sent for GPT-5 family model",
134
+ }
135
+ )
136
+ else:
137
+ settings.update(
138
+ {
139
+ "temperature": temperature,
140
+ "top_p": top_p,
141
+ "frequency_penalty": frequency_penalty,
142
+ "presence_penalty": presence_penalty,
143
+ }
144
+ )
145
+ return settings_note + format_settings("Generation Settings", settings) + text
146
+
147
+ return settings_note + text
148
+
149
+ except Exception as exc:
150
+ return (
151
+ "OpenAI API error:\n"
152
+ f"{exc}\n\n"
153
+ "Tip: If you selected a GPT-5 family model, try keeping generation controls at default "
154
+ "or use the Reasoning Controls tab.\n\n"
155
+ f"Technical details:\n{traceback.format_exc()}"
156
+ )
157
+
158
+
159
+ def run_reasoning(
160
+ prompt: str,
161
+ model: str,
162
+ reasoning_effort: str,
163
+ max_output_tokens: int,
164
+ show_settings: bool,
165
+ ) -> str:
166
+ client = get_client()
167
+ if client is None:
168
+ return (
169
+ "Missing API key.\n\n"
170
+ "In Hugging Face Spaces, go to Settings → Secrets and add:\n"
171
+ "Name: OPENAI_API_KEY\n"
172
+ "Value: your OpenAI API key"
173
+ )
174
+
175
+ if not prompt or not prompt.strip():
176
+ return "Please enter a prompt."
177
+
178
+ params: dict[str, Any] = {
179
+ "model": model,
180
+ "input": prompt,
181
+ "reasoning": {"effort": reasoning_effort},
182
+ "max_output_tokens": int(max_output_tokens),
183
+ }
184
+
185
+ try:
186
+ response = client.responses.create(**params)
187
+ text = extract_output_text(response)
188
+
189
+ if show_settings:
190
+ settings = {
191
+ "model": model,
192
+ "reasoning_effort": reasoning_effort,
193
+ "max_output_tokens": max_output_tokens,
194
+ "api": "OpenAI Responses API",
195
+ }
196
+ return format_settings("Reasoning Settings", settings) + text
197
+
198
+ return text
199
+
200
+ except Exception as exc:
201
+ return (
202
+ "OpenAI API error:\n"
203
+ f"{exc}\n\n"
204
+ "Tip: Make sure your account has access to the selected model, or try another model "
205
+ "from the dropdown.\n\n"
206
+ f"Technical details:\n{traceback.format_exc()}"
207
+ )
208
+
209
+
210
+ custom_css = """
211
+ .gradio-container {
212
+ max-width: 1180px !important;
213
+ margin: auto !important;
214
+ }
215
+ #main-title {
216
+ text-align: center;
217
+ }
218
+ .output-box textarea {
219
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
220
+ }
221
+ """
222
+
223
+
224
+ with gr.Blocks(
225
+ title="OpenAI LLM Controls",
226
+ theme=gr.themes.Soft(),
227
+ css=custom_css,
228
+ ) as demo:
229
+ gr.Markdown(
230
+ """
231
+ # OpenAI LLM Controls
232
+
233
+ Experiment with generation settings and reasoning effort using the OpenAI Responses API.
234
+ Add your key in Hugging Face Spaces as the secret `OPENAI_API_KEY`.
235
+ """,
236
+ elem_id="main-title",
237
+ )
238
+
239
+ with gr.Tab("Generation Controls"):
240
+ gr.Markdown(
241
+ """
242
+ Use this tab to test practical writing and completion tasks.
243
+ For GPT-5 family models, the app avoids sending custom sampling controls to prevent unsupported-parameter errors.
244
+ """
245
+ )
246
+
247
+ with gr.Row():
248
+ with gr.Column(scale=1):
249
+ gen_prompt = gr.Textbox(
250
+ lines=7,
251
+ label="Prompt",
252
+ value="Write a short LinkedIn post explaining why business leaders should learn AI. Maximum 120 words.",
253
+ )
254
+ gen_model = gr.Dropdown(
255
+ GENERATION_MODELS,
256
+ label="Model",
257
+ value="gpt-4.1-mini",
258
+ )
259
+ system_message = gr.Textbox(
260
+ lines=3,
261
+ label="System Message",
262
+ value="You are a helpful AI instructor. Keep answers clear and practical.",
263
+ )
264
+ with gr.Accordion("Advanced Generation Settings", open=True):
265
+ temperature = gr.Slider(
266
+ minimum=0.0,
267
+ maximum=2.0,
268
+ step=0.01,
269
+ value=0.7,
270
+ label="Temperature",
271
+ )
272
+ top_p = gr.Slider(
273
+ minimum=0.0,
274
+ maximum=1.0,
275
+ step=0.01,
276
+ value=1.0,
277
+ label="Top P",
278
+ )
279
+ max_output_tokens_gen = gr.Slider(
280
+ minimum=50,
281
+ maximum=4000,
282
+ step=10,
283
+ value=300,
284
+ label="Max Output Tokens",
285
+ )
286
+ frequency_penalty = gr.Slider(
287
+ minimum=-2.0,
288
+ maximum=2.0,
289
+ step=0.01,
290
+ value=0.0,
291
+ label="Frequency Penalty",
292
+ )
293
+ presence_penalty = gr.Slider(
294
+ minimum=-2.0,
295
+ maximum=2.0,
296
+ step=0.01,
297
+ value=0.0,
298
+ label="Presence Penalty",
299
+ )
300
+ show_settings_gen = gr.Checkbox(value=True, label="Show Settings")
301
+ gen_button = gr.Button("Generate", variant="primary")
302
+
303
+ with gr.Column(scale=1):
304
+ gen_output = gr.Textbox(
305
+ lines=22,
306
+ label="Output",
307
+ elem_classes=["output-box"],
308
+ show_copy_button=True,
309
+ )
310
+
311
+ gen_button.click(
312
+ fn=run_generation,
313
+ inputs=[
314
+ gen_prompt,
315
+ gen_model,
316
+ system_message,
317
+ temperature,
318
+ top_p,
319
+ max_output_tokens_gen,
320
+ frequency_penalty,
321
+ presence_penalty,
322
+ show_settings_gen,
323
+ ],
324
+ outputs=gen_output,
325
+ )
326
+
327
+ with gr.Tab("Reasoning Controls"):
328
+ gr.Markdown(
329
+ """
330
+ Use this tab for analysis, recommendations, technical trade-offs, planning, and decision-making tasks.
331
+ """
332
+ )
333
+
334
+ with gr.Row():
335
+ with gr.Column(scale=1):
336
+ reason_prompt = gr.Textbox(
337
+ lines=9,
338
+ label="Prompt",
339
+ value=(
340
+ "A telecom company wants to build an AI customer support assistant. "
341
+ "They have 50,000 past support tickets, a FAQ website, billing policies, "
342
+ "and a small developer team. Should they start with: "
343
+ "1. Simple prompt-based chatbot 2. RAG chatbot 3. Fine-tuning "
344
+ "4. Agent with tools. Give a practical recommendation with trade-offs."
345
+ ),
346
+ )
347
+ reason_model = gr.Dropdown(
348
+ REASONING_MODELS,
349
+ label="Model",
350
+ value="gpt-5.5",
351
+ )
352
+ reasoning_effort = gr.Radio(
353
+ ["low", "medium", "high"],
354
+ label="Reasoning Effort",
355
+ value="medium",
356
+ )
357
+ max_output_tokens_reason = gr.Slider(
358
+ minimum=100,
359
+ maximum=8000,
360
+ step=50,
361
+ value=900,
362
+ label="Max Output Tokens",
363
+ )
364
+ show_settings_reason = gr.Checkbox(value=True, label="Show Settings")
365
+ reason_button = gr.Button("Reason", variant="primary")
366
+
367
+ with gr.Column(scale=1):
368
+ reason_output = gr.Textbox(
369
+ lines=22,
370
+ label="Output",
371
+ elem_classes=["output-box"],
372
+ show_copy_button=True,
373
+ )
374
+
375
+ reason_button.click(
376
+ fn=run_reasoning,
377
+ inputs=[
378
+ reason_prompt,
379
+ reason_model,
380
+ reasoning_effort,
381
+ max_output_tokens_reason,
382
+ show_settings_reason,
383
+ ],
384
+ outputs=reason_output,
385
+ )
386
+
387
+
388
+ if __name__ == "__main__":
389
+ demo.queue()
390
+ demo.launch(
391
+ server_name="0.0.0.0",
392
+ server_port=int(os.getenv("PORT", "7860")),
393
+ )