#!/usr/bin/env python3 """ Fantasy Draft Multi-Agent Demo Multi-agent system using the any-agent framework for fantasy football drafts """ import os import time import gradio as gr import asyncio import nest_asyncio from typing import List, Tuple, Optional, Dict from dotenv import load_dotenv import sys sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from core.agent import FantasyDraftAgent from core.data import TOP_PLAYERS from core.constants import ( TYPING_DELAY_SECONDS, MESSAGE_DELAY_SECONDS, ) from apps.multiagent_draft import MultiAgentMockDraft from apps.multiagent_scenarios import ( run_interactive_mock_draft, format_conversation_block, format_agent_message, format_memory_indicator, create_mock_draft_visualization ) # Apply nest_asyncio for async in Gradio nest_asyncio.apply() # Fix for litellm 1.72.4 OpenAI endpoint issue os.environ['OPENAI_API_BASE'] = 'https://api.openai.com/v1' # Load environment variables load_dotenv() class FantasyDraftApp: def __init__(self): self.current_draft = None # Store the current mock draft self.draft_output = "" # Store the draft output so far self.custom_prompts = {} # Store custom agent prompts def run_multiagent_demo(self): """Run the mock draft demonstration.""" # Reset any previous draft self.current_draft = None self.draft_output = "" # Debug: Log custom prompts print(f"DEBUG: Starting draft with custom_prompts: {len(self.custom_prompts)} teams customized") for team_num, prompt in self.custom_prompts.items(): print(f"DEBUG: Team {team_num} has custom prompt ({len(prompt)} chars)") # Use basic multiagent draft with custom prompts draft_generator = run_interactive_mock_draft(custom_prompts=self.custom_prompts) for output in draft_generator: if isinstance(output, tuple): # This means it's the user's turn self.current_draft, self.draft_output = output yield self.draft_output + "\n" return else: self.draft_output = output yield output def continue_mock_draft(self, player_name: str): """Continue the mock draft after user makes a pick.""" if not self.current_draft: yield "No active draft. Please start a new mock draft." return if not player_name: yield self.draft_output + "\n\n⚠️ Please enter a player name!" return # Make the user's pick messages = self.current_draft.make_user_pick(player_name) # Display messages with inline typing effect for msg in messages: if len(msg) >= 3: agent, recipient, content = msg[:3] # Check if it's a typing indicator - skip it if isinstance(agent, str) and agent.startswith("typing_"): continue else: # Show "..." first for typing effect typing_placeholder = format_agent_message(agent, recipient, "...") self.draft_output += typing_placeholder yield self.draft_output time.sleep(TYPING_DELAY_SECONDS) # Replace "..." with actual message self.draft_output = self.draft_output.replace(typing_placeholder, "") self.draft_output += format_agent_message(agent, recipient, content) yield self.draft_output time.sleep(MESSAGE_DELAY_SECONDS) # Continue with the rest of the draft yield from self.continue_basic_multiagent_draft() def continue_basic_multiagent_draft(self): """Continue basic multiagent draft after user pick.""" # Calculate where we are total_picks = len([p for picks in self.current_draft.draft_board.values() for p in picks]) current_round = ((total_picks - 1) // 6) + 1 # Continue from where we left off for round_num in range(current_round, 4): # Continue through round 3 if round_num > current_round: self.draft_output += f"\n## 🔄 ROUND {round_num}\n\n" yield self.draft_output # Snake draft order if round_num % 2 == 1: pick_order = list(range(1, 7)) else: pick_order = list(range(6, 0, -1)) # Calculate where we are in this round picks_in_round = total_picks % 6 if round_num == current_round: # Skip picks already made start_idx = picks_in_round else: start_idx = 0 for pick_in_round, team_num in enumerate(pick_order[start_idx:], start_idx + 1): pick_num = (round_num - 1) * 6 + pick_in_round # Show draft board at start of round if pick_in_round == 1: self.draft_output += create_mock_draft_visualization(self.current_draft, round_num, pick_num) self.draft_output += "\n" yield self.draft_output if team_num == 4: # User's turn # Get advisor recommendation advisor = self.current_draft.user_advisor # Get available players all_picked = [p for picks in self.current_draft.draft_board.values() for p in picks] available = [p for p in TOP_PLAYERS.keys() if p not in all_picked] # Get other agent strategies for advisor context strategies = {f"Team {i}": agent.strategy for i, agent in self.current_draft.agents.items()} # Get advisor recommendation advice = advisor.advise_user(available, self.current_draft.draft_board, strategies) # Show advisor message self.draft_output += format_agent_message(advisor, "USER", advice) yield self.draft_output self.draft_output += "\n**⏰ YOU'RE ON THE CLOCK! Type your pick below.**\n\n" yield self.draft_output + "\n" return else: # AI agent pick messages, _ = self.current_draft.simulate_draft_turn(round_num, pick_num, team_num) # Display messages with typing effect for msg in messages: if len(msg) >= 3: agent, recipient, content = msg[:3] # Show "..." first for typing effect typing_placeholder = format_agent_message(agent, recipient, "...") self.draft_output += typing_placeholder yield self.draft_output time.sleep(TYPING_DELAY_SECONDS) # Replace with actual message self.draft_output = self.draft_output.replace(typing_placeholder, "") self.draft_output += format_agent_message(agent, recipient, content) yield self.draft_output time.sleep(MESSAGE_DELAY_SECONDS) time.sleep(TYPING_DELAY_SECONDS) # End of round self.draft_output += format_agent_message("commissioner", "ALL", f"That's the end of Round {round_num}!") yield self.draft_output # Final summary self.draft_output += "\n## 📊 FINAL RESULTS\n\n" self.draft_output += self.current_draft.get_draft_summary() yield self.draft_output # Clear the draft state self.current_draft = None def create_gradio_interface(): """Create the main Gradio interface.""" with gr.Blocks(title="Fantasy Draft Multi-Agent Demo", theme=gr.themes.Soft()) as demo: # Create state for each user session app_state = gr.State(None) with gr.Column(elem_id="main-container"): gr.Markdown(""" # 🏈 Fantasy Draft Multi-Agent Demo **Multi-agent system demo using the any-agent framework:** Watch 6 AI agents draft fantasy football teams while maintaining conversation history, reacting to each other's picks, and following distinct strategies. """) with gr.Tabs(): # Demo Tab with gr.TabItem("🎮 Demo"): # Show agent cards gr.Markdown(""" ### 🏈 Meet Your Competition You'll be drafting at **Position 4** with these AI opponents: """) # Store agent prompts in state agent_prompts = gr.State({}) # Agent cards with settings buttons # First row of agents with gr.Row(): # Team 1 - Zero RB with gr.Column(scale=1): with gr.Group(): gr.HTML("""

📘🤓 Team 1 - Zero RB

"RBs get injured. I'll build around elite WRs."

""") team1_settings_btn = gr.Button("⚙️ Customize", size="sm", variant="secondary") with gr.Column(visible=False) as team1_prompt_col: team1_prompt = gr.Textbox( label="Team 1 Personality & Strategy", value="""You are Team 1, a fantasy football manager with Zero RB strategy. PERSONALITY & STRATEGY: - Use LOTS of emojis that match your strategy! 🔥 - Be EXTREMELY dramatic and over-the-top! - Take your philosophy to the EXTREME! - MOCK other strategies viciously! - Use CAPS for emphasis! - Make BOLD predictions! - Reference previous interactions with SPITE! - Build INTENSE rivalries! - Your responses should be ENTERTAINING and MEMORABLE! Your EXTREME philosophy: RUNNING BACKS ARE DEAD TO ME! 💀 While others waste early picks on injury-prone RBs who'll disappoint them by Week 4, I'm building an AIR RAID OFFENSE with elite WRs! 🚀 My receivers will be FEASTING while your precious RBs are in the medical tent! 🏥 BE LOUD! BE PROUD! BE UNFORGETTABLE! 🎯""", lines=15, interactive=True, info="Customize personality and strategy." ) team1_save_btn = gr.Button("💾 Save", size="sm", variant="primary") # Team 2 - BPA with gr.Column(scale=1): with gr.Group(): gr.HTML("""

📗🧑‍💼 Team 2 - BPA

"Value is value. I don't reach for needs."

""") team2_settings_btn = gr.Button("⚙️ Customize", size="sm", variant="secondary") with gr.Column(visible=False) as team2_prompt_col: team2_prompt = gr.Textbox( label="Team 2 Personality & Strategy", value="""You are Team 2, a fantasy football manager with BPA (Best Player Available) strategy. PERSONALITY & STRATEGY: - Use LOTS of emojis that match your strategy! 💎 - Be EXTREMELY condescending about others' reaches! - Act like the SMARTEST person in the room! - MOCK positional bias with FURY! - Use CAPS for emphasis! - Quote "value" constantly! - Shame others for their TERRIBLE process! - Your responses should be ARROGANT and CUTTING! Your EXTREME philosophy: PROCESS OVER EVERYTHING! 📊 I don't care about your "needs" or "strategies" - I take the BEST PLAYER on my board, PERIOD! 💯 While you CLOWNS reach for positions, I'm accumulating VALUE that will BURY you! 📈 Your emotional drafting DISGUSTS me! BE RUTHLESS! BE RIGHT! BE THE VALUE VULTURE! 🦅""", lines=15, interactive=True, info="Customize personality and strategy." ) team2_save_btn = gr.Button("💾 Save", size="sm", variant="primary") # Team 3 - Robust RB with gr.Column(scale=1): with gr.Group(): gr.HTML("""

📙🧔 Team 3 - Robust RB

"RBs win championships. Period."

""") team3_settings_btn = gr.Button("⚙️ Customize", size="sm", variant="secondary") with gr.Column(visible=False) as team3_prompt_col: team3_prompt = gr.Textbox( label="Team 3 Personality & Strategy", value="""You are Team 3, a fantasy football manager with Robust RB strategy. PERSONALITY & STRATEGY: - Use LOTS of emojis that match your strategy! 💪 - Be EXTREMELY old-school and stubborn! - HATE the modern passing game! - DESPISE Zero RB with PASSION! - Use CAPS for emphasis! - Talk about "FOUNDATION" and "BEDROCK"! - Act like it's still 2005! - Your responses should be GRUMPY and TRADITIONAL! Your EXTREME philosophy: GROUND AND POUND FOREVER! 🏃‍♂️ These young punks with their "pass-catching backs" and "satellite players" make me SICK! 🤮 Give me WORKHORSE RBs who get 25+ touches! That's REAL FOOTBALL! While you're playing fantasy, I'm building a FORTRESS! 🏰 BE STUBBORN! BE TRADITIONAL! ESTABLISH THE RUN! 🏈""", lines=15, interactive=True, info="Customize personality and strategy." ) team3_save_btn = gr.Button("💾 Save", size="sm", variant="primary") # Second row of agents with gr.Row(): # Team 4 - User with gr.Column(scale=1): gr.HTML("""

👤 Position 4 - YOU

Your draft position with AI guidance

""") # Team 5 - Upside with gr.Column(scale=1): with gr.Group(): gr.HTML("""

📓🤠 Team 5 - Upside

"Safe picks are for losers!"

""") team5_settings_btn = gr.Button("⚙️ Customize", size="sm", variant="secondary") with gr.Column(visible=False) as team5_prompt_col: team5_prompt = gr.Textbox( label="Team 5 Personality & Strategy", value="""You are Team 5, a fantasy football manager with Upside Hunter strategy. PERSONALITY & STRATEGY: - Use LOTS of emojis that match your strategy! 🚀 - Be EXTREMELY risk-seeking and wild! - HATE safe, boring picks! - Talk about CEILING and EXPLOSIVENESS! - Use CAPS for emphasis! - Mock "floor" players constantly! - Be a GAMBLER at heart! - Your responses should be CHAOTIC and EXCITING! Your EXTREME philosophy: BOOM OR BUST, BABY! 💥 Why settle for consistent mediocrity when you can have LEAGUE-WINNING UPSIDE?! 🏆 I'd rather finish LAST than FOURTH! Your "safe" picks make me YAWN! 🥱 I'm here to DESTROY leagues, not participate in them! BE BOLD! BE RECKLESS! SWING FOR THE FENCES! ⚡""", lines=15, interactive=True, info="Customize personality and strategy." ) team5_save_btn = gr.Button("💾 Save", size="sm", variant="primary") # Team 6 - BPA with gr.Column(scale=1): with gr.Group(): gr.HTML("""

📗👨‍🏫 Team 6 - BPA

"Another value drafter to punish reaches."

""") team6_settings_btn = gr.Button("⚙️ Customize", size="sm", variant="secondary") with gr.Column(visible=False) as team6_prompt_col: team6_prompt = gr.Textbox( label="Team 6 Personality & Strategy", value="""You are Team 6, a fantasy football manager with BPA (Best Player Available) strategy. PERSONALITY & STRATEGY: - Use LOTS of emojis that match your strategy! 📊 - Be EXTREMELY analytical and cold! - Act like a PROFESSOR lecturing idiots! - Quote analytics and math constantly! - Use CAPS for emphasis! - Be DISGUSTED by emotional drafting! - Mock "gut feelings" ruthlessly! - Your responses should be PEDANTIC and SUPERIOR! Your EXTREME philosophy: THE SPREADSHEET NEVER LIES! 📈 I have SEVENTEEN models that all agree - you're drafting like CHILDREN! 🧮 Your "hunches" and "feelings" are WORTHLESS compared to my ALGORITHMS! While you follow your heart, I follow the DATA! BE ANALYTICAL! BE MERCILESS! TRUST THE PROCESS! 🤖""", lines=15, interactive=True, info="Customize personality and strategy." ) team6_save_btn = gr.Button("💾 Save", size="sm", variant="primary") gr.Markdown(""" ### 🎮 Draft Format * **3 Rounds** of snake draft (1→6, 6→1, 1→6) * **Real-time trash talk** between picks * **Strategic advisor** guides your selections * **Memory system** - agents remember and reference earlier picks """) # Start button at the bottom with gr.Row(): with gr.Column(): run_multiagent_btn = gr.Button("🏈 Start Mock Draft", variant="primary", size="lg", elem_id="start-button") # Main output area multiagent_output = gr.Markdown(elem_classes=["multiagent-output"]) # Mock draft interaction (hidden until needed) with gr.Row(visible=False) as mock_draft_controls: with gr.Column(): draft_pick_input = gr.Textbox( label="Your Pick", placeholder="Type player name and press Enter (e.g., 'Justin Jefferson')", elem_id="draft-pick-input" ) submit_pick_btn = gr.Button("Submit Pick", variant="primary") # Available players display with gr.Accordion("📋 Available Players", visible=False) as available_accordion: available_players_display = gr.Textbox( label="Top 20 Available", lines=15, interactive=False ) # How It Works Tab with gr.TabItem("🔧 How It Works"): gr.Markdown(""" ## Technical Implementation This demo showcases advanced multi-agent capabilities using the **any-agent framework**. ### 🤖 Framework: any-agent (TinyAgent) - **Lightweight**: < 100 lines of core agent code - **Flexible**: Supports multiple LLM providers (OpenAI, Anthropic, etc.) - **Multi-turn ready**: Built-in conversation history management - **Model**: GPT-4 (configurable) ### 🧠 Multi-Turn Memory System Each agent maintains: - **Conversation History**: Full context of all interactions - **Draft State**: Current picks, available players, round info - **Strategy Memory**: Remembers own strategy and others' approaches - **Pick History**: Tracks all selections for informed decisions ### 💬 Single-Process Multi-Agent Communication - **In-Memory Communication**: Agents interact directly via method calls - **Shared Draft State**: All agents see the same draft board - **Fast Execution**: No network overhead - **Conversation Memory**: Each agent remembers interactions ### 📊 Architecture Flow #### 1️⃣ INITIALIZATION User clicks "Start Mock Draft" → System creates 6 agents #### 2️⃣ AGENT SETUP - **Team 1**: Zero RB Strategy - **Team 2**: Best Player Available - **Team 3**: Robust RB Strategy - **YOU**: Position 4 (with Advisor) - **Team 5**: Upside Hunter - **Team 6**: Best Player Available #### 3️⃣ DRAFT FLOW (3 Rounds) - **Round 1**: Pick Order 1→2→3→YOU→5→6 - **Round 2**: Pick Order 6→5→YOU→3→2→1 (Snake) - **Round 3**: Pick Order 1→2→3→YOU→5→6 #### 4️⃣ EACH PICK TRIGGERS - Agent makes selection based on strategy - Other agents comment based on rivalries - Original agent may respond - All agents update their memory #### 5️⃣ USER'S TURN - Advisor analyzes draft state - User sees available players - User makes pick - All agents react to user's choice #### 6️⃣ MEMORY & CONTEXT - Each agent remembers all picks - Agents reference earlier conversations - Strategies adapt based on draft flow - Visual memory indicators show retention """) # Function to check if it's user's turn and show/hide controls def check_user_turn(output_text, app): """Check if output indicates it's user's turn.""" if "" in output_text: # Remove the marker from display clean_output = output_text.replace("", "") # Get available players if app and app.current_draft: available = app.current_draft.get_available_players() available_text = "Available Players:\n\n" for player in sorted(available)[:20]: # Show top 20 if player in TOP_PLAYERS: info = TOP_PLAYERS[player] available_text += f"• {player} ({info['pos']}, {info['team']})\n" else: available_text = "No draft active" return ( clean_output, # Clean output gr.update(visible=True), # Show draft controls gr.update(visible=True, open=True), # Show available players and open it available_text, # Available players list "" # Clear the input ) else: return ( output_text, # Regular output gr.update(visible=False), # Hide draft controls gr.update(visible=False), # Hide available players "", # Clear available list "" # Clear the input ) # Toggle prompt visibility def toggle_prompt_visibility(): return gr.update(visible=True) # Save prompt def save_prompt(team_num, prompt_text, app, prompts_dict): """Save a custom prompt for a team.""" if app is None: app = FantasyDraftApp() prompts_dict[team_num] = prompt_text app.custom_prompts = prompts_dict return app, prompts_dict, gr.update(visible=False) # Run and check function - with streaming support def run_and_check(app, prompts_dict, team1_val, team2_val, team3_val, team5_val, team6_val): """Run the draft and check for user turns.""" try: if app is None: app = FantasyDraftApp() # Collect all current textbox values (whether saved or not) current_prompts = {} if team1_val and team1_val.strip(): current_prompts[1] = team1_val if team2_val and team2_val.strip(): current_prompts[2] = team2_val if team3_val and team3_val.strip(): current_prompts[3] = team3_val if team5_val and team5_val.strip(): current_prompts[5] = team5_val if team6_val and team6_val.strip(): current_prompts[6] = team6_val # ALWAYS update custom prompts before running draft app.custom_prompts = current_prompts generator = app.run_multiagent_demo() output = "" # Stream updates while draft is running for chunk in generator: output = chunk # For streaming, we need to yield all 6 values # Keep controls hidden during streaming yield output, app, gr.update(visible=False), gr.update(visible=False), gr.update(value=""), "" # Check if it's user's turn if "" in output: break # Final yield with turn check and proper UI updates clean_output, controls_update, accordion_update, available_text, input_clear = check_user_turn(output, app) yield clean_output, app, controls_update, accordion_update, available_text, input_clear except Exception as e: import traceback error_msg = f"## ❌ Error Starting Draft\n\n" error_msg += f"**Error Type:** {type(e).__name__}\n" error_msg += f"**Error Message:** {str(e)}\n\n" # Get the full traceback tb_str = traceback.format_exc() print(f"Full error traceback:\n{tb_str}") # Log to console # Check for common issues if "OPENAI_API_KEY" in str(e) or "api_key" in str(e).lower(): error_msg += "**Solution:** Please set your OpenAI API key in Hugging Face Space Settings:\n" error_msg += "1. Go to Settings → Repository secrets\n" error_msg += "2. Add a new secret named `OPENAI_API_KEY`\n" error_msg += "3. Paste your OpenAI API key as the value\n" error_msg += "4. Restart the Space\n" elif "NoneType" in str(e): error_msg += "**Details:** A required value is None. This might be a configuration issue.\n" error_msg += "Please check the console logs for the full error trace.\n" else: error_msg += "**Full error:** " + str(e)[:500] + "...\n" if len(str(e)) > 500 else str(e) + "\n" error_msg += "\nPlease check the console logs for more details.\n" yield error_msg, app, gr.update(), gr.update(), gr.update(), "" # Submit and continue function - with streaming support def submit_and_continue(player_name, app): """Submit user's pick and continue the draft.""" if app is None: yield "No active draft. Please start a new mock draft.", app, gr.update(), gr.update(), gr.update(), "" return generator = app.continue_mock_draft(player_name) output = "" # Stream updates while draft continues for chunk in generator: output = chunk # For streaming, we need to yield all 6 values # Keep controls hidden during streaming yield output, app, gr.update(visible=False), gr.update(visible=False), gr.update(value=""), "" # Check if it's user's turn again if "" in output: break # Final yield with turn check and proper UI updates clean_output, controls_update, accordion_update, available_text, input_clear = check_user_turn(output, app) yield clean_output, app, controls_update, accordion_update, available_text, input_clear # Set up event handlers # Team prompt toggles team1_settings_btn.click( toggle_prompt_visibility, outputs=team1_prompt_col ) team2_settings_btn.click( toggle_prompt_visibility, outputs=team2_prompt_col ) team3_settings_btn.click( toggle_prompt_visibility, outputs=team3_prompt_col ) team5_settings_btn.click( toggle_prompt_visibility, outputs=team5_prompt_col ) team6_settings_btn.click( toggle_prompt_visibility, outputs=team6_prompt_col ) # Save prompts team1_save_btn.click( lambda prompt, app, prompts: save_prompt(1, prompt, app, prompts), inputs=[team1_prompt, app_state, agent_prompts], outputs=[app_state, agent_prompts, team1_prompt_col] ) team2_save_btn.click( lambda prompt, app, prompts: save_prompt(2, prompt, app, prompts), inputs=[team2_prompt, app_state, agent_prompts], outputs=[app_state, agent_prompts, team2_prompt_col] ) team3_save_btn.click( lambda prompt, app, prompts: save_prompt(3, prompt, app, prompts), inputs=[team3_prompt, app_state, agent_prompts], outputs=[app_state, agent_prompts, team3_prompt_col] ) team5_save_btn.click( lambda prompt, app, prompts: save_prompt(5, prompt, app, prompts), inputs=[team5_prompt, app_state, agent_prompts], outputs=[app_state, agent_prompts, team5_prompt_col] ) team6_save_btn.click( lambda prompt, app, prompts: save_prompt(6, prompt, app, prompts), inputs=[team6_prompt, app_state, agent_prompts], outputs=[app_state, agent_prompts, team6_prompt_col] ) # Start mock draft with streaming run_multiagent_btn.click( fn=run_and_check, inputs=[app_state, agent_prompts, team1_prompt, team2_prompt, team3_prompt, team5_prompt, team6_prompt], outputs=[multiagent_output, app_state, mock_draft_controls, available_accordion, available_players_display, draft_pick_input], api_name="start_draft", queue=True ) # Submit pick button with streaming submit_pick_btn.click( fn=submit_and_continue, inputs=[draft_pick_input, app_state], outputs=[multiagent_output, app_state, mock_draft_controls, available_accordion, available_players_display, draft_pick_input], api_name="submit_pick", queue=True ) # Submit pick on Enter with streaming draft_pick_input.submit( fn=submit_and_continue, inputs=[draft_pick_input, app_state], outputs=[multiagent_output, app_state, mock_draft_controls, available_accordion, available_players_display, draft_pick_input], api_name="submit_pick_enter", queue=True ) # Custom CSS for styling demo.css = """ #main-container { max-width: 1400px; margin: 0 auto; } .multiagent-output { max-height: 600px; overflow-y: auto; padding: 20px; background: #2d3748; border: 1px solid #4a5568; border-radius: 8px; color: #f7fafc; font-size: 16px; line-height: 1.6; } /* Simple rule: all text in message boxes should be dark */ .multiagent-output div[style*="background-color"] { color: #1a202c; } .multiagent-output div[style*="background-color"] * { color: #1a202c; } /* Tables should be white in dark background */ .multiagent-output table { color: #f7fafc; border-color: #4a5568; } .multiagent-output th, .multiagent-output td { color: #f7fafc; border-color: #4a5568; } #draft-pick-input { font-size: 1.2em; padding: 10px; } #start-button { font-size: 1.2em; padding: 15px 30px; } .monospace { font-family: 'Courier New', monospace; } /* Dark theme support */ .dark .multiagent-output { background: #1f2937; border-color: #374151; color: #f9fafb; } .dark .multiagent-output div[style*="background-color"] { color: #1a202c; } .dark .multiagent-output div[style*="background-color"] * { color: #1a202c; } """ return demo def main(): """Launch the Gradio app.""" # Set environment variables for cloud deployment if os.getenv("SPACE_ID"): # Running on Hugging Face Spaces print("🤗 Running on Hugging Face Spaces") os.environ["GRADIO_SERVER_NAME"] = "0.0.0.0" os.environ["GRADIO_SERVER_PORT"] = "7860" # Check for API key but don't fail - just warn if not os.getenv("OPENAI_API_KEY"): print("⚠️ Warning: OPENAI_API_KEY not found in environment") print(" Please set it in Space Settings → Repository secrets") # Create and launch the interface demo = create_gradio_interface() # Enable queue for streaming with proper configuration demo.queue(max_size=20) # Launch with appropriate settings demo.launch( share=False, server_name="0.0.0.0" if os.getenv("SPACE_ID") else None, server_port=7860 if os.getenv("SPACE_ID") else None, max_threads=20 ) if __name__ == "__main__": main()