diff --git a/bindings/ceylon/examples/auction/auction_plaground.md b/bindings/ceylon/examples/auction/auction_plaground.md new file mode 100644 index 0000000..34f1f7c --- /dev/null +++ b/bindings/ceylon/examples/auction/auction_plaground.md @@ -0,0 +1,345 @@ +# Building a Distributed Auction System with Ceylon Playground + +This tutorial demonstrates how to build a distributed auction system using Ceylon's TaskPlayGround functionality. The system implements a multi-agent auction where bidders compete for items using random bidding strategies. + +## System Architecture + +````mermaid +graph TB + subgraph Playground[Auction Playground] + TM[Task Manager] + AG[Auction Group] + GT[Goal Tracker] + end + + subgraph Agents[Auction Agents] + B1[Bidder 1] + B2[Bidder 2] + B3[Bidder 3] + end + + subgraph Tasks[Auction Tasks] + T1[Bid Task 1] + T2[Bid Task 2] + T3[Bid Task 3] + end + + TM --> AG + AG --> GT + AG --> |Assigns| T1 + AG --> |Assigns| T2 + AG --> |Assigns| T3 + T1 --> |Executed by| B1 + T2 --> |Executed by| B2 + T3 --> |Executed by| B3 + GT --> |Monitors| T1 + GT --> |Monitors| T2 + GT --> |Monitors| T3 +```` + +## Core Components + +### 1. Data Models + +First, let's define our core data structures: + +```python +@dataclass +class Item: + name: str + starting_price: float + +@dataclass +class Bid: + bidder: str + amount: float + item_id: str +``` + +### 2. Auction Agent + +The AuctionAgent class represents each bidder in the system: + +```python +class AuctionAgent(TaskExecutionAgent): + def __init__(self, name: str, budget: float, max_concurrent_tasks: int = 1): + super().__init__( + name=name, + worker_role="bidder", + max_concurrent_tasks=max_concurrent_tasks + ) + self.budget = budget + self.bids: Dict[str, Bid] = {} +``` + +Key features: +- Inherits from TaskExecutionAgent for task management +- Maintains a budget for bidding +- Tracks bid history +- Implements random bidding strategy + +### 3. Auction Setup + +The setup_auction function configures the playground and creates tasks: + +```python +async def setup_auction( + item: Item, + bidders: List[AuctionAgent], + min_bids: int +) -> TaskPlayGround: + playground = TaskPlayGround(name="auction_system") + + # Create auction tasks + auction_tasks = [ + TaskMessage( + task_id=f"bid_{bidder.name}_{item.name}", + name=f"Place bid for {item.name}", + description=f"Submit bid for {item.name}", + duration=1, + required_role="bidder", + metadata={ + 'type': 'auction_bid', + 'item': item.__dict__, + 'item_id': item.name, + } + ) + for bidder in bidders + ] + + # Create task group + auction_group = TaskManager.create_task_group(...) + + return playground, auction_group +``` + +## Implementation Steps + +### 1. Initialize the System + +```python +# Create auction item +item = Item("Rare Painting", 1000.0) + +# Create bidders with budgets +bidders = [ + AuctionAgent("Alice", 1500.0), + AuctionAgent("Bob", 1200.0), + AuctionAgent("Charlie", 2000.0) +] +``` + +### 2. Configure Bidding Strategy + +The random bidding strategy uses two factors: +```python +# Random multiplier (1.0 to 2.0) for starting price +random_multiplier = 1.0 + random.random() + +# Random percentage of remaining budget +budget_percentage = random.random() + +# Calculate final bid +base_bid = item.starting_price * random_multiplier +budget_portion = (self.budget - base_bid) * budget_percentage +bid_amount = min(self.budget, base_bid + budget_portion) +``` + +### 3. Run the Auction + +```python +async def run_auction( + item: Item, + bidders: List[AuctionAgent], + min_bids: int +) -> Optional[Bid]: + + # Setup system + playground, auction_group = await setup_auction(item, bidders, min_bids) + + # Run auction + async with playground.play(workers=bidders) as active_playground: + await active_playground.assign_task_groups([auction_group]) + completed_tasks = await active_playground.wait_and_get_completed_tasks() + + # Process results + valid_bids = [ + Bid(**task.metadata['bid']) + for task in completed_tasks.values() + if task.completed and 'bid' in (task.metadata or {}) + ] + + # Determine winner + if valid_bids: + return max(valid_bids, key=lambda x: x.amount) + return None +``` + +## Monitoring and Control + +### 1. Goal Tracking + +The system uses a goal checker to monitor bid progress: +```python +def check_bid_count(task_groups: dict, completed_tasks: dict) -> bool: + bid_count = sum( + 1 for task in completed_tasks.values() + if task.completed and 'bid' in (task.metadata or {}) + ) + return bid_count >= min_bids +``` + +### 2. Task Status Updates + +Monitor task completion and results: +```python +completed_tasks = await active_playground.wait_and_get_completed_tasks() +task_results = active_playground.get_task_results() +``` + +## Error Handling + +The system includes comprehensive error handling: + +1. **Task Level**: +```python +try: + # Process auction task + task.completed = True +except Exception as e: + task.completed = False + task.metadata['error'] = str(e) +``` + +2. **Auction Level**: +```python +try: + # Run auction + async with playground.play(workers=bidders) as active_playground: + # ... auction logic ... +except Exception as e: + logger.error(f"Error running auction: {e}") + return None +``` + +## Running the System + +Complete example of running an auction: + +```python +async def main(): + # Setup auction + item = Item("Rare Painting", 1000.0) + bidders = [ + AuctionAgent("Alice", 1500.0), + AuctionAgent("Bob", 1200.0), + AuctionAgent("Charlie", 2000.0), + AuctionAgent("David", 2800.0) + ] + + # Run auction + winning_bid = await run_auction( + item=item, + bidders=bidders, + min_bids=2 + ) + + # Process results + if winning_bid: + logger.info(f"Winner: {winning_bid.bidder}") + logger.info(f"Amount: ${winning_bid.amount:.2f}") + else: + logger.info("Auction failed to complete") + +if __name__ == "__main__": + asyncio.run(main()) +``` + +## System Flow + +````mermaid +sequenceDiagram + participant M as Main + participant P as Playground + participant TM as TaskManager + participant B as Bidders + + M->>P: Create Playground + M->>TM: Create Task Group + + loop For each bidder + TM->>B: Assign Bid Task + B->>TM: Submit Bid + end + + TM->>P: Check Goal Progress + + alt Enough Bids + P->>M: Return Results + else Timeout/Failure + P->>M: Return None + end +```` + +## Customization Options + +1. **Bidding Strategy** + - Modify random factors + - Implement different bidding algorithms + - Add bid increment rules + +2. **Auction Rules** + - Change minimum bid requirements + - Add time limits + - Implement reserve prices + +3. **Task Configuration** + - Adjust task duration + - Modify concurrent task limits + - Add task dependencies + +## Best Practices + +1. **Error Handling** + - Always include try-except blocks + - Log errors with context + - Implement cleanup in finally blocks + +2. **Resource Management** + - Use async context managers + - Clean up resources properly + - Monitor system resources + +3. **Monitoring** + - Log important events + - Track task progress + - Monitor system health + +## Example Output + +``` +2025-01-26 10:15:30 | INFO | Starting auction for Rare Painting +2025-01-26 10:15:30 | INFO | Starting price: $1000.00 +2025-01-26 10:15:30 | INFO | Minimum bids required: 2 +2025-01-26 10:15:30 | INFO | Number of bidders: 4 + +2025-01-26 10:15:31 | INFO | Alice placed bid: $1432.50 +2025-01-26 10:15:31 | INFO | Bob placed bid: $1150.75 +2025-01-26 10:15:31 | INFO | Charlie placed bid: $1875.25 +2025-01-26 10:15:31 | INFO | David placed bid: $2234.80 + +2025-01-26 10:15:32 | INFO | Auction completed successfully! +2025-01-26 10:15:32 | INFO | Winner: David +2025-01-26 10:15:32 | INFO | Winning bid: $2234.80 +``` + +## Conclusion + +This tutorial demonstrated building a distributed auction system using Ceylon's TaskPlayGround. The system provides: +- Scalable multi-agent architecture +- Random bidding strategies +- Comprehensive monitoring +- Robust error handling +- Flexible customization options + +For more information, visit the Ceylon documentation at [https://docs.ceylon.ai](https://docs.ceylon.ai) \ No newline at end of file diff --git a/bindings/ceylon/examples/auction/auction_playground.py b/bindings/ceylon/examples/auction/auction_playground.py new file mode 100644 index 0000000..3fdd0e2 --- /dev/null +++ b/bindings/ceylon/examples/auction/auction_playground.py @@ -0,0 +1,206 @@ +import asyncio +import random +from dataclasses import dataclass +from typing import List, Dict, Optional +from loguru import logger + +from ceylon.task import TaskPlayGround +from ceylon.task.data import TaskMessage, TaskGroup, TaskGroupGoal, GoalStatus +from ceylon.task.manager import TaskManager +from ceylon.task.agent import TaskExecutionAgent + +# Data Models +@dataclass +class Item: + name: str + starting_price: float + +@dataclass +class Bid: + bidder: str + amount: float + item_id: str + +# Auction Agent Implementation +class AuctionAgent(TaskExecutionAgent): + def __init__(self, name: str, budget: float, max_concurrent_tasks: int = 1): + super().__init__( + name=name, + worker_role="bidder", + max_concurrent_tasks=max_concurrent_tasks + ) + self.budget = budget + self.bids: Dict[str, Bid] = {} + + async def execute_task(self, task: TaskMessage) -> None: + """Process auction tasks""" + try: + if task.metadata.get('type') == 'auction_bid': + # Extract auction details from task + item = Item(**task.metadata['item']) + if self.budget >= item.starting_price: + # Calculate random bid amount between starting price and budget + # Random multiplier between 1.0 and 2.0 for starting price + random_multiplier = 1.0 + random.random() + # Random percentage of remaining budget (0% to 100%) + budget_percentage = random.random() + + # Combine both random factors + base_bid = item.starting_price * random_multiplier + budget_portion = (self.budget - base_bid) * budget_percentage + bid_amount = min( + self.budget, + base_bid + budget_portion + ) + + # Record bid + bid = Bid( + bidder=self.name, + amount=bid_amount, + item_id=task.metadata['item_id'] + ) + self.bids[task.task_id] = bid + + # Update task with bid information + task.completed = True + task.metadata['bid'] = bid.__dict__ + logger.info(f"{self.name} placed bid: ${bid_amount:.2f}") + else: + logger.info(f"{self.name} cannot afford starting price of ${item.starting_price:.2f}") + task.completed = True + + task.end_time = asyncio.get_event_loop().time() + await self.broadcast_message(task) + + except Exception as e: + logger.error(f"Error in auction task execution: {e}") + task.completed = False + task.metadata['error'] = str(e) + await self.broadcast_message(task) + +async def setup_auction( + item: Item, + bidders: List[AuctionAgent], + min_bids: int +) -> TaskPlayGround: + """Setup and configure the auction playground""" + + # Initialize playground + playground = TaskPlayGround(name="auction_system") + + def check_bid_count(task_groups: dict, completed_tasks: dict) -> bool: + """Check if we've received enough valid bids""" + bid_count = sum( + 1 for task in completed_tasks.values() + if task.completed and 'bid' in (task.metadata or {}) + ) + logger.info(f"Current bid count: {bid_count}/{min_bids}") + return bid_count >= min_bids + + # Create auction tasks for each bidder + auction_tasks = [ + TaskMessage( + task_id=f"bid_{bidder.name}_{item.name}", + name=f"Place bid for {item.name}", + description=f"Submit bid for {item.name} (Starting price: ${item.starting_price:.2f})", + duration=1, + required_role="bidder", + metadata={ + 'type': 'auction_bid', + 'item': item.__dict__, + 'item_id': item.name, + } + ) + for bidder in bidders + ] + + # Create task group with goal + auction_group = TaskManager.create_task_group( + name=f"Auction: {item.name}", + description=f"Auction process for {item.name}", + subtasks=auction_tasks, + goal=TaskGroupGoal( + name="Minimum Bids Received", + description=f"Receive at least {min_bids} valid bids", + check_condition=check_bid_count, + success_message=f"Successfully received {min_bids} bids!", + failure_message="Failed to receive enough valid bids." + ), + priority=1 + ) + + return playground, auction_group + +async def run_auction( + item: Item, + bidders: List[AuctionAgent], + min_bids: int +) -> Optional[Bid]: + """Run an auction and return the winning bid""" + + # Setup auction system + playground, auction_group = await setup_auction(item, bidders, min_bids) + + try: + logger.info(f"\nStarting auction for {item.name}") + logger.info(f"Starting price: ${item.starting_price:.2f}") + logger.info(f"Minimum bids required: {min_bids}") + logger.info(f"Number of bidders: {len(bidders)}") + + # Run the auction + async with playground.play(workers=bidders) as active_playground: + # Start the auction group + await active_playground.assign_task_groups([auction_group]) + + # Wait for completion + completed_tasks = await active_playground.wait_and_get_completed_tasks() + + # Process results + valid_bids = [ + Bid(**task.metadata['bid']) + for task in completed_tasks.values() + if task.completed and 'bid' in (task.metadata or {}) + ] + + if valid_bids: + winning_bid = max(valid_bids, key=lambda x: x.amount) + logger.info(f"\nAuction completed successfully!") + logger.info(f"Winner: {winning_bid.bidder}") + logger.info(f"Winning bid: ${winning_bid.amount:.2f}") + return winning_bid + else: + logger.info("\nNo valid bids received") + return None + + except Exception as e: + logger.error(f"Error running auction: {e}") + return None + +async def main(): + # Setup test auction + item = Item("Rare Painting", 1000.0) + + # Create bidders with different budgets + bidders = [ + AuctionAgent("Alice", 1500.0), + AuctionAgent("Bob", 1200.0), + AuctionAgent("Charlie", 2000.0), + AuctionAgent("David", 2800.0) + ] + + # Run the auction + winning_bid = await run_auction( + item=item, + bidders=bidders, + min_bids=2 # Require at least 2 valid bids + ) + + if winning_bid: + logger.info(f"Winner: {winning_bid.bidder}") + logger.info(f"Winning bid: ${winning_bid.amount:.2f}") + logger.info("Auction completed successfully!") + else: + logger.info("Auction failed to complete") + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file