-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #77 from Madhuvod/ai-chess
Added new demo - AI Agents playing Chess
- Loading branch information
Showing
3 changed files
with
285 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
# AI Chess Game - Autogen | ||
|
||
This is a simple Chess game that uses an AI agents - Player black and player white to play the game. There's also a board proxy agent to execute the tools and manage the game. It is important to use a board proxy as a non-LLM "guard rail" to ensure the game is played correctly and to prevent agents from making illegal moves. | ||
|
||
Two agents (agent_white and agent_black) are initialized using the OpenAI API key. These agents are configured to play chess as white and black, respectively. | ||
A board_proxy agent is created to manage the board state and validate moves. | ||
Functions (make_move and available_moves) are registered with the agents to allow them to interact with the board. | ||
|
||
|
||
### How to get Started? | ||
|
||
1. Clone the GitHub repository | ||
|
||
```bash | ||
git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git | ||
cd ai_agent_tutorials/ai_chess_game | ||
``` | ||
2. Install the required dependencies: | ||
|
||
```bash | ||
pip install -r requirements.txt | ||
``` | ||
3. Get your OpenAI API Key | ||
|
||
- Sign up for an [OpenAI account](https://platform.openai.com/) (or the LLM provider of your choice) and obtain your API key. | ||
|
||
4. Run the Streamlit App | ||
```bash | ||
streamlit run ai_chess_agents.py | ||
``` | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,249 @@ | ||
import chess | ||
import chess.svg | ||
import streamlit as st | ||
from autogen import ConversableAgent, register_function | ||
|
||
if "openai_api_key" not in st.session_state: | ||
st.session_state.openai_api_key = None | ||
if "board" not in st.session_state: | ||
st.session_state.board = chess.Board() | ||
if "made_move" not in st.session_state: | ||
st.session_state.made_move = False | ||
if "board_svg" not in st.session_state: | ||
st.session_state.board_svg = None | ||
if "move_history" not in st.session_state: | ||
st.session_state.move_history = [] | ||
if "max_turns" not in st.session_state: | ||
st.session_state.max_turns = 5 | ||
|
||
st.sidebar.title("Chess Agent Configuration") | ||
openai_api_key = st.sidebar.text_input("Enter your OpenAI API key:", type="password") | ||
if openai_api_key: | ||
st.session_state.openai_api_key = openai_api_key | ||
st.sidebar.success("API key saved!") | ||
|
||
st.sidebar.info(""" | ||
For a complete chess game with potential checkmate, it would take max_turns > 200 approximately. | ||
However, this will consume significant API credits and a lot of time. | ||
For demo purposes, using 5-10 turns is recommended. | ||
""") | ||
|
||
max_turns_input = st.sidebar.number_input( | ||
"Enter the number of turns (max_turns):", | ||
min_value=1, | ||
max_value=1000, | ||
value=st.session_state.max_turns, | ||
step=1 | ||
) | ||
|
||
if max_turns_input: | ||
st.session_state.max_turns = max_turns_input | ||
st.sidebar.success(f"Max turns of total chess moves set to {st.session_state.max_turns}!") | ||
|
||
st.title("Chess with AutoGen Agents") | ||
|
||
def available_moves() -> str: | ||
available_moves = [str(move) for move in st.session_state.board.legal_moves] | ||
return "Available moves are: " + ",".join(available_moves) | ||
|
||
def execute_move(move: str) -> str: | ||
try: | ||
chess_move = chess.Move.from_uci(move) | ||
if chess_move not in st.session_state.board.legal_moves: | ||
return f"Invalid move: {move}. Please call available_moves() to see valid moves." | ||
|
||
# Update board state | ||
st.session_state.board.push(chess_move) | ||
st.session_state.made_move = True | ||
|
||
# Generate and store board visualization | ||
board_svg = chess.svg.board(st.session_state.board, | ||
arrows=[(chess_move.from_square, chess_move.to_square)], | ||
fill={chess_move.from_square: "gray"}, | ||
size=400) | ||
st.session_state.board_svg = board_svg | ||
st.session_state.move_history.append(board_svg) | ||
|
||
# Get piece information | ||
moved_piece = st.session_state.board.piece_at(chess_move.to_square) | ||
piece_unicode = moved_piece.unicode_symbol() | ||
piece_type_name = chess.piece_name(moved_piece.piece_type) | ||
piece_name = piece_type_name.capitalize() if piece_unicode.isupper() else piece_type_name | ||
|
||
# Generate move description | ||
from_square = chess.SQUARE_NAMES[chess_move.from_square] | ||
to_square = chess.SQUARE_NAMES[chess_move.to_square] | ||
move_desc = f"Moved {piece_name} ({piece_unicode}) from {from_square} to {to_square}." | ||
if st.session_state.board.is_checkmate(): | ||
winner = 'White' if st.session_state.board.turn == chess.BLACK else 'Black' | ||
move_desc += f"\nCheckmate! {winner} wins!" | ||
elif st.session_state.board.is_stalemate(): | ||
move_desc += "\nGame ended in stalemate!" | ||
elif st.session_state.board.is_insufficient_material(): | ||
move_desc += "\nGame ended - insufficient material to checkmate!" | ||
elif st.session_state.board.is_check(): | ||
move_desc += "\nCheck!" | ||
|
||
return move_desc | ||
except ValueError: | ||
return f"Invalid move format: {move}. Please use UCI format (e.g., 'e2e4')." | ||
|
||
def check_made_move(msg): | ||
if st.session_state.made_move: | ||
st.session_state.made_move = False | ||
return True | ||
else: | ||
return False | ||
|
||
if st.session_state.openai_api_key: | ||
try: | ||
agent_white_config_list = [ | ||
{ | ||
"model": "gpt-4o-mini", | ||
"api_key": st.session_state.openai_api_key, | ||
}, | ||
] | ||
|
||
agent_black_config_list = [ | ||
{ | ||
"model": "gpt-4o-mini", | ||
"api_key": st.session_state.openai_api_key, | ||
}, | ||
] | ||
|
||
agent_white = ConversableAgent( | ||
name="Agent_White", | ||
system_message="You are a professional chess player and you play as white. " | ||
"First call available_moves() first, to get list of legal available moves. " | ||
"Then call execute_move(move) to make a move.", | ||
llm_config={"config_list": agent_white_config_list, "cache_seed": None}, | ||
) | ||
|
||
agent_black = ConversableAgent( | ||
name="Agent_Black", | ||
system_message="You are a professional chess player and you play as black. " | ||
"First call available_moves() first, to get list of legal available moves. " | ||
"Then call execute_move(move) to make a move.", | ||
llm_config={"config_list": agent_black_config_list, "cache_seed": None}, | ||
) | ||
|
||
game_master = ConversableAgent( | ||
name="Game_Master", | ||
llm_config=False, | ||
is_termination_msg=check_made_move, | ||
default_auto_reply="Please make a move.", | ||
human_input_mode="NEVER", | ||
) | ||
|
||
register_function( | ||
execute_move, | ||
caller=agent_white, | ||
executor=game_master, | ||
name="execute_move", | ||
description="Call this tool to make a move.", | ||
) | ||
|
||
register_function( | ||
available_moves, | ||
caller=agent_white, | ||
executor=game_master, | ||
name="available_moves", | ||
description="Get legal moves.", | ||
) | ||
|
||
register_function( | ||
execute_move, | ||
caller=agent_black, | ||
executor=game_master, | ||
name="execute_move", | ||
description="Call this tool to make a move.", | ||
) | ||
|
||
register_function( | ||
available_moves, | ||
caller=agent_black, | ||
executor=game_master, | ||
name="available_moves", | ||
description="Get legal moves.", | ||
) | ||
|
||
agent_white.register_nested_chats( | ||
trigger=agent_black, | ||
chat_queue=[ | ||
{ | ||
"sender": game_master, | ||
"recipient": agent_white, | ||
"summary_method": "last_msg", | ||
} | ||
], | ||
) | ||
|
||
agent_black.register_nested_chats( | ||
trigger=agent_white, | ||
chat_queue=[ | ||
{ | ||
"sender": game_master, | ||
"recipient": agent_black, | ||
"summary_method": "last_msg", | ||
} | ||
], | ||
) | ||
|
||
st.info(""" | ||
This chess game is played between two AG2 AI agents: | ||
- **Agent White**: A GPT-4o-mini powered chess player controlling white pieces | ||
- **Agent Black**: A GPT-4o-mini powered chess player controlling black pieces | ||
The game is managed by a **Game Master** that: | ||
- Validates all moves | ||
- Updates the chess board | ||
- Manages turn-taking between players | ||
- Provides legal move information | ||
""") | ||
|
||
initial_board_svg = chess.svg.board(st.session_state.board, size=300) | ||
st.subheader("Initial Board") | ||
st.image(initial_board_svg) | ||
|
||
if st.button("Start Game"): | ||
st.session_state.board.reset() | ||
st.session_state.made_move = False | ||
st.session_state.move_history = [] | ||
st.session_state.board_svg = chess.svg.board(st.session_state.board, size=300) | ||
st.info("The AI agents will now play against each other. Each agent will analyze the board, " | ||
"request legal moves from the Game Master (proxy agent), and make strategic decisions.") | ||
st.success("You can view the interaction between the agents in the terminal output, after the turns between agents end, you get view all the chess board moves displayed below!") | ||
st.write("Game started! White's turn.") | ||
|
||
chat_result = agent_black.initiate_chat( | ||
recipient=agent_white, | ||
message="Let's play chess! You go first, its your move.", | ||
max_turns=st.session_state.max_turns, | ||
summary_method="reflection_with_llm" | ||
) | ||
st.markdown(chat_result.summary) | ||
|
||
# Display the move history (boards for each move) | ||
st.subheader("Move History") | ||
for i, move_svg in enumerate(st.session_state.move_history): | ||
# Determine which agent made the move | ||
if i % 2 == 0: | ||
move_by = "Agent White" # Even-indexed moves are by White | ||
else: | ||
move_by = "Agent Black" # Odd-indexed moves are by Black | ||
|
||
st.write(f"Move {i + 1} by {move_by}") | ||
st.image(move_svg) | ||
|
||
if st.button("Reset Game"): | ||
st.session_state.board.reset() | ||
st.session_state.made_move = False | ||
st.session_state.move_history = [] | ||
st.session_state.board_svg = None | ||
st.write("Game reset! Click 'Start Game' to begin a new game.") | ||
|
||
except Exception as e: | ||
st.error(f"An error occurred: {e}. Please check your API key and try again.") | ||
|
||
else: | ||
st.warning("Please enter your OpenAI API key in the sidebar to start the game.") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
streamlit | ||
chess==1.11.1 | ||
autogen==0.6.1 | ||
cairosvg | ||
pillow |