tmzh commited on
Commit
ef8dd8b
1 Parent(s): 4e357cc

refactoring

Browse files
Files changed (1) hide show
  1. app.py +95 -141
app.py CHANGED
@@ -1,89 +1,75 @@
1
  import json
2
  import os
3
  import random
 
4
 
5
  import google.generativeai as genai
6
  import gradio as gr
7
  from jinja2 import Environment, FileSystemLoader
8
 
9
  GOOGLE_API_KEY = os.getenv('GOOGLE_API_KEY')
10
-
11
  genai.configure(api_key=GOOGLE_API_KEY)
12
 
13
- model = genai.GenerativeModel("gemini-1.5-flash-latest",
14
  generation_config={"response_mime_type": "application/json"})
15
 
16
-
17
- def merge_games(clues, num_merges=10):
18
- """Generates around 10 merges of words from the given clues.
19
-
20
- Args:
21
- clues: A list of clues, where each clue is a list containing the words, the answer, and the explanation.
22
- num_merges: The approximate number of merges to generate (default: 10).
23
-
24
- Returns:
25
- A list of tuples, where each tuple contains the merged words and the indices of the selected rows.
26
- """
27
-
28
- merged = []
29
- while len(merged) < num_merges:
30
- num_rows = random.choice([3, 4])
31
- selected_rows = random.sample(range(len(clues)), num_rows)
32
- words_list = [word for row in [clues[i][0] for i in selected_rows] for word in row]
33
- if len(words_list) in [8, 9]:
34
- merged.append((words_list, selected_rows))
35
-
36
- return merged
37
-
38
-
39
- example_clues = [
40
  (['ARROW', 'TIE', 'HONOR'], 'BOW', 'such as a bow and arrow, a bow tie, or a bow as a sign of honor'),
41
  (['DOG', 'TREE'], 'BARK', 'such as the sound a dog makes, or a tree is made of bark'),
42
- (['MONEY', 'RIVER', 'ROB', 'BLOOD'], 'CRIME', 'such as money being stolen, a river being a potential crime scene, '
43
- 'robbery, or blood being a result of a violent crime'),
44
- (['BEEF', 'TURKEY', 'FIELD', 'GRASS'], 'GROUND',
45
- 'such as ground beef, a turkey being a ground-dwelling bird, a field or grass being a type of ground'),
46
- (['BANK', 'GUITAR', 'LIBRARY'], 'NOTE',
47
- 'such as a bank note, a musical note on a guitar, or a note being a written comment in a library book'),
48
  (['ROOM', 'PIANO', 'TYPEWRITER'], 'KEYS', 'such as a room key, piano keys, or typewriter keys'),
49
  (['TRAFFIC', 'RADAR', 'PHONE'], 'SIGNAL', 'such as traffic signals, radar signals, or phone signals'),
50
- (['FENCE', 'PICTURE', 'COOKIE'], 'FRAME',
51
- 'such as a frame around a yard, a picture frame, or a cookie cutter being a type of frame'),
52
  (['YARN', 'VIOLIN', 'DRESS'], 'STRING', 'strings like material, instrument, clothing fastener'),
53
- (['JUMP', 'FLOWER', 'CLOCK'], 'SPRING',
54
- 'such as jumping, flowers blooming in the spring, or a clock having a sprint component'),
55
- (['SPY', 'KNIFE'], 'WAR',
56
- 'Both relate to aspects of war, such as spies being involved in war or knives being used as weapons'),
57
  (['STADIUM', 'SHOE', 'FIELD'], 'SPORT', 'Sports like venues, equipment, playing surfaces'),
58
- (['TEACHER', 'CLUB'], 'SCHOOL',
59
- 'such as a teacher being a school staff member or a club being a type of school organization'),
60
  (['CYCLE', 'ARMY', 'COURT', 'FEES'], 'CHARGE', 'charges like electricity, battle, legal, payments'),
61
- (['FRUIT', 'MUSIC', 'TRAFFIC', 'STUCK'], 'JAM',
62
- 'Jams such as fruit jam, a music jam session, traffic jam, or being stuck in a jam'),
63
- (['POLICE', 'DOG', 'THIEF'], 'CRIME',
64
- 'such as police investigating crimes, dogs being used to detect crimes, or a thief committing a crime'),
65
- (['ARCTIC', 'SHUT', 'STAMP'], 'SEAL',
66
- 'such as the Arctic being home to seals, or shutting a seal on an envelope, or a stamp being a type of seal'),
67
  ]
68
 
69
- example_groupings = []
70
- merges = merge_games(example_clues, 5)
71
- for merged_words, indices in merges:
72
- groups = [{
73
- "words": example_clues[i][0],
74
- "clue": example_clues[i][1],
75
- "explanation": example_clues[i][2]
76
- } for i in indices]
77
- example_groupings.append((merged_words, json.dumps(groups, separators=(',', ':'))))
78
-
79
-
80
- def render_jinja2_template(template, system_prompt, history, query):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  env = Environment(loader=FileSystemLoader('.'))
82
  template = env.from_string(template)
83
  return template.render(system_prompt=system_prompt, history=history, query=query)
84
 
85
-
86
- def group_words(words):
87
  template = '''
88
  {% for example in history %}
89
  INPUT:
@@ -101,24 +87,15 @@ def group_words(words):
101
  Return: Groups
102
  '''
103
 
104
- grouping_system_prompt = ("You are an assistant for the game Codenames. Your task is to help players by grouping a "
105
- "given group of secrets into 3 to 4 groups of 2 to 4 words. Each group should consist of secrets that "
106
- "share a common theme or other word connections such as homonym, hypernyms or synonyms. Avoid clues that are not too generic or not unique enough to be guessed easily")
107
-
108
- prompt = render_jinja2_template(template, grouping_system_prompt, example_groupings, words)
109
- # print(prompt)
110
- raw_response = model.generate_content(
111
- prompt,
112
- generation_config={'top_k': 3, 'temperature': 1.1})
113
- response = json.loads(raw_response.text)
114
-
115
- print("Grouping words:", words)
116
 
117
- print("Got groupings: ", json.dumps(response, indent=4))
118
- return response
 
119
 
120
-
121
- def generate_clues(group):
122
  template = '''
123
  {% for example in history %}
124
  INPUT:
@@ -130,99 +107,76 @@ def generate_clues(group):
130
  {{ query }}
131
  OUTPUT:
132
 
133
-
134
  {{ system }}
135
 
136
  Clue = {'clue': str, 'explanation': str}
137
  Return: Clue
138
  '''
139
 
140
- clue_system_prompt = ("You are a codenames game companion. Your task is to give a single word clue related to "
141
- "a given group of words. You will only respond with a single word clue. The clue can be a common theme or other word connections such as homonym, hypernyms or synonyms. Avoid clues that are not too generic or not unique enough to be guessed easily")
142
-
143
- prompt = render_jinja2_template(template, clue_system_prompt, example_clues, group)
144
 
145
- raw_response = model.generate_content(
146
- prompt,
147
- generation_config={'top_k': 3, 'temperature': 1.1})
148
- response = json.loads(raw_response.text)
149
 
150
- print("Generating clues for: ", group)
151
 
152
- print("Got clue: ", json.dumps(response, indent=4))
153
- return response
 
 
 
 
 
 
 
154
 
 
 
 
 
155
 
156
- def process_image(img):
157
- raw_response = model.generate_content(['Identify the words in this game of Codenames. Provide only a list of '
158
- 'words. Provide the words in capital letters only. Group these words into '
159
- '6 or 8 groups that can be guessed together using a single word clue in '
160
- 'the game of codenames. Give a response as json of the form: {"Game": '
161
- '<list of words in the game>}', img], stream=True)
162
- raw_response.resolve()
163
- response = json.loads(raw_response.text)
164
- words = response['Game']
165
- return gr.update(choices=words, value=words)
166
 
 
 
 
 
167
 
168
- with gr.Blocks() as demo:
169
- gr.Markdown("# *Codenames* clue generator")
170
- gr.Markdown("Provide a list of words to generate a clue")
 
171
 
172
  with gr.Row():
173
  game_image = gr.Image(type="pil")
174
- word_list_input = gr.Dropdown(label="Detected words",
175
- choices=[],
176
- multiselect=True,
177
- interactive=True)
178
 
179
  with gr.Row():
180
  detect_words_button = gr.Button("Detect Words")
181
  group_words_button = gr.Button("Group Words")
182
 
183
- dropdowns, buttons, outputs = [], [], []
184
 
185
  for i in range(4):
186
  with gr.Row():
187
- group_input = gr.Dropdown(label=f"Group {i + 1}",
188
- choices=[],
189
- allow_custom_value=True,
190
- multiselect=True,
191
- interactive=True)
192
  clue_button = gr.Button("Generate Clue", size='sm')
193
  clue_output = gr.Textbox(label=f"Clue {i + 1}")
194
- dropdowns.append(group_input)
195
- buttons.append(clue_button)
196
- outputs.append(clue_output)
197
-
198
-
199
- def pad_or_truncate(lst, n=4):
200
- # Ensure the length of the list is at most n
201
- truncated_lst = lst[:n]
202
- return truncated_lst + (n - len(truncated_lst)) * [None]
203
-
204
-
205
- def group_words_callback(words):
206
- groups = group_words(words)
207
- groups = pad_or_truncate(groups, 4)
208
- print("Got groups: ", groups, type(groups))
209
- return [gr.update(value=groups[i]["words"], choices=words, info=groups[i]["explanation"]) for i in range(4)]
210
-
211
-
212
- def generate_clues_callback(group):
213
- print("Generating clues: ", group)
214
- g = generate_clues(group)
215
- return gr.update(value=g['clue'], info=g['explanation'])
216
-
217
 
218
- detect_words_button.click(fn=process_image,
219
- inputs=game_image,
220
- outputs=[word_list_input])
221
- group_words_button.click(fn=group_words_callback,
222
- inputs=word_list_input,
223
- outputs=dropdowns)
224
 
225
  for i in range(4):
226
- buttons[i].click(generate_clues_callback, inputs=dropdowns[i], outputs=outputs[i])
227
 
228
  demo.launch(share=True, debug=True)
 
1
  import json
2
  import os
3
  import random
4
+ from typing import List, Tuple
5
 
6
  import google.generativeai as genai
7
  import gradio as gr
8
  from jinja2 import Environment, FileSystemLoader
9
 
10
  GOOGLE_API_KEY = os.getenv('GOOGLE_API_KEY')
 
11
  genai.configure(api_key=GOOGLE_API_KEY)
12
 
13
+ MODEL = genai.GenerativeModel("gemini-1.5-flash-latest",
14
  generation_config={"response_mime_type": "application/json"})
15
 
16
+ # Example clues and groupings
17
+ EXAMPLE_CLUES = [
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  (['ARROW', 'TIE', 'HONOR'], 'BOW', 'such as a bow and arrow, a bow tie, or a bow as a sign of honor'),
19
  (['DOG', 'TREE'], 'BARK', 'such as the sound a dog makes, or a tree is made of bark'),
20
+ (['MONEY', 'RIVER', 'ROB', 'BLOOD'], 'CRIME', 'such as money being stolen, a river being a potential crime scene, robbery, or blood being a result of a violent crime'),
21
+ (['BEEF', 'TURKEY', 'FIELD', 'GRASS'], 'GROUND', 'such as ground beef, a turkey being a ground-dwelling bird, a field or grass being a type of ground'),
22
+ (['BANK', 'GUITAR', 'LIBRARY'], 'NOTE', 'such as a bank note, a musical note on a guitar, or a note being a written comment in a library book'),
 
 
 
23
  (['ROOM', 'PIANO', 'TYPEWRITER'], 'KEYS', 'such as a room key, piano keys, or typewriter keys'),
24
  (['TRAFFIC', 'RADAR', 'PHONE'], 'SIGNAL', 'such as traffic signals, radar signals, or phone signals'),
25
+ (['FENCE', 'PICTURE', 'COOKIE'], 'FRAME', 'such as a frame around a yard, a picture frame, or a cookie cutter being a type of frame'),
 
26
  (['YARN', 'VIOLIN', 'DRESS'], 'STRING', 'strings like material, instrument, clothing fastener'),
27
+ (['JUMP', 'FLOWER', 'CLOCK'], 'SPRING', 'such as jumping, flowers blooming in the spring, or a clock having a sprint component'),
28
+ (['SPY', 'KNIFE'], 'WAR', 'Both relate to aspects of war, such as spies being involved in war or knives being used as weapons'),
 
 
29
  (['STADIUM', 'SHOE', 'FIELD'], 'SPORT', 'Sports like venues, equipment, playing surfaces'),
30
+ (['TEACHER', 'CLUB'], 'SCHOOL', 'such as a teacher being a school staff member or a club being a type of school organization'),
 
31
  (['CYCLE', 'ARMY', 'COURT', 'FEES'], 'CHARGE', 'charges like electricity, battle, legal, payments'),
32
+ (['FRUIT', 'MUSIC', 'TRAFFIC', 'STUCK'], 'JAM', 'Jams such as fruit jam, a music jam session, traffic jam, or being stuck in a jam'),
33
+ (['POLICE', 'DOG', 'THIEF'], 'CRIME', 'such as police investigating crimes, dogs being used to detect crimes, or a thief committing a crime'),
34
+ (['ARCTIC', 'SHUT', 'STAMP'], 'SEAL', 'such as the Arctic being home to seals, or shutting a seal on an envelope, or a stamp being a type of seal'),
 
 
 
35
  ]
36
 
37
+ def create_example_groupings(clues: List[Tuple[List[str], str, str]], num_groups: int = 5) -> List[Tuple[List[str], str]]:
38
+ """Creates example groupings from the given clues."""
39
+ merged = create_random_word_groups(clues, num_groups)
40
+ return [
41
+ (
42
+ merged_words,
43
+ json.dumps([{
44
+ "words": clues[i][0],
45
+ "clue": clues[i][1],
46
+ "explanation": clues[i][2]
47
+ } for i in indices], separators=(',', ':'))
48
+ )
49
+ for merged_words, indices in merged
50
+ ]
51
+
52
+ EXAMPLE_GROUPINGS = create_example_groupings(EXAMPLE_CLUES)
53
+
54
+ def create_random_word_groups(clues: List[Tuple[List[str], str, str]], num_groups: int = 10) -> List[Tuple[List[str], List[int]]]:
55
+ """Creates random groups of words from the given clues."""
56
+ word_groups = []
57
+ while len(word_groups) < num_groups:
58
+ group_size = random.choice([3, 4])
59
+ selected_indices = random.sample(range(len(clues)), group_size)
60
+ words = [word for row in [clues[i][0] for i in selected_indices] for word in row]
61
+ if len(words) in [8, 9]:
62
+ word_groups.append((words, selected_indices))
63
+ return word_groups
64
+
65
+ def render_template(template: str, system_prompt: str, history: List[Tuple], query: str) -> str:
66
+ """Renders a Jinja2 template with the given parameters."""
67
  env = Environment(loader=FileSystemLoader('.'))
68
  template = env.from_string(template)
69
  return template.render(system_prompt=system_prompt, history=history, query=query)
70
 
71
+ def group_words(words: List[str]) -> List[dict]:
72
+ """Groups the given words using the AI model."""
73
  template = '''
74
  {% for example in history %}
75
  INPUT:
 
87
  Return: Groups
88
  '''
89
 
90
+ grouping_prompt = ("You are an assistant for the game Codenames. Group the given words into 3 to 4 sets of 2 to 4 words each. "
91
+ "Each group should share a common theme or word connection. Avoid generic or easily guessable clues.")
 
 
 
 
 
 
 
 
 
 
92
 
93
+ prompt = render_template(template, grouping_prompt, EXAMPLE_GROUPINGS, words)
94
+ response = MODEL.generate_content(prompt, generation_config={'top_k': 3, 'temperature': 1.1})
95
+ return json.loads(response.text)
96
 
97
+ def generate_clue(group: List[str]) -> dict:
98
+ """Generates a clue for the given group of words using the AI model."""
99
  template = '''
100
  {% for example in history %}
101
  INPUT:
 
107
  {{ query }}
108
  OUTPUT:
109
 
 
110
  {{ system }}
111
 
112
  Clue = {'clue': str, 'explanation': str}
113
  Return: Clue
114
  '''
115
 
116
+ clue_prompt = ("As a Codenames game companion, provide a single-word clue for the given group of words. "
117
+ "The clue should relate to a common theme or word connection. DO NOT reuse any of the given "
118
+ "words as a clue. Avoid generic or easily guessable clues.")
 
119
 
120
+ prompt = render_template(template, clue_prompt, EXAMPLE_CLUES, group)
121
+ response = MODEL.generate_content(prompt, generation_config={'top_k': 3, 'temperature': 1.1})
122
+ return json.loads(response.text)
 
123
 
 
124
 
125
+ def process_image(img) -> gr.update:
126
+ """Processes the uploaded image and extracts words for the game."""
127
+ prompt = ('Identify the words in this Codenames game image. Provide only a list of words in capital letters. '
128
+ 'Group these words into 6 or 8 sets that can be guessed together using a single-word clue. '
129
+ 'Respond with JSON in the format: {"Game": <list of words in the game>}')
130
+ response = MODEL.generate_content([prompt, img], stream=True)
131
+ response.resolve()
132
+ words = json.loads(response.text)['Game']
133
+ return gr.update(choices=words, value=words)
134
 
135
+ def pad_or_truncate(lst: List, n: int = 4) -> List:
136
+ """Ensures the list has exactly n elements, padding with None if necessary."""
137
+ truncated_lst = lst[:n]
138
+ return truncated_lst + (n - len(truncated_lst)) * [{}]
139
 
140
+ def group_words_callback(words: List[str]) -> List[gr.update]:
141
+ """Callback function for grouping words."""
142
+ groups = group_words(words)
143
+ groups = pad_or_truncate(groups, 4)
144
+ return [gr.update(value=groups[i].get("words", ""), choices=words, info=groups[i].get("explanation","")) for i in range(4)]
 
 
 
 
 
145
 
146
+ def generate_clue_callback(group: List[str]) -> gr.update:
147
+ """Callback function for generating clues."""
148
+ clue = generate_clue(group)
149
+ return gr.update(value=clue['clue'], info=clue['explanation'])
150
 
151
+ # UI Setup
152
+ with gr.Blocks(theme=gr.themes.Monochrome()) as demo:
153
+ gr.Markdown("# *Codenames* Clue Generator")
154
+ gr.Markdown("Provide a list of words to generate clues")
155
 
156
  with gr.Row():
157
  game_image = gr.Image(type="pil")
158
+ word_list_input = gr.Dropdown(label="Detected words", choices=[], multiselect=True, interactive=True)
 
 
 
159
 
160
  with gr.Row():
161
  detect_words_button = gr.Button("Detect Words")
162
  group_words_button = gr.Button("Group Words")
163
 
164
+ group_inputs, clue_buttons, clue_outputs = [], [], []
165
 
166
  for i in range(4):
167
  with gr.Row():
168
+ group_input = gr.Dropdown(label=f"Group {i + 1}", choices=[], allow_custom_value=True, multiselect=True, interactive=True)
 
 
 
 
169
  clue_button = gr.Button("Generate Clue", size='sm')
170
  clue_output = gr.Textbox(label=f"Clue {i + 1}")
171
+ group_inputs.append(group_input)
172
+ clue_buttons.append(clue_button)
173
+ clue_outputs.append(clue_output)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
 
175
+ # Event handlers
176
+ detect_words_button.click(fn=process_image, inputs=game_image, outputs=[word_list_input])
177
+ group_words_button.click(fn=group_words_callback, inputs=word_list_input, outputs=group_inputs)
 
 
 
178
 
179
  for i in range(4):
180
+ clue_buttons[i].click(generate_clue_callback, inputs=group_inputs[i], outputs=clue_outputs[i])
181
 
182
  demo.launch(share=True, debug=True)