alexmec commited on
Commit
a27a4ef
·
verified ·
1 Parent(s): 1233b3f

Upload folder using huggingface_hub

Browse files
.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: Fantasy Draft Demo
3
- emoji: 🐨
4
- colorFrom: purple
5
  colorTo: green
6
  sdk: gradio
7
- sdk_version: 5.35.0
8
  app_file: app.py
9
  pinned: false
 
 
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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