Jonathan Bejarano commited on
Commit
b9ad539
·
1 Parent(s): f9e564d

Refactor for better organization

Browse files
Files changed (3) hide show
  1. ai.py +58 -0
  2. app.py +29 -176
  3. game.py +77 -0
ai.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dotenv import load_dotenv
2
+ import os
3
+
4
+ from huggingface_hub import InferenceClient
5
+ # Load environment variables from .env file if it exists
6
+ load_dotenv()
7
+
8
+ # Check if we're running locally with custom model settings
9
+ BASE_URL = os.getenv('BASE_URL')
10
+ LOCAL_TOKEN = os.getenv('TOKEN')
11
+ LOCAL_MODE = bool(BASE_URL and LOCAL_TOKEN)
12
+ MODEL_NAME = os.getenv('MODEL_NAME', 'openai/gpt-oss-20b')
13
+ client = None
14
+ # Choose client based on whether we're running locally or in the cloud
15
+ if LOCAL_MODE:
16
+ # Running locally with custom model settings
17
+ # Use local inference server
18
+ client = InferenceClient(model=BASE_URL, token=LOCAL_TOKEN)
19
+ else:
20
+ hf_token = os.environ["HF_TOKEN"]
21
+ client = InferenceClient(token=hf_token, model=MODEL_NAME)
22
+
23
+
24
+ def clean_response(response):
25
+ """Clean up the response by removing unwanted metadata and formatting artifacts"""
26
+ import re
27
+ import json
28
+
29
+ # Remove channel/commentary tags and similar artifacts
30
+ response = re.sub(r'<\|channel\|>commentary to=assistant', '', response, flags=re.IGNORECASE)
31
+ response = re.sub(r'<\|constrain\|>json<\|message\|>', '', response, flags=re.IGNORECASE)
32
+ response = re.sub(r'<\|.*?\|>', '', response) # Remove any other <|...|> patterns
33
+ response = response.replace("\\n", "\n")
34
+
35
+ # Try to parse JSON response and extract the actual message
36
+ try:
37
+ # Look for JSON-like content
38
+ json_match = re.search(r'\{[^}]*"response"\s*:\s*"([^"]*)"[^}]*\}', response)
39
+ if json_match:
40
+ actual_response = json_match.group(1)
41
+ print(f"🔍 DEBUG - Extracted from JSON: {actual_response}")
42
+ return actual_response
43
+
44
+ # Try to parse as complete JSON
45
+ parsed = json.loads(response.strip())
46
+ if isinstance(parsed, dict) and "response" in parsed:
47
+ actual_response = parsed["response"]
48
+ print(f"🔍 DEBUG - Parsed JSON response: {actual_response}")
49
+ return actual_response
50
+ except (json.JSONDecodeError, AttributeError):
51
+ # If JSON parsing fails, continue with text cleaning
52
+ pass
53
+
54
+ # Clean up extra whitespace and newlines
55
+ response = re.sub(r'\n\s*\n', '\n', response) # Remove multiple empty lines
56
+ response = response.strip() # Remove leading/trailing whitespace
57
+
58
+ return response
app.py CHANGED
@@ -1,151 +1,23 @@
1
  import gradio as gr
2
- from huggingface_hub import InferenceClient
 
3
 
4
- import random
5
- import os
6
- import re
7
- from dotenv import load_dotenv
8
 
9
- from rag import fetch_facts
10
 
11
- # Load environment variables from .env file if it exists
12
- load_dotenv()
13
 
14
- # Check if we're running locally with custom model settings
15
- BASE_URL = os.getenv('BASE_URL')
16
- LOCAL_TOKEN = os.getenv('TOKEN')
17
- LOCAL_MODE = bool(BASE_URL and LOCAL_TOKEN)
18
- MODEL_NAME = os.getenv('MODEL_NAME', 'openai/gpt-oss-20b')
19
-
20
-
21
- import json
22
-
23
- # Load country list from countries.json
24
- COUNTRIES_JSON_PATH = os.path.join(os.path.dirname(__file__), "countries.json")
25
- with open(COUNTRIES_JSON_PATH, "r", encoding="utf-8") as f:
26
- GEOGRAPHY_GUESS_LIST = json.load(f)
27
-
28
- STATES_JSON_PATH = os.path.join(os.path.dirname(__file__), "us_states.json")
29
- with open(STATES_JSON_PATH, "r", encoding="utf-8") as f:
30
- US_STATES_GUESS_LIST = json.load(f)
31
-
32
- # Game mode constants
33
- GAME_MODE_COUNTRIES = "Countries of the World"
34
- GAME_MODE_STATES = "US States"
35
-
36
- # Global variables for current game state
37
- current_system = ""
38
- selected_country = ""
39
- selected_country_dict = {}
40
- game_mode = GAME_MODE_COUNTRIES
41
-
42
-
43
-
44
- def get_system_message(mode):
45
- """Generate a system message with a randomly selected location based on game mode"""
46
- global selected_country, selected_country_dict, game_mode
47
-
48
- if mode == GAME_MODE_STATES:
49
- selected_country_dict = random.choice(US_STATES_GUESS_LIST)
50
- location_type = "state"
51
- location_type_upper = "US state"
52
- else:
53
- selected_country_dict = random.choice(GEOGRAPHY_GUESS_LIST)
54
- location_type = "country"
55
- location_type_upper = "country"
56
-
57
- selected_country = selected_country_dict["name"]
58
- game_mode = mode
59
-
60
- print(f"Selected {location_type} for this session: {selected_country}")
61
- print(f"Fetching facts from: {selected_country_dict['url']}")
62
-
63
- facts = fetch_facts(game_mode, selected_country_dict["url"])
64
- print(facts)
65
-
66
- if mode == GAME_MODE_STATES:
67
- location_info = f"""
68
- State: {selected_country_dict['name']}
69
- Capital: {selected_country_dict['capital']}
70
- Nickname: {selected_country_dict['nickname']}
71
- Facts: {facts}
72
- """
73
- info_label = "STATE INFORMATION"
74
- reveal_text = "Do NOT reveal the state name"
75
- else:
76
- location_info = facts
77
- info_label = "COUNTRY FACTS"
78
- reveal_text = "Do NOT reveal the country name"
79
-
80
- return f"""You are a friendly geography game host playing 20 questions with students. You are thinking of the {location_type_upper}: {selected_country}
81
-
82
- {info_label} (use these to answer questions accurately - {reveal_text}):
83
- {location_info}
84
-
85
- RULES:
86
- NEVER reveal the {location_type} name ({selected_country}) in your responses
87
- Answer only 'Yes' or 'No' to their question
88
- When they correctly guess or ask if it is {selected_country}, respond with: 'Congratulations! The {location_type} was <<{selected_country}>>'
89
- 7. If they want to play again tell them they need to reload the page.
90
- 8. IMPORTANT: Only accept the {location_type} name "{selected_country}" as correct, but Spelling is not important and they can ask a question like it is? Do NOT accept neighboring {location_type}s, similar {location_type}s, or regions that contain this {location_type}.
91
- 9. If they guess a neighboring {location_type} or similar {location_type}, respond with "No" and continue the game.
92
- 10. Be very strict about the exact {location_type} match - only "{selected_country}" is the correct answer.
93
- 11. Use the {info_label} above to provide accurate yes/no answers - do not make up information.
94
-
95
- This is the users guess number: """
96
-
97
- current_system = ""
98
-
99
- def clean_response(response):
100
- """Clean up the response by removing unwanted metadata and formatting artifacts"""
101
- import re
102
- import json
103
-
104
- # Remove channel/commentary tags and similar artifacts
105
- response = re.sub(r'<\|channel\|>commentary to=assistant', '', response, flags=re.IGNORECASE)
106
- response = re.sub(r'<\|constrain\|>json<\|message\|>', '', response, flags=re.IGNORECASE)
107
- response = re.sub(r'<\|.*?\|>', '', response) # Remove any other <|...|> patterns
108
- response = response.replace("\\n", "\n")
109
-
110
- # Try to parse JSON response and extract the actual message
111
- try:
112
- # Look for JSON-like content
113
- json_match = re.search(r'\{[^}]*"response"\s*:\s*"([^"]*)"[^}]*\}', response)
114
- if json_match:
115
- actual_response = json_match.group(1)
116
- print(f"🔍 DEBUG - Extracted from JSON: {actual_response}")
117
- return actual_response
118
-
119
- # Try to parse as complete JSON
120
- parsed = json.loads(response.strip())
121
- if isinstance(parsed, dict) and "response" in parsed:
122
- actual_response = parsed["response"]
123
- print(f"🔍 DEBUG - Parsed JSON response: {actual_response}")
124
- return actual_response
125
- except (json.JSONDecodeError, AttributeError):
126
- # If JSON parsing fails, continue with text cleaning
127
- pass
128
-
129
- # Clean up extra whitespace and newlines
130
- response = re.sub(r'\n\s*\n', '\n', response) # Remove multiple empty lines
131
- response = response.strip() # Remove leading/trailing whitespace
132
-
133
- return response
134
-
135
- def format_game_result(response, guess_number):
136
  """Format the game result with proper styling"""
137
- global game_mode
138
- location_type = "state" if game_mode == GAME_MODE_STATES else "country"
139
 
140
  if f"The {location_type} was" in response:
141
- print(f"🔍 DEBUG - Game end detected! {location_type.capitalize()} extracted: {selected_country}")
142
  else:
143
  print("🔍 DEBUG - Regular response (no game end)")
144
 
145
  if "Congratulations" in response:
146
- return f"🎉 **Congratulations!** You correctly guessed **{selected_country}**! It took you **{guess_number}** guesses. Well done! 🎉\n\nTo play another round, please start a new conversation or reload the page."
147
  elif "Game over" in response:
148
- return f"😔 **Game Over!** You've used all 20 questions. The {location_type} I was thinking of was **{selected_country}**. 😔\n\nTo try again, please start a new conversation or reload the page."
149
 
150
  return response
151
 
@@ -163,20 +35,19 @@ def respond(
163
  """
164
  For more information on `huggingface_hub` Inference API support, please check the docs: https://huggingface.co/docs/huggingface_hub/v0.22.2/en/guides/inference
165
  """
166
- global current_system, guess_number
167
 
168
  # If this is the start of a new conversation (empty history), generate a new country/state
169
  if not history:
170
- guess_number = 0
171
- if game_mode_selection == GAME_MODE_STATES:
172
- current_system = get_system_message(GAME_MODE_STATES)
173
- print(f"🔍 DEBUG - New session started, selected state: {selected_country}")
174
  else:
175
- current_system = get_system_message(GAME_MODE_COUNTRIES)
176
- print(f"🔍 DEBUG - New session started, selected country: {selected_country}")
177
 
178
- guess_number += 1
179
- messages = [{"role": "system", "content": current_system + str(guess_number)}]
180
  messages.append({"role": "user", "content": message})
181
 
182
  # Debug: Calculate approximate input token count
@@ -198,30 +69,16 @@ def respond(
198
  elif role == "assistant":
199
  print(f"🔍 DEBUG - Assistant message: {content[:50]}...")
200
 
201
- # Choose client based on whether we're running locally or in the cloud
202
- if LOCAL_MODE:
203
- # Running locally with custom model settings
204
- try:
205
- # Use local inference server
206
- client = InferenceClient(model=BASE_URL, token=LOCAL_TOKEN)
207
- except Exception as e:
208
- return f"Error connecting to local model: {str(e)}"
209
- else:
210
- hf_token = os.environ["HF_TOKEN"]
211
- client = InferenceClient(token=hf_token, model=MODEL_NAME)
212
 
213
  response = ""
214
  output_token_count = 0
215
 
216
  try:
217
- for message_chunk in client.chat_completion(
218
  messages,
219
- model=MODEL_NAME,
220
- max_tokens=max_tokens,
221
  stream=True,
222
  response_format={"type": "text"},
223
- temperature=temperature,
224
- top_p=top_p,
225
  ):
226
  choices = message_chunk.choices
227
  token = ""
@@ -239,16 +96,16 @@ def respond(
239
  print(f"🔍 DEBUG - Raw response: {response}")
240
 
241
  # Clean the response to remove unwanted artifacts
242
- response = clean_response(response)
243
  print(f"🔍 DEBUG - Cleaned response: {response}")
244
 
245
  # Check if this is a game end response and format it nicely
246
  if "The country was" in response or "The state was" in response:
247
- print(f"🔍 DEBUG - Game end detected! Location extracted: {selected_country}")
248
- return format_game_result(response, guess_number)
249
- elif guess_number == 20:
250
- print(f"🔍 DEBUG - Maximum guesses reached: {guess_number}")
251
- return format_game_result(response, guess_number)
252
  else:
253
  print("🔍 DEBUG - Regular response (no game end)")
254
  return response
@@ -256,13 +113,9 @@ def respond(
256
  return f"Error during inference: {str(e)}"
257
 
258
 
259
- """
260
- For information on how to customize the ChatInterface, peruse the gradio docs: https://www.gradio.app/docs/chatinterface
261
- """
262
-
263
  # Create description based on mode
264
- if LOCAL_MODE:
265
- description = f"🏠 Running locally with {MODEL_NAME}. Choose your game mode and I'll think of a location for you to guess with 20 yes or no questions!"
266
  else:
267
  description = "Choose your game mode and I'll think of a location for you to guess with 20 yes or no questions!"
268
 
@@ -292,9 +145,9 @@ with gr.Blocks() as demo:
292
  gr.Markdown(description)
293
 
294
  # Game mode selection at the top
295
- game_mode_dropdown = gr.Dropdown(
296
- choices=[GAME_MODE_STATES, GAME_MODE_COUNTRIES],
297
- value=GAME_MODE_STATES,
298
  label="Game Mode",
299
  info="Choose what type of location to guess"
300
  )
@@ -305,7 +158,7 @@ with gr.Blocks() as demo:
305
  type="messages",
306
  examples=examples,
307
  cache_examples=False,
308
- additional_inputs=[game_mode_dropdown],
309
  )
310
 
311
 
 
1
  import gradio as gr
2
+ import game
3
+ import ai
4
 
 
 
 
 
5
 
 
6
 
 
 
7
 
8
+ def format_game_result(response):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  """Format the game result with proper styling"""
10
+ location_type = "state" if game.mode == game.MODE_STATES else "country"
 
11
 
12
  if f"The {location_type} was" in response:
13
+ print(f"🔍 DEBUG - Game end detected! {location_type.capitalize()} extracted: {game.selected_country}")
14
  else:
15
  print("🔍 DEBUG - Regular response (no game end)")
16
 
17
  if "Congratulations" in response:
18
+ return f"🎉 **Congratulations!** You correctly guessed **{game.selected_country}**! It took you **{game.guess_number}** guesses. Well done! 🎉\n\nTo play another round, please start a new conversation or reload the page."
19
  elif "Game over" in response:
20
+ return f"😔 **Game Over!** You've used all 20 questions. The {location_type} I was thinking of was **{game.selected_country}**. 😔\n\nTo try again, please start a new conversation or reload the page."
21
 
22
  return response
23
 
 
35
  """
36
  For more information on `huggingface_hub` Inference API support, please check the docs: https://huggingface.co/docs/huggingface_hub/v0.22.2/en/guides/inference
37
  """
 
38
 
39
  # If this is the start of a new conversation (empty history), generate a new country/state
40
  if not history:
41
+ game.guess_number = 0
42
+ if game_mode_selection == game.MODE_STATES:
43
+ game.current_system = game.get_system_message(game.MODE_STATES)
44
+ print(f"🔍 DEBUG - New session started, selected state: {game.selected_country}")
45
  else:
46
+ game.current_system = game.get_system_message(game.MODE_COUNTRIES)
47
+ print(f"🔍 DEBUG - New session started, selected country: {game.selected_country}")
48
 
49
+ game.guess_number += 1
50
+ messages = [{"role": "system", "content": game.current_system + str(game.guess_number)}]
51
  messages.append({"role": "user", "content": message})
52
 
53
  # Debug: Calculate approximate input token count
 
69
  elif role == "assistant":
70
  print(f"🔍 DEBUG - Assistant message: {content[:50]}...")
71
 
72
+
 
 
 
 
 
 
 
 
 
 
73
 
74
  response = ""
75
  output_token_count = 0
76
 
77
  try:
78
+ for message_chunk in ai.client.chat_completion(
79
  messages,
 
 
80
  stream=True,
81
  response_format={"type": "text"},
 
 
82
  ):
83
  choices = message_chunk.choices
84
  token = ""
 
96
  print(f"🔍 DEBUG - Raw response: {response}")
97
 
98
  # Clean the response to remove unwanted artifacts
99
+ response = ai.clean_response(response)
100
  print(f"🔍 DEBUG - Cleaned response: {response}")
101
 
102
  # Check if this is a game end response and format it nicely
103
  if "The country was" in response or "The state was" in response:
104
+ print(f"🔍 DEBUG - Game end detected! Location extracted: {game.selected_country}")
105
+ return format_game_result(response)
106
+ elif game.guess_number == 20:
107
+ print(f"🔍 DEBUG - Maximum guesses reached: {game.guess_number}")
108
+ return format_game_result(response)
109
  else:
110
  print("🔍 DEBUG - Regular response (no game end)")
111
  return response
 
113
  return f"Error during inference: {str(e)}"
114
 
115
 
 
 
 
 
116
  # Create description based on mode
117
+ if ai.LOCAL_MODE:
118
+ description = f"🏠 Running locally with {ai.MODEL_NAME}. Choose your game mode and I'll think of a location for you to guess with 20 yes or no questions!"
119
  else:
120
  description = "Choose your game mode and I'll think of a location for you to guess with 20 yes or no questions!"
121
 
 
145
  gr.Markdown(description)
146
 
147
  # Game mode selection at the top
148
+ game.mode_dropdown = gr.Dropdown(
149
+ choices=[game.MODE_STATES, game.MODE_COUNTRIES],
150
+ value=game.MODE_STATES,
151
  label="Game Mode",
152
  info="Choose what type of location to guess"
153
  )
 
158
  type="messages",
159
  examples=examples,
160
  cache_examples=False,
161
+ additional_inputs=[game.mode_dropdown],
162
  )
163
 
164
 
game.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ from rag import fetch_facts
4
+ import random
5
+
6
+ # Load country list from countries.json
7
+ COUNTRIES_JSON_PATH = os.path.join(os.path.dirname(__file__), "countries.json")
8
+ with open(COUNTRIES_JSON_PATH, "r", encoding="utf-8") as f:
9
+ GEOGRAPHY_GUESS_LIST = json.load(f)
10
+
11
+ STATES_JSON_PATH = os.path.join(os.path.dirname(__file__), "us_states.json")
12
+ with open(STATES_JSON_PATH, "r", encoding="utf-8") as f:
13
+ US_STATES_GUESS_LIST = json.load(f)
14
+
15
+ # Game mode constants
16
+ MODE_COUNTRIES = "Countries of the World"
17
+ MODE_STATES = "US States"
18
+
19
+
20
+ current_system = ""
21
+ selected_country = ""
22
+ selected_country_dict = {}
23
+ mode = MODE_COUNTRIES
24
+ guess_number = 0
25
+
26
+ def get_system_message(game_mode):
27
+ """Generate a system message with a randomly selected location based on game mode"""
28
+ global selected_country, selected_country_dict, mode
29
+
30
+ if game_mode == MODE_STATES:
31
+ selected_country_dict = random.choice(US_STATES_GUESS_LIST)
32
+ location_type = "state"
33
+ location_type_upper = "US state"
34
+ else:
35
+ selected_country_dict = random.choice(GEOGRAPHY_GUESS_LIST)
36
+ location_type = "country"
37
+ location_type_upper = "country"
38
+
39
+ selected_country = selected_country_dict["name"]
40
+ mode = game_mode
41
+
42
+ print(f"Selected {location_type} for this session: {selected_country}")
43
+ print(f"Fetching facts from: {selected_country_dict['url']}")
44
+
45
+ facts = fetch_facts(game_mode, selected_country_dict["url"])
46
+ print(facts)
47
+
48
+ if game_mode == MODE_STATES:
49
+ location_info = f"""
50
+ State: {selected_country_dict['name']}
51
+ Capital: {selected_country_dict['capital']}
52
+ Nickname: {selected_country_dict['nickname']}
53
+ Facts: {facts}
54
+ """
55
+ info_label = "STATE INFORMATION"
56
+ reveal_text = "Do NOT reveal the state name"
57
+ else:
58
+ location_info = facts
59
+ info_label = "COUNTRY FACTS"
60
+ reveal_text = "Do NOT reveal the country name"
61
+
62
+ return f"""You are a friendly geography game host playing 20 questions with students. You are thinking of the {location_type_upper}: {selected_country}
63
+
64
+ {info_label} (use these to answer questions accurately - {reveal_text}):
65
+ {location_info}
66
+
67
+ RULES:
68
+ NEVER reveal the {location_type} name ({selected_country}) in your responses
69
+ Answer only 'Yes' or 'No' to their question
70
+ When they correctly guess or ask if it is {selected_country}, respond with: 'Congratulations! The {location_type} was <<{selected_country}>>'
71
+ 7. If they want to play again tell them they need to reload the page.
72
+ 8. IMPORTANT: Only accept the {location_type} name "{selected_country}" as correct, but Spelling is not important and they can ask a question like it is? Do NOT accept neighboring {location_type}s, similar {location_type}s, or regions that contain this {location_type}.
73
+ 9. If they guess a neighboring {location_type} or similar {location_type}, respond with "No" and continue the
74
+ 10. Be very strict about the exact {location_type} match - only "{selected_country}" is the correct answer.
75
+ 11. Use the {info_label} above to provide accurate yes/no answers - do not make up information.
76
+
77
+ This is the users guess number: """