ZygAI commited on
Commit
ad91d05
·
verified ·
1 Parent(s): 705b936

Upload 3 files

Browse files
Files changed (2) hide show
  1. README.md +1 -1
  2. app.py +35 -31
README.md CHANGED
@@ -13,7 +13,7 @@ pinned: false
13
 
14
  An AI-involved multiple-choice I Spy game from ZygAI Research.
15
 
16
- The app uses `meta-llama/Llama-3.1-8B-Instruct` as the game master. Llama creates each clue, four possible answers, the correct answer, and a short explanation. Players answer 10 rounds and get a final score.
17
 
18
  ## Hugging Face Space Setup
19
 
 
13
 
14
  An AI-involved multiple-choice I Spy game from ZygAI Research.
15
 
16
+ The app uses `meta-llama/Llama-3.1-8B-Instruct` as the game master. When the player starts, Llama generates all 10 clues, answer sets, correct answers, and explanations in one request. The game then plays from that cached set, which keeps the demo cost-effective.
17
 
18
  ## Hugging Face Space Setup
19
 
app.py CHANGED
@@ -82,7 +82,7 @@ def get_client() -> InferenceClient | None:
82
  return InferenceClient(model=MODEL_ID, token=HF_TOKEN)
83
 
84
 
85
- def call_llama(messages: list[dict[str, str]], max_tokens: int = 500) -> str:
86
  client = get_client()
87
  if not client:
88
  raise RuntimeError("HF_TOKEN is not configured.")
@@ -115,7 +115,25 @@ def fallback_round(round_number: int) -> dict[str, Any]:
115
  return data
116
 
117
 
118
- def generate_round(round_number: int, previous_answers: list[str]) -> dict[str, Any]:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  try:
120
  model_text = call_llama(
121
  [
@@ -123,37 +141,25 @@ def generate_round(round_number: int, previous_answers: list[str]) -> dict[str,
123
  {
124
  "role": "user",
125
  "content": (
126
- f"Create round {round_number} of {TOTAL_ROUNDS} for I See. "
127
- "Make an I Spy-style clue and four answer options. "
128
- "The answer should be one of the options. "
129
- "Avoid repeating these previous answers: "
130
- f"{', '.join(previous_answers) or 'none'}. "
131
  "Return only JSON with this exact shape: "
132
- '{"clue":"I see something that...","answer":"An umbrella",'
133
  '"options":["An umbrella","A chair","A candle","A spoon"],'
134
- '"explanation":"The umbrella keeps rain off you."}'
135
  ),
136
  },
137
  ]
138
  )
139
  data = extract_json(model_text)
140
- clue = clean_text(data["clue"])
141
- answer = clean_text(data["answer"])
142
- options = [clean_text(option) for option in data["options"]]
143
- explanation = clean_text(data.get("explanation", ""))
144
-
145
- if len(options) != 4 or answer not in options:
146
- raise ValueError("Round must have four options and include the answer.")
147
-
148
- random.shuffle(options)
149
- return {
150
- "clue": clue,
151
- "answer": answer,
152
- "options": options,
153
- "explanation": explanation or f"The answer is {answer}.",
154
- }
155
  except Exception:
156
- return fallback_round(round_number)
157
 
158
 
159
  def option_text(round_data: dict[str, Any], index: int) -> str:
@@ -179,13 +185,13 @@ def render_question(state: dict[str, Any] | None) -> tuple[str, str, str, str, s
179
 
180
 
181
  def start_game() -> tuple[dict[str, Any], str, str, str, str, str, str, str, str]:
182
- first_round = generate_round(1, [])
183
  state = {
184
  "round_number": 1,
185
  "score": 0,
186
  "answered": False,
187
- "previous_answers": [first_round["answer"]],
188
- "round": first_round,
189
  }
190
  return state, *render_question(state), "", gr.update(visible=False)
191
 
@@ -226,13 +232,11 @@ def next_round(state: dict[str, Any] | None) -> tuple[dict[str, Any] | None, str
226
  return state, message, "FINISHED", f"SCORE: {score}", "A", "B", "C", "D", "Start a new game to play again.", gr.update(visible=False)
227
 
228
  round_number = state["round_number"] + 1
229
- round_data = generate_round(round_number, state["previous_answers"])
230
  state.update(
231
  {
232
  "round_number": round_number,
233
  "answered": False,
234
- "round": round_data,
235
- "previous_answers": state["previous_answers"] + [round_data["answer"]],
236
  }
237
  )
238
  return state, *render_question(state), "", gr.update(visible=False)
 
82
  return InferenceClient(model=MODEL_ID, token=HF_TOKEN)
83
 
84
 
85
+ def call_llama(messages: list[dict[str, str]], max_tokens: int = 1800) -> str:
86
  client = get_client()
87
  if not client:
88
  raise RuntimeError("HF_TOKEN is not configured.")
 
115
  return data
116
 
117
 
118
+ def validate_round(data: dict[str, Any]) -> dict[str, Any]:
119
+ clue = clean_text(data["clue"])
120
+ answer = clean_text(data["answer"])
121
+ options = [clean_text(option) for option in data["options"]]
122
+ explanation = clean_text(data.get("explanation", ""))
123
+
124
+ if len(options) != 4 or answer not in options:
125
+ raise ValueError("Round must have four options and include the answer.")
126
+
127
+ random.shuffle(options)
128
+ return {
129
+ "clue": clue,
130
+ "answer": answer,
131
+ "options": options,
132
+ "explanation": explanation or f"The answer is {answer}.",
133
+ }
134
+
135
+
136
+ def generate_game() -> list[dict[str, Any]]:
137
  try:
138
  model_text = call_llama(
139
  [
 
141
  {
142
  "role": "user",
143
  "content": (
144
+ f"Create all {TOTAL_ROUNDS} rounds for one I See demo game. "
145
+ "Each round needs a short I Spy-style clue, four answer options, "
146
+ "one correct answer that appears exactly in options, and one short explanation. "
147
+ "Use varied everyday objects. Do not repeat correct answers. "
 
148
  "Return only JSON with this exact shape: "
149
+ '{"rounds":[{"clue":"I see something that...","answer":"An umbrella",'
150
  '"options":["An umbrella","A chair","A candle","A spoon"],'
151
+ '"explanation":"The umbrella keeps rain off you."}]}'
152
  ),
153
  },
154
  ]
155
  )
156
  data = extract_json(model_text)
157
+ rounds = [validate_round(round_data) for round_data in data["rounds"][:TOTAL_ROUNDS]]
158
+ if len(rounds) != TOTAL_ROUNDS:
159
+ raise ValueError("Game must include exactly 10 rounds.")
160
+ return rounds
 
 
 
 
 
 
 
 
 
 
 
161
  except Exception:
162
+ return [fallback_round(round_number) for round_number in range(1, TOTAL_ROUNDS + 1)]
163
 
164
 
165
  def option_text(round_data: dict[str, Any], index: int) -> str:
 
185
 
186
 
187
  def start_game() -> tuple[dict[str, Any], str, str, str, str, str, str, str, str]:
188
+ rounds = generate_game()
189
  state = {
190
  "round_number": 1,
191
  "score": 0,
192
  "answered": False,
193
+ "rounds": rounds,
194
+ "round": rounds[0],
195
  }
196
  return state, *render_question(state), "", gr.update(visible=False)
197
 
 
232
  return state, message, "FINISHED", f"SCORE: {score}", "A", "B", "C", "D", "Start a new game to play again.", gr.update(visible=False)
233
 
234
  round_number = state["round_number"] + 1
 
235
  state.update(
236
  {
237
  "round_number": round_number,
238
  "answered": False,
239
+ "round": state["rounds"][round_number - 1],
 
240
  }
241
  )
242
  return state, *render_question(state), "", gr.update(visible=False)