You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

chess_game.py 8.7 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. """This is an example of simulating a chess game with two agents
  2. that play against each other, using tools to reason about the game state
  3. and make moves, and using a group chat manager to orchestrate the conversation."""
  4. import argparse
  5. import asyncio
  6. import logging
  7. import os
  8. import sys
  9. from typing import Annotated, Literal
  10. from agnext.application import SingleThreadedAgentRuntime
  11. from agnext.components import DefaultTopicId
  12. from agnext.components._type_subscription import TypeSubscription
  13. from agnext.components.models import SystemMessage
  14. from agnext.components.tools import FunctionTool
  15. from agnext.core import AgentInstantiationContext, AgentRuntime
  16. from chess import BLACK, SQUARE_NAMES, WHITE, Board, Move
  17. from chess import piece_name as get_piece_name
  18. sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
  19. from agnext.core import AgentId
  20. from common.agents._chat_completion_agent import ChatCompletionAgent
  21. from common.memory import BufferedChatMemory
  22. from common.patterns._group_chat_manager import GroupChatManager
  23. from common.types import TextMessage
  24. from common.utils import get_chat_completion_client_from_envs
  25. def validate_turn(board: Board, player: Literal["white", "black"]) -> None:
  26. """Validate that it is the player's turn to move."""
  27. last_move = board.peek() if board.move_stack else None
  28. if last_move is not None:
  29. if player == "white" and board.color_at(last_move.to_square) == WHITE:
  30. raise ValueError("It is not your turn to move. Wait for black to move.")
  31. if player == "black" and board.color_at(last_move.to_square) == BLACK:
  32. raise ValueError("It is not your turn to move. Wait for white to move.")
  33. elif last_move is None and player != "white":
  34. raise ValueError("It is not your turn to move. Wait for white to move first.")
  35. def get_legal_moves(
  36. board: Board, player: Literal["white", "black"]
  37. ) -> Annotated[str, "A list of legal moves in UCI format."]:
  38. """Get legal moves for the given player."""
  39. validate_turn(board, player)
  40. legal_moves = list(board.legal_moves)
  41. if player == "black":
  42. legal_moves = [move for move in legal_moves if board.color_at(move.from_square) == BLACK]
  43. elif player == "white":
  44. legal_moves = [move for move in legal_moves if board.color_at(move.from_square) == WHITE]
  45. else:
  46. raise ValueError("Invalid player, must be either 'black' or 'white'.")
  47. if not legal_moves:
  48. return "No legal moves. The game is over."
  49. return "Possible moves are: " + ", ".join([move.uci() for move in legal_moves])
  50. def get_board(board: Board) -> str:
  51. return str(board)
  52. def make_move(
  53. board: Board,
  54. player: Literal["white", "black"],
  55. thinking: Annotated[str, "Thinking for the move."],
  56. move: Annotated[str, "A move in UCI format."],
  57. ) -> Annotated[str, "Result of the move."]:
  58. """Make a move on the board."""
  59. validate_turn(board, player)
  60. newMove = Move.from_uci(move)
  61. board.push(newMove)
  62. # Print the move.
  63. print("-" * 50)
  64. print("Player:", player)
  65. print("Move:", newMove.uci())
  66. print("Thinking:", thinking)
  67. print("Board:")
  68. print(board.unicode(borders=True))
  69. # Get the piece name.
  70. piece = board.piece_at(newMove.to_square)
  71. assert piece is not None
  72. piece_symbol = piece.unicode_symbol()
  73. piece_name = get_piece_name(piece.piece_type)
  74. if piece_symbol.isupper():
  75. piece_name = piece_name.capitalize()
  76. return f"Moved {piece_name} ({piece_symbol}) from {SQUARE_NAMES[newMove.from_square]} to {SQUARE_NAMES[newMove.to_square]}."
  77. async def chess_game(runtime: AgentRuntime) -> None: # type: ignore
  78. """Create agents for a chess game and return the group chat."""
  79. # Create the board.
  80. board = Board()
  81. # Create tools for each player.
  82. # @functools.wraps(get_legal_moves)
  83. def get_legal_moves_black() -> str:
  84. return get_legal_moves(board, "black")
  85. # @functools.wraps(get_legal_moves)
  86. def get_legal_moves_white() -> str:
  87. return get_legal_moves(board, "white")
  88. # @functools.wraps(make_move)
  89. def make_move_black(
  90. thinking: Annotated[str, "Thinking for the move"],
  91. move: Annotated[str, "A move in UCI format"],
  92. ) -> str:
  93. return make_move(board, "black", thinking, move)
  94. # @functools.wraps(make_move)
  95. def make_move_white(
  96. thinking: Annotated[str, "Thinking for the move"],
  97. move: Annotated[str, "A move in UCI format"],
  98. ) -> str:
  99. return make_move(board, "white", thinking, move)
  100. def get_board_text() -> Annotated[str, "The current board state"]:
  101. return get_board(board)
  102. black_tools = [
  103. FunctionTool(
  104. get_legal_moves_black,
  105. name="get_legal_moves",
  106. description="Get legal moves.",
  107. ),
  108. FunctionTool(
  109. make_move_black,
  110. name="make_move",
  111. description="Make a move.",
  112. ),
  113. FunctionTool(
  114. get_board_text,
  115. name="get_board",
  116. description="Get the current board state.",
  117. ),
  118. ]
  119. white_tools = [
  120. FunctionTool(
  121. get_legal_moves_white,
  122. name="get_legal_moves",
  123. description="Get legal moves.",
  124. ),
  125. FunctionTool(
  126. make_move_white,
  127. name="make_move",
  128. description="Make a move.",
  129. ),
  130. FunctionTool(
  131. get_board_text,
  132. name="get_board",
  133. description="Get the current board state.",
  134. ),
  135. ]
  136. await runtime.register(
  137. "PlayerBlack",
  138. lambda: ChatCompletionAgent(
  139. description="Player playing black.",
  140. system_messages=[
  141. SystemMessage(
  142. content="You are a chess player and you play as black. "
  143. "Use get_legal_moves() to get list of legal moves. "
  144. "Use get_board() to get the current board state. "
  145. "Think about your strategy and call make_move(thinking, move) to make a move."
  146. ),
  147. ],
  148. memory=BufferedChatMemory(buffer_size=10),
  149. model_client=get_chat_completion_client_from_envs(model="gpt-4o"),
  150. tools=black_tools,
  151. ),
  152. )
  153. await runtime.add_subscription(TypeSubscription("default", "PlayerBlack"))
  154. await runtime.register(
  155. "PlayerWhite",
  156. lambda: ChatCompletionAgent(
  157. description="Player playing white.",
  158. system_messages=[
  159. SystemMessage(
  160. content="You are a chess player and you play as white. "
  161. "Use get_legal_moves() to get list of legal moves. "
  162. "Use get_board() to get the current board state. "
  163. "Think about your strategy and call make_move(thinking, move) to make a move."
  164. ),
  165. ],
  166. memory=BufferedChatMemory(buffer_size=10),
  167. model_client=get_chat_completion_client_from_envs(model="gpt-4o"),
  168. tools=white_tools,
  169. ),
  170. )
  171. await runtime.add_subscription(TypeSubscription("default", "PlayerWhite"))
  172. # Create a group chat manager for the chess game to orchestrate a turn-based
  173. # conversation between the two agents.
  174. await runtime.register(
  175. "ChessGame",
  176. lambda: GroupChatManager(
  177. description="A chess game between two agents.",
  178. memory=BufferedChatMemory(buffer_size=10),
  179. participants=[
  180. AgentId("PlayerWhite", AgentInstantiationContext.current_agent_id().key),
  181. AgentId("PlayerBlack", AgentInstantiationContext.current_agent_id().key),
  182. ], # white goes first
  183. ),
  184. )
  185. async def main() -> None:
  186. runtime = SingleThreadedAgentRuntime()
  187. await chess_game(runtime)
  188. runtime.start()
  189. # Publish an initial message to trigger the group chat manager to start orchestration.
  190. await runtime.publish_message(TextMessage(content="Game started.", source="System"), topic_id=DefaultTopicId())
  191. await runtime.stop_when_idle()
  192. if __name__ == "__main__":
  193. parser = argparse.ArgumentParser(description="Run a chess game between two agents.")
  194. parser.add_argument("--verbose", action="store_true", help="Enable verbose logging.")
  195. args = parser.parse_args()
  196. if args.verbose:
  197. logging.basicConfig(level=logging.WARNING)
  198. logging.getLogger("agnext").setLevel(logging.DEBUG)
  199. handler = logging.FileHandler("chess_game.log")
  200. logging.getLogger("agnext").addHandler(handler)
  201. asyncio.run(main())