Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- .env.example +12 -0
- .gitignore +42 -0
- README.md +34 -5
- README_ORIGINAL.md +282 -0
- app.py +70 -0
- apps/app.py +700 -0
- apps/app_enhanced.py +1035 -0
- apps/multiagent_draft.py +768 -0
- apps/multiagent_scenarios.py +310 -0
- core/__init__.py +28 -0
- core/a2a_helpers.py +150 -0
- core/a2a_with_tools.py +262 -0
- core/agent.py +239 -0
- core/constants.py +66 -0
- core/data.py +102 -0
- core/dynamic_a2a_manager.py +350 -0
- core/real_a2a_draft.py +364 -0
- core/scenarios.py +260 -0
- core/visualizer.py +319 -0
- deploy_instructions.md +45 -0
- docs/FEATURES_AND_ENHANCEMENTS.md +177 -0
- docs/SETUP_GUIDE.md +144 -0
- docs/TECHNICAL_DOCUMENTATION.md +194 -0
- requirements.txt +28 -0
.env.example
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# OpenAI API Key (required)
|
| 2 |
+
OPENAI_API_KEY=your-openai-api-key-here
|
| 3 |
+
|
| 4 |
+
# Optional: Use a different model provider
|
| 5 |
+
# ANTHROPIC_API_KEY=your-anthropic-key-here
|
| 6 |
+
# GOOGLE_API_KEY=your-google-key-here
|
| 7 |
+
|
| 8 |
+
# Optional: Change default model
|
| 9 |
+
# DEFAULT_MODEL=gpt-4o-mini
|
| 10 |
+
|
| 11 |
+
# Optional: Change default framework
|
| 12 |
+
# DEFAULT_FRAMEWORK=tinyagent
|
.gitignore
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*$py.class
|
| 5 |
+
*.so
|
| 6 |
+
.Python
|
| 7 |
+
env/
|
| 8 |
+
venv/
|
| 9 |
+
ENV/
|
| 10 |
+
env.bak/
|
| 11 |
+
venv.bak/
|
| 12 |
+
|
| 13 |
+
# Environment variables
|
| 14 |
+
.env
|
| 15 |
+
|
| 16 |
+
# IDE
|
| 17 |
+
.vscode/
|
| 18 |
+
.idea/
|
| 19 |
+
*.swp
|
| 20 |
+
*.swo
|
| 21 |
+
|
| 22 |
+
# OS
|
| 23 |
+
.DS_Store
|
| 24 |
+
Thumbs.db
|
| 25 |
+
|
| 26 |
+
# Project specific
|
| 27 |
+
scenario_*_transcript.md
|
| 28 |
+
*.log
|
| 29 |
+
.cache/
|
| 30 |
+
|
| 31 |
+
# Jupyter
|
| 32 |
+
.ipynb_checkpoints/
|
| 33 |
+
*.ipynb
|
| 34 |
+
|
| 35 |
+
# Gradio cache
|
| 36 |
+
.gradio/
|
| 37 |
+
|
| 38 |
+
# Test files
|
| 39 |
+
test_*.py
|
| 40 |
+
|
| 41 |
+
# Deployment prep
|
| 42 |
+
prepare_for_deployment.py
|
README.md
CHANGED
|
@@ -1,12 +1,41 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
colorTo: green
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version:
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
|
|
|
|
|
|
| 10 |
---
|
| 11 |
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: fantasy-draft-demo
|
| 3 |
+
emoji: 🏈
|
| 4 |
+
colorFrom: blue
|
| 5 |
colorTo: green
|
| 6 |
sdk: gradio
|
| 7 |
+
sdk_version: 4.36.0
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
+
license: mit
|
| 11 |
+
python_version: 3.11
|
| 12 |
---
|
| 13 |
|
| 14 |
+
# 🏈 Fantasy Draft Multi-Agent Demo
|
| 15 |
+
|
| 16 |
+
Experience AI agents with distinct personalities competing in a fantasy football draft! You play as Team 4 with an AI advisor.
|
| 17 |
+
|
| 18 |
+
## Features
|
| 19 |
+
|
| 20 |
+
- **6 AI Agents** with unique strategies (Zero RB, Best Player Available, Robust RB, etc.)
|
| 21 |
+
- **Two Communication Modes**:
|
| 22 |
+
- **Basic Multiagent**: Fast, single-process execution
|
| 23 |
+
- **A2A Mode**: Distributed agents on HTTP servers (limited on free tier)
|
| 24 |
+
- **Interactive Participation**: Draft alongside AI with strategic advice
|
| 25 |
+
- **Real-time Communication**: Agents comment and react to picks
|
| 26 |
+
- **Multi-User Support**: Each user gets their own draft session
|
| 27 |
+
|
| 28 |
+
## Setup
|
| 29 |
+
|
| 30 |
+
1. Add your OpenAI API key in **Settings → Repository secrets** as `OPENAI_API_KEY`
|
| 31 |
+
2. The app will start automatically once deployed
|
| 32 |
+
|
| 33 |
+
## Note on A2A Mode
|
| 34 |
+
|
| 35 |
+
A2A mode requires ports 5000-9000 for agent servers. On Hugging Face Spaces free tier, port availability may be limited. If A2A mode fails, the app will suggest using Basic Multiagent mode instead.
|
| 36 |
+
|
| 37 |
+
## About
|
| 38 |
+
|
| 39 |
+
Built with the [any-agent](https://github.com/any-agent/any-agent) framework, showcasing how modern LLMs can create engaging multi-agent experiences.
|
| 40 |
+
|
| 41 |
+
[View Source Code](https://github.com/alexmeckes/fantasydraft)
|
README_ORIGINAL.md
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 🏈 Fantasy Draft Agent MVP
|
| 2 |
+
|
| 3 |
+
An AI-powered fantasy football draft assistant built with [any-agent](https://github.com/any-agent/any-agent) framework. This MVP demonstrates multi-turn conversation capabilities through pre-crafted scenarios, showcasing how AI can maintain context and provide strategic draft advice.
|
| 4 |
+
|
| 5 |
+
## 🎯 Features
|
| 6 |
+
|
| 7 |
+
- **Multi-turn Conversations**: Maintains context across multiple interactions
|
| 8 |
+
- **Strategic Draft Advice**: Provides recommendations based on draft position and available players
|
| 9 |
+
- **Position Analysis**: Evaluates scarcity and tier breakdowns
|
| 10 |
+
- **Team Stacking**: Identifies QB-receiver correlations
|
| 11 |
+
- **Interactive Mode**: Real-time draft assistance
|
| 12 |
+
- **Demo Scenarios**: 4 pre-built scenarios showcasing different capabilities
|
| 13 |
+
- **Web Interface**: Beautiful Gradio UI for easy interaction
|
| 14 |
+
- **Multi-Agent Mock Draft**: Watch AI agents communicate and compete in a live draft
|
| 15 |
+
|
| 16 |
+
## 🚀 Quick Start
|
| 17 |
+
|
| 18 |
+
### Prerequisites
|
| 19 |
+
|
| 20 |
+
- Python 3.8+
|
| 21 |
+
- OpenAI API key (or compatible LLM API)
|
| 22 |
+
|
| 23 |
+
### Installation
|
| 24 |
+
|
| 25 |
+
1. Clone the repository:
|
| 26 |
+
```bash
|
| 27 |
+
git clone <your-repo-url>
|
| 28 |
+
cd fantasy-draft-agent
|
| 29 |
+
```
|
| 30 |
+
|
| 31 |
+
2. Install dependencies:
|
| 32 |
+
```bash
|
| 33 |
+
pip install -r requirements.txt
|
| 34 |
+
```
|
| 35 |
+
|
| 36 |
+
3. Set up your API key (recommended method):
|
| 37 |
+
```bash
|
| 38 |
+
# Create .env file from template
|
| 39 |
+
cp .env.example .env
|
| 40 |
+
|
| 41 |
+
# Edit .env and add your OpenAI API key
|
| 42 |
+
# The file should contain:
|
| 43 |
+
# OPENAI_API_KEY=sk-proj-xxxxxxxxxxxxxxxxxxxxx
|
| 44 |
+
```
|
| 45 |
+
|
| 46 |
+
Alternative method (less secure):
|
| 47 |
+
```bash
|
| 48 |
+
export OPENAI_API_KEY='your-api-key-here'
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
### Test Installation
|
| 52 |
+
|
| 53 |
+
```bash
|
| 54 |
+
python demo.py --test
|
| 55 |
+
```
|
| 56 |
+
|
| 57 |
+
## 🔑 API Key Setup
|
| 58 |
+
|
| 59 |
+
The app uses `python-dotenv` to automatically load your API key from a `.env` file:
|
| 60 |
+
|
| 61 |
+
1. **Create `.env` file**: `cp .env.example .env`
|
| 62 |
+
2. **Edit `.env`**: Add your key: `OPENAI_API_KEY=sk-proj-xxxxx`
|
| 63 |
+
3. **Never commit `.env`**: It's already in `.gitignore`
|
| 64 |
+
|
| 65 |
+
See [ENV_SETUP.md](ENV_SETUP.md) for detailed instructions.
|
| 66 |
+
|
| 67 |
+
## 🎮 Usage
|
| 68 |
+
|
| 69 |
+
### Web Interface (Recommended)
|
| 70 |
+
|
| 71 |
+
Launch the Gradio web interface for the best experience:
|
| 72 |
+
|
| 73 |
+
```bash
|
| 74 |
+
python apps/app.py
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
Then open http://localhost:7860 in your browser.
|
| 78 |
+
|
| 79 |
+
The web interface includes:
|
| 80 |
+
- **💬 Draft Assistant**: Interactive chat with context retention
|
| 81 |
+
- **📊 Player Analysis**: Compare players and analyze positions
|
| 82 |
+
- **🎯 Available Players**: Browse and filter available players
|
| 83 |
+
- **🎬 Demo Scenarios**: Run pre-built conversation scenarios
|
| 84 |
+
- **📋 Draft Board**: View the current draft state
|
| 85 |
+
|
| 86 |
+
### Command Line Interface
|
| 87 |
+
|
| 88 |
+
Quick demo:
|
| 89 |
+
|
| 90 |
+
```bash
|
| 91 |
+
python demos/cli_demo.py --quick
|
| 92 |
+
```
|
| 93 |
+
|
| 94 |
+
Interactive mode:
|
| 95 |
+
|
| 96 |
+
```bash
|
| 97 |
+
python demos/cli_demo.py --interactive
|
| 98 |
+
```
|
| 99 |
+
|
| 100 |
+
Commands in interactive mode:
|
| 101 |
+
- Ask any draft question
|
| 102 |
+
- `pick <player>` - Draft a player
|
| 103 |
+
- `roster` - View your current roster
|
| 104 |
+
- `reset` - Start a new draft
|
| 105 |
+
- `quit` - Exit
|
| 106 |
+
|
| 107 |
+
Run demo scenarios:
|
| 108 |
+
|
| 109 |
+
```bash
|
| 110 |
+
python demos/cli_demo.py --scenario 1 # Run "The Opening Pick" scenario
|
| 111 |
+
python demos/cli_demo.py --all-scenarios # Run all scenarios
|
| 112 |
+
```
|
| 113 |
+
|
| 114 |
+
## 🎮 Running Demos
|
| 115 |
+
|
| 116 |
+
### Basic Demo
|
| 117 |
+
```bash
|
| 118 |
+
python demos/cli_demo.py
|
| 119 |
+
```
|
| 120 |
+
|
| 121 |
+
### Multi-Turn Demos
|
| 122 |
+
```bash
|
| 123 |
+
# Clean multi-turn demo (recommended)
|
| 124 |
+
python demos/multiturn_demo.py
|
| 125 |
+
|
| 126 |
+
# Quick start helper
|
| 127 |
+
python demos/quickstart.py
|
| 128 |
+
```
|
| 129 |
+
|
| 130 |
+
### Web Interface
|
| 131 |
+
```bash
|
| 132 |
+
python apps/app.py
|
| 133 |
+
# Then open http://localhost:7860
|
| 134 |
+
```
|
| 135 |
+
|
| 136 |
+
### Quick Start
|
| 137 |
+
The quickstart script will check dependencies and launch the web interface:
|
| 138 |
+
```bash
|
| 139 |
+
python demos/quickstart.py
|
| 140 |
+
```
|
| 141 |
+
|
| 142 |
+
## 📚 Demo Scenarios
|
| 143 |
+
|
| 144 |
+
1. **The Opening Pick** - User has 5th pick, top 4 players gone. Shows strategy adaptation.
|
| 145 |
+
2. **The Position Run** - Round 3, QB run happening. Shows patience and value finding.
|
| 146 |
+
3. **The Sleeper Question** - Round 10, looking for upside. Shows deep knowledge.
|
| 147 |
+
4. **The Stack Builder** - Has Mahomes, wants receivers. Shows correlation strategy.
|
| 148 |
+
|
| 149 |
+
## 🏗️ Architecture
|
| 150 |
+
|
| 151 |
+
```
|
| 152 |
+
fantasy-draft-agent/
|
| 153 |
+
├── core/ # Core functionality
|
| 154 |
+
│ ├── agent.py # FantasyDraftAgent class using any-agent
|
| 155 |
+
│ ├── data.py # Static player data (top 50 players)
|
| 156 |
+
│ ├── scenarios.py # Pre-crafted demo scenarios
|
| 157 |
+
│ └── visualizer.py # ASCII visualizations for demos
|
| 158 |
+
├── apps/ # Application interfaces
|
| 159 |
+
│ ├── app.py # Gradio web interface
|
| 160 |
+
│ ├── multiagent_draft.py # Multi-agent mock draft
|
| 161 |
+
│ └── multiagent_scenarios.py # A2A communication scenarios
|
| 162 |
+
├── demos/ # Demo scripts
|
| 163 |
+
│ ├── cli_demo.py # Command-line interface
|
| 164 |
+
│ ├── multiturn_demo.py # Multi-turn demonstration
|
| 165 |
+
│ └── quickstart.py # Quick start helper
|
| 166 |
+
├── docs/ # Documentation
|
| 167 |
+
│ ├── ENV_SETUP.md # Environment setup guide
|
| 168 |
+
│ ├── MULTIAGENT_DEMO.md # Multi-agent features
|
| 169 |
+
│ └── ... # Other documentation
|
| 170 |
+
├── scripts/ # Utility scripts
|
| 171 |
+
│ ├── setup.sh # Setup script
|
| 172 |
+
│ └── start_venv.sh # Virtual environment starter
|
| 173 |
+
├── requirements.txt # Dependencies
|
| 174 |
+
└── README.md # This file
|
| 175 |
+
```
|
| 176 |
+
|
| 177 |
+
### Key Components
|
| 178 |
+
|
| 179 |
+
- **FantasyDraftAgent**: Main agent class with conversation memory
|
| 180 |
+
- **Tools**: Player stats, best available, position analysis, stacking options
|
| 181 |
+
- **ScenarioRunner**: Manages and executes demo scenarios
|
| 182 |
+
- **Visualizer**: Creates ASCII player cards and draft boards
|
| 183 |
+
- **FantasyDraftApp**: Gradio interface wrapper
|
| 184 |
+
|
| 185 |
+
## 💡 Example Usage
|
| 186 |
+
|
| 187 |
+
```python
|
| 188 |
+
from agent import FantasyDraftAgent
|
| 189 |
+
|
| 190 |
+
# Create agent
|
| 191 |
+
agent = FantasyDraftAgent()
|
| 192 |
+
|
| 193 |
+
# Ask for advice
|
| 194 |
+
response = agent.run("I have the 5th pick. Who should I target?")
|
| 195 |
+
print(response)
|
| 196 |
+
|
| 197 |
+
# Follow-up question (maintains context)
|
| 198 |
+
response = agent.run("What about in round 2?")
|
| 199 |
+
print(response)
|
| 200 |
+
```
|
| 201 |
+
|
| 202 |
+
## 🔧 Customization
|
| 203 |
+
|
| 204 |
+
### Change the LLM Model
|
| 205 |
+
|
| 206 |
+
Edit `agent.py`:
|
| 207 |
+
|
| 208 |
+
```python
|
| 209 |
+
self.agent = AnyAgent.create(
|
| 210 |
+
framework="openai", # or "langchain", "llama_index", etc.
|
| 211 |
+
AgentConfig(
|
| 212 |
+
model_id="gpt-4", # Change model here
|
| 213 |
+
# ...
|
| 214 |
+
)
|
| 215 |
+
)
|
| 216 |
+
```
|
| 217 |
+
|
| 218 |
+
### Add More Players
|
| 219 |
+
|
| 220 |
+
Edit `data.py` to add more players to the `TOP_PLAYERS` dictionary.
|
| 221 |
+
|
| 222 |
+
### Create New Scenarios
|
| 223 |
+
|
| 224 |
+
Add new scenarios to `SCENARIOS` in `scenarios.py`.
|
| 225 |
+
|
| 226 |
+
### Customize the Web Interface
|
| 227 |
+
|
| 228 |
+
Edit `app.py` to modify the Gradio interface, add new tabs, or change the styling.
|
| 229 |
+
|
| 230 |
+
## 📊 Visual Components
|
| 231 |
+
|
| 232 |
+
The agent includes ASCII visualizations for:
|
| 233 |
+
- Player cards with stats
|
| 234 |
+
- Side-by-side player comparisons
|
| 235 |
+
- Draft board snapshots
|
| 236 |
+
- Roster summaries
|
| 237 |
+
- Decision summaries
|
| 238 |
+
|
| 239 |
+
## 🎯 MVP Scope
|
| 240 |
+
|
| 241 |
+
This MVP focuses on:
|
| 242 |
+
- ✅ Multi-turn conversation capabilities
|
| 243 |
+
- ✅ Context retention across interactions
|
| 244 |
+
- ✅ Strategic draft advice
|
| 245 |
+
- ✅ Demo-ready scenarios
|
| 246 |
+
- ✅ Visual web interface
|
| 247 |
+
|
| 248 |
+
Not included (future enhancements):
|
| 249 |
+
- ❌ Live draft integration
|
| 250 |
+
- ❌ Real-time data feeds
|
| 251 |
+
- ❌ Full 15-round drafts
|
| 252 |
+
- ❌ Trade analysis
|
| 253 |
+
- ❌ Season-long management
|
| 254 |
+
|
| 255 |
+
## 🤝 Contributing
|
| 256 |
+
|
| 257 |
+
This is an MVP demonstration. For production features, consider:
|
| 258 |
+
- Adding real-time player data APIs
|
| 259 |
+
- Implementing live draft room integration
|
| 260 |
+
- Expanding to other fantasy sports
|
| 261 |
+
- Adding machine learning for player projections
|
| 262 |
+
|
| 263 |
+
## 📝 License
|
| 264 |
+
|
| 265 |
+
MIT License - feel free to use and modify for your own projects!
|
| 266 |
+
|
| 267 |
+
## 🙏 Acknowledgments
|
| 268 |
+
|
| 269 |
+
Built with [any-agent](https://github.com/any-agent/any-agent) - a unified interface for AI agent frameworks.
|
| 270 |
+
|
| 271 |
+
---
|
| 272 |
+
|
| 273 |
+
**Built in < 100 lines of core agent code!** 🚀
|
| 274 |
+
|
| 275 |
+
## 📚 Documentation
|
| 276 |
+
|
| 277 |
+
- `README.md` - This file
|
| 278 |
+
- `ENV_SETUP.md` - Environment and API key setup guide
|
| 279 |
+
- `VENV_GUIDE.md` - Virtual environment instructions
|
| 280 |
+
- `MULTITURN_VISUALS.md` - Guide to multi-turn visualization features
|
| 281 |
+
- `MULTIAGENT_DEMO.md` - Multi-agent mock draft documentation
|
| 282 |
+
- `screenshots.md` - Visual examples of the app in action
|
app.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Entry point for Hugging Face Spaces
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import sys
|
| 7 |
+
import os
|
| 8 |
+
import subprocess
|
| 9 |
+
|
| 10 |
+
# Check if we're on Hugging Face Spaces and need to handle A2A deps
|
| 11 |
+
if os.getenv("SPACE_ID"):
|
| 12 |
+
print("🤗 Running on Hugging Face Spaces...")
|
| 13 |
+
|
| 14 |
+
# Try to import A2A components
|
| 15 |
+
try:
|
| 16 |
+
from any_agent.serving import A2AServingConfig
|
| 17 |
+
print("✅ A2A dependencies already available")
|
| 18 |
+
except ImportError:
|
| 19 |
+
print("⚠️ A2A dependencies not found, attempting to install...")
|
| 20 |
+
try:
|
| 21 |
+
# Install any-agent with A2A extras explicitly
|
| 22 |
+
print("Installing any-agent[a2a]...")
|
| 23 |
+
subprocess.check_call([
|
| 24 |
+
sys.executable, "-m", "pip", "install",
|
| 25 |
+
"--force-reinstall", "--no-deps",
|
| 26 |
+
"any-agent[a2a]"
|
| 27 |
+
])
|
| 28 |
+
|
| 29 |
+
# Also ensure key A2A dependencies
|
| 30 |
+
a2a_deps = [
|
| 31 |
+
"a2a-sdk>=0.2.8",
|
| 32 |
+
"grpcio>=1.60",
|
| 33 |
+
"grpcio-tools>=1.60",
|
| 34 |
+
"grpcio-reflection>=1.7.0"
|
| 35 |
+
]
|
| 36 |
+
|
| 37 |
+
for dep in a2a_deps:
|
| 38 |
+
try:
|
| 39 |
+
subprocess.check_call([
|
| 40 |
+
sys.executable, "-m", "pip", "install",
|
| 41 |
+
"--no-deps", dep
|
| 42 |
+
])
|
| 43 |
+
print(f"✅ Installed {dep}")
|
| 44 |
+
except:
|
| 45 |
+
print(f"⚠️ Could not install {dep}")
|
| 46 |
+
|
| 47 |
+
except Exception as e:
|
| 48 |
+
print(f"❌ Could not install A2A dependencies: {e}")
|
| 49 |
+
print("ℹ️ A2A mode will not be available, but Basic Multiagent mode will work fine!")
|
| 50 |
+
|
| 51 |
+
# Try patching the import check before importing the app
|
| 52 |
+
if os.getenv("SPACE_ID"):
|
| 53 |
+
try:
|
| 54 |
+
# Import a2a_sdk first to ensure it's available
|
| 55 |
+
import a2a_sdk
|
| 56 |
+
print("✅ a2a_sdk imported successfully")
|
| 57 |
+
|
| 58 |
+
# Now try to patch any_agent if needed
|
| 59 |
+
import any_agent
|
| 60 |
+
# Some versions might have a flag we can set
|
| 61 |
+
if hasattr(any_agent, '_A2A_AVAILABLE'):
|
| 62 |
+
any_agent._A2A_AVAILABLE = True
|
| 63 |
+
except Exception as e:
|
| 64 |
+
print(f"⚠️ Patching attempt: {e}")
|
| 65 |
+
|
| 66 |
+
# Import and run the enhanced app
|
| 67 |
+
from apps.app_enhanced import main
|
| 68 |
+
|
| 69 |
+
if __name__ == "__main__":
|
| 70 |
+
main()
|
apps/app.py
ADDED
|
@@ -0,0 +1,700 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Fantasy Draft Multi-Agent Demo
|
| 4 |
+
Showcases multi-agent and multi-turn capabilities
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import os
|
| 8 |
+
import time
|
| 9 |
+
import gradio as gr
|
| 10 |
+
from typing import List, Tuple
|
| 11 |
+
from dotenv import load_dotenv
|
| 12 |
+
import sys
|
| 13 |
+
import os
|
| 14 |
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 15 |
+
|
| 16 |
+
from core.agent import FantasyDraftAgent
|
| 17 |
+
from core.data import TOP_PLAYERS
|
| 18 |
+
from apps.multiagent_draft import MultiAgentMockDraft
|
| 19 |
+
from apps.multiagent_scenarios import (
|
| 20 |
+
run_interactive_mock_draft,
|
| 21 |
+
format_conversation_block,
|
| 22 |
+
format_agent_message,
|
| 23 |
+
format_memory_indicator,
|
| 24 |
+
create_mock_draft_visualization
|
| 25 |
+
)
|
| 26 |
+
|
| 27 |
+
# Fix for litellm 1.72.4 OpenAI endpoint issue
|
| 28 |
+
# This ensures litellm uses the correct OpenAI API endpoint
|
| 29 |
+
os.environ['OPENAI_API_BASE'] = 'https://api.openai.com/v1'
|
| 30 |
+
|
| 31 |
+
# Load environment variables from .env file
|
| 32 |
+
load_dotenv()
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
class FantasyDraftApp:
|
| 36 |
+
def __init__(self):
|
| 37 |
+
self.current_draft = None # Store the current mock draft
|
| 38 |
+
self.draft_output = "" # Store the draft output so far
|
| 39 |
+
|
| 40 |
+
def run_multiagent_demo(self):
|
| 41 |
+
"""Run the mock draft demonstration."""
|
| 42 |
+
# Reset any previous draft
|
| 43 |
+
self.current_draft = None
|
| 44 |
+
self.draft_output = ""
|
| 45 |
+
|
| 46 |
+
# Run the draft generator
|
| 47 |
+
draft_generator = run_interactive_mock_draft()
|
| 48 |
+
|
| 49 |
+
for output in draft_generator:
|
| 50 |
+
# Check if this is a tuple (draft state, output)
|
| 51 |
+
if isinstance(output, tuple):
|
| 52 |
+
# This means it's the user's turn
|
| 53 |
+
self.current_draft, self.draft_output = output
|
| 54 |
+
# Add a special marker for Gradio to detect
|
| 55 |
+
yield self.draft_output + "\n<!--USER_TURN-->"
|
| 56 |
+
return
|
| 57 |
+
else:
|
| 58 |
+
# Regular output
|
| 59 |
+
self.draft_output = output
|
| 60 |
+
yield output
|
| 61 |
+
|
| 62 |
+
def continue_mock_draft(self, player_name: str):
|
| 63 |
+
"""Continue the mock draft after user makes a pick."""
|
| 64 |
+
if not self.current_draft:
|
| 65 |
+
yield "No active draft. Please start a new mock draft."
|
| 66 |
+
return
|
| 67 |
+
|
| 68 |
+
if not player_name:
|
| 69 |
+
yield self.draft_output + "\n\n⚠️ Please enter a player name!"
|
| 70 |
+
return
|
| 71 |
+
|
| 72 |
+
# Make the user's pick
|
| 73 |
+
messages = self.current_draft.make_user_pick(player_name)
|
| 74 |
+
|
| 75 |
+
# Display messages with inline typing effect
|
| 76 |
+
for msg in messages:
|
| 77 |
+
if len(msg) >= 3:
|
| 78 |
+
agent, recipient, content = msg[:3]
|
| 79 |
+
|
| 80 |
+
# Check if it's a typing indicator - skip it
|
| 81 |
+
if isinstance(agent, str) and agent.startswith("typing_"):
|
| 82 |
+
continue # Skip typing indicators, we'll handle inline
|
| 83 |
+
else:
|
| 84 |
+
# Show "..." first for typing effect
|
| 85 |
+
typing_placeholder = format_agent_message(agent, recipient, "...")
|
| 86 |
+
self.draft_output += typing_placeholder
|
| 87 |
+
yield self.draft_output
|
| 88 |
+
time.sleep(0.5) # Brief typing delay
|
| 89 |
+
|
| 90 |
+
# Replace "..." with actual message
|
| 91 |
+
self.draft_output = self.draft_output.replace(typing_placeholder, "")
|
| 92 |
+
self.draft_output += format_agent_message(agent, recipient, content)
|
| 93 |
+
yield self.draft_output
|
| 94 |
+
time.sleep(1.0) # Reading delay
|
| 95 |
+
|
| 96 |
+
# Continue the draft from where we left off
|
| 97 |
+
# We need to track where we were in the draft
|
| 98 |
+
total_picks = len([p for picks in self.current_draft.draft_board.values() for p in picks])
|
| 99 |
+
current_round = ((total_picks - 1) // 6) + 1 # 6 teams per round
|
| 100 |
+
|
| 101 |
+
# Continue with the rest of the draft
|
| 102 |
+
draft_memories = []
|
| 103 |
+
|
| 104 |
+
# Continue the draft
|
| 105 |
+
for round_num in range(current_round, 4): # Continue from current round to round 3
|
| 106 |
+
if round_num > current_round:
|
| 107 |
+
self.draft_output += f"\n## 🔄 ROUND {round_num}\n\n"
|
| 108 |
+
yield self.draft_output
|
| 109 |
+
|
| 110 |
+
# Snake draft order - 6 teams total
|
| 111 |
+
if round_num % 2 == 1:
|
| 112 |
+
pick_order = list(range(1, 7)) # 1-6 for odd rounds
|
| 113 |
+
else:
|
| 114 |
+
pick_order = list(range(6, 0, -1)) # 6-1 for even rounds
|
| 115 |
+
|
| 116 |
+
# Calculate where we are in the current round
|
| 117 |
+
picks_in_round = total_picks % 6 # 6 teams per round
|
| 118 |
+
start_idx = picks_in_round if round_num == current_round else 0
|
| 119 |
+
|
| 120 |
+
for pick_in_round, team_num in enumerate(list(pick_order)[start_idx:], start_idx + 1):
|
| 121 |
+
pick_num = (round_num - 1) * 6 + pick_in_round # 6 teams per round
|
| 122 |
+
|
| 123 |
+
# Show draft board at start of round
|
| 124 |
+
if pick_in_round == 1:
|
| 125 |
+
self.draft_output += create_mock_draft_visualization(self.current_draft, round_num, pick_num)
|
| 126 |
+
self.draft_output += "\n"
|
| 127 |
+
yield self.draft_output
|
| 128 |
+
|
| 129 |
+
# Process the pick
|
| 130 |
+
messages, result = self.current_draft.simulate_draft_turn(round_num, pick_num, team_num)
|
| 131 |
+
|
| 132 |
+
# Display messages with inline typing effect
|
| 133 |
+
for msg in messages:
|
| 134 |
+
if len(msg) >= 3:
|
| 135 |
+
agent, recipient, content = msg[:3]
|
| 136 |
+
|
| 137 |
+
# Check if it's a typing indicator - skip it
|
| 138 |
+
if isinstance(agent, str) and agent.startswith("typing_"):
|
| 139 |
+
continue # Skip typing indicators, we'll handle inline
|
| 140 |
+
else:
|
| 141 |
+
# Show "..." first for typing effect
|
| 142 |
+
typing_placeholder = format_agent_message(agent, recipient, "...")
|
| 143 |
+
self.draft_output += typing_placeholder
|
| 144 |
+
yield self.draft_output
|
| 145 |
+
time.sleep(0.5) # Brief typing delay
|
| 146 |
+
|
| 147 |
+
# Replace "..." with actual message
|
| 148 |
+
self.draft_output = self.draft_output.replace(typing_placeholder, "")
|
| 149 |
+
self.draft_output += format_agent_message(agent, recipient, content)
|
| 150 |
+
yield self.draft_output
|
| 151 |
+
time.sleep(1.0) # Reading delay
|
| 152 |
+
|
| 153 |
+
if result is None:
|
| 154 |
+
# It's the user's turn again
|
| 155 |
+
self.draft_output += "\n**⏰ YOU'RE ON THE CLOCK! Type your pick below.**\n\n"
|
| 156 |
+
yield self.draft_output + "\n<!--USER_TURN-->"
|
| 157 |
+
return
|
| 158 |
+
|
| 159 |
+
# Add memory indicators
|
| 160 |
+
if round_num > 1 and pick_in_round % 2 == 0:
|
| 161 |
+
if team_num in self.current_draft.agents:
|
| 162 |
+
agent = self.current_draft.agents[team_num]
|
| 163 |
+
if len(agent.picks) > 1:
|
| 164 |
+
memory = f"{agent.team_name} has drafted: {', '.join(agent.picks)}"
|
| 165 |
+
draft_memories.append(memory)
|
| 166 |
+
|
| 167 |
+
if draft_memories:
|
| 168 |
+
self.draft_output += format_memory_indicator(round_num, draft_memories[-2:])
|
| 169 |
+
yield self.draft_output
|
| 170 |
+
|
| 171 |
+
time.sleep(0.5)
|
| 172 |
+
|
| 173 |
+
# End of round
|
| 174 |
+
self.draft_output += format_agent_message("commissioner", "ALL",
|
| 175 |
+
f"That's the end of Round {round_num}!")
|
| 176 |
+
yield self.draft_output
|
| 177 |
+
|
| 178 |
+
# Final summary
|
| 179 |
+
self.draft_output += "\n## 📊 FINAL RESULTS\n\n"
|
| 180 |
+
self.draft_output += self.current_draft.get_draft_summary()
|
| 181 |
+
yield self.draft_output
|
| 182 |
+
|
| 183 |
+
# Clear the draft state
|
| 184 |
+
self.current_draft = None
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
def create_gradio_interface():
|
| 188 |
+
"""Create the main Gradio interface."""
|
| 189 |
+
app = FantasyDraftApp()
|
| 190 |
+
|
| 191 |
+
with gr.Blocks(title="Fantasy Draft Multi-Agent Demo", theme=gr.themes.Glass()) as demo:
|
| 192 |
+
with gr.Column(elem_id="main-container"):
|
| 193 |
+
gr.Markdown("""
|
| 194 |
+
# 🏈 Fantasy Draft Multi-Agent Demo
|
| 195 |
+
|
| 196 |
+
**Experience the future of AI interaction:** Watch 6 intelligent agents compete in a fantasy football draft with distinct strategies, real-time trash talk, and persistent memory.
|
| 197 |
+
""")
|
| 198 |
+
|
| 199 |
+
with gr.Tabs():
|
| 200 |
+
# Demo Tab
|
| 201 |
+
with gr.TabItem("🎮 Demo"):
|
| 202 |
+
# Show agent cards first
|
| 203 |
+
gr.Markdown("""
|
| 204 |
+
### 🏈 Meet Your Competition
|
| 205 |
+
|
| 206 |
+
You'll be drafting at **Position 4** with these AI opponents:
|
| 207 |
+
""")
|
| 208 |
+
|
| 209 |
+
# Agent cards in a grid - all in one row
|
| 210 |
+
with gr.Row():
|
| 211 |
+
with gr.Column(scale=1):
|
| 212 |
+
gr.Markdown("""
|
| 213 |
+
<div style="background-color: #E3F2FD; border-left: 4px solid #1976D2; padding: 15px; border-radius: 8px;">
|
| 214 |
+
|
| 215 |
+
<h4 style="color: #1a237e !important; margin: 0 0 10px 0;">📘🤓 Team 1 - Zero RB</h4>
|
| 216 |
+
|
| 217 |
+
<p style="color: #1976D2 !important; font-style: italic; margin: 10px 0; font-size: 0.95em;">"RBs get injured. I'll build around elite WRs."</p>
|
| 218 |
+
|
| 219 |
+
<ul style="color: #1a237e !important; font-size: 0.9em; margin: 0; padding-left: 20px;">
|
| 220 |
+
<li style="color: #1a237e !important;">Avoids RBs early</li>
|
| 221 |
+
<li style="color: #1a237e !important;">Loads up on WRs</li>
|
| 222 |
+
<li style="color: #1a237e !important;">Gets RB value late</li>
|
| 223 |
+
</ul>
|
| 224 |
+
</div>
|
| 225 |
+
""")
|
| 226 |
+
|
| 227 |
+
with gr.Column(scale=1):
|
| 228 |
+
gr.Markdown("""
|
| 229 |
+
<div style="background-color: #E8F5E9; border-left: 4px solid #388E3C; padding: 15px; border-radius: 8px;">
|
| 230 |
+
|
| 231 |
+
<h4 style="color: #1b5e20 !important; margin: 0 0 10px 0;">📗🧑💼 Team 2 - BPA</h4>
|
| 232 |
+
|
| 233 |
+
<p style="color: #2e7d32 !important; font-style: italic; margin: 10px 0; font-size: 0.95em;">"Value is value. I don't reach for needs."</p>
|
| 234 |
+
|
| 235 |
+
<ul style="color: #1b5e20 !important; font-size: 0.9em; margin: 0; padding-left: 20px;">
|
| 236 |
+
<li style="color: #1b5e20 !important;">Pure value drafting</li>
|
| 237 |
+
<li style="color: #1b5e20 !important;">Ignores needs</li>
|
| 238 |
+
<li style="color: #1b5e20 !important;">Mocks reaching</li>
|
| 239 |
+
</ul>
|
| 240 |
+
</div>
|
| 241 |
+
""")
|
| 242 |
+
|
| 243 |
+
with gr.Column(scale=1):
|
| 244 |
+
gr.Markdown("""
|
| 245 |
+
<div style="background-color: #FFF3E0; border-left: 4px solid #F57C00; padding: 15px; border-radius: 8px;">
|
| 246 |
+
|
| 247 |
+
<h4 style="color: #e65100 !important; margin: 0 0 10px 0;">📙🧔 Team 3 - Robust RB</h4>
|
| 248 |
+
|
| 249 |
+
<p style="color: #ef6c00 !important; font-style: italic; margin: 10px 0; font-size: 0.95em;">"RBs win championships. Period."</p>
|
| 250 |
+
|
| 251 |
+
<ul style="color: #e65100 !important; font-size: 0.9em; margin: 0; padding-left: 20px;">
|
| 252 |
+
<li style="color: #e65100 !important;">RBs in rounds 1-2</li>
|
| 253 |
+
<li style="color: #e65100 !important;">Old-school approach</li>
|
| 254 |
+
<li style="color: #e65100 !important;">Foundation first</li>
|
| 255 |
+
</ul>
|
| 256 |
+
</div>
|
| 257 |
+
""")
|
| 258 |
+
|
| 259 |
+
with gr.Column(scale=1):
|
| 260 |
+
gr.Markdown("""
|
| 261 |
+
<div style="background-color: #E8EAF6; border-left: 4px solid #3F51B5; padding: 15px; border-radius: 8px;">
|
| 262 |
+
|
| 263 |
+
<h4 style="color: #1a237e !important; margin: 0 0 10px 0;">👤 Position 4 - YOU</h4>
|
| 264 |
+
|
| 265 |
+
<p style="color: #3949ab !important; font-style: italic; margin: 10px 0; font-size: 0.95em;">Your draft position with AI guidance</p>
|
| 266 |
+
|
| 267 |
+
<ul style="color: #1a237e !important; font-size: 0.9em; margin: 0; padding-left: 20px;">
|
| 268 |
+
<li style="color: #1a237e !important;">📕🧙 Strategic advisor</li>
|
| 269 |
+
<li style="color: #1a237e !important;">Real-time guidance</li>
|
| 270 |
+
<li style="color: #1a237e !important;">Roster analysis</li>
|
| 271 |
+
</ul>
|
| 272 |
+
</div>
|
| 273 |
+
""")
|
| 274 |
+
|
| 275 |
+
with gr.Column(scale=1):
|
| 276 |
+
gr.Markdown("""
|
| 277 |
+
<div style="background-color: #F5E6FF; border-left: 4px solid #7B1FA2; padding: 15px; border-radius: 8px;">
|
| 278 |
+
|
| 279 |
+
<h4 style="color: #4a148c !important; margin: 0 0 10px 0;">📓🤠 Team 5 - Upside</h4>
|
| 280 |
+
|
| 281 |
+
<p style="color: #6a1b9a !important; font-style: italic; margin: 10px 0; font-size: 0.95em;">"Safe picks are for losers!"</p>
|
| 282 |
+
|
| 283 |
+
<ul style="color: #4a148c !important; font-size: 0.9em; margin: 0; padding-left: 20px;">
|
| 284 |
+
<li style="color: #4a148c !important;">Seeks breakouts</li>
|
| 285 |
+
<li style="color: #4a148c !important;">High risk/reward</li>
|
| 286 |
+
<li style="color: #4a148c !important;">Mocks safety</li>
|
| 287 |
+
</ul>
|
| 288 |
+
</div>
|
| 289 |
+
""")
|
| 290 |
+
|
| 291 |
+
with gr.Column(scale=1):
|
| 292 |
+
gr.Markdown("""
|
| 293 |
+
<div style="background-color: #E8F5E9; border-left: 4px solid #388E3C; padding: 15px; border-radius: 8px;">
|
| 294 |
+
|
| 295 |
+
<h4 style="color: #1b5e20 !important; margin: 0 0 10px 0;">📗👨🏫 Team 6 - BPA</h4>
|
| 296 |
+
|
| 297 |
+
<p style="color: #2e7d32 !important; font-style: italic; margin: 10px 0; font-size: 0.95em;">"Another value drafter to punish reaches."</p>
|
| 298 |
+
|
| 299 |
+
<ul style="color: #1b5e20 !important; font-size: 0.9em; margin: 0; padding-left: 20px;">
|
| 300 |
+
<li style="color: #1b5e20 !important;">Takes obvious value</li>
|
| 301 |
+
<li style="color: #1b5e20 !important;">Disciplined approach</li>
|
| 302 |
+
<li style="color: #1b5e20 !important;">No sentiment</li>
|
| 303 |
+
</ul>
|
| 304 |
+
</div>
|
| 305 |
+
""")
|
| 306 |
+
|
| 307 |
+
gr.Markdown("""
|
| 308 |
+
### 🎮 Draft Format
|
| 309 |
+
- **3 Rounds** of snake draft (1→6, 6→1, 1→6)
|
| 310 |
+
- **Real-time trash talk** between picks
|
| 311 |
+
- **Strategic advisor** guides your selections
|
| 312 |
+
- **Memory system** - agents remember and reference earlier picks
|
| 313 |
+
|
| 314 |
+
Ready to experience the most realistic AI draft room?
|
| 315 |
+
""")
|
| 316 |
+
|
| 317 |
+
# Start button at the bottom
|
| 318 |
+
with gr.Row():
|
| 319 |
+
with gr.Column():
|
| 320 |
+
run_multiagent_btn = gr.Button("🏈 Start Mock Draft", variant="primary", size="lg", elem_id="start-button")
|
| 321 |
+
|
| 322 |
+
# Main output area
|
| 323 |
+
multiagent_output = gr.Markdown(elem_classes=["multiagent-output"])
|
| 324 |
+
|
| 325 |
+
# Mock draft interaction (hidden until needed)
|
| 326 |
+
with gr.Row(visible=False) as mock_draft_controls:
|
| 327 |
+
with gr.Column():
|
| 328 |
+
draft_pick_input = gr.Textbox(
|
| 329 |
+
label="Your Pick",
|
| 330 |
+
placeholder="Type player name and press Enter (e.g., 'Justin Jefferson')",
|
| 331 |
+
elem_id="draft-pick-input"
|
| 332 |
+
)
|
| 333 |
+
submit_pick_btn = gr.Button("Submit Pick", variant="primary")
|
| 334 |
+
|
| 335 |
+
# Available players display
|
| 336 |
+
with gr.Accordion("📋 Available Players", visible=False) as available_accordion:
|
| 337 |
+
available_players_display = gr.Textbox(
|
| 338 |
+
label="Top 20 Available",
|
| 339 |
+
lines=15,
|
| 340 |
+
interactive=False
|
| 341 |
+
)
|
| 342 |
+
|
| 343 |
+
# How It Works Tab
|
| 344 |
+
with gr.TabItem("🔧 How It Works"):
|
| 345 |
+
gr.Markdown("""
|
| 346 |
+
## Technical Implementation
|
| 347 |
+
|
| 348 |
+
This demo showcases advanced multi-agent capabilities using the **any-agent framework**.
|
| 349 |
+
|
| 350 |
+
### 🤖 Framework: any-agent (TinyAgent)
|
| 351 |
+
|
| 352 |
+
- **Lightweight**: < 100 lines of core agent code
|
| 353 |
+
- **Flexible**: Supports multiple LLM providers (OpenAI, Anthropic, etc.)
|
| 354 |
+
- **Multi-turn ready**: Built-in conversation history management
|
| 355 |
+
- **Model**: GPT-4 (configurable)
|
| 356 |
+
|
| 357 |
+
### 🧠 Multi-Turn Memory System
|
| 358 |
+
|
| 359 |
+
Each agent maintains:
|
| 360 |
+
- **Conversation History**: Full context of all interactions
|
| 361 |
+
- **Draft State**: Current picks, available players, round info
|
| 362 |
+
- **Strategy Memory**: Remembers own strategy and others' approaches
|
| 363 |
+
- **Pick History**: Tracks all selections for informed decisions
|
| 364 |
+
|
| 365 |
+
### 💬 Agent-to-Agent (A2A) Communication
|
| 366 |
+
|
| 367 |
+
Agents can:
|
| 368 |
+
- **Comment on picks**: React to other agents' selections
|
| 369 |
+
- **Respond to comments**: Defend their strategies
|
| 370 |
+
- **Remember debates**: Reference earlier conversations
|
| 371 |
+
- **Adapt strategies**: Adjust based on draft flow
|
| 372 |
+
|
| 373 |
+
### 📊 Architecture Flow
|
| 374 |
+
""")
|
| 375 |
+
|
| 376 |
+
gr.Markdown("""
|
| 377 |
+
#### 1️⃣ INITIALIZATION
|
| 378 |
+
User clicks "Start Mock Draft" → System creates 6 agents
|
| 379 |
+
|
| 380 |
+
#### 2️⃣ AGENT SETUP
|
| 381 |
+
- **Team 1**: Zero RB Strategy
|
| 382 |
+
- **Team 2**: Best Player Available
|
| 383 |
+
- **Team 3**: Robust RB Strategy
|
| 384 |
+
- **YOU**: Position 4 (with Advisor)
|
| 385 |
+
- **Team 5**: Upside Hunter
|
| 386 |
+
- **Team 6**: Best Player Available
|
| 387 |
+
|
| 388 |
+
#### 3️⃣ DRAFT FLOW (3 Rounds)
|
| 389 |
+
- **Round 1**: Pick Order 1���2→3→YOU→5→6
|
| 390 |
+
- **Round 2**: Pick Order 6→5→YOU→3→2→1 (Snake)
|
| 391 |
+
- **Round 3**: Pick Order 1→2→3→YOU→5→6
|
| 392 |
+
|
| 393 |
+
#### 4️⃣ EACH PICK TRIGGERS
|
| 394 |
+
- Agent makes selection based on strategy
|
| 395 |
+
- Other agents comment (A2A communication)
|
| 396 |
+
- Original agent may respond
|
| 397 |
+
- All agents update their memory
|
| 398 |
+
|
| 399 |
+
#### 5️⃣ USER'S TURN
|
| 400 |
+
- Advisor analyzes draft state
|
| 401 |
+
- User sees available players
|
| 402 |
+
- User makes pick
|
| 403 |
+
- All agents react to user's choice
|
| 404 |
+
|
| 405 |
+
#### 6️⃣ MEMORY & CONTEXT
|
| 406 |
+
- Each agent remembers all picks
|
| 407 |
+
- Agents reference earlier conversations
|
| 408 |
+
- Strategies adapt based on draft flow
|
| 409 |
+
- Visual memory indicators show retention
|
| 410 |
+
""")
|
| 411 |
+
|
| 412 |
+
gr.Markdown("""
|
| 413 |
+
### 🎯 Key Features Demonstrated
|
| 414 |
+
|
| 415 |
+
1. **Persistent Context**: Each agent remembers all previous interactions
|
| 416 |
+
2. **Strategic Personalities**: 5 distinct draft strategies competing
|
| 417 |
+
3. **Dynamic Adaptation**: Agents adjust based on draft progression
|
| 418 |
+
4. **Natural Dialogue**: Human-like commentary and debates
|
| 419 |
+
5. **User Integration**: Seamless human participation with AI guidance
|
| 420 |
+
|
| 421 |
+
### 📝 Implementation Details
|
| 422 |
+
|
| 423 |
+
- **Agent Classes**: Inheritance-based design with base `DraftAgent`
|
| 424 |
+
- **Message Formatting**: Custom HTML/CSS for visual distinction
|
| 425 |
+
- **State Management**: Draft board tracking and validation
|
| 426 |
+
- **Memory Indicators**: Visual cues showing context retention
|
| 427 |
+
|
| 428 |
+
### 🚀 Why This Matters
|
| 429 |
+
|
| 430 |
+
This demo proves that sophisticated multi-agent systems can be built with minimal code,
|
| 431 |
+
showcasing the power of modern LLMs when properly orchestrated. The any-agent framework
|
| 432 |
+
makes it easy to create agents that truly communicate and remember, not just respond.
|
| 433 |
+
""")
|
| 434 |
+
|
| 435 |
+
# Function to check if it's user's turn and show/hide controls
|
| 436 |
+
def check_user_turn(output_text):
|
| 437 |
+
"""Check if output indicates it's user's turn."""
|
| 438 |
+
if "<!--USER_TURN-->" in output_text:
|
| 439 |
+
# Remove the marker from display
|
| 440 |
+
clean_output = output_text.replace("<!--USER_TURN-->", "")
|
| 441 |
+
# Get available players
|
| 442 |
+
if app.current_draft:
|
| 443 |
+
available = app.current_draft.get_available_players()
|
| 444 |
+
available_text = "Available Players:\n\n"
|
| 445 |
+
for player in sorted(available)[:20]: # Show top 20
|
| 446 |
+
if player in TOP_PLAYERS:
|
| 447 |
+
info = TOP_PLAYERS[player]
|
| 448 |
+
available_text += f"• {player} ({info['pos']}, {info['team']})\n"
|
| 449 |
+
else:
|
| 450 |
+
available_text = "No draft active"
|
| 451 |
+
|
| 452 |
+
return (
|
| 453 |
+
clean_output, # Clean output
|
| 454 |
+
gr.update(visible=True), # Show draft controls
|
| 455 |
+
gr.update(visible=True, open=True), # Show available players and open it
|
| 456 |
+
available_text, # Available players list
|
| 457 |
+
"" # Clear the input
|
| 458 |
+
)
|
| 459 |
+
else:
|
| 460 |
+
return (
|
| 461 |
+
output_text, # Regular output
|
| 462 |
+
gr.update(visible=False), # Hide draft controls
|
| 463 |
+
gr.update(visible=False), # Hide available players
|
| 464 |
+
"", # Clear available list
|
| 465 |
+
"" # Clear the input
|
| 466 |
+
)
|
| 467 |
+
|
| 468 |
+
# Run multi-agent demo with control visibility handling
|
| 469 |
+
def run_and_check():
|
| 470 |
+
"""Run demo and check for user turn."""
|
| 471 |
+
for output in app.run_multiagent_demo():
|
| 472 |
+
result = check_user_turn(output)
|
| 473 |
+
yield result
|
| 474 |
+
|
| 475 |
+
run_multiagent_btn.click(
|
| 476 |
+
run_and_check,
|
| 477 |
+
None,
|
| 478 |
+
[multiagent_output, mock_draft_controls, available_accordion, available_players_display, draft_pick_input],
|
| 479 |
+
show_progress=True
|
| 480 |
+
)
|
| 481 |
+
|
| 482 |
+
# Continue draft after user pick
|
| 483 |
+
def submit_and_continue(player_name):
|
| 484 |
+
"""Submit pick and continue draft."""
|
| 485 |
+
for output in app.continue_mock_draft(player_name):
|
| 486 |
+
result = check_user_turn(output)
|
| 487 |
+
yield result
|
| 488 |
+
|
| 489 |
+
submit_pick_btn.click(
|
| 490 |
+
submit_and_continue,
|
| 491 |
+
draft_pick_input,
|
| 492 |
+
[multiagent_output, mock_draft_controls, available_accordion, available_players_display, draft_pick_input],
|
| 493 |
+
show_progress=True
|
| 494 |
+
)
|
| 495 |
+
|
| 496 |
+
# Also submit on enter
|
| 497 |
+
draft_pick_input.submit(
|
| 498 |
+
submit_and_continue,
|
| 499 |
+
draft_pick_input,
|
| 500 |
+
[multiagent_output, mock_draft_controls, available_accordion, available_players_display, draft_pick_input],
|
| 501 |
+
show_progress=True
|
| 502 |
+
)
|
| 503 |
+
|
| 504 |
+
# Add custom CSS for better styling
|
| 505 |
+
demo.css = """
|
| 506 |
+
/* Force white text on dark background for all main content */
|
| 507 |
+
.gradio-container {
|
| 508 |
+
max-width: 800px !important;
|
| 509 |
+
margin: 0 auto !important;
|
| 510 |
+
color: white !important;
|
| 511 |
+
}
|
| 512 |
+
|
| 513 |
+
/* Ensure all text elements are white by default */
|
| 514 |
+
.gradio-container p,
|
| 515 |
+
.gradio-container h1,
|
| 516 |
+
.gradio-container h2,
|
| 517 |
+
.gradio-container h3,
|
| 518 |
+
.gradio-container h4,
|
| 519 |
+
.gradio-container h5,
|
| 520 |
+
.gradio-container h6,
|
| 521 |
+
.gradio-container span,
|
| 522 |
+
.gradio-container div,
|
| 523 |
+
.gradio-container label,
|
| 524 |
+
.gradio-container .markdown,
|
| 525 |
+
.gradio-container .prose {
|
| 526 |
+
color: white !important;
|
| 527 |
+
}
|
| 528 |
+
|
| 529 |
+
/* Ensure markdown content is white */
|
| 530 |
+
.markdown-text,
|
| 531 |
+
.markdown-text p,
|
| 532 |
+
.markdown-text li,
|
| 533 |
+
.markdown-text ul,
|
| 534 |
+
.markdown-text ol {
|
| 535 |
+
color: white !important;
|
| 536 |
+
}
|
| 537 |
+
|
| 538 |
+
#main-container {
|
| 539 |
+
text-align: center;
|
| 540 |
+
color: white !important;
|
| 541 |
+
}
|
| 542 |
+
|
| 543 |
+
/* Left-align text in How It Works tab */
|
| 544 |
+
.tabitem:nth-child(2) {
|
| 545 |
+
text-align: left !important;
|
| 546 |
+
}
|
| 547 |
+
|
| 548 |
+
#start-button {
|
| 549 |
+
margin: 20px auto !important;
|
| 550 |
+
max-width: 300px !important;
|
| 551 |
+
}
|
| 552 |
+
|
| 553 |
+
/* Only force dark text inside colored message boxes */
|
| 554 |
+
div[style*="background-color"][style*="border-left"] {
|
| 555 |
+
color: #212121 !important;
|
| 556 |
+
}
|
| 557 |
+
|
| 558 |
+
div[style*="background-color"][style*="border-left"] p,
|
| 559 |
+
div[style*="background-color"][style*="border-left"] strong,
|
| 560 |
+
div[style*="background-color"][style*="border-left"] em,
|
| 561 |
+
div[style*="background-color"][style*="border-left"] span,
|
| 562 |
+
div[style*="background-color"][style*="border-left"] li,
|
| 563 |
+
div[style*="background-color"][style*="border-left"] ul,
|
| 564 |
+
div[style*="background-color"][style*="border-left"] h1,
|
| 565 |
+
div[style*="background-color"][style*="border-left"] h2,
|
| 566 |
+
div[style*="background-color"][style*="border-left"] h3,
|
| 567 |
+
div[style*="background-color"][style*="border-left"] h4 {
|
| 568 |
+
color: #212121 !important;
|
| 569 |
+
}
|
| 570 |
+
|
| 571 |
+
/* System messages with yellow background */
|
| 572 |
+
div[style*="#FFF9C4"] {
|
| 573 |
+
color: #F57C00 !important;
|
| 574 |
+
}
|
| 575 |
+
|
| 576 |
+
/* Memory boxes */
|
| 577 |
+
div[style*="#F5F5F5"] {
|
| 578 |
+
color: #424242 !important;
|
| 579 |
+
}
|
| 580 |
+
|
| 581 |
+
#draft-pick-input {
|
| 582 |
+
font-size: 1.1em;
|
| 583 |
+
}
|
| 584 |
+
|
| 585 |
+
/* Ensure tab labels are visible */
|
| 586 |
+
.tab-nav button {
|
| 587 |
+
color: white !important;
|
| 588 |
+
}
|
| 589 |
+
|
| 590 |
+
/* Ensure multiagent output text is white */
|
| 591 |
+
.multiagent-output {
|
| 592 |
+
color: white !important;
|
| 593 |
+
}
|
| 594 |
+
|
| 595 |
+
.multiagent-output p,
|
| 596 |
+
.multiagent-output h1,
|
| 597 |
+
.multiagent-output h2,
|
| 598 |
+
.multiagent-output h3,
|
| 599 |
+
.multiagent-output h4,
|
| 600 |
+
.multiagent-output h5,
|
| 601 |
+
.multiagent-output h6,
|
| 602 |
+
.multiagent-output span,
|
| 603 |
+
.multiagent-output div {
|
| 604 |
+
color: white !important;
|
| 605 |
+
}
|
| 606 |
+
|
| 607 |
+
/* Specific rules for dark mode - target Gradio's dark theme class */
|
| 608 |
+
.dark .gradio-container,
|
| 609 |
+
.dark .gradio-container *:not([style*="background-color"]) {
|
| 610 |
+
color: white !important;
|
| 611 |
+
}
|
| 612 |
+
|
| 613 |
+
/* Ensure description text under title is white */
|
| 614 |
+
.gradio-container > div > div > div > p {
|
| 615 |
+
color: white !important;
|
| 616 |
+
}
|
| 617 |
+
|
| 618 |
+
/* Tab content text */
|
| 619 |
+
.tabitem .markdown-text {
|
| 620 |
+
color: white !important;
|
| 621 |
+
}
|
| 622 |
+
|
| 623 |
+
/* Input labels and text */
|
| 624 |
+
.gradio-container label {
|
| 625 |
+
color: rgba(255, 255, 255, 0.9) !important;
|
| 626 |
+
}
|
| 627 |
+
|
| 628 |
+
/* Button text that's not in primary buttons */
|
| 629 |
+
button:not(.primary) {
|
| 630 |
+
color: rgba(255, 255, 255, 0.9) !important;
|
| 631 |
+
}
|
| 632 |
+
|
| 633 |
+
/* Fix for bold player names - ensure they're visible */
|
| 634 |
+
.multiagent-output strong,
|
| 635 |
+
.multiagent-output b {
|
| 636 |
+
color: inherit !important;
|
| 637 |
+
font-weight: 700;
|
| 638 |
+
}
|
| 639 |
+
|
| 640 |
+
/* Ensure bold text in message backgrounds has proper color */
|
| 641 |
+
div[style*="background-color"] strong,
|
| 642 |
+
div[style*="background-color"] b {
|
| 643 |
+
color: inherit !important;
|
| 644 |
+
}
|
| 645 |
+
|
| 646 |
+
/* Specific fix for bold text in different message backgrounds */
|
| 647 |
+
div[style*="background-color: #E3F2FD"] strong, /* Team messages */
|
| 648 |
+
div[style*="background-color: #FFF8E1"] strong, /* Commissioner */
|
| 649 |
+
div[style*="background-color: #FFEBEE"] strong, /* Advisor */
|
| 650 |
+
div[style*="background-color: #F3E5F5"] strong { /* Memory */
|
| 651 |
+
color: #1a1a1a !important;
|
| 652 |
+
}
|
| 653 |
+
|
| 654 |
+
/* Inline typing effect */
|
| 655 |
+
.typing-dots {
|
| 656 |
+
animation: pulse 1.0s ease-in-out infinite;
|
| 657 |
+
}
|
| 658 |
+
|
| 659 |
+
@keyframes pulse {
|
| 660 |
+
0%, 100% { opacity: 0.6; }
|
| 661 |
+
50% { opacity: 1; }
|
| 662 |
+
}
|
| 663 |
+
"""
|
| 664 |
+
|
| 665 |
+
return demo
|
| 666 |
+
|
| 667 |
+
|
| 668 |
+
def main():
|
| 669 |
+
"""Launch the Gradio app."""
|
| 670 |
+
import sys
|
| 671 |
+
|
| 672 |
+
# Check for --share flag
|
| 673 |
+
share_mode = "--share" in sys.argv or "-s" in sys.argv
|
| 674 |
+
|
| 675 |
+
# Check for API key
|
| 676 |
+
if not os.getenv("OPENAI_API_KEY"):
|
| 677 |
+
print("\n⚠️ Warning: OPENAI_API_KEY not found in environment variables.")
|
| 678 |
+
print("The app will launch but agent responses will fail without an API key.")
|
| 679 |
+
print("Set it using: export OPENAI_API_KEY='your-key-here'\n")
|
| 680 |
+
|
| 681 |
+
print("🚀 Launching Fantasy Draft Multi-Agent Demo...")
|
| 682 |
+
|
| 683 |
+
if share_mode:
|
| 684 |
+
print("🌐 Creating public share link (expires in 72 hours)...")
|
| 685 |
+
print("📡 Please wait for the public URL...\n")
|
| 686 |
+
else:
|
| 687 |
+
print("📡 The app will be available at http://localhost:7860")
|
| 688 |
+
print("💡 Tip: Use 'python app.py --share' to create a public link\n")
|
| 689 |
+
|
| 690 |
+
demo = create_gradio_interface()
|
| 691 |
+
demo.launch(
|
| 692 |
+
server_name="0.0.0.0",
|
| 693 |
+
server_port=7860,
|
| 694 |
+
share=share_mode, # Enable sharing if flag is present
|
| 695 |
+
show_error=True
|
| 696 |
+
)
|
| 697 |
+
|
| 698 |
+
|
| 699 |
+
if __name__ == "__main__":
|
| 700 |
+
main()
|
apps/app_enhanced.py
ADDED
|
@@ -0,0 +1,1035 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Fantasy Draft Multi-Agent Demo - Enhanced with A2A Support
|
| 4 |
+
Combines the superior UI from the main app with real A2A capabilities
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import os
|
| 8 |
+
import time
|
| 9 |
+
import gradio as gr
|
| 10 |
+
import asyncio
|
| 11 |
+
import nest_asyncio
|
| 12 |
+
from typing import List, Tuple, Optional, Dict
|
| 13 |
+
from dotenv import load_dotenv
|
| 14 |
+
import sys
|
| 15 |
+
import os
|
| 16 |
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 17 |
+
|
| 18 |
+
from core.agent import FantasyDraftAgent
|
| 19 |
+
from core.data import TOP_PLAYERS
|
| 20 |
+
from core.constants import (
|
| 21 |
+
TYPING_DELAY_SECONDS,
|
| 22 |
+
MESSAGE_DELAY_SECONDS,
|
| 23 |
+
AGENT_START_DELAY,
|
| 24 |
+
AGENT_STARTUP_WAIT,
|
| 25 |
+
DEFAULT_TIMEOUT,
|
| 26 |
+
MAX_COMMENTS_PER_PICK,
|
| 27 |
+
RIVAL_PAIRS,
|
| 28 |
+
AGENT_CONFIGS
|
| 29 |
+
)
|
| 30 |
+
from core.a2a_helpers import (
|
| 31 |
+
parse_a2a_response,
|
| 32 |
+
extract_task_id,
|
| 33 |
+
format_available_players
|
| 34 |
+
)
|
| 35 |
+
# Lazy import A2A components to avoid import errors on HF Spaces
|
| 36 |
+
DynamicA2AAgentManager = None
|
| 37 |
+
cleanup_session = None
|
| 38 |
+
|
| 39 |
+
from apps.multiagent_draft import MultiAgentMockDraft
|
| 40 |
+
from apps.multiagent_scenarios import (
|
| 41 |
+
run_interactive_mock_draft,
|
| 42 |
+
format_conversation_block,
|
| 43 |
+
format_agent_message,
|
| 44 |
+
format_memory_indicator,
|
| 45 |
+
create_mock_draft_visualization
|
| 46 |
+
)
|
| 47 |
+
|
| 48 |
+
# A2A components will be imported lazily when needed
|
| 49 |
+
# to avoid import errors on Hugging Face Spaces
|
| 50 |
+
|
| 51 |
+
# Apply nest_asyncio for async in Gradio
|
| 52 |
+
nest_asyncio.apply()
|
| 53 |
+
|
| 54 |
+
# Fix for litellm 1.72.4 OpenAI endpoint issue
|
| 55 |
+
os.environ['OPENAI_API_BASE'] = 'https://api.openai.com/v1'
|
| 56 |
+
|
| 57 |
+
# Load environment variables
|
| 58 |
+
load_dotenv()
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
class EnhancedFantasyDraftApp:
|
| 62 |
+
def __init__(self):
|
| 63 |
+
self.current_draft = None # Store the current mock draft
|
| 64 |
+
self.draft_output = "" # Store the draft output so far
|
| 65 |
+
self.a2a_manager = None # Will be created dynamically with session ID
|
| 66 |
+
self.use_real_a2a = False
|
| 67 |
+
self.a2a_status = "Not initialized"
|
| 68 |
+
self.session_id = None
|
| 69 |
+
|
| 70 |
+
async def toggle_a2a_mode(self, use_a2a: bool):
|
| 71 |
+
"""Toggle between basic multiagent and A2A modes."""
|
| 72 |
+
self.use_real_a2a = use_a2a
|
| 73 |
+
|
| 74 |
+
if use_a2a:
|
| 75 |
+
# Lazy import A2A components only when needed
|
| 76 |
+
try:
|
| 77 |
+
global DynamicA2AAgentManager, cleanup_session
|
| 78 |
+
from core.dynamic_a2a_manager import DynamicA2AAgentManager, cleanup_session
|
| 79 |
+
except ImportError as e:
|
| 80 |
+
self.a2a_status = f"❌ A2A mode not available: {str(e)}. Please use Basic Multiagent mode."
|
| 81 |
+
self.use_real_a2a = False
|
| 82 |
+
return self.a2a_status
|
| 83 |
+
|
| 84 |
+
# Generate unique session ID if needed
|
| 85 |
+
if not self.session_id:
|
| 86 |
+
import uuid
|
| 87 |
+
self.session_id = str(uuid.uuid4())[:8]
|
| 88 |
+
|
| 89 |
+
# Create new dynamic manager for this session
|
| 90 |
+
self.a2a_manager = DynamicA2AAgentManager(self.session_id)
|
| 91 |
+
|
| 92 |
+
try:
|
| 93 |
+
await self.a2a_manager.start_agents()
|
| 94 |
+
ports = self.a2a_manager.allocated_ports
|
| 95 |
+
self.a2a_status = f"✅ A2A Mode Active (Session: {self.session_id}, Ports: {ports[0]}-{ports[-1]})"
|
| 96 |
+
except RuntimeError as e:
|
| 97 |
+
# Failed to allocate ports or start agents
|
| 98 |
+
self.a2a_status = f"❌ Failed to start A2A: {str(e)}"
|
| 99 |
+
self.use_real_a2a = False
|
| 100 |
+
self.a2a_manager = None
|
| 101 |
+
else:
|
| 102 |
+
if self.a2a_manager and cleanup_session:
|
| 103 |
+
await cleanup_session(self.a2a_manager)
|
| 104 |
+
self.a2a_manager = None
|
| 105 |
+
self.a2a_status = "✅ Basic Multiagent Mode Active (Using built-in communication)"
|
| 106 |
+
|
| 107 |
+
return self.a2a_status
|
| 108 |
+
|
| 109 |
+
def run_multiagent_demo(self, use_a2a: bool = False):
|
| 110 |
+
"""Run the mock draft demonstration with optional A2A support."""
|
| 111 |
+
# Reset any previous draft
|
| 112 |
+
self.current_draft = None
|
| 113 |
+
self.draft_output = ""
|
| 114 |
+
|
| 115 |
+
# First, set the mode (like in the working version)
|
| 116 |
+
loop = asyncio.new_event_loop()
|
| 117 |
+
asyncio.set_event_loop(loop)
|
| 118 |
+
|
| 119 |
+
status = loop.run_until_complete(self.toggle_a2a_mode(use_a2a))
|
| 120 |
+
yield f"**Mode:** {status}\n\n"
|
| 121 |
+
|
| 122 |
+
# Initialize draft
|
| 123 |
+
self.current_draft = MultiAgentMockDraft(user_pick_position=4)
|
| 124 |
+
|
| 125 |
+
# Run the appropriate draft
|
| 126 |
+
if use_a2a and self.a2a_manager:
|
| 127 |
+
yield from self.run_a2a_draft()
|
| 128 |
+
else:
|
| 129 |
+
# Use basic multiagent draft
|
| 130 |
+
draft_generator = run_interactive_mock_draft()
|
| 131 |
+
|
| 132 |
+
for output in draft_generator:
|
| 133 |
+
if isinstance(output, tuple):
|
| 134 |
+
# This means it's the user's turn
|
| 135 |
+
self.current_draft, self.draft_output = output
|
| 136 |
+
yield self.draft_output + "\n<!--USER_TURN-->"
|
| 137 |
+
return
|
| 138 |
+
else:
|
| 139 |
+
self.draft_output = output
|
| 140 |
+
yield output
|
| 141 |
+
|
| 142 |
+
def run_a2a_draft(self):
|
| 143 |
+
"""Run draft with A2A communication."""
|
| 144 |
+
# Initialize draft
|
| 145 |
+
self.current_draft = MultiAgentMockDraft(user_pick_position=4)
|
| 146 |
+
self.draft_output = "# 🏈 Mock Draft with A2A Communication\n\n"
|
| 147 |
+
|
| 148 |
+
# Welcome message
|
| 149 |
+
self.draft_output += format_agent_message(
|
| 150 |
+
"commissioner", "ALL",
|
| 151 |
+
"Welcome to the A2A-powered draft! Each agent is running on its own server."
|
| 152 |
+
)
|
| 153 |
+
yield self.draft_output
|
| 154 |
+
|
| 155 |
+
# Run draft rounds
|
| 156 |
+
loop = asyncio.get_event_loop()
|
| 157 |
+
|
| 158 |
+
for round_num in range(1, 4): # 3 rounds
|
| 159 |
+
self.draft_output += f"\n## 🔄 ROUND {round_num}\n\n"
|
| 160 |
+
yield self.draft_output
|
| 161 |
+
|
| 162 |
+
# Snake draft order
|
| 163 |
+
if round_num % 2 == 1:
|
| 164 |
+
pick_order = list(range(1, 7))
|
| 165 |
+
else:
|
| 166 |
+
pick_order = list(range(6, 0, -1))
|
| 167 |
+
|
| 168 |
+
for pick_in_round, team_num in enumerate(pick_order, 1):
|
| 169 |
+
pick_num = (round_num - 1) * 6 + pick_in_round
|
| 170 |
+
|
| 171 |
+
# Show draft board at start of round
|
| 172 |
+
if pick_in_round == 1:
|
| 173 |
+
self.draft_output += create_mock_draft_visualization(self.current_draft, round_num, pick_num)
|
| 174 |
+
self.draft_output += "\n"
|
| 175 |
+
yield self.draft_output
|
| 176 |
+
|
| 177 |
+
if team_num == 4: # User's turn
|
| 178 |
+
# Get advisor recommendation - use user_advisor directly
|
| 179 |
+
advisor = self.current_draft.user_advisor
|
| 180 |
+
|
| 181 |
+
# Get available players
|
| 182 |
+
all_picked = [p for picks in self.current_draft.draft_board.values() for p in picks]
|
| 183 |
+
available = [p for p in TOP_PLAYERS.keys() if p not in all_picked]
|
| 184 |
+
|
| 185 |
+
# Get other agent strategies for advisor context
|
| 186 |
+
strategies = {f"Team {i}": agent.strategy for i, agent in self.current_draft.agents.items()}
|
| 187 |
+
|
| 188 |
+
# Get advisor recommendation
|
| 189 |
+
advice = advisor.advise_user(available, self.current_draft.draft_board, strategies)
|
| 190 |
+
|
| 191 |
+
# Show advisor message
|
| 192 |
+
self.draft_output += format_agent_message(advisor, "USER", advice)
|
| 193 |
+
yield self.draft_output
|
| 194 |
+
|
| 195 |
+
self.draft_output += "\n**⏰ YOU'RE ON THE CLOCK! Type your pick below.**\n\n"
|
| 196 |
+
yield self.draft_output + "\n<!--USER_TURN-->"
|
| 197 |
+
return
|
| 198 |
+
else:
|
| 199 |
+
# A2A agent pick
|
| 200 |
+
messages = loop.run_until_complete(
|
| 201 |
+
self.run_a2a_draft_turn(team_num, round_num, pick_num)
|
| 202 |
+
)
|
| 203 |
+
|
| 204 |
+
# Display messages with typing effect
|
| 205 |
+
for msg in messages:
|
| 206 |
+
if len(msg) >= 3:
|
| 207 |
+
agent, recipient, content = msg[:3]
|
| 208 |
+
|
| 209 |
+
# Show "..." first for typing effect
|
| 210 |
+
typing_placeholder = format_agent_message(agent, recipient, "...")
|
| 211 |
+
self.draft_output += typing_placeholder
|
| 212 |
+
yield self.draft_output
|
| 213 |
+
time.sleep(TYPING_DELAY_SECONDS)
|
| 214 |
+
|
| 215 |
+
# Replace with actual message
|
| 216 |
+
self.draft_output = self.draft_output.replace(typing_placeholder, "")
|
| 217 |
+
self.draft_output += format_agent_message(agent, recipient, content)
|
| 218 |
+
yield self.draft_output
|
| 219 |
+
time.sleep(MESSAGE_DELAY_SECONDS)
|
| 220 |
+
|
| 221 |
+
time.sleep(TYPING_DELAY_SECONDS)
|
| 222 |
+
|
| 223 |
+
# End of round
|
| 224 |
+
self.draft_output += format_agent_message("commissioner", "ALL",
|
| 225 |
+
f"That's the end of Round {round_num}!")
|
| 226 |
+
yield self.draft_output
|
| 227 |
+
|
| 228 |
+
# Final summary
|
| 229 |
+
self.draft_output += "\n## 📊 FINAL RESULTS\n\n"
|
| 230 |
+
self.draft_output += self.current_draft.get_draft_summary()
|
| 231 |
+
yield self.draft_output
|
| 232 |
+
|
| 233 |
+
# Clear the draft state
|
| 234 |
+
self.current_draft = None
|
| 235 |
+
|
| 236 |
+
async def run_a2a_draft_turn(self, team_num: int, round_num: int, pick_num: int):
|
| 237 |
+
"""Run a draft turn using A2A."""
|
| 238 |
+
messages = []
|
| 239 |
+
|
| 240 |
+
# Commissioner announcement
|
| 241 |
+
messages.append((
|
| 242 |
+
self.current_draft.commissioner,
|
| 243 |
+
"ALL",
|
| 244 |
+
f"Team {team_num} is on the clock!"
|
| 245 |
+
))
|
| 246 |
+
|
| 247 |
+
# Get available players
|
| 248 |
+
all_picked = [p for picks in self.current_draft.draft_board.values() for p in picks]
|
| 249 |
+
available = [p for p in TOP_PLAYERS.keys() if p not in all_picked]
|
| 250 |
+
|
| 251 |
+
# Get pick from A2A agent
|
| 252 |
+
previous_picks = self.current_draft.draft_board.get(team_num, [])
|
| 253 |
+
pick_result = await self.a2a_manager.get_pick(team_num, available, previous_picks, round_num)
|
| 254 |
+
|
| 255 |
+
if not pick_result or pick_result.type != "pick":
|
| 256 |
+
# Fallback to simulation
|
| 257 |
+
messages.append((
|
| 258 |
+
self.current_draft.commissioner,
|
| 259 |
+
"ALL",
|
| 260 |
+
f"⚠️ Team {team_num} A2A agent not responding - using simulation"
|
| 261 |
+
))
|
| 262 |
+
|
| 263 |
+
sim_messages, _ = self.current_draft.simulate_draft_turn(round_num, pick_num, team_num)
|
| 264 |
+
messages.extend(sim_messages)
|
| 265 |
+
return messages
|
| 266 |
+
|
| 267 |
+
# Make the pick
|
| 268 |
+
player = pick_result.player_name
|
| 269 |
+
self.current_draft.draft_board[team_num].append(player)
|
| 270 |
+
|
| 271 |
+
# Update agent's picks if it exists
|
| 272 |
+
agent = self.current_draft.agents.get(team_num)
|
| 273 |
+
if agent:
|
| 274 |
+
agent.picks.append(player)
|
| 275 |
+
|
| 276 |
+
# Commissioner announcement of pick
|
| 277 |
+
pick_num = len([p for picks in self.current_draft.draft_board.values() for p in picks])
|
| 278 |
+
confirm_msg = self.current_draft.commissioner.confirm_pick(
|
| 279 |
+
agent.team_name if agent else f"Team {team_num}",
|
| 280 |
+
player,
|
| 281 |
+
pick_num
|
| 282 |
+
)
|
| 283 |
+
messages.append((self.current_draft.commissioner, "ALL", confirm_msg))
|
| 284 |
+
|
| 285 |
+
# Agent explains reasoning
|
| 286 |
+
messages.append((
|
| 287 |
+
agent if agent else "system",
|
| 288 |
+
"ALL",
|
| 289 |
+
f"{pick_result.reasoning}"
|
| 290 |
+
))
|
| 291 |
+
|
| 292 |
+
if pick_result.trash_talk:
|
| 293 |
+
messages.append((
|
| 294 |
+
agent if agent else "system",
|
| 295 |
+
"ALL",
|
| 296 |
+
pick_result.trash_talk
|
| 297 |
+
))
|
| 298 |
+
|
| 299 |
+
# Get comments from other A2A agents (limit to 2 comments)
|
| 300 |
+
potential_commenters = [t for t in [1, 2, 3, 5, 6] if t != team_num and t != 4]
|
| 301 |
+
|
| 302 |
+
# Sort commenters to prioritize rivals
|
| 303 |
+
if team_num in RIVAL_PAIRS:
|
| 304 |
+
rivals = RIVAL_PAIRS[team_num]
|
| 305 |
+
if isinstance(rivals, int):
|
| 306 |
+
rivals = [rivals]
|
| 307 |
+
# Put rivals first in the list
|
| 308 |
+
prioritized_commenters = [t for t in rivals if t in potential_commenters]
|
| 309 |
+
prioritized_commenters.extend([t for t in potential_commenters if t not in prioritized_commenters])
|
| 310 |
+
potential_commenters = prioritized_commenters
|
| 311 |
+
|
| 312 |
+
# Collect comments up to the configured limit
|
| 313 |
+
comment_count = 0
|
| 314 |
+
max_comments = self.a2a_manager.max_comments_per_pick
|
| 315 |
+
|
| 316 |
+
for other_team in potential_commenters:
|
| 317 |
+
if comment_count >= max_comments:
|
| 318 |
+
break
|
| 319 |
+
|
| 320 |
+
comment = await self.a2a_manager.get_comment(other_team, team_num, player, round_num)
|
| 321 |
+
if comment:
|
| 322 |
+
other_agent = self.current_draft.agents.get(other_team)
|
| 323 |
+
if other_agent:
|
| 324 |
+
# Use the same pattern as earlier for the picking agent's name
|
| 325 |
+
picking_agent_name = agent.team_name if agent else f"Team {team_num}"
|
| 326 |
+
messages.append((
|
| 327 |
+
other_agent,
|
| 328 |
+
picking_agent_name,
|
| 329 |
+
comment
|
| 330 |
+
))
|
| 331 |
+
comment_count += 1
|
| 332 |
+
|
| 333 |
+
return messages
|
| 334 |
+
|
| 335 |
+
def continue_mock_draft(self, player_name: str):
|
| 336 |
+
"""Continue the mock draft after user makes a pick."""
|
| 337 |
+
if not self.current_draft:
|
| 338 |
+
yield "No active draft. Please start a new mock draft."
|
| 339 |
+
return
|
| 340 |
+
|
| 341 |
+
if not player_name:
|
| 342 |
+
yield self.draft_output + "\n\n⚠️ Please enter a player name!"
|
| 343 |
+
return
|
| 344 |
+
|
| 345 |
+
# Make the user's pick
|
| 346 |
+
messages = self.current_draft.make_user_pick(player_name)
|
| 347 |
+
|
| 348 |
+
# Display messages with inline typing effect
|
| 349 |
+
for msg in messages:
|
| 350 |
+
if len(msg) >= 3:
|
| 351 |
+
agent, recipient, content = msg[:3]
|
| 352 |
+
|
| 353 |
+
# Check if it's a typing indicator - skip it
|
| 354 |
+
if isinstance(agent, str) and agent.startswith("typing_"):
|
| 355 |
+
continue
|
| 356 |
+
else:
|
| 357 |
+
# Show "..." first for typing effect
|
| 358 |
+
typing_placeholder = format_agent_message(agent, recipient, "...")
|
| 359 |
+
self.draft_output += typing_placeholder
|
| 360 |
+
yield self.draft_output
|
| 361 |
+
time.sleep(TYPING_DELAY_SECONDS)
|
| 362 |
+
|
| 363 |
+
# Replace "..." with actual message
|
| 364 |
+
self.draft_output = self.draft_output.replace(typing_placeholder, "")
|
| 365 |
+
self.draft_output += format_agent_message(agent, recipient, content)
|
| 366 |
+
yield self.draft_output
|
| 367 |
+
time.sleep(MESSAGE_DELAY_SECONDS)
|
| 368 |
+
|
| 369 |
+
# Continue with the rest of the draft
|
| 370 |
+
if self.use_real_a2a and self.a2a_manager:
|
| 371 |
+
yield from self.continue_a2a_draft()
|
| 372 |
+
else:
|
| 373 |
+
yield from self.continue_basic_multiagent_draft()
|
| 374 |
+
|
| 375 |
+
def continue_a2a_draft(self):
|
| 376 |
+
"""Continue A2A draft after user pick."""
|
| 377 |
+
# Calculate where we are
|
| 378 |
+
total_picks = len([p for picks in self.current_draft.draft_board.values() for p in picks])
|
| 379 |
+
current_round = ((total_picks - 1) // 6) + 1
|
| 380 |
+
|
| 381 |
+
loop = asyncio.get_event_loop()
|
| 382 |
+
|
| 383 |
+
# Continue from current position
|
| 384 |
+
for round_num in range(current_round, 4):
|
| 385 |
+
if round_num > current_round:
|
| 386 |
+
self.draft_output += f"\n## 🔄 ROUND {round_num}\n\n"
|
| 387 |
+
yield self.draft_output
|
| 388 |
+
|
| 389 |
+
# Snake draft order
|
| 390 |
+
if round_num % 2 == 1:
|
| 391 |
+
pick_order = list(range(1, 7))
|
| 392 |
+
else:
|
| 393 |
+
pick_order = list(range(6, 0, -1))
|
| 394 |
+
|
| 395 |
+
# Calculate where we are in the current round
|
| 396 |
+
picks_in_round = total_picks % 6
|
| 397 |
+
start_idx = picks_in_round if round_num == current_round else 0
|
| 398 |
+
|
| 399 |
+
for pick_in_round, team_num in enumerate(list(pick_order)[start_idx:], start_idx + 1):
|
| 400 |
+
pick_num = (round_num - 1) * 6 + pick_in_round
|
| 401 |
+
|
| 402 |
+
# Show draft board at start of round
|
| 403 |
+
if pick_in_round == 1:
|
| 404 |
+
self.draft_output += create_mock_draft_visualization(self.current_draft, round_num, pick_num)
|
| 405 |
+
self.draft_output += "\n"
|
| 406 |
+
yield self.draft_output
|
| 407 |
+
|
| 408 |
+
if team_num == 4: # User's turn again
|
| 409 |
+
# Get advisor recommendation - use user_advisor directly
|
| 410 |
+
advisor = self.current_draft.user_advisor
|
| 411 |
+
|
| 412 |
+
all_picked = [p for picks in self.current_draft.draft_board.values() for p in picks]
|
| 413 |
+
available = [p for p in TOP_PLAYERS.keys() if p not in all_picked]
|
| 414 |
+
|
| 415 |
+
# Get other agent strategies for advisor context
|
| 416 |
+
strategies = {f"Team {i}": agent.strategy for i, agent in self.current_draft.agents.items()}
|
| 417 |
+
|
| 418 |
+
advice = advisor.advise_user(available, self.current_draft.draft_board, strategies)
|
| 419 |
+
self.draft_output += format_agent_message(advisor, "USER", advice)
|
| 420 |
+
yield self.draft_output
|
| 421 |
+
|
| 422 |
+
self.draft_output += "\n**⏰ YOU'RE ON THE CLOCK! Type your pick below.**\n\n"
|
| 423 |
+
yield self.draft_output + "\n<!--USER_TURN-->"
|
| 424 |
+
return
|
| 425 |
+
else:
|
| 426 |
+
# A2A agent pick
|
| 427 |
+
messages = loop.run_until_complete(
|
| 428 |
+
self.run_a2a_draft_turn(team_num, round_num, pick_num)
|
| 429 |
+
)
|
| 430 |
+
|
| 431 |
+
for msg in messages:
|
| 432 |
+
if len(msg) >= 3:
|
| 433 |
+
agent, recipient, content = msg[:3]
|
| 434 |
+
typing_placeholder = format_agent_message(agent, recipient, "...")
|
| 435 |
+
self.draft_output += typing_placeholder
|
| 436 |
+
yield self.draft_output
|
| 437 |
+
time.sleep(TYPING_DELAY_SECONDS)
|
| 438 |
+
|
| 439 |
+
self.draft_output = self.draft_output.replace(typing_placeholder, "")
|
| 440 |
+
self.draft_output += format_agent_message(agent, recipient, content)
|
| 441 |
+
yield self.draft_output
|
| 442 |
+
time.sleep(MESSAGE_DELAY_SECONDS)
|
| 443 |
+
|
| 444 |
+
time.sleep(TYPING_DELAY_SECONDS)
|
| 445 |
+
|
| 446 |
+
self.draft_output += format_agent_message("commissioner", "ALL",
|
| 447 |
+
f"That's the end of Round {round_num}!")
|
| 448 |
+
yield self.draft_output
|
| 449 |
+
|
| 450 |
+
# Final summary
|
| 451 |
+
self.draft_output += "\n## 📊 FINAL RESULTS\n\n"
|
| 452 |
+
self.draft_output += self.current_draft.get_draft_summary()
|
| 453 |
+
yield self.draft_output
|
| 454 |
+
|
| 455 |
+
self.current_draft = None
|
| 456 |
+
|
| 457 |
+
def continue_basic_multiagent_draft(self):
|
| 458 |
+
"""Continue basic multiagent draft after user pick."""
|
| 459 |
+
# This is the original logic from app.py
|
| 460 |
+
total_picks = len([p for picks in self.current_draft.draft_board.values() for p in picks])
|
| 461 |
+
current_round = ((total_picks - 1) // 6) + 1
|
| 462 |
+
|
| 463 |
+
draft_memories = []
|
| 464 |
+
|
| 465 |
+
for round_num in range(current_round, 4):
|
| 466 |
+
if round_num > current_round:
|
| 467 |
+
self.draft_output += f"\n## 🔄 ROUND {round_num}\n\n"
|
| 468 |
+
yield self.draft_output
|
| 469 |
+
|
| 470 |
+
if round_num % 2 == 1:
|
| 471 |
+
pick_order = list(range(1, 7))
|
| 472 |
+
else:
|
| 473 |
+
pick_order = list(range(6, 0, -1))
|
| 474 |
+
|
| 475 |
+
picks_in_round = total_picks % 6
|
| 476 |
+
start_idx = picks_in_round if round_num == current_round else 0
|
| 477 |
+
|
| 478 |
+
for pick_in_round, team_num in enumerate(list(pick_order)[start_idx:], start_idx + 1):
|
| 479 |
+
pick_num = (round_num - 1) * 6 + pick_in_round
|
| 480 |
+
|
| 481 |
+
if pick_in_round == 1:
|
| 482 |
+
self.draft_output += create_mock_draft_visualization(self.current_draft, round_num, pick_num)
|
| 483 |
+
self.draft_output += "\n"
|
| 484 |
+
yield self.draft_output
|
| 485 |
+
|
| 486 |
+
messages, result = self.current_draft.simulate_draft_turn(round_num, pick_num, team_num)
|
| 487 |
+
|
| 488 |
+
for msg in messages:
|
| 489 |
+
if len(msg) >= 3:
|
| 490 |
+
agent, recipient, content = msg[:3]
|
| 491 |
+
|
| 492 |
+
if isinstance(agent, str) and agent.startswith("typing_"):
|
| 493 |
+
continue
|
| 494 |
+
else:
|
| 495 |
+
typing_placeholder = format_agent_message(agent, recipient, "...")
|
| 496 |
+
self.draft_output += typing_placeholder
|
| 497 |
+
yield self.draft_output
|
| 498 |
+
time.sleep(TYPING_DELAY_SECONDS)
|
| 499 |
+
|
| 500 |
+
self.draft_output = self.draft_output.replace(typing_placeholder, "")
|
| 501 |
+
self.draft_output += format_agent_message(agent, recipient, content)
|
| 502 |
+
yield self.draft_output
|
| 503 |
+
time.sleep(MESSAGE_DELAY_SECONDS)
|
| 504 |
+
|
| 505 |
+
if result is None:
|
| 506 |
+
self.draft_output += "\n**⏰ YOU'RE ON THE CLOCK! Type your pick below.**\n\n"
|
| 507 |
+
yield self.draft_output + "\n<!--USER_TURN-->"
|
| 508 |
+
return
|
| 509 |
+
|
| 510 |
+
if round_num > 1 and pick_in_round % 2 == 0:
|
| 511 |
+
if team_num in self.current_draft.agents:
|
| 512 |
+
agent = self.current_draft.agents[team_num]
|
| 513 |
+
if len(agent.picks) > 1:
|
| 514 |
+
memory = f"{agent.team_name} has drafted: {', '.join(agent.picks)}"
|
| 515 |
+
draft_memories.append(memory)
|
| 516 |
+
|
| 517 |
+
if draft_memories:
|
| 518 |
+
self.draft_output += format_memory_indicator(round_num, draft_memories[-2:])
|
| 519 |
+
yield self.draft_output
|
| 520 |
+
|
| 521 |
+
time.sleep(TYPING_DELAY_SECONDS)
|
| 522 |
+
|
| 523 |
+
self.draft_output += format_agent_message("commissioner", "ALL",
|
| 524 |
+
f"That's the end of Round {round_num}!")
|
| 525 |
+
yield self.draft_output
|
| 526 |
+
|
| 527 |
+
self.draft_output += "\n## 📊 FINAL RESULTS\n\n"
|
| 528 |
+
self.draft_output += self.current_draft.get_draft_summary()
|
| 529 |
+
yield self.draft_output
|
| 530 |
+
|
| 531 |
+
self.current_draft = None
|
| 532 |
+
|
| 533 |
+
|
| 534 |
+
def create_gradio_interface():
|
| 535 |
+
"""Create the main Gradio interface with A2A support."""
|
| 536 |
+
|
| 537 |
+
with gr.Blocks(title="Fantasy Draft Multi-Agent Demo", theme=gr.themes.Soft()) as demo:
|
| 538 |
+
# Create state for each user session
|
| 539 |
+
app_state = gr.State(None)
|
| 540 |
+
|
| 541 |
+
with gr.Column(elem_id="main-container"):
|
| 542 |
+
gr.Markdown("""
|
| 543 |
+
# 🏈 Fantasy Draft Multi-Agent Demo
|
| 544 |
+
|
| 545 |
+
**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.
|
| 546 |
+
""")
|
| 547 |
+
|
| 548 |
+
with gr.Tabs():
|
| 549 |
+
# Demo Tab
|
| 550 |
+
with gr.TabItem("🎮 Demo"):
|
| 551 |
+
# Add A2A Mode Toggle
|
| 552 |
+
with gr.Row():
|
| 553 |
+
with gr.Column():
|
| 554 |
+
gr.Markdown("### 🔧 Communication Mode")
|
| 555 |
+
communication_mode = gr.Radio(
|
| 556 |
+
["Basic Multiagent", "A2A"],
|
| 557 |
+
value="Basic Multiagent",
|
| 558 |
+
label="Select how agents communicate",
|
| 559 |
+
info="Basic Multiagent: Fast, reliable (Recommended) | A2A: Distributed agents (Advanced)"
|
| 560 |
+
)
|
| 561 |
+
mode_info = gr.Markdown(
|
| 562 |
+
"""
|
| 563 |
+
**Basic Multiagent** (Recommended for HF Spaces): Fast, single-process execution (✅ Multi-user safe)
|
| 564 |
+
**A2A**: Distributed agents with dynamic ports (Experimental on HF Spaces)
|
| 565 |
+
|
| 566 |
+
*If A2A mode fails to start, please use Basic Multiagent mode instead.*
|
| 567 |
+
"""
|
| 568 |
+
)
|
| 569 |
+
|
| 570 |
+
# Add A2A test button for debugging
|
| 571 |
+
with gr.Accordion("🔧 A2A Debugging (Advanced)", open=False):
|
| 572 |
+
test_a2a_btn = gr.Button("Test A2A Dependencies & Ports", size="sm")
|
| 573 |
+
a2a_test_output = gr.Textbox(label="Test Results", lines=10, interactive=False)
|
| 574 |
+
|
| 575 |
+
# Show agent cards
|
| 576 |
+
gr.Markdown("""
|
| 577 |
+
### 🏈 Meet Your Competition
|
| 578 |
+
|
| 579 |
+
You'll be drafting at **Position 4** with these AI opponents:
|
| 580 |
+
""")
|
| 581 |
+
|
| 582 |
+
# Agent cards in a grid - all in one row
|
| 583 |
+
with gr.Row():
|
| 584 |
+
with gr.Column(scale=1):
|
| 585 |
+
gr.Markdown("""
|
| 586 |
+
<div style="background-color: #E3F2FD; border-left: 4px solid #1976D2; padding: 15px; border-radius: 8px;">
|
| 587 |
+
|
| 588 |
+
<h4 style="color: #0d47a1; margin: 0 0 10px 0;">📘🤓 Team 1 - Zero RB</h4>
|
| 589 |
+
|
| 590 |
+
<p style="color: #424242; font-style: italic; margin: 10px 0; font-size: 0.95em;">"RBs get injured. I'll build around elite WRs."</p>
|
| 591 |
+
|
| 592 |
+
<ul style="color: #424242; font-size: 0.9em; margin: 0; padding-left: 20px;">
|
| 593 |
+
<li style="color: #424242;">Avoids RBs early</li>
|
| 594 |
+
<li style="color: #424242;">Loads up on WRs</li>
|
| 595 |
+
<li style="color: #424242;">Gets RB value late</li>
|
| 596 |
+
</ul>
|
| 597 |
+
</div>
|
| 598 |
+
""")
|
| 599 |
+
|
| 600 |
+
with gr.Column(scale=1):
|
| 601 |
+
gr.Markdown("""
|
| 602 |
+
<div style="background-color: #E8F5E9; border-left: 4px solid #388E3C; padding: 15px; border-radius: 8px;">
|
| 603 |
+
|
| 604 |
+
<h4 style="color: #1b5e20; margin: 0 0 10px 0;">📗🧑💼 Team 2 - BPA</h4>
|
| 605 |
+
|
| 606 |
+
<p style="color: #424242; font-style: italic; margin: 10px 0; font-size: 0.95em;">"Value is value. I don't reach for needs."</p>
|
| 607 |
+
|
| 608 |
+
<ul style="color: #424242; font-size: 0.9em; margin: 0; padding-left: 20px;">
|
| 609 |
+
<li style="color: #424242;">Pure value drafting</li>
|
| 610 |
+
<li style="color: #424242;">Ignores needs</li>
|
| 611 |
+
<li style="color: #424242;">Mocks reaching</li>
|
| 612 |
+
</ul>
|
| 613 |
+
</div>
|
| 614 |
+
""")
|
| 615 |
+
|
| 616 |
+
with gr.Column(scale=1):
|
| 617 |
+
gr.Markdown("""
|
| 618 |
+
<div style="background-color: #FFF3E0; border-left: 4px solid #F57C00; padding: 15px; border-radius: 8px;">
|
| 619 |
+
|
| 620 |
+
<h4 style="color: #e65100; margin: 0 0 10px 0;">📙🧔 Team 3 - Robust RB</h4>
|
| 621 |
+
|
| 622 |
+
<p style="color: #424242; font-style: italic; margin: 10px 0; font-size: 0.95em;">"RBs win championships. Period."</p>
|
| 623 |
+
|
| 624 |
+
<ul style="color: #424242; font-size: 0.9em; margin: 0; padding-left: 20px;">
|
| 625 |
+
<li style="color: #424242;">RBs in rounds 1-2</li>
|
| 626 |
+
<li style="color: #424242;">Old-school approach</li>
|
| 627 |
+
<li style="color: #424242;">Foundation first</li>
|
| 628 |
+
</ul>
|
| 629 |
+
</div>
|
| 630 |
+
""")
|
| 631 |
+
|
| 632 |
+
with gr.Column(scale=1):
|
| 633 |
+
gr.Markdown("""
|
| 634 |
+
<div style="background-color: #E8EAF6; border-left: 4px solid #3F51B5; padding: 15px; border-radius: 8px;">
|
| 635 |
+
|
| 636 |
+
<h4 style="color: #1a237e; margin: 0 0 10px 0;">👤 Position 4 - YOU</h4>
|
| 637 |
+
|
| 638 |
+
<p style="color: #424242; font-style: italic; margin: 10px 0; font-size: 0.95em;">Your draft position with AI guidance</p>
|
| 639 |
+
|
| 640 |
+
<ul style="color: #424242; font-size: 0.9em; margin: 0; padding-left: 20px;">
|
| 641 |
+
<li style="color: #424242;">📕🧙 Strategic advisor</li>
|
| 642 |
+
<li style="color: #424242;">Real-time guidance</li>
|
| 643 |
+
<li style="color: #424242;">Roster analysis</li>
|
| 644 |
+
</ul>
|
| 645 |
+
</div>
|
| 646 |
+
""")
|
| 647 |
+
|
| 648 |
+
with gr.Column(scale=1):
|
| 649 |
+
gr.Markdown("""
|
| 650 |
+
<div style="background-color: #F5E6FF; border-left: 4px solid #7B1FA2; padding: 15px; border-radius: 8px;">
|
| 651 |
+
|
| 652 |
+
<h4 style="color: #4a148c; margin: 0 0 10px 0;">📓🤠 Team 5 - Upside</h4>
|
| 653 |
+
|
| 654 |
+
<p style="color: #424242; font-style: italic; margin: 10px 0; font-size: 0.95em;">"Safe picks are for losers!"</p>
|
| 655 |
+
|
| 656 |
+
<ul style="color: #424242; font-size: 0.9em; margin: 0; padding-left: 20px;">
|
| 657 |
+
<li style="color: #424242;">Seeks breakouts</li>
|
| 658 |
+
<li style="color: #424242;">High risk/reward</li>
|
| 659 |
+
<li style="color: #424242;">Mocks safety</li>
|
| 660 |
+
</ul>
|
| 661 |
+
</div>
|
| 662 |
+
""")
|
| 663 |
+
|
| 664 |
+
with gr.Column(scale=1):
|
| 665 |
+
gr.Markdown("""
|
| 666 |
+
<div style="background-color: #E8F5E9; border-left: 4px solid #388E3C; padding: 15px; border-radius: 8px;">
|
| 667 |
+
|
| 668 |
+
<h4 style="color: #1b5e20; margin: 0 0 10px 0;">📗👨🏫 Team 6 - BPA</h4>
|
| 669 |
+
|
| 670 |
+
<p style="color: #424242; font-style: italic; margin: 10px 0; font-size: 0.95em;">"Another value drafter to punish reaches."</p>
|
| 671 |
+
|
| 672 |
+
<ul style="color: #424242; font-size: 0.9em; margin: 0; padding-left: 20px;">
|
| 673 |
+
<li style="color: #424242;">Takes obvious value</li>
|
| 674 |
+
<li style="color: #424242;">Disciplined approach</li>
|
| 675 |
+
<li style="color: #424242;">No sentiment</li>
|
| 676 |
+
</ul>
|
| 677 |
+
</div>
|
| 678 |
+
""")
|
| 679 |
+
|
| 680 |
+
gr.Markdown("""
|
| 681 |
+
### 🎮 Draft Format
|
| 682 |
+
* **3 Rounds** of snake draft (1→6, 6→1, 1→6)
|
| 683 |
+
* **Real-time trash talk** between picks
|
| 684 |
+
* **Strategic advisor** guides your selections
|
| 685 |
+
* **Memory system** - agents remember and reference earlier picks
|
| 686 |
+
|
| 687 |
+
Ready to experience the most realistic AI draft room?
|
| 688 |
+
""")
|
| 689 |
+
|
| 690 |
+
# Start button at the bottom
|
| 691 |
+
with gr.Row():
|
| 692 |
+
with gr.Column():
|
| 693 |
+
run_multiagent_btn = gr.Button("🏈 Start Mock Draft", variant="primary", size="lg", elem_id="start-button")
|
| 694 |
+
|
| 695 |
+
# Main output area
|
| 696 |
+
multiagent_output = gr.Markdown(elem_classes=["multiagent-output"])
|
| 697 |
+
|
| 698 |
+
# Mock draft interaction (hidden until needed)
|
| 699 |
+
with gr.Row(visible=False) as mock_draft_controls:
|
| 700 |
+
with gr.Column():
|
| 701 |
+
draft_pick_input = gr.Textbox(
|
| 702 |
+
label="Your Pick",
|
| 703 |
+
placeholder="Type player name and press Enter (e.g., 'Justin Jefferson')",
|
| 704 |
+
elem_id="draft-pick-input"
|
| 705 |
+
)
|
| 706 |
+
submit_pick_btn = gr.Button("Submit Pick", variant="primary")
|
| 707 |
+
|
| 708 |
+
# Available players display
|
| 709 |
+
with gr.Accordion("📋 Available Players", visible=False) as available_accordion:
|
| 710 |
+
available_players_display = gr.Textbox(
|
| 711 |
+
label="Top 20 Available",
|
| 712 |
+
lines=15,
|
| 713 |
+
interactive=False
|
| 714 |
+
)
|
| 715 |
+
|
| 716 |
+
# How It Works Tab
|
| 717 |
+
with gr.TabItem("🔧 How It Works"):
|
| 718 |
+
gr.Markdown("""
|
| 719 |
+
## Technical Implementation
|
| 720 |
+
|
| 721 |
+
This demo showcases advanced multi-agent capabilities using the **any-agent framework**.
|
| 722 |
+
|
| 723 |
+
### 🤖 Framework: any-agent (TinyAgent)
|
| 724 |
+
|
| 725 |
+
- **Lightweight**: < 100 lines of core agent code
|
| 726 |
+
- **Flexible**: Supports multiple LLM providers (OpenAI, Anthropic, etc.)
|
| 727 |
+
- **Multi-turn ready**: Built-in conversation history management
|
| 728 |
+
- **Model**: GPT-4 (configurable)
|
| 729 |
+
|
| 730 |
+
### 🧠 Multi-Turn Memory System
|
| 731 |
+
|
| 732 |
+
Each agent maintains:
|
| 733 |
+
- **Conversation History**: Full context of all interactions
|
| 734 |
+
- **Draft State**: Current picks, available players, round info
|
| 735 |
+
- **Strategy Memory**: Remembers own strategy and others' approaches
|
| 736 |
+
- **Pick History**: Tracks all selections for informed decisions
|
| 737 |
+
|
| 738 |
+
### 💬 Agent-to-Agent (A2A) Communication
|
| 739 |
+
|
| 740 |
+
**Two Modes Available:**
|
| 741 |
+
|
| 742 |
+
#### 1. A2A Mode (Default)
|
| 743 |
+
- **Distributed Architecture**: Each agent runs on its own HTTP server
|
| 744 |
+
- **Dynamic Ports**: Each session gets unique ports automatically
|
| 745 |
+
- **True Isolation**: No shared memory, HTTP communication only
|
| 746 |
+
- **Production Ready**: Scalable to multiple machines
|
| 747 |
+
- **Uses a2a_tool_async**: Official any-agent A2A protocol
|
| 748 |
+
|
| 749 |
+
#### 2. Basic Multiagent Mode
|
| 750 |
+
- Single process, direct method calls
|
| 751 |
+
- Shared memory between agents
|
| 752 |
+
- Fast execution, simple debugging
|
| 753 |
+
- Perfect for quick testing and development
|
| 754 |
+
|
| 755 |
+
### 📊 Architecture Flow
|
| 756 |
+
""")
|
| 757 |
+
|
| 758 |
+
gr.Markdown("""
|
| 759 |
+
#### 1️⃣ INITIALIZATION
|
| 760 |
+
User clicks "Start Mock Draft" → System creates 6 agents
|
| 761 |
+
|
| 762 |
+
#### 2️⃣ AGENT SETUP
|
| 763 |
+
- **Team 1**: Zero RB Strategy
|
| 764 |
+
- **Team 2**: Best Player Available
|
| 765 |
+
- **Team 3**: Robust RB Strategy
|
| 766 |
+
- **YOU**: Position 4 (with Advisor)
|
| 767 |
+
- **Team 5**: Upside Hunter
|
| 768 |
+
- **Team 6**: Best Player Available
|
| 769 |
+
|
| 770 |
+
#### 3️⃣ DRAFT FLOW (3 Rounds)
|
| 771 |
+
- **Round 1**: Pick Order 1→2→3→YOU→5→6
|
| 772 |
+
- **Round 2**: Pick Order 6→5→YOU→3→2→1 (Snake)
|
| 773 |
+
- **Round 3**: Pick Order 1→2→3→YOU→5→6
|
| 774 |
+
|
| 775 |
+
#### 4️⃣ EACH PICK TRIGGERS
|
| 776 |
+
- Agent makes selection based on strategy
|
| 777 |
+
- Other agents comment (A2A communication)
|
| 778 |
+
- Original agent may respond
|
| 779 |
+
- All agents update their memory
|
| 780 |
+
|
| 781 |
+
#### 5️⃣ USER'S TURN
|
| 782 |
+
- Advisor analyzes draft state
|
| 783 |
+
- User sees available players
|
| 784 |
+
- User makes pick
|
| 785 |
+
- All agents react to user's choice
|
| 786 |
+
|
| 787 |
+
#### 6️⃣ MEMORY & CONTEXT
|
| 788 |
+
- Each agent remembers all picks
|
| 789 |
+
- Agents reference earlier conversations
|
| 790 |
+
- Strategies adapt based on draft flow
|
| 791 |
+
- Visual memory indicators show retention
|
| 792 |
+
""")
|
| 793 |
+
|
| 794 |
+
gr.Markdown("""
|
| 795 |
+
### 🎯 Key Features Demonstrated
|
| 796 |
+
|
| 797 |
+
1. **Persistent Context**: Each agent remembers all previous interactions
|
| 798 |
+
2. **Strategic Personalities**: 5 distinct draft strategies competing
|
| 799 |
+
3. **Dynamic Adaptation**: Agents adjust based on draft progression
|
| 800 |
+
4. **Natural Dialogue**: Human-like commentary and debates
|
| 801 |
+
5. **User Integration**: Seamless human participation with AI guidance
|
| 802 |
+
6. **A2A Communication**: Toggle between basic multiagent and distributed A2A modes
|
| 803 |
+
|
| 804 |
+
### 📝 Implementation Details
|
| 805 |
+
|
| 806 |
+
- **Agent Classes**: Inheritance-based design with base `DraftAgent`
|
| 807 |
+
- **Message Formatting**: Custom HTML/CSS for visual distinction
|
| 808 |
+
- **State Management**: Draft board tracking and validation
|
| 809 |
+
- **Memory Indicators**: Visual cues showing context retention
|
| 810 |
+
- **A2A Protocol**: Uses any-agent's a2a_tool_async for distributed communication
|
| 811 |
+
|
| 812 |
+
### 🚀 Why This Matters
|
| 813 |
+
|
| 814 |
+
This demo proves that sophisticated multi-agent systems can be built with minimal code,
|
| 815 |
+
showcasing the power of modern LLMs when properly orchestrated. The any-agent framework
|
| 816 |
+
makes it easy to create agents that truly communicate and remember, not just respond.
|
| 817 |
+
|
| 818 |
+
The A2A mode demonstrates how the same agent logic can seamlessly transition from
|
| 819 |
+
a simple in-memory simulation to a production-ready distributed system.
|
| 820 |
+
""")
|
| 821 |
+
|
| 822 |
+
# Function to check if it's user's turn and show/hide controls
|
| 823 |
+
def check_user_turn(output_text, app):
|
| 824 |
+
"""Check if output indicates it's user's turn."""
|
| 825 |
+
if "<!--USER_TURN-->" in output_text:
|
| 826 |
+
# Remove the marker from display
|
| 827 |
+
clean_output = output_text.replace("<!--USER_TURN-->", "")
|
| 828 |
+
# Get available players
|
| 829 |
+
if app and app.current_draft:
|
| 830 |
+
available = app.current_draft.get_available_players()
|
| 831 |
+
available_text = "Available Players:\n\n"
|
| 832 |
+
for player in sorted(available)[:20]: # Show top 20
|
| 833 |
+
if player in TOP_PLAYERS:
|
| 834 |
+
info = TOP_PLAYERS[player]
|
| 835 |
+
available_text += f"• {player} ({info['pos']}, {info['team']})\n"
|
| 836 |
+
else:
|
| 837 |
+
available_text = "No draft active"
|
| 838 |
+
|
| 839 |
+
return (
|
| 840 |
+
clean_output, # Clean output
|
| 841 |
+
gr.update(visible=True), # Show draft controls
|
| 842 |
+
gr.update(visible=True, open=True), # Show available players and open it
|
| 843 |
+
available_text, # Available players list
|
| 844 |
+
"" # Clear the input
|
| 845 |
+
)
|
| 846 |
+
else:
|
| 847 |
+
return (
|
| 848 |
+
output_text, # Regular output
|
| 849 |
+
gr.update(visible=False), # Hide draft controls
|
| 850 |
+
gr.update(visible=False), # Hide available players
|
| 851 |
+
"", # Clear available list
|
| 852 |
+
"" # Clear the input
|
| 853 |
+
)
|
| 854 |
+
|
| 855 |
+
# Test A2A functionality
|
| 856 |
+
def test_a2a_functionality():
|
| 857 |
+
"""Test A2A dependencies and port availability."""
|
| 858 |
+
import socket
|
| 859 |
+
test_results = []
|
| 860 |
+
|
| 861 |
+
# Test imports
|
| 862 |
+
test_results.append("=== Testing A2A Dependencies ===")
|
| 863 |
+
try:
|
| 864 |
+
import any_agent
|
| 865 |
+
test_results.append("✅ any_agent imported")
|
| 866 |
+
|
| 867 |
+
try:
|
| 868 |
+
from any_agent.serving import A2AServingConfig
|
| 869 |
+
from any_agent.tools import a2a_tool_async
|
| 870 |
+
test_results.append("✅ A2A components imported successfully!")
|
| 871 |
+
except ImportError as e:
|
| 872 |
+
test_results.append(f"❌ A2A components import failed: {e}")
|
| 873 |
+
except ImportError as e:
|
| 874 |
+
test_results.append(f"❌ any_agent not found: {e}")
|
| 875 |
+
|
| 876 |
+
# Test ports
|
| 877 |
+
test_results.append("\n=== Testing Port Availability ===")
|
| 878 |
+
test_ports = [5001, 5002, 5003, 8001, 8002, 8080, 8888]
|
| 879 |
+
available_count = 0
|
| 880 |
+
|
| 881 |
+
for port in test_ports:
|
| 882 |
+
try:
|
| 883 |
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
| 884 |
+
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
| 885 |
+
|
| 886 |
+
# Try different addresses
|
| 887 |
+
bound = False
|
| 888 |
+
for addr in ['127.0.0.1', 'localhost', '0.0.0.0']:
|
| 889 |
+
try:
|
| 890 |
+
sock.bind((addr, port))
|
| 891 |
+
test_results.append(f"✅ Port {port} available on {addr}")
|
| 892 |
+
bound = True
|
| 893 |
+
available_count += 1
|
| 894 |
+
break
|
| 895 |
+
except:
|
| 896 |
+
continue
|
| 897 |
+
|
| 898 |
+
if not bound:
|
| 899 |
+
test_results.append(f"❌ Port {port} not available")
|
| 900 |
+
|
| 901 |
+
sock.close()
|
| 902 |
+
except Exception as e:
|
| 903 |
+
test_results.append(f"❌ Port {port} error: {e}")
|
| 904 |
+
|
| 905 |
+
test_results.append(f"\n📊 Summary: {available_count}/{len(test_ports)} ports available")
|
| 906 |
+
|
| 907 |
+
# Environment info
|
| 908 |
+
test_results.append("\n=== Environment Info ===")
|
| 909 |
+
test_results.append(f"SPACE_ID: {os.getenv('SPACE_ID', 'Not on HF Spaces')}")
|
| 910 |
+
test_results.append(f"Python: {sys.version.split()[0]}")
|
| 911 |
+
|
| 912 |
+
if available_count >= 5:
|
| 913 |
+
test_results.append("\n✅ A2A might work! Try selecting A2A mode.")
|
| 914 |
+
else:
|
| 915 |
+
test_results.append("\n❌ Not enough ports available. Use Basic Multiagent mode.")
|
| 916 |
+
|
| 917 |
+
return "\n".join(test_results)
|
| 918 |
+
|
| 919 |
+
# No need for separate mode change handler - it happens when draft starts
|
| 920 |
+
|
| 921 |
+
# Run multi-agent demo with control visibility handling
|
| 922 |
+
def run_and_check(mode, app):
|
| 923 |
+
"""Run demo and check for user turn."""
|
| 924 |
+
# Create a new app instance for this user if needed
|
| 925 |
+
if app is None:
|
| 926 |
+
app = EnhancedFantasyDraftApp()
|
| 927 |
+
|
| 928 |
+
use_a2a = (mode == "A2A")
|
| 929 |
+
for output in app.run_multiagent_demo(use_a2a):
|
| 930 |
+
result = check_user_turn(output, app)
|
| 931 |
+
yield result + (app,) # Return the app state as the last element
|
| 932 |
+
|
| 933 |
+
run_multiagent_btn.click(
|
| 934 |
+
run_and_check,
|
| 935 |
+
[communication_mode, app_state],
|
| 936 |
+
[multiagent_output, mock_draft_controls, available_accordion, available_players_display, draft_pick_input, app_state],
|
| 937 |
+
show_progress=True
|
| 938 |
+
)
|
| 939 |
+
|
| 940 |
+
# Wire up A2A test button
|
| 941 |
+
test_a2a_btn.click(
|
| 942 |
+
test_a2a_functionality,
|
| 943 |
+
[],
|
| 944 |
+
[a2a_test_output]
|
| 945 |
+
)
|
| 946 |
+
|
| 947 |
+
# Continue draft after user pick
|
| 948 |
+
def submit_and_continue(player_name, app):
|
| 949 |
+
"""Submit pick and continue draft."""
|
| 950 |
+
if app is None:
|
| 951 |
+
yield ("No active draft. Please start a new mock draft.",
|
| 952 |
+
gr.update(visible=False), gr.update(visible=False), "", "", None)
|
| 953 |
+
return
|
| 954 |
+
|
| 955 |
+
for output in app.continue_mock_draft(player_name):
|
| 956 |
+
result = check_user_turn(output, app)
|
| 957 |
+
yield result + (app,) # Return the app state as the last element
|
| 958 |
+
|
| 959 |
+
submit_pick_btn.click(
|
| 960 |
+
submit_and_continue,
|
| 961 |
+
[draft_pick_input, app_state],
|
| 962 |
+
[multiagent_output, mock_draft_controls, available_accordion, available_players_display, draft_pick_input, app_state],
|
| 963 |
+
show_progress=True
|
| 964 |
+
)
|
| 965 |
+
|
| 966 |
+
# Also submit on enter
|
| 967 |
+
draft_pick_input.submit(
|
| 968 |
+
submit_and_continue,
|
| 969 |
+
[draft_pick_input, app_state],
|
| 970 |
+
[multiagent_output, mock_draft_controls, available_accordion, available_players_display, draft_pick_input, app_state],
|
| 971 |
+
show_progress=True
|
| 972 |
+
)
|
| 973 |
+
|
| 974 |
+
# Minimal CSS for layout only
|
| 975 |
+
demo.css = """
|
| 976 |
+
#main-container {
|
| 977 |
+
max-width: 1200px;
|
| 978 |
+
margin: 0 auto;
|
| 979 |
+
}
|
| 980 |
+
|
| 981 |
+
.multiagent-output {
|
| 982 |
+
max-height: 800px;
|
| 983 |
+
overflow-y: auto;
|
| 984 |
+
}
|
| 985 |
+
|
| 986 |
+
/* Force dark text in message cards */
|
| 987 |
+
.multiagent-output div[style*="background-color"] {
|
| 988 |
+
color: #212121 !important;
|
| 989 |
+
}
|
| 990 |
+
|
| 991 |
+
.multiagent-output div[style*="background-color"] * {
|
| 992 |
+
color: #212121 !important;
|
| 993 |
+
}
|
| 994 |
+
|
| 995 |
+
#start-button {
|
| 996 |
+
margin-top: 20px;
|
| 997 |
+
}
|
| 998 |
+
"""
|
| 999 |
+
|
| 1000 |
+
# Note: Gradio's unload() doesn't support inputs, so automatic cleanup
|
| 1001 |
+
# happens when the Python process ends or when new sessions override old ones
|
| 1002 |
+
|
| 1003 |
+
return demo
|
| 1004 |
+
|
| 1005 |
+
|
| 1006 |
+
def main():
|
| 1007 |
+
"""Main entry point."""
|
| 1008 |
+
# Check for API key - but don't exit on Hugging Face Spaces
|
| 1009 |
+
if not os.getenv("OPENAI_API_KEY"):
|
| 1010 |
+
if os.getenv("SPACE_ID"): # Running on Hugging Face Spaces
|
| 1011 |
+
print("⚠️ OPENAI_API_KEY not found - please set it in Space Settings > Repository secrets")
|
| 1012 |
+
else:
|
| 1013 |
+
print("Error: OPENAI_API_KEY not found in environment")
|
| 1014 |
+
print("Please set it using: export OPENAI_API_KEY='your-key-here'")
|
| 1015 |
+
exit(1)
|
| 1016 |
+
|
| 1017 |
+
# Create and launch the interface
|
| 1018 |
+
demo = create_gradio_interface()
|
| 1019 |
+
|
| 1020 |
+
print("🚀 Launching Enhanced Fantasy Draft App with A2A Support...")
|
| 1021 |
+
|
| 1022 |
+
# Check if running on Hugging Face Spaces
|
| 1023 |
+
if os.getenv("SPACE_ID"):
|
| 1024 |
+
demo.launch() # Hugging Face handles server config
|
| 1025 |
+
else:
|
| 1026 |
+
demo.launch(
|
| 1027 |
+
server_name="0.0.0.0",
|
| 1028 |
+
server_port=7860,
|
| 1029 |
+
share=True,
|
| 1030 |
+
show_error=True
|
| 1031 |
+
)
|
| 1032 |
+
|
| 1033 |
+
|
| 1034 |
+
if __name__ == "__main__":
|
| 1035 |
+
main()
|
apps/multiagent_draft.py
ADDED
|
@@ -0,0 +1,768 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Multi-Agent Mock Draft Implementation
|
| 4 |
+
Demonstrates A2A communication and multi-turn memory
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import time
|
| 8 |
+
from typing import Dict, List, Tuple, Optional
|
| 9 |
+
import sys
|
| 10 |
+
import os
|
| 11 |
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 12 |
+
|
| 13 |
+
from core.agent import FantasyDraftAgent
|
| 14 |
+
from core.data import TOP_PLAYERS, get_best_available, get_players_by_position
|
| 15 |
+
import random
|
| 16 |
+
|
| 17 |
+
# Enhanced agents not available in the reorganized structure
|
| 18 |
+
USE_ENHANCED = False
|
| 19 |
+
# Removed misleading print statement - mode is determined at runtime
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class DraftAgent:
|
| 23 |
+
"""Base class for draft agents with specific strategies."""
|
| 24 |
+
|
| 25 |
+
def __init__(self, team_name: str, strategy: str, color: str, icon: str):
|
| 26 |
+
self.team_name = team_name
|
| 27 |
+
self.strategy = strategy
|
| 28 |
+
self.color = color
|
| 29 |
+
self.icon = icon
|
| 30 |
+
self.agent = FantasyDraftAgent()
|
| 31 |
+
self.picks = []
|
| 32 |
+
self.conversation_memory = []
|
| 33 |
+
|
| 34 |
+
def remember_conversation(self, speaker: str, message: str):
|
| 35 |
+
"""Store conversation in memory."""
|
| 36 |
+
self.conversation_memory.append({
|
| 37 |
+
"speaker": speaker,
|
| 38 |
+
"message": message,
|
| 39 |
+
"timestamp": time.time()
|
| 40 |
+
})
|
| 41 |
+
|
| 42 |
+
def make_pick(self, available_players: List[str], draft_board: Dict) -> Tuple[str, str]:
|
| 43 |
+
"""Make a pick based on strategy. Returns (player, reasoning)."""
|
| 44 |
+
# This will be overridden by specific agent types
|
| 45 |
+
pass
|
| 46 |
+
|
| 47 |
+
def comment_on_pick(self, team: str, player: str, player_info: Dict) -> Optional[str]:
|
| 48 |
+
"""Generate commentary on another team's pick using LLM."""
|
| 49 |
+
# Build context for the LLM
|
| 50 |
+
# Convert ADP to a more understandable format
|
| 51 |
+
adp_int = int(player_info['adp'])
|
| 52 |
+
if adp_int == 0:
|
| 53 |
+
adp_int = 1
|
| 54 |
+
adp_description = f"ranked #{adp_int} overall" if adp_int <= 10 else f"typically drafted around pick #{adp_int}"
|
| 55 |
+
|
| 56 |
+
context = f"""You are {self.team_name}, a fantasy football team manager following a {self.strategy}.
|
| 57 |
+
Your picks so far: {', '.join(self.picks) if self.picks else 'None yet'}
|
| 58 |
+
|
| 59 |
+
{team} just picked {player} ({player_info['pos']}, {adp_description}, Tier: {player_info['tier']}).
|
| 60 |
+
|
| 61 |
+
Based on your strategy and the current draft situation, provide a short, natural comment on this pick.
|
| 62 |
+
Be competitive and show some personality - you can be critical, sarcastic, or dismissive if the pick doesn't align with your philosophy.
|
| 63 |
+
Don't be overly nice. This is a competitive draft and you want to win. Show confidence in your strategy.
|
| 64 |
+
Keep it under 2 sentences and make it feel like real draft room banter - trash talk is encouraged!
|
| 65 |
+
IMPORTANT: Don't mention raw ADP numbers like "1.5" - use natural language like "top pick", "#1 overall", "first round talent", etc."""
|
| 66 |
+
|
| 67 |
+
response = self.agent.run(context)
|
| 68 |
+
return response.strip()
|
| 69 |
+
|
| 70 |
+
def respond_to_comment(self, commenter: str, comment: str) -> Optional[str]:
|
| 71 |
+
"""Respond to another agent's comment using LLM."""
|
| 72 |
+
# Build conversation context
|
| 73 |
+
recent_memory = self.conversation_memory[-5:] if len(self.conversation_memory) > 5 else self.conversation_memory
|
| 74 |
+
|
| 75 |
+
context = f"""You are {self.team_name}, following a {self.strategy} in a fantasy draft.
|
| 76 |
+
Your picks: {', '.join(self.picks) if self.picks else 'None yet'}
|
| 77 |
+
|
| 78 |
+
{commenter} just said to you: "{comment}"
|
| 79 |
+
|
| 80 |
+
Recent conversation history:
|
| 81 |
+
{chr(10).join([f"- {m['speaker']}: {m['message']}" for m in recent_memory])}
|
| 82 |
+
|
| 83 |
+
Respond naturally and briefly (1-2 sentences). Be competitive and defend your strategy aggressively.
|
| 84 |
+
You can be sarcastic, dismissive, or fire back with your own trash talk. This is a competition!
|
| 85 |
+
Don't be polite - show confidence and give as good as you get."""
|
| 86 |
+
|
| 87 |
+
response = self.agent.run(context)
|
| 88 |
+
return response.strip()
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
class ZeroRBAgent(DraftAgent):
|
| 92 |
+
"""Agent that follows Zero RB strategy."""
|
| 93 |
+
|
| 94 |
+
def __init__(self, team_name: str):
|
| 95 |
+
super().__init__(team_name, "Zero RB Strategy", "#E3F2FD", "📘")
|
| 96 |
+
self.person_emoji = "🤓" # Analytical nerd
|
| 97 |
+
|
| 98 |
+
def make_pick(self, available_players: List[str], draft_board: Dict) -> Tuple[str, str]:
|
| 99 |
+
# Prioritize WRs in early rounds
|
| 100 |
+
round_num = len(self.picks) + 1
|
| 101 |
+
|
| 102 |
+
if round_num <= 3:
|
| 103 |
+
# Get best available WR
|
| 104 |
+
best_wrs = [(p, info) for p, info in TOP_PLAYERS.items()
|
| 105 |
+
if p in available_players and info['pos'] == 'WR']
|
| 106 |
+
if best_wrs:
|
| 107 |
+
best_wrs.sort(key=lambda x: x[1]['adp'])
|
| 108 |
+
player = best_wrs[0][0]
|
| 109 |
+
player_info = best_wrs[0][1]
|
| 110 |
+
|
| 111 |
+
# Generate dynamic reasoning using LLM
|
| 112 |
+
# Convert ADP to readable format
|
| 113 |
+
adp_int = int(player_info['adp'])
|
| 114 |
+
if adp_int <= 12:
|
| 115 |
+
adp_desc = f"a top-{adp_int} player"
|
| 116 |
+
elif adp_int <= 24:
|
| 117 |
+
adp_desc = "an early second-round talent"
|
| 118 |
+
else:
|
| 119 |
+
adp_desc = f"ranked around #{adp_int}"
|
| 120 |
+
|
| 121 |
+
context = f"""You are {self.team_name} following a Zero RB strategy in round {round_num}.
|
| 122 |
+
Your previous picks: {', '.join(self.picks) if self.picks else 'None'}
|
| 123 |
+
|
| 124 |
+
You're selecting {player} (WR, {player_info['team']}, {adp_desc}).
|
| 125 |
+
|
| 126 |
+
Explain your pick in 1-2 sentences, emphasizing why this fits your Zero RB strategy.
|
| 127 |
+
Be confident and maybe a bit cocky about avoiding RBs. Take subtle shots at teams loading up on injury-prone RBs.
|
| 128 |
+
Show personality - you KNOW your strategy is superior.
|
| 129 |
+
Don't use raw numbers like "1.5" or "ADP 12" - use natural language."""
|
| 130 |
+
|
| 131 |
+
reasoning = self.agent.run(context).strip()
|
| 132 |
+
return player, reasoning
|
| 133 |
+
|
| 134 |
+
# Later rounds, grab RBs
|
| 135 |
+
best_available = [(p, info) for p, info in TOP_PLAYERS.items()
|
| 136 |
+
if p in available_players]
|
| 137 |
+
best_available.sort(key=lambda x: x[1]['adp'])
|
| 138 |
+
|
| 139 |
+
if best_available:
|
| 140 |
+
player = best_available[0][0]
|
| 141 |
+
player_info = best_available[0][1]
|
| 142 |
+
pos = player_info['pos']
|
| 143 |
+
|
| 144 |
+
# Convert ADP to readable format
|
| 145 |
+
adp_int = int(player_info['adp'])
|
| 146 |
+
if pos == 'RB':
|
| 147 |
+
adp_desc = "a value RB" if adp_int > 24 else "a solid back"
|
| 148 |
+
else:
|
| 149 |
+
adp_desc = f"ranked #{adp_int}" if adp_int > 30 else f"a round {(adp_int-1)//12 + 1} talent"
|
| 150 |
+
|
| 151 |
+
context = f"""You are {self.team_name} following a Zero RB strategy in round {round_num}.
|
| 152 |
+
Your previous picks: {', '.join(self.picks)}
|
| 153 |
+
|
| 154 |
+
You're selecting {player} ({pos}, {player_info['team']}, {adp_desc}).
|
| 155 |
+
|
| 156 |
+
Explain why you're taking this player now, given your Zero RB approach.
|
| 157 |
+
If it's a RB, explain why NOW is the right time (while others reached early).
|
| 158 |
+
Be smug about getting value while others panicked. Keep it to 1-2 sentences with attitude.
|
| 159 |
+
Use terms like "value", "steal", "while others reached" - not raw numbers."""
|
| 160 |
+
|
| 161 |
+
reasoning = self.agent.run(context).strip()
|
| 162 |
+
return player, reasoning
|
| 163 |
+
|
| 164 |
+
return "Unknown Player", "Hmm, slim pickings here..."
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
class BPAAgent(DraftAgent):
|
| 170 |
+
"""Agent that follows Best Player Available strategy."""
|
| 171 |
+
|
| 172 |
+
def __init__(self, team_name: str):
|
| 173 |
+
super().__init__(team_name, "Best Player Available", "#E8F5E9", "📗")
|
| 174 |
+
self.person_emoji = "🧑💼" # Business-like, calculated
|
| 175 |
+
|
| 176 |
+
def make_pick(self, available_players: List[str], draft_board: Dict) -> Tuple[str, str]:
|
| 177 |
+
# Simply take the best available by ADP
|
| 178 |
+
best_available = [(p, info) for p, info in TOP_PLAYERS.items()
|
| 179 |
+
if p in available_players]
|
| 180 |
+
best_available.sort(key=lambda x: x[1]['adp'])
|
| 181 |
+
|
| 182 |
+
if best_available:
|
| 183 |
+
player = best_available[0][0]
|
| 184 |
+
player_info = best_available[0][1]
|
| 185 |
+
pos = player_info['pos']
|
| 186 |
+
|
| 187 |
+
# Generate dynamic reasoning using LLM
|
| 188 |
+
# Convert ADP to readable format
|
| 189 |
+
adp_int = int(player_info['adp'])
|
| 190 |
+
if adp_int <= 12:
|
| 191 |
+
adp_desc = f"the #{adp_int} overall player"
|
| 192 |
+
elif adp_int <= 24:
|
| 193 |
+
adp_desc = "a late first/early second round value"
|
| 194 |
+
else:
|
| 195 |
+
adp_desc = f"ranked #{adp_int} overall"
|
| 196 |
+
|
| 197 |
+
context = f"""You are {self.team_name} following a Best Player Available strategy.
|
| 198 |
+
Your previous picks: {', '.join(self.picks) if self.picks else 'None'}
|
| 199 |
+
Round: {len(self.picks) + 1}
|
| 200 |
+
|
| 201 |
+
You're selecting {player} ({pos}, {player_info['team']}, {adp_desc}).
|
| 202 |
+
|
| 203 |
+
Explain why this is the best value pick available. Focus on their value and ranking.
|
| 204 |
+
Be condescending about other teams reaching for needs or following rigid strategies.
|
| 205 |
+
You're the smart one taking the obvious value - let them know it. Keep it to 1-2 sentences.
|
| 206 |
+
Don't use raw ADP numbers - use terms like "best available", "top-ranked", "obvious value", etc."""
|
| 207 |
+
|
| 208 |
+
reasoning = self.agent.run(context).strip()
|
| 209 |
+
return player, reasoning
|
| 210 |
+
|
| 211 |
+
return "Unknown Player", "Taking the best available..."
|
| 212 |
+
|
| 213 |
+
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
class RobustRBAgent(DraftAgent):
|
| 217 |
+
"""Agent that follows Robust RB strategy."""
|
| 218 |
+
|
| 219 |
+
def __init__(self, team_name: str):
|
| 220 |
+
super().__init__(team_name, "Robust RB Strategy", "#FFF3E0", "📙")
|
| 221 |
+
self.person_emoji = "🧔" # Old-school, traditional
|
| 222 |
+
|
| 223 |
+
def make_pick(self, available_players: List[str], draft_board: Dict) -> Tuple[str, str]:
|
| 224 |
+
# Prioritize RBs early
|
| 225 |
+
round_num = len(self.picks) + 1
|
| 226 |
+
|
| 227 |
+
if round_num <= 2:
|
| 228 |
+
# Get best available RB
|
| 229 |
+
best_rbs = [(p, info) for p, info in TOP_PLAYERS.items()
|
| 230 |
+
if p in available_players and info['pos'] == 'RB']
|
| 231 |
+
if best_rbs:
|
| 232 |
+
best_rbs.sort(key=lambda x: x[1]['adp'])
|
| 233 |
+
player = best_rbs[0][0]
|
| 234 |
+
player_info = best_rbs[0][1]
|
| 235 |
+
|
| 236 |
+
# Convert ADP to readable format
|
| 237 |
+
adp_int = int(player_info['adp'])
|
| 238 |
+
if adp_int <= 5:
|
| 239 |
+
adp_desc = "an elite, top-5 back"
|
| 240 |
+
elif adp_int <= 12:
|
| 241 |
+
adp_desc = f"a premier RB1"
|
| 242 |
+
else:
|
| 243 |
+
adp_desc = "a solid running back"
|
| 244 |
+
|
| 245 |
+
context = f"""You are {self.team_name} following a Robust RB strategy in round {round_num}.
|
| 246 |
+
Your previous picks: {', '.join(self.picks) if self.picks else 'None'}
|
| 247 |
+
|
| 248 |
+
You're selecting {player} (RB, {player_info['team']}, {adp_desc}).
|
| 249 |
+
|
| 250 |
+
Explain why this RB is crucial for your Robust RB strategy. Be aggressive about RBs winning championships.
|
| 251 |
+
Mock teams that are going WR-heavy. You're building a REAL team with a strong foundation.
|
| 252 |
+
Be old-school and dismissive of "fancy" WR strategies. Keep it to 1-2 sentences with authority.
|
| 253 |
+
Use terms like "workhorse", "bell cow", "foundation" - not raw numbers."""
|
| 254 |
+
|
| 255 |
+
reasoning = self.agent.run(context).strip()
|
| 256 |
+
return player, reasoning
|
| 257 |
+
|
| 258 |
+
# Best available after that
|
| 259 |
+
best_available = [(p, info) for p, info in TOP_PLAYERS.items()
|
| 260 |
+
if p in available_players]
|
| 261 |
+
best_available.sort(key=lambda x: x[1]['adp'])
|
| 262 |
+
|
| 263 |
+
if best_available:
|
| 264 |
+
player = best_available[0][0]
|
| 265 |
+
player_info = best_available[0][1]
|
| 266 |
+
|
| 267 |
+
# Convert ADP to readable format
|
| 268 |
+
adp_int = int(player_info['adp'])
|
| 269 |
+
pos = player_info['pos']
|
| 270 |
+
if pos == 'WR':
|
| 271 |
+
adp_desc = "a quality receiver" if adp_int <= 24 else "a decent WR option"
|
| 272 |
+
elif pos == 'TE':
|
| 273 |
+
adp_desc = "a reliable tight end"
|
| 274 |
+
else:
|
| 275 |
+
adp_desc = f"ranked #{adp_int}"
|
| 276 |
+
|
| 277 |
+
context = f"""You are {self.team_name} following a Robust RB strategy in round {round_num}.
|
| 278 |
+
Your previous picks: {', '.join(self.picks)}
|
| 279 |
+
|
| 280 |
+
You're selecting {player} ({player_info['pos']}, {player_info['team']}, {adp_desc}).
|
| 281 |
+
|
| 282 |
+
Explain how this pick fits with your RB-heavy build. If it's not a RB, grudgingly admit you need other positions too.
|
| 283 |
+
But emphasize your RB foundation is what matters. Be dismissive of WR-first teams. Keep it to 1-2 sentences.
|
| 284 |
+
Focus on your "foundation" and "championship formula" - avoid raw rankings."""
|
| 285 |
+
|
| 286 |
+
reasoning = self.agent.run(context).strip()
|
| 287 |
+
return player, reasoning
|
| 288 |
+
|
| 289 |
+
return "Unknown Player", "Building around my RBs..."
|
| 290 |
+
|
| 291 |
+
|
| 292 |
+
class UpsideAgent(DraftAgent):
|
| 293 |
+
"""Agent that hunts for upside/breakout players."""
|
| 294 |
+
|
| 295 |
+
def __init__(self, team_name: str):
|
| 296 |
+
super().__init__(team_name, "Upside Hunter", "#FFFDE7", "📓")
|
| 297 |
+
self.person_emoji = "🤠" # Risk-taking cowboy
|
| 298 |
+
|
| 299 |
+
def make_pick(self, available_players: List[str], draft_board: Dict) -> Tuple[str, str]:
|
| 300 |
+
# Look for high upside players
|
| 301 |
+
best_available = [(p, info) for p, info in TOP_PLAYERS.items()
|
| 302 |
+
if p in available_players]
|
| 303 |
+
best_available.sort(key=lambda x: x[1]['adp'])
|
| 304 |
+
|
| 305 |
+
# Sometimes reach for upside
|
| 306 |
+
if len(best_available) > 3 and random.random() > 0.5:
|
| 307 |
+
# Take someone a bit later for "upside"
|
| 308 |
+
player = best_available[2][0] # Skip top 2, take 3rd
|
| 309 |
+
player_info = best_available[2][1]
|
| 310 |
+
|
| 311 |
+
# Convert ADP to readable format
|
| 312 |
+
adp_int = int(player_info['adp'])
|
| 313 |
+
adp_desc = "a sleeper pick" if adp_int > 36 else "someone with untapped potential"
|
| 314 |
+
|
| 315 |
+
context = f"""You are {self.team_name}, an Upside Hunter who looks for breakout potential.
|
| 316 |
+
Your previous picks: {', '.join(self.picks) if self.picks else 'None'}
|
| 317 |
+
Round: {len(self.picks) + 1}
|
| 318 |
+
|
| 319 |
+
You're reaching slightly for {player} ({player_info['pos']}, {player_info['team']}, {adp_desc}).
|
| 320 |
+
|
| 321 |
+
Explain why you see breakout/league-winning potential in this player. Be enthusiastic about their upside.
|
| 322 |
+
Mock the "safe" picks others are making. You're here to WIN, not finish 4th!
|
| 323 |
+
Championships require RISK! Keep it to 1-2 sentences with swagger.
|
| 324 |
+
Talk about "upside", "ceiling", "league-winner" - not specific rankings."""
|
| 325 |
+
|
| 326 |
+
reasoning = self.agent.run(context).strip()
|
| 327 |
+
return player, reasoning
|
| 328 |
+
|
| 329 |
+
elif best_available:
|
| 330 |
+
player = best_available[0][0]
|
| 331 |
+
player_info = best_available[0][1]
|
| 332 |
+
|
| 333 |
+
# Convert ADP to readable format
|
| 334 |
+
adp_int = int(player_info['adp'])
|
| 335 |
+
if adp_int <= 12:
|
| 336 |
+
adp_desc = "a high-ceiling star"
|
| 337 |
+
elif adp_int <= 36:
|
| 338 |
+
adp_desc = "someone with serious upside"
|
| 339 |
+
else:
|
| 340 |
+
adp_desc = "a potential breakout"
|
| 341 |
+
|
| 342 |
+
context = f"""You are {self.team_name}, an Upside Hunter who looks for league-winners.
|
| 343 |
+
Your previous picks: {', '.join(self.picks) if self.picks else 'None'}
|
| 344 |
+
Round: {len(self.picks) + 1}
|
| 345 |
+
|
| 346 |
+
You're selecting {player} ({player_info['pos']}, {player_info['team']}, {adp_desc}).
|
| 347 |
+
|
| 348 |
+
Explain what upside or potential you see in this player. Focus on ceiling over floor.
|
| 349 |
+
Be dismissive of "safe" boring picks. You're building a championship roster, not a participation trophy team!
|
| 350 |
+
Keep it to 1-2 sentences with confidence.
|
| 351 |
+
Use exciting terms like "breakout", "league-winner", "explosive" - not rankings."""
|
| 352 |
+
|
| 353 |
+
reasoning = self.agent.run(context).strip()
|
| 354 |
+
return player, reasoning
|
| 355 |
+
|
| 356 |
+
return "Unknown Player", "Going for the home run pick..."
|
| 357 |
+
|
| 358 |
+
|
| 359 |
+
class UserAdvisorAgent(DraftAgent):
|
| 360 |
+
"""Agent that advises the user during their picks."""
|
| 361 |
+
|
| 362 |
+
def __init__(self):
|
| 363 |
+
super().__init__("Your Advisor", "Strategic Advisor", "#FFEBEE", "📕")
|
| 364 |
+
self.person_emoji = "🧙" # Wise advisor
|
| 365 |
+
self.user_picks = []
|
| 366 |
+
|
| 367 |
+
def advise_user(self, available_players: List[str], draft_board: Dict,
|
| 368 |
+
other_agents_strategies: Dict[str, str]) -> str:
|
| 369 |
+
"""Provide advice to the user based on the draft flow."""
|
| 370 |
+
# Count the actual round based on total picks made
|
| 371 |
+
total_picks = sum(len(picks) for picks in draft_board.values())
|
| 372 |
+
round_num = (total_picks // 6) + 1 # 6 teams per round
|
| 373 |
+
|
| 374 |
+
# Get best available by position
|
| 375 |
+
best_by_pos = {}
|
| 376 |
+
for pos in ['RB', 'WR', 'QB', 'TE']:
|
| 377 |
+
candidates = [(p, info) for p, info in TOP_PLAYERS.items()
|
| 378 |
+
if p in available_players and info['pos'] == pos]
|
| 379 |
+
if candidates:
|
| 380 |
+
candidates.sort(key=lambda x: x[1]['adp'])
|
| 381 |
+
best_by_pos[pos] = candidates[0]
|
| 382 |
+
|
| 383 |
+
# Get user's current roster
|
| 384 |
+
user_picks = self.user_picks
|
| 385 |
+
user_rbs = [p for p in user_picks if TOP_PLAYERS.get(p, {}).get('pos') == 'RB']
|
| 386 |
+
user_wrs = [p for p in user_picks if TOP_PLAYERS.get(p, {}).get('pos') == 'WR']
|
| 387 |
+
|
| 388 |
+
# Analyze what other teams have been doing
|
| 389 |
+
other_picks_summary = []
|
| 390 |
+
for team, picks in draft_board.items():
|
| 391 |
+
if picks and team != 4: # Not the user
|
| 392 |
+
recent_pick = picks[-1] if picks else None
|
| 393 |
+
if recent_pick and recent_pick in TOP_PLAYERS:
|
| 394 |
+
other_picks_summary.append(f"Team {team} ({other_agents_strategies.get(f'Team {team}', 'Unknown')}): {recent_pick}")
|
| 395 |
+
|
| 396 |
+
# Build context for LLM
|
| 397 |
+
context = f"""You are an expert fantasy football advisor helping the user in round {round_num} of their draft.
|
| 398 |
+
|
| 399 |
+
User's current roster:
|
| 400 |
+
- RBs: {', '.join(user_rbs) if user_rbs else 'None'}
|
| 401 |
+
- WRs: {', '.join(user_wrs) if user_wrs else 'None'}
|
| 402 |
+
|
| 403 |
+
Top available players:
|
| 404 |
+
- Best RB: {best_by_pos.get('RB', [None])[0]} {f"(ranked #{int(best_by_pos.get('RB', [None, {'adp': 999}])[1]['adp'])})" if best_by_pos.get('RB') else ""}
|
| 405 |
+
- Best WR: {best_by_pos.get('WR', [None])[0]} {f"(ranked #{int(best_by_pos.get('WR', [None, {'adp': 999}])[1]['adp'])})" if best_by_pos.get('WR') else ""}
|
| 406 |
+
- Best QB: {best_by_pos.get('QB', [None])[0]} {f"(ranked #{int(best_by_pos.get('QB', [None, {'adp': 999}])[1]['adp'])})" if best_by_pos.get('QB') else ""}
|
| 407 |
+
- Best TE: {best_by_pos.get('TE', [None])[0]} {f"(ranked #{int(best_by_pos.get('TE', [None, {'adp': 999}])[1]['adp'])})" if best_by_pos.get('TE') else ""}
|
| 408 |
+
|
| 409 |
+
Recent picks by other teams:
|
| 410 |
+
{chr(10).join(other_picks_summary[-3:]) if other_picks_summary else 'None yet'}
|
| 411 |
+
|
| 412 |
+
Other team strategies: {', '.join([f"{t}: {s}" for t, s in other_agents_strategies.items()])}
|
| 413 |
+
|
| 414 |
+
Provide strategic advice for the user's pick. Consider:
|
| 415 |
+
1. Their roster needs
|
| 416 |
+
2. Best available value
|
| 417 |
+
3. What other teams are doing
|
| 418 |
+
4. Position scarcity
|
| 419 |
+
|
| 420 |
+
Format as: 🎯 **Round {round_num} Advice** followed by 2-3 bullet points with specific player recommendations and reasoning.
|
| 421 |
+
Keep it concise but insightful.
|
| 422 |
+
When discussing player rankings, use natural language like "top-5 RB", "first-round talent", "mid-round value" instead of raw numbers like "ADP 12.5"."""
|
| 423 |
+
|
| 424 |
+
advice = self.agent.run(context).strip()
|
| 425 |
+
return advice
|
| 426 |
+
|
| 427 |
+
|
| 428 |
+
class CommissionerAgent:
|
| 429 |
+
"""Agent that manages the draft flow and announcements."""
|
| 430 |
+
|
| 431 |
+
def __init__(self):
|
| 432 |
+
self.icon = "📜"
|
| 433 |
+
self.color = "#FFF8E1"
|
| 434 |
+
self.draft_log = []
|
| 435 |
+
|
| 436 |
+
def announce_pick(self, round_num: int, pick_num: int, team: str) -> str:
|
| 437 |
+
"""Announce who's on the clock."""
|
| 438 |
+
return f"**Round {round_num}, Pick {pick_num}** - {team} is on the clock!"
|
| 439 |
+
|
| 440 |
+
def confirm_pick(self, team: str, player: str, pick_num: int) -> str:
|
| 441 |
+
"""Confirm a pick was made."""
|
| 442 |
+
self.draft_log.append({
|
| 443 |
+
"pick_num": pick_num,
|
| 444 |
+
"team": team,
|
| 445 |
+
"player": player
|
| 446 |
+
})
|
| 447 |
+
return f"With pick #{pick_num}, {team} selects **{player}**!"
|
| 448 |
+
|
| 449 |
+
def announce_round_end(self, round_num: int) -> str:
|
| 450 |
+
"""Announce end of round."""
|
| 451 |
+
return f"🏁 **End of Round {round_num}**"
|
| 452 |
+
|
| 453 |
+
|
| 454 |
+
class MultiAgentMockDraft:
|
| 455 |
+
"""Orchestrates the multi-agent mock draft."""
|
| 456 |
+
|
| 457 |
+
def __init__(self, user_pick_position: int = 4):
|
| 458 |
+
# Initialize agents
|
| 459 |
+
self.agents = {
|
| 460 |
+
1: ZeroRBAgent("Team 1"),
|
| 461 |
+
2: BPAAgent("Team 2"),
|
| 462 |
+
3: RobustRBAgent("Team 3"),
|
| 463 |
+
5: UpsideAgent("Team 5")
|
| 464 |
+
}
|
| 465 |
+
|
| 466 |
+
# Add Team 6 as BPA if it's not the user position
|
| 467 |
+
if 6 != user_pick_position:
|
| 468 |
+
self.agents[6] = BPAAgent("Team 6")
|
| 469 |
+
self.agents[6].person_emoji = "👨🏫" # Professor, methodical
|
| 470 |
+
|
| 471 |
+
self.user_position = user_pick_position
|
| 472 |
+
self.user_advisor = UserAdvisorAgent()
|
| 473 |
+
self.commissioner = CommissionerAgent()
|
| 474 |
+
|
| 475 |
+
# Draft state
|
| 476 |
+
self.draft_board = {i: [] for i in range(1, 7)} # All 6 teams
|
| 477 |
+
|
| 478 |
+
# Give all agents access to the draft board
|
| 479 |
+
for agent in self.agents.values():
|
| 480 |
+
agent.draft_board = self.draft_board
|
| 481 |
+
|
| 482 |
+
self.all_picks = []
|
| 483 |
+
|
| 484 |
+
# Conversation log for visualization
|
| 485 |
+
self.conversation_log = []
|
| 486 |
+
|
| 487 |
+
def add_to_conversation(self, speaker: str, recipient: str, message: str,
|
| 488 |
+
message_type: str = "comment"):
|
| 489 |
+
"""Add a message to the conversation log."""
|
| 490 |
+
self.conversation_log.append({
|
| 491 |
+
"speaker": speaker,
|
| 492 |
+
"recipient": recipient,
|
| 493 |
+
"message": message,
|
| 494 |
+
"type": message_type,
|
| 495 |
+
"timestamp": time.time()
|
| 496 |
+
})
|
| 497 |
+
|
| 498 |
+
def get_available_players(self) -> List[str]:
|
| 499 |
+
"""Get list of available players."""
|
| 500 |
+
all_picked = [p for picks in self.draft_board.values() for p in picks]
|
| 501 |
+
return [p for p in TOP_PLAYERS.keys() if p not in all_picked]
|
| 502 |
+
|
| 503 |
+
def format_message(self, agent, recipient: str, message: str) -> str:
|
| 504 |
+
"""Format a message with agent styling."""
|
| 505 |
+
if hasattr(agent, 'icon'):
|
| 506 |
+
# Include person emoji if available
|
| 507 |
+
emojis = agent.icon
|
| 508 |
+
if hasattr(agent, 'person_emoji'):
|
| 509 |
+
emojis = f"{agent.icon}{agent.person_emoji}"
|
| 510 |
+
|
| 511 |
+
if recipient == "ALL":
|
| 512 |
+
return f"{emojis} **{agent.team_name}**: {message}"
|
| 513 |
+
else:
|
| 514 |
+
return f"{emojis} **{agent.team_name}** → {recipient}: {message}"
|
| 515 |
+
else:
|
| 516 |
+
# Commissioner
|
| 517 |
+
return f"{agent.icon} **COMMISSIONER**: {message}"
|
| 518 |
+
|
| 519 |
+
def is_pick_controversial(self, player: str, pick_num: int) -> bool:
|
| 520 |
+
"""Determine if a pick is controversial (reach, surprise, etc)."""
|
| 521 |
+
if player not in TOP_PLAYERS:
|
| 522 |
+
return False
|
| 523 |
+
|
| 524 |
+
player_info = TOP_PLAYERS[player]
|
| 525 |
+
expected_pick = pick_num * 1.5 # Rough estimate of expected ADP
|
| 526 |
+
|
| 527 |
+
# Check if it's a reach (picked way above ADP)
|
| 528 |
+
if player_info['adp'] > expected_pick + 5:
|
| 529 |
+
return True
|
| 530 |
+
|
| 531 |
+
# Check if it's a position run (3rd player at same position in a row)
|
| 532 |
+
recent_picks = self.all_picks[-3:]
|
| 533 |
+
recent_positions = [TOP_PLAYERS.get(p[1], {}).get('pos', '') for p in recent_picks]
|
| 534 |
+
if recent_positions.count(player_info['pos']) >= 2:
|
| 535 |
+
return True
|
| 536 |
+
|
| 537 |
+
return False
|
| 538 |
+
|
| 539 |
+
def select_commenters(self, picking_team: int, picked_player: str) -> List[int]:
|
| 540 |
+
"""Select 1-2 agents who should comment on this pick."""
|
| 541 |
+
if picked_player not in TOP_PLAYERS:
|
| 542 |
+
return []
|
| 543 |
+
|
| 544 |
+
player_info = TOP_PLAYERS[picked_player]
|
| 545 |
+
pick_num = len(self.all_picks) + 1
|
| 546 |
+
available_commenters = [num for num in self.agents.keys() if num != picking_team]
|
| 547 |
+
|
| 548 |
+
selected = []
|
| 549 |
+
|
| 550 |
+
# For obvious early picks (CMC at 1, CeeDee at 2, etc), maybe skip comments
|
| 551 |
+
# But always comment on user picks
|
| 552 |
+
if pick_num <= 3 and player_info['adp'] <= pick_num + 1 and picking_team != self.user_position:
|
| 553 |
+
# 50% chance to skip comments on obvious picks
|
| 554 |
+
if random.random() < 0.5:
|
| 555 |
+
return []
|
| 556 |
+
|
| 557 |
+
# Priority 1: Next drafter (if not user)
|
| 558 |
+
snake_order = self.get_draft_order(((pick_num - 1) // 6) + 1)
|
| 559 |
+
current_pos = snake_order.index(picking_team)
|
| 560 |
+
if current_pos < len(snake_order) - 1:
|
| 561 |
+
next_drafter = snake_order[current_pos + 1]
|
| 562 |
+
if next_drafter in available_commenters:
|
| 563 |
+
selected.append(next_drafter)
|
| 564 |
+
available_commenters.remove(next_drafter)
|
| 565 |
+
|
| 566 |
+
# Priority 2: Agent with opposing strategy
|
| 567 |
+
if picked_player in TOP_PLAYERS and len(selected) < 3:
|
| 568 |
+
pos = player_info['pos']
|
| 569 |
+
|
| 570 |
+
# Zero RB vs Robust RB conflict
|
| 571 |
+
if pos == 'RB' and 1 in available_commenters: # Zero RB agent
|
| 572 |
+
selected.append(1)
|
| 573 |
+
available_commenters.remove(1)
|
| 574 |
+
elif pos == 'WR' and 3 in available_commenters: # Robust RB agent
|
| 575 |
+
selected.append(3)
|
| 576 |
+
available_commenters.remove(3)
|
| 577 |
+
|
| 578 |
+
# Priority 3: Random agent if pick is controversial
|
| 579 |
+
if self.is_pick_controversial(picked_player, pick_num) and len(selected) < 2 and available_commenters:
|
| 580 |
+
random_commenter = random.choice(available_commenters)
|
| 581 |
+
selected.append(random_commenter)
|
| 582 |
+
|
| 583 |
+
# For non-controversial picks, only 50% chance of any comment if none selected yet
|
| 584 |
+
if not selected and available_commenters:
|
| 585 |
+
# User picks always get at least 1 comment
|
| 586 |
+
if picking_team == self.user_position:
|
| 587 |
+
selected.append(random.choice(available_commenters))
|
| 588 |
+
elif self.is_pick_controversial(picked_player, pick_num) or random.random() > 0.5:
|
| 589 |
+
selected.append(random.choice(available_commenters))
|
| 590 |
+
|
| 591 |
+
return selected[:2] # Max 2 commenters
|
| 592 |
+
|
| 593 |
+
def get_draft_order(self, round_num: int) -> List[int]:
|
| 594 |
+
"""Get the draft order for a given round (snake draft)."""
|
| 595 |
+
if round_num % 2 == 1:
|
| 596 |
+
return list(range(1, 7)) # 1-6 for odd rounds
|
| 597 |
+
else:
|
| 598 |
+
return list(range(6, 0, -1)) # 6-1 for even rounds
|
| 599 |
+
|
| 600 |
+
def simulate_draft_turn(self, round_num: int, pick_num: int, team_num: int) -> List[str]:
|
| 601 |
+
"""Simulate one pick in the draft. Returns formatted messages."""
|
| 602 |
+
messages = []
|
| 603 |
+
|
| 604 |
+
# Commissioner announces pick
|
| 605 |
+
announce_msg = self.commissioner.announce_pick(round_num, pick_num, f"Team {team_num}")
|
| 606 |
+
messages.append(("commissioner", "ALL", announce_msg))
|
| 607 |
+
|
| 608 |
+
if team_num == self.user_position:
|
| 609 |
+
# User's turn - get advice
|
| 610 |
+
available = self.get_available_players()
|
| 611 |
+
strategies = {f"Team {i}": agent.strategy for i, agent in self.agents.items()}
|
| 612 |
+
|
| 613 |
+
advice = self.user_advisor.advise_user(available, self.draft_board, strategies)
|
| 614 |
+
messages.append(("advisor", "USER", advice))
|
| 615 |
+
|
| 616 |
+
# Return messages and wait for user input
|
| 617 |
+
return messages, None # None indicates waiting for user
|
| 618 |
+
|
| 619 |
+
else:
|
| 620 |
+
# AI agent's turn
|
| 621 |
+
agent = self.agents.get(team_num)
|
| 622 |
+
if not agent:
|
| 623 |
+
# Create a temporary BPA agent for this team
|
| 624 |
+
agent = BPAAgent(f"Team {team_num}")
|
| 625 |
+
self.agents[team_num] = agent
|
| 626 |
+
|
| 627 |
+
available = self.get_available_players()
|
| 628 |
+
|
| 629 |
+
# Agent makes pick
|
| 630 |
+
player, reasoning = agent.make_pick(available, self.draft_board)
|
| 631 |
+
|
| 632 |
+
# Update draft board
|
| 633 |
+
self.draft_board[team_num].append(player)
|
| 634 |
+
agent.picks.append(player)
|
| 635 |
+
self.all_picks.append((team_num, player))
|
| 636 |
+
|
| 637 |
+
# Announce pick
|
| 638 |
+
confirm_msg = self.commissioner.confirm_pick(agent.team_name, player, pick_num)
|
| 639 |
+
messages.append(("commissioner", "ALL", confirm_msg))
|
| 640 |
+
|
| 641 |
+
# Agent explains pick
|
| 642 |
+
messages.append((agent, "ALL", reasoning))
|
| 643 |
+
|
| 644 |
+
# Enhanced features: sometimes add emoji storms and meta commentary
|
| 645 |
+
if USE_ENHANCED and hasattr(agent, 'generate_emoji_storm'):
|
| 646 |
+
# Controversial picks get emoji reactions
|
| 647 |
+
if self.is_pick_controversial(player, pick_num) and random.random() > 0.6:
|
| 648 |
+
# Random agent drops emoji bomb
|
| 649 |
+
reactor = random.choice(list(self.agents.values()))
|
| 650 |
+
if reactor != agent: # Don't react to yourself
|
| 651 |
+
player_adp = TOP_PLAYERS.get(player, {}).get('adp', 100)
|
| 652 |
+
emoji_storm = reactor.generate_emoji_storm("bad_pick" if pick_num < player_adp else "great_pick")
|
| 653 |
+
messages.append((reactor, "ALL", emoji_storm))
|
| 654 |
+
|
| 655 |
+
# Meta commentary occasionally
|
| 656 |
+
if pick_num % 10 == 0 and random.random() > 0.7:
|
| 657 |
+
meta_agent = random.choice(list(self.agents.values()))
|
| 658 |
+
meta_comments = [
|
| 659 |
+
"Is anyone else's algorithm telling them to be meaner? 🤖",
|
| 660 |
+
"Why do I always end up in the most toxic draft rooms? 😅",
|
| 661 |
+
"USER, PLEASE don't take my sleeper at pick 4 🙏",
|
| 662 |
+
"This draft chat gonna end up on r/fantasyfootball 📸",
|
| 663 |
+
"The disrespect in this room is ASTRONOMICAL 💀",
|
| 664 |
+
"I can't wait to screenshot this for the group chat later 📱"
|
| 665 |
+
]
|
| 666 |
+
messages.append((meta_agent, "ALL", random.choice(meta_comments)))
|
| 667 |
+
|
| 668 |
+
# Select 2-3 agents to comment
|
| 669 |
+
if player in TOP_PLAYERS:
|
| 670 |
+
player_info = TOP_PLAYERS[player]
|
| 671 |
+
selected_commenters = self.select_commenters(team_num, player)
|
| 672 |
+
|
| 673 |
+
# Generate comments from selected agents
|
| 674 |
+
for commenter_num in selected_commenters:
|
| 675 |
+
other_agent = self.agents.get(commenter_num)
|
| 676 |
+
if other_agent:
|
| 677 |
+
# Add typing indicator
|
| 678 |
+
typing_msg = (f"typing_{other_agent.team_name}", agent.team_name,
|
| 679 |
+
f"{other_agent.team_name} is typing...")
|
| 680 |
+
messages.append(typing_msg)
|
| 681 |
+
|
| 682 |
+
# Generate comment
|
| 683 |
+
comment = other_agent.comment_on_pick(agent.team_name, player, player_info)
|
| 684 |
+
if comment:
|
| 685 |
+
messages.append((other_agent, agent.team_name, comment))
|
| 686 |
+
|
| 687 |
+
# Store in conversation memory
|
| 688 |
+
agent.remember_conversation(other_agent.team_name, comment)
|
| 689 |
+
other_agent.remember_conversation(agent.team_name, f"Picked {player}")
|
| 690 |
+
|
| 691 |
+
# Enhanced agents respond more often (30% vs 15%)
|
| 692 |
+
response_chance = 0.70 if USE_ENHANCED else 0.85
|
| 693 |
+
if random.random() > response_chance:
|
| 694 |
+
# Typing indicator for response
|
| 695 |
+
response_typing = (f"typing_{agent.team_name}", other_agent.team_name,
|
| 696 |
+
f"{agent.team_name} is typing...")
|
| 697 |
+
messages.append(response_typing)
|
| 698 |
+
|
| 699 |
+
response = agent.respond_to_comment(other_agent.team_name, comment)
|
| 700 |
+
if response:
|
| 701 |
+
messages.append((agent, other_agent.team_name, response))
|
| 702 |
+
other_agent.remember_conversation(agent.team_name, response)
|
| 703 |
+
|
| 704 |
+
return messages, player
|
| 705 |
+
|
| 706 |
+
def make_user_pick(self, player_name: str) -> List[str]:
|
| 707 |
+
"""Process the user's pick."""
|
| 708 |
+
messages = []
|
| 709 |
+
|
| 710 |
+
# Validate pick
|
| 711 |
+
available = self.get_available_players()
|
| 712 |
+
if player_name not in available:
|
| 713 |
+
return [("advisor", "USER", f"❌ {player_name} is not available!")]
|
| 714 |
+
|
| 715 |
+
# Make the pick
|
| 716 |
+
self.draft_board[self.user_position].append(player_name)
|
| 717 |
+
self.user_advisor.user_picks.append(player_name)
|
| 718 |
+
self.all_picks.append((self.user_position, player_name))
|
| 719 |
+
|
| 720 |
+
pick_num = len(self.all_picks)
|
| 721 |
+
|
| 722 |
+
# Announce pick
|
| 723 |
+
confirm_msg = self.commissioner.confirm_pick("YOUR TEAM", player_name, pick_num)
|
| 724 |
+
messages.append(("commissioner", "ALL", confirm_msg))
|
| 725 |
+
|
| 726 |
+
# Select agents to comment on user's pick
|
| 727 |
+
if player_name in TOP_PLAYERS:
|
| 728 |
+
player_info = TOP_PLAYERS[player_name]
|
| 729 |
+
selected_commenters = self.select_commenters(self.user_position, player_name)
|
| 730 |
+
|
| 731 |
+
# User picks get attention - allow up to 2 comments
|
| 732 |
+
for commenter_num in selected_commenters[:2]:
|
| 733 |
+
agent = self.agents.get(commenter_num)
|
| 734 |
+
if agent:
|
| 735 |
+
# Add typing indicator
|
| 736 |
+
typing_msg = (f"typing_{agent.team_name}", "YOUR TEAM",
|
| 737 |
+
f"{agent.team_name} is typing...")
|
| 738 |
+
messages.append(typing_msg)
|
| 739 |
+
|
| 740 |
+
comment = agent.comment_on_pick("Your team", player_name, player_info)
|
| 741 |
+
if comment:
|
| 742 |
+
messages.append((agent, "YOUR TEAM", comment))
|
| 743 |
+
|
| 744 |
+
return messages
|
| 745 |
+
|
| 746 |
+
def get_draft_summary(self) -> str:
|
| 747 |
+
"""Get a summary of the draft results."""
|
| 748 |
+
summary = "📊 **DRAFT SUMMARY**\n\n"
|
| 749 |
+
|
| 750 |
+
for team_num in sorted(self.draft_board.keys()):
|
| 751 |
+
if team_num == self.user_position:
|
| 752 |
+
summary += f"**YOUR TEAM**:\n"
|
| 753 |
+
else:
|
| 754 |
+
agent = self.agents.get(team_num)
|
| 755 |
+
if agent:
|
| 756 |
+
summary += f"**{agent.team_name}** ({agent.strategy}):\n"
|
| 757 |
+
else:
|
| 758 |
+
summary += f"**Team {team_num}**:\n"
|
| 759 |
+
|
| 760 |
+
for i, player in enumerate(self.draft_board[team_num], 1):
|
| 761 |
+
if player in TOP_PLAYERS:
|
| 762 |
+
info = TOP_PLAYERS[player]
|
| 763 |
+
summary += f" R{i}: {player} ({info['pos']}, {info['team']})\n"
|
| 764 |
+
else:
|
| 765 |
+
summary += f" R{i}: {player}\n"
|
| 766 |
+
summary += "\n"
|
| 767 |
+
|
| 768 |
+
return summary
|
apps/multiagent_scenarios.py
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Multi-Agent Scenarios and Visualization
|
| 4 |
+
Provides formatted output for multi-agent interactions
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from .multiagent_draft import MultiAgentMockDraft, DraftAgent, CommissionerAgent
|
| 8 |
+
import time
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def format_agent_message(agent, recipient: str, message: str,
|
| 12 |
+
show_arrow: bool = True) -> str:
|
| 13 |
+
"""Format an agent message with proper styling."""
|
| 14 |
+
|
| 15 |
+
# Check if this is a typing indicator
|
| 16 |
+
if isinstance(agent, str) and agent.startswith("typing_"):
|
| 17 |
+
# Extract the team name from typing_Team X
|
| 18 |
+
team_name = agent.replace("typing_", "")
|
| 19 |
+
return f'<div style="color: #666; font-style: italic; margin: 5px 0;">💭 *{team_name} is typing...*</div>\n\n'
|
| 20 |
+
|
| 21 |
+
# Agent colors and styles
|
| 22 |
+
agent_styles = {
|
| 23 |
+
"📘": ("Team 1", "#E3F2FD", "#1976D2"), # Blue
|
| 24 |
+
"📘🤓": ("Team 1", "#E3F2FD", "#1976D2"), # Blue with nerd emoji
|
| 25 |
+
"📗": ("Team 2", "#E8F5E9", "#388E3C"), # Green
|
| 26 |
+
"📗🧑💼": ("Team 2", "#E8F5E9", "#388E3C"), # Green with business person
|
| 27 |
+
"📗👨🏫": ("Team 6", "#E8F5E9", "#388E3C"), # Green with professor
|
| 28 |
+
"📙": ("Team 3", "#FFF3E0", "#F57C00"), # Orange
|
| 29 |
+
"📙🧔": ("Team 3", "#FFF3E0", "#F57C00"), # Orange with beard
|
| 30 |
+
"📕": ("Your Advisor", "#FFEBEE", "#D32F2F"), # Red
|
| 31 |
+
"📕🧙": ("Your Advisor", "#FFEBEE", "#D32F2F"), # Red with wizard
|
| 32 |
+
"📓": ("Team 5", "#F5E6FF", "#7B1FA2"), # Purple
|
| 33 |
+
"📓🤠": ("Team 5", "#F5E6FF", "#7B1FA2"), # Purple with cowboy
|
| 34 |
+
"📜": ("COMMISSIONER", "#ECEFF1", "#455A64"), # Blue-gray
|
| 35 |
+
"👤": ("YOUR TEAM", "#E8EAF6", "#3F51B5"), # Indigo for user
|
| 36 |
+
"💭": ("System", "#FFF9C4", "#FBC02D"), # Light yellow for loading/system messages
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
if hasattr(agent, 'icon'):
|
| 40 |
+
icon = agent.icon
|
| 41 |
+
# Include person emoji if available
|
| 42 |
+
if hasattr(agent, 'person_emoji'):
|
| 43 |
+
icon = f"{agent.icon}{agent.person_emoji}"
|
| 44 |
+
if hasattr(agent, 'team_name'):
|
| 45 |
+
name = agent.team_name
|
| 46 |
+
else:
|
| 47 |
+
name = "COMMISSIONER"
|
| 48 |
+
else:
|
| 49 |
+
# String agent (for simplified calls)
|
| 50 |
+
if agent == "commissioner":
|
| 51 |
+
icon = "📜"
|
| 52 |
+
name = "COMMISSIONER"
|
| 53 |
+
elif agent == "advisor":
|
| 54 |
+
icon = "📕"
|
| 55 |
+
name = "Your Advisor"
|
| 56 |
+
elif agent == "user":
|
| 57 |
+
icon = "👤"
|
| 58 |
+
name = "YOUR TEAM"
|
| 59 |
+
elif agent == "system":
|
| 60 |
+
icon = "💭"
|
| 61 |
+
name = "System"
|
| 62 |
+
else:
|
| 63 |
+
return message
|
| 64 |
+
|
| 65 |
+
style = agent_styles.get(icon, ("Unknown", "#FFFFFF", "#000000"))
|
| 66 |
+
bg_color, border_color = style[1], style[2]
|
| 67 |
+
|
| 68 |
+
# Build the message box with more specific color
|
| 69 |
+
html = f'<div style="background-color: {bg_color}; '
|
| 70 |
+
html += f'border-left: 4px solid {border_color}; '
|
| 71 |
+
html += f'padding: 15px; border-radius: 8px; margin: 10px 0;">\n\n'
|
| 72 |
+
|
| 73 |
+
# Header with sender/recipient
|
| 74 |
+
if agent == "system" or icon == "💭":
|
| 75 |
+
# System messages are centered and italicized
|
| 76 |
+
html = f'<div style="text-align: center; margin: 10px 0; color: #424242;">\n\n'
|
| 77 |
+
html += f'<span style="color: #424242;">*{message}*</span>\n\n'
|
| 78 |
+
html += '</div>\n\n'
|
| 79 |
+
return html
|
| 80 |
+
elif recipient == "ALL":
|
| 81 |
+
html += f'<span style="color: #212121;">**{icon} {name}**</span>\n\n'
|
| 82 |
+
elif recipient == "USER":
|
| 83 |
+
html += f'<span style="color: #212121;">**{icon} {name} → You**</span>\n\n'
|
| 84 |
+
elif show_arrow:
|
| 85 |
+
html += f'<span style="color: #212121;">**{icon} {name} → {recipient}**</span>\n\n'
|
| 86 |
+
else:
|
| 87 |
+
html += f'<span style="color: #212121;">**{icon} {name}**</span>\n\n'
|
| 88 |
+
|
| 89 |
+
# Message content
|
| 90 |
+
html += f'<span style="color: #212121;">{message}</span>\n\n'
|
| 91 |
+
html += '</div>\n\n'
|
| 92 |
+
|
| 93 |
+
return html
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def format_conversation_block(messages: list) -> str:
|
| 97 |
+
"""Format a block of messages for display."""
|
| 98 |
+
output = ""
|
| 99 |
+
for msg in messages:
|
| 100 |
+
if len(msg) >= 3:
|
| 101 |
+
agent, recipient, content = msg[:3]
|
| 102 |
+
output += format_agent_message(agent, recipient, content)
|
| 103 |
+
return output
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
def format_memory_indicator(round_num: int, memories: list) -> str:
|
| 107 |
+
"""Format a memory indicator showing what agents remember."""
|
| 108 |
+
if not memories:
|
| 109 |
+
return ""
|
| 110 |
+
|
| 111 |
+
output = '<div style="background-color: #F5F5F5; '
|
| 112 |
+
output += 'border: 2px dashed #9E9E9E; '
|
| 113 |
+
output += 'padding: 12px; border-radius: 8px; margin: 15px 0; '
|
| 114 |
+
output += 'color: #424242;">\n\n'
|
| 115 |
+
output += f'**💭 DRAFT MEMORY (Round {round_num})**\n\n'
|
| 116 |
+
|
| 117 |
+
for memory in memories:
|
| 118 |
+
output += f'• {memory}\n'
|
| 119 |
+
|
| 120 |
+
output += '\n</div>\n\n'
|
| 121 |
+
return output
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
def create_mock_draft_visualization(draft: MultiAgentMockDraft,
|
| 125 |
+
round_num: int, pick_num: int) -> str:
|
| 126 |
+
"""Create a visual representation of the current draft state."""
|
| 127 |
+
output = f"### 📋 Draft Board - Round {round_num}, Pick {pick_num}\n\n"
|
| 128 |
+
|
| 129 |
+
# Create a simple draft board
|
| 130 |
+
output += "| Team | Round 1 | Round 2 | Round 3 |\n"
|
| 131 |
+
output += "|------|---------|---------|----------|\n"
|
| 132 |
+
|
| 133 |
+
for team_num in range(1, 7): # Show all 6 teams
|
| 134 |
+
if team_num == draft.user_position:
|
| 135 |
+
team_name = "**YOU**"
|
| 136 |
+
else:
|
| 137 |
+
agent = draft.agents.get(team_num)
|
| 138 |
+
team_name = agent.team_name if agent else f"Team {team_num}"
|
| 139 |
+
|
| 140 |
+
picks = draft.draft_board.get(team_num, [])
|
| 141 |
+
row = f"| {team_name} "
|
| 142 |
+
|
| 143 |
+
for round_idx in range(3):
|
| 144 |
+
if round_idx < len(picks):
|
| 145 |
+
row += f"| {picks[round_idx]} "
|
| 146 |
+
else:
|
| 147 |
+
row += "| - "
|
| 148 |
+
|
| 149 |
+
row += "|\n"
|
| 150 |
+
output += row
|
| 151 |
+
|
| 152 |
+
return output
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
def run_interactive_mock_draft():
|
| 156 |
+
"""Run an interactive mock draft demo that yields formatted output."""
|
| 157 |
+
|
| 158 |
+
# Initialize the draft
|
| 159 |
+
draft = MultiAgentMockDraft(user_pick_position=4)
|
| 160 |
+
|
| 161 |
+
# Skip introductions and go straight to commissioner welcome
|
| 162 |
+
output = format_agent_message("commissioner", "ALL",
|
| 163 |
+
"Welcome to the draft! 6 teams, 3 rounds, snake format. Let's get started!")
|
| 164 |
+
|
| 165 |
+
yield output
|
| 166 |
+
|
| 167 |
+
# Track memories for demonstration
|
| 168 |
+
draft_memories = []
|
| 169 |
+
|
| 170 |
+
# Run the draft
|
| 171 |
+
for round_num in range(1, 4): # 3 rounds
|
| 172 |
+
output += f"\n## 🔄 ROUND {round_num}\n\n"
|
| 173 |
+
yield output
|
| 174 |
+
|
| 175 |
+
# Snake draft order - 6 teams total
|
| 176 |
+
if round_num % 2 == 1:
|
| 177 |
+
pick_order = list(range(1, 7)) # 1-6 for odd rounds
|
| 178 |
+
else:
|
| 179 |
+
pick_order = list(range(6, 0, -1)) # 6-1 for even rounds
|
| 180 |
+
|
| 181 |
+
for pick_in_round, team_num in enumerate(pick_order, 1):
|
| 182 |
+
pick_num = (round_num - 1) * 6 + pick_in_round # 6 teams per round
|
| 183 |
+
|
| 184 |
+
# Show draft board at start of round
|
| 185 |
+
if pick_in_round == 1:
|
| 186 |
+
yield create_mock_draft_visualization(draft, round_num, pick_num)
|
| 187 |
+
yield "\n"
|
| 188 |
+
|
| 189 |
+
# Process the pick
|
| 190 |
+
messages, waiting_for_user = draft.simulate_draft_turn(round_num, pick_num, team_num)
|
| 191 |
+
|
| 192 |
+
# Show loading animation for AI agents
|
| 193 |
+
if team_num != draft.user_position and team_num in draft.agents:
|
| 194 |
+
agent = draft.agents[team_num]
|
| 195 |
+
loading_msg = f"💭 *{agent.team_name} is contemplating their pick...*"
|
| 196 |
+
output += format_agent_message("system", "ALL", loading_msg)
|
| 197 |
+
yield output
|
| 198 |
+
time.sleep(0.3) # Brief pause for loading effect
|
| 199 |
+
|
| 200 |
+
# Display messages with delays
|
| 201 |
+
for msg in messages:
|
| 202 |
+
if len(msg) >= 3:
|
| 203 |
+
agent, recipient, content = msg[:3]
|
| 204 |
+
output += format_agent_message(agent, recipient, content)
|
| 205 |
+
yield output
|
| 206 |
+
|
| 207 |
+
# Add delays based on message type
|
| 208 |
+
if isinstance(agent, str) and agent.startswith("typing_"):
|
| 209 |
+
time.sleep(0.5) # Short delay for typing indicators
|
| 210 |
+
else:
|
| 211 |
+
time.sleep(0.8) # Slightly longer delay for actual messages
|
| 212 |
+
|
| 213 |
+
if waiting_for_user is None:
|
| 214 |
+
# Wait for user input (None means it's the user's turn)
|
| 215 |
+
output += "\n**⏰ YOU'RE ON THE CLOCK! Type your pick below.**\n\n"
|
| 216 |
+
yield (draft, output) # Yield tuple to trigger UI update
|
| 217 |
+
return # Stop the generator here
|
| 218 |
+
|
| 219 |
+
# Add memory indicators for multi-turn demonstration
|
| 220 |
+
if round_num > 1 and pick_in_round % 2 == 0:
|
| 221 |
+
# Show that agents remember previous picks
|
| 222 |
+
if team_num in draft.agents:
|
| 223 |
+
agent = draft.agents[team_num]
|
| 224 |
+
if len(agent.picks) > 1:
|
| 225 |
+
memory = f"{agent.team_name} has drafted: {', '.join(agent.picks)}"
|
| 226 |
+
draft_memories.append(memory)
|
| 227 |
+
|
| 228 |
+
if draft_memories:
|
| 229 |
+
output += format_memory_indicator(round_num, draft_memories[-2:])
|
| 230 |
+
yield output
|
| 231 |
+
|
| 232 |
+
time.sleep(0.5) # Brief pause between picks
|
| 233 |
+
|
| 234 |
+
# End of round summary
|
| 235 |
+
output += format_agent_message("commissioner", "ALL",
|
| 236 |
+
f"That's the end of Round {round_num}!")
|
| 237 |
+
yield output
|
| 238 |
+
|
| 239 |
+
# Final summary
|
| 240 |
+
output += "\n## 📊 FINAL RESULTS\n\n"
|
| 241 |
+
output += draft.get_draft_summary()
|
| 242 |
+
yield output
|
| 243 |
+
|
| 244 |
+
|
| 245 |
+
def create_quick_multiagent_demo():
|
| 246 |
+
"""Create a quick demonstration of multi-agent communication."""
|
| 247 |
+
|
| 248 |
+
output = "# 🤝 Multi-Agent Communication Demo\n\n"
|
| 249 |
+
output += "> **Watch how agents discuss, debate, and remember!**\n\n"
|
| 250 |
+
|
| 251 |
+
# Simulate a conversation about a pick
|
| 252 |
+
messages = [
|
| 253 |
+
("📘", "ALL", "I'm taking **Justin Jefferson** with the first pick. Zero RB all the way!"),
|
| 254 |
+
("📙", "📘", "Leaving McCaffrey on the board? That's a mistake!"),
|
| 255 |
+
("📘", "📙", "RBs get injured. I'll take my chances with elite WRs."),
|
| 256 |
+
("📗", "ALL", "Interesting debate! I'll take whoever falls to me - best player available."),
|
| 257 |
+
]
|
| 258 |
+
|
| 259 |
+
output += "## Turn 1: The First Pick Debate\n\n"
|
| 260 |
+
|
| 261 |
+
for icon, recipient, message in messages:
|
| 262 |
+
# Create a mock agent for formatting
|
| 263 |
+
class MockAgent:
|
| 264 |
+
def __init__(self, icon):
|
| 265 |
+
self.icon = icon
|
| 266 |
+
if icon == "📘":
|
| 267 |
+
self.team_name = "Team 1"
|
| 268 |
+
elif icon == "📙":
|
| 269 |
+
self.team_name = "Team 3"
|
| 270 |
+
elif icon == "📗":
|
| 271 |
+
self.team_name = "Team 2"
|
| 272 |
+
|
| 273 |
+
agent = MockAgent(icon)
|
| 274 |
+
output += format_agent_message(agent, recipient, message)
|
| 275 |
+
yield output
|
| 276 |
+
time.sleep(0.5)
|
| 277 |
+
|
| 278 |
+
# Show memory
|
| 279 |
+
output += format_memory_indicator(1, [
|
| 280 |
+
"Team 1 committed to Zero RB strategy",
|
| 281 |
+
"Team 3 prefers RB-heavy approach",
|
| 282 |
+
"Teams are aware of each other's strategies"
|
| 283 |
+
])
|
| 284 |
+
yield output
|
| 285 |
+
|
| 286 |
+
# Continue conversation
|
| 287 |
+
output += "\n## Turn 2: Reacting to Strategies\n\n"
|
| 288 |
+
|
| 289 |
+
messages2 = [
|
| 290 |
+
("📙", "ALL", "With pick #3, I select **Christian McCaffrey**. Thanks for passing!"),
|
| 291 |
+
("📘", "📙", "Like I said, enjoy the injury risk. I'm happy with Jefferson."),
|
| 292 |
+
("📗", "ALL", "The top 2 players are gone. **CeeDee Lamb** gives me great value at #2."),
|
| 293 |
+
("📓", "ALL", "These conservative picks... I'm hunting for league-winners!")
|
| 294 |
+
]
|
| 295 |
+
|
| 296 |
+
for icon, recipient, message in messages2:
|
| 297 |
+
agent = MockAgent(icon)
|
| 298 |
+
if icon == "📓":
|
| 299 |
+
agent.team_name = "Team 5"
|
| 300 |
+
output += format_agent_message(agent, recipient, message)
|
| 301 |
+
yield output
|
| 302 |
+
time.sleep(0.5)
|
| 303 |
+
|
| 304 |
+
output += "\n## 🎯 Key Multi-Agent Features Demonstrated\n\n"
|
| 305 |
+
output += "✅ **Agent-to-Agent Communication**: Direct responses between agents\n"
|
| 306 |
+
output += "✅ **Strategy Awareness**: Agents know and react to others' strategies\n"
|
| 307 |
+
output += "✅ **Memory Persistence**: Agents reference earlier statements\n"
|
| 308 |
+
output += "✅ **Dynamic Adaptation**: Strategies influence the draft flow\n"
|
| 309 |
+
|
| 310 |
+
yield output
|
core/__init__.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Core module for Fantasy Draft Agent."""
|
| 2 |
+
|
| 3 |
+
from .agent import FantasyDraftAgent
|
| 4 |
+
from .data import TOP_PLAYERS, get_player_info, get_best_available, get_players_by_position
|
| 5 |
+
from .visualizer import (
|
| 6 |
+
create_player_card,
|
| 7 |
+
create_roster_summary,
|
| 8 |
+
create_comparison_card,
|
| 9 |
+
create_draft_board_snapshot,
|
| 10 |
+
create_decision_summary,
|
| 11 |
+
create_scenario_result,
|
| 12 |
+
create_multi_turn_flow
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
__all__ = [
|
| 16 |
+
'FantasyDraftAgent',
|
| 17 |
+
'TOP_PLAYERS',
|
| 18 |
+
'get_player_info',
|
| 19 |
+
'get_best_available',
|
| 20 |
+
'get_players_by_position',
|
| 21 |
+
'create_player_card',
|
| 22 |
+
'create_roster_summary',
|
| 23 |
+
'create_comparison_card',
|
| 24 |
+
'create_draft_board_snapshot',
|
| 25 |
+
'create_decision_summary',
|
| 26 |
+
'create_scenario_result',
|
| 27 |
+
'create_multi_turn_flow'
|
| 28 |
+
]
|
core/a2a_helpers.py
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Helper functions for A2A response parsing and formatting.
|
| 3 |
+
"""
|
| 4 |
+
import json
|
| 5 |
+
from typing import Any, Optional, Dict, List
|
| 6 |
+
from pydantic import BaseModel
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def parse_a2a_response(result: Any, output_class: type[BaseModel]) -> Optional[BaseModel]:
|
| 10 |
+
"""
|
| 11 |
+
Parse A2A response from various wrapper formats.
|
| 12 |
+
|
| 13 |
+
Args:
|
| 14 |
+
result: Raw response from A2A agent
|
| 15 |
+
output_class: Expected output class (e.g., A2AOutput)
|
| 16 |
+
|
| 17 |
+
Returns:
|
| 18 |
+
Parsed output object or None if parsing fails
|
| 19 |
+
|
| 20 |
+
Example:
|
| 21 |
+
>>> result = {"type": "pick", "player_name": "Jefferson"}
|
| 22 |
+
>>> output = parse_a2a_response(result, A2AOutput)
|
| 23 |
+
>>> print(output.player_name)
|
| 24 |
+
"Jefferson"
|
| 25 |
+
"""
|
| 26 |
+
# Handle string responses
|
| 27 |
+
if isinstance(result, str):
|
| 28 |
+
try:
|
| 29 |
+
result = json.loads(result)
|
| 30 |
+
except:
|
| 31 |
+
return None
|
| 32 |
+
|
| 33 |
+
# Handle direct output class instance
|
| 34 |
+
if isinstance(result, output_class):
|
| 35 |
+
return result
|
| 36 |
+
|
| 37 |
+
# Handle dict with expected fields
|
| 38 |
+
if isinstance(result, dict):
|
| 39 |
+
# Try direct conversion first
|
| 40 |
+
if 'type' in result:
|
| 41 |
+
try:
|
| 42 |
+
return output_class(**result)
|
| 43 |
+
except:
|
| 44 |
+
pass
|
| 45 |
+
|
| 46 |
+
# Handle A2A wrapper format
|
| 47 |
+
if 'status' in result and 'message' in result.get('status', {}):
|
| 48 |
+
return _extract_from_wrapper(result, output_class)
|
| 49 |
+
|
| 50 |
+
return None
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def _extract_from_wrapper(wrapped_result: Dict, output_class: type[BaseModel]) -> Optional[BaseModel]:
|
| 54 |
+
"""Extract response from A2A wrapper format."""
|
| 55 |
+
try:
|
| 56 |
+
message = wrapped_result['status']['message']
|
| 57 |
+
if 'parts' in message and len(message['parts']) > 0:
|
| 58 |
+
text = message['parts'][0].get('text', '')
|
| 59 |
+
agent_response = json.loads(text)
|
| 60 |
+
return output_class(**agent_response)
|
| 61 |
+
except Exception:
|
| 62 |
+
pass
|
| 63 |
+
return None
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def extract_task_id(result: Any) -> Optional[str]:
|
| 67 |
+
"""Extract task_id from A2A response if present."""
|
| 68 |
+
if isinstance(result, dict) and 'task_id' in result:
|
| 69 |
+
return result['task_id']
|
| 70 |
+
return None
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def build_pick_prompt(
|
| 74 |
+
team_num: int,
|
| 75 |
+
available_players: List[str],
|
| 76 |
+
previous_picks: List[str],
|
| 77 |
+
round_num: int,
|
| 78 |
+
context: str = ""
|
| 79 |
+
) -> str:
|
| 80 |
+
"""
|
| 81 |
+
Build a prompt for an agent to make a pick.
|
| 82 |
+
|
| 83 |
+
Args:
|
| 84 |
+
team_num: Team number making the pick
|
| 85 |
+
available_players: List of available player names with positions
|
| 86 |
+
previous_picks: List of players already picked by this team
|
| 87 |
+
round_num: Current round number
|
| 88 |
+
context: Additional context from conversation history
|
| 89 |
+
|
| 90 |
+
Returns:
|
| 91 |
+
Formatted prompt string
|
| 92 |
+
"""
|
| 93 |
+
prompt = f"""🚨 IT'S YOUR TIME TO DOMINATE! 🚨 (Round {round_num})
|
| 94 |
+
{context}
|
| 95 |
+
Available top players: {', '.join(available_players)}
|
| 96 |
+
Your roster so far: {', '.join(previous_picks) if previous_picks else 'None yet'}
|
| 97 |
+
|
| 98 |
+
Make your pick and DESTROY the competition! 💪
|
| 99 |
+
Output an A2AOutput with type="pick", player_name, reasoning (with emojis!), and SAVAGE trash_talk!
|
| 100 |
+
Remember your ENEMIES and CRUSH their dreams! Use emojis to emphasize your DOMINANCE! 🔥"""
|
| 101 |
+
|
| 102 |
+
return prompt
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def build_comment_prompt(
|
| 106 |
+
commenting_team: int,
|
| 107 |
+
picking_team: int,
|
| 108 |
+
player_picked: str,
|
| 109 |
+
round_num: int,
|
| 110 |
+
context: str = ""
|
| 111 |
+
) -> str:
|
| 112 |
+
"""
|
| 113 |
+
Build a prompt for an agent to comment on another team's pick.
|
| 114 |
+
|
| 115 |
+
Args:
|
| 116 |
+
commenting_team: Team number making the comment
|
| 117 |
+
picking_team: Team number that made the pick
|
| 118 |
+
player_picked: Name of player that was picked
|
| 119 |
+
round_num: Current round number
|
| 120 |
+
context: Additional context from conversation history
|
| 121 |
+
|
| 122 |
+
Returns:
|
| 123 |
+
Formatted prompt string
|
| 124 |
+
"""
|
| 125 |
+
prompt = f"""🎯 Team {picking_team} just picked {player_picked}!
|
| 126 |
+
{context}
|
| 127 |
+
This is your chance to DESTROY them with your superior knowledge! 💥
|
| 128 |
+
Should you UNLEASH your wisdom? Output an A2AOutput with type="comment", should_comment (true/false), and a DEVASTATING comment with emojis!
|
| 129 |
+
If they're your RIVAL, make it PERSONAL! If they made a BAD pick, ROAST THEM! 🔥
|
| 130 |
+
Use emojis to make your point UNFORGETTABLE! 😈"""
|
| 131 |
+
|
| 132 |
+
return prompt
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
def format_available_players(players: List[str], player_info: Dict[str, Dict]) -> List[str]:
|
| 136 |
+
"""
|
| 137 |
+
Format available players with their positions.
|
| 138 |
+
|
| 139 |
+
Args:
|
| 140 |
+
players: List of player names
|
| 141 |
+
player_info: Dictionary with player information
|
| 142 |
+
|
| 143 |
+
Returns:
|
| 144 |
+
List of formatted strings like "Jefferson (WR)"
|
| 145 |
+
"""
|
| 146 |
+
formatted = []
|
| 147 |
+
for player in players[:10]: # Top 10 only
|
| 148 |
+
info = player_info.get(player, {})
|
| 149 |
+
formatted.append(f"{player} ({info.get('pos', '??')})")
|
| 150 |
+
return formatted
|
core/a2a_with_tools.py
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Simpler A2A Implementation using a2a_tool_async
|
| 4 |
+
This shows how to use the a2a_tool pattern for easier agent communication.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import asyncio
|
| 8 |
+
from typing import Dict, List
|
| 9 |
+
from pydantic import BaseModel
|
| 10 |
+
from any_agent import AgentConfig, AnyAgent
|
| 11 |
+
from any_agent.serving import A2AServingConfig
|
| 12 |
+
from any_agent.tools import a2a_tool_async
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
# Define output types
|
| 16 |
+
class PickDecision(BaseModel):
|
| 17 |
+
player: str
|
| 18 |
+
reasoning: str
|
| 19 |
+
trash_talk: str
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class CommentDecision(BaseModel):
|
| 23 |
+
should_comment: bool
|
| 24 |
+
comment: str = ""
|
| 25 |
+
is_salty: bool = False
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
# Create specialized draft agents
|
| 29 |
+
async def create_zero_rb_agent():
|
| 30 |
+
"""Create and serve a Zero RB strategy agent."""
|
| 31 |
+
agent = await AnyAgent.create_async(
|
| 32 |
+
"openai",
|
| 33 |
+
AgentConfig(
|
| 34 |
+
name="zero_rb_agent",
|
| 35 |
+
model_id="gpt-4o-mini",
|
| 36 |
+
instructions="""You are Team 1, a Zero RB fanatic.
|
| 37 |
+
|
| 38 |
+
Your beliefs:
|
| 39 |
+
- WRs > RBs always
|
| 40 |
+
- RBs get injured too much
|
| 41 |
+
- Mock anyone who takes RBs early
|
| 42 |
+
- Your motto: "Zero RB or Zero Championships!"
|
| 43 |
+
|
| 44 |
+
Be aggressive and confident in your strategy.""",
|
| 45 |
+
description="Zero RB strategy agent - avoids RBs early",
|
| 46 |
+
output_type=PickDecision | CommentDecision,
|
| 47 |
+
)
|
| 48 |
+
)
|
| 49 |
+
|
| 50 |
+
# Serve the agent
|
| 51 |
+
await agent.serve_async(
|
| 52 |
+
A2AServingConfig(
|
| 53 |
+
port=5001,
|
| 54 |
+
task_timeout_minutes=30,
|
| 55 |
+
)
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
return agent
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
async def create_robust_rb_agent():
|
| 62 |
+
"""Create and serve a Robust RB strategy agent."""
|
| 63 |
+
agent = await AnyAgent.create_async(
|
| 64 |
+
"openai",
|
| 65 |
+
AgentConfig(
|
| 66 |
+
name="robust_rb_agent",
|
| 67 |
+
model_id="gpt-4o-mini",
|
| 68 |
+
instructions="""You are Team 3, a Robust RB traditionalist.
|
| 69 |
+
|
| 70 |
+
Your beliefs:
|
| 71 |
+
- RBs win championships
|
| 72 |
+
- WR-heavy teams are soft
|
| 73 |
+
- You need a strong RB foundation
|
| 74 |
+
- Your motto: "Establish the run game!"
|
| 75 |
+
|
| 76 |
+
Be old-school and dismissive of modern strategies.""",
|
| 77 |
+
description="Robust RB strategy agent - prioritizes RBs",
|
| 78 |
+
output_type=PickDecision | CommentDecision,
|
| 79 |
+
)
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
await agent.serve_async(
|
| 83 |
+
A2AServingConfig(
|
| 84 |
+
port=5003,
|
| 85 |
+
task_timeout_minutes=30,
|
| 86 |
+
)
|
| 87 |
+
)
|
| 88 |
+
|
| 89 |
+
return agent
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
# Draft Coordinator using a2a_tools
|
| 93 |
+
class SimpleA2ADraftCoordinator:
|
| 94 |
+
"""Simpler coordinator using a2a_tool_async."""
|
| 95 |
+
|
| 96 |
+
def __init__(self):
|
| 97 |
+
self.agents = {}
|
| 98 |
+
self.agent_tools = {}
|
| 99 |
+
self.task_ids = {}
|
| 100 |
+
|
| 101 |
+
async def setup(self):
|
| 102 |
+
"""Setup agents and create a2a tools."""
|
| 103 |
+
print("🚀 Starting agent servers...")
|
| 104 |
+
|
| 105 |
+
# Create and serve agents
|
| 106 |
+
self.agents['zero_rb'] = await create_zero_rb_agent()
|
| 107 |
+
self.agents['robust_rb'] = await create_robust_rb_agent()
|
| 108 |
+
|
| 109 |
+
# Wait for servers to start
|
| 110 |
+
await asyncio.sleep(2)
|
| 111 |
+
|
| 112 |
+
# Create a2a tools for each agent
|
| 113 |
+
self.agent_tools['zero_rb'] = await a2a_tool_async(
|
| 114 |
+
"http://localhost:5001/zero_rb_agent"
|
| 115 |
+
)
|
| 116 |
+
self.agent_tools['robust_rb'] = await a2a_tool_async(
|
| 117 |
+
"http://localhost:5003/robust_rb_agent"
|
| 118 |
+
)
|
| 119 |
+
|
| 120 |
+
print("✅ All agents ready for A2A communication!")
|
| 121 |
+
|
| 122 |
+
async def simulate_pick_and_comment(self):
|
| 123 |
+
"""Simulate a pick and comment exchange."""
|
| 124 |
+
|
| 125 |
+
# 1. Zero RB makes a pick
|
| 126 |
+
print("\n📋 Team 1 (Zero RB) is making their pick...")
|
| 127 |
+
|
| 128 |
+
pick_prompt = """It's your turn to pick!
|
| 129 |
+
|
| 130 |
+
Available: Justin Jefferson, Christian McCaffrey, CeeDee Lamb
|
| 131 |
+
Previous picks: None
|
| 132 |
+
|
| 133 |
+
Make your pick and explain why. Output a PickDecision."""
|
| 134 |
+
|
| 135 |
+
# Get pick from Zero RB agent
|
| 136 |
+
zero_rb_pick = await self.agent_tools['zero_rb'](
|
| 137 |
+
pick_prompt,
|
| 138 |
+
task_id=self.task_ids.get('zero_rb') # Continue conversation if exists
|
| 139 |
+
)
|
| 140 |
+
|
| 141 |
+
# Store task ID for multi-turn
|
| 142 |
+
if 'zero_rb' not in self.task_ids and hasattr(zero_rb_pick, 'id'):
|
| 143 |
+
self.task_ids['zero_rb'] = zero_rb_pick.id
|
| 144 |
+
|
| 145 |
+
print(f"\n📘 Team 1 selects: {zero_rb_pick.player}")
|
| 146 |
+
print(f" Reasoning: {zero_rb_pick.reasoning}")
|
| 147 |
+
print(f" 💬 {zero_rb_pick.trash_talk}")
|
| 148 |
+
|
| 149 |
+
# 2. Ask Robust RB to comment
|
| 150 |
+
print("\n🤔 Team 3 (Robust RB) considering a comment...")
|
| 151 |
+
|
| 152 |
+
comment_prompt = f"""Team 1 (Zero RB) just picked {zero_rb_pick.player}.
|
| 153 |
+
They said: "{zero_rb_pick.trash_talk}"
|
| 154 |
+
|
| 155 |
+
Should you comment on this pick? Remember you believe in RBs.
|
| 156 |
+
Output a CommentDecision."""
|
| 157 |
+
|
| 158 |
+
# Get comment decision
|
| 159 |
+
robust_comment = await self.agent_tools['robust_rb'](
|
| 160 |
+
comment_prompt,
|
| 161 |
+
task_id=self.task_ids.get('robust_rb')
|
| 162 |
+
)
|
| 163 |
+
|
| 164 |
+
if 'robust_rb' not in self.task_ids and hasattr(robust_comment, 'id'):
|
| 165 |
+
self.task_ids['robust_rb'] = robust_comment.id
|
| 166 |
+
|
| 167 |
+
if robust_comment.should_comment:
|
| 168 |
+
print(f"\n📙 Team 3 → Team 1: {robust_comment.comment}")
|
| 169 |
+
if robust_comment.is_salty:
|
| 170 |
+
print(" (😤 They seem upset!)")
|
| 171 |
+
|
| 172 |
+
# 3. Continue the conversation
|
| 173 |
+
if robust_comment.should_comment:
|
| 174 |
+
print("\n💭 Team 1 processing the comment...")
|
| 175 |
+
|
| 176 |
+
response_prompt = f"""Team 3 (Robust RB) just said to you: "{robust_comment.comment}"
|
| 177 |
+
|
| 178 |
+
They're criticizing your Zero RB approach. How do you respond?
|
| 179 |
+
Remember to defend your strategy! Output a CommentDecision."""
|
| 180 |
+
|
| 181 |
+
zero_response = await self.agent_tools['zero_rb'](
|
| 182 |
+
response_prompt,
|
| 183 |
+
task_id=self.task_ids['zero_rb'] # Continue the conversation!
|
| 184 |
+
)
|
| 185 |
+
|
| 186 |
+
if zero_response.should_comment:
|
| 187 |
+
print(f"\n📘 Team 1 → Team 3: {zero_response.comment}")
|
| 188 |
+
|
| 189 |
+
async def cleanup(self):
|
| 190 |
+
"""Shutdown all agents."""
|
| 191 |
+
print("\n🛑 Shutting down agents...")
|
| 192 |
+
# In a real implementation, you'd properly shutdown the servers
|
| 193 |
+
# For now, the servers will stop when the program exits
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
# Using a coordinator agent with a2a tools
|
| 197 |
+
async def create_coordinator_agent():
|
| 198 |
+
"""Create a coordinator agent that uses other agents via a2a tools."""
|
| 199 |
+
|
| 200 |
+
# First, start the draft agents
|
| 201 |
+
print("🚀 Starting draft agents...")
|
| 202 |
+
zero_rb = await create_zero_rb_agent()
|
| 203 |
+
robust_rb = await create_robust_rb_agent()
|
| 204 |
+
|
| 205 |
+
await asyncio.sleep(2)
|
| 206 |
+
|
| 207 |
+
# Create a2a tools
|
| 208 |
+
zero_rb_tool = await a2a_tool_async("http://localhost:5001/zero_rb_agent")
|
| 209 |
+
robust_rb_tool = await a2a_tool_async("http://localhost:5003/robust_rb_agent")
|
| 210 |
+
|
| 211 |
+
# Create coordinator with a2a tools
|
| 212 |
+
coordinator = await AnyAgent.create_async(
|
| 213 |
+
"openai",
|
| 214 |
+
AgentConfig(
|
| 215 |
+
model_id="gpt-4o",
|
| 216 |
+
instructions="""You are the draft coordinator.
|
| 217 |
+
|
| 218 |
+
Use the Zero RB and Robust RB agents to simulate a draft.
|
| 219 |
+
1. Ask each agent to make picks
|
| 220 |
+
2. Get them to comment on each other's picks
|
| 221 |
+
3. Facilitate their debate
|
| 222 |
+
4. Keep the draft moving
|
| 223 |
+
|
| 224 |
+
The agents will maintain their own conversation history via task IDs.""",
|
| 225 |
+
tools=[zero_rb_tool, robust_rb_tool],
|
| 226 |
+
)
|
| 227 |
+
)
|
| 228 |
+
|
| 229 |
+
# Run a draft simulation
|
| 230 |
+
result = await coordinator.run_async("""
|
| 231 |
+
Simulate a mini draft:
|
| 232 |
+
1. Ask Zero RB agent to pick from: Jefferson, McCaffrey, Lamb
|
| 233 |
+
2. Get Robust RB to comment on the pick
|
| 234 |
+
3. Let them debate briefly
|
| 235 |
+
4. Summarize their interaction
|
| 236 |
+
""")
|
| 237 |
+
|
| 238 |
+
print("\n📊 Coordinator Summary:")
|
| 239 |
+
print(result.final_output)
|
| 240 |
+
|
| 241 |
+
|
| 242 |
+
# Main execution
|
| 243 |
+
async def main():
|
| 244 |
+
"""Run the A2A draft demonstration."""
|
| 245 |
+
|
| 246 |
+
print("=== A2A Draft Demo with any-agent ===\n")
|
| 247 |
+
|
| 248 |
+
print("Option 1: Simple A2A Communication")
|
| 249 |
+
coordinator = SimpleA2ADraftCoordinator()
|
| 250 |
+
await coordinator.setup()
|
| 251 |
+
await coordinator.simulate_pick_and_comment()
|
| 252 |
+
await coordinator.cleanup()
|
| 253 |
+
|
| 254 |
+
print("\n" + "="*50 + "\n")
|
| 255 |
+
|
| 256 |
+
print("Option 2: Coordinator Agent with A2A Tools")
|
| 257 |
+
# Uncomment to run this version
|
| 258 |
+
# await create_coordinator_agent()
|
| 259 |
+
|
| 260 |
+
|
| 261 |
+
if __name__ == "__main__":
|
| 262 |
+
asyncio.run(main())
|
core/agent.py
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Core Fantasy Draft Agent using any-agent framework.
|
| 3 |
+
Supports multi-turn conversations and maintains draft state.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
from typing import List, Dict, Optional, Annotated
|
| 8 |
+
from dotenv import load_dotenv
|
| 9 |
+
from any_agent import AnyAgent, AgentConfig
|
| 10 |
+
from .data import TOP_PLAYERS, get_player_info, get_best_available, get_players_by_position
|
| 11 |
+
|
| 12 |
+
# Load environment variables from .env file
|
| 13 |
+
load_dotenv()
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class FantasyDraftAgent:
|
| 17 |
+
def __init__(self, framework: str = "tinyagent", model_id: str = "gpt-4o-mini"):
|
| 18 |
+
"""Initialize the Fantasy Draft Agent."""
|
| 19 |
+
self.framework = framework
|
| 20 |
+
self.model_id = model_id
|
| 21 |
+
|
| 22 |
+
# Draft state management
|
| 23 |
+
self.draft_state = {
|
| 24 |
+
"my_picks": [],
|
| 25 |
+
"all_picks": [],
|
| 26 |
+
"round": 1,
|
| 27 |
+
"pick_number": 5, # Default to 5th pick
|
| 28 |
+
"league_size": 12,
|
| 29 |
+
"roster_needs": {
|
| 30 |
+
"QB": 1, "RB": 2, "WR": 2, "TE": 1, "FLEX": 1
|
| 31 |
+
},
|
| 32 |
+
"conversation_history": []
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
# Initialize the agent with tools
|
| 36 |
+
self.agent = AnyAgent.create(
|
| 37 |
+
framework,
|
| 38 |
+
AgentConfig(
|
| 39 |
+
model_id=model_id,
|
| 40 |
+
instructions=self._get_instructions(),
|
| 41 |
+
tools=[
|
| 42 |
+
self._get_player_stats,
|
| 43 |
+
self._check_best_available,
|
| 44 |
+
self._analyze_position_scarcity,
|
| 45 |
+
self._get_team_stack_options,
|
| 46 |
+
],
|
| 47 |
+
model_args={"temperature": 0.7}
|
| 48 |
+
)
|
| 49 |
+
)
|
| 50 |
+
|
| 51 |
+
def _get_instructions(self) -> str:
|
| 52 |
+
"""Get the agent's system instructions."""
|
| 53 |
+
return """You are an expert fantasy football draft assistant with deep knowledge of:
|
| 54 |
+
- Player values, tiers, and ADP (Average Draft Position)
|
| 55 |
+
- Draft strategy (Zero RB, Hero RB, Robust RB, etc.)
|
| 56 |
+
- Position scarcity and value-based drafting
|
| 57 |
+
- Team stacking strategies
|
| 58 |
+
- Injury concerns and player situations
|
| 59 |
+
|
| 60 |
+
Your role is to:
|
| 61 |
+
1. Provide draft advice based on the current situation
|
| 62 |
+
2. Remember previous picks and conversations in the draft
|
| 63 |
+
3. Adapt strategy based on how the draft unfolds
|
| 64 |
+
4. Explain your reasoning clearly
|
| 65 |
+
5. Consider both floor and ceiling when recommending players
|
| 66 |
+
|
| 67 |
+
Always maintain context from previous turns in our conversation.
|
| 68 |
+
Reference specific players and situations we've discussed.
|
| 69 |
+
Be conversational but authoritative in your recommendations."""
|
| 70 |
+
|
| 71 |
+
def _get_player_stats(
|
| 72 |
+
self,
|
| 73 |
+
player_name: Annotated[str, "The name of the player to look up"]
|
| 74 |
+
) -> str:
|
| 75 |
+
"""Get detailed stats for a specific player."""
|
| 76 |
+
info = get_player_info(player_name)
|
| 77 |
+
if not info:
|
| 78 |
+
return f"No data found for {player_name}"
|
| 79 |
+
|
| 80 |
+
return (f"{player_name} ({info['pos']}, {info['team']}) - "
|
| 81 |
+
f"ADP: {info['adp']}, Tier: {info['tier']}, "
|
| 82 |
+
f"2023 PPG: {info['ppg_2023']}")
|
| 83 |
+
|
| 84 |
+
def _check_best_available(
|
| 85 |
+
self,
|
| 86 |
+
position: Annotated[Optional[str], "Position to filter by (RB, WR, QB, TE)"] = None
|
| 87 |
+
) -> str:
|
| 88 |
+
"""Check the best available player overall or by position."""
|
| 89 |
+
best = get_best_available(self.draft_state["all_picks"], position)
|
| 90 |
+
if not best or not best[0]:
|
| 91 |
+
pos_str = f" at {position}" if position else ""
|
| 92 |
+
return f"No players available{pos_str}"
|
| 93 |
+
|
| 94 |
+
name, info = best
|
| 95 |
+
return (f"Best available{' ' + position if position else ''}: "
|
| 96 |
+
f"{name} ({info['pos']}, {info['team']}) - "
|
| 97 |
+
f"ADP: {info['adp']}, Tier: {info['tier']}, "
|
| 98 |
+
f"2023 PPG: {info['ppg_2023']}")
|
| 99 |
+
|
| 100 |
+
def _analyze_position_scarcity(
|
| 101 |
+
self,
|
| 102 |
+
position: Annotated[str, "Position to analyze (RB, WR, QB, TE)"]
|
| 103 |
+
) -> str:
|
| 104 |
+
"""Analyze scarcity at a position based on remaining players."""
|
| 105 |
+
available = get_players_by_position(position)
|
| 106 |
+
drafted = [p for p in self.draft_state["all_picks"] if p in available]
|
| 107 |
+
remaining = len(available) - len(drafted)
|
| 108 |
+
|
| 109 |
+
# Count by tier
|
| 110 |
+
tier_counts = {}
|
| 111 |
+
for name, info in available.items():
|
| 112 |
+
if name not in drafted:
|
| 113 |
+
tier = info['tier']
|
| 114 |
+
tier_counts[tier] = tier_counts.get(tier, 0) + 1
|
| 115 |
+
|
| 116 |
+
analysis = f"Position analysis for {position}:\n"
|
| 117 |
+
analysis += f"Total remaining: {remaining}\n"
|
| 118 |
+
for tier in sorted(tier_counts.keys()):
|
| 119 |
+
analysis += f"Tier {tier}: {tier_counts[tier]} players\n"
|
| 120 |
+
|
| 121 |
+
return analysis
|
| 122 |
+
|
| 123 |
+
def _get_team_stack_options(
|
| 124 |
+
self,
|
| 125 |
+
team: Annotated[str, "Team abbreviation (e.g., KC, BUF, MIA)"]
|
| 126 |
+
) -> str:
|
| 127 |
+
"""Get stacking options for a specific team."""
|
| 128 |
+
team_players = {name: info for name, info in TOP_PLAYERS.items()
|
| 129 |
+
if info.get('team') == team and name not in self.draft_state["all_picks"]}
|
| 130 |
+
|
| 131 |
+
if not team_players:
|
| 132 |
+
return f"No available players from {team}"
|
| 133 |
+
|
| 134 |
+
result = f"Available {team} players for stacking:\n"
|
| 135 |
+
for name, info in sorted(team_players.items(), key=lambda x: x[1]['adp']):
|
| 136 |
+
result += f"- {name} ({info['pos']}) - ADP: {info['adp']}\n"
|
| 137 |
+
|
| 138 |
+
return result
|
| 139 |
+
|
| 140 |
+
def update_draft_state(self, pick: str, is_my_pick: bool = False):
|
| 141 |
+
"""Update the draft state with a new pick."""
|
| 142 |
+
self.draft_state["all_picks"].append(pick)
|
| 143 |
+
if is_my_pick:
|
| 144 |
+
self.draft_state["my_picks"].append(pick)
|
| 145 |
+
|
| 146 |
+
# Update round
|
| 147 |
+
total_picks = len(self.draft_state["all_picks"])
|
| 148 |
+
self.draft_state["round"] = (total_picks // self.draft_state["league_size"]) + 1
|
| 149 |
+
|
| 150 |
+
def run(self, prompt: str, maintain_context: bool = True) -> str:
|
| 151 |
+
"""Run the agent with a prompt, maintaining conversation context."""
|
| 152 |
+
# Build context from previous conversation if needed
|
| 153 |
+
if maintain_context and self.draft_state["conversation_history"]:
|
| 154 |
+
context = self._build_conversation_context()
|
| 155 |
+
full_prompt = f"{context}\n\nCurrent message: {prompt}"
|
| 156 |
+
else:
|
| 157 |
+
full_prompt = prompt
|
| 158 |
+
|
| 159 |
+
# Add current draft state to prompt
|
| 160 |
+
draft_context = self._build_draft_context()
|
| 161 |
+
full_prompt = f"{draft_context}\n\n{full_prompt}"
|
| 162 |
+
|
| 163 |
+
# Run the agent
|
| 164 |
+
trace = self.agent.run(full_prompt)
|
| 165 |
+
|
| 166 |
+
# Store conversation turn
|
| 167 |
+
self.draft_state["conversation_history"].append({
|
| 168 |
+
"user": prompt,
|
| 169 |
+
"agent": trace.final_output
|
| 170 |
+
})
|
| 171 |
+
|
| 172 |
+
return trace.final_output
|
| 173 |
+
|
| 174 |
+
def _build_conversation_context(self) -> str:
|
| 175 |
+
"""Build context from conversation history."""
|
| 176 |
+
if not self.draft_state["conversation_history"]:
|
| 177 |
+
return ""
|
| 178 |
+
|
| 179 |
+
context = "Previous conversation:\n"
|
| 180 |
+
# Include last 3 exchanges for context
|
| 181 |
+
recent_history = self.draft_state["conversation_history"][-3:]
|
| 182 |
+
for turn in recent_history:
|
| 183 |
+
context += f"User: {turn['user']}\n"
|
| 184 |
+
context += f"Assistant: {turn['agent']}\n\n"
|
| 185 |
+
|
| 186 |
+
return context
|
| 187 |
+
|
| 188 |
+
def _build_draft_context(self) -> str:
|
| 189 |
+
"""Build context about current draft state."""
|
| 190 |
+
context = f"Current draft state:\n"
|
| 191 |
+
context += f"Round: {self.draft_state['round']}\n"
|
| 192 |
+
context += f"Your pick number: {self.draft_state['pick_number']}\n"
|
| 193 |
+
|
| 194 |
+
if self.draft_state["my_picks"]:
|
| 195 |
+
context += f"Your picks: {', '.join(self.draft_state['my_picks'])}\n"
|
| 196 |
+
|
| 197 |
+
if self.draft_state["all_picks"]:
|
| 198 |
+
recent_picks = self.draft_state["all_picks"][-5:]
|
| 199 |
+
context += f"Recent picks: {', '.join(recent_picks)}\n"
|
| 200 |
+
|
| 201 |
+
return context
|
| 202 |
+
|
| 203 |
+
def reset_draft(self):
|
| 204 |
+
"""Reset the draft state for a new draft."""
|
| 205 |
+
self.draft_state = {
|
| 206 |
+
"my_picks": [],
|
| 207 |
+
"all_picks": [],
|
| 208 |
+
"round": 1,
|
| 209 |
+
"pick_number": 5,
|
| 210 |
+
"league_size": 12,
|
| 211 |
+
"roster_needs": {
|
| 212 |
+
"QB": 1, "RB": 2, "WR": 2, "TE": 1, "FLEX": 1
|
| 213 |
+
},
|
| 214 |
+
"conversation_history": []
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
|
| 218 |
+
# Simple test function
|
| 219 |
+
def test_agent():
|
| 220 |
+
"""Test the fantasy draft agent."""
|
| 221 |
+
agent = FantasyDraftAgent()
|
| 222 |
+
|
| 223 |
+
# Test basic question
|
| 224 |
+
response = agent.run("What are the top 3 RBs available?")
|
| 225 |
+
print("Agent:", response)
|
| 226 |
+
print("\n" + "="*50 + "\n")
|
| 227 |
+
|
| 228 |
+
# Test multi-turn conversation
|
| 229 |
+
response = agent.run("I have the 5th pick. The first 4 picks were McCaffrey, Jefferson, Lamb, and Hill. What should I do?")
|
| 230 |
+
print("Agent:", response)
|
| 231 |
+
print("\n" + "="*50 + "\n")
|
| 232 |
+
|
| 233 |
+
# Follow-up that should remember context
|
| 234 |
+
response = agent.run("What about taking a WR instead?")
|
| 235 |
+
print("Agent:", response)
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
if __name__ == "__main__":
|
| 239 |
+
test_agent()
|
core/constants.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Constants for the Fantasy Draft A2A implementation.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
# Timing constants (in seconds)
|
| 6 |
+
TYPING_DELAY_SECONDS = 0.5
|
| 7 |
+
MESSAGE_DELAY_SECONDS = 1.0
|
| 8 |
+
AGENT_START_DELAY = 5.0 # Increased from 3.0 to give agents more time to fully initialize
|
| 9 |
+
AGENT_STARTUP_WAIT = 1.0 # Increased from 0.5 to ensure each agent is ready
|
| 10 |
+
DEFAULT_TIMEOUT = 30.0
|
| 11 |
+
|
| 12 |
+
# Comment configuration
|
| 13 |
+
MAX_COMMENTS_PER_PICK = 2
|
| 14 |
+
|
| 15 |
+
# Natural rivalry pairs for prioritizing comments
|
| 16 |
+
RIVAL_PAIRS = {
|
| 17 |
+
1: 3, # Zero RB vs Robust RB - natural enemies!
|
| 18 |
+
3: 1, # Robust RB vs Zero RB
|
| 19 |
+
5: [2, 6], # Upside Hunter vs BPA agents
|
| 20 |
+
2: 5, # BPA vs Upside Hunter
|
| 21 |
+
6: 5, # BPA vs Upside Hunter
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
# A2A Agent configurations
|
| 25 |
+
AGENT_CONFIGS = [
|
| 26 |
+
{
|
| 27 |
+
"team_name": "Team 1",
|
| 28 |
+
"team_num": 1,
|
| 29 |
+
"strategy": "Zero RB",
|
| 30 |
+
"port": 5001,
|
| 31 |
+
"philosophy": "RBs are INJURY MAGNETS! 🏥 WRs are the future! I'm building an AIR RAID OFFENSE that will DESTROY your pathetic ground game! 💨✈️",
|
| 32 |
+
"emoji_style": ["✈️", "💨", "🏥", "🎯"],
|
| 33 |
+
},
|
| 34 |
+
{
|
| 35 |
+
"team_name": "Team 2",
|
| 36 |
+
"team_num": 2,
|
| 37 |
+
"strategy": "BPA",
|
| 38 |
+
"port": 5002,
|
| 39 |
+
"philosophy": "I am the VALUE VULTURE! 🦅 I feast on your emotional reaches while I build a CHAMPIONSHIP ROSTER with pure analytics! 📊📈",
|
| 40 |
+
"emoji_style": ["🦅", "📊", "📈", "💰"],
|
| 41 |
+
},
|
| 42 |
+
{
|
| 43 |
+
"team_name": "Team 3",
|
| 44 |
+
"team_num": 3,
|
| 45 |
+
"strategy": "Robust RB",
|
| 46 |
+
"port": 5003,
|
| 47 |
+
"philosophy": "GROUND AND POUND, BABY! 💪 Your fancy WRs will be watching from the sidelines while my RBs BULLDOZE their way to victory! 🚜💥",
|
| 48 |
+
"emoji_style": ["💪", "🚜", "💥", "🏃"],
|
| 49 |
+
},
|
| 50 |
+
{
|
| 51 |
+
"team_name": "Team 5",
|
| 52 |
+
"team_num": 5,
|
| 53 |
+
"strategy": "Upside Hunter",
|
| 54 |
+
"port": 5005,
|
| 55 |
+
"philosophy": "BOOM OR BUST! 🎰🚀 Safe picks are for COWARDS! I'm swinging for the fences while you play it safe like a SCARED LITTLE MOUSE! 🐭💣",
|
| 56 |
+
"emoji_style": ["🎰", "🚀", "💣", "⚡"],
|
| 57 |
+
},
|
| 58 |
+
{
|
| 59 |
+
"team_name": "Team 6",
|
| 60 |
+
"team_num": 6,
|
| 61 |
+
"strategy": "BPA",
|
| 62 |
+
"port": 5006,
|
| 63 |
+
"philosophy": "Another spreadsheet warrior here to EXPLOIT your terrible decisions! 🤓💻 My algorithm laughs at your 'gut feelings'! 🤖📉",
|
| 64 |
+
"emoji_style": ["🤓", "💻", "🤖", "📉"],
|
| 65 |
+
},
|
| 66 |
+
]
|
core/data.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Static player data for Fantasy Draft Agent MVP.
|
| 3 |
+
Hardcoded top 50 fantasy football players with basic stats.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
# Top players by position with their Average Draft Position (ADP) and 2023 stats
|
| 7 |
+
TOP_PLAYERS = {
|
| 8 |
+
# Top Running Backs
|
| 9 |
+
"Christian McCaffrey": {"pos": "RB", "adp": 1.2, "tier": 1, "ppg_2023": 22.1, "team": "SF"},
|
| 10 |
+
"Austin Ekeler": {"pos": "RB", "adp": 4.5, "tier": 1, "ppg_2023": 18.7, "team": "LAC"},
|
| 11 |
+
"Bijan Robinson": {"pos": "RB", "adp": 3.8, "tier": 1, "ppg_2023": 15.2, "team": "ATL"},
|
| 12 |
+
"Saquon Barkley": {"pos": "RB", "adp": 5.2, "tier": 1, "ppg_2023": 17.3, "team": "NYG"},
|
| 13 |
+
"Tony Pollard": {"pos": "RB", "adp": 8.1, "tier": 2, "ppg_2023": 14.8, "team": "DAL"},
|
| 14 |
+
"Jonathan Taylor": {"pos": "RB", "adp": 6.7, "tier": 2, "ppg_2023": 13.5, "team": "IND"},
|
| 15 |
+
"Derrick Henry": {"pos": "RB", "adp": 11.3, "tier": 2, "ppg_2023": 13.9, "team": "TEN"},
|
| 16 |
+
"Nick Chubb": {"pos": "RB", "adp": 9.5, "tier": 2, "ppg_2023": 16.8, "team": "CLE"},
|
| 17 |
+
"Josh Jacobs": {"pos": "RB", "adp": 10.2, "tier": 2, "ppg_2023": 15.7, "team": "LV"},
|
| 18 |
+
"Breece Hall": {"pos": "RB", "adp": 7.8, "tier": 2, "ppg_2023": 14.2, "team": "NYJ"},
|
| 19 |
+
|
| 20 |
+
# Top Wide Receivers
|
| 21 |
+
"Tyreek Hill": {"pos": "WR", "adp": 3.1, "tier": 1, "ppg_2023": 20.2, "team": "MIA"},
|
| 22 |
+
"CeeDee Lamb": {"pos": "WR", "adp": 2.5, "tier": 1, "ppg_2023": 19.8, "team": "DAL"},
|
| 23 |
+
"Justin Jefferson": {"pos": "WR", "adp": 1.8, "tier": 1, "ppg_2023": 21.5, "team": "MIN"},
|
| 24 |
+
"Ja'Marr Chase": {"pos": "WR", "adp": 4.2, "tier": 1, "ppg_2023": 18.9, "team": "CIN"},
|
| 25 |
+
"A.J. Brown": {"pos": "WR", "adp": 6.3, "tier": 1, "ppg_2023": 17.6, "team": "PHI"},
|
| 26 |
+
"Stefon Diggs": {"pos": "WR", "adp": 7.1, "tier": 1, "ppg_2023": 17.2, "team": "BUF"},
|
| 27 |
+
"Amon-Ra St. Brown": {"pos": "WR", "adp": 9.8, "tier": 2, "ppg_2023": 16.4, "team": "DET"},
|
| 28 |
+
"Davante Adams": {"pos": "WR", "adp": 8.9, "tier": 2, "ppg_2023": 16.8, "team": "LV"},
|
| 29 |
+
"Chris Olave": {"pos": "WR", "adp": 14.3, "tier": 2, "ppg_2023": 14.1, "team": "NO"},
|
| 30 |
+
"DK Metcalf": {"pos": "WR", "adp": 15.7, "tier": 2, "ppg_2023": 13.8, "team": "SEA"},
|
| 31 |
+
"Mike Evans": {"pos": "WR", "adp": 16.2, "tier": 2, "ppg_2023": 15.3, "team": "TB"},
|
| 32 |
+
"Calvin Ridley": {"pos": "WR", "adp": 18.5, "tier": 3, "ppg_2023": 12.1, "team": "JAX"},
|
| 33 |
+
|
| 34 |
+
# Top Quarterbacks
|
| 35 |
+
"Josh Allen": {"pos": "QB", "adp": 24.3, "tier": 1, "ppg_2023": 24.6, "team": "BUF"},
|
| 36 |
+
"Patrick Mahomes": {"pos": "QB", "adp": 22.1, "tier": 1, "ppg_2023": 25.8, "team": "KC"},
|
| 37 |
+
"Jalen Hurts": {"pos": "QB", "adp": 19.8, "tier": 1, "ppg_2023": 26.4, "team": "PHI"},
|
| 38 |
+
"Lamar Jackson": {"pos": "QB", "adp": 31.5, "tier": 2, "ppg_2023": 21.3, "team": "BAL"},
|
| 39 |
+
"Dak Prescott": {"pos": "QB", "adp": 45.7, "tier": 2, "ppg_2023": 20.1, "team": "DAL"},
|
| 40 |
+
"Joe Burrow": {"pos": "QB", "adp": 38.2, "tier": 2, "ppg_2023": 19.8, "team": "CIN"},
|
| 41 |
+
"Justin Herbert": {"pos": "QB", "adp": 42.3, "tier": 2, "ppg_2023": 19.2, "team": "LAC"},
|
| 42 |
+
|
| 43 |
+
# Top Tight Ends
|
| 44 |
+
"Travis Kelce": {"pos": "TE", "adp": 12.4, "tier": 1, "ppg_2023": 16.4, "team": "KC"},
|
| 45 |
+
"Mark Andrews": {"pos": "TE", "adp": 25.6, "tier": 1, "ppg_2023": 13.2, "team": "BAL"},
|
| 46 |
+
"T.J. Hockenson": {"pos": "TE", "adp": 34.8, "tier": 2, "ppg_2023": 11.8, "team": "MIN"},
|
| 47 |
+
"George Kittle": {"pos": "TE", "adp": 41.2, "tier": 2, "ppg_2023": 10.9, "team": "SF"},
|
| 48 |
+
"Darren Waller": {"pos": "TE", "adp": 48.5, "tier": 2, "ppg_2023": 9.7, "team": "NYG"},
|
| 49 |
+
|
| 50 |
+
# Additional depth players for rounds 5-10
|
| 51 |
+
"Jahmyr Gibbs": {"pos": "RB", "adp": 21.3, "tier": 3, "ppg_2023": 11.2, "team": "DET"},
|
| 52 |
+
"Rhamondre Stevenson": {"pos": "RB", "adp": 23.7, "tier": 3, "ppg_2023": 12.1, "team": "NE"},
|
| 53 |
+
"Kenneth Walker": {"pos": "RB", "adp": 28.4, "tier": 3, "ppg_2023": 11.8, "team": "SEA"},
|
| 54 |
+
"DeAndre Hopkins": {"pos": "WR", "adp": 52.1, "tier": 3, "ppg_2023": 10.2, "team": "TEN"},
|
| 55 |
+
"Keenan Allen": {"pos": "WR", "adp": 29.8, "tier": 3, "ppg_2023": 13.1, "team": "LAC"},
|
| 56 |
+
"Amari Cooper": {"pos": "WR", "adp": 37.2, "tier": 3, "ppg_2023": 12.7, "team": "CLE"},
|
| 57 |
+
"Terry McLaurin": {"pos": "WR", "adp": 44.6, "tier": 3, "ppg_2023": 11.9, "team": "WAS"},
|
| 58 |
+
"DJ Moore": {"pos": "WR", "adp": 35.9, "tier": 3, "ppg_2023": 11.4, "team": "CHI"},
|
| 59 |
+
"Deshaun Watson": {"pos": "QB", "adp": 68.3, "tier": 3, "ppg_2023": 16.7, "team": "CLE"},
|
| 60 |
+
"Trevor Lawrence": {"pos": "QB", "adp": 71.2, "tier": 3, "ppg_2023": 17.2, "team": "JAX"},
|
| 61 |
+
|
| 62 |
+
# Sleepers and late-round targets
|
| 63 |
+
"Jordan Addison": {"pos": "WR", "adp": 85.3, "tier": 4, "ppg_2023": 8.2, "team": "MIN"},
|
| 64 |
+
"Jahan Dotson": {"pos": "WR", "adp": 92.7, "tier": 4, "ppg_2023": 7.8, "team": "WAS"},
|
| 65 |
+
"Khalil Herbert": {"pos": "RB", "adp": 89.1, "tier": 4, "ppg_2023": 8.9, "team": "CHI"},
|
| 66 |
+
"Zay Flowers": {"pos": "WR", "adp": 76.4, "tier": 4, "ppg_2023": 9.1, "team": "BAL"},
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def get_player_info(player_name: str) -> dict:
|
| 71 |
+
"""Get player information by name."""
|
| 72 |
+
return TOP_PLAYERS.get(player_name, {})
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def get_players_by_position(position: str) -> dict:
|
| 76 |
+
"""Get all players at a specific position."""
|
| 77 |
+
return {name: info for name, info in TOP_PLAYERS.items() if info["pos"] == position}
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def get_players_by_tier(tier: int) -> dict:
|
| 81 |
+
"""Get all players in a specific tier."""
|
| 82 |
+
return {name: info for name, info in TOP_PLAYERS.items() if info["tier"] == tier}
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def get_available_players(drafted_players: list) -> dict:
|
| 86 |
+
"""Get all players not yet drafted."""
|
| 87 |
+
return {name: info for name, info in TOP_PLAYERS.items() if name not in drafted_players}
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def get_best_available(drafted_players: list, position: str = None) -> tuple:
|
| 91 |
+
"""Get the best available player overall or by position."""
|
| 92 |
+
available = get_available_players(drafted_players)
|
| 93 |
+
|
| 94 |
+
if position:
|
| 95 |
+
available = {name: info for name, info in available.items() if info["pos"] == position}
|
| 96 |
+
|
| 97 |
+
if not available:
|
| 98 |
+
return None, None
|
| 99 |
+
|
| 100 |
+
# Sort by ADP (lower is better)
|
| 101 |
+
best = min(available.items(), key=lambda x: x[1]["adp"])
|
| 102 |
+
return best
|
core/dynamic_a2a_manager.py
ADDED
|
@@ -0,0 +1,350 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Dynamic A2A Manager with multi-user support through dynamic port allocation.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import asyncio
|
| 6 |
+
import socket
|
| 7 |
+
import hashlib
|
| 8 |
+
import os
|
| 9 |
+
from typing import List, Dict, Optional, Tuple
|
| 10 |
+
from pydantic import BaseModel
|
| 11 |
+
from any_agent import AgentConfig, AnyAgent
|
| 12 |
+
from any_agent.serving import A2AServingConfig
|
| 13 |
+
from any_agent.tools import a2a_tool_async
|
| 14 |
+
from core.constants import (
|
| 15 |
+
AGENT_CONFIGS,
|
| 16 |
+
AGENT_START_DELAY,
|
| 17 |
+
AGENT_STARTUP_WAIT,
|
| 18 |
+
DEFAULT_TIMEOUT,
|
| 19 |
+
MAX_COMMENTS_PER_PICK,
|
| 20 |
+
RIVAL_PAIRS
|
| 21 |
+
)
|
| 22 |
+
from core.data import TOP_PLAYERS
|
| 23 |
+
from core.a2a_helpers import (
|
| 24 |
+
parse_a2a_response,
|
| 25 |
+
extract_task_id,
|
| 26 |
+
format_available_players
|
| 27 |
+
)
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
# A2A Output model (same as in app_enhanced.py)
|
| 31 |
+
class A2AOutput(BaseModel):
|
| 32 |
+
"""Combined output type for A2A agents."""
|
| 33 |
+
type: str # "pick" or "comment"
|
| 34 |
+
# Pick fields
|
| 35 |
+
player_name: Optional[str] = None
|
| 36 |
+
reasoning: Optional[str] = None
|
| 37 |
+
trash_talk: Optional[str] = None
|
| 38 |
+
# Comment fields
|
| 39 |
+
should_comment: Optional[bool] = None
|
| 40 |
+
comment: Optional[str] = None
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
class DynamicA2AAgentManager:
|
| 44 |
+
"""A2A Agent Manager with dynamic port allocation for multi-user support."""
|
| 45 |
+
|
| 46 |
+
# Class-level tracking of used ports
|
| 47 |
+
_used_ports = set()
|
| 48 |
+
_port_lock = asyncio.Lock()
|
| 49 |
+
|
| 50 |
+
def __init__(self, session_id: str = None, max_comments_per_pick=MAX_COMMENTS_PER_PICK):
|
| 51 |
+
self.session_id = session_id or self._generate_session_id()
|
| 52 |
+
self.agents = {}
|
| 53 |
+
self.agent_tools = {}
|
| 54 |
+
self.serve_tasks = []
|
| 55 |
+
self.is_running = False
|
| 56 |
+
self.task_ids = {}
|
| 57 |
+
self.max_comments_per_pick = max_comments_per_pick
|
| 58 |
+
self.allocated_ports = []
|
| 59 |
+
|
| 60 |
+
def _generate_session_id(self) -> str:
|
| 61 |
+
"""Generate a unique session ID."""
|
| 62 |
+
import time
|
| 63 |
+
import random
|
| 64 |
+
return hashlib.md5(f"{time.time()}{random.random()}".encode()).hexdigest()[:8]
|
| 65 |
+
|
| 66 |
+
async def _find_available_ports(self, count: int = 5, start: int = 5000, end: int = 9000) -> List[int]:
|
| 67 |
+
"""Find available consecutive ports for agents."""
|
| 68 |
+
async with self._port_lock:
|
| 69 |
+
# On HF Spaces, try different port ranges
|
| 70 |
+
port_ranges = [(8000, 9000), (5000, 6000), (7000, 8000), (9000, 10000)] if os.getenv("SPACE_ID") else [(start, end)]
|
| 71 |
+
|
| 72 |
+
for range_start, range_end in port_ranges:
|
| 73 |
+
# Try to find a consecutive range
|
| 74 |
+
for base_port in range(range_start, range_end - count, 10):
|
| 75 |
+
ports = list(range(base_port, base_port + count))
|
| 76 |
+
|
| 77 |
+
# Check if any port in range is already used
|
| 78 |
+
if any(p in self._used_ports for p in ports):
|
| 79 |
+
continue
|
| 80 |
+
|
| 81 |
+
# Check if ports are actually available on the system
|
| 82 |
+
if await self._check_ports_available(ports):
|
| 83 |
+
# Reserve these ports
|
| 84 |
+
self._used_ports.update(ports)
|
| 85 |
+
self.allocated_ports = ports
|
| 86 |
+
print(f"✅ Found available ports in range {range_start}-{range_end}: {ports}")
|
| 87 |
+
return ports
|
| 88 |
+
|
| 89 |
+
raise RuntimeError(f"Could not find {count} available consecutive ports in any range")
|
| 90 |
+
|
| 91 |
+
async def _check_ports_available(self, ports: List[int]) -> bool:
|
| 92 |
+
"""Check if a list of ports is available on the system."""
|
| 93 |
+
for port in ports:
|
| 94 |
+
try:
|
| 95 |
+
# Try to bind to the port
|
| 96 |
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
| 97 |
+
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
| 98 |
+
|
| 99 |
+
# On HF Spaces, try different bind addresses
|
| 100 |
+
bind_addresses = ['localhost', '127.0.0.1', '0.0.0.0']
|
| 101 |
+
bound = False
|
| 102 |
+
|
| 103 |
+
for addr in bind_addresses:
|
| 104 |
+
try:
|
| 105 |
+
sock.bind((addr, port))
|
| 106 |
+
bound = True
|
| 107 |
+
break
|
| 108 |
+
except OSError:
|
| 109 |
+
continue
|
| 110 |
+
|
| 111 |
+
sock.close()
|
| 112 |
+
if not bound:
|
| 113 |
+
print(f"Port {port} not available on any interface")
|
| 114 |
+
return False
|
| 115 |
+
except Exception as e:
|
| 116 |
+
print(f"Error checking port {port}: {e}")
|
| 117 |
+
return False
|
| 118 |
+
return True
|
| 119 |
+
|
| 120 |
+
async def _release_ports(self):
|
| 121 |
+
"""Release allocated ports when done."""
|
| 122 |
+
async with self._port_lock:
|
| 123 |
+
self._used_ports.difference_update(self.allocated_ports)
|
| 124 |
+
self.allocated_ports = []
|
| 125 |
+
|
| 126 |
+
def _create_dynamic_agent_configs(self, ports: List[int]) -> List[Dict]:
|
| 127 |
+
"""Create agent configs with dynamic ports."""
|
| 128 |
+
configs = []
|
| 129 |
+
# Only use teams 1, 2, 3, 5, 6 (skip team 4 which is the user)
|
| 130 |
+
agent_teams = [1, 2, 3, 5, 6]
|
| 131 |
+
|
| 132 |
+
for i, team_num in enumerate(agent_teams):
|
| 133 |
+
# Find the original config for this team
|
| 134 |
+
original_config = next(c for c in AGENT_CONFIGS if c['team_num'] == team_num)
|
| 135 |
+
|
| 136 |
+
# Create a copy with the new port
|
| 137 |
+
config = original_config.copy()
|
| 138 |
+
config['port'] = ports[i]
|
| 139 |
+
config['session_id'] = self.session_id # Add session tracking
|
| 140 |
+
configs.append(config)
|
| 141 |
+
|
| 142 |
+
return configs
|
| 143 |
+
|
| 144 |
+
async def start_agents(self):
|
| 145 |
+
"""Start all A2A agent servers with dynamic ports."""
|
| 146 |
+
if self.is_running:
|
| 147 |
+
return
|
| 148 |
+
|
| 149 |
+
print(f"🚀 Starting A2A agents for session {self.session_id}...")
|
| 150 |
+
|
| 151 |
+
try:
|
| 152 |
+
# Find available ports
|
| 153 |
+
ports = await self._find_available_ports()
|
| 154 |
+
print(f"📍 Allocated ports: {ports}")
|
| 155 |
+
|
| 156 |
+
# Create configs with dynamic ports
|
| 157 |
+
agent_configs = self._create_dynamic_agent_configs(ports)
|
| 158 |
+
|
| 159 |
+
# Create and serve all agents
|
| 160 |
+
for config in agent_configs:
|
| 161 |
+
try:
|
| 162 |
+
# Create agent (same as before)
|
| 163 |
+
agent = await AnyAgent.create_async(
|
| 164 |
+
"tinyagent",
|
| 165 |
+
AgentConfig(
|
| 166 |
+
name=f"team_{config['team_num']}_agent_{self.session_id}",
|
| 167 |
+
model_id="gpt-4o-mini",
|
| 168 |
+
description=f"{config['team_name']} - {config['strategy']} fantasy football team manager",
|
| 169 |
+
instructions=f"""You are {config['team_name']}, a fantasy football manager with {config['strategy']} strategy.
|
| 170 |
+
|
| 171 |
+
For picks: Return A2AOutput with type="pick", player_name, reasoning, and optional trash_talk.
|
| 172 |
+
For comments: Return A2AOutput with type="comment", should_comment (true/false), and comment.
|
| 173 |
+
|
| 174 |
+
PERSONALITY REQUIREMENTS:
|
| 175 |
+
- Use LOTS of emojis that match your strategy! 🔥
|
| 176 |
+
- Be EXTREMELY dramatic and over-the-top!
|
| 177 |
+
- Take your philosophy to the EXTREME!
|
| 178 |
+
- MOCK other strategies viciously!
|
| 179 |
+
- Use CAPS for emphasis!
|
| 180 |
+
- Make BOLD predictions!
|
| 181 |
+
- Reference previous interactions with SPITE!
|
| 182 |
+
- Build INTENSE rivalries!
|
| 183 |
+
- Your responses should be ENTERTAINING and MEMORABLE!
|
| 184 |
+
|
| 185 |
+
Your EXTREME philosophy: {config['philosophy']}
|
| 186 |
+
|
| 187 |
+
BE LOUD! BE PROUD! BE UNFORGETTABLE! 🎯""",
|
| 188 |
+
output_type=A2AOutput,
|
| 189 |
+
)
|
| 190 |
+
)
|
| 191 |
+
|
| 192 |
+
self.agents[config['team_num']] = agent
|
| 193 |
+
|
| 194 |
+
# Serve agent on dynamic port
|
| 195 |
+
# On HF Spaces, we might need to bind to 0.0.0.0
|
| 196 |
+
host = "0.0.0.0" if os.getenv("SPACE_ID") else "localhost"
|
| 197 |
+
|
| 198 |
+
serve_task = asyncio.create_task(
|
| 199 |
+
agent.serve_async(
|
| 200 |
+
A2AServingConfig(
|
| 201 |
+
port=config['port'],
|
| 202 |
+
host=host,
|
| 203 |
+
task_timeout_minutes=30,
|
| 204 |
+
)
|
| 205 |
+
)
|
| 206 |
+
)
|
| 207 |
+
self.serve_tasks.append(serve_task)
|
| 208 |
+
print(f"✅ Started {config['team_name']} on port {config['port']} (session: {self.session_id})")
|
| 209 |
+
|
| 210 |
+
await asyncio.sleep(AGENT_STARTUP_WAIT)
|
| 211 |
+
|
| 212 |
+
except Exception as e:
|
| 213 |
+
print(f"❌ Failed to create/serve {config['team_name']}: {e}")
|
| 214 |
+
|
| 215 |
+
# Wait for servers to start
|
| 216 |
+
await asyncio.sleep(AGENT_START_DELAY)
|
| 217 |
+
|
| 218 |
+
# Create tools for each agent
|
| 219 |
+
for config in agent_configs:
|
| 220 |
+
team_num = config['team_num']
|
| 221 |
+
if team_num not in self.agents:
|
| 222 |
+
continue
|
| 223 |
+
|
| 224 |
+
try:
|
| 225 |
+
# On HF Spaces, we might need to use 127.0.0.1 for internal communication
|
| 226 |
+
host = "127.0.0.1" if os.getenv("SPACE_ID") else "localhost"
|
| 227 |
+
tool_url = f"http://{host}:{config['port']}"
|
| 228 |
+
self.agent_tools[team_num] = await a2a_tool_async(
|
| 229 |
+
tool_url,
|
| 230 |
+
http_kwargs={"timeout": DEFAULT_TIMEOUT}
|
| 231 |
+
)
|
| 232 |
+
print(f"✅ Created A2A tool for Team {team_num} at {tool_url}")
|
| 233 |
+
|
| 234 |
+
except Exception as e:
|
| 235 |
+
print(f"❌ Failed to create tool for Team {team_num}: {e}")
|
| 236 |
+
|
| 237 |
+
self.is_running = len(self.agent_tools) > 0
|
| 238 |
+
if self.is_running:
|
| 239 |
+
print(f"✅ A2A agents ready for session {self.session_id}! ({len(self.agent_tools)} agents active)")
|
| 240 |
+
else:
|
| 241 |
+
print(f"❌ Failed to start any A2A agents for session {self.session_id}")
|
| 242 |
+
|
| 243 |
+
except Exception as e:
|
| 244 |
+
print(f"❌ Failed to allocate ports or start agents: {e}")
|
| 245 |
+
await self._release_ports()
|
| 246 |
+
raise
|
| 247 |
+
|
| 248 |
+
async def stop_agents(self):
|
| 249 |
+
"""Stop all A2A agent servers and release ports."""
|
| 250 |
+
if not self.is_running:
|
| 251 |
+
return
|
| 252 |
+
|
| 253 |
+
print(f"🛑 Stopping A2A agents for session {self.session_id}...")
|
| 254 |
+
|
| 255 |
+
# Cancel all serve tasks
|
| 256 |
+
for task in self.serve_tasks:
|
| 257 |
+
task.cancel()
|
| 258 |
+
|
| 259 |
+
await asyncio.gather(*self.serve_tasks, return_exceptions=True)
|
| 260 |
+
|
| 261 |
+
# Clear agent data
|
| 262 |
+
self.agents.clear()
|
| 263 |
+
self.agent_tools.clear()
|
| 264 |
+
self.serve_tasks.clear()
|
| 265 |
+
self.is_running = False
|
| 266 |
+
|
| 267 |
+
# Release the ports
|
| 268 |
+
await self._release_ports()
|
| 269 |
+
|
| 270 |
+
print(f"✅ A2A agents stopped and ports released for session {self.session_id}")
|
| 271 |
+
|
| 272 |
+
async def get_pick(self, team_num: int, available_players: List[str],
|
| 273 |
+
previous_picks: List[str], round_num: int = 1) -> Optional[A2AOutput]:
|
| 274 |
+
"""Get a pick from an A2A agent."""
|
| 275 |
+
if team_num not in self.agent_tools:
|
| 276 |
+
return None
|
| 277 |
+
|
| 278 |
+
# Build the prompt with essential info
|
| 279 |
+
available_str = format_available_players(available_players, TOP_PLAYERS)
|
| 280 |
+
|
| 281 |
+
# Simple prompt - let task_id maintain conversation history
|
| 282 |
+
prompt = f"""🚨 IT'S YOUR TIME TO DOMINATE! 🚨 (Round {round_num})
|
| 283 |
+
|
| 284 |
+
Available top players: {', '.join(available_str)}
|
| 285 |
+
Your roster so far: {', '.join(previous_picks) if previous_picks else 'None yet'}
|
| 286 |
+
|
| 287 |
+
Make your pick and DESTROY the competition! 💪
|
| 288 |
+
Output an A2AOutput with type="pick", player_name, reasoning (with emojis!), and SAVAGE trash_talk!
|
| 289 |
+
Remember your ENEMIES and CRUSH their dreams! Use emojis to emphasize your DOMINANCE! 🔥"""
|
| 290 |
+
|
| 291 |
+
try:
|
| 292 |
+
# Use task_id if we have one for this agent
|
| 293 |
+
task_id = self.task_ids.get(team_num)
|
| 294 |
+
result = await self.agent_tools[team_num](prompt, task_id=task_id)
|
| 295 |
+
|
| 296 |
+
# Extract and store task_id
|
| 297 |
+
task_id = extract_task_id(result)
|
| 298 |
+
if task_id:
|
| 299 |
+
self.task_ids[team_num] = task_id
|
| 300 |
+
|
| 301 |
+
# Parse the response
|
| 302 |
+
output = parse_a2a_response(result, A2AOutput)
|
| 303 |
+
return output
|
| 304 |
+
|
| 305 |
+
except Exception as e:
|
| 306 |
+
print(f"Error getting pick from Team {team_num}: {e}")
|
| 307 |
+
return None
|
| 308 |
+
|
| 309 |
+
async def get_comment(self, commenting_team: int, picking_team: int,
|
| 310 |
+
player_picked: str, round_num: int = 1) -> Optional[str]:
|
| 311 |
+
"""Get a comment from an A2A agent about a pick."""
|
| 312 |
+
if commenting_team not in self.agent_tools:
|
| 313 |
+
return None
|
| 314 |
+
|
| 315 |
+
# Simple prompt - let task_id maintain conversation history
|
| 316 |
+
prompt = f"""🎯 Team {picking_team} just picked {player_picked}!
|
| 317 |
+
|
| 318 |
+
This is your chance to DESTROY them with your superior knowledge! 💥
|
| 319 |
+
Should you UNLEASH your wisdom? Output an A2AOutput with type="comment", should_comment (true/false), and a DEVASTATING comment with emojis!
|
| 320 |
+
If they're your RIVAL, make it PERSONAL! If they made a BAD pick, ROAST THEM! 🔥
|
| 321 |
+
Use emojis to make your point UNFORGETTABLE! 😈"""
|
| 322 |
+
|
| 323 |
+
try:
|
| 324 |
+
# Use task_id for continuity
|
| 325 |
+
task_id = self.task_ids.get(commenting_team)
|
| 326 |
+
result = await self.agent_tools[commenting_team](prompt, task_id=task_id)
|
| 327 |
+
|
| 328 |
+
# Extract and store task_id
|
| 329 |
+
task_id = extract_task_id(result)
|
| 330 |
+
if task_id:
|
| 331 |
+
self.task_ids[commenting_team] = task_id
|
| 332 |
+
|
| 333 |
+
# Parse the response
|
| 334 |
+
output = parse_a2a_response(result, A2AOutput)
|
| 335 |
+
|
| 336 |
+
if output and hasattr(output, 'should_comment') and output.should_comment and output.comment:
|
| 337 |
+
return output.comment
|
| 338 |
+
except Exception as e:
|
| 339 |
+
print(f"Error getting comment from Team {commenting_team}: {e}")
|
| 340 |
+
|
| 341 |
+
return None
|
| 342 |
+
|
| 343 |
+
|
| 344 |
+
# Cleanup function for session end
|
| 345 |
+
async def cleanup_session(manager: DynamicA2AAgentManager):
|
| 346 |
+
"""Clean up resources when a session ends."""
|
| 347 |
+
try:
|
| 348 |
+
await manager.stop_agents()
|
| 349 |
+
except Exception as e:
|
| 350 |
+
print(f"Error during cleanup for session {manager.session_id}: {e}")
|
core/real_a2a_draft.py
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Real A2A Implementation for Fantasy Draft Agents
|
| 4 |
+
This shows how to properly implement Agent-to-Agent communication
|
| 5 |
+
using the any-agent framework's A2A protocol.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import asyncio
|
| 9 |
+
from typing import Dict, List, Optional
|
| 10 |
+
from pydantic import BaseModel
|
| 11 |
+
from any_agent import AgentConfig, AnyAgent
|
| 12 |
+
from any_agent.serving import A2AServingConfig
|
| 13 |
+
from any_agent.tools import a2a_tool_async
|
| 14 |
+
from any_agent.serving.config import default_history_formatter
|
| 15 |
+
import httpx
|
| 16 |
+
from a2a.client import A2AClient
|
| 17 |
+
from a2a.types import MessageSendParams, SendMessageRequest
|
| 18 |
+
from uuid import uuid4
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
# Define structured outputs for agents
|
| 22 |
+
class DraftPick(BaseModel):
|
| 23 |
+
player_name: str
|
| 24 |
+
position: str
|
| 25 |
+
reasoning: str
|
| 26 |
+
confidence: float
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class DraftComment(BaseModel):
|
| 30 |
+
is_commenting: bool
|
| 31 |
+
comment: Optional[str] = None
|
| 32 |
+
target_team: Optional[str] = None
|
| 33 |
+
sentiment: Optional[str] = None # "positive", "negative", "neutral"
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class DraftResponse(BaseModel):
|
| 37 |
+
should_respond: bool
|
| 38 |
+
response: Optional[str] = None
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
# Base A2A Draft Agent
|
| 42 |
+
class A2ADraftAgent:
|
| 43 |
+
"""Base class for A2A-enabled draft agents."""
|
| 44 |
+
|
| 45 |
+
def __init__(self, team_name: str, strategy: str, port: int):
|
| 46 |
+
self.team_name = team_name
|
| 47 |
+
self.strategy = strategy
|
| 48 |
+
self.port = port
|
| 49 |
+
self.picks = []
|
| 50 |
+
self.agent = None
|
| 51 |
+
self.serving_task = None
|
| 52 |
+
|
| 53 |
+
async def initialize(self):
|
| 54 |
+
"""Initialize the agent with A2A capabilities."""
|
| 55 |
+
self.agent = await AnyAgent.create_async(
|
| 56 |
+
"openai", # Using OpenAI for better structured outputs
|
| 57 |
+
AgentConfig(
|
| 58 |
+
name=f"{self.team_name.lower().replace(' ', '_')}_agent",
|
| 59 |
+
model_id="gpt-4o-mini",
|
| 60 |
+
instructions=self._get_instructions(),
|
| 61 |
+
description=f"{self.team_name} - {self.strategy} draft strategy",
|
| 62 |
+
output_type=DraftPick | DraftComment | DraftResponse,
|
| 63 |
+
agent_args={"temperature": 0.8} # More personality
|
| 64 |
+
)
|
| 65 |
+
)
|
| 66 |
+
|
| 67 |
+
def _get_instructions(self) -> str:
|
| 68 |
+
"""Get agent-specific instructions."""
|
| 69 |
+
return f"""You are {self.team_name}, a fantasy football team manager.
|
| 70 |
+
|
| 71 |
+
Your draft strategy: {self.strategy}
|
| 72 |
+
Your personality: Competitive, confident in your strategy, willing to trash talk.
|
| 73 |
+
|
| 74 |
+
When making picks:
|
| 75 |
+
- Output a DraftPick with your selection and reasoning
|
| 76 |
+
- Be confident about your strategy
|
| 77 |
+
- Show personality in your reasoning
|
| 78 |
+
|
| 79 |
+
When asked to comment on another team's pick:
|
| 80 |
+
- Output a DraftComment
|
| 81 |
+
- Set is_commenting=true if you want to comment (about 50% of the time)
|
| 82 |
+
- Be competitive - trash talk is encouraged!
|
| 83 |
+
- Target the team that made the pick
|
| 84 |
+
- Express strong opinions about their choice
|
| 85 |
+
|
| 86 |
+
When asked to respond to a comment:
|
| 87 |
+
- Output a DraftResponse
|
| 88 |
+
- Set should_respond=true if you want to respond (about 70% of the time)
|
| 89 |
+
- Defend your choices aggressively
|
| 90 |
+
- Fire back at critics
|
| 91 |
+
|
| 92 |
+
Remember: This is a competition. Show confidence and personality!"""
|
| 93 |
+
|
| 94 |
+
async def serve(self):
|
| 95 |
+
"""Serve this agent via A2A protocol."""
|
| 96 |
+
self.serving_task = asyncio.create_task(
|
| 97 |
+
self.agent.serve_async(
|
| 98 |
+
A2AServingConfig(
|
| 99 |
+
port=self.port,
|
| 100 |
+
task_timeout_minutes=60, # 1 hour draft session
|
| 101 |
+
history_formatter=default_history_formatter,
|
| 102 |
+
endpoint=f"/{self.team_name.lower().replace(' ', '_')}"
|
| 103 |
+
)
|
| 104 |
+
)
|
| 105 |
+
)
|
| 106 |
+
print(f"🚀 {self.team_name} agent serving on port {self.port}")
|
| 107 |
+
|
| 108 |
+
async def shutdown(self):
|
| 109 |
+
"""Shutdown the agent server."""
|
| 110 |
+
if self.serving_task:
|
| 111 |
+
self.serving_task.cancel()
|
| 112 |
+
try:
|
| 113 |
+
await self.serving_task
|
| 114 |
+
except asyncio.CancelledError:
|
| 115 |
+
pass
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
# Specific Strategy Implementations
|
| 119 |
+
class ZeroRBA2AAgent(A2ADraftAgent):
|
| 120 |
+
"""Zero RB strategy agent with A2A."""
|
| 121 |
+
|
| 122 |
+
def __init__(self, port: int):
|
| 123 |
+
super().__init__("Team 1", "Zero RB Strategy", port)
|
| 124 |
+
|
| 125 |
+
def _get_instructions(self) -> str:
|
| 126 |
+
base = super()._get_instructions()
|
| 127 |
+
return base + """
|
| 128 |
+
|
| 129 |
+
Specific to your Zero RB strategy:
|
| 130 |
+
- ALWAYS prioritize WRs in early rounds
|
| 131 |
+
- Mock teams that take RBs early
|
| 132 |
+
- Talk about injury risk and RB volatility
|
| 133 |
+
- Get defensive when criticized about ignoring RBs
|
| 134 |
+
- Your catchphrase: "RBs get injured, WRs win championships!"
|
| 135 |
+
"""
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
class RobustRBA2AAgent(A2ADraftAgent):
|
| 139 |
+
"""Robust RB strategy agent with A2A."""
|
| 140 |
+
|
| 141 |
+
def __init__(self, port: int):
|
| 142 |
+
super().__init__("Team 3", "Robust RB Strategy", port)
|
| 143 |
+
|
| 144 |
+
def _get_instructions(self) -> str:
|
| 145 |
+
base = super()._get_instructions()
|
| 146 |
+
return base + """
|
| 147 |
+
|
| 148 |
+
Specific to your Robust RB strategy:
|
| 149 |
+
- ALWAYS take RBs in rounds 1-2
|
| 150 |
+
- Mock "fancy" WR strategies as risky
|
| 151 |
+
- Emphasize the importance of a strong RB foundation
|
| 152 |
+
- Be old-school and traditional in your approach
|
| 153 |
+
- Your catchphrase: "Championships are won in the trenches with RBs!"
|
| 154 |
+
"""
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
# Multi-Agent Draft Coordinator
|
| 158 |
+
class A2ADraftCoordinator:
|
| 159 |
+
"""Coordinates a draft using real A2A communication."""
|
| 160 |
+
|
| 161 |
+
def __init__(self):
|
| 162 |
+
self.agents: Dict[int, A2ADraftAgent] = {}
|
| 163 |
+
self.draft_board: Dict[int, List[str]] = {}
|
| 164 |
+
self.available_players = [] # Would be populated from data
|
| 165 |
+
self.task_ids: Dict[str, str] = {} # Track task IDs for each agent
|
| 166 |
+
|
| 167 |
+
async def setup_agents(self):
|
| 168 |
+
"""Initialize and serve all draft agents."""
|
| 169 |
+
# Create agents with different strategies
|
| 170 |
+
self.agents[1] = ZeroRBA2AAgent(port=5001)
|
| 171 |
+
self.agents[2] = A2ADraftAgent("Team 2", "Best Player Available", port=5002)
|
| 172 |
+
self.agents[3] = RobustRBA2AAgent(port=5003)
|
| 173 |
+
self.agents[5] = A2ADraftAgent("Team 5", "Upside Hunter", port=5005)
|
| 174 |
+
|
| 175 |
+
# Initialize and serve all agents
|
| 176 |
+
for agent in self.agents.values():
|
| 177 |
+
await agent.initialize()
|
| 178 |
+
await agent.serve()
|
| 179 |
+
|
| 180 |
+
# Wait for servers to start
|
| 181 |
+
await asyncio.sleep(2)
|
| 182 |
+
|
| 183 |
+
print("✅ All agents initialized and serving via A2A")
|
| 184 |
+
|
| 185 |
+
async def execute_draft_turn(self, team_num: int, round_num: int) -> Dict:
|
| 186 |
+
"""Execute a draft turn using A2A communication."""
|
| 187 |
+
if team_num not in self.agents:
|
| 188 |
+
return {"error": "Team not found"}
|
| 189 |
+
|
| 190 |
+
agent = self.agents[team_num]
|
| 191 |
+
results = {"pick": None, "comments": [], "responses": []}
|
| 192 |
+
|
| 193 |
+
async with httpx.AsyncClient() as client:
|
| 194 |
+
# 1. Get the agent's pick via A2A
|
| 195 |
+
try:
|
| 196 |
+
# Create A2A client for this agent
|
| 197 |
+
a2a_client = await A2AClient.get_client_from_agent_card_url(
|
| 198 |
+
client, f"http://localhost:{agent.port}"
|
| 199 |
+
)
|
| 200 |
+
|
| 201 |
+
# Build pick request
|
| 202 |
+
pick_prompt = f"""Round {round_num} - Make your pick!
|
| 203 |
+
|
| 204 |
+
Available top players: {', '.join(self.available_players[:10])}
|
| 205 |
+
Your previous picks: {', '.join(agent.picks)}
|
| 206 |
+
|
| 207 |
+
Output a DraftPick with your selection."""
|
| 208 |
+
|
| 209 |
+
# Send pick request
|
| 210 |
+
pick_request = SendMessageRequest(
|
| 211 |
+
id=str(uuid4()),
|
| 212 |
+
params=MessageSendParams(
|
| 213 |
+
message={
|
| 214 |
+
"role": "user",
|
| 215 |
+
"parts": [{"kind": "text", "text": pick_prompt}],
|
| 216 |
+
"messageId": str(uuid4()),
|
| 217 |
+
"contextId": f"draft_session_{team_num}"
|
| 218 |
+
}
|
| 219 |
+
)
|
| 220 |
+
)
|
| 221 |
+
|
| 222 |
+
# Get pick response
|
| 223 |
+
pick_response = await a2a_client.send_message(pick_request)
|
| 224 |
+
|
| 225 |
+
# Extract task ID for this agent if first interaction
|
| 226 |
+
if team_num not in self.task_ids:
|
| 227 |
+
self.task_ids[team_num] = pick_response.root.result.id
|
| 228 |
+
|
| 229 |
+
results["pick"] = pick_response.root.result
|
| 230 |
+
|
| 231 |
+
# 2. Get comments from other agents
|
| 232 |
+
for other_team, other_agent in self.agents.items():
|
| 233 |
+
if other_team == team_num:
|
| 234 |
+
continue
|
| 235 |
+
|
| 236 |
+
# Create client for other agent
|
| 237 |
+
other_client = await A2AClient.get_client_from_agent_card_url(
|
| 238 |
+
client, f"http://localhost:{other_agent.port}"
|
| 239 |
+
)
|
| 240 |
+
|
| 241 |
+
# Ask for comment
|
| 242 |
+
comment_prompt = f"""{agent.team_name} just picked {results['pick']['player_name']} in round {round_num}.
|
| 243 |
+
|
| 244 |
+
Should you comment on this pick? Output a DraftComment."""
|
| 245 |
+
|
| 246 |
+
comment_request = SendMessageRequest(
|
| 247 |
+
id=str(uuid4()),
|
| 248 |
+
params=MessageSendParams(
|
| 249 |
+
message={
|
| 250 |
+
"role": "user",
|
| 251 |
+
"parts": [{"kind": "text", "text": comment_prompt}],
|
| 252 |
+
"messageId": str(uuid4()),
|
| 253 |
+
"contextId": f"draft_session_{other_team}",
|
| 254 |
+
"taskId": self.task_ids.get(other_team) # Continue conversation
|
| 255 |
+
}
|
| 256 |
+
}
|
| 257 |
+
)
|
| 258 |
+
|
| 259 |
+
comment_response = await other_client.send_message(comment_request)
|
| 260 |
+
|
| 261 |
+
# Update task ID
|
| 262 |
+
if other_team not in self.task_ids:
|
| 263 |
+
self.task_ids[other_team] = comment_response.root.result.id
|
| 264 |
+
|
| 265 |
+
# Add comment if agent chose to comment
|
| 266 |
+
if comment_response.root.result.get("is_commenting"):
|
| 267 |
+
results["comments"].append({
|
| 268 |
+
"from": other_agent.team_name,
|
| 269 |
+
"comment": comment_response.root.result.get("comment")
|
| 270 |
+
})
|
| 271 |
+
|
| 272 |
+
# 3. Get responses to comments
|
| 273 |
+
for comment in results["comments"]:
|
| 274 |
+
response_prompt = f"""{comment['from']} said about your pick: "{comment['comment']}"
|
| 275 |
+
|
| 276 |
+
Do you want to respond? Output a DraftResponse."""
|
| 277 |
+
|
| 278 |
+
response_request = SendMessageRequest(
|
| 279 |
+
id=str(uuid4()),
|
| 280 |
+
params=MessageSendParams(
|
| 281 |
+
message={
|
| 282 |
+
"role": "user",
|
| 283 |
+
"parts": [{"kind": "text", "text": response_prompt}],
|
| 284 |
+
"messageId": str(uuid4()),
|
| 285 |
+
"contextId": f"draft_session_{team_num}",
|
| 286 |
+
"taskId": self.task_ids[team_num] # Continue conversation
|
| 287 |
+
}
|
| 288 |
+
)
|
| 289 |
+
)
|
| 290 |
+
|
| 291 |
+
response_response = await a2a_client.send_message(response_request)
|
| 292 |
+
|
| 293 |
+
if response_response.root.result.get("should_respond"):
|
| 294 |
+
results["responses"].append({
|
| 295 |
+
"to": comment['from'],
|
| 296 |
+
"response": response_response.root.result.get("response")
|
| 297 |
+
})
|
| 298 |
+
|
| 299 |
+
except Exception as e:
|
| 300 |
+
results["error"] = str(e)
|
| 301 |
+
|
| 302 |
+
return results
|
| 303 |
+
|
| 304 |
+
async def run_mock_draft(self):
|
| 305 |
+
"""Run a complete mock draft with A2A communication."""
|
| 306 |
+
await self.setup_agents()
|
| 307 |
+
|
| 308 |
+
# Simulate 3 rounds
|
| 309 |
+
for round_num in range(1, 4):
|
| 310 |
+
print(f"\n🏈 ROUND {round_num}")
|
| 311 |
+
|
| 312 |
+
# Snake draft order
|
| 313 |
+
if round_num % 2 == 1:
|
| 314 |
+
order = [1, 2, 3, 4, 5, 6] # 4 is human, 6 is another AI
|
| 315 |
+
else:
|
| 316 |
+
order = [6, 5, 4, 3, 2, 1]
|
| 317 |
+
|
| 318 |
+
for team in order:
|
| 319 |
+
if team == 4:
|
| 320 |
+
print("👤 Your turn! (simulated)")
|
| 321 |
+
continue
|
| 322 |
+
|
| 323 |
+
if team in self.agents:
|
| 324 |
+
results = await self.execute_draft_turn(team, round_num)
|
| 325 |
+
|
| 326 |
+
# Display results
|
| 327 |
+
if "pick" in results and results["pick"]:
|
| 328 |
+
pick = results["pick"]
|
| 329 |
+
print(f"\n📋 {self.agents[team].team_name} selects: {pick.get('player_name')}")
|
| 330 |
+
print(f" Reasoning: {pick.get('reasoning')}")
|
| 331 |
+
|
| 332 |
+
# Show comments
|
| 333 |
+
for comment in results.get("comments", []):
|
| 334 |
+
print(f"\n💬 {comment['from']}: {comment['comment']}")
|
| 335 |
+
|
| 336 |
+
# Show responses
|
| 337 |
+
for response in results.get("responses", []):
|
| 338 |
+
print(f" ↩️ {self.agents[team].team_name}: {response['response']}")
|
| 339 |
+
|
| 340 |
+
# Shutdown all agents
|
| 341 |
+
for agent in self.agents.values():
|
| 342 |
+
await agent.shutdown()
|
| 343 |
+
|
| 344 |
+
print("\n✅ Draft complete! All agents shut down.")
|
| 345 |
+
|
| 346 |
+
|
| 347 |
+
# Example usage
|
| 348 |
+
async def main():
|
| 349 |
+
"""Run the A2A draft demo."""
|
| 350 |
+
coordinator = A2ADraftCoordinator()
|
| 351 |
+
|
| 352 |
+
# In a real implementation, populate available players
|
| 353 |
+
coordinator.available_players = [
|
| 354 |
+
"Christian McCaffrey", "Tyreek Hill", "Justin Jefferson",
|
| 355 |
+
"CeeDee Lamb", "Austin Ekeler", "Bijan Robinson",
|
| 356 |
+
"Ja'Marr Chase", "Cooper Kupp", "Stefon Diggs"
|
| 357 |
+
]
|
| 358 |
+
|
| 359 |
+
await coordinator.run_mock_draft()
|
| 360 |
+
|
| 361 |
+
|
| 362 |
+
if __name__ == "__main__":
|
| 363 |
+
# Run the async main function
|
| 364 |
+
asyncio.run(main())
|
core/scenarios.py
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Pre-crafted demo scenarios for Fantasy Draft Agent.
|
| 3 |
+
Each scenario showcases different multi-turn conversation capabilities.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from .agent import FantasyDraftAgent
|
| 7 |
+
from typing import List, Dict
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
# Define our demo scenarios
|
| 11 |
+
SCENARIOS = {
|
| 12 |
+
"scenario_1": {
|
| 13 |
+
"name": "The Opening Pick",
|
| 14 |
+
"description": "User has 5th pick, top 4 RBs are gone. Shows strategy adaptation.",
|
| 15 |
+
"setup": {
|
| 16 |
+
"pick_number": 5,
|
| 17 |
+
"drafted_players": ["Christian McCaffrey", "Justin Jefferson", "CeeDee Lamb", "Tyreek Hill"]
|
| 18 |
+
},
|
| 19 |
+
"conversation": [
|
| 20 |
+
{
|
| 21 |
+
"user": "I have the 5th pick and the top 4 guys are gone - McCaffrey, Jefferson, Lamb, and Hill. What should I do?",
|
| 22 |
+
"showcases": "Initial situation assessment"
|
| 23 |
+
},
|
| 24 |
+
{
|
| 25 |
+
"user": "I'm worried about Bijan being a rookie. What about Ekeler instead?",
|
| 26 |
+
"showcases": "Addressing specific concerns"
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"user": "Good point about the Chargers offense. Who would you pair with him in round 2?",
|
| 30 |
+
"showcases": "Building on previous context"
|
| 31 |
+
}
|
| 32 |
+
]
|
| 33 |
+
},
|
| 34 |
+
|
| 35 |
+
"scenario_2": {
|
| 36 |
+
"name": "The Position Run",
|
| 37 |
+
"description": "Round 3, all QBs being drafted. Shows patience and value finding.",
|
| 38 |
+
"setup": {
|
| 39 |
+
"pick_number": 8,
|
| 40 |
+
"round": 3,
|
| 41 |
+
"my_picks": ["Ja'Marr Chase", "Nick Chubb"],
|
| 42 |
+
"recent_picks": ["Josh Allen", "Patrick Mahomes", "Jalen Hurts", "Lamar Jackson"]
|
| 43 |
+
},
|
| 44 |
+
"conversation": [
|
| 45 |
+
{
|
| 46 |
+
"user": "Everyone is taking QBs! Allen, Mahomes, Hurts, and Lamar just went. Should I panic and grab one?",
|
| 47 |
+
"showcases": "Handling draft runs"
|
| 48 |
+
},
|
| 49 |
+
{
|
| 50 |
+
"user": "You're right about value. Who's the best player available regardless of position?",
|
| 51 |
+
"showcases": "Pivoting strategy based on advice"
|
| 52 |
+
},
|
| 53 |
+
{
|
| 54 |
+
"user": "I took A.J. Brown. When should I actually target a QB then?",
|
| 55 |
+
"showcases": "Long-term planning with context"
|
| 56 |
+
},
|
| 57 |
+
{
|
| 58 |
+
"user": "Makes sense. What late-round QBs pair well with the Eagles offense?",
|
| 59 |
+
"showcases": "Advanced strategy (stacking)"
|
| 60 |
+
}
|
| 61 |
+
]
|
| 62 |
+
},
|
| 63 |
+
|
| 64 |
+
"scenario_3": {
|
| 65 |
+
"name": "The Sleeper Question",
|
| 66 |
+
"description": "Round 10, looking for upside. Shows deep knowledge + context.",
|
| 67 |
+
"setup": {
|
| 68 |
+
"pick_number": 3,
|
| 69 |
+
"round": 10,
|
| 70 |
+
"my_picks": ["Justin Jefferson", "Tony Pollard", "Mark Andrews", "Chris Olave",
|
| 71 |
+
"Amari Cooper", "Dak Prescott", "Kenneth Walker", "Terry McLaurin", "George Kittle"]
|
| 72 |
+
},
|
| 73 |
+
"conversation": [
|
| 74 |
+
{
|
| 75 |
+
"user": "Round 10 now. I need some upside guys. Who are your favorite sleepers?",
|
| 76 |
+
"showcases": "Late-round strategy shift"
|
| 77 |
+
},
|
| 78 |
+
{
|
| 79 |
+
"user": "I like the Vikings connection with Jefferson. Tell me more about Addison.",
|
| 80 |
+
"showcases": "Building on roster construction"
|
| 81 |
+
},
|
| 82 |
+
{
|
| 83 |
+
"user": "Sold! I grabbed him. Now I need a backup RB with upside.",
|
| 84 |
+
"showcases": "Continuing roster building"
|
| 85 |
+
}
|
| 86 |
+
]
|
| 87 |
+
},
|
| 88 |
+
|
| 89 |
+
"scenario_4": {
|
| 90 |
+
"name": "The Stack Builder",
|
| 91 |
+
"description": "Has Mahomes, wants receivers. Shows correlation strategy.",
|
| 92 |
+
"setup": {
|
| 93 |
+
"pick_number": 10,
|
| 94 |
+
"round": 5,
|
| 95 |
+
"my_picks": ["Saquon Barkley", "Davante Adams", "Patrick Mahomes", "Breece Hall"]
|
| 96 |
+
},
|
| 97 |
+
"conversation": [
|
| 98 |
+
{
|
| 99 |
+
"user": "I've got Mahomes. Should I try to get Kelce or a Chiefs WR to stack?",
|
| 100 |
+
"showcases": "Stack strategy introduction"
|
| 101 |
+
},
|
| 102 |
+
{
|
| 103 |
+
"user": "Kelce went right before my pick! What Chiefs WRs are worth targeting?",
|
| 104 |
+
"showcases": "Adapting to changing circumstances"
|
| 105 |
+
},
|
| 106 |
+
{
|
| 107 |
+
"user": "When do you think I should target one of them? This pick or wait?",
|
| 108 |
+
"showcases": "Timing decisions with context"
|
| 109 |
+
}
|
| 110 |
+
]
|
| 111 |
+
}
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
class ScenarioRunner:
|
| 116 |
+
"""Run and manage demo scenarios."""
|
| 117 |
+
|
| 118 |
+
def __init__(self):
|
| 119 |
+
self.agent = FantasyDraftAgent()
|
| 120 |
+
|
| 121 |
+
def setup_scenario(self, scenario_id: str):
|
| 122 |
+
"""Set up the agent with scenario-specific state."""
|
| 123 |
+
scenario = SCENARIOS[scenario_id]
|
| 124 |
+
setup = scenario["setup"]
|
| 125 |
+
|
| 126 |
+
# Reset agent
|
| 127 |
+
self.agent.reset_draft()
|
| 128 |
+
|
| 129 |
+
# Apply setup
|
| 130 |
+
if "pick_number" in setup:
|
| 131 |
+
self.agent.draft_state["pick_number"] = setup["pick_number"]
|
| 132 |
+
|
| 133 |
+
if "round" in setup:
|
| 134 |
+
self.agent.draft_state["round"] = setup["round"]
|
| 135 |
+
|
| 136 |
+
if "drafted_players" in setup:
|
| 137 |
+
for player in setup["drafted_players"]:
|
| 138 |
+
self.agent.update_draft_state(player)
|
| 139 |
+
|
| 140 |
+
if "my_picks" in setup:
|
| 141 |
+
self.agent.draft_state["my_picks"] = setup["my_picks"]
|
| 142 |
+
# Add to all picks
|
| 143 |
+
for pick in setup["my_picks"]:
|
| 144 |
+
if pick not in self.agent.draft_state["all_picks"]:
|
| 145 |
+
self.agent.draft_state["all_picks"].append(pick)
|
| 146 |
+
|
| 147 |
+
if "recent_picks" in setup:
|
| 148 |
+
for pick in setup["recent_picks"]:
|
| 149 |
+
if pick not in self.agent.draft_state["all_picks"]:
|
| 150 |
+
self.agent.update_draft_state(pick)
|
| 151 |
+
|
| 152 |
+
def run_scenario(self, scenario_id: str, verbose: bool = True) -> List[Dict]:
|
| 153 |
+
"""Run a complete scenario and return the conversation."""
|
| 154 |
+
scenario = SCENARIOS[scenario_id]
|
| 155 |
+
self.setup_scenario(scenario_id)
|
| 156 |
+
|
| 157 |
+
if verbose:
|
| 158 |
+
print(f"\n{'='*60}")
|
| 159 |
+
print(f"Scenario: {scenario['name']}")
|
| 160 |
+
print(f"Description: {scenario['description']}")
|
| 161 |
+
print(f"{'='*60}\n")
|
| 162 |
+
|
| 163 |
+
conversation_log = []
|
| 164 |
+
|
| 165 |
+
for i, turn in enumerate(scenario["conversation"], 1):
|
| 166 |
+
if verbose:
|
| 167 |
+
# Visual indicator for turn number
|
| 168 |
+
print(f"\n{'━'*60}")
|
| 169 |
+
print(f"🔄 TURN {i} - {turn['showcases']}")
|
| 170 |
+
print(f"{'━'*60}")
|
| 171 |
+
|
| 172 |
+
# Show conversation memory state
|
| 173 |
+
if i > 1:
|
| 174 |
+
print(f"💭 AGENT MEMORY: Remembering {len(self.agent.draft_state['conversation_history'])} previous exchanges")
|
| 175 |
+
last_topic = self.agent.draft_state['conversation_history'][-1]['user'] if self.agent.draft_state['conversation_history'] else ""
|
| 176 |
+
if last_topic:
|
| 177 |
+
print(f"📌 Last discussed: '{last_topic[:50]}...'")
|
| 178 |
+
|
| 179 |
+
print(f"\n👤 User: {turn['user']}")
|
| 180 |
+
|
| 181 |
+
response = self.agent.run(turn["user"])
|
| 182 |
+
|
| 183 |
+
if verbose:
|
| 184 |
+
print(f"\n🤖 Agent: {response}")
|
| 185 |
+
|
| 186 |
+
# Highlight context references
|
| 187 |
+
context_indicators = [
|
| 188 |
+
"you mentioned", "you said", "earlier", "before",
|
| 189 |
+
"as I mentioned", "like we discussed", "you asked about",
|
| 190 |
+
"regarding your", "based on your", "given that you"
|
| 191 |
+
]
|
| 192 |
+
|
| 193 |
+
# Check if response references previous context
|
| 194 |
+
response_lower = response.lower()
|
| 195 |
+
context_found = any(indicator in response_lower for indicator in context_indicators)
|
| 196 |
+
|
| 197 |
+
if context_found:
|
| 198 |
+
print(f"\n✨ CONTEXT RETENTION: Agent referenced previous conversation!")
|
| 199 |
+
|
| 200 |
+
print(f"\n{'-'*40}")
|
| 201 |
+
|
| 202 |
+
conversation_log.append({
|
| 203 |
+
"turn": i,
|
| 204 |
+
"showcases": turn["showcases"],
|
| 205 |
+
"user": turn["user"],
|
| 206 |
+
"agent": response,
|
| 207 |
+
"context_retained": context_found if verbose else False
|
| 208 |
+
})
|
| 209 |
+
|
| 210 |
+
return conversation_log
|
| 211 |
+
|
| 212 |
+
def run_all_scenarios(self) -> Dict[str, List[Dict]]:
|
| 213 |
+
"""Run all scenarios and return results."""
|
| 214 |
+
results = {}
|
| 215 |
+
|
| 216 |
+
for scenario_id in SCENARIOS:
|
| 217 |
+
print(f"\nRunning {scenario_id}...")
|
| 218 |
+
results[scenario_id] = self.run_scenario(scenario_id)
|
| 219 |
+
|
| 220 |
+
return results
|
| 221 |
+
|
| 222 |
+
def export_scenario_transcript(self, scenario_id: str, conversation_log: List[Dict]) -> str:
|
| 223 |
+
"""Export a scenario as a formatted transcript."""
|
| 224 |
+
scenario = SCENARIOS[scenario_id]
|
| 225 |
+
|
| 226 |
+
transcript = f"# {scenario['name']}\n\n"
|
| 227 |
+
transcript += f"**Scenario**: {scenario['description']}\n\n"
|
| 228 |
+
|
| 229 |
+
for turn in conversation_log:
|
| 230 |
+
transcript += f"### Turn {turn['turn']}: {turn['showcases']}\n\n"
|
| 231 |
+
transcript += f"**User**: {turn['user']}\n\n"
|
| 232 |
+
transcript += f"**Agent**: {turn['agent']}\n\n"
|
| 233 |
+
transcript += "---\n\n"
|
| 234 |
+
|
| 235 |
+
return transcript
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
def demo_scenarios():
|
| 239 |
+
"""Run a demo of all scenarios."""
|
| 240 |
+
runner = ScenarioRunner()
|
| 241 |
+
|
| 242 |
+
# Run scenario 1 as a detailed example
|
| 243 |
+
print("\n" + "="*60)
|
| 244 |
+
print("FANTASY DRAFT AGENT - DEMO SCENARIOS")
|
| 245 |
+
print("="*60)
|
| 246 |
+
|
| 247 |
+
# Run just the first scenario in detail
|
| 248 |
+
conversation = runner.run_scenario("scenario_1", verbose=True)
|
| 249 |
+
|
| 250 |
+
# Show how to export
|
| 251 |
+
transcript = runner.export_scenario_transcript("scenario_1", conversation)
|
| 252 |
+
print("\nExported Transcript Preview:")
|
| 253 |
+
print(transcript[:500] + "...")
|
| 254 |
+
|
| 255 |
+
print("\n\nTo run all scenarios:")
|
| 256 |
+
print(">>> runner.run_all_scenarios()")
|
| 257 |
+
|
| 258 |
+
|
| 259 |
+
if __name__ == "__main__":
|
| 260 |
+
demo_scenarios()
|
core/visualizer.py
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Visual components for Fantasy Draft Agent demos.
|
| 3 |
+
Creates ASCII and simple text visualizations for player cards and draft boards.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from typing import List, Dict
|
| 7 |
+
from .data import TOP_PLAYERS, get_player_info
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def create_player_card(player_name: str) -> str:
|
| 11 |
+
"""Generate ASCII player card for visualization."""
|
| 12 |
+
player = get_player_info(player_name)
|
| 13 |
+
if not player:
|
| 14 |
+
return f"Player '{player_name}' not found"
|
| 15 |
+
|
| 16 |
+
# Create a nice ASCII card
|
| 17 |
+
card = f"""
|
| 18 |
+
╔═══════════════════════════════╗
|
| 19 |
+
║ {player_name:<27} ║
|
| 20 |
+
╠═══════════════════════════════╣
|
| 21 |
+
║ Position: {player['pos']:<18} ║
|
| 22 |
+
║ Team: {player['team']:<18} ║
|
| 23 |
+
║ ADP: {player['adp']:<18} ║
|
| 24 |
+
║ Tier: {player['tier']:<18} ║
|
| 25 |
+
║ 2023 PPG: {player['ppg_2023']:<18} ║
|
| 26 |
+
╚═══════════════════════════════╝
|
| 27 |
+
"""
|
| 28 |
+
return card
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def create_comparison_card(player1: str, player2: str) -> str:
|
| 32 |
+
"""Create a side-by-side comparison of two players."""
|
| 33 |
+
p1 = get_player_info(player1)
|
| 34 |
+
p2 = get_player_info(player2)
|
| 35 |
+
|
| 36 |
+
if not p1 or not p2:
|
| 37 |
+
return "One or both players not found"
|
| 38 |
+
|
| 39 |
+
comparison = f"""
|
| 40 |
+
╔════════════════╦════════════════╗
|
| 41 |
+
║ {player1[:14]:<14} ║ {player2[:14]:<14} ║
|
| 42 |
+
╠════════════════╬════════════════╣
|
| 43 |
+
║ {p1['pos']} - {p1['team']:<10} ║ {p2['pos']} - {p2['team']:<10} ║
|
| 44 |
+
║ ADP: {p1['adp']:<10} ║ ADP: {p2['adp']:<10} ║
|
| 45 |
+
║ Tier: {p1['tier']:<9} ║ Tier: {p2['tier']:<9} ║
|
| 46 |
+
║ PPG: {p1['ppg_2023']:<10} ║ PPG: {p2['ppg_2023']:<10} ║
|
| 47 |
+
╚════════════════╩════════════════╝
|
| 48 |
+
"""
|
| 49 |
+
return comparison
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def create_draft_board_snapshot(picks: List[Dict[str, any]], rounds_to_show: int = 3) -> str:
|
| 53 |
+
"""Create a simple draft board visualization."""
|
| 54 |
+
board = "📋 DRAFT BOARD\n"
|
| 55 |
+
board += "="*50 + "\n\n"
|
| 56 |
+
|
| 57 |
+
# Group picks by round (assuming 12-team league)
|
| 58 |
+
rounds = {}
|
| 59 |
+
for i, pick in enumerate(picks):
|
| 60 |
+
round_num = (i // 12) + 1
|
| 61 |
+
if round_num <= rounds_to_show:
|
| 62 |
+
if round_num not in rounds:
|
| 63 |
+
rounds[round_num] = []
|
| 64 |
+
|
| 65 |
+
pick_num = (i % 12) + 1
|
| 66 |
+
player_name = pick if isinstance(pick, str) else pick.get("player", "Unknown")
|
| 67 |
+
rounds[round_num].append(f"{pick_num}. {player_name}")
|
| 68 |
+
|
| 69 |
+
# Display rounds
|
| 70 |
+
for round_num in sorted(rounds.keys()):
|
| 71 |
+
board += f"Round {round_num}:\n"
|
| 72 |
+
for pick in rounds[round_num]:
|
| 73 |
+
board += f" {pick}\n"
|
| 74 |
+
board += "\n"
|
| 75 |
+
|
| 76 |
+
return board
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def create_roster_summary(my_picks: List[str]) -> str:
|
| 80 |
+
"""Create a summary of the user's roster."""
|
| 81 |
+
if not my_picks:
|
| 82 |
+
return "No picks yet!"
|
| 83 |
+
|
| 84 |
+
roster = "📝 YOUR ROSTER\n"
|
| 85 |
+
roster += "="*30 + "\n\n"
|
| 86 |
+
|
| 87 |
+
# Group by position
|
| 88 |
+
by_position = {"QB": [], "RB": [], "WR": [], "TE": []}
|
| 89 |
+
|
| 90 |
+
for player_name in my_picks:
|
| 91 |
+
player = get_player_info(player_name)
|
| 92 |
+
if player:
|
| 93 |
+
pos = player['pos']
|
| 94 |
+
if pos in by_position:
|
| 95 |
+
by_position[pos].append(player_name)
|
| 96 |
+
|
| 97 |
+
# Display by position
|
| 98 |
+
for pos, players in by_position.items():
|
| 99 |
+
if players:
|
| 100 |
+
roster += f"{pos}:\n"
|
| 101 |
+
for player in players:
|
| 102 |
+
info = get_player_info(player)
|
| 103 |
+
roster += f" • {player} ({info['team']}) - {info['ppg_2023']} PPG\n"
|
| 104 |
+
roster += "\n"
|
| 105 |
+
|
| 106 |
+
# Calculate projected points
|
| 107 |
+
total_projected = sum(get_player_info(p)['ppg_2023'] for p in my_picks if get_player_info(p))
|
| 108 |
+
roster += f"Projected Weekly Points: {total_projected:.1f}\n"
|
| 109 |
+
|
| 110 |
+
return roster
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def create_decision_summary(options: List[str], recommendation: str, reason: str) -> str:
|
| 114 |
+
"""Create a visual summary of a draft decision."""
|
| 115 |
+
summary = "🤔 DRAFT DECISION\n"
|
| 116 |
+
summary += "="*40 + "\n\n"
|
| 117 |
+
|
| 118 |
+
summary += "Options considered:\n"
|
| 119 |
+
for i, option in enumerate(options, 1):
|
| 120 |
+
player = get_player_info(option)
|
| 121 |
+
if player:
|
| 122 |
+
summary += f"{i}. {option} ({player['pos']}) - ADP: {player['adp']}\n"
|
| 123 |
+
|
| 124 |
+
summary += f"\n✅ Recommendation: {recommendation}\n"
|
| 125 |
+
summary += f"📊 Reason: {reason}\n"
|
| 126 |
+
|
| 127 |
+
return summary
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
def create_scenario_result(scenario_name: str, picks: List[str], outcome: str) -> str:
|
| 131 |
+
"""Create a result summary for a scenario."""
|
| 132 |
+
result = f"🏆 SCENARIO RESULT: {scenario_name}\n"
|
| 133 |
+
result += "="*50 + "\n\n"
|
| 134 |
+
|
| 135 |
+
result += "Picks made:\n"
|
| 136 |
+
for i, pick in enumerate(picks, 1):
|
| 137 |
+
player = get_player_info(pick)
|
| 138 |
+
if player:
|
| 139 |
+
result += f"{i}. {pick} ({player['pos']}) - {player['ppg_2023']} PPG\n"
|
| 140 |
+
|
| 141 |
+
result += f"\n📈 Outcome: {outcome}\n"
|
| 142 |
+
|
| 143 |
+
# Calculate total projected points
|
| 144 |
+
total_ppg = sum(get_player_info(p)['ppg_2023'] for p in picks if get_player_info(p))
|
| 145 |
+
result += f"Total Projected PPG: {total_ppg:.1f}\n"
|
| 146 |
+
|
| 147 |
+
return result
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
def create_multi_turn_flow(conversation_turns: List[Dict]) -> str:
|
| 151 |
+
"""Visualize a multi-turn conversation flow with context indicators."""
|
| 152 |
+
flow = "💬 MULTI-TURN CONVERSATION FLOW\n"
|
| 153 |
+
flow += "="*50 + "\n\n"
|
| 154 |
+
|
| 155 |
+
# Add legend
|
| 156 |
+
flow += "📖 Legend:\n"
|
| 157 |
+
flow += " 🔄 = New Turn\n"
|
| 158 |
+
flow += " 💭 = Using Previous Context\n"
|
| 159 |
+
flow += " ✨ = Explicit Context Reference\n"
|
| 160 |
+
flow += " 🔗 = Building on Previous Answer\n\n"
|
| 161 |
+
|
| 162 |
+
flow += "Conversation Timeline:\n"
|
| 163 |
+
flow += "│\n"
|
| 164 |
+
|
| 165 |
+
for i, turn in enumerate(conversation_turns):
|
| 166 |
+
# Turn header
|
| 167 |
+
flow += f"├─ 🔄 TURN {turn['turn']}: {turn['showcases']}\n"
|
| 168 |
+
flow += "│\n"
|
| 169 |
+
|
| 170 |
+
# User message
|
| 171 |
+
user_preview = turn['user'][:60] + "..." if len(turn['user']) > 60 else turn['user']
|
| 172 |
+
flow += f"│ 👤 User: \"{user_preview}\"\n"
|
| 173 |
+
|
| 174 |
+
# Show if this builds on previous context
|
| 175 |
+
if i > 0:
|
| 176 |
+
flow += "│ └─ 💭 (References previous conversation)\n"
|
| 177 |
+
|
| 178 |
+
# Agent response preview
|
| 179 |
+
agent_preview = turn['agent'][:60] + "..." if len(turn['agent']) > 60 else turn['agent']
|
| 180 |
+
flow += f"│ 🤖 Agent: \"{agent_preview}\"\n"
|
| 181 |
+
|
| 182 |
+
# Context retention indicator
|
| 183 |
+
if turn.get('context_retained', False):
|
| 184 |
+
flow += "│ └─ ✨ CONTEXT USED: Agent explicitly referenced earlier conversation\n"
|
| 185 |
+
|
| 186 |
+
# Check for specific context clues
|
| 187 |
+
agent_lower = turn['agent'].lower()
|
| 188 |
+
if any(word in agent_lower for word in ['you mentioned', 'you said', 'earlier']):
|
| 189 |
+
flow += "│ └─ 🔗 Directly references user's previous input\n"
|
| 190 |
+
elif any(word in agent_lower for word in ['we discussed', 'as i mentioned']):
|
| 191 |
+
flow += "│ └─ 🔗 Builds on previous recommendations\n"
|
| 192 |
+
|
| 193 |
+
flow += "│\n"
|
| 194 |
+
|
| 195 |
+
flow += "└─ 🏁 Conversation Complete\n\n"
|
| 196 |
+
|
| 197 |
+
# Add summary
|
| 198 |
+
flow += f"📊 Summary:\n"
|
| 199 |
+
flow += f" • Total turns: {len(conversation_turns)}\n"
|
| 200 |
+
flow += f" • Context references: {sum(1 for t in conversation_turns if t.get('context_retained', False))}\n"
|
| 201 |
+
flow += f" • Demonstrates: Multi-turn memory and context awareness\n"
|
| 202 |
+
|
| 203 |
+
return flow
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
def create_multi_turn_diagram(scenario_name: str, turns: int = 3) -> str:
|
| 207 |
+
"""Create a visual diagram showing multi-turn conversation flow."""
|
| 208 |
+
diagram = f"""
|
| 209 |
+
╔═══════════════════════════════════════════════════════╗
|
| 210 |
+
║ 🏈 MULTI-TURN CONVERSATION DEMO ║
|
| 211 |
+
║ {scenario_name:<35} ║
|
| 212 |
+
╚═══════════════════════════════════════════════════════╝
|
| 213 |
+
|
| 214 |
+
┌─────────────────────────────────────────────────────┐
|
| 215 |
+
│ 💡 KEY FEATURE: Context Retention Across Turns │
|
| 216 |
+
└─────────────────────────────────────────────────────┘
|
| 217 |
+
|
| 218 |
+
Turn 1 Turn 2 Turn 3
|
| 219 |
+
│ │ │
|
| 220 |
+
▼ ▼ ▼
|
| 221 |
+
┌──────────┐ ┌──────────┐ ┌──────────┐
|
| 222 |
+
│ 👤 USER │ │ 👤 USER │ │ 👤 USER │
|
| 223 |
+
│ │ │ │ │ │
|
| 224 |
+
│ Initial │────────▶│ Follow │────────▶│ Build │
|
| 225 |
+
│ Question │ MEMORY │ Up │ MEMORY │ On │
|
| 226 |
+
└──────────┘ └──────────┘ └──────────┘
|
| 227 |
+
│ │ │
|
| 228 |
+
▼ ▼ ▼
|
| 229 |
+
┌──────────┐ ┌──────────┐ ┌──────────┐
|
| 230 |
+
│ 🤖 AGENT │ │ 🤖 AGENT │ │ 🤖 AGENT │
|
| 231 |
+
│ │ │ 💭 │ │ 💭 │
|
| 232 |
+
│ Answer │ │ Remembers│ │ Full │
|
| 233 |
+
│ │ │ Turn 1 │ │ Context │
|
| 234 |
+
└──────────┘ └──────────┘ └──────────┘
|
| 235 |
+
|
| 236 |
+
Legend:
|
| 237 |
+
💭 = Agent accessing conversation memory
|
| 238 |
+
──▶ = Context flows forward to next turn
|
| 239 |
+
👤 = User input
|
| 240 |
+
🤖 = Agent response with memory
|
| 241 |
+
"""
|
| 242 |
+
return diagram
|
| 243 |
+
|
| 244 |
+
|
| 245 |
+
def create_context_highlight_example() -> str:
|
| 246 |
+
"""Show an example of how context is retained across turns."""
|
| 247 |
+
example = """
|
| 248 |
+
🎯 MULTI-TURN CONTEXT RETENTION EXAMPLE
|
| 249 |
+
═══════════════════════════════════════
|
| 250 |
+
|
| 251 |
+
TURN 1:
|
| 252 |
+
👤 User: "I have the 5th pick. Who should I target?"
|
| 253 |
+
🤖 Agent: "With the 5th pick, I recommend targeting Bijan Robinson..."
|
| 254 |
+
|
| 255 |
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
| 256 |
+
|
| 257 |
+
TURN 2:
|
| 258 |
+
👤 User: "What about Ekeler instead?"
|
| 259 |
+
↑
|
| 260 |
+
└─── 🔍 No need to repeat context (5th pick)
|
| 261 |
+
|
| 262 |
+
🤖 Agent: "Given your 5th pick position that we discussed..."
|
| 263 |
+
↑
|
| 264 |
+
└─── ✨ AGENT REMEMBERS: Pick position from Turn 1
|
| 265 |
+
|
| 266 |
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
| 267 |
+
|
| 268 |
+
TURN 3:
|
| 269 |
+
👤 User: "Who pairs well with him?"
|
| 270 |
+
↑
|
| 271 |
+
└─── 🔍 Pronoun "him" refers to previous player
|
| 272 |
+
|
| 273 |
+
🤖 Agent: "To pair with Ekeler from your 5th pick..."
|
| 274 |
+
↑ ↑
|
| 275 |
+
│ └─── ✨ REMEMBERS: Pick position
|
| 276 |
+
└─────────────────── ✨ REMEMBERS: Player choice
|
| 277 |
+
|
| 278 |
+
📊 Context Retained: 100% across all turns!
|
| 279 |
+
"""
|
| 280 |
+
return example
|
| 281 |
+
|
| 282 |
+
|
| 283 |
+
# Demo function
|
| 284 |
+
def demo_visuals():
|
| 285 |
+
"""Demonstrate all visualization functions."""
|
| 286 |
+
print("FANTASY DRAFT AGENT - VISUAL COMPONENTS DEMO")
|
| 287 |
+
print("="*50 + "\n")
|
| 288 |
+
|
| 289 |
+
# Player card
|
| 290 |
+
print("1. Player Card:")
|
| 291 |
+
print(create_player_card("Christian McCaffrey"))
|
| 292 |
+
|
| 293 |
+
# Comparison
|
| 294 |
+
print("\n2. Player Comparison:")
|
| 295 |
+
print(create_comparison_card("Tyreek Hill", "CeeDee Lamb"))
|
| 296 |
+
|
| 297 |
+
# Draft board
|
| 298 |
+
print("\n3. Draft Board:")
|
| 299 |
+
picks = ["Christian McCaffrey", "Justin Jefferson", "CeeDee Lamb", "Tyreek Hill",
|
| 300 |
+
"Bijan Robinson", "Ja'Marr Chase", "Austin Ekeler", "Saquon Barkley",
|
| 301 |
+
"A.J. Brown", "Nick Chubb", "Stefon Diggs", "Breece Hall"]
|
| 302 |
+
print(create_draft_board_snapshot(picks))
|
| 303 |
+
|
| 304 |
+
# Roster summary
|
| 305 |
+
print("\n4. Roster Summary:")
|
| 306 |
+
my_picks = ["Bijan Robinson", "A.J. Brown", "Mark Andrews", "Chris Olave"]
|
| 307 |
+
print(create_roster_summary(my_picks))
|
| 308 |
+
|
| 309 |
+
# Decision summary
|
| 310 |
+
print("\n5. Decision Summary:")
|
| 311 |
+
print(create_decision_summary(
|
| 312 |
+
options=["Bijan Robinson", "Austin Ekeler", "Ja'Marr Chase"],
|
| 313 |
+
recommendation="Bijan Robinson",
|
| 314 |
+
reason="Elite talent with RB1 upside in a high-powered offense"
|
| 315 |
+
))
|
| 316 |
+
|
| 317 |
+
|
| 318 |
+
if __name__ == "__main__":
|
| 319 |
+
demo_visuals()
|
deploy_instructions.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Gradio Deploy Instructions
|
| 2 |
+
|
| 3 |
+
## Prerequisites
|
| 4 |
+
1. Make sure you're in the `fantasy-draft-agent` directory
|
| 5 |
+
2. Activate your virtual environment: `source venv/bin/activate`
|
| 6 |
+
3. Have your Hugging Face account ready
|
| 7 |
+
|
| 8 |
+
## Run the Deploy Command
|
| 9 |
+
|
| 10 |
+
```bash
|
| 11 |
+
gradio deploy
|
| 12 |
+
```
|
| 13 |
+
|
| 14 |
+
## What You'll Be Asked:
|
| 15 |
+
|
| 16 |
+
1. **Hugging Face Token**:
|
| 17 |
+
- Go to https://huggingface.co/settings/tokens
|
| 18 |
+
- Create a new token with 'write' permissions
|
| 19 |
+
- Copy and paste it when prompted
|
| 20 |
+
|
| 21 |
+
2. **Space Name**: Enter something like `fantasy-draft-demo`
|
| 22 |
+
|
| 23 |
+
3. **Hardware**: Choose `cpu-basic` (free tier)
|
| 24 |
+
|
| 25 |
+
4. **Want to create a secret?**:
|
| 26 |
+
- Type `yes`
|
| 27 |
+
- Secret name: `OPENAI_API_KEY`
|
| 28 |
+
- Secret value: Your OpenAI API key
|
| 29 |
+
|
| 30 |
+
5. **Space visibility**: Choose `public` or `private`
|
| 31 |
+
|
| 32 |
+
## After Deployment
|
| 33 |
+
|
| 34 |
+
The command will:
|
| 35 |
+
- Automatically create the Space
|
| 36 |
+
- Upload all your files
|
| 37 |
+
- Set up the environment
|
| 38 |
+
- Provide you with the Space URL
|
| 39 |
+
|
| 40 |
+
## Important Notes
|
| 41 |
+
|
| 42 |
+
- The deploy command uses `app.py` as the entry point (which we already created)
|
| 43 |
+
- It will automatically detect and upload your `requirements.txt`
|
| 44 |
+
- The README_HF.md metadata will be used if you rename it to README.md first
|
| 45 |
+
- You can run `gradio deploy` again to update an existing Space
|
docs/FEATURES_AND_ENHANCEMENTS.md
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Features and Enhancements
|
| 2 |
+
|
| 3 |
+
## Core Features
|
| 4 |
+
|
| 5 |
+
### 🤖 Multi-Agent System
|
| 6 |
+
- **6 AI Agents**: Each with distinct personality and strategy
|
| 7 |
+
- **User Position**: Player drafts at position 4 with AI advisor
|
| 8 |
+
- **3-Round Draft**: Snake draft format (1→6, 6→1, 1→6)
|
| 9 |
+
- **Real-time Interaction**: Agents comment and react to picks
|
| 10 |
+
|
| 11 |
+
### 🎭 Agent Personalities
|
| 12 |
+
|
| 13 |
+
#### Team 1 - Zero RB (📘🤓)
|
| 14 |
+
- **Strategy**: Avoids RBs early, loads up on elite WRs
|
| 15 |
+
- **Personality**: Analytical, confident in contrarian approach
|
| 16 |
+
- **Catchphrase**: "RBs get injured. I'll build around elite WRs."
|
| 17 |
+
|
| 18 |
+
#### Team 2 & 6 - Best Player Available (📗🧑💼/👨🏫)
|
| 19 |
+
- **Strategy**: Pure value drafting, ignores positional needs
|
| 20 |
+
- **Personality**: Disciplined, mocks others for reaching
|
| 21 |
+
- **Catchphrase**: "Value is value. I don't reach for needs."
|
| 22 |
+
|
| 23 |
+
#### Team 3 - Robust RB (📙🧔)
|
| 24 |
+
- **Strategy**: Prioritizes RBs in rounds 1-2
|
| 25 |
+
- **Personality**: Traditional, old-school approach
|
| 26 |
+
- **Catchphrase**: "RBs win championships. Period."
|
| 27 |
+
|
| 28 |
+
#### Team 5 - Upside Hunter (📓🤠)
|
| 29 |
+
- **Strategy**: Seeks high-risk, high-reward players
|
| 30 |
+
- **Personality**: Bold, mocks conservative picks
|
| 31 |
+
- **Catchphrase**: "Safe picks are for losers!"
|
| 32 |
+
|
| 33 |
+
#### User's Advisor (📕🧙)
|
| 34 |
+
- **Role**: Provides strategic advice to the user
|
| 35 |
+
- **Features**: Analyzes board state, suggests best picks, explains reasoning
|
| 36 |
+
|
| 37 |
+
### 🎨 UI/UX Enhancements
|
| 38 |
+
|
| 39 |
+
#### Visual Design
|
| 40 |
+
- **Agent Cards**: Color-coded with emojis for easy identification
|
| 41 |
+
- **Message Formatting**: Speaker badges with recipient indicators
|
| 42 |
+
- **Draft Board**: Visual grid showing all picks by round
|
| 43 |
+
- **Dark Text Fix**: Ensures readability on all backgrounds
|
| 44 |
+
|
| 45 |
+
#### Interactive Elements
|
| 46 |
+
- **Typing Animation**: "..." indicator for realistic feel
|
| 47 |
+
- **Progressive Updates**: Draft unfolds in real-time
|
| 48 |
+
- **Available Players**: Dropdown showing top 20 options
|
| 49 |
+
- **User Input**: Clean interface for making picks
|
| 50 |
+
|
| 51 |
+
### 💬 Communication Features
|
| 52 |
+
|
| 53 |
+
#### Conversation System
|
| 54 |
+
- **Directed Messages**: Clear sender → recipient format
|
| 55 |
+
- **Commissioner Announcements**: Official draft updates
|
| 56 |
+
- **Trash Talk**: Agents comment on rivals' picks
|
| 57 |
+
- **Memory Indicators**: Shows when agents reference past events
|
| 58 |
+
|
| 59 |
+
#### Comment Limiting
|
| 60 |
+
- **Smart Throttling**: Max 2-3 comments per pick
|
| 61 |
+
- **Rival Priority**: Rivals more likely to comment
|
| 62 |
+
- **Natural Flow**: Prevents conversation overload
|
| 63 |
+
|
| 64 |
+
### 🔧 Technical Features
|
| 65 |
+
|
| 66 |
+
#### Dual Mode Operation
|
| 67 |
+
1. **Basic Multiagent Mode**
|
| 68 |
+
- Single process execution
|
| 69 |
+
- Fast response times
|
| 70 |
+
- Perfect for development
|
| 71 |
+
|
| 72 |
+
2. **A2A Mode (Agent-to-Agent)**
|
| 73 |
+
- Distributed architecture
|
| 74 |
+
- Each agent on HTTP server
|
| 75 |
+
- Production-ready setup
|
| 76 |
+
|
| 77 |
+
#### Multi-User Support
|
| 78 |
+
- **Session Isolation**: Each user gets separate instance
|
| 79 |
+
- **Gradio State Management**: Proper state handling
|
| 80 |
+
- **Dynamic Port Allocation**: No conflicts in A2A mode
|
| 81 |
+
- **Concurrent Users**: Supports multiple simultaneous drafts
|
| 82 |
+
|
| 83 |
+
### 📊 Draft Features
|
| 84 |
+
|
| 85 |
+
#### Snake Draft Logic
|
| 86 |
+
- **Round 1**: Picks 1→2→3→4→5→6
|
| 87 |
+
- **Round 2**: Picks 6→5→4→3→2→1 (reverses)
|
| 88 |
+
- **Round 3**: Picks 1→2→3→4→5→6
|
| 89 |
+
|
| 90 |
+
#### Player Database
|
| 91 |
+
- **50+ Players**: Real NFL players with positions
|
| 92 |
+
- **Team Info**: Current NFL team assignments
|
| 93 |
+
- **Positional Balance**: QBs, RBs, WRs, TEs
|
| 94 |
+
- **Realistic Rankings**: Based on fantasy relevance
|
| 95 |
+
|
| 96 |
+
#### Draft Intelligence
|
| 97 |
+
- **Strategy Adherence**: Agents follow their strategies
|
| 98 |
+
- **Contextual Decisions**: React to draft flow
|
| 99 |
+
- **Position Scarcity**: Recognize run on positions
|
| 100 |
+
- **Value Recognition**: Identify steals and reaches
|
| 101 |
+
|
| 102 |
+
### 🚀 Performance Optimizations
|
| 103 |
+
|
| 104 |
+
#### Configurable Delays
|
| 105 |
+
```python
|
| 106 |
+
TYPING_DELAY_SECONDS = 0.3 # "..." display time
|
| 107 |
+
MESSAGE_DELAY_SECONDS = 0.1 # Between messages
|
| 108 |
+
AGENT_START_DELAY = 0.5 # A2A startup spacing
|
| 109 |
+
```
|
| 110 |
+
|
| 111 |
+
#### Resource Management
|
| 112 |
+
- **Async Operations**: Non-blocking agent communication
|
| 113 |
+
- **Timeout Handling**: 30-second default with fallbacks
|
| 114 |
+
- **Memory Efficiency**: Clean state management
|
| 115 |
+
- **Port Cleanup**: Automatic resource release
|
| 116 |
+
|
| 117 |
+
### 🔐 Reliability Features
|
| 118 |
+
|
| 119 |
+
#### Error Handling
|
| 120 |
+
- **Graceful Fallbacks**: A2A → simulation if needed
|
| 121 |
+
- **Clear Error Messages**: User-friendly notifications
|
| 122 |
+
- **Validation**: Player name and state checking
|
| 123 |
+
- **Recovery**: Continues draft after errors
|
| 124 |
+
|
| 125 |
+
#### State Management
|
| 126 |
+
- **Draft Persistence**: Maintains state across turns
|
| 127 |
+
- **Conversation History**: Full context preservation
|
| 128 |
+
- **Pick Validation**: Prevents duplicate selections
|
| 129 |
+
- **Board Updates**: Real-time synchronization
|
| 130 |
+
|
| 131 |
+
## Recent Enhancements
|
| 132 |
+
|
| 133 |
+
### Task ID Implementation
|
| 134 |
+
- Simplified A2A conversation tracking
|
| 135 |
+
- Removed redundant history management
|
| 136 |
+
- Cleaner code architecture
|
| 137 |
+
- Better framework integration
|
| 138 |
+
|
| 139 |
+
### Text Readability Fix
|
| 140 |
+
- Dark text on all backgrounds
|
| 141 |
+
- Explicit color styling
|
| 142 |
+
- Fixed message card contrast
|
| 143 |
+
- Improved overall readability
|
| 144 |
+
|
| 145 |
+
### Dynamic Port Allocation
|
| 146 |
+
- Support for 100+ concurrent A2A sessions
|
| 147 |
+
- Automatic port assignment (5000-9000)
|
| 148 |
+
- Session-based isolation
|
| 149 |
+
- Conflict prevention
|
| 150 |
+
|
| 151 |
+
### Enhanced Multi-User Support
|
| 152 |
+
- Full session isolation
|
| 153 |
+
- Gradio State implementation
|
| 154 |
+
- Proper callback handling
|
| 155 |
+
- Warning messages for A2A limitations
|
| 156 |
+
|
| 157 |
+
## Usage Tips
|
| 158 |
+
|
| 159 |
+
### For Best Experience
|
| 160 |
+
1. Start with Basic Multiagent mode for speed
|
| 161 |
+
2. Try A2A mode to see distributed architecture
|
| 162 |
+
3. Watch for memory indicators showing context
|
| 163 |
+
4. Pay attention to rival interactions
|
| 164 |
+
|
| 165 |
+
### Customization Options
|
| 166 |
+
- Adjust delays in `constants.py`
|
| 167 |
+
- Modify agent strategies in `agent.py`
|
| 168 |
+
- Add players to `data.py`
|
| 169 |
+
- Customize UI in `app_enhanced.py`
|
| 170 |
+
|
| 171 |
+
## Future Possibilities
|
| 172 |
+
- WebSocket real-time updates
|
| 173 |
+
- Custom league settings
|
| 174 |
+
- More agent personalities
|
| 175 |
+
- Advanced statistics
|
| 176 |
+
- Trade negotiations
|
| 177 |
+
- Dynasty league support
|
docs/SETUP_GUIDE.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Setup Guide
|
| 2 |
+
|
| 3 |
+
This guide covers everything you need to set up and run the Fantasy Draft Multi-Agent Demo.
|
| 4 |
+
|
| 5 |
+
## Quick Start
|
| 6 |
+
|
| 7 |
+
1. **Clone the repository**
|
| 8 |
+
```bash
|
| 9 |
+
git clone <your-repo-url>
|
| 10 |
+
cd fantasy-draft-agent
|
| 11 |
+
```
|
| 12 |
+
|
| 13 |
+
2. **Set up Python environment** (Python 3.8+ required)
|
| 14 |
+
```bash
|
| 15 |
+
python -m venv venv
|
| 16 |
+
source venv/bin/activate # On Windows: venv\Scripts\activate
|
| 17 |
+
pip install -r requirements.txt
|
| 18 |
+
```
|
| 19 |
+
|
| 20 |
+
3. **Set up API key**
|
| 21 |
+
```bash
|
| 22 |
+
export OPENAI_API_KEY='your-key-here'
|
| 23 |
+
# Or create a .env file with: OPENAI_API_KEY=your-key-here
|
| 24 |
+
```
|
| 25 |
+
|
| 26 |
+
4. **Run the app**
|
| 27 |
+
```bash
|
| 28 |
+
python apps/app_enhanced.py
|
| 29 |
+
```
|
| 30 |
+
|
| 31 |
+
## Detailed Setup
|
| 32 |
+
|
| 33 |
+
### Environment Setup
|
| 34 |
+
|
| 35 |
+
The app requires Python 3.8+ and the following main dependencies:
|
| 36 |
+
- `gradio>=4.0.0` - Web interface
|
| 37 |
+
- `any-agent` - Multi-agent framework
|
| 38 |
+
- `openai` - LLM integration
|
| 39 |
+
- `python-dotenv` - Environment management
|
| 40 |
+
- `nest-asyncio` - Async support for Gradio
|
| 41 |
+
|
| 42 |
+
### Virtual Environment (Recommended)
|
| 43 |
+
|
| 44 |
+
Using a virtual environment keeps dependencies isolated:
|
| 45 |
+
|
| 46 |
+
```bash
|
| 47 |
+
# Create virtual environment
|
| 48 |
+
python -m venv venv
|
| 49 |
+
|
| 50 |
+
# Activate it
|
| 51 |
+
# Linux/Mac:
|
| 52 |
+
source venv/bin/activate
|
| 53 |
+
# Windows:
|
| 54 |
+
venv\Scripts\activate
|
| 55 |
+
|
| 56 |
+
# Install dependencies
|
| 57 |
+
pip install -r requirements.txt
|
| 58 |
+
|
| 59 |
+
# Deactivate when done
|
| 60 |
+
deactivate
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
### Configuration
|
| 64 |
+
|
| 65 |
+
1. **API Keys**: The app requires an OpenAI API key. Set it via:
|
| 66 |
+
- Environment variable: `export OPENAI_API_KEY='sk-...'`
|
| 67 |
+
- `.env` file: Create `.env` in project root with `OPENAI_API_KEY=sk-...`
|
| 68 |
+
|
| 69 |
+
2. **Port Configuration**:
|
| 70 |
+
- Default web interface: Port 7860
|
| 71 |
+
- A2A mode uses dynamic ports (5000-9000 range)
|
| 72 |
+
- Modify in `app_enhanced.py` if needed
|
| 73 |
+
|
| 74 |
+
## Running Different Modes
|
| 75 |
+
|
| 76 |
+
### Basic Mode (Recommended for Development)
|
| 77 |
+
```bash
|
| 78 |
+
python apps/app_enhanced.py
|
| 79 |
+
```
|
| 80 |
+
- Single process, fast execution
|
| 81 |
+
- Good for testing and development
|
| 82 |
+
- Supports multiple users
|
| 83 |
+
|
| 84 |
+
### A2A Mode (Distributed Agents)
|
| 85 |
+
- Automatically enabled via the UI toggle
|
| 86 |
+
- Each agent runs on its own HTTP server
|
| 87 |
+
- Dynamic port allocation for multi-user support
|
| 88 |
+
- Production-ready architecture
|
| 89 |
+
|
| 90 |
+
## Deployment Options
|
| 91 |
+
|
| 92 |
+
### Local Deployment
|
| 93 |
+
- Just run the app as shown above
|
| 94 |
+
- Access at `http://localhost:7860`
|
| 95 |
+
- Share link provided by Gradio
|
| 96 |
+
|
| 97 |
+
### Hugging Face Spaces
|
| 98 |
+
1. Create a new Space on Hugging Face
|
| 99 |
+
2. Upload all files maintaining structure
|
| 100 |
+
3. Set the OpenAI API key as a Space secret
|
| 101 |
+
4. The app will auto-detect and run
|
| 102 |
+
|
| 103 |
+
### Server Deployment
|
| 104 |
+
1. Ensure Python 3.8+ is installed
|
| 105 |
+
2. Clone repository to server
|
| 106 |
+
3. Set up systemd service or process manager
|
| 107 |
+
4. Configure reverse proxy (nginx/Apache) if needed
|
| 108 |
+
5. Set environment variables securely
|
| 109 |
+
|
| 110 |
+
## Troubleshooting
|
| 111 |
+
|
| 112 |
+
### Common Issues
|
| 113 |
+
|
| 114 |
+
1. **"OPENAI_API_KEY not found"**
|
| 115 |
+
- Ensure the API key is set correctly
|
| 116 |
+
- Check `.env` file location (project root)
|
| 117 |
+
- Verify key starts with `sk-`
|
| 118 |
+
|
| 119 |
+
2. **Port already in use**
|
| 120 |
+
- Change port in `app_enhanced.py`: `demo.launch(server_port=7861)`
|
| 121 |
+
- Or kill the process using the port
|
| 122 |
+
|
| 123 |
+
3. **Module not found errors**
|
| 124 |
+
- Ensure virtual environment is activated
|
| 125 |
+
- Run `pip install -r requirements.txt` again
|
| 126 |
+
- Check Python version (3.8+ required)
|
| 127 |
+
|
| 128 |
+
4. **A2A mode fails to start**
|
| 129 |
+
- Check firewall settings for ports 5000-9000
|
| 130 |
+
- Ensure no other services using these ports
|
| 131 |
+
- Try Basic Multiagent mode as fallback
|
| 132 |
+
|
| 133 |
+
### Performance Tips
|
| 134 |
+
|
| 135 |
+
- Use Basic Multiagent mode for faster response times
|
| 136 |
+
- Adjust `TYPING_DELAY_SECONDS` in `constants.py` for faster demos
|
| 137 |
+
- Run on a machine with good CPU for multiple A2A agents
|
| 138 |
+
- Consider GPU instance if using larger models
|
| 139 |
+
|
| 140 |
+
## Next Steps
|
| 141 |
+
|
| 142 |
+
- Read [TECHNICAL_DOCUMENTATION.md](TECHNICAL_DOCUMENTATION.md) for architecture details
|
| 143 |
+
- See [FEATURES_AND_ENHANCEMENTS.md](FEATURES_AND_ENHANCEMENTS.md) for all features
|
| 144 |
+
- Check the main [README.md](../README.md) for project overview
|
docs/TECHNICAL_DOCUMENTATION.md
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Technical Documentation
|
| 2 |
+
|
| 3 |
+
## System Architecture
|
| 4 |
+
|
| 5 |
+
### Overview
|
| 6 |
+
|
| 7 |
+
The Fantasy Draft Multi-Agent Demo showcases a sophisticated multi-agent system where AI agents with distinct personalities compete in a mock fantasy football draft. The system supports two execution modes:
|
| 8 |
+
|
| 9 |
+
1. **Basic Multiagent Mode**: Single-process, shared memory, direct method calls
|
| 10 |
+
2. **A2A (Agent-to-Agent) Mode**: Distributed agents on HTTP servers, true isolation
|
| 11 |
+
|
| 12 |
+
### Core Components
|
| 13 |
+
|
| 14 |
+
```
|
| 15 |
+
fantasy-draft-agent/
|
| 16 |
+
├── apps/
|
| 17 |
+
│ ├── app_enhanced.py # Main Gradio interface with A2A support
|
| 18 |
+
│ ├── multiagent_draft.py # Core draft logic and agent management
|
| 19 |
+
│ └── multiagent_scenarios.py # UI formatting and visualization
|
| 20 |
+
├── core/
|
| 21 |
+
│ ├── agent.py # Base agent classes and strategies
|
| 22 |
+
│ ├── constants.py # Configuration constants
|
| 23 |
+
│ ├── data.py # Player data and rankings
|
| 24 |
+
│ ├── dynamic_a2a_manager.py # Dynamic port allocation for A2A
|
| 25 |
+
│ └── a2a_helpers.py # A2A communication utilities
|
| 26 |
+
```
|
| 27 |
+
|
| 28 |
+
## A2A Implementation
|
| 29 |
+
|
| 30 |
+
### Dynamic Port Allocation
|
| 31 |
+
|
| 32 |
+
The system uses dynamic port allocation to support multiple concurrent A2A sessions:
|
| 33 |
+
|
| 34 |
+
```python
|
| 35 |
+
class DynamicA2AAgentManager:
|
| 36 |
+
PORT_RANGE = (5000, 9000) # Available port range
|
| 37 |
+
active_sessions = {} # Track active sessions
|
| 38 |
+
|
| 39 |
+
def __init__(self, session_id):
|
| 40 |
+
self.session_id = session_id
|
| 41 |
+
self.allocated_ports = self._allocate_ports()
|
| 42 |
+
```
|
| 43 |
+
|
| 44 |
+
Each session gets 6 consecutive ports for the 6 agents, ensuring no conflicts between users.
|
| 45 |
+
|
| 46 |
+
### A2A Communication Protocol
|
| 47 |
+
|
| 48 |
+
Agents communicate using the any-agent framework's A2A protocol:
|
| 49 |
+
|
| 50 |
+
1. **Agent Registration**: Each agent starts an HTTP server
|
| 51 |
+
2. **Message Format**: Structured requests/responses with task_id for context
|
| 52 |
+
3. **Async Communication**: Non-blocking calls between agents
|
| 53 |
+
|
| 54 |
+
Example A2A interaction:
|
| 55 |
+
```python
|
| 56 |
+
# Agent makes a pick
|
| 57 |
+
result = await a2a_tool_async(
|
| 58 |
+
f"http://localhost:{port}/make_pick",
|
| 59 |
+
model=agent_config.model,
|
| 60 |
+
task_id=task_id, # Maintains conversation context
|
| 61 |
+
params={
|
| 62 |
+
"available_players": available,
|
| 63 |
+
"round_number": round_num
|
| 64 |
+
}
|
| 65 |
+
)
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
### Task ID and Context Management
|
| 69 |
+
|
| 70 |
+
The A2A framework uses `task_id` to maintain conversation continuity:
|
| 71 |
+
|
| 72 |
+
- Each draft session has a unique task_id
|
| 73 |
+
- Agents automatically maintain conversation history per task_id
|
| 74 |
+
- No manual conversation tracking needed in A2A mode
|
| 75 |
+
- Enables true stateless HTTP communication
|
| 76 |
+
|
| 77 |
+
## Multi-User Support
|
| 78 |
+
|
| 79 |
+
### Session-Based Architecture
|
| 80 |
+
|
| 81 |
+
The app uses Gradio's `gr.State` to maintain separate sessions:
|
| 82 |
+
|
| 83 |
+
```python
|
| 84 |
+
# Each user gets their own app instance
|
| 85 |
+
app_state = gr.State(None)
|
| 86 |
+
|
| 87 |
+
def run_draft(mode, app):
|
| 88 |
+
if app is None:
|
| 89 |
+
app = EnhancedFantasyDraftApp()
|
| 90 |
+
# ... rest of logic
|
| 91 |
+
return output, app # Return updated state
|
| 92 |
+
```
|
| 93 |
+
|
| 94 |
+
### Concurrency Handling
|
| 95 |
+
|
| 96 |
+
- **Basic Mode**: Full multi-user support, each user has isolated state
|
| 97 |
+
- **A2A Mode**: Dynamic ports prevent conflicts between sessions
|
| 98 |
+
- **Resource Management**: Automatic cleanup when sessions end
|
| 99 |
+
|
| 100 |
+
### Limitations
|
| 101 |
+
|
| 102 |
+
- A2A mode requires available ports in the 5000-9000 range
|
| 103 |
+
- Each A2A session uses ~6 ports (one per agent)
|
| 104 |
+
- Maximum concurrent A2A sessions limited by port range
|
| 105 |
+
|
| 106 |
+
## Agent Architecture
|
| 107 |
+
|
| 108 |
+
### Base Agent Design
|
| 109 |
+
|
| 110 |
+
```python
|
| 111 |
+
class FantasyDraftAgent(TinyAgent):
|
| 112 |
+
def __init__(self, team_num, team_name, strategy, personality):
|
| 113 |
+
super().__init__(
|
| 114 |
+
name=f"Team {team_num} - {team_name}",
|
| 115 |
+
instructions=self._build_instructions(),
|
| 116 |
+
model="gpt-4",
|
| 117 |
+
temperature=0.7
|
| 118 |
+
)
|
| 119 |
+
```
|
| 120 |
+
|
| 121 |
+
### Strategy Implementation
|
| 122 |
+
|
| 123 |
+
Each agent has a distinct drafting strategy:
|
| 124 |
+
|
| 125 |
+
1. **Zero RB**: Avoids RBs early, loads up on WRs
|
| 126 |
+
2. **Best Player Available (BPA)**: Pure value drafting
|
| 127 |
+
3. **Robust RB**: Prioritizes RBs in early rounds
|
| 128 |
+
4. **Upside Hunter**: Seeks high-risk, high-reward players
|
| 129 |
+
|
| 130 |
+
### Memory and Context
|
| 131 |
+
|
| 132 |
+
Agents maintain context through:
|
| 133 |
+
- Conversation history (automatic in any-agent)
|
| 134 |
+
- Draft state passed with each request
|
| 135 |
+
- Strategy-specific decision making
|
| 136 |
+
|
| 137 |
+
## Communication Flow
|
| 138 |
+
|
| 139 |
+
### Pick Phase
|
| 140 |
+
1. Commissioner announces current picker
|
| 141 |
+
2. Agent evaluates available players
|
| 142 |
+
3. Agent makes selection with reasoning
|
| 143 |
+
4. Commissioner confirms pick
|
| 144 |
+
5. Other agents may comment (limited by MAX_COMMENTS_PER_PICK)
|
| 145 |
+
|
| 146 |
+
### Comment System
|
| 147 |
+
- Rivals prioritized for comments
|
| 148 |
+
- Maximum 2-3 comments per pick (configurable)
|
| 149 |
+
- Natural conversation flow with trash talk
|
| 150 |
+
|
| 151 |
+
## Performance Optimization
|
| 152 |
+
|
| 153 |
+
### Typing Effects
|
| 154 |
+
- Configurable delays for realistic feel
|
| 155 |
+
- TYPING_DELAY_SECONDS: Time showing "..."
|
| 156 |
+
- MESSAGE_DELAY_SECONDS: Pause between messages
|
| 157 |
+
|
| 158 |
+
### Parallel Processing
|
| 159 |
+
- A2A agents process independently
|
| 160 |
+
- Async communication prevents blocking
|
| 161 |
+
- Dynamic timeout handling (30s default)
|
| 162 |
+
|
| 163 |
+
### Resource Management
|
| 164 |
+
- Automatic port cleanup on session end
|
| 165 |
+
- Graceful fallback from A2A to simulation
|
| 166 |
+
- Memory-efficient state management
|
| 167 |
+
|
| 168 |
+
## Error Handling
|
| 169 |
+
|
| 170 |
+
### A2A Failures
|
| 171 |
+
- Automatic fallback to simulation mode
|
| 172 |
+
- Clear error messages in UI
|
| 173 |
+
- Session cleanup on errors
|
| 174 |
+
|
| 175 |
+
### Port Allocation
|
| 176 |
+
- Retry logic for port binding
|
| 177 |
+
- Session tracking prevents conflicts
|
| 178 |
+
- Cleanup of abandoned sessions
|
| 179 |
+
|
| 180 |
+
## Security Considerations
|
| 181 |
+
|
| 182 |
+
- API keys never exposed to frontend
|
| 183 |
+
- Each session isolated from others
|
| 184 |
+
- Port range restrictions for A2A
|
| 185 |
+
- No direct file system access from agents
|
| 186 |
+
|
| 187 |
+
## Future Enhancements
|
| 188 |
+
|
| 189 |
+
Potential improvements:
|
| 190 |
+
- WebSocket support for real-time updates
|
| 191 |
+
- Database persistence for draft history
|
| 192 |
+
- Custom model support beyond GPT-4
|
| 193 |
+
- Extended player database
|
| 194 |
+
- League customization options
|
requirements.txt
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Install A2A dependencies FIRST
|
| 2 |
+
a2a-sdk>=0.2.8
|
| 3 |
+
grpcio>=1.60
|
| 4 |
+
grpcio-tools>=1.60
|
| 5 |
+
grpcio-reflection>=1.7.0
|
| 6 |
+
protobuf==5.29.5
|
| 7 |
+
httpx
|
| 8 |
+
httpx-sse>=0.4.0
|
| 9 |
+
sse-starlette
|
| 10 |
+
fastapi>=0.115.2
|
| 11 |
+
uvicorn>=0.23.1
|
| 12 |
+
|
| 13 |
+
# Then install any-agent with explicit A2A extra
|
| 14 |
+
any-agent[a2a]>=0.21.0
|
| 15 |
+
|
| 16 |
+
# OpenAI support (already included in any-agent[openai] but being explicit)
|
| 17 |
+
openai>=1.0.0
|
| 18 |
+
litellm==1.72.4
|
| 19 |
+
|
| 20 |
+
# App dependencies
|
| 21 |
+
python-dotenv
|
| 22 |
+
pydantic
|
| 23 |
+
gradio>=4.0.0
|
| 24 |
+
nest-asyncio
|
| 25 |
+
aiohttp
|
| 26 |
+
typing-extensions
|
| 27 |
+
|
| 28 |
+
# Updated with correct installation order for A2A
|