From e9c1856b2569c11020d6b5e605e30c761d6397b8 Mon Sep 17 00:00:00 2001 From: "panxuchen.pxc" Date: Tue, 8 Oct 2024 18:44:47 +0800 Subject: [PATCH] simplify auction example --- .../environments/auction_simulation/README.md | 35 +++++-- .../environments/auction_simulation/agents.py | 75 +++++++-------- .../environments/auction_simulation/env.py | 92 ++++++++++++------- .../auction_simulation/listeners.py | 65 +++++++------ .../environments/auction_simulation/main.py | 38 ++------ 5 files changed, 161 insertions(+), 144 deletions(-) diff --git a/examples/environments/auction_simulation/README.md b/examples/environments/auction_simulation/README.md index e579e9a44..e01167d5f 100644 --- a/examples/environments/auction_simulation/README.md +++ b/examples/environments/auction_simulation/README.md @@ -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 -``` \ No newline at end of file +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 +``` diff --git a/examples/environments/auction_simulation/agents.py b/examples/environments/auction_simulation/agents.py index 9b8a9003d..0b6526d41 100644 --- a/examples/environments/auction_simulation/agents.py +++ b/examples/environments/auction_simulation/agents.py @@ -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.""" @@ -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 @@ -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 @@ -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) diff --git a/examples/environments/auction_simulation/env.py b/examples/environments/auction_simulation/env.py index 9653b5d96..bf9c4cd4f 100644 --- a/examples/environments/auction_simulation/env.py +++ b/examples/environments/auction_simulation/env.py @@ -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: @@ -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. @@ -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] + ), + ), ) diff --git a/examples/environments/auction_simulation/listeners.py b/examples/environments/auction_simulation/listeners.py index 282193fed..01b40dcab 100644 --- a/examples/environments/auction_simulation/listeners.py +++ b/examples/environments/auction_simulation/listeners.py @@ -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 @@ -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", @@ -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): @@ -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", @@ -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) diff --git a/examples/environments/auction_simulation/main.py b/examples/environments/auction_simulation/main.py index 83b813ae2..b24e8deeb 100644 --- a/examples/environments/auction_simulation/main.py +++ b/examples/environments/auction_simulation/main.py @@ -1,14 +1,12 @@ # -*- coding: utf-8 -*- """An auction simulation.""" import argparse -from multiprocessing import Event -from agents import Auctioneer, Bidder, RandomBidder +from agents import Bidder, RandomBidder from env import Item, Auction -from listeners import StartListener, BidListener, BidTimerListener +from listeners import StartListener, BidListener import agentscope -from agentscope.server import RpcAgentServerLauncher def parse_args() -> argparse.Namespace: @@ -41,7 +39,7 @@ def main( use_monitor=False, ) - auction = Auction("auction") + auction = Auction("auction", waiting_time=waiting_time) if agent_type == "random": bidders = [RandomBidder(f"bidder_{i}") for i in range(bidder_num)] @@ -51,50 +49,28 @@ def main( for i in range(bidder_num) ] + # enable distributed mode if use_dist: - stop_event = Event() - env_server_launcher = RpcAgentServerLauncher() - env_server_launcher.stop_event = stop_event - env_server_launcher.launch() - auction = auction.to_dist( - host=env_server_launcher.host, - port=env_server_launcher.port, - ) - + auction = auction.to_dist() bidders = [bidder.to_dist() for bidder in bidders] - auctioneer = Auctioneer( - "auctioneer", - auction, - waiting_time=waiting_time, - ).to_dist() - else: - auctioneer = Auctioneer( - "auctioneer", - auction, - waiting_time=waiting_time, - ) # Set up listeners start_listeners = [ StartListener(f"start_{i}", bidders[i]) for i in range(bidder_num) ] - bid_timer_listener = [BidTimerListener("bid_timer", auctioneer)] bid_listeners = [ BidListener(f"bid_{i}", bidders[i]) for i in range(bidder_num) ] listeners = { "start": start_listeners, - "bid": bid_timer_listener + bid_listeners, + "bid": bid_listeners, } for target_event, listeners in listeners.items(): for listener in listeners: auction.add_listener(target_event, listener) item = Item("oil_painting", opening_price=10) - auction.start(item) - - if use_dist: - stop_event.wait() # need to manually shut down in dist mode + auction.run(item) if __name__ == "__main__":