Skip to content

Commit

Permalink
simplify auction example
Browse files Browse the repository at this point in the history
  • Loading branch information
pan-x-c committed Oct 8, 2024
1 parent f5f3002 commit e9c1856
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 144 deletions.
35 changes: 28 additions & 7 deletions examples/environments/auction_simulation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,31 @@ You can also set the following arguments:
The following is sample output:

```log
2024-08-13 14:52:26.056 | INFO | listeners:__call__:43 - bidder_1 bid 20 for oil_painting
2024-08-13 14:52:26.799 | INFO | listeners:__call__:86 - bidder_0 bid 25 for oil_painting
2024-08-13 14:52:27.744 | INFO | listeners:__call__:86 - bidder_2 bid 30 for oil_painting
2024-08-13 14:52:28.434 | INFO | listeners:__call__:86 - bidder_0 bid 35 for oil_painting
2024-08-13 14:52:28.863 | INFO | listeners:__call__:86 - bidder_1 bid 100 for oil_painting
2024-08-13 14:52:31.865 | INFO | env:sold:87 - oil_painting is sold to bidder_1 for 100
```
Auction: Auction starts!
Listener: Notifying the bidder bidder_0...
Listener: Notifying the bidder bidder_1...
Listener: Notifying the bidder bidder_2...
Listener: Notifying the bidder bidder_3...
Listener: Notifying the bidder bidder_4...
bidder_1: Bid 34 for oil_painting
Listener: Bidder bidder_1 bids 34 for oil_painting. Notifying Bidder bidder_0
Listener: Bidder bidder_1 bids 34 for oil_painting. Notifying Bidder bidder_2
Listener: Bidder bidder_1 bids 34 for oil_painting. Notifying Bidder bidder_3
Listener: Bidder bidder_1 bids 34 for oil_painting. Notifying Bidder bidder_4
...
bidder_1: Bid 88 for oil_painting
Listener: Bidder bidder_1 bids 88 for oil_painting. Notifying Bidder bidder_0
bidder_0: Bid 53 for oil_painting
Listener: Bidder bidder_1 bids 88 for oil_painting. Notifying Bidder bidder_2
Listener: Bidder bidder_1 bids 88 for oil_painting. Notifying Bidder bidder_3
Listener: Bidder bidder_1 bids 88 for oil_painting. Notifying Bidder bidder_4
bidder_3: Not bid for oil_painting
bidder_0: Not bid for oil_painting
bidder_3: Bid 35 for oil_painting
bidder_4: Bid 21 for oil_painting
bidder_0: Not bid for oil_painting
bidder_1: Bid 26 for oil_painting
bidder_2: Not bid for oil_painting
Auction: Auction ends!
Auction: oil_painting is sold to bidder_1 for 88
```
75 changes: 35 additions & 40 deletions examples/environments/auction_simulation/agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,16 @@
"""The agents used to simulate an auction."""
import random
import re
import threading
import time
from typing import Optional, Sequence, Union

from env import Auction, Item
from env import Item

from loguru import logger
from agentscope.agents import AgentBase
from agentscope.message import Msg


class Auctioneer(AgentBase):
"""The auctioneer agent."""

def __init__(
self,
name: str,
auction: Auction,
waiting_time: float = 3.0,
) -> None:
"""Initialize the auctioneer agent.
Args:
name: The name of the auctioneer.
auction: The auction.
waiting_time: The waiting time for the auctioneer
to decide the winner.
"""
super().__init__(name=name)
self.auction = auction
self.wating_time = waiting_time
self.timer = None

def start_timer(self) -> None:
"""Start the timer for the auctioneer to decide the winner."""
if self.timer is not None:
self.timer.cancel()
self.timer = threading.Timer(self.wating_time, self.decide_winner)
self.timer.start()

def decide_winner(self) -> None:
"""Decide the winner of the auction."""
if self.auction.get_bid_info() is None:
self.auction.fail()
else:
self.auction.sold()


class RandomBidder(AgentBase):
"""A fake bidder agent who bids randomly."""

Expand Down Expand Up @@ -77,6 +40,23 @@ def reply(self, x: Optional[Union[Msg, Sequence[Msg]]] = None) -> Msg:
item = Item.from_dict(x.content["item"])
# generate a random bid or not to bid
response = self.generate_random_response(item.opening_price)
if response is None:
self.speak(
Msg(
self.name,
content=f"Not bid for {item.name}",
role="assistant",
),
)
return Msg(self.name, content=None, role="assistant")
else:
self.speak(
Msg(
self.name,
content=f"Bid {response} for {item.name}",
role="assistant",
),
)
msg = Msg(self.name, content=response, role="assistant")
return msg

Expand Down Expand Up @@ -139,7 +119,7 @@ def reply(self, x: Optional[Union[Msg, Sequence[Msg]]] = None) -> Msg:
f"the opening price is {item.opening_price}."
)
if bidder_name and prev_bid:
content += f" Now {bidder_name} bid {prev_bid} for the item."
content += f"\n{bidder_name} bid {prev_bid} for the item."
bid_info = Msg("assistant", content=content, role="assistant")

# prepare prompt
Expand All @@ -153,7 +133,22 @@ def reply(self, x: Optional[Union[Msg, Sequence[Msg]]] = None) -> Msg:
response = self.model(prompt).text
bid = self.parse_value(response)
msg = Msg(self.name, bid, role="assistant")

if response is None:
self.speak(
Msg(
self.name,
content=f"Not bid for {item.name}",
role="assistant",
),
)
else:
self.speak(
Msg(
self.name,
content=f"Bid {response} for {item.name}",
role="assistant",
),
)
# Record the message in memory
if self.memory:
self.memory.add(msg)
Expand Down
92 changes: 60 additions & 32 deletions examples/environments/auction_simulation/env.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# -*- coding: utf-8 -*-
"""The envs used to simulate an auction."""
from typing import Any, Dict, List, Optional
import time
from typing import Any, Dict, Optional
from threading import Lock

from loguru import logger

from agentscope.agents import AgentBase
from agentscope.environment import Event, BasicEnv, EventListener, event_func
from agentscope.environment.env import trigger_listener
from agentscope.environment import BasicEnv, event_func
from agentscope.message import Msg


class Item:
Expand Down Expand Up @@ -48,20 +49,22 @@ class Auction(BasicEnv):
def __init__(
self,
name: str = None,
listeners: List[EventListener] = None,
waiting_time: float = 3.0,
) -> None:
"""Initialize the auction env.
Args:
name (`str`): The name of the Auction.
listeners (`List[EventListener]`): The listeners.
waiting_time (`float`): The waiting time between bids.
"""
super().__init__(
name=name,
listeners=listeners,
)
self.waiting_time = waiting_time
self.end_time = 0
self.cur_item = None
self.cur_bid_info = None
self.bid_lock = Lock()

def get_bid_info(self) -> Optional[Dict[str, Any]]:
"""Get the bid info.
Expand All @@ -78,46 +81,71 @@ def start(self, item: Item) -> None:
"""
self.cur_item = item
self.cur_bid_info = None
self.end_time = time.time() + self.waiting_time
logger.chat(
Msg(name="Auction", role="system", content="Auction starts!"),
)

def bid(self, bidder: AgentBase, item: Item, bid: int) -> bool:
def run(self, item: Item) -> None:
"""Run bidding for an item.
Args:
item (`Item`): The item.
"""
self.start(item)
while time.time() < self.end_time:
time.sleep(1)
logger.chat(
Msg(name="Auction", role="system", content="Auction ends!"),
)
if self.cur_bid_info is None:
self.fail()
else:
self.sold()

@event_func
def bid(self, bidder_name: str, item: Item, bid: int) -> bool:
"""Bid for the auction.
Args:
bidder (`AgentBase`): The bidder agent.
bidder (`str`): The name of the bidder.
item (`Item`): The item.
bid (`int`): The bid of the bidder.
Returns:
`bool`: Whether the bid was successful.
"""
if (
self.cur_item.is_auctioned
or bid < item.opening_price
or (self.cur_bid_info and bid <= self.cur_bid_info["bid"])
):
return False
self.cur_bid_info = {"bidder": bidder, "bid": bid}
logger.info(f"{bidder.name} bid {bid} for {item.name}")
trigger_listener(
self,
Event(
"bid",
args={"bidder": bidder, "item": self.cur_item, "bid": bid},
),
)
return True
with self.bid_lock:
if (
self.cur_item.is_auctioned
or bid < item.opening_price
or (self.cur_bid_info and bid <= self.cur_bid_info["bid"])
):
return False
self.cur_bid_info = {"bidder": bidder_name, "bid": bid}
self.end_time = time.time() + self.waiting_time
return True

@event_func
def fail(self) -> None:
"""Pass the auction. (No bid for the item)"""
self.cur_item.is_auctioned = True
logger.info(f"{self.cur_item.name} is not sold")
logger.chat(
Msg(
name="Auction",
role="system",
content=f"{self.cur_item.name} is not sold",
),
)

@event_func
def sold(self) -> None:
"""Sold the item."""
self.cur_item.is_auctioned = True
logger.info(
f"{self.cur_item.name} is sold to "
f"{self.cur_bid_info['bidder'].name} " # type: ignore[index]
f"for {self.cur_bid_info['bid']}", # type: ignore[index]
logger.chat(
Msg(
name="Auction",
role="system",
content=(
f"{self.cur_item.name} is sold to "
f"{self.cur_bid_info['bidder']} " # type: ignore[index]
f"for {self.cur_bid_info['bid']}" # type: ignore[index]
),
),
)
65 changes: 31 additions & 34 deletions examples/environments/auction_simulation/listeners.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
"""Listerners for the auction simulation."""
from agents import Auctioneer, Bidder
from agents import Bidder
from env import Auction

from loguru import logger
from agentscope.environment import Event, EventListener
from agentscope.message import Msg

Expand Down Expand Up @@ -31,6 +32,13 @@ def __call__(
"""
item = event.args["item"]
if not item.is_auctioned:
logger.chat(
Msg(
name="Listener",
role="system",
content=f"Notifying the bidder {self.bidder.name}...",
),
)
bid = self.bidder(
Msg(
"auctioneer",
Expand All @@ -39,7 +47,7 @@ def __call__(
),
).content
if bid:
env.bid(self.bidder, item, bid)
env.bid(self.bidder.name, item, bid)


class BidListener(EventListener):
Expand Down Expand Up @@ -67,18 +75,35 @@ def __call__(
env (`Auction`): The auction env.
event (`Event`): The bidding event.
"""
bidder = event.args["bidder"]
# skip failed biddings
if not event.returns:
return

bidder = event.args["bidder_name"]
item = event.args["item"]
prev_bid = event.args["bid"]
if bidder.agent_id == self.bidder.agent_id:

# skip the bidder itself to avoid infinite loop
name = self.bidder.name
if bidder == name:
return

if not item.is_auctioned:
msg_content = {
"item": item.to_dict(),
"bidder_name": bidder.name,
"bidder_name": bidder,
"bid": prev_bid,
}
logger.chat(
Msg(
name="Listener",
role="system",
content=(
f"Bidder {bidder} bids {prev_bid} for {item.name}."
f" Notifying Bidder {name}"
),
),
)
bid = self.bidder(
Msg(
"auctioneer",
Expand All @@ -87,32 +112,4 @@ def __call__(
),
).content
if bid:
env.bid(self.bidder, item, bid)


class BidTimerListener(EventListener):
"""
A listener of bidding of an item for the auctioneer
to start the timer.
"""

def __init__(self, name: str, auctioneer: Auctioneer) -> None:
"""Initialize the listener.
Args:
name (`str`): The name of the listener.
auctioneer (`Auctioneer`): The auctioneer.
"""
super().__init__(name=name)
self.auctioneer = auctioneer

def __call__(
self,
env: Auction,
event: Event,
) -> None:
"""Activate the listener.
Args:
env (`Auction`): The auction env.
event (`Event`): The bidding event.
"""
self.auctioneer.start_timer()
env.bid(self.bidder.name, item, bid)
Loading

0 comments on commit e9c1856

Please sign in to comment.