Spaces:
Runtime error
Runtime error
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}') |