BhargavMN commited on
Commit
30aba87
Β·
1 Parent(s): f7f184b

Hide and seek dataset for fine tuninig

Browse files
.gitignore CHANGED
@@ -110,3 +110,30 @@ app/data/*/run_generation.log
110
  # other files to ignore
111
  CityQuest_AI_Project_Specification.md
112
  ai_pipeline_source_of_truth.md
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  # other files to ignore
111
  CityQuest_AI_Project_Specification.md
112
  ai_pipeline_source_of_truth.md
113
+ scavenger_hunt_pipeline_summary.md
114
+ keys.txt
115
+ scripts/.key_state
116
+ scripts/key_manager.py
117
+ scripts/scavenger_hunt/run_generation.py
118
+ prompts/hide_and_seek_spec.md
119
+ prompts/tag_spec.md
120
+ scripts/common/cq_common.py
121
+ scripts/hide_and_seek/generator.py
122
+ scripts/hide_and_seek/run_generation.py
123
+ scripts/hide_and_seek/sampler.py
124
+ scripts/hide_and_seek/test_pipeline.py
125
+ scripts/hide_and_seek/validate_dataset.py
126
+ scripts/hide_and_seek/validator.py
127
+ scripts/scavenger_hunt/generator.py
128
+
129
+ scripts/scavenger_hunt/run_generation.py
130
+ scripts/scavenger_hunt/sampler.py
131
+ scripts/scavenger_hunt/validator.py
132
+
133
+ scripts/tag/sampler.py
134
+ scripts/scavenger_hunt/validator.py
135
+ scripts/tag/generator.py
136
+ scripts/tag/run_generation.py
137
+ scripts/tag/test_pipeline.py
138
+ scripts/tag/validate_dataset.py
139
+ scripts/tag/validator.py
app/data/hide_and_seek/dataset.json ADDED
The diff for this file is too large to render. See raw diff
 
scripts/scavenger_hunt/generator.py CHANGED
@@ -246,6 +246,7 @@ def main():
246
  errors_path = args.output.replace(".json", "_errors.jsonl")
247
  start_idx = len(dataset) + 1
248
  kept = skipped = 0
 
249
 
250
  # ── generation loop ───────────────────────────────────────────────────────
251
  for i in range(start_idx, start_idx + args.n):
@@ -274,6 +275,11 @@ def main():
274
  f.write(json.dumps({"id": record["id"], "errors": errors}) + "\n")
275
  log.info(" βœ— skipped after retries β€” logged to %s", errors_path)
276
 
 
 
 
 
 
277
  if not args.mock:
278
  time.sleep(args.sleep)
279
 
@@ -286,6 +292,9 @@ def main():
286
  if skipped:
287
  log.info(" Failures: %s", errors_path)
288
 
 
 
 
289
 
290
  if __name__ == "__main__":
291
  main()
 
246
  errors_path = args.output.replace(".json", "_errors.jsonl")
247
  start_idx = len(dataset) + 1
248
  kept = skipped = 0
249
+ quota_exhausted = False
250
 
251
  # ── generation loop ───────────────────────────────────────────────────────
252
  for i in range(start_idx, start_idx + args.n):
 
275
  f.write(json.dumps({"id": record["id"], "errors": errors}) + "\n")
276
  log.info(" βœ— skipped after retries β€” logged to %s", errors_path)
277
 
278
+ if not args.mock and any("RESOURCE_EXHAUSTED" in e or "429" in e for e in errors):
279
+ log.warning(" quota exhausted on this API key β€” stopping run early")
280
+ quota_exhausted = True
281
+ break
282
+
283
  if not args.mock:
284
  time.sleep(args.sleep)
285
 
 
292
  if skipped:
293
  log.info(" Failures: %s", errors_path)
294
 
295
+ if quota_exhausted:
296
+ raise SystemExit(2)
297
+
298
 
299
  if __name__ == "__main__":
300
  main()
scripts/scavenger_hunt/run_generation.py CHANGED
@@ -20,6 +20,9 @@ import sys
20
  from collections import Counter
21
 
22
  HERE = os.path.dirname(os.path.abspath(__file__))
 
 
 
23
  GENERATOR = os.path.join(HERE, "generator.py")
24
  DEFAULT_OUTPUT = os.path.normpath(os.path.join(
25
  HERE, "..", "..", "app", "data", "scavenger_hunt", "dataset.json"))
@@ -55,8 +58,8 @@ def main():
55
  ap.add_argument("--sleep", type=float, default=8.0, help="seconds between Gemini calls (passed to generator.py)")
56
  args = ap.parse_args()
57
 
58
- if not os.environ.get("GEMINI_API_KEY"):
59
- print("GEMINI_API_KEY is not set in this shell. Set it before running.")
60
  sys.exit(1)
61
 
62
  try:
@@ -67,11 +70,27 @@ def main():
67
  print(f"\nTarget reached: {current}/{args.target} examples in {args.output}")
68
  break
69
 
 
 
 
 
 
 
 
70
  n = min(args.batch, args.target - current)
71
- print(f"\n=== Generating {n} example(s) ({current} -> {current + n} of {args.target}) ===")
 
 
 
72
  result = subprocess.run(
73
- [sys.executable, GENERATOR, "--n", str(n), "--output", args.output, "--sleep", str(args.sleep)]
 
74
  )
 
 
 
 
 
75
  if result.returncode != 0:
76
  print("generator.py exited with an error β€” stopping.")
77
  break
 
20
  from collections import Counter
21
 
22
  HERE = os.path.dirname(os.path.abspath(__file__))
23
+ sys.path.insert(0, os.path.join(HERE, ".."))
24
+ import key_manager
25
+
26
  GENERATOR = os.path.join(HERE, "generator.py")
27
  DEFAULT_OUTPUT = os.path.normpath(os.path.join(
28
  HERE, "..", "..", "app", "data", "scavenger_hunt", "dataset.json"))
 
58
  ap.add_argument("--sleep", type=float, default=8.0, help="seconds between Gemini calls (passed to generator.py)")
59
  args = ap.parse_args()
60
 
61
+ if not key_manager.load_keys():
62
+ print(f"No API keys found in {key_manager.KEYS_FILE}. Add at least one key (one per line) before running.")
63
  sys.exit(1)
64
 
65
  try:
 
70
  print(f"\nTarget reached: {current}/{args.target} examples in {args.output}")
71
  break
72
 
73
+ idx, key = key_manager.get_active_key()
74
+ if key is None:
75
+ print(f"\nAll API keys in {key_manager.KEYS_FILE} are exhausted.")
76
+ print(f"Progress so far: {current}/{args.target} examples in {args.output}")
77
+ print("Add more keys or wait for quotas to reset, then re-run to resume.")
78
+ break
79
+
80
  n = min(args.batch, args.target - current)
81
+ print(f"\n=== Generating {n} example(s) ({current} -> {current + n} of {args.target}) [key #{idx + 1}] ===")
82
+
83
+ env = os.environ.copy()
84
+ env["GEMINI_API_KEY"] = key
85
  result = subprocess.run(
86
+ [sys.executable, GENERATOR, "--n", str(n), "--output", args.output, "--sleep", str(args.sleep)],
87
+ env=env,
88
  )
89
+
90
+ if result.returncode == 2:
91
+ print(f" Key #{idx + 1} quota exhausted β€” rotating to the next key.")
92
+ key_manager.rotate_key()
93
+ continue
94
  if result.returncode != 0:
95
  print("generator.py exited with an error β€” stopping.")
96
  break
scripts/scavenger_hunt/sampler.py CHANGED
@@ -4,42 +4,13 @@ Samples ONE input config in Python (distributions guaranteed here, not by the LL
4
  and builds a prompt asking Gemini to write only the output for that input.
5
  """
6
  import json
 
7
  import random
 
8
 
9
- # ── city bank ─────────────────────────────────────────────────────────────────
10
- # (city_name) -> (country, code, available_tags, climate_zone)
11
- CITY_BANK = {
12
- "Paris": ("France", "PAR", ["iconic_landmark","wide_boulevard","river_waterfront","garden_formal","historic_district","cafe_dense","museum_cluster"], "temperate"),
13
- "Tokyo": ("Japan", "TYO", ["dense_urban_grid","narrow_alley_network","religious_site_accessible","market_covered","iconic_landmark","shopping_street"], "temperate"),
14
- "New York City": ("USA", "NYC", ["dense_urban_grid","park_large","iconic_landmark","coastal_waterfront","museum_cluster","wide_boulevard"], "continental"),
15
- "Cape Town": ("South Africa", "CPT", ["coastal_waterfront","hill_or_elevation","market_outdoor","historic_district","garden_formal"], "mediterranean"),
16
- "Marrakech": ("Morocco", "MRK", ["narrow_alley_network","market_outdoor","religious_site_accessible","plaza_or_square","food_market"], "arid"),
17
- "Buenos Aires": ("Argentina", "BUE", ["wide_boulevard","plaza_or_square","cafe_dense","street_art_district","historic_district"], "temperate"),
18
- "Mumbai": ("India", "BOM", ["coastal_waterfront","market_outdoor","dense_urban_grid","religious_site_accessible","food_market"], "tropical"),
19
- "Berlin": ("Germany", "BER", ["park_large","street_art_district","museum_cluster","historic_district","wide_boulevard","cafe_dense"], "continental"),
20
- "Sydney": ("Australia", "SYD", ["coastal_waterfront","park_large","iconic_landmark","market_covered","bridge_pedestrian"], "temperate"),
21
- "Nairobi": ("Kenya", "NBO", ["park_large","market_outdoor","residential_neighbourhood","dense_urban_grid"], "tropical"),
22
- "Istanbul": ("Turkey", "IST", ["historic_district","religious_site_accessible","market_covered","coastal_waterfront","hill_or_elevation","plaza_or_square"], "mediterranean"),
23
- "Mexico City": ("Mexico", "MEX", ["plaza_or_square","market_outdoor","museum_cluster","street_art_district","dense_urban_grid"], "temperate"),
24
- "Amsterdam": ("Netherlands", "AMS", ["river_waterfront","bridge_pedestrian","narrow_alley_network","cafe_dense","museum_cluster","park_small"], "temperate"),
25
- "Bangalore": ("India", "BLR", ["park_large","market_outdoor","cafe_dense","residential_neighbourhood","shopping_street"], "tropical"),
26
- "Lagos": ("Nigeria", "LOS", ["coastal_waterfront","market_outdoor","dense_urban_grid","food_market"], "tropical"),
27
- "Seoul": ("South Korea", "SEO", ["dense_urban_grid","shopping_street","park_large","historic_district","narrow_alley_network"], "continental"),
28
- "Copenhagen": ("Denmark", "CPH", ["coastal_waterfront","cafe_dense","bridge_pedestrian","market_covered","park_small"], "continental"),
29
- "Lisbon": ("Portugal", "LIS", ["hill_or_elevation","narrow_alley_network","historic_district","coastal_waterfront","plaza_or_square"], "mediterranean"),
30
- "Bogota": ("Colombia", "BOG", ["street_art_district","market_outdoor","park_large","historic_district","plaza_or_square"], "temperate"),
31
- "Auckland": ("New Zealand", "AKL", ["coastal_waterfront","hill_or_elevation","park_large","market_outdoor","bridge_pedestrian"], "temperate"),
32
- }
33
-
34
- DENSITY = {
35
- "Paris":"dense","Tokyo":"hyper_dense","New York City":"hyper_dense",
36
- "Cape Town":"mixed","Marrakech":"dense","Buenos Aires":"dense",
37
- "Mumbai":"hyper_dense","Berlin":"dense","Sydney":"mixed",
38
- "Nairobi":"mixed","Istanbul":"dense","Mexico City":"hyper_dense",
39
- "Amsterdam":"dense","Bangalore":"dense","Lagos":"hyper_dense",
40
- "Seoul":"hyper_dense","Copenhagen":"mixed","Lisbon":"dense",
41
- "Bogota":"dense","Auckland":"mixed",
42
- }
43
 
44
  # (duration_minutes, difficulty) β†’ task count
45
  TASK_COUNT = {
@@ -50,9 +21,6 @@ TASK_COUNT = {
50
  (120,"easy"):8, (120,"medium"):10,(120,"hard"):12,
51
  }
52
 
53
- AREA_TYPES = ["city_center","historic_district","waterfront","park_district",
54
- "mixed_residential","market_district","university_campus"]
55
- AGE_GROUPS = ["children_only","teens","adults","mixed_family","mixed_adults"]
56
  THEMES = ["observation","history","social","nature","urban_exploration","photography","logic"]
57
  TASK_TYPES = ["find_and_photograph","observe_and_answer","collect_and_return",
58
  "reach_and_verify","social_interaction","timed_challenge"]
@@ -137,23 +105,8 @@ def build_prompt(record: dict) -> str:
137
  supervise = age in ("children_only","mixed_family")
138
  max_task_time = int(dur * 0.80 // n_tasks) # generous per-task ceiling
139
 
140
- water_tags = {"river_waterfront","lake_or_pond","coastal_waterfront"}
141
- has_water = bool(water_tags & set(loc["landscape_tags"]))
142
- has_religious = "religious_site_accessible" in loc["landscape_tags"]
143
-
144
- exclusions = ["private_property","active_roadway","construction_sites",
145
- "restricted_government_buildings"]
146
- if has_water: exclusions.append("water_edge")
147
- if has_religious: exclusions.append("religious_interiors")
148
-
149
- climate_note = {
150
- "tropical": "Tropical climate β€” note heat/humidity; require shade access within 10 min of each task.",
151
- "arid": "Arid climate β€” mandatory water advisory; no sustained outdoor walk > 15 min per task.",
152
- "mediterranean": "Mediterranean climate β€” no special constraint.",
153
- "temperate": "Temperate climate β€” no special constraint.",
154
- "continental": f"Continental climate β€” {'add cold-weather advisory (duration ' + str(dur) + ' min).' if dur >= 90 else 'no special constraint.'}",
155
- "polar": "Polar climate β€” all outdoor tasks ≀ 15 min each; mandatory warm-clothing note.",
156
- }.get(loc["climate_zone"], "No special constraint.")
157
 
158
  diff_rule = {
159
  "easy": f"ALL {n_tasks} tasks easy (10 pts each). ZERO hard tasks.",
@@ -188,7 +141,7 @@ points scale : easy=10, medium=20, hard=30
188
  max per-task time : {max_task_time} min (total task time MUST be ≀ {int(dur*0.85)} min)
189
  adult_supervision : {json.dumps(supervise)}
190
  exclusion_zones : {json.dumps(exclusions)}
191
- climate note : {climate_note}
192
 
193
  ═══ ABSOLUTE RULES ═══
194
  1. NO proper nouns anywhere β€” no city names, landmark names, street names, brand names.
 
4
  and builds a prompt asking Gemini to write only the output for that input.
5
  """
6
  import json
7
+ import os
8
  import random
9
+ import sys
10
 
11
+ HERE = os.path.dirname(os.path.abspath(__file__))
12
+ sys.path.insert(0, os.path.join(HERE, "..", "common"))
13
+ from cq_common import CITY_BANK, DENSITY, AREA_TYPES, AGE_GROUPS, compute_exclusions, climate_note
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
  # (duration_minutes, difficulty) β†’ task count
16
  TASK_COUNT = {
 
21
  (120,"easy"):8, (120,"medium"):10,(120,"hard"):12,
22
  }
23
 
 
 
 
24
  THEMES = ["observation","history","social","nature","urban_exploration","photography","logic"]
25
  TASK_TYPES = ["find_and_photograph","observe_and_answer","collect_and_return",
26
  "reach_and_verify","social_interaction","timed_challenge"]
 
105
  supervise = age in ("children_only","mixed_family")
106
  max_task_time = int(dur * 0.80 // n_tasks) # generous per-task ceiling
107
 
108
+ exclusions = compute_exclusions(loc["landscape_tags"])
109
+ climate_note_txt = climate_note(loc["climate_zone"], duration_minutes=dur, unit="task", unit_activity="walk")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
 
111
  diff_rule = {
112
  "easy": f"ALL {n_tasks} tasks easy (10 pts each). ZERO hard tasks.",
 
141
  max per-task time : {max_task_time} min (total task time MUST be ≀ {int(dur*0.85)} min)
142
  adult_supervision : {json.dumps(supervise)}
143
  exclusion_zones : {json.dumps(exclusions)}
144
+ climate note : {climate_note_txt}
145
 
146
  ═══ ABSOLUTE RULES ═══
147
  1. NO proper nouns anywhere β€” no city names, landmark names, street names, brand names.
scripts/scavenger_hunt/validator.py CHANGED
@@ -3,46 +3,17 @@ validator.py β€” scavenger_hunt
3
  Validates one training example. Returns (is_valid: bool, errors: list[str]).
4
  auto_fix() recomputes arithmetic fields in-place β€” never reject for math the model shouldn't be trusted with.
5
  """
6
- import re
 
7
  from Levenshtein import distance as lev
8
 
9
- # ── proper-noun blocklist ──────────────────────────────────────────────────────
10
- _NOUNS = [
11
- "Paris","Tokyo","New York","NYC","Cape Town","Marrakech","Buenos Aires",
12
- "Mumbai","Berlin","Sydney","Nairobi","Istanbul","Mexico City","Amsterdam",
13
- "Bangalore","Lagos","Seoul","Copenhagen","Lisbon","Bogota","Auckland",
14
- # famous landmarks
15
- "Eiffel","Louvre","Seine","Notre-Dame","Notre Dame","Montmartre",
16
- "Shibuya","Senso-ji","Sensoji","Skytree","Harajuku",
17
- "Central Park","Times Square","Brooklyn","Manhattan","Hudson",
18
- "Table Mountain","Bosphorus","Hagia","Zocalo","ZΓ³calo",
19
- "Han River","Gyeongbokgung","Nyhavn","Alfama","Tagus",
20
- "La Boca","Recoleta","Gateway of India","Marine Drive",
21
- "Brandenburg","Tiergarten","Opera House","Harbour Bridge","Bondi",
22
- "Big Ben","Statue of Liberty","Golden Gate","Taj Mahal",
23
- "Colosseum","Vatican","Kremlin","Buckingham","Westminster",
24
- ]
25
- _NOUN_RE = re.compile(r"\b(" + "|".join(re.escape(n) for n in _NOUNS) + r")\b")
26
-
27
- # ── controlled tag vocabulary ─────────────────────────────────────────────────
28
- VALID_TAGS = {
29
- "park_large","park_small","river_waterfront","lake_or_pond",
30
- "coastal_waterfront","forest_urban","garden_formal","hill_or_elevation",
31
- "dense_urban_grid","wide_boulevard","narrow_alley_network","market_outdoor",
32
- "market_covered","plaza_or_square","bridge_pedestrian","train_station_major",
33
- "port_or_harbour","iconic_landmark","historic_district",
34
- "religious_site_accessible","museum_cluster","street_art_district",
35
- "university_campus","shopping_street","cafe_dense","food_market",
36
- "residential_neighbourhood",
37
- }
38
 
39
  POINTS = {"easy": 10, "medium": 20, "hard": 30}
40
 
41
 
42
- def _nouns_in(text: str) -> list:
43
- return _NOUN_RE.findall(text or "")
44
-
45
-
46
  def validate_example(ex: dict) -> tuple[bool, list[str]]:
47
  errors = []
48
 
 
3
  Validates one training example. Returns (is_valid: bool, errors: list[str]).
4
  auto_fix() recomputes arithmetic fields in-place β€” never reject for math the model shouldn't be trusted with.
5
  """
6
+ import os
7
+ import sys
8
  from Levenshtein import distance as lev
9
 
10
+ HERE = os.path.dirname(os.path.abspath(__file__))
11
+ sys.path.insert(0, os.path.join(HERE, "..", "common"))
12
+ from cq_common import VALID_TAGS, nouns_in as _nouns_in
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
  POINTS = {"easy": 10, "medium": 20, "hard": 30}
15
 
16
 
 
 
 
 
17
  def validate_example(ex: dict) -> tuple[bool, list[str]]:
18
  errors = []
19