Auction-Arena-Demo / auction_workflow.py
jiangjiechen's picture
init app
8acb22e
import os
import time
import gradio as gr
import ujson as json
import traceback
from typing import List
from tqdm import tqdm
from src.auctioneer_base import Auctioneer
from src.bidder_base import Bidder, bidders_to_chatbots, bidding_multithread
from utils import trace_back
LOG_DIR = 'logs'
enable_gr = gr.update(interactive=True)
disable_gr = gr.update(interactive=False)
def monitor_all(bidder_list: List[Bidder]):
return sum([bidder.to_monitors() for bidder in bidder_list], [])
def parse_bid_price(auctioneer: Auctioneer, bidder: Bidder, msg: str):
# rebid if the message is not parsible into a bid price
bid_price = auctioneer.parse_bid(msg)
while bid_price is None:
re_msg = bidder.bid("You must be clear about your bidding decision, say either \"I'm out!\" or \"I bid $xxx!\". Please rebid.")
bid_price = auctioneer.parse_bid(re_msg)
print(f"{bidder.name} rebid: {re_msg}")
return bid_price
def enable_human_box(bidder_list):
signals = []
for bidder in bidder_list:
if 'human' in bidder.model_name and not bidder.withdraw:
signals.append(gr.update(interactive=True, visible=True,
placeholder="Please bid! Enter \"I'm out\" or \"I bid $xxx\"."))
else:
signals.append(disable_gr)
return signals
def disable_all_box(bidder_list):
signals = []
for bidder in bidder_list:
if 'human' in bidder.model_name:
signals.append(gr.update(interactive=False, visible=True,
placeholder="Wait a moment to engage in the auction."))
else:
signals.append(gr.update(interactive=False, visible=False))
return signals
def run_auction(
auction_hash: str,
auctioneer: Auctioneer,
bidder_list: List[Bidder],
thread_num: int,
yield_for_demo=True,
log_dir=LOG_DIR,
repeat_num=0,
memo_file=None):
# bidder_list[0].verbose=True
if yield_for_demo:
chatbot_list = bidders_to_chatbots(bidder_list)
yield [bidder_list] + chatbot_list + monitor_all(bidder_list) + [auctioneer.log()] + [disable_gr, disable_gr] + disable_all_box(bidder_list)
# ***************** Learn Round ****************
for bidder in bidder_list:
if bidder.enable_learning and memo_file:
# if no prev memo file, then no need to learn.
if os.path.exists(memo_file):
with open(memo_file) as f:
data = json.load(f)
past_learnings = data['learnings'][bidder.name]
past_auction_log = data['auction_log']
bidder.learn_from_prev_auction(past_learnings, past_auction_log)
# ***************** Plan Round *****************
# init bidder profit
bidder_profit_info = auctioneer.gather_all_status(bidder_list)
for bidder in bidder_list:
bidder.set_all_bidders_status(bidder_profit_info)
plan_instructs = [bidder.get_plan_instruct(auctioneer.items) for bidder in bidder_list]
bidding_multithread(bidder_list, plan_instructs, func_type='plan', thread_num=thread_num)
if yield_for_demo:
chatbot_list = bidders_to_chatbots(bidder_list)
yield [bidder_list] + chatbot_list + monitor_all(bidder_list) + [auctioneer.log()] + [disable_gr, disable_gr] + disable_all_box(bidder_list)
bar = tqdm(total=len(auctioneer.items_queue), desc='Auction Progress')
while not auctioneer.end_auction():
cur_item = auctioneer.present_item()
bid_round = 0
while True:
# ***************** Bid Round *****************
auctioneer_msg = auctioneer.ask_for_bid(bid_round)
_bidder_list = []
_bid_instruct_list = []
# remove highest bidder and withdrawn bidders
for bidder in bidder_list:
if bidder is auctioneer.highest_bidder or bidder.withdraw:
bidder.need_input = False
continue
else:
bidder.need_input = True # enable input from demo
instruct = bidder.get_bid_instruct(auctioneer_msg, bid_round)
_bidder_list.append(bidder)
_bid_instruct_list.append(instruct)
if yield_for_demo:
chatbot_list = bidders_to_chatbots(bidder_list)
yield [bidder_list] + chatbot_list + monitor_all(bidder_list) + [auctioneer.log()] + [disable_gr, disable_gr] + enable_human_box(bidder_list)
_msgs = bidding_multithread(_bidder_list, _bid_instruct_list, func_type='bid', thread_num=thread_num)
for i, (msg, bidder) in enumerate(zip(_msgs, _bidder_list)):
if bidder.model_name == 'rule':
bid_price = bidder.bid_rule(auctioneer.prev_round_max_bid, auctioneer.min_markup_pct)
else:
bid_price = parse_bid_price(auctioneer, bidder, msg)
# can't bid more than budget or less than previous highest bid
while True:
fail_msg = bidder.bid_sanity_check(bid_price, auctioneer.prev_round_max_bid, auctioneer.min_markup_pct)
if fail_msg is None:
break
else:
bidder.need_input = True # enable input from demo
auctioneer_msg = auctioneer.ask_for_rebid(fail_msg=fail_msg, bid_price=bid_price)
rebid_instruct = bidder.get_rebid_instruct(auctioneer_msg)
if yield_for_demo:
chatbot_list = bidders_to_chatbots(bidder_list)
yield [bidder_list] + chatbot_list + monitor_all(bidder_list) + [auctioneer.log()] + [disable_gr, disable_gr] + disable_all_box(bidder_list)
msg = bidder.rebid_for_failure(rebid_instruct)
bid_price = parse_bid_price(auctioneer, bidder, msg)
if yield_for_demo:
chatbot_list = bidders_to_chatbots(bidder_list)
yield [bidder_list] + chatbot_list + monitor_all(bidder_list) + [auctioneer.log()] + [disable_gr, disable_gr] + disable_all_box(bidder_list)
bidder.set_withdraw(bid_price)
auctioneer.record_bid({'bidder': bidder, 'bid': bid_price, 'raw_msg': msg}, bid_round)
if yield_for_demo:
chatbot_list = bidders_to_chatbots(bidder_list)
yield [bidder_list] + chatbot_list + monitor_all(bidder_list) + [auctioneer.log()] + [disable_gr, disable_gr] + disable_all_box(bidder_list)
is_sold = auctioneer.check_hammer(bid_round)
bid_round += 1
if is_sold:
break
else:
if auctioneer.fail_to_sell and auctioneer.enable_discount:
for bidder in bidder_list:
bidder.set_withdraw(0) # back in the game
# ***************** Summarize *****************
summarize_instruct_list = []
for bidder in bidder_list:
if bidder is auctioneer.highest_bidder:
win_lose_msg = bidder.win_bid(cur_item, auctioneer.highest_bid)
else:
win_lose_msg = bidder.lose_bid(cur_item)
msg = bidder.get_summarize_instruct(
bidding_history=auctioneer.all_bidding_history_to_string(),
hammer_msg=auctioneer.get_hammer_msg(),
win_lose_msg=win_lose_msg
)
summarize_instruct_list.append(msg)
# record profit information of all bidders for each bidder
# (not used in the auction, just for belief tracking evaluation)
bidder_profit_info = auctioneer.gather_all_status(bidder_list)
for bidder in bidder_list:
bidder.set_all_bidders_status(bidder_profit_info)
bidding_multithread(bidder_list, summarize_instruct_list, func_type='summarize', thread_num=thread_num)
if yield_for_demo:
chatbot_list = bidders_to_chatbots(bidder_list)
yield [bidder_list] + chatbot_list + monitor_all(bidder_list) + [auctioneer.log()] + [disable_gr, disable_gr] + disable_all_box(bidder_list)
# ***************** Replan *****************
if len(auctioneer.items_queue) > 0: # no need to replan if all items are sold
replan_instruct_list = [bidder.get_replan_instruct(
# bidding_history=auctioneer.all_bidding_history_to_string(),
# hammer_msg=auctioneer.get_hammer_msg()
) for bidder in bidder_list]
bidding_multithread(bidder_list, replan_instruct_list, func_type='replan', thread_num=thread_num)
if yield_for_demo:
chatbot_list = bidders_to_chatbots(bidder_list)
yield [bidder_list] + chatbot_list + monitor_all(bidder_list) + [auctioneer.log()] + [disable_gr, disable_gr] + disable_all_box(bidder_list)
auctioneer.hammer_fall()
bar.update(1)
total_cost = sum([b.openai_cost for b in bidder_list]) + auctioneer.openai_cost
bidder_reports = [bidder.profit_report() for bidder in bidder_list]
if yield_for_demo:
chatbot_list = bidders_to_chatbots(bidder_list, profit_report=True)
yield [bidder_list] + chatbot_list + monitor_all(bidder_list) + [auctioneer.log(bidder_reports) + f'\n## Total Cost: ${total_cost}'] + [disable_gr, enable_gr] + disable_all_box(bidder_list)
memo = {'auction_log': auctioneer.log(show_model_name=False),
'memo_text': bidder_reports,
'profit': {bidder.name: bidder.profit for bidder in bidder_list},
'total_cost': total_cost,
'learnings': {bidder.name: bidder.learnings for bidder in bidder_list},
'model_info': {bidder.name: bidder.model_name for bidder in bidder_list}}
log_bidders(log_dir, auction_hash, bidder_list, repeat_num, memo)
auctioneer.finish_auction()
if not yield_for_demo:
yield total_cost
def log_bidders(log_dir: str, auction_hash: str, bidder_list: List[Bidder], repeat_num: int, memo: dict):
for bidder in bidder_list:
log_file = f"{log_dir}/{auction_hash}/{bidder.name.replace(' ', '')}-{repeat_num}.jsonl"
if not os.path.exists(log_file):
os.makedirs(os.path.dirname(log_file), exist_ok=True)
with open(log_file, 'a') as f:
log_data = bidder.to_monitors(as_json=True)
f.write(json.dumps(log_data) + '\n')
with open(f"{log_dir}/{auction_hash}/memo-{repeat_num}.json", 'w') as f:
f.write(json.dumps(memo) + '\n')
def make_auction_hash():
return str(int(time.time()))
if __name__ == '__main__':
import argparse
from src.item_base import create_items
from src.bidder_base import create_bidders
from transformers import GPT2TokenizerFast
import cjjpy as cjj
parser = argparse.ArgumentParser()
parser.add_argument('--input_dir', '-i', type=str, default='data/exp_base/')
parser.add_argument('--shuffle', action='store_true')
parser.add_argument('--repeat', type=int, default=1)
parser.add_argument('--threads', '-t', type=int, help='Number of threads. Max is number of bidders. Reduce it if rate limit is low (e.g., GPT-4).', required=True)
parser.add_argument('--memo_file', '-m', type=str, help='The last memo.json file to be loaded for learning. Only useful when the repeated auctions are interrupted (i.e., auction hash is different).')
args = parser.parse_args()
auction_hash = make_auction_hash()
total_money_spent = 0
for i in tqdm(range(args.repeat), desc='Repeat'):
cnt = 3
while cnt > 0:
try:
item_file = os.path.join(args.input_dir, f'items_demo.jsonl')
bidder_file = os.path.join(args.input_dir, f'bidders_demo.jsonl')
memo_file = args.memo_file if args.memo_file else f'{args.input_dir}/{auction_hash}/memo-{i-1}.json' # past memo for learning
items = create_items(item_file)
bidders = create_bidders(bidder_file, auction_hash=auction_hash)
auctioneer = Auctioneer(enable_discount=False)
auctioneer.init_items(items)
if args.shuffle:
auctioneer.shuffle_items()
money_spent = list(run_auction(
auction_hash,
auctioneer,
bidders,
thread_num=min(args.threads, len(bidders)),
yield_for_demo=False,
log_dir=args.input_dir,
repeat_num=i,
memo_file=memo_file,
))
total_money_spent += sum(money_spent)
break
except Exception as e:
cnt -= 1
print(f"Error in {i}th auction: {e}\n{trace_back(e)}")
print(f"Retry {cnt} more times...")
print('Total money spent: $', total_money_spent)
cjj.SendEmail(f'Completed: {args.input_dir} - {auction_hash}', f'Total money spent: ${total_money_spent}')