fantasy-draft-demo / apps /app_enhanced.py
alexmec's picture
Upload folder using huggingface_hub
c9d8d4a verified
#!/usr/bin/env python3
"""
Fantasy Draft Multi-Agent Demo - Enhanced with A2A Support
Combines the superior UI from the main app with real A2A capabilities
"""
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
import os
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,
AGENT_START_DELAY,
AGENT_STARTUP_WAIT,
DEFAULT_TIMEOUT,
MAX_COMMENTS_PER_PICK,
RIVAL_PAIRS,
AGENT_CONFIGS
)
from core.a2a_helpers import (
parse_a2a_response,
extract_task_id,
format_available_players
)
# Lazy import A2A components to avoid import errors on HF Spaces
DynamicA2AAgentManager = None
cleanup_session = None
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
)
# A2A components will be imported lazily when needed
# to avoid import errors on Hugging Face Spaces
# 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 EnhancedFantasyDraftApp:
def __init__(self):
self.current_draft = None # Store the current mock draft
self.draft_output = "" # Store the draft output so far
self.a2a_manager = None # Will be created dynamically with session ID
self.use_real_a2a = False
self.a2a_status = "Not initialized"
self.session_id = None
async def toggle_a2a_mode(self, use_a2a: bool):
"""Toggle between basic multiagent and A2A modes."""
self.use_real_a2a = use_a2a
if use_a2a:
# Lazy import A2A components only when needed
try:
global DynamicA2AAgentManager, cleanup_session
from core.dynamic_a2a_manager import DynamicA2AAgentManager, cleanup_session
self.real_a2a = True
self.a2a_type = "full"
except ImportError as e:
# Fall back to simulated A2A
try:
from core.simulated_a2a_manager import SimulatedA2AAgentManager, cleanup_session
DynamicA2AAgentManager = SimulatedA2AAgentManager
self.real_a2a = False
self.a2a_type = "simulated"
print("Using simulated A2A mode (real A2A not available)")
except ImportError as e2:
self.a2a_status = f"❌ A2A mode not available: {str(e)}. Please use Basic Multiagent mode."
self.use_real_a2a = False
return self.a2a_status
# Generate unique session ID if needed
if not self.session_id:
import uuid
self.session_id = str(uuid.uuid4())[:8]
# Create new dynamic manager for this session
self.a2a_manager = DynamicA2AAgentManager(self.session_id)
try:
await self.a2a_manager.start_agents()
ports = self.a2a_manager.allocated_ports
if hasattr(self, 'a2a_type'):
if self.a2a_type == "full":
self.a2a_status = f"✅ Full A2A Mode Active (Session: {self.session_id}, Ports: {ports[0]}-{ports[-1]})"
elif self.a2a_type == "lightweight":
self.a2a_status = f"✅ Lightweight A2A Mode Active (Session: {self.session_id}, HTTP Ports: {ports[0]}-{ports[-1]})"
else: # simulated
self.a2a_status = f"✅ Simulated A2A Mode Active (Session: {self.session_id}, Mock Ports: {ports[0]}-{ports[-1]})"
else:
self.a2a_status = f"✅ A2A Mode Active (Session: {self.session_id}, Ports: {ports[0]}-{ports[-1]})"
except RuntimeError as e:
# Failed to allocate ports or start agents
self.a2a_status = f"❌ Failed to start A2A: {str(e)}"
self.use_real_a2a = False
self.a2a_manager = None
else:
if self.a2a_manager and cleanup_session:
await cleanup_session(self.a2a_manager)
self.a2a_manager = None
self.a2a_status = "✅ Basic Multiagent Mode Active (Using built-in communication)"
return self.a2a_status
def run_multiagent_demo(self, use_a2a: bool = False):
"""Run the mock draft demonstration with optional A2A support."""
# Reset any previous draft
self.current_draft = None
self.draft_output = ""
# First, set the mode (like in the working version)
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
status = loop.run_until_complete(self.toggle_a2a_mode(use_a2a))
yield f"**Mode:** {status}\n\n"
# Initialize draft
self.current_draft = MultiAgentMockDraft(user_pick_position=4)
# Run the appropriate draft
if use_a2a and self.a2a_manager:
yield from self.run_a2a_draft()
else:
# Use basic multiagent draft
draft_generator = run_interactive_mock_draft()
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<!--USER_TURN-->"
return
else:
self.draft_output = output
yield output
def run_a2a_draft(self):
"""Run draft with A2A communication."""
# Initialize draft
self.current_draft = MultiAgentMockDraft(user_pick_position=4)
self.draft_output = "# 🏈 Mock Draft with A2A Communication\n\n"
# Welcome message
if hasattr(self, 'a2a_type'):
if self.a2a_type == "full":
welcome_msg = "Welcome to the A2A-powered draft! Each agent is running on its own server with full A2A protocol."
elif self.a2a_type == "lightweight":
welcome_msg = "Welcome to the lightweight A2A draft! Each agent runs on its own HTTP server (no grpcio needed)."
else: # simulated
welcome_msg = "Welcome to the simulated A2A draft! Agents communicate using mock HTTP calls."
else:
welcome_msg = "Welcome to the A2A-powered draft! Each agent is running on its own server."
self.draft_output += format_agent_message(
"commissioner", "ALL",
welcome_msg
)
yield self.draft_output
# Run draft rounds
loop = asyncio.get_event_loop()
for round_num in range(1, 4): # 3 rounds
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))
for pick_in_round, team_num in enumerate(pick_order, 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 - use user_advisor directly
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<!--USER_TURN-->"
return
else:
# A2A agent pick
messages = loop.run_until_complete(
self.run_a2a_draft_turn(team_num, round_num, pick_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
async def run_a2a_draft_turn(self, team_num: int, round_num: int, pick_num: int):
"""Run a draft turn using A2A."""
messages = []
# Commissioner announcement
messages.append((
self.current_draft.commissioner,
"ALL",
f"Team {team_num} is on the clock!"
))
# 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 pick from A2A agent
previous_picks = self.current_draft.draft_board.get(team_num, [])
pick_result = await self.a2a_manager.get_pick(team_num, available, previous_picks, round_num)
if not pick_result or pick_result.type != "pick":
# Fallback to simulation
messages.append((
self.current_draft.commissioner,
"ALL",
f"⚠️ Team {team_num} A2A agent not responding - using simulation"
))
sim_messages, _ = self.current_draft.simulate_draft_turn(round_num, pick_num, team_num)
messages.extend(sim_messages)
return messages
# Make the pick
player = pick_result.player_name
self.current_draft.draft_board[team_num].append(player)
# Update agent's picks if it exists
agent = self.current_draft.agents.get(team_num)
if agent:
agent.picks.append(player)
# Commissioner announcement of pick
pick_num = len([p for picks in self.current_draft.draft_board.values() for p in picks])
confirm_msg = self.current_draft.commissioner.confirm_pick(
agent.team_name if agent else f"Team {team_num}",
player,
pick_num
)
messages.append((self.current_draft.commissioner, "ALL", confirm_msg))
# Agent explains reasoning
messages.append((
agent if agent else "system",
"ALL",
f"{pick_result.reasoning}"
))
if pick_result.trash_talk:
messages.append((
agent if agent else "system",
"ALL",
pick_result.trash_talk
))
# Get comments from other A2A agents (limit to 2 comments)
potential_commenters = [t for t in [1, 2, 3, 5, 6] if t != team_num and t != 4]
# Sort commenters to prioritize rivals
if team_num in RIVAL_PAIRS:
rivals = RIVAL_PAIRS[team_num]
if isinstance(rivals, int):
rivals = [rivals]
# Put rivals first in the list
prioritized_commenters = [t for t in rivals if t in potential_commenters]
prioritized_commenters.extend([t for t in potential_commenters if t not in prioritized_commenters])
potential_commenters = prioritized_commenters
# Collect comments up to the configured limit
comment_count = 0
max_comments = self.a2a_manager.max_comments_per_pick
for other_team in potential_commenters:
if comment_count >= max_comments:
break
comment = await self.a2a_manager.get_comment(other_team, team_num, player, round_num)
if comment:
other_agent = self.current_draft.agents.get(other_team)
if other_agent:
# Use the same pattern as earlier for the picking agent's name
picking_agent_name = agent.team_name if agent else f"Team {team_num}"
messages.append((
other_agent,
picking_agent_name,
comment
))
comment_count += 1
return messages
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
if self.use_real_a2a and self.a2a_manager:
yield from self.continue_a2a_draft()
else:
yield from self.continue_basic_multiagent_draft()
def continue_a2a_draft(self):
"""Continue A2A 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
loop = asyncio.get_event_loop()
# Continue from current position
for round_num in range(current_round, 4):
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 the current round
picks_in_round = total_picks % 6
start_idx = picks_in_round if round_num == current_round else 0
for pick_in_round, team_num in enumerate(list(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 again
# Get advisor recommendation - use user_advisor directly
advisor = self.current_draft.user_advisor
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()}
advice = advisor.advise_user(available, self.current_draft.draft_board, strategies)
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<!--USER_TURN-->"
return
else:
# A2A agent pick
messages = loop.run_until_complete(
self.run_a2a_draft_turn(team_num, round_num, pick_num)
)
for msg in messages:
if len(msg) >= 3:
agent, recipient, content = msg[:3]
typing_placeholder = format_agent_message(agent, recipient, "...")
self.draft_output += typing_placeholder
yield self.draft_output
time.sleep(TYPING_DELAY_SECONDS)
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)
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
self.current_draft = None
def continue_basic_multiagent_draft(self):
"""Continue basic multiagent draft after user pick."""
# This is the original logic from app.py
total_picks = len([p for picks in self.current_draft.draft_board.values() for p in picks])
current_round = ((total_picks - 1) // 6) + 1
draft_memories = []
for round_num in range(current_round, 4):
if round_num > current_round:
self.draft_output += f"\n## 🔄 ROUND {round_num}\n\n"
yield self.draft_output
if round_num % 2 == 1:
pick_order = list(range(1, 7))
else:
pick_order = list(range(6, 0, -1))
picks_in_round = total_picks % 6
start_idx = picks_in_round if round_num == current_round else 0
for pick_in_round, team_num in enumerate(list(pick_order)[start_idx:], start_idx + 1):
pick_num = (round_num - 1) * 6 + pick_in_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
messages, result = self.current_draft.simulate_draft_turn(round_num, pick_num, team_num)
for msg in messages:
if len(msg) >= 3:
agent, recipient, content = msg[:3]
if isinstance(agent, str) and agent.startswith("typing_"):
continue
else:
typing_placeholder = format_agent_message(agent, recipient, "...")
self.draft_output += typing_placeholder
yield self.draft_output
time.sleep(TYPING_DELAY_SECONDS)
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)
if result is None:
self.draft_output += "\n**⏰ YOU'RE ON THE CLOCK! Type your pick below.**\n\n"
yield self.draft_output + "\n<!--USER_TURN-->"
return
if round_num > 1 and pick_in_round % 2 == 0:
if team_num in self.current_draft.agents:
agent = self.current_draft.agents[team_num]
if len(agent.picks) > 1:
memory = f"{agent.team_name} has drafted: {', '.join(agent.picks)}"
draft_memories.append(memory)
if draft_memories:
self.draft_output += format_memory_indicator(round_num, draft_memories[-2:])
yield self.draft_output
time.sleep(TYPING_DELAY_SECONDS)
self.draft_output += format_agent_message("commissioner", "ALL",
f"That's the end of Round {round_num}!")
yield self.draft_output
self.draft_output += "\n## 📊 FINAL RESULTS\n\n"
self.draft_output += self.current_draft.get_draft_summary()
yield self.draft_output
self.current_draft = None
def create_gradio_interface():
"""Create the main Gradio interface with A2A support."""
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"):
# Add A2A Mode Toggle
with gr.Row():
with gr.Column():
gr.Markdown("### 🔧 Communication Mode")
communication_mode = gr.Radio(
["Basic Multiagent", "A2A"],
value="Basic Multiagent",
label="Select how agents communicate",
info="Basic Multiagent: Fast, reliable (Recommended) | A2A: Distributed agents (Advanced)"
)
mode_info = gr.Markdown(
"""
**Basic Multiagent** (Recommended): Fast, single-process execution (✅ Multi-user safe)
**A2A**: Distributed agents on HTTP servers (requires a2a-sdk package)
*If A2A mode fails to start, please use Basic Multiagent mode.*
"""
)
# Add A2A test button for debugging
with gr.Accordion("🔧 A2A Debugging (Advanced)", open=False):
test_a2a_btn = gr.Button("Test A2A Dependencies & Ports", size="sm")
a2a_test_output = gr.Textbox(label="Test Results", lines=10, interactive=False)
# Show agent cards
gr.Markdown("""
### 🏈 Meet Your Competition
You'll be drafting at **Position 4** with these AI opponents:
""")
# Agent cards in a grid - all in one row
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("""
<div style="background-color: #E3F2FD; border-left: 4px solid #1976D2; padding: 15px; border-radius: 8px;">
<h4 style="color: #0d47a1; margin: 0 0 10px 0;">📘🤓 Team 1 - Zero RB</h4>
<p style="color: #424242; font-style: italic; margin: 10px 0; font-size: 0.95em;">"RBs get injured. I'll build around elite WRs."</p>
<ul style="color: #424242; font-size: 0.9em; margin: 0; padding-left: 20px;">
<li style="color: #424242;">Avoids RBs early</li>
<li style="color: #424242;">Loads up on WRs</li>
<li style="color: #424242;">Gets RB value late</li>
</ul>
</div>
""")
with gr.Column(scale=1):
gr.Markdown("""
<div style="background-color: #E8F5E9; border-left: 4px solid #388E3C; padding: 15px; border-radius: 8px;">
<h4 style="color: #1b5e20; margin: 0 0 10px 0;">📗🧑‍💼 Team 2 - BPA</h4>
<p style="color: #424242; font-style: italic; margin: 10px 0; font-size: 0.95em;">"Value is value. I don't reach for needs."</p>
<ul style="color: #424242; font-size: 0.9em; margin: 0; padding-left: 20px;">
<li style="color: #424242;">Pure value drafting</li>
<li style="color: #424242;">Ignores needs</li>
<li style="color: #424242;">Mocks reaching</li>
</ul>
</div>
""")
with gr.Column(scale=1):
gr.Markdown("""
<div style="background-color: #FFF3E0; border-left: 4px solid #F57C00; padding: 15px; border-radius: 8px;">
<h4 style="color: #e65100; margin: 0 0 10px 0;">📙🧔 Team 3 - Robust RB</h4>
<p style="color: #424242; font-style: italic; margin: 10px 0; font-size: 0.95em;">"RBs win championships. Period."</p>
<ul style="color: #424242; font-size: 0.9em; margin: 0; padding-left: 20px;">
<li style="color: #424242;">RBs in rounds 1-2</li>
<li style="color: #424242;">Old-school approach</li>
<li style="color: #424242;">Foundation first</li>
</ul>
</div>
""")
with gr.Column(scale=1):
gr.Markdown("""
<div style="background-color: #E8EAF6; border-left: 4px solid #3F51B5; padding: 15px; border-radius: 8px;">
<h4 style="color: #1a237e; margin: 0 0 10px 0;">👤 Position 4 - YOU</h4>
<p style="color: #424242; font-style: italic; margin: 10px 0; font-size: 0.95em;">Your draft position with AI guidance</p>
<ul style="color: #424242; font-size: 0.9em; margin: 0; padding-left: 20px;">
<li style="color: #424242;">📕🧙 Strategic advisor</li>
<li style="color: #424242;">Real-time guidance</li>
<li style="color: #424242;">Roster analysis</li>
</ul>
</div>
""")
with gr.Column(scale=1):
gr.Markdown("""
<div style="background-color: #F5E6FF; border-left: 4px solid #7B1FA2; padding: 15px; border-radius: 8px;">
<h4 style="color: #4a148c; margin: 0 0 10px 0;">📓🤠 Team 5 - Upside</h4>
<p style="color: #424242; font-style: italic; margin: 10px 0; font-size: 0.95em;">"Safe picks are for losers!"</p>
<ul style="color: #424242; font-size: 0.9em; margin: 0; padding-left: 20px;">
<li style="color: #424242;">Seeks breakouts</li>
<li style="color: #424242;">High risk/reward</li>
<li style="color: #424242;">Mocks safety</li>
</ul>
</div>
""")
with gr.Column(scale=1):
gr.Markdown("""
<div style="background-color: #E8F5E9; border-left: 4px solid #388E3C; padding: 15px; border-radius: 8px;">
<h4 style="color: #1b5e20; margin: 0 0 10px 0;">📗👨‍🏫 Team 6 - BPA</h4>
<p style="color: #424242; font-style: italic; margin: 10px 0; font-size: 0.95em;">"Another value drafter to punish reaches."</p>
<ul style="color: #424242; font-size: 0.9em; margin: 0; padding-left: 20px;">
<li style="color: #424242;">Takes obvious value</li>
<li style="color: #424242;">Disciplined approach</li>
<li style="color: #424242;">No sentiment</li>
</ul>
</div>
""")
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
Ready to experience the most realistic AI draft room?
""")
# 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
### 💬 Agent-to-Agent (A2A) Communication
**Two Modes Available:**
#### 1. A2A Mode (Default)
- **Distributed Architecture**: Each agent runs on its own HTTP server
- **Dynamic Ports**: Each session gets unique ports automatically
- **True Isolation**: No shared memory, HTTP communication only
- **Production Ready**: Scalable to multiple machines
- **Uses a2a_tool_async**: Official any-agent A2A protocol
#### 2. Basic Multiagent Mode
- Single process, direct method calls
- Shared memory between agents
- Fast execution, simple debugging
- Perfect for quick testing and development
### 📊 Architecture Flow
""")
gr.Markdown("""
#### 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 (A2A communication)
- 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
""")
gr.Markdown("""
### 🎯 Key Features Demonstrated
1. **Persistent Context**: Each agent remembers all previous interactions
2. **Strategic Personalities**: 5 distinct draft strategies competing
3. **Dynamic Adaptation**: Agents adjust based on draft progression
4. **Natural Dialogue**: Human-like commentary and debates
5. **User Integration**: Seamless human participation with AI guidance
6. **A2A Communication**: Toggle between basic multiagent and distributed A2A modes
### 📝 Implementation Details
- **Agent Classes**: Inheritance-based design with base `DraftAgent`
- **Message Formatting**: Custom HTML/CSS for visual distinction
- **State Management**: Draft board tracking and validation
- **Memory Indicators**: Visual cues showing context retention
- **A2A Protocol**: Uses any-agent's a2a_tool_async for distributed communication
### 🚀 Why This Matters
This demo proves that sophisticated multi-agent systems can be built with minimal code,
showcasing the power of modern LLMs when properly orchestrated. The any-agent framework
makes it easy to create agents that truly communicate and remember, not just respond.
The A2A mode demonstrates how the same agent logic can seamlessly transition from
a simple in-memory simulation to a production-ready distributed system.
""")
# 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 "<!--USER_TURN-->" in output_text:
# Remove the marker from display
clean_output = output_text.replace("<!--USER_TURN-->", "")
# 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
)
# Test A2A functionality
def test_a2a_functionality():
"""Test A2A dependencies and port availability."""
import socket
import subprocess
import importlib.util
import site
test_results = []
# 1. Python Environment
test_results.append("=== Python Environment ===")
test_results.append(f"Python: {sys.version.split()[0]}")
test_results.append(f"Platform: {sys.platform}")
test_results.append(f"SPACE_ID: {os.getenv('SPACE_ID', 'Not on HF Spaces')}")
# 2. Check a2a-sdk installation
test_results.append("\n=== Package Installation ===")
try:
result = subprocess.run([sys.executable, "-m", "pip", "show", "a2a-sdk"],
capture_output=True, text=True, timeout=5)
if result.returncode == 0:
version_line = [line for line in result.stdout.split('\n') if line.startswith('Version:')]
location_line = [line for line in result.stdout.split('\n') if line.startswith('Location:')]
test_results.append(f"✅ a2a-sdk installed: {version_line[0] if version_line else 'Unknown version'}")
if location_line:
test_results.append(f" {location_line[0]}")
else:
test_results.append("❌ a2a-sdk NOT installed according to pip")
except Exception as e:
test_results.append(f"❌ Error checking pip: {e}")
# 3. Module search
test_results.append("\n=== Module Search ===")
a2a_spec = importlib.util.find_spec("a2a")
if a2a_spec:
test_results.append(f"✅ a2a module found at: {a2a_spec.origin}")
else:
test_results.append("❌ a2a module NOT found by importlib")
# Manual search
for path in site.getsitepackages():
if os.path.exists(path):
a2a_path = os.path.join(path, "a2a")
if os.path.exists(a2a_path):
test_results.append(f" Found a2a directory at: {a2a_path}")
# 4. Import tests
test_results.append("\n=== Import Tests ===")
# Basic a2a import
try:
import a2a
test_results.append(f"✅ import a2a: Success")
try:
import a2a.types
test_results.append("✅ import a2a.types: Success")
try:
from a2a.types import AgentSkill
test_results.append("✅ from a2a.types import AgentSkill: Success")
except ImportError as e:
test_results.append(f"❌ AgentSkill import: {e}")
except ImportError as e:
test_results.append(f"❌ a2a.types import: {e}")
except ImportError as e:
test_results.append(f"❌ a2a import failed: {e}")
# any_agent A2A imports
try:
from any_agent.serving import A2AServingConfig
from any_agent.tools import a2a_tool_async
test_results.append("✅ any_agent A2A components: Success!")
except ImportError as e:
test_results.append(f"❌ any_agent A2A import: {e}")
# 5. Port availability
test_results.append("\n=== Port Availability ===")
test_ports = [5001, 5002, 5003, 5004, 5005, 5006]
available_count = 0
for port in test_ports:
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('127.0.0.1', port))
test_results.append(f"✅ Port {port} available")
available_count += 1
sock.close()
except Exception:
test_results.append(f"❌ Port {port} not available")
test_results.append(f"\n📊 Summary: {available_count}/{len(test_ports)} ports available")
# 6. Try fixing a2a if needed
if "❌ a2a import failed" in "\n".join(test_results):
test_results.append("\n=== Attempting Fix ===")
try:
# Try reinstalling without deps
result = subprocess.run(
[sys.executable, "-m", "pip", "install", "a2a-sdk", "--no-deps", "--force-reinstall"],
capture_output=True, text=True, timeout=10
)
if result.returncode == 0:
test_results.append("✅ Reinstalled a2a-sdk")
# Test import again
try:
import importlib
if 'a2a' in sys.modules:
del sys.modules['a2a']
import a2a
test_results.append("✅ Import after reinstall: Success!")
except Exception as e:
test_results.append(f"❌ Import after reinstall: {e}")
else:
test_results.append(f"❌ Reinstall failed: {result.stderr[:200]}")
except Exception as e:
test_results.append(f"❌ Fix attempt error: {e}")
# Final verdict
if available_count >= 6 and "✅ any_agent A2A components: Success!" in "\n".join(test_results):
test_results.append("\n✅ A2A should work! Try selecting A2A mode.")
else:
test_results.append("\n❌ A2A requirements not met. Use Basic Multiagent mode.")
return "\n".join(test_results)
# No need for separate mode change handler - it happens when draft starts
# Run multi-agent demo with control visibility handling
def run_and_check(mode, app):
"""Run demo and check for user turn."""
# Create a new app instance for this user if needed
if app is None:
app = EnhancedFantasyDraftApp()
use_a2a = (mode == "A2A")
for output in app.run_multiagent_demo(use_a2a):
result = check_user_turn(output, app)
yield result + (app,) # Return the app state as the last element
run_multiagent_btn.click(
run_and_check,
[communication_mode, app_state],
[multiagent_output, mock_draft_controls, available_accordion, available_players_display, draft_pick_input, app_state],
show_progress=True
)
# Wire up A2A test button
test_a2a_btn.click(
test_a2a_functionality,
[],
[a2a_test_output]
)
# Continue draft after user pick
def submit_and_continue(player_name, app):
"""Submit pick and continue draft."""
if app is None:
yield ("No active draft. Please start a new mock draft.",
gr.update(visible=False), gr.update(visible=False), "", "", None)
return
for output in app.continue_mock_draft(player_name):
result = check_user_turn(output, app)
yield result + (app,) # Return the app state as the last element
submit_pick_btn.click(
submit_and_continue,
[draft_pick_input, app_state],
[multiagent_output, mock_draft_controls, available_accordion, available_players_display, draft_pick_input, app_state],
show_progress=True
)
# Also submit on enter
draft_pick_input.submit(
submit_and_continue,
[draft_pick_input, app_state],
[multiagent_output, mock_draft_controls, available_accordion, available_players_display, draft_pick_input, app_state],
show_progress=True
)
# Minimal CSS for layout only
demo.css = """
#main-container {
max-width: 1200px;
margin: 0 auto;
}
.multiagent-output {
max-height: 800px;
overflow-y: auto;
}
/* Force dark text in message cards */
.multiagent-output div[style*="background-color"] {
color: #212121 !important;
}
.multiagent-output div[style*="background-color"] * {
color: #212121 !important;
}
#start-button {
margin-top: 20px;
}
"""
# Note: Gradio's unload() doesn't support inputs, so automatic cleanup
# happens when the Python process ends or when new sessions override old ones
return demo
def main():
"""Main entry point."""
# Check for API key - but don't exit on Hugging Face Spaces
if not os.getenv("OPENAI_API_KEY"):
if os.getenv("SPACE_ID"): # Running on Hugging Face Spaces
print("⚠️ OPENAI_API_KEY not found - please set it in Space Settings > Repository secrets")
else:
print("Error: OPENAI_API_KEY not found in environment")
print("Please set it using: export OPENAI_API_KEY='your-key-here'")
exit(1)
# Create and launch the interface
demo = create_gradio_interface()
print("🚀 Launching Enhanced Fantasy Draft App with A2A Support...")
# Check if running on Hugging Face Spaces
if os.getenv("SPACE_ID"):
demo.launch() # Hugging Face handles server config
else:
demo.launch(
server_name="0.0.0.0",
server_port=7860,
share=True,
show_error=True
)
if __name__ == "__main__":
main()