alexmec commited on
Commit
4686bc9
Β·
verified Β·
1 Parent(s): df94a77

Upload folder using huggingface_hub

Browse files
README.md CHANGED
@@ -20,7 +20,7 @@ Experience AI agents with distinct personalities competing in a fantasy football
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
@@ -32,8 +32,11 @@ Experience AI agents with distinct personalities competing in a fantasy football
32
 
33
  ## About Communication Modes
34
 
35
- - **Basic Multiagent (Recommended)**: Works perfectly on HF Spaces! All agents run in a single process with fast, reliable communication.
36
- - **A2A Mode (Experimental)**: Distributed agents on separate servers. Due to dependency issues with `a2a-sdk` on HF Spaces, this mode may not work. Basic Multiagent provides the same great experience!
 
 
 
37
 
38
  ## About
39
 
 
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 with automatic fallback (Full β†’ Lightweight β†’ Simulated)
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
 
32
 
33
  ## About Communication Modes
34
 
35
+ - **Basic Multiagent (Recommended)**: Works perfectly everywhere! All agents run in a single process with fast, reliable communication.
36
+ - **A2A Mode (Adaptive)**: Automatically selects the best distributed mode:
37
+ - **Full A2A**: Complete protocol with gRPC (when available)
38
+ - **Lightweight A2A**: HTTP-only servers (works on HF Spaces!)
39
+ - **Simulated A2A**: Mock distributed experience (fallback)
40
 
41
  ## About
42
 
app.py CHANGED
@@ -17,10 +17,18 @@ if os.getenv("SPACE_ID"):
17
  # Test the specific imports that fail
18
  import a2a # This is what usually fails
19
  from any_agent.serving import A2AServingConfig
20
- print("βœ… Surprisingly, A2A dependencies are available! You can try A2A mode.")
21
  except ImportError as e:
22
- print(f"⚠️ A2A dependencies not available: {e}")
23
- print("βœ… No problem! Basic Multiagent mode works perfectly and is recommended.")
 
 
 
 
 
 
 
 
24
 
25
  # Import and run the enhanced app
26
  from apps.app_enhanced import main
 
17
  # Test the specific imports that fail
18
  import a2a # This is what usually fails
19
  from any_agent.serving import A2AServingConfig
20
+ print("βœ… Surprisingly, full A2A dependencies are available! You can try A2A mode.")
21
  except ImportError as e:
22
+ print(f"⚠️ Full A2A dependencies not available: {e}")
23
+ # Check if lightweight A2A can work
24
+ try:
25
+ import httpx
26
+ import fastapi
27
+ import uvicorn
28
+ print("βœ… Lightweight A2A dependencies available! A2A mode will work using HTTP-only.")
29
+ os.environ["A2A_MODE"] = "lightweight"
30
+ except ImportError:
31
+ print("βœ… No problem! Basic Multiagent mode works perfectly and is recommended.")
32
 
33
  # Import and run the enhanced app
34
  from apps.app_enhanced import main
apps/app_enhanced.py CHANGED
@@ -76,10 +76,28 @@ class EnhancedFantasyDraftApp:
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:
@@ -92,7 +110,15 @@ class EnhancedFantasyDraftApp:
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)}"
@@ -146,9 +172,18 @@ class EnhancedFantasyDraftApp:
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
 
@@ -560,10 +595,13 @@ def create_gradio_interface():
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
 
@@ -856,74 +894,139 @@ def create_gradio_interface():
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
- # Test the critical a2a module first
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
868
  try:
869
- import a2a
870
- test_results.append("βœ… a2a module found (from a2a-sdk)")
871
  try:
872
- import a2a.types
873
- test_results.append("βœ… a2a.types imported")
874
  except ImportError as e:
875
- test_results.append(f"❌ a2a.types failed: {e}")
876
- except ImportError:
877
- test_results.append("❌ a2a module NOT found - this is why A2A fails!")
878
- test_results.append(" The a2a-sdk package should provide the 'a2a' module")
879
-
880
- try:
881
- from any_agent.serving import A2AServingConfig
882
- from any_agent.tools import a2a_tool_async
883
- test_results.append("βœ… A2A components imported successfully!")
884
  except ImportError as e:
885
- test_results.append(f"❌ A2A components import failed: {e}")
886
  except ImportError as e:
887
- test_results.append(f"❌ any_agent not found: {e}")
888
 
889
- # Test ports
890
- test_results.append("\n=== Testing Port Availability ===")
891
- test_ports = [5001, 5002, 5003, 8001, 8002, 8080, 8888]
 
 
 
 
 
 
 
 
892
  available_count = 0
893
 
894
  for port in test_ports:
895
  try:
896
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
897
  sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
898
-
899
- # Try different addresses
900
- bound = False
901
- for addr in ['127.0.0.1', 'localhost', '0.0.0.0']:
902
- try:
903
- sock.bind((addr, port))
904
- test_results.append(f"βœ… Port {port} available on {addr}")
905
- bound = True
906
- available_count += 1
907
- break
908
- except:
909
- continue
910
-
911
- if not bound:
912
- test_results.append(f"❌ Port {port} not available")
913
-
914
  sock.close()
915
- except Exception as e:
916
- test_results.append(f"❌ Port {port} error: {e}")
917
 
918
  test_results.append(f"\nπŸ“Š Summary: {available_count}/{len(test_ports)} ports available")
919
 
920
- # Environment info
921
- test_results.append("\n=== Environment Info ===")
922
- test_results.append(f"SPACE_ID: {os.getenv('SPACE_ID', 'Not on HF Spaces')}")
923
- test_results.append(f"Python: {sys.version.split()[0]}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
924
 
925
- if available_count >= 5:
926
- test_results.append("\nβœ… A2A might work! Try selecting A2A mode.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
927
  else:
928
  test_results.append("\n❌ Not enough ports available. Use Basic Multiagent mode.")
929
 
 
76
  try:
77
  global DynamicA2AAgentManager, cleanup_session
78
  from core.dynamic_a2a_manager import DynamicA2AAgentManager, cleanup_session
79
+ self.real_a2a = True
80
+ self.a2a_type = "full"
81
  except ImportError as e:
82
+ # Try lightweight A2A for HF Spaces (no grpcio)
83
+ try:
84
+ from core.lightweight_a2a_manager import LightweightA2AAgentManager, cleanup_session
85
+ DynamicA2AAgentManager = LightweightA2AAgentManager
86
+ self.real_a2a = True
87
+ self.a2a_type = "lightweight"
88
+ print("Using lightweight A2A mode (HTTP-only, no grpcio)")
89
+ except ImportError as e2:
90
+ # Fall back to simulated A2A
91
+ try:
92
+ from core.simulated_a2a_manager import SimulatedA2AAgentManager, cleanup_session
93
+ DynamicA2AAgentManager = SimulatedA2AAgentManager
94
+ self.real_a2a = False
95
+ self.a2a_type = "simulated"
96
+ print("Using simulated A2A mode (real A2A not available)")
97
+ except ImportError as e3:
98
+ self.a2a_status = f"❌ A2A mode not available: {str(e)}. Please use Basic Multiagent mode."
99
+ self.use_real_a2a = False
100
+ return self.a2a_status
101
 
102
  # Generate unique session ID if needed
103
  if not self.session_id:
 
110
  try:
111
  await self.a2a_manager.start_agents()
112
  ports = self.a2a_manager.allocated_ports
113
+ if hasattr(self, 'a2a_type'):
114
+ if self.a2a_type == "full":
115
+ self.a2a_status = f"βœ… Full A2A Mode Active (Session: {self.session_id}, Ports: {ports[0]}-{ports[-1]})"
116
+ elif self.a2a_type == "lightweight":
117
+ self.a2a_status = f"βœ… Lightweight A2A Mode Active (Session: {self.session_id}, HTTP Ports: {ports[0]}-{ports[-1]})"
118
+ else: # simulated
119
+ self.a2a_status = f"βœ… Simulated A2A Mode Active (Session: {self.session_id}, Mock Ports: {ports[0]}-{ports[-1]})"
120
+ else:
121
+ self.a2a_status = f"βœ… A2A Mode Active (Session: {self.session_id}, Ports: {ports[0]}-{ports[-1]})"
122
  except RuntimeError as e:
123
  # Failed to allocate ports or start agents
124
  self.a2a_status = f"❌ Failed to start A2A: {str(e)}"
 
172
  self.draft_output = "# 🏈 Mock Draft with A2A Communication\n\n"
173
 
174
  # Welcome message
175
+ if hasattr(self, 'a2a_type'):
176
+ if self.a2a_type == "full":
177
+ welcome_msg = "Welcome to the A2A-powered draft! Each agent is running on its own server with full A2A protocol."
178
+ elif self.a2a_type == "lightweight":
179
+ welcome_msg = "Welcome to the lightweight A2A draft! Each agent runs on its own HTTP server (no grpcio needed)."
180
+ else: # simulated
181
+ welcome_msg = "Welcome to the simulated A2A draft! Agents communicate using mock HTTP calls."
182
+ else:
183
+ welcome_msg = "Welcome to the A2A-powered draft! Each agent is running on its own server."
184
  self.draft_output += format_agent_message(
185
  "commissioner", "ALL",
186
+ welcome_msg
187
  )
188
  yield self.draft_output
189
 
 
595
  )
596
  mode_info = gr.Markdown(
597
  """
598
+ **Basic Multiagent** (Recommended): Fast, single-process execution (βœ… Multi-user safe)
599
+ **A2A**: Distributed agents mode with automatic fallback:
600
+ - Full A2A: gRPC + HTTP protocol (requires grpcio)
601
+ - Lightweight A2A: HTTP-only (works on HF Spaces!)
602
+ - Simulated A2A: Mock HTTP calls (fallback)
603
 
604
+ *A2A mode will automatically select the best available option.*
605
  """
606
  )
607
 
 
894
  def test_a2a_functionality():
895
  """Test A2A dependencies and port availability."""
896
  import socket
897
+ import subprocess
898
+ import importlib.util
899
+ import site
900
+
901
  test_results = []
902
 
903
+ # 1. Python Environment
904
+ test_results.append("=== Python Environment ===")
905
+ test_results.append(f"Python: {sys.version.split()[0]}")
906
+ test_results.append(f"Platform: {sys.platform}")
907
+ test_results.append(f"SPACE_ID: {os.getenv('SPACE_ID', 'Not on HF Spaces')}")
908
+
909
+ # 2. Check a2a-sdk installation
910
+ test_results.append("\n=== Package Installation ===")
911
  try:
912
+ result = subprocess.run([sys.executable, "-m", "pip", "show", "a2a-sdk"],
913
+ capture_output=True, text=True, timeout=5)
914
+ if result.returncode == 0:
915
+ version_line = [line for line in result.stdout.split('\n') if line.startswith('Version:')]
916
+ location_line = [line for line in result.stdout.split('\n') if line.startswith('Location:')]
917
+ test_results.append(f"βœ… a2a-sdk installed: {version_line[0] if version_line else 'Unknown version'}")
918
+ if location_line:
919
+ test_results.append(f" {location_line[0]}")
920
+ else:
921
+ test_results.append("❌ a2a-sdk NOT installed according to pip")
922
+ except Exception as e:
923
+ test_results.append(f"❌ Error checking pip: {e}")
924
+
925
+ # 3. Module search
926
+ test_results.append("\n=== Module Search ===")
927
+ a2a_spec = importlib.util.find_spec("a2a")
928
+ if a2a_spec:
929
+ test_results.append(f"βœ… a2a module found at: {a2a_spec.origin}")
930
+ else:
931
+ test_results.append("❌ a2a module NOT found by importlib")
932
+ # Manual search
933
+ for path in site.getsitepackages():
934
+ if os.path.exists(path):
935
+ a2a_path = os.path.join(path, "a2a")
936
+ if os.path.exists(a2a_path):
937
+ test_results.append(f" Found a2a directory at: {a2a_path}")
938
+
939
+ # 4. Import tests
940
+ test_results.append("\n=== Import Tests ===")
941
+
942
+ # Basic a2a import
943
+ try:
944
+ import a2a
945
+ test_results.append(f"βœ… import a2a: Success")
946
  try:
947
+ import a2a.types
948
+ test_results.append("βœ… import a2a.types: Success")
949
  try:
950
+ from a2a.types import AgentSkill
951
+ test_results.append("βœ… from a2a.types import AgentSkill: Success")
952
  except ImportError as e:
953
+ test_results.append(f"❌ AgentSkill import: {e}")
 
 
 
 
 
 
 
 
954
  except ImportError as e:
955
+ test_results.append(f"❌ a2a.types import: {e}")
956
  except ImportError as e:
957
+ test_results.append(f"❌ a2a import failed: {e}")
958
 
959
+ # any_agent A2A imports
960
+ try:
961
+ from any_agent.serving import A2AServingConfig
962
+ from any_agent.tools import a2a_tool_async
963
+ test_results.append("βœ… any_agent A2A components: Success!")
964
+ except ImportError as e:
965
+ test_results.append(f"❌ any_agent A2A import: {e}")
966
+
967
+ # 5. Port availability
968
+ test_results.append("\n=== Port Availability ===")
969
+ test_ports = [5001, 5002, 5003, 5004, 5005, 5006]
970
  available_count = 0
971
 
972
  for port in test_ports:
973
  try:
974
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
975
  sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
976
+ sock.bind(('127.0.0.1', port))
977
+ test_results.append(f"βœ… Port {port} available")
978
+ available_count += 1
 
 
 
 
 
 
 
 
 
 
 
 
 
979
  sock.close()
980
+ except Exception:
981
+ test_results.append(f"❌ Port {port} not available")
982
 
983
  test_results.append(f"\nπŸ“Š Summary: {available_count}/{len(test_ports)} ports available")
984
 
985
+ # 6. Try fixing a2a if needed
986
+ if "❌ a2a import failed" in "\n".join(test_results):
987
+ test_results.append("\n=== Attempting Fix ===")
988
+ try:
989
+ # Try reinstalling without deps
990
+ result = subprocess.run(
991
+ [sys.executable, "-m", "pip", "install", "a2a-sdk", "--no-deps", "--force-reinstall"],
992
+ capture_output=True, text=True, timeout=10
993
+ )
994
+ if result.returncode == 0:
995
+ test_results.append("βœ… Reinstalled a2a-sdk")
996
+ # Test import again
997
+ try:
998
+ import importlib
999
+ if 'a2a' in sys.modules:
1000
+ del sys.modules['a2a']
1001
+ import a2a
1002
+ test_results.append("βœ… Import after reinstall: Success!")
1003
+ except Exception as e:
1004
+ test_results.append(f"❌ Import after reinstall: {e}")
1005
+ else:
1006
+ test_results.append(f"❌ Reinstall failed: {result.stderr[:200]}")
1007
+ except Exception as e:
1008
+ test_results.append(f"❌ Fix attempt error: {e}")
1009
 
1010
+ # Test lightweight A2A requirements
1011
+ test_results.append("\n=== Lightweight A2A Test ===")
1012
+ try:
1013
+ import httpx
1014
+ import fastapi
1015
+ import uvicorn
1016
+ test_results.append("βœ… HTTP dependencies available (httpx, fastapi, uvicorn)")
1017
+ lightweight_ready = True
1018
+ except ImportError as e:
1019
+ test_results.append(f"❌ Missing HTTP dependencies: {e}")
1020
+ lightweight_ready = False
1021
+
1022
+ # Final verdict
1023
+ if available_count >= 6:
1024
+ if "βœ… any_agent A2A components: Success!" in "\n".join(test_results):
1025
+ test_results.append("\nβœ… Full A2A available! A2A mode will use the complete protocol.")
1026
+ elif lightweight_ready:
1027
+ test_results.append("\nβœ… Lightweight A2A available! A2A mode will use HTTP-only servers.")
1028
+ else:
1029
+ test_results.append("\n⚠️ Only Simulated A2A available. A2A mode will use mock communication.")
1030
  else:
1031
  test_results.append("\n❌ Not enough ports available. Use Basic Multiagent mode.")
1032
 
core/lightweight_a2a_manager.py ADDED
@@ -0,0 +1,293 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Lightweight A2A Manager for HF Spaces - Real distributed agents using HTTP only.
3
+ Works without grpcio dependencies by using httpx and FastAPI directly.
4
+ """
5
+
6
+ import asyncio
7
+ import httpx
8
+ import uvicorn
9
+ from fastapi import FastAPI, HTTPException
10
+ from pydantic import BaseModel
11
+ from typing import Optional, List, Dict, Any
12
+ import multiprocessing
13
+ import time
14
+ import socket
15
+ from contextlib import closing
16
+ from core.agent import FantasyDraftAgent
17
+ from core.constants import AGENT_CONFIGS
18
+
19
+
20
+ # Pydantic models for API
21
+ class PickRequest(BaseModel):
22
+ available_players: List[str]
23
+ previous_picks: List[str]
24
+ round_num: int
25
+
26
+
27
+ class PickResponse(BaseModel):
28
+ type: str = "pick"
29
+ player_name: str
30
+ reasoning: str
31
+ trash_talk: Optional[str] = None
32
+
33
+
34
+ class CommentRequest(BaseModel):
35
+ picking_team: int
36
+ player: str
37
+ round_num: int
38
+
39
+
40
+ class CommentResponse(BaseModel):
41
+ comment: Optional[str]
42
+
43
+
44
+ class LightweightA2AAgent:
45
+ """Single agent server that runs in its own process"""
46
+
47
+ def __init__(self, team_num: int, port: int):
48
+ self.team_num = team_num
49
+ self.port = port
50
+ self.app = FastAPI()
51
+ self.agent = None
52
+ self._setup_routes()
53
+
54
+ def _setup_routes(self):
55
+ @self.app.on_event("startup")
56
+ async def startup():
57
+ config = AGENT_CONFIGS[self.team_num]
58
+ self.agent = FantasyDraftAgent(
59
+ team_name=config["team_name"],
60
+ strategy=config["strategy"],
61
+ traits=config["traits"],
62
+ rival_teams=config.get("rival_teams", [])
63
+ )
64
+ print(f"βœ… Agent {self.team_num} initialized on port {self.port}")
65
+
66
+ @self.app.get("/health")
67
+ async def health():
68
+ return {"status": "healthy", "team": self.team_num}
69
+
70
+ @self.app.post("/pick", response_model=PickResponse)
71
+ async def make_pick(request: PickRequest):
72
+ if not self.agent:
73
+ raise HTTPException(status_code=500, detail="Agent not initialized")
74
+
75
+ # Update agent's picks
76
+ self.agent.picks = request.previous_picks.copy()
77
+
78
+ # Make pick
79
+ player = self.agent.make_pick(request.available_players, request.round_num)
80
+ reasoning = self.agent.explain_pick(player, request.round_num)
81
+
82
+ # Optional trash talk
83
+ trash_talk = None
84
+ import random
85
+ if random.random() < 0.3:
86
+ trash_talk = random.choice([
87
+ f"Can't believe {player} was still available!",
88
+ f"{player} is going to be huge this year!",
89
+ "Building a championship team here.",
90
+ "Y'all sleeping on my picks!"
91
+ ])
92
+
93
+ return PickResponse(
94
+ player_name=player,
95
+ reasoning=reasoning,
96
+ trash_talk=trash_talk
97
+ )
98
+
99
+ @self.app.post("/comment", response_model=CommentResponse)
100
+ async def make_comment(request: CommentRequest):
101
+ if not self.agent:
102
+ raise HTTPException(status_code=500, detail="Agent not initialized")
103
+
104
+ # Higher chance of comment for rivals
105
+ import random
106
+ if request.picking_team in self.agent.rival_teams:
107
+ if random.random() < 0.8:
108
+ comment = self.agent.react_to_pick(
109
+ f"Team {request.picking_team}",
110
+ request.player,
111
+ request.round_num
112
+ )
113
+ return CommentResponse(comment=comment)
114
+ else:
115
+ if random.random() < 0.3:
116
+ comment = self.agent.react_to_pick(
117
+ f"Team {request.picking_team}",
118
+ request.player,
119
+ request.round_num
120
+ )
121
+ return CommentResponse(comment=comment)
122
+
123
+ return CommentResponse(comment=None)
124
+
125
+ def run(self):
126
+ """Run the agent server"""
127
+ uvicorn.run(self.app, host="127.0.0.1", port=self.port, log_level="error")
128
+
129
+
130
+ def _run_agent_server(team_num: int, port: int):
131
+ """Function to run in separate process"""
132
+ agent = LightweightA2AAgent(team_num, port)
133
+ agent.run()
134
+
135
+
136
+ class LightweightA2AAgentManager:
137
+ """
138
+ Manages lightweight A2A agents using only HTTP (no grpcio).
139
+ Each agent runs as a FastAPI server in a separate process.
140
+ """
141
+
142
+ def __init__(self, session_id: str = "lightweight"):
143
+ self.session_id = session_id
144
+ self.processes: Dict[int, multiprocessing.Process] = {}
145
+ self.allocated_ports: List[int] = []
146
+ self.base_port = 5001
147
+ self.running = False
148
+ self.max_comments_per_pick = 2
149
+ self.client = None
150
+
151
+ def _find_free_port(self, start_port: int) -> int:
152
+ """Find a free port starting from start_port"""
153
+ for port in range(start_port, start_port + 100):
154
+ with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock:
155
+ try:
156
+ sock.bind(('127.0.0.1', port))
157
+ return port
158
+ except OSError:
159
+ continue
160
+ raise RuntimeError(f"No free ports found starting from {start_port}")
161
+
162
+ async def start_agents(self):
163
+ """Start all agent servers"""
164
+ print(f"πŸš€ Starting lightweight A2A agents for session {self.session_id}...")
165
+
166
+ # Initialize HTTP client
167
+ self.client = httpx.AsyncClient(timeout=10.0)
168
+
169
+ # Allocate ports and start processes
170
+ for team_num in [1, 2, 3, 5, 6]:
171
+ port = self._find_free_port(self.base_port + team_num - 1)
172
+ self.allocated_ports.append(port)
173
+
174
+ # Start agent process
175
+ process = multiprocessing.Process(
176
+ target=_run_agent_server,
177
+ args=(team_num, port)
178
+ )
179
+ process.start()
180
+ self.processes[team_num] = process
181
+
182
+ print(f"⏳ Starting agent {team_num} on port {port}...")
183
+
184
+ # Wait for all agents to be ready
185
+ await self._wait_for_agents()
186
+
187
+ self.running = True
188
+ print(f"βœ… All lightweight A2A agents ready!")
189
+
190
+ async def _wait_for_agents(self):
191
+ """Wait for all agents to respond to health checks"""
192
+ max_retries = 30
193
+ for team_num, port in zip([1, 2, 3, 5, 6], self.allocated_ports):
194
+ url = f"http://127.0.0.1:{port}/health"
195
+ for i in range(max_retries):
196
+ try:
197
+ response = await self.client.get(url)
198
+ if response.status_code == 200:
199
+ print(f"βœ… Agent {team_num} ready on port {port}")
200
+ break
201
+ except:
202
+ pass
203
+ await asyncio.sleep(0.5)
204
+ else:
205
+ raise RuntimeError(f"Agent {team_num} failed to start on port {port}")
206
+
207
+ async def get_pick(self, team_num: int, available_players: List[str],
208
+ previous_picks: List[str], round_num: int) -> Optional[PickResponse]:
209
+ """Get pick from agent via HTTP"""
210
+ if team_num not in self.processes:
211
+ return None
212
+
213
+ port_index = [1, 2, 3, 5, 6].index(team_num)
214
+ port = self.allocated_ports[port_index]
215
+
216
+ try:
217
+ response = await self.client.post(
218
+ f"http://127.0.0.1:{port}/pick",
219
+ json={
220
+ "available_players": available_players,
221
+ "previous_picks": previous_picks,
222
+ "round_num": round_num
223
+ }
224
+ )
225
+
226
+ if response.status_code == 200:
227
+ data = response.json()
228
+ return PickResponse(**data)
229
+ else:
230
+ print(f"Error from agent {team_num}: {response.status_code}")
231
+ return None
232
+ except Exception as e:
233
+ print(f"Failed to get pick from agent {team_num}: {e}")
234
+ return None
235
+
236
+ async def get_comment(self, commenting_team: int, picking_team: int,
237
+ player: str, round_num: int) -> Optional[str]:
238
+ """Get comment from agent via HTTP"""
239
+ if commenting_team not in self.processes:
240
+ return None
241
+
242
+ port_index = [1, 2, 3, 5, 6].index(commenting_team)
243
+ port = self.allocated_ports[port_index]
244
+
245
+ try:
246
+ response = await self.client.post(
247
+ f"http://127.0.0.1:{port}/comment",
248
+ json={
249
+ "picking_team": picking_team,
250
+ "player": player,
251
+ "round_num": round_num
252
+ }
253
+ )
254
+
255
+ if response.status_code == 200:
256
+ data = response.json()
257
+ return data.get("comment")
258
+ else:
259
+ return None
260
+ except Exception as e:
261
+ print(f"Failed to get comment from agent {commenting_team}: {e}")
262
+ return None
263
+
264
+ async def cleanup(self):
265
+ """Stop all agent servers"""
266
+ if self.running:
267
+ print(f"πŸ›‘ Stopping lightweight A2A agents for session {self.session_id}...")
268
+
269
+ # Close HTTP client
270
+ if self.client:
271
+ await self.client.aclose()
272
+
273
+ # Terminate all processes
274
+ for team_num, process in self.processes.items():
275
+ if process.is_alive():
276
+ process.terminate()
277
+ process.join(timeout=2)
278
+ if process.is_alive():
279
+ process.kill()
280
+ process.join()
281
+ print(f"βœ… Agent {team_num} stopped")
282
+
283
+ self.processes.clear()
284
+ self.allocated_ports.clear()
285
+ self.running = False
286
+ print(f"βœ… Lightweight A2A session {self.session_id} cleaned up")
287
+
288
+
289
+ # Provide same cleanup function interface
290
+ async def cleanup_session(manager: LightweightA2AAgentManager):
291
+ """Clean up a lightweight A2A session"""
292
+ if manager:
293
+ await manager.cleanup()
core/simulated_a2a_manager.py ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Simulated A2A Manager for environments where real A2A won't work (like HF Spaces).
3
+ Provides the same interface and experience as real A2A but uses in-process communication.
4
+ """
5
+
6
+ import asyncio
7
+ import time
8
+ import random
9
+ from typing import Optional, List, Dict
10
+ from core.agent import FantasyDraftAgent
11
+ from core.constants import AGENT_CONFIGS
12
+
13
+
14
+ class SimulatedA2AResponse:
15
+ """Simulated response that looks like an A2A response"""
16
+ def __init__(self, player_name: str, reasoning: str, trash_talk: Optional[str] = None):
17
+ self.type = "pick"
18
+ self.player_name = player_name
19
+ self.reasoning = reasoning
20
+ self.trash_talk = trash_talk
21
+
22
+
23
+ class SimulatedA2AAgentManager:
24
+ """
25
+ Simulates A2A behavior without actual HTTP servers.
26
+ Provides the same interface as DynamicA2AAgentManager but runs in-process.
27
+ """
28
+
29
+ def __init__(self, session_id: str = "sim"):
30
+ self.session_id = session_id
31
+ self.agents: Dict[int, FantasyDraftAgent] = {}
32
+ self.running = False
33
+ self.max_comments_per_pick = 2
34
+ # Simulate port allocation
35
+ self.allocated_ports = [5001, 5002, 5003, 5004, 5005, 5006]
36
+
37
+ async def start_agents(self):
38
+ """Initialize agents (simulated startup)"""
39
+ print(f"πŸš€ Starting simulated A2A agents for session {self.session_id}...")
40
+
41
+ # Simulate startup delay
42
+ await asyncio.sleep(0.5)
43
+
44
+ # Create agents
45
+ for team_num in [1, 2, 3, 5, 6]:
46
+ config = AGENT_CONFIGS[team_num]
47
+ self.agents[team_num] = FantasyDraftAgent(
48
+ team_name=config["team_name"],
49
+ strategy=config["strategy"],
50
+ traits=config["traits"],
51
+ rival_teams=config.get("rival_teams", [])
52
+ )
53
+
54
+ # Simulate server startup messages
55
+ for team_num, port in zip([1, 2, 3, 5, 6], self.allocated_ports):
56
+ await asyncio.sleep(0.1)
57
+ print(f"βœ… Agent {team_num} ready on simulated port {port}")
58
+
59
+ self.running = True
60
+ print(f"βœ… All simulated A2A agents ready!")
61
+
62
+ async def get_pick(self, team_num: int, available_players: List[str],
63
+ previous_picks: List[str], round_num: int) -> Optional[SimulatedA2AResponse]:
64
+ """Get pick from agent (simulated A2A call)"""
65
+ if team_num not in self.agents:
66
+ return None
67
+
68
+ # Simulate network delay
69
+ await asyncio.sleep(random.uniform(0.2, 0.5))
70
+
71
+ agent = self.agents[team_num]
72
+
73
+ # Get pick decision
74
+ player = agent.make_pick(available_players, round_num)
75
+
76
+ # Get reasoning
77
+ reasoning = agent.explain_pick(player, round_num)
78
+
79
+ # Maybe add trash talk
80
+ trash_talk = None
81
+ if random.random() < 0.3: # 30% chance of trash talk
82
+ responses = [
83
+ f"Easy choice. Can't believe {player} was still available!",
84
+ f"You all sleeping? {player} is a steal here!",
85
+ f"Building a championship team, one pick at a time.",
86
+ "This is how you draft, take notes everyone."
87
+ ]
88
+ trash_talk = random.choice(responses)
89
+
90
+ return SimulatedA2AResponse(player, reasoning, trash_talk)
91
+
92
+ async def get_comment(self, commenting_team: int, picking_team: int,
93
+ player: str, round_num: int) -> Optional[str]:
94
+ """Get comment from another agent (simulated A2A call)"""
95
+ if commenting_team not in self.agents:
96
+ return None
97
+
98
+ # Simulate network delay
99
+ await asyncio.sleep(random.uniform(0.1, 0.3))
100
+
101
+ agent = self.agents[commenting_team]
102
+
103
+ # Higher chance of comment if they're rivals
104
+ if picking_team in agent.rival_teams:
105
+ if random.random() < 0.8: # 80% chance for rivals
106
+ return agent.react_to_pick(
107
+ self.agents[picking_team].team_name,
108
+ player,
109
+ round_num
110
+ )
111
+ else:
112
+ if random.random() < 0.3: # 30% chance for non-rivals
113
+ return agent.react_to_pick(
114
+ self.agents[picking_team].team_name,
115
+ player,
116
+ round_num
117
+ )
118
+
119
+ return None
120
+
121
+ async def cleanup(self):
122
+ """Cleanup simulated agents"""
123
+ if self.running:
124
+ print(f"πŸ›‘ Stopping simulated A2A agents for session {self.session_id}...")
125
+ # Simulate shutdown
126
+ await asyncio.sleep(0.2)
127
+ self.agents.clear()
128
+ self.running = False
129
+ print(f"βœ… Simulated A2A session {self.session_id} cleaned up")
130
+
131
+
132
+ # Provide same cleanup function interface
133
+ async def cleanup_session(manager: SimulatedA2AAgentManager):
134
+ """Clean up a simulated A2A session"""
135
+ if manager:
136
+ await manager.cleanup()
requirements_hf.txt ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Special requirements for Hugging Face Spaces
2
+ # Avoids grpcio dependencies that won't install properly
3
+
4
+ # Install any-agent WITHOUT a2a extras
5
+ any-agent[openai]>=0.21.0
6
+
7
+ # Direct HTTP dependencies
8
+ httpx>=0.24.0
9
+ fastapi>=0.100.0
10
+ uvicorn>=0.22.0
11
+ sse-starlette>=1.6.0
12
+
13
+ # App dependencies
14
+ python-dotenv
15
+ pydantic>=2.0.0
16
+ gradio>=4.0.0
17
+ nest-asyncio
18
+ aiohttp
19
+ typing-extensions
20
+
21
+ # Note: We'll use httpx directly for A2A-like communication
22
+ # without the heavyweight grpcio dependencies