Spaces:
Runtime error
Runtime error
import re | |
import json | |
import gradio as gr | |
from glob import glob | |
from time import sleep | |
from chatarena.arena import Arena, TooManyInvalidActions | |
from chatarena.backends import BACKEND_REGISTRY | |
from chatarena.backends.human import HumanBackendError | |
from chatarena.config import ArenaConfig | |
from chatarena.environments import ENV_REGISTRY, TimeStep | |
from chatarena.database import log_arena, log_messages, SupabaseDB, supabase_available | |
from chatarena.message import Message | |
css = """#col-container {max-width: 90%; margin-left: auto; margin-right: auto; display: flex; flex-direction: column;} | |
#header {text-align: center;} | |
#col-chatbox {flex: 1; max-height: min(750px, 100%); display: flex;} | |
#chatbox {height: min(750px, 100%); max-height: 750px; display:flex;} | |
#label {font-size: 2em; padding: 0.5em; margin: 0;} | |
.message {font-size: 1.2em;} | |
.wrap {min-width: min(640px, 100vh)} | |
.message-wrap {max-height: min(700px, 100vh);} | |
""" | |
DEBUG = False | |
DEFAULT_BACKEND = "openai-chat" | |
DEFAULT_ENV = "conversation" | |
MAX_NUM_PLAYERS = 6 | |
DEFAULT_NUM_PLAYERS = 2 | |
def load_examples(): | |
example_configs = {} | |
# Load json config files from examples folder | |
example_files = glob("examples/*.json") | |
for example_file in example_files: | |
with open(example_file, 'r') as f: | |
example = json.load(f) | |
example_configs[example["name"]] = example | |
return example_configs | |
EXAMPLE_REGISTRY = load_examples() | |
DB = SupabaseDB() if supabase_available else None | |
def get_moderator_components(visible=True): | |
name = "Moderator" | |
with gr.Row(): | |
with gr.Column(): | |
role_desc = gr.Textbox(label="Moderator role", lines=1, visible=visible, interactive=True, | |
placeholder=f"Enter the role description for {name}") | |
terminal_condition = gr.Textbox(show_label=False, lines=1, visible=visible, interactive=True, | |
placeholder="Enter the end criteria for the conversation") | |
with gr.Column(): | |
backend_type = gr.Dropdown(show_label=False, visible=visible, interactive=True, | |
choices=list(BACKEND_REGISTRY.keys()), value=DEFAULT_BACKEND) | |
with gr.Accordion(f"{name} Parameters", open=False, visible=visible) as accordion: | |
temperature = gr.Slider(minimum=0, maximum=2.0, step=0.1, interactive=True, visible=visible, | |
label=f"temperature", value=0.7) | |
max_tokens = gr.Slider(minimum=10, maximum=500, step=10, interactive=True, visible=visible, | |
label=f"max tokens", value=200) | |
return [role_desc, terminal_condition, backend_type, accordion, temperature, max_tokens] | |
def get_player_components(name, visible): | |
with gr.Row(): | |
with gr.Column(): | |
role_desc = gr.Textbox(label=name, lines=3, interactive=True, visible=visible, | |
placeholder=f"Enter the role description for {name}") | |
with gr.Column(): | |
backend_type = gr.Dropdown(show_label=False, choices=list(BACKEND_REGISTRY.keys()), | |
interactive=True, visible=visible, value=DEFAULT_BACKEND) | |
with gr.Accordion(f"{name} Parameters", open=False, visible=visible) as accordion: | |
temperature = gr.Slider(minimum=0, maximum=2.0, step=0.1, interactive=True, visible=visible, | |
label=f"temperature", value=0.7) | |
max_tokens = gr.Slider(minimum=10, maximum=500, step=10, interactive=True, visible=visible, | |
label=f"max tokens", value=200) | |
return [role_desc, backend_type, accordion, temperature, max_tokens] | |
def get_empty_state(): | |
return gr.State({"arena": None}) | |
with gr.Blocks(css=css) as demo: | |
state = get_empty_state() | |
all_components = [] | |
with gr.Column(elem_id="col-container"): | |
gr.Markdown("""# 🏟 ChatArena️<br> | |
Prompting multiple AI agents to play games in a language-driven environment. | |
**[Project Homepage](https://github.com/chatarena/chatarena)** | |
### Due to the high volume of requests, our OpenAI API Token exceeded the billing limit. We are approaching OpenAI to get the research access at the moment. We encourage users to run the demo locally following [this instruction](https://github.com/chatarena/chatarena#launch-the-demo-locally).""", elem_id="header") | |
with gr.Row(): | |
env_selector = gr.Dropdown(choices=list(ENV_REGISTRY.keys()), value=DEFAULT_ENV, interactive=True, | |
label="Environment Type", show_label=True) | |
example_selector = gr.Dropdown(choices=list(EXAMPLE_REGISTRY.keys()), interactive=True, | |
label="Select Example", show_label=True) | |
# Environment configuration | |
env_desc_textbox = gr.Textbox(show_label=True, lines=2, visible=True, label="Environment Description", | |
placeholder="Enter a description of a scenario or the game rules.") | |
all_components += [env_selector, example_selector, env_desc_textbox] | |
with gr.Row(): | |
with gr.Column(elem_id="col-chatbox"): | |
with gr.Tab("All"): | |
chatbot = gr.Chatbot(elem_id="chatbox", visible=True, show_label=False) | |
player_chatbots = [] | |
for i in range(MAX_NUM_PLAYERS): | |
player_name = f"Player {i + 1}" | |
with gr.Tab(player_name, visible=(i < DEFAULT_NUM_PLAYERS)): | |
player_chatbot = gr.Chatbot(elem_id=f"chatbox-{i}", visible=i < DEFAULT_NUM_PLAYERS, | |
label=player_name, show_label=False) | |
player_chatbots.append(player_chatbot) | |
all_components += [chatbot, *player_chatbots] | |
with gr.Column(elem_id="col-config"): # Player Configuration | |
# gr.Markdown("Player Configuration") | |
parallel_checkbox = gr.Checkbox(label="Parallel Actions", value=False, visible=True) | |
with gr.Accordion("Moderator", open=False, visible=True): | |
moderator_components = get_moderator_components(True) | |
all_components += [parallel_checkbox, *moderator_components] | |
all_players_components, players_idx2comp = [], {} | |
with gr.Blocks(): | |
num_player_slider = gr.Slider(2, MAX_NUM_PLAYERS, value=DEFAULT_NUM_PLAYERS, step=1, | |
label="Number of players:") | |
for i in range(MAX_NUM_PLAYERS): | |
player_name = f"Player {i + 1}" | |
with gr.Tab(player_name, visible=(i < DEFAULT_NUM_PLAYERS)) as tab: | |
player_comps = get_player_components(player_name, visible=(i < DEFAULT_NUM_PLAYERS)) | |
players_idx2comp[i] = player_comps + [tab] | |
all_players_components += player_comps + [tab] | |
all_components += [num_player_slider] + all_players_components | |
def variable_players(k): | |
k = int(k) | |
update_dict = {} | |
for i in range(MAX_NUM_PLAYERS): | |
if i < k: | |
for comp in players_idx2comp[i]: | |
update_dict[comp] = gr.update(visible=True) | |
update_dict[player_chatbots[i]] = gr.update(visible=True) | |
else: | |
for comp in players_idx2comp[i]: | |
update_dict[comp] = gr.update(visible=False) | |
update_dict[player_chatbots[i]] = gr.update(visible=False) | |
return update_dict | |
num_player_slider.change(variable_players, num_player_slider, all_players_components + player_chatbots) | |
human_input_textbox = gr.Textbox(show_label=True, label="Human Input", lines=1, visible=True, | |
interactive=True, placeholder="Enter your input here") | |
with gr.Row(): | |
btn_step = gr.Button("Start") | |
btn_restart = gr.Button("Clear") | |
gr.HTML( | |
"""<div style="text-align:center"><p><small>By clicking the "Start" button above, you consent to the collection and use of your input data for research purposes.</small></p></dev>""") | |
all_components += [human_input_textbox, btn_step, btn_restart] | |
def _convert_to_chatbot_output(all_messages, display_recv=False): | |
chatbot_output = [] | |
for i, message in enumerate(all_messages): | |
agent_name, msg, recv = message.agent_name, message.content, str(message.visible_to) | |
new_msg = re.sub(r'\n+', '<br>', msg.strip()) # Preprocess message for chatbot output | |
if display_recv: | |
new_msg = f"**{agent_name} (-> {recv})**: {new_msg}" # Add role to the message | |
else: | |
new_msg = f"**{agent_name}**: {new_msg}" | |
if agent_name == "Moderator": | |
chatbot_output.append((new_msg, None)) | |
else: | |
chatbot_output.append((None, new_msg)) | |
return chatbot_output | |
def _create_arena_config_from_components(all_comps: dict) -> ArenaConfig: | |
env_desc = all_comps[env_desc_textbox] | |
# Initialize the players | |
num_players = all_comps[num_player_slider] | |
player_configs = [] | |
for i in range(num_players): | |
player_name = f"Player {i + 1}" | |
role_desc, backend_type, temperature, max_tokens = [ | |
all_comps[c] for c in players_idx2comp[i] if not isinstance(c, (gr.Accordion, gr.Tab))] | |
player_config = { | |
"name": player_name, | |
"role_desc": role_desc, | |
"global_prompt": env_desc, | |
"backend": { | |
"backend_type": backend_type, | |
"temperature": temperature, | |
"max_tokens": max_tokens | |
} | |
} | |
player_configs.append(player_config) | |
# Initialize the environment | |
env_type = all_comps[env_selector] | |
# Get moderator config | |
mod_role_desc, mod_terminal_condition, moderator_backend_type, mod_temp, mod_max_tokens = [ | |
all_comps[c] for c in moderator_components if not isinstance(c, (gr.Accordion, gr.Tab))] | |
moderator_config = { | |
"role_desc": mod_role_desc, | |
"global_prompt": env_desc, | |
"terminal_condition": mod_terminal_condition, | |
"backend": { | |
"backend_type": moderator_backend_type, | |
"temperature": mod_temp, | |
"max_tokens": mod_max_tokens | |
} | |
} | |
env_config = { | |
"env_type": env_type, | |
"parallel": all_comps[parallel_checkbox], | |
"moderator": moderator_config, | |
"moderator_visibility": "all", | |
"moderator_period": "turn" | |
} | |
# arena_config = {"players": player_configs, "environment": env_config} | |
arena_config = ArenaConfig(players=player_configs, environment=env_config) | |
return arena_config | |
def step_game(all_comps: dict): | |
yield {btn_step: gr.update(value="Running...", interactive=False), | |
btn_restart: gr.update(interactive=False)} | |
sleep(2) # control the rate of requests | |
cur_state = all_comps[state] | |
# If arena is not yet created, create it | |
if cur_state["arena"] is None: | |
# Create the Arena | |
arena_config = _create_arena_config_from_components(all_comps) | |
arena = Arena.from_config(arena_config) | |
log_arena(arena, database=DB) | |
cur_state["arena"] = arena | |
else: | |
arena = cur_state["arena"] | |
try: | |
timestep = arena.step() | |
except HumanBackendError as e: | |
# Handle human input and recover with the game update | |
human_input = all_comps[human_input_textbox] | |
if human_input == "": | |
timestep = None # Failed to get human input | |
else: | |
timestep = arena.environment.step(e.agent_name, human_input) | |
except TooManyInvalidActions as e: | |
timestep = arena.current_timestep | |
timestep.observation.append( | |
Message("System", "Too many invalid actions. Game over.", turn=-1, visible_to="all")) | |
timestep.terminal = True | |
if timestep is None: | |
yield {human_input_textbox: gr.update(value="", placeholder="Please enter a valid input"), | |
btn_step: gr.update(value="Next Step", interactive=True), | |
btn_restart: gr.update(interactive=True)} | |
else: | |
all_messages = timestep.observation # user sees what the moderator sees | |
log_messages(arena, all_messages, database=DB) | |
chatbot_output = _convert_to_chatbot_output(all_messages, display_recv=True) | |
update_dict = {human_input_textbox: gr.Textbox.update(value=""), | |
chatbot: chatbot_output, | |
btn_step: gr.update(value="Next Step", interactive=not timestep.terminal), | |
btn_restart: gr.update(interactive=True), state: cur_state} | |
# Get the visible messages for each player | |
for i, player in enumerate(arena.players): | |
player_messages = arena.environment.get_observation(player.name) | |
player_output = _convert_to_chatbot_output(player_messages) | |
# Update the player's chatbot output | |
update_dict[player_chatbots[i]] = player_output | |
if DEBUG: | |
arena.environment.print() | |
yield update_dict | |
def restart_game(all_comps: dict): | |
cur_state = all_comps[state] | |
cur_state["arena"] = None | |
yield {chatbot: [], btn_restart: gr.update(interactive=False), | |
btn_step: gr.update(interactive=False), state: cur_state} | |
arena_config = _create_arena_config_from_components(all_comps) | |
arena = Arena.from_config(arena_config) | |
log_arena(arena, database=DB) | |
cur_state["arena"] = arena | |
yield {btn_step: gr.update(value="Start", interactive=True), | |
btn_restart: gr.update(interactive=True), state: cur_state} | |
# Remove Accordion and Tab from the list of components | |
all_components = [comp for comp in all_components if not isinstance(comp, (gr.Accordion, gr.Tab))] | |
# If any of the Textbox, Slider, Checkbox, Dropdown, RadioButtons is changed, the Step button is disabled | |
for comp in all_components: | |
def _disable_step_button(state): | |
if state["arena"] is not None: | |
return gr.update(interactive=False) | |
else: | |
return gr.update() | |
if isinstance(comp, | |
(gr.Textbox, gr.Slider, gr.Checkbox, gr.Dropdown, gr.Radio)) and comp is not human_input_textbox: | |
comp.change(_disable_step_button, state, btn_step) | |
btn_step.click(step_game, set(all_components + [state]), | |
[chatbot, *player_chatbots, btn_step, btn_restart, state, human_input_textbox]) | |
btn_restart.click(restart_game, set(all_components + [state]), | |
[chatbot, *player_chatbots, btn_step, btn_restart, state, human_input_textbox]) | |
# If an example is selected, update the components | |
def update_components_from_example(all_comps: dict): | |
example_name = all_comps[example_selector] | |
example_config = EXAMPLE_REGISTRY[example_name] | |
update_dict = {} | |
# Update the environment components | |
env_config = example_config['environment'] | |
update_dict[env_desc_textbox] = gr.update(value=example_config['global_prompt']) | |
update_dict[env_selector] = gr.update(value=env_config['env_type']) | |
update_dict[parallel_checkbox] = gr.update(value=env_config['parallel']) | |
# Update the moderator components | |
if "moderator" in env_config: | |
mod_role_desc, mod_terminal_condition, moderator_backend_type, mod_temp, mod_max_tokens = [ | |
c for c in moderator_components if not isinstance(c, (gr.Accordion, gr.Tab)) | |
] | |
update_dict[mod_role_desc] = gr.update(value=env_config['moderator']['role_desc']) | |
update_dict[mod_terminal_condition] = gr.update(value=env_config['moderator']['terminal_condition']) | |
update_dict[moderator_backend_type] = gr.update(value=env_config['moderator']['backend']['backend_type']) | |
update_dict[mod_temp] = gr.update(value=env_config['moderator']['backend']['temperature']) | |
update_dict[mod_max_tokens] = gr.update(value=env_config['moderator']['backend']['max_tokens']) | |
# Update the player components | |
update_dict[num_player_slider] = gr.update(value=len(example_config['players'])) | |
for i, player_config in enumerate(example_config['players']): | |
role_desc, backend_type, temperature, max_tokens = [ | |
c for c in players_idx2comp[i] if not isinstance(c, (gr.Accordion, gr.Tab)) | |
] | |
update_dict[role_desc] = gr.update(value=player_config['role_desc']) | |
update_dict[backend_type] = gr.update(value=player_config['backend']['backend_type']) | |
update_dict[temperature] = gr.update(value=player_config['backend']['temperature']) | |
update_dict[max_tokens] = gr.update(value=player_config['backend']['max_tokens']) | |
return update_dict | |
example_selector.change(update_components_from_example, set(all_components + [state]), all_components + [state]) | |
demo.queue(concurrency_count=32) # To improve concurrency | |
demo.launch(debug=DEBUG) | |