import os import traceback from typing import Any import gradio as gr from openai import OpenAI GENERATION_MODELS = [ "gpt-4.1-mini", "gpt-4.1", "gpt-4o-mini", "gpt-5.5", ] REASONING_MODELS = [ "gpt-5.5", "o4-mini", "o3-mini", ] def get_client() -> OpenAI | None: """ Hugging Face Spaces exposes Secrets as environment variables. Add your OpenAI key in Space Settings as OPENAI_API_KEY. The lowercase fallback is included only to help during local testing. """ api_key = os.getenv("OPENAI_API_KEY") or os.getenv("openai_api_key") if not api_key: return None return OpenAI(api_key=api_key) def extract_output_text(response: Any) -> str: """Robustly extract text from an OpenAI Responses API response.""" output_text = getattr(response, "output_text", None) if output_text: return output_text.strip() chunks: list[str] = [] for item in getattr(response, "output", []) or []: content = getattr(item, "content", None) if content is None and isinstance(item, dict): content = item.get("content", []) for part in content or []: if isinstance(part, dict): text = part.get("text") or part.get("output_text") else: text = getattr(part, "text", None) or getattr(part, "output_text", None) if text: chunks.append(str(text)) return "\n".join(chunks).strip() if chunks else str(response) def is_gpt5_family(model: str) -> bool: """ GPT-5 family models may reject custom sampling controls such as temperature. To avoid the common 400 error, this app does not send those controls to GPT-5.x models. """ return model.strip().lower().startswith("gpt-5") def format_settings(title: str, settings: dict[str, Any]) -> str: lines = [f"--- {title} ---"] for key, value in settings.items(): lines.append(f"{key}: {value}") lines.append("------------------------\n") return "\n".join(lines) def run_generation( prompt: str, model: str, system_message: str, temperature: float, top_p: float, max_output_tokens: int, frequency_penalty: float, presence_penalty: float, show_settings: bool, ) -> str: client = get_client() if client is None: return ( "Missing API key.\n\n" "In Hugging Face Spaces, go to Settings → Secrets and add:\n" "Name: OPENAI_API_KEY\n" "Value: your OpenAI API key" ) if not prompt or not prompt.strip(): return "Please enter a prompt." params: dict[str, Any] = { "model": model, "instructions": system_message or "You are a helpful assistant.", "input": prompt, "max_output_tokens": int(max_output_tokens), } settings_note = "" if is_gpt5_family(model): settings_note = ( "Note: GPT-5 family models can reject custom sampling controls. " "Temperature, top_p, frequency_penalty, and presence_penalty were not sent.\n\n" ) else: params.update( { "temperature": float(temperature), "top_p": float(top_p), "frequency_penalty": float(frequency_penalty), "presence_penalty": float(presence_penalty), } ) try: response = client.responses.create(**params) text = extract_output_text(response) if show_settings: settings = { "model": model, "system_message": system_message, "max_output_tokens": max_output_tokens, } if is_gpt5_family(model): settings.update( { "sampling_controls": "not sent for GPT-5 family model", } ) else: settings.update( { "temperature": temperature, "top_p": top_p, "frequency_penalty": frequency_penalty, "presence_penalty": presence_penalty, } ) return settings_note + format_settings("Generation Settings", settings) + text return settings_note + text except Exception as exc: return ( "OpenAI API error:\n" f"{exc}\n\n" "Tip: If you selected a GPT-5 family model, try keeping generation controls at default " "or use the Reasoning Controls tab.\n\n" f"Technical details:\n{traceback.format_exc()}" ) def run_reasoning( prompt: str, model: str, reasoning_effort: str, max_output_tokens: int, show_settings: bool, ) -> str: client = get_client() if client is None: return ( "Missing API key.\n\n" "In Hugging Face Spaces, go to Settings → Secrets and add:\n" "Name: OPENAI_API_KEY\n" "Value: your OpenAI API key" ) if not prompt or not prompt.strip(): return "Please enter a prompt." params: dict[str, Any] = { "model": model, "input": prompt, "reasoning": {"effort": reasoning_effort}, "max_output_tokens": int(max_output_tokens), } try: response = client.responses.create(**params) text = extract_output_text(response) if show_settings: settings = { "model": model, "reasoning_effort": reasoning_effort, "max_output_tokens": max_output_tokens, "api": "OpenAI Responses API", } return format_settings("Reasoning Settings", settings) + text return text except Exception as exc: return ( "OpenAI API error:\n" f"{exc}\n\n" "Tip: Make sure your account has access to the selected model, or try another model " "from the dropdown.\n\n" f"Technical details:\n{traceback.format_exc()}" ) custom_css = """ .gradio-container { max-width: 1180px !important; margin: auto !important; } #main-title { text-align: center; } .output-box textarea { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace; } """ with gr.Blocks( title="OpenAI LLM Controls", theme=gr.themes.Soft(), css=custom_css, ) as demo: gr.Markdown( """ # OpenAI LLM Controls Experiment with generation settings and reasoning effort using the OpenAI Responses API. Add your key in Hugging Face Spaces as the secret `OPENAI_API_KEY`. """, elem_id="main-title", ) with gr.Tab("Generation Controls"): gr.Markdown( """ Use this tab to test practical writing and completion tasks. For GPT-5 family models, the app avoids sending custom sampling controls to prevent unsupported-parameter errors. """ ) with gr.Row(): with gr.Column(scale=1): gen_prompt = gr.Textbox( lines=7, label="Prompt", value="Write a short LinkedIn post explaining why business leaders should learn AI. Maximum 120 words.", ) gen_model = gr.Dropdown( GENERATION_MODELS, label="Model", value="gpt-4.1-mini", ) system_message = gr.Textbox( lines=3, label="System Message", value="You are a helpful AI instructor. Keep answers clear and practical.", ) with gr.Accordion("Advanced Generation Settings", open=True): temperature = gr.Slider( minimum=0.0, maximum=2.0, step=0.01, value=0.7, label="Temperature", ) top_p = gr.Slider( minimum=0.0, maximum=1.0, step=0.01, value=1.0, label="Top P", ) max_output_tokens_gen = gr.Slider( minimum=50, maximum=4000, step=10, value=300, label="Max Output Tokens", ) frequency_penalty = gr.Slider( minimum=-2.0, maximum=2.0, step=0.01, value=0.0, label="Frequency Penalty", ) presence_penalty = gr.Slider( minimum=-2.0, maximum=2.0, step=0.01, value=0.0, label="Presence Penalty", ) show_settings_gen = gr.Checkbox(value=True, label="Show Settings") gen_button = gr.Button("Generate", variant="primary") with gr.Column(scale=1): gen_output = gr.Textbox( lines=22, label="Output", elem_classes=["output-box"], show_copy_button=True, ) gen_button.click( fn=run_generation, inputs=[ gen_prompt, gen_model, system_message, temperature, top_p, max_output_tokens_gen, frequency_penalty, presence_penalty, show_settings_gen, ], outputs=gen_output, ) with gr.Tab("Reasoning Controls"): gr.Markdown( """ Use this tab for analysis, recommendations, technical trade-offs, planning, and decision-making tasks. """ ) with gr.Row(): with gr.Column(scale=1): reason_prompt = gr.Textbox( lines=9, label="Prompt", value=( "A telecom company wants to build an AI customer support assistant. " "They have 50,000 past support tickets, a FAQ website, billing policies, " "and a small developer team. Should they start with: " "1. Simple prompt-based chatbot 2. RAG chatbot 3. Fine-tuning " "4. Agent with tools. Give a practical recommendation with trade-offs." ), ) reason_model = gr.Dropdown( REASONING_MODELS, label="Model", value="gpt-5.5", ) reasoning_effort = gr.Radio( ["low", "medium", "high"], label="Reasoning Effort", value="medium", ) max_output_tokens_reason = gr.Slider( minimum=100, maximum=8000, step=50, value=900, label="Max Output Tokens", ) show_settings_reason = gr.Checkbox(value=True, label="Show Settings") reason_button = gr.Button("Reason", variant="primary") with gr.Column(scale=1): reason_output = gr.Textbox( lines=22, label="Output", elem_classes=["output-box"], show_copy_button=True, ) reason_button.click( fn=run_reasoning, inputs=[ reason_prompt, reason_model, reasoning_effort, max_output_tokens_reason, show_settings_reason, ], outputs=reason_output, ) if __name__ == "__main__": demo.queue() demo.launch( server_name="0.0.0.0", server_port=int(os.getenv("PORT", "7860")), )