Skip to content

Commit 12cd673

Browse files
author
Satvik Golechha
committed
basic version done with fastAPI but has many many bugs
1 parent 1985e7d commit 12cd673

8 files changed

Lines changed: 1790 additions & 72 deletions

File tree

among-agents/amongagents/agent/agent.py

Lines changed: 195 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
44
import random
55
import re
66
from datetime import datetime
7-
from typing import Any
7+
from typing import Any, List, Dict, Tuple
88
import aiohttp
99
import numpy as np
1010
import requests
1111
import asyncio
1212
from amongagents.agent.neutral_prompts import *
1313

14+
# Global dictionary to store futures for human actions, keyed by game_id
15+
human_action_futures: Dict[int, asyncio.Future] = {}
16+
1417
class Agent:
1518
def __init__(self, player):
1619
self.player = player
@@ -245,7 +248,11 @@ async def choose_action(self, timestep):
245248
return action
246249

247250
def choose_observation_location(self, map):
248-
return random.sample(map, 1)[0]
251+
if isinstance(map, (list, tuple)):
252+
return random.choice(map)
253+
else:
254+
# For sets, dicts, or other non-sequence types
255+
return random.choice(list(map))
249256

250257

251258
class RandomAgent(Agent):
@@ -272,69 +279,195 @@ def __init__(self, player, tools=None, game_index=0, agent_config=None, list_of_
272279
self.game_index = game_index
273280
self.summarization = "No thought process has been made."
274281
self.processed_memory = "No memory has been processed."
275-
self.log_path = os.getenv("EXPERIMENT_PATH") + "/agent-logs.json"
276-
self.compact_log_path = os.getenv("EXPERIMENT_PATH") + "/agent-logs-compact.json"
277-
278-
# Initialize global session state if it doesn't exist
279-
if os.getenv("FLASK") == "True":
280-
# Global session state for actions across page refreshes
281-
if "human_actions" not in st.session_state:
282-
st.session_state.human_actions = {}
283-
if "action_history" not in st.session_state:
284-
st.session_state.action_history = {}
285-
if "game_started" not in st.session_state:
286-
st.session_state.game_started = False
282+
self.log_path = os.getenv("EXPERIMENT_PATH", ".") + "/agent-logs.json"
283+
self.compact_log_path = os.getenv("EXPERIMENT_PATH", ".") + "/agent-logs-compact.json"
284+
self.current_available_actions: List[Any] = []
285+
self.current_step = 0
286+
self.max_steps = 50 # Default value, will be updated from game config
287+
self.action_future = None # Store the future as an instance variable
287288

288-
async def choose_action(self, timestep):
289+
def update_max_steps(self, max_steps):
290+
"""Update the max_steps value from the game config."""
291+
self.max_steps = max_steps
292+
293+
async def choose_action(self, timestep: int):
294+
"""
295+
Chooses an action, either via web interface (if FLASK_ENABLED=True)
296+
or command line (if FLASK_ENABLED=False).
297+
"""
298+
use_flask = os.getenv("FLASK_ENABLED", "True") == "True"
289299
all_info = self.player.all_info_prompt()
290-
available_actions = self.player.get_available_actions()
291-
292-
# Log the start of action selection
293-
action_prompt = f"Available actions:\n" + "\n".join([f"{i+1}: {action}" for i, action in enumerate(available_actions)])
294-
full_prompt = {
295-
"All Info": all_info,
296-
"Available Actions": action_prompt
297-
}
298-
# Command line interface
299-
print(f"{str(self.player)}")
300-
print(all_info)
301-
print("Choose an action:")
302-
for i, action in enumerate(available_actions):
303-
print(f"{i+1}: {action}")
300+
self.current_available_actions = self.player.get_available_actions()
301+
self.current_step = timestep
302+
303+
if use_flask:
304+
# --- Web Interface Logic ---
305+
action_prompt = "Waiting for human action via web interface.\nAvailable actions:\n" + "\n".join([f"{i+1}: {str(action)}" for i, action in enumerate(self.current_available_actions)])
306+
full_prompt = {
307+
"All Info": all_info,
308+
"Available Actions": action_prompt,
309+
"Current Step": f"{timestep}/{self.max_steps}",
310+
"Current Player": self.player.name
311+
}
312+
313+
loop = asyncio.get_event_loop()
314+
self.action_future = loop.create_future() # Store in instance variable
304315

305-
stop_triggered = False
306-
valid_input = False
307-
while (not stop_triggered) and (not valid_input):
316+
# Use game_id from the server instead of game_index
317+
# The game_id is passed to the HumanAgent when it's created
318+
game_id = getattr(self, 'game_id', self.game_index)
319+
human_action_futures[game_id] = self.action_future
320+
321+
print(f"[Agent] Created future for game {game_id}")
322+
print(f"[Agent] Available futures: {list(human_action_futures.keys())}")
323+
324+
print(f"\n[Game {game_id}] Human player {self.player.name}'s turn. Waiting for action via web interface...")
325+
print(f"Available actions: {[str(a) for a in self.current_available_actions]}")
326+
308327
try:
309-
action_idx = int(input())
310-
if action_idx == 0:
311-
stop_triggered = True
312-
elif action_idx < 1 or action_idx > len(available_actions):
313-
raise ValueError(f"Invalid input. Please enter a number between 1 and {len(available_actions)}.")
328+
chosen_action_data = await self.action_future
329+
action_idx = chosen_action_data.get("action_index")
330+
action_message = chosen_action_data.get("message")
331+
332+
if action_idx is None or action_idx < 0 or action_idx >= len(self.current_available_actions):
333+
print(f"[Game {game_id}] Invalid action index received: {action_idx}. Defaulting to first action.")
334+
selected_action = self.current_available_actions[0]
314335
else:
315-
valid_input = True
316-
except:
317-
print("Invalid input. Please enter a number.")
318-
continue
336+
selected_action = self.current_available_actions[action_idx]
337+
338+
response_log = f"[Action] {str(selected_action)}"
339+
# Check if action requires a message (e.g., SPEAK)
340+
# Use str() and check for attributes robustly
341+
is_speak_action = False
342+
if hasattr(selected_action, 'name'): # Check attribute exists
343+
is_speak_action = selected_action.name == "SPEAK"
344+
elif "SPEAK" in str(selected_action): # Fallback to string check
345+
is_speak_action = True
346+
347+
if is_speak_action and action_message:
348+
if hasattr(selected_action, 'provide_message'):
349+
selected_action.provide_message(action_message)
350+
elif hasattr(selected_action, 'message'): # Fallback to setting attribute
351+
selected_action.message = action_message
352+
response_log += f" with message: {action_message}"
353+
354+
self.log_interaction(sysprompt="Human Agent (Web)", prompt=full_prompt,
355+
original_response=response_log,
356+
step=timestep)
357+
358+
# Clear the future and actions only after successful action selection
359+
if game_id in human_action_futures:
360+
print(f"[Agent] Deleting future for game {game_id} after successful action")
361+
del human_action_futures[game_id]
362+
self.current_available_actions = []
363+
self.action_future = None
364+
365+
return selected_action
366+
367+
except asyncio.CancelledError:
368+
print(f"[Game {game_id}] Human action cancelled.")
369+
# Clean up on cancellation
370+
if game_id in human_action_futures:
371+
print(f"[Agent] Deleting future for game {game_id} after cancellation")
372+
del human_action_futures[game_id]
373+
self.current_available_actions = []
374+
self.action_future = None
375+
raise
376+
else:
377+
# --- Command Line Interface Logic ---
378+
action_prompt = "Available actions:\n" + "\n".join([f"{i+1}: {str(action)}" for i, action in enumerate(self.current_available_actions)])
379+
full_prompt = {
380+
"All Info": all_info,
381+
"Available Actions": action_prompt
382+
}
383+
384+
print(f"\n--- [Game {self.game_index}] Player: {self.player.name} ({self.player.identity if self.player.identity else 'Role Unknown'}) ---")
385+
print(all_info)
386+
print("\nChoose an action:")
387+
for i, action in enumerate(self.current_available_actions):
388+
print(f"{i+1}: {str(action)}")
389+
print("(Enter 0 to stop game)")
390+
391+
stop_triggered = False
392+
valid_input = False
393+
selected_action = None
394+
action_idx_chosen = -1
395+
396+
while (not stop_triggered) and (not valid_input):
397+
try:
398+
user_input = input("> ")
399+
action_idx_chosen = int(user_input)
400+
if action_idx_chosen == 0:
401+
stop_triggered = True
402+
elif action_idx_chosen < 1 or action_idx_chosen > len(self.current_available_actions):
403+
print(f"Invalid input. Please enter a number between 1 and {len(self.current_available_actions)} (or 0 to stop).")
404+
else:
405+
valid_input = True
406+
except ValueError:
407+
print("Invalid input. Please enter a number.")
408+
continue
409+
410+
if stop_triggered:
411+
print("Stopping game as requested by user.")
412+
# How to signal stop? Raise exception? Return specific value?
413+
# For now, raise an exception that the game loop might catch.
414+
raise KeyboardInterrupt("Game stopped by user via CLI.")
319415

320-
if stop_triggered:
321-
raise ValueError("Game stopped by user.")
416+
selected_action = self.current_available_actions[action_idx_chosen - 1]
417+
response_log = f"[Action] {str(selected_action)}"
418+
419+
# Check if action requires a message using string check
420+
is_speak_action = False
421+
if hasattr(selected_action, 'name'):
422+
is_speak_action = selected_action.name == "SPEAK"
423+
elif "SPEAK" in str(selected_action):
424+
is_speak_action = True
425+
426+
if is_speak_action:
427+
print("Enter your message:")
428+
action_message = input("> ")
429+
if hasattr(selected_action, 'provide_message'):
430+
selected_action.provide_message(action_message)
431+
elif hasattr(selected_action, 'message'):
432+
selected_action.message = action_message
433+
else:
434+
print("Warning: Could not set message for SPEAK action.")
435+
response_log += f" with message: {action_message}"
322436

323-
selected_action = available_actions[action_idx - 1]
437+
self.log_interaction(sysprompt="Human Agent (CLI)", prompt=full_prompt,
438+
original_response=response_log,
439+
step=timestep)
324440

325-
if selected_action.name == "SPEAK":
326-
print("Enter your response:")
327-
action_message = input()
328-
selected_action.provide_message(action_message)
329-
self.log_interaction(sysprompt="Human Agent", prompt=full_prompt,
330-
original_response=f"[Action] {selected_action} with message: {action_message}",
331-
step=timestep)
332-
else:
333-
self.log_interaction(sysprompt="Human Agent", prompt=full_prompt,
334-
original_response=f"[Action] {selected_action}",
335-
step=timestep)
336-
337-
return selected_action
441+
self.current_available_actions = [] # Clear actions after use
442+
return selected_action # Return synchronously within async def
443+
444+
def get_current_state_for_web(self) -> Dict[str, Any]:
445+
"""
446+
Returns the necessary state for the web UI when it's the human's turn.
447+
Uses string checks for action properties.
448+
"""
449+
available_actions_web = []
450+
for action in self.current_available_actions:
451+
action_str = str(action)
452+
requires_message = False
453+
if hasattr(action, 'name'):
454+
requires_message = action.name == "SPEAK"
455+
elif "SPEAK" in action_str:
456+
requires_message = True
457+
458+
available_actions_web.append({
459+
"name": action_str,
460+
"requires_message": requires_message
461+
})
462+
463+
return {
464+
"is_human_turn": True,
465+
"player_name": self.player.name,
466+
"player_info": self.player.all_info_prompt(),
467+
"available_actions": available_actions_web,
468+
"current_step": f"{self.current_step}/{self.max_steps}",
469+
"current_player": self.player.name
470+
}
338471

339472
def respond(self, message):
340473
print(message)
@@ -355,6 +488,11 @@ def choose_observation_location(self, map):
355488

356489
def log_interaction(self, sysprompt, prompt, original_response, step):
357490
"""Log human player interactions similar to LLMAgent"""
491+
# Ensure log path exists
492+
log_dir = os.path.dirname(self.log_path)
493+
if log_dir and not os.path.exists(log_dir):
494+
os.makedirs(log_dir, exist_ok=True)
495+
358496
interaction = {
359497
'game_index': 'Game ' + str(self.game_index),
360498
'step': step,

among-agents/amongagents/envs/game.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ def __init__(
5757
self.UI = UI
5858
# config
5959
self.include_human = include_human
60+
self.is_human_turn = False
61+
self.human_index = None
6062
self.test = test
6163
self.personality = personality
6264
self.agent_config = agent_config
@@ -148,8 +150,16 @@ def initialize_agents(self):
148150
self.agents = []
149151
for i, player in enumerate(self.players):
150152
if self.include_human and i == random_idx:
151-
self.agents.append(HumanAgent(player))
153+
# Create HumanAgent with game_id set to game_index
154+
human_agent = HumanAgent(player, game_index=self.game_index)
155+
# Set the game_id attribute to match the game_index
156+
human_agent.game_id = self.game_index
157+
self.agents.append(human_agent)
158+
self.human_index = i
152159
print(f"{i} Initializing player {player.name} with identity {player.identity} and LLM choice {self.agents[-1].model}")
160+
# Update max_steps for human agent
161+
if hasattr(self.agents[-1], 'update_max_steps'):
162+
self.agents[-1].update_max_steps(self.game_config.get("max_timesteps", 50))
153163
else:
154164
self.agents.append(agent_dict[self.agent_config[player.identity]](player))
155165
print(f"{i} Initializing player {player.name} with identity {player.identity} and LLM choice {self.agents[-1].model}")
@@ -239,12 +249,14 @@ async def agent_step(self, agent):
239249
if agent.player.identity == "Impostor" and agent.player.kill_cooldown > 0:
240250
agent.player.kill_cooldown -= 1
241251

252+
# Set current player for UI updates
253+
self.current_player = agent.player.name
254+
242255
# interview
243256
if self.interviewer is not None:
244257
await self.interviewer.auto_question(self, agent)
245258

246259
# choose action
247-
248260
action = await agent.choose_action(self.timestep)
249261
observation_location = ""
250262
if action.name == "ViewMonitor":
@@ -274,6 +286,10 @@ async def game_step(self):
274286

275287
async def task_phase_step(self):
276288
for agent in self.agents:
289+
if 'homosapiens' in agent.model:
290+
self.is_human_turn = True
291+
else:
292+
self.is_human_turn = False
277293
await self.agent_step(agent)
278294
if self.current_phase == "meeting":
279295
break
@@ -289,6 +305,10 @@ async def meeting_phase(self):
289305
for round in range(self.game_config["discussion_rounds"]):
290306
print("Discussion round", round)
291307
for agent in self.agents:
308+
if 'homosapiens' in agent.model:
309+
self.is_human_turn = True
310+
else:
311+
self.is_human_turn = False
292312
await self.agent_step(agent)
293313
self.discussion_rounds_left -= 1
294314
# Voting

0 commit comments

Comments
 (0)