| 
									
										
										
										
											2024-06-07 13:33:51 -07:00
										 |  |  | """This is an example of simulating a chess game with two agents
 | 
					
						
							|  |  |  | that play against each other, using tools to reason about the game state | 
					
						
							| 
									
										
										
										
											2024-06-10 19:51:51 -07:00
										 |  |  | and make moves, and using a group chat manager to orchestrate the conversation."""
 | 
					
						
							| 
									
										
										
										
											2024-06-07 13:33:51 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | import argparse | 
					
						
							|  |  |  | import asyncio | 
					
						
							|  |  |  | import logging | 
					
						
							| 
									
										
										
										
											2024-06-25 13:23:29 -07:00
										 |  |  | import os | 
					
						
							|  |  |  | import sys | 
					
						
							| 
									
										
										
										
											2024-06-08 01:27:27 -07:00
										 |  |  | from typing import Annotated, Literal | 
					
						
							| 
									
										
										
										
											2024-06-07 13:33:51 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | from agnext.application import SingleThreadedAgentRuntime | 
					
						
							| 
									
										
										
										
											2024-06-28 23:15:46 -07:00
										 |  |  | from agnext.components.models import SystemMessage | 
					
						
							| 
									
										
										
										
											2024-06-07 13:33:51 -07:00
										 |  |  | from agnext.components.tools import FunctionTool | 
					
						
							|  |  |  | from agnext.core import AgentRuntime | 
					
						
							| 
									
										
										
										
											2024-06-08 01:27:27 -07:00
										 |  |  | from chess import BLACK, SQUARE_NAMES, WHITE, Board, Move | 
					
						
							| 
									
										
										
										
											2024-06-07 13:33:51 -07:00
										 |  |  | from chess import piece_name as get_piece_name | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-25 13:23:29 -07:00
										 |  |  | sys.path.append(os.path.join(os.path.dirname(__file__), "..")) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | from common.agents._chat_completion_agent import ChatCompletionAgent | 
					
						
							|  |  |  | from common.memory import BufferedChatMemory | 
					
						
							|  |  |  | from common.patterns._group_chat_manager import GroupChatManager | 
					
						
							|  |  |  | from common.types import TextMessage | 
					
						
							| 
									
										
										
										
											2024-06-28 23:15:46 -07:00
										 |  |  | from common.utils import get_chat_completion_client_from_envs | 
					
						
							| 
									
										
										
										
											2024-06-25 13:23:29 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-07 13:33:51 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-08 01:27:27 -07:00
										 |  |  | def validate_turn(board: Board, player: Literal["white", "black"]) -> None: | 
					
						
							|  |  |  |     """Validate that it is the player's turn to move.""" | 
					
						
							|  |  |  |     last_move = board.peek() if board.move_stack else None | 
					
						
							|  |  |  |     if last_move is not None: | 
					
						
							|  |  |  |         if player == "white" and board.color_at(last_move.to_square) == WHITE: | 
					
						
							|  |  |  |             raise ValueError("It is not your turn to move. Wait for black to move.") | 
					
						
							|  |  |  |         if player == "black" and board.color_at(last_move.to_square) == BLACK: | 
					
						
							|  |  |  |             raise ValueError("It is not your turn to move. Wait for white to move.") | 
					
						
							|  |  |  |     elif last_move is None and player != "white": | 
					
						
							|  |  |  |         raise ValueError("It is not your turn to move. Wait for white to move first.") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_legal_moves( | 
					
						
							|  |  |  |     board: Board, player: Literal["white", "black"] | 
					
						
							|  |  |  | ) -> Annotated[str, "A list of legal moves in UCI format."]: | 
					
						
							|  |  |  |     """Get legal moves for the given player.""" | 
					
						
							|  |  |  |     validate_turn(board, player) | 
					
						
							|  |  |  |     legal_moves = list(board.legal_moves) | 
					
						
							|  |  |  |     if player == "black": | 
					
						
							|  |  |  |         legal_moves = [move for move in legal_moves if board.color_at(move.from_square) == BLACK] | 
					
						
							|  |  |  |     elif player == "white": | 
					
						
							|  |  |  |         legal_moves = [move for move in legal_moves if board.color_at(move.from_square) == WHITE] | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         raise ValueError("Invalid player, must be either 'black' or 'white'.") | 
					
						
							|  |  |  |     if not legal_moves: | 
					
						
							|  |  |  |         return "No legal moves. The game is over." | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return "Possible moves are: " + ", ".join([move.uci() for move in legal_moves]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_board(board: Board) -> str: | 
					
						
							|  |  |  |     return str(board) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def make_move( | 
					
						
							|  |  |  |     board: Board, | 
					
						
							|  |  |  |     player: Literal["white", "black"], | 
					
						
							|  |  |  |     thinking: Annotated[str, "Thinking for the move."], | 
					
						
							|  |  |  |     move: Annotated[str, "A move in UCI format."], | 
					
						
							|  |  |  | ) -> Annotated[str, "Result of the move."]: | 
					
						
							|  |  |  |     """Make a move on the board.""" | 
					
						
							|  |  |  |     validate_turn(board, player) | 
					
						
							|  |  |  |     newMove = Move.from_uci(move) | 
					
						
							|  |  |  |     board.push(newMove) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Print the move. | 
					
						
							|  |  |  |     print("-" * 50) | 
					
						
							|  |  |  |     print("Player:", player) | 
					
						
							|  |  |  |     print("Move:", newMove.uci()) | 
					
						
							|  |  |  |     print("Thinking:", thinking) | 
					
						
							|  |  |  |     print("Board:") | 
					
						
							|  |  |  |     print(board.unicode(borders=True)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Get the piece name. | 
					
						
							|  |  |  |     piece = board.piece_at(newMove.to_square) | 
					
						
							|  |  |  |     assert piece is not None | 
					
						
							|  |  |  |     piece_symbol = piece.unicode_symbol() | 
					
						
							|  |  |  |     piece_name = get_piece_name(piece.piece_type) | 
					
						
							|  |  |  |     if piece_symbol.isupper(): | 
					
						
							|  |  |  |         piece_name = piece_name.capitalize() | 
					
						
							|  |  |  |     return f"Moved {piece_name} ({piece_symbol}) from {SQUARE_NAMES[newMove.from_square]} to {SQUARE_NAMES[newMove.to_square]}." | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-23 11:49:38 -07:00
										 |  |  | async def chess_game(runtime: AgentRuntime) -> None:  # type: ignore | 
					
						
							| 
									
										
										
										
											2024-06-07 13:33:51 -07:00
										 |  |  |     """Create agents for a chess game and return the group chat.""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Create the board. | 
					
						
							|  |  |  |     board = Board() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-08 01:27:27 -07:00
										 |  |  |     # Create tools for each player. | 
					
						
							|  |  |  |     # @functools.wraps(get_legal_moves) | 
					
						
							|  |  |  |     def get_legal_moves_black() -> str: | 
					
						
							|  |  |  |         return get_legal_moves(board, "black") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # @functools.wraps(get_legal_moves) | 
					
						
							|  |  |  |     def get_legal_moves_white() -> str: | 
					
						
							|  |  |  |         return get_legal_moves(board, "white") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # @functools.wraps(make_move) | 
					
						
							|  |  |  |     def make_move_black( | 
					
						
							|  |  |  |         thinking: Annotated[str, "Thinking for the move"], | 
					
						
							|  |  |  |         move: Annotated[str, "A move in UCI format"], | 
					
						
							|  |  |  |     ) -> str: | 
					
						
							|  |  |  |         return make_move(board, "black", thinking, move) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # @functools.wraps(make_move) | 
					
						
							|  |  |  |     def make_move_white( | 
					
						
							|  |  |  |         thinking: Annotated[str, "Thinking for the move"], | 
					
						
							|  |  |  |         move: Annotated[str, "A move in UCI format"], | 
					
						
							|  |  |  |     ) -> str: | 
					
						
							|  |  |  |         return make_move(board, "white", thinking, move) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def get_board_text() -> Annotated[str, "The current board state"]: | 
					
						
							|  |  |  |         return get_board(board) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     black_tools = [ | 
					
						
							|  |  |  |         FunctionTool( | 
					
						
							|  |  |  |             get_legal_moves_black, | 
					
						
							|  |  |  |             name="get_legal_moves", | 
					
						
							|  |  |  |             description="Get legal moves.", | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |         FunctionTool( | 
					
						
							|  |  |  |             make_move_black, | 
					
						
							|  |  |  |             name="make_move", | 
					
						
							|  |  |  |             description="Make a move.", | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |         FunctionTool( | 
					
						
							|  |  |  |             get_board_text, | 
					
						
							|  |  |  |             name="get_board", | 
					
						
							|  |  |  |             description="Get the current board state.", | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |     ] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     white_tools = [ | 
					
						
							|  |  |  |         FunctionTool( | 
					
						
							|  |  |  |             get_legal_moves_white, | 
					
						
							|  |  |  |             name="get_legal_moves", | 
					
						
							|  |  |  |             description="Get legal moves.", | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |         FunctionTool( | 
					
						
							|  |  |  |             make_move_white, | 
					
						
							|  |  |  |             name="make_move", | 
					
						
							|  |  |  |             description="Make a move.", | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |         FunctionTool( | 
					
						
							|  |  |  |             get_board_text, | 
					
						
							|  |  |  |             name="get_board", | 
					
						
							|  |  |  |             description="Get the current board state.", | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |     ] | 
					
						
							| 
									
										
										
										
											2024-06-07 13:33:51 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-23 11:49:38 -07:00
										 |  |  |     black = await runtime.register_and_get( | 
					
						
							| 
									
										
										
										
											2024-06-18 14:53:18 -04:00
										 |  |  |         "PlayerBlack", | 
					
						
							|  |  |  |         lambda: ChatCompletionAgent( | 
					
						
							|  |  |  |             description="Player playing black.", | 
					
						
							|  |  |  |             system_messages=[ | 
					
						
							|  |  |  |                 SystemMessage( | 
					
						
							|  |  |  |                     content="You are a chess player and you play as black. " | 
					
						
							|  |  |  |                     "Use get_legal_moves() to get list of legal moves. " | 
					
						
							|  |  |  |                     "Use get_board() to get the current board state. " | 
					
						
							|  |  |  |                     "Think about your strategy and call make_move(thinking, move) to make a move." | 
					
						
							|  |  |  |                 ), | 
					
						
							|  |  |  |             ], | 
					
						
							|  |  |  |             memory=BufferedChatMemory(buffer_size=10), | 
					
						
							| 
									
										
										
										
											2024-07-30 00:11:42 -07:00
										 |  |  |             model_client=get_chat_completion_client_from_envs(model="gpt-4o"), | 
					
						
							| 
									
										
										
										
											2024-06-18 14:53:18 -04:00
										 |  |  |             tools=black_tools, | 
					
						
							|  |  |  |         ), | 
					
						
							| 
									
										
										
										
											2024-06-07 13:33:51 -07:00
										 |  |  |     ) | 
					
						
							| 
									
										
										
										
											2024-07-23 11:49:38 -07:00
										 |  |  |     white = await runtime.register_and_get( | 
					
						
							| 
									
										
										
										
											2024-06-18 14:53:18 -04:00
										 |  |  |         "PlayerWhite", | 
					
						
							|  |  |  |         lambda: ChatCompletionAgent( | 
					
						
							|  |  |  |             description="Player playing white.", | 
					
						
							|  |  |  |             system_messages=[ | 
					
						
							|  |  |  |                 SystemMessage( | 
					
						
							|  |  |  |                     content="You are a chess player and you play as white. " | 
					
						
							|  |  |  |                     "Use get_legal_moves() to get list of legal moves. " | 
					
						
							|  |  |  |                     "Use get_board() to get the current board state. " | 
					
						
							|  |  |  |                     "Think about your strategy and call make_move(thinking, move) to make a move." | 
					
						
							|  |  |  |                 ), | 
					
						
							|  |  |  |             ], | 
					
						
							|  |  |  |             memory=BufferedChatMemory(buffer_size=10), | 
					
						
							| 
									
										
										
										
											2024-07-30 00:11:42 -07:00
										 |  |  |             model_client=get_chat_completion_client_from_envs(model="gpt-4o"), | 
					
						
							| 
									
										
										
										
											2024-06-18 14:53:18 -04:00
										 |  |  |             tools=white_tools, | 
					
						
							|  |  |  |         ), | 
					
						
							| 
									
										
										
										
											2024-06-07 13:33:51 -07:00
										 |  |  |     ) | 
					
						
							| 
									
										
										
										
											2024-06-10 19:51:51 -07:00
										 |  |  |     # Create a group chat manager for the chess game to orchestrate a turn-based | 
					
						
							|  |  |  |     # conversation between the two agents. | 
					
						
							| 
									
										
										
										
											2024-07-23 11:49:38 -07:00
										 |  |  |     await runtime.register( | 
					
						
							| 
									
										
										
										
											2024-06-18 14:53:18 -04:00
										 |  |  |         "ChessGame", | 
					
						
							|  |  |  |         lambda: GroupChatManager( | 
					
						
							|  |  |  |             description="A chess game between two agents.", | 
					
						
							|  |  |  |             memory=BufferedChatMemory(buffer_size=10), | 
					
						
							|  |  |  |             participants=[white, black],  # white goes first | 
					
						
							|  |  |  |         ), | 
					
						
							| 
									
										
										
										
											2024-06-07 13:33:51 -07:00
										 |  |  |     ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-10 19:51:51 -07:00
										 |  |  | async def main() -> None: | 
					
						
							| 
									
										
										
										
											2024-06-07 13:33:51 -07:00
										 |  |  |     runtime = SingleThreadedAgentRuntime() | 
					
						
							| 
									
										
										
										
											2024-07-23 11:49:38 -07:00
										 |  |  |     await chess_game(runtime) | 
					
						
							| 
									
										
										
										
											2024-07-30 00:11:42 -07:00
										 |  |  |     run_context = runtime.start() | 
					
						
							| 
									
										
										
										
											2024-06-10 19:51:51 -07:00
										 |  |  |     # Publish an initial message to trigger the group chat manager to start orchestration. | 
					
						
							| 
									
										
										
										
											2024-06-27 13:40:12 -04:00
										 |  |  |     await runtime.publish_message(TextMessage(content="Game started.", source="System"), namespace="default") | 
					
						
							| 
									
										
										
										
											2024-07-30 00:11:42 -07:00
										 |  |  |     await run_context.stop_when_idle() | 
					
						
							| 
									
										
										
										
											2024-06-07 13:33:51 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | if __name__ == "__main__": | 
					
						
							|  |  |  |     parser = argparse.ArgumentParser(description="Run a chess game between two agents.") | 
					
						
							| 
									
										
										
										
											2024-06-08 01:27:27 -07:00
										 |  |  |     parser.add_argument("--verbose", action="store_true", help="Enable verbose logging.") | 
					
						
							| 
									
										
										
										
											2024-06-07 13:33:51 -07:00
										 |  |  |     args = parser.parse_args() | 
					
						
							| 
									
										
										
										
											2024-06-08 01:27:27 -07:00
										 |  |  |     if args.verbose: | 
					
						
							|  |  |  |         logging.basicConfig(level=logging.WARNING) | 
					
						
							|  |  |  |         logging.getLogger("agnext").setLevel(logging.DEBUG) | 
					
						
							| 
									
										
										
										
											2024-06-17 17:54:27 -07:00
										 |  |  |         handler = logging.FileHandler("chess_game.log") | 
					
						
							|  |  |  |         logging.getLogger("agnext").addHandler(handler) | 
					
						
							| 
									
										
										
										
											2024-06-08 01:27:27 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-10 19:51:51 -07:00
										 |  |  |     asyncio.run(main()) |