Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- README.md +6 -3
- app.py +11 -3
- apps/app_enhanced.py +159 -56
- core/lightweight_a2a_manager.py +293 -0
- core/simulated_a2a_manager.py +136 -0
- requirements_hf.txt +22 -0
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
|
| 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
|
| 36 |
-
- **A2A Mode (
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 81 |
-
|
| 82 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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
|
| 564 |
-
**A2A**: Distributed agents with
|
|
|
|
|
|
|
|
|
|
| 565 |
|
| 566 |
-
*
|
| 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 |
-
#
|
| 862 |
-
test_results.append("===
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 863 |
try:
|
| 864 |
-
|
| 865 |
-
|
| 866 |
-
|
| 867 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 868 |
try:
|
| 869 |
-
import a2a
|
| 870 |
-
test_results.append("β
|
| 871 |
try:
|
| 872 |
-
|
| 873 |
-
test_results.append("β
a2a.types
|
| 874 |
except ImportError as e:
|
| 875 |
-
test_results.append(f"β
|
| 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"β
|
| 886 |
except ImportError as e:
|
| 887 |
-
test_results.append(f"β
|
| 888 |
|
| 889 |
-
#
|
| 890 |
-
|
| 891 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 900 |
-
|
| 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
|
| 916 |
-
test_results.append(f"β Port {port}
|
| 917 |
|
| 918 |
test_results.append(f"\nπ Summary: {available_count}/{len(test_ports)} ports available")
|
| 919 |
|
| 920 |
-
#
|
| 921 |
-
|
| 922 |
-
|
| 923 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 924 |
|
| 925 |
-
|
| 926 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|