Spaces:
Runtime error
Runtime error
File size: 10,888 Bytes
8acb22e |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 |
import re
from typing import List, Dict
from langchain.prompts import PromptTemplate
from langchain.chat_models import ChatOpenAI
from langchain.callbacks import get_openai_callback
from pydantic import BaseModel
from collections import defaultdict
from langchain.schema import (
AIMessage,
HumanMessage,
SystemMessage
)
import random
import inflect
from .bidder_base import Bidder
from .human_bidder import HumanBidder
from .item_base import Item
from .prompt_base import PARSE_BID_INSTRUCTION
p = inflect.engine()
class Auctioneer(BaseModel):
enable_discount: bool = False
items: List[Item] = []
cur_item: Item = None
highest_bidder: Bidder = None
highest_bid: int = -1
bidding_history = defaultdict(list) # history about the bidding war of one item
items_queue: List[Item] = [] # updates when a item is taken.
auction_logs = defaultdict(list) # history about the bidding war of all items
openai_cost = 0
prev_round_max_bid: int = -1
min_bid: int = 0
fail_to_sell = False
min_markup_pct = 0.1
class Config:
arbitrary_types_allowed = True
def init_items(self, items: List[Item]):
for item in items:
# reset discounted price
item.reset_price()
self.items = items
self.items_queue = items.copy()
def summarize_items_info(self):
desc = ''
for item in self.items:
desc += f"- {item.get_desc()}\n"
return desc.strip()
def present_item(self):
cur_item = self.items_queue.pop(0)
self.cur_item = cur_item
return cur_item
def shuffle_items(self):
random.shuffle(self.items)
self.items_queue = self.items.copy()
def record_bid(self, bid_info: dict, bid_round: int):
'''
Save the bidding history for each round, log the highest bidder and highest bidding
'''
# bid_info: {'bidder': xxx, 'bid': xxx, 'raw_msg': xxx}
self.bidding_history[bid_round].append(bid_info)
for hist in self.bidding_history[bid_round]:
if hist['bid'] > 0:
if self.highest_bid < hist['bid']:
self.highest_bid = hist['bid']
self.highest_bidder = hist['bidder']
elif self.highest_bid == hist['bid']:
# random if there's a tie
self.highest_bidder = random.choice([self.highest_bidder, hist['bidder']])
self.auction_logs[f"{self.cur_item.get_desc()}"].append(
{'bidder': bid_info['bidder'],
'bid': bid_info['bid'],
'bid_round': bid_round})
def _biddings_to_string(self, bid_round: int):
'''
Return a string that summarizes the bidding history in a round
'''
# bid_hist_text = '' if bid_round == 0 else f'- {self.highest_bidder}: ${self.highest_bid}\n'
bid_hist_text = ''
for js in self.bidding_history[bid_round]:
if js['bid'] < 0:
bid_hist_text += f"- {js['bidder']} withdrew\n"
else:
bid_hist_text += f"- {js['bidder']}: ${js['bid']}\n"
return bid_hist_text.strip()
def all_bidding_history_to_string(self):
bid_hist_text = ''
for bid_round in self.bidding_history:
bid_hist_text += f"Round {bid_round}:\n{self._biddings_to_string(bid_round)}\n\n"
return bid_hist_text.strip()
def ask_for_bid(self, bid_round: int):
'''
Ask for bid, return the message to be sent to bidders
'''
if self.highest_bidder is None:
if bid_round > 0:
msg = f"Seeing as we've had no takers at the initial price, we're going to lower the starting bid to ${self.cur_item.price} for {self.cur_item.name} to spark some interest! Do I have any takers?"
else:
remaining_items = [self.cur_item.name] + [item.name for item in self.items_queue]
msg = f"Attention, bidders! {len(remaining_items)} item(s) left, they are: {', '.join(remaining_items)}.\n\nNow, please bid on {self.cur_item}. The starting price for bidding for {self.cur_item} is ${self.cur_item.price}. Anyone interested in this item?"
else:
bidding_history = self._biddings_to_string(bid_round - 1)
msg = f"Thank you! This is the {p.ordinal(bid_round)} round of bidding for this item:\n{bidding_history}\n\nNow we have ${self.highest_bid} from {self.highest_bidder.name} for {self.cur_item.name}. The minimum increase over this highest bid is ${int(self.cur_item.price * self.min_markup_pct)}. Do I have any advance on ${self.highest_bid}?"
return msg
def ask_for_rebid(self, fail_msg: str, bid_price: int):
return f"Your bid of ${bid_price} failed, because {fail_msg}: You must reconsider your bid."
def get_hammer_msg(self):
if self.highest_bidder is None:
return f"Since no one bid on {self.cur_item.name}, we'll move on to the next item."
else:
return f"Sold! {self.cur_item} to {self.highest_bidder} at ${self.highest_bid}! The true value for {self.cur_item} is ${self.cur_item.true_value}."# Thus {self.highest_bidder}'s profit by winning this item is ${self.cur_item.true_value - self.highest_bid}."
def check_hammer(self, bid_round: int):
# check if the item is sold
self.fail_to_sell = False
num_bid = self._num_bids_in_round(bid_round)
# highest_bidder has already been updated in record_bid().
# so when num_bid == 0 & highest_bidder is None, it means no one bid on this item
if self.highest_bidder is None:
if num_bid == 0:
# failed to sell, as there is no highest bidder
self.fail_to_sell = True
if self.enable_discount and bid_round < 3:
# lower the starting price by 50%. discoutn only applies to the first 3 rounds
self.cur_item.lower_price(0.5)
is_sold = False
else:
is_sold = True
else:
# won't happen
raise ValueError(f"highest_bidder is None but num_bid is {num_bid}")
else:
if self.prev_round_max_bid < 0 and num_bid == 1:
# only one bidder in the first round
is_sold = True
else:
self.prev_round_max_bid = self.highest_bid
is_sold = self._num_bids_in_round(bid_round) == 0
return is_sold
def _num_bids_in_round(self, bid_round: int):
# check if there is no bid in the current round
cnt = 0
for hist in self.bidding_history[bid_round]:
if hist['bid'] > 0:
cnt += 1
return cnt
def hammer_fall(self):
print(f'* Sold! {self.cur_item} (${self.cur_item.true_value}) goes to {self.highest_bidder} at ${self.highest_bid}.')
self.auction_logs[f"{self.cur_item.get_desc()}"].append({
'bidder': self.highest_bidder,
'bid': f"{self.highest_bid} (${self.cur_item.true_value})", # no need for the first $, as it will be added in the self.log()
'bid_round': 'Hammer price (true value)'})
self.cur_item = None
self.highest_bidder = None
self.highest_bid = -1
self.bidding_history = defaultdict(list)
self.prev_round_max_bid = -1
self.fail_to_sell = False
def end_auction(self):
return len(self.items_queue) == 0
def gather_all_status(self, bidders: List[Bidder]):
status = {}
for bidder in bidders:
status[bidder.name] = {
'profit': bidder.profit,
'items_won': bidder.items_won
}
return status
def parse_bid(self, text: str):
prompt = PARSE_BID_INSTRUCTION.format(response=text)
with get_openai_callback() as cb:
llm = ChatOpenAI(model='gpt-3.5-turbo-0613', temperature=0)
result = llm([HumanMessage(content=prompt)]).content
self.openai_cost += cb.total_cost
bid_number = re.findall(r'\$?\d+', result.replace(',', ''))
# find number in the result
if '-1' in result:
return -1
elif len(bid_number) > 0:
return int(bid_number[-1].replace('$', ''))
else:
print('* Rebid:', text)
return None
def log(self, bidder_personal_reports: list = [], show_model_name=True):
''' example
Apparatus H, starting at $1000.
1st bid:
Bidder 1 (gpt-3.5-turbo-16k-0613): $1200
Bidder 2 (gpt-3.5-turbo-16k-0613): $1100
Bidder 3 (gpt-3.5-turbo-16k-0613): Withdrawn
Bidder 4 (gpt-3.5-turbo-16k-0613): $1200
2nd bid:
Bidder 1 (gpt-3.5-turbo-16k-0613): Withdrawn
Bidder 2 (gpt-3.5-turbo-16k-0613): Withdrawn
Hammer price:
Bidder 4 (gpt-3.5-turbo-16k-0613): $1200
'''
markdown_output = "## Auction Log\n\n"
for i, (item, bids) in enumerate(self.auction_logs.items()):
markdown_output += f"### {i+1}. {item}\n\n"
cur_bid_round = -1
for i, bid in enumerate(bids):
if bid['bid_round'] != cur_bid_round:
cur_bid_round = bid['bid_round']
if isinstance(bid['bid_round'], int):
markdown_output += f"\n#### {p.ordinal(bid['bid_round']+1)} bid:\n\n"
else:
markdown_output += f"\n#### {bid['bid_round']}:\n\n"
bid_price = f"${bid['bid']}" if bid['bid'] != -1 else 'Withdrew'
if isinstance(bid['bidder'], Bidder) or isinstance(bid['bidder'], HumanBidder):
if show_model_name:
markdown_output += f"* {bid['bidder']} ({bid['bidder'].model_name}): {bid_price}\n"
else:
markdown_output += f"* {bid['bidder']}: {bid_price}\n"
else:
markdown_output += f"* None bid\n"
markdown_output += "\n"
if len(bidder_personal_reports) != 0:
markdown_output += f"\n## Personal Report"
for report in bidder_personal_reports:
markdown_output += f"\n\n{report}"
return markdown_output.strip()
def finish_auction(self):
self.auction_logs = defaultdict(list)
self.cur_item = None
self.highest_bidder = None
self.highest_bid = -1
self.bidding_history = defaultdict(list)
self.items_queue = []
self.items = []
self.prev_round_max_bid = -1
self.fail_to_sell = False
self.min_bid = 0
|