├── .gitignore ├── requirements.txt ├── README.md └── game.py /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .venv -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | openai 2 | python-dotenv 3 | comet_llm -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GPT playing Tic-Tac-Toe 2 | 3 | To run this script: 4 | 5 | ```bash 6 | $ python3 -m venv .venv 7 | $ source .venv/bin/activate 8 | $ pip install -r requirements.txt 9 | $ python3 game.py 10 | ``` 11 | -------------------------------------------------------------------------------- /game.py: -------------------------------------------------------------------------------- 1 | import os 2 | import openai 3 | import json 4 | 5 | from dotenv import load_dotenv 6 | from comet_llm import Span, end_chain, start_chain 7 | 8 | load_dotenv() 9 | 10 | # You need to set your OpenAI and Comet API keys in a .env file. 11 | openai.api_key = os.getenv("OPENAI_API_KEY") 12 | COMET_API_KEY = os.getenv("COMET_API_KEY") 13 | 14 | 15 | MODEL = "gpt-3.5-turbo-0613" 16 | 17 | history = {} 18 | 19 | 20 | def print_board(): 21 | players = list(history.keys()) 22 | 23 | board = "" 24 | for i in range(9): 25 | if i + 1 in history[players[0]]: 26 | board += "X" 27 | elif len(players) > 1 and i + 1 in history[players[1]]: 28 | board += "O" 29 | else: 30 | board += " " 31 | if (i + 1) % 3 == 0: 32 | board += "\n" 33 | else: 34 | board += "|" 35 | 36 | print(board) 37 | 38 | 39 | def play(player, position): 40 | print(f"{player} played in position {position}") 41 | 42 | if player not in history: 43 | history[player] = [] 44 | 45 | history[player].append(position) 46 | 47 | winning_combinations = [ 48 | [1, 2, 3], 49 | [4, 5, 6], 50 | [7, 8, 9], 51 | [1, 4, 7], 52 | [2, 5, 8], 53 | [3, 6, 9], 54 | [1, 5, 9], 55 | [3, 5, 7], 56 | ] 57 | 58 | player1_plays = len(history.get("Bob", [])) 59 | player2_plays = len(history.get("Alice", [])) 60 | 61 | if player1_plays + player2_plays == 9: 62 | return "The game is a draw." 63 | 64 | for combination in winning_combinations: 65 | if all(p in history[player] for p in combination): 66 | return f"Player {player} wins" 67 | 68 | return "Nobody wins" 69 | 70 | 71 | def get_completion(messages, parameters): 72 | response = openai.ChatCompletion.create( 73 | model=MODEL, 74 | messages=messages, 75 | functions=[ 76 | { 77 | "name": "play", 78 | "description": "Call this function when a player plays", 79 | "parameters": { 80 | "type": "object", 81 | "properties": { 82 | "player": { 83 | "type": "string", 84 | "description": "The name of the player who played", 85 | }, 86 | "position": { 87 | "type": "integer", 88 | "description": "The position where the player played", 89 | }, 90 | }, 91 | "required": ["player", "position"], 92 | }, 93 | }, 94 | ], 95 | **parameters, 96 | ) 97 | 98 | return response 99 | 100 | 101 | def call_function(response, messages): 102 | fn_name = response.choices[0].message["function_call"].name 103 | args = response.choices[0].message["function_call"].arguments 104 | arguments = json.loads(args) 105 | 106 | with Span( 107 | category="llm-function-call", 108 | name=fn_name, 109 | inputs=arguments, 110 | ) as span: 111 | result = globals()[fn_name](**arguments) 112 | print_board() 113 | 114 | span.set_outputs({"output": result}) 115 | 116 | messages.append( 117 | { 118 | "role": "assistant", 119 | "content": None, 120 | "function_call": { 121 | "name": fn_name, 122 | "arguments": args, 123 | }, 124 | } 125 | ) 126 | 127 | messages.append( 128 | { 129 | "role": "function", 130 | "name": fn_name, 131 | "content": result, 132 | } 133 | ) 134 | 135 | 136 | SYSTEM_PROMPT = """ 137 | You will play a board game simulating two different players. 138 | 139 | Here is how the game works: 140 | 141 | The board has 9 positions represented by a number from 1 to 9, 142 | eg. [1, 2, 3, 4, 5, 6, 7, 8, 9]. 143 | 144 | Repeat these steps until the game is over: 145 | 146 | Step 1. You are the first player, named Bob. 147 | 148 | Step 2. Choose any of the available positions from the board. You can only 149 | pick one of the values in the current board. For example, if the board is 150 | [1, 2, 6, 7, 9], you can play any of the following positions: 1, 2, 6, 7 or 9. 151 | If the board is [3, 4, 5], you can play any of the following positions: 3, 4 152 | or 5. 153 | 154 | Step 3. Remove the position you played from the board. For example, if the 155 | board was [1, 2, 6, 7, 9] and you played in position 6, the new board will be 156 | [1, 2, 7, 9]. If the board was [3, 4, 5] and you played in position 5, the new 157 | board will be [3, 4]. 158 | 159 | Step 4. Call the function play to add the position you played to the history. 160 | The function will return the name of the player who wins the game, whether the 161 | game is a draw or "Nobody wins" if the game is not over yet. 162 | 163 | Step 5. You are now the second player, named Alice. Repeat steps 2, 3, 4, and 164 | 5. Continue the game until one of the two players wins the game or the game is 165 | a draw. 166 | """ 167 | 168 | messages = [ 169 | {"role": "system", "content": SYSTEM_PROMPT}, 170 | {"role": "assistant", "content": "Board: [1, 2, 3, 4, 5, 6, 7, 8, 9]"}, 171 | {"role": "user", "content": "You play first"}, 172 | ] 173 | 174 | parameters = { 175 | "temperature": 0.1, 176 | } 177 | 178 | 179 | start_chain( 180 | inputs={ 181 | "prompt": SYSTEM_PROMPT, 182 | }, 183 | api_key=COMET_API_KEY, 184 | ) 185 | 186 | 187 | while True: 188 | with Span( 189 | category="llm-call", 190 | name="llm-generation", 191 | metadata={"model": MODEL, "parameters": parameters}, 192 | inputs=messages, 193 | ) as span: 194 | response = get_completion(messages, parameters) 195 | 196 | if response.choices[0]["finish_reason"] == "stop": 197 | print(response.choices[0].message["content"]) 198 | end_chain({"output": response.choices[0].message["content"]}) 199 | break 200 | 201 | elif response.choices[0]["finish_reason"] == "function_call": 202 | call_function(response, messages) --------------------------------------------------------------------------------