qeval_tasks.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. """Task set for the DS-V4.1-Flash quality gate. Auto-scored, no LLM judge.
  2. Every task is (id, category, thinking, max_tokens, prompt, checker). A checker takes the
  3. assistant's content and returns (ok: bool, reason: str). Checkers must be deterministic and
  4. must not depend on wording -- only on what the prompt actually demands.
  5. """
  6. import json, re, subprocess, sys, tempfile, os
  7. # ---------------------------------------------------------------- extractors
  8. def extract_code(text):
  9. m = re.findall(r"```(?:python|py)?\s*\n(.*?)```", text, re.S)
  10. if m:
  11. return max(m, key=len)
  12. return text
  13. def extract_json(text):
  14. m = re.findall(r"```(?:json)?\s*\n(.*?)```", text, re.S)
  15. for blob in (m or []) + [text]:
  16. blob = blob.strip()
  17. i, j = blob.find("{"), blob.rfind("}")
  18. if i >= 0 and j > i:
  19. try:
  20. return json.loads(blob[i:j + 1])
  21. except Exception:
  22. continue
  23. return None
  24. _NUM = re.compile(r"-?\d[\d,]*(?:\.\d+)?")
  25. def extract_final_number(text):
  26. """The last standalone number, preferring one after an 'answer'-ish marker."""
  27. tail = text
  28. for marker in ("final answer", "answer:", "answer is", "**", "="):
  29. idx = text.lower().rfind(marker)
  30. if idx >= 0:
  31. tail = text[idx:]
  32. break
  33. nums = _NUM.findall(tail) or _NUM.findall(text)
  34. if not nums:
  35. return None
  36. try:
  37. return float(nums[-1].replace(",", ""))
  38. except ValueError:
  39. return None
  40. def run_python(code, harness, timeout=20):
  41. """Run model code plus an assert harness in an isolated interpreter. Returns (ok, reason).
  42. The tasks are benign algorithmic prompts written here, but this does execute generated
  43. code: isolated mode (-I, no user site / PYTHONPATH), a scratch cwd, a hard timeout, no shell.
  44. """
  45. with tempfile.TemporaryDirectory() as d:
  46. path = os.path.join(d, "cand.py")
  47. with open(path, "w") as f:
  48. f.write(code + "\n\n" + harness + "\nprint('HARNESS_OK')\n")
  49. try:
  50. p = subprocess.run([sys.executable, "-I", "-S", path], cwd=d, timeout=timeout,
  51. capture_output=True, text=True)
  52. except subprocess.TimeoutExpired:
  53. return False, "timeout"
  54. if "HARNESS_OK" in p.stdout:
  55. return True, ""
  56. err = (p.stderr or p.stdout or "").strip().splitlines()
  57. return False, (err[-1][:120] if err else "no output")
  58. def code_task(harness):
  59. return lambda text: run_python(extract_code(text), harness)
  60. def json_task(check):
  61. def inner(text):
  62. obj = extract_json(text)
  63. if obj is None:
  64. return False, "no parseable JSON object"
  65. try:
  66. return check(obj)
  67. except Exception as exc:
  68. return False, f"checker raised {exc!r}"
  69. return inner
  70. def number_task(expected, tol=1e-6):
  71. def inner(text):
  72. got = extract_final_number(text)
  73. if got is None:
  74. return False, "no number found"
  75. return (abs(got - expected) <= tol), f"got {got}, want {expected}"
  76. return inner
  77. def contains_task(*needles, forbid=()):
  78. def inner(text):
  79. low = text.lower()
  80. for n in needles:
  81. if n.lower() not in low:
  82. return False, f"missing {n!r}"
  83. for n in forbid:
  84. if n.lower() in low:
  85. return False, f"contains forbidden {n!r}"
  86. return True, ""
  87. return inner
  88. def degeneration_check(min_words, max_words, forbid_headings=True):
  89. """Prose is not judged for quality -- only for the failure modes that are objective."""
  90. def inner(text):
  91. words = text.split()
  92. if not (min_words <= len(words) <= max_words):
  93. return False, f"{len(words)} words, want {min_words}-{max_words}"
  94. if forbid_headings and re.search(r"^\s*#{1,6}\s", text, re.M):
  95. return False, "markdown heading present"
  96. non_ascii = sum(1 for c in text if ord(c) > 0x2100)
  97. if non_ascii > len(text) * 0.02:
  98. return False, f"{non_ascii} exotic glyphs"
  99. low = [w.lower().strip(".,;:!?") for w in words]
  100. for n in (4, 6): # verbatim n-gram loops
  101. grams = [tuple(low[i:i + n]) for i in range(len(low) - n)]
  102. if grams and (len(grams) - len(set(grams))) > max(2, len(grams) * 0.05):
  103. return False, f"repeated {n}-grams"
  104. return True, ""
  105. return inner
  106. # ---------------------------------------------------------------- the tasks
  107. T = []
  108. def add(tid, cat, thinking, max_tokens, prompt, checker):
  109. T.append(dict(id=tid, category=cat, thinking=thinking, max_tokens=max_tokens,
  110. prompt=prompt, checker=checker))
  111. _CODE = [
  112. ("merge_intervals", "merge_intervals(intervals)", "merges overlapping closed integer intervals given as a list of [start, end] pairs and returns them sorted by start",
  113. "assert merge_intervals([[1,3],[2,6],[8,10],[15,18]]) == [[1,6],[8,10],[15,18]]\n"
  114. "assert merge_intervals([]) == []\nassert merge_intervals([[1,4],[4,5]]) == [[1,5]]"),
  115. ("longest_run", "longest_run(xs)", "returns the length of the longest strictly increasing contiguous run in a list of ints",
  116. "assert longest_run([1,2,3,1,2]) == 3\nassert longest_run([]) == 0\nassert longest_run([5,4,3]) == 1"),
  117. ("roman_to_int", "roman_to_int(s)", "converts a Roman numeral string to an int",
  118. "assert roman_to_int('MCMXCIV') == 1994\nassert roman_to_int('IX') == 9\nassert roman_to_int('LVIII') == 58"),
  119. ("balanced", "balanced(s)", "returns True iff the brackets ()[]{} in the string are balanced and correctly nested",
  120. "assert balanced('([]{})') is True\nassert balanced('([)]') is False\nassert balanced('') is True"),
  121. ("flatten", "flatten(xs)", "flattens an arbitrarily nested list of ints into a flat list, preserving order",
  122. "assert flatten([1,[2,[3,[4]]],5]) == [1,2,3,4,5]\nassert flatten([]) == []\nassert flatten([[],[1]]) == [1]"),
  123. ("top_words", "top_words(text, n)", "returns the n most frequent lowercase alphabetic words as a list of (word, count), ties broken alphabetically",
  124. "assert top_words('a b a c b a', 2) == [('a',3),('b',2)]\n"
  125. "assert top_words('x y', 2) == [('x',1),('y',1)]"),
  126. ("bisect_left", "bisect_left(xs, v)", "returns the leftmost index at which v can be inserted into the sorted list xs keeping it sorted",
  127. "assert bisect_left([1,2,2,3], 2) == 1\nassert bisect_left([], 5) == 0\nassert bisect_left([1,3], 4) == 2"),
  128. ("transpose", "transpose(m)", "transposes a rectangular matrix given as a list of equal-length lists",
  129. "assert transpose([[1,2,3],[4,5,6]]) == [[1,4],[2,5],[3,6]]\nassert transpose([]) == []"),
  130. ("rle", "rle(s)", "run-length encodes a string as a list of (char, count) pairs",
  131. "assert rle('aaabbc') == [('a',3),('b',2),('c',1)]\nassert rle('') == []"),
  132. ("is_palindrome", "is_palindrome(s)", "returns True iff the string is a palindrome ignoring case and non-alphanumeric characters",
  133. "assert is_palindrome('A man, a plan, a canal: Panama') is True\nassert is_palindrome('ab') is False\nassert is_palindrome('') is True"),
  134. ("chunk", "chunk(xs, k)", "splits a list into consecutive chunks of size k, the last one possibly shorter",
  135. "assert chunk([1,2,3,4,5], 2) == [[1,2],[3,4],[5]]\nassert chunk([], 3) == []"),
  136. ("dedupe", "dedupe(xs)", "removes duplicates from a list while preserving first-occurrence order",
  137. "assert dedupe([3,1,3,2,1]) == [3,1,2]\nassert dedupe([]) == []"),
  138. ("camel_to_snake", "camel_to_snake(s)", "converts a CamelCase or camelCase identifier to snake_case",
  139. "assert camel_to_snake('parseHTTPResponse') == 'parse_http_response'\n"
  140. "assert camel_to_snake('Simple') == 'simple'\nassert camel_to_snake('a') == 'a'"),
  141. ("divisor_sum", "divisor_sum(n)", "returns the sum of all positive divisors of n including n itself",
  142. "assert divisor_sum(28) == 56\nassert divisor_sum(1) == 1\nassert divisor_sum(12) == 28"),
  143. ("levenshtein", "levenshtein(a, b)", "returns the Levenshtein edit distance between two strings",
  144. "assert levenshtein('kitten','sitting') == 3\nassert levenshtein('','abc') == 3\nassert levenshtein('x','x') == 0"),
  145. ]
  146. _CODE += [
  147. ("group_anagrams", "group_anagrams(words)", "groups words that are anagrams of each other; returns a list of groups, each group sorted alphabetically, and the groups sorted by their first element",
  148. "assert group_anagrams(['eat','tea','tan','ate','nat','bat']) == [['ate','eat','tea'],['bat'],['nat','tan']]\n"
  149. "assert group_anagrams([]) == []"),
  150. ("two_sum", "two_sum(xs, target)", "returns the pair of indices (i, j) with i < j such that xs[i] + xs[j] == target, or None if there is none; if several exist return the one with the smallest i then smallest j",
  151. "assert two_sum([2,7,11,15], 9) == (0,1)\nassert two_sum([3,2,4], 6) == (1,2)\nassert two_sum([1,2], 99) is None"),
  152. ("rotate90", "rotate90(m)", "rotates a square matrix 90 degrees clockwise and returns a new matrix",
  153. "assert rotate90([[1,2],[3,4]]) == [[3,1],[4,2]]\nassert rotate90([]) == []\n"
  154. "assert rotate90([[1,2,3],[4,5,6],[7,8,9]]) == [[7,4,1],[8,5,2],[9,6,3]]"),
  155. ("valid_ipv4", "valid_ipv4(s)", "returns True iff the string is a valid dotted-quad IPv4 address with no leading zeros and each octet in 0-255",
  156. "assert valid_ipv4('192.168.0.1') is True\nassert valid_ipv4('256.1.1.1') is False\n"
  157. "assert valid_ipv4('01.2.3.4') is False\nassert valid_ipv4('1.2.3') is False"),
  158. ("parse_query", "parse_query(qs)", "parses a URL query string into a dict; repeated keys collect into a list in order; a key with no '=' maps to the empty string",
  159. "assert parse_query('a=1&b=2') == {'a':'1','b':'2'}\n"
  160. "assert parse_query('a=1&a=2') == {'a':['1','2']}\nassert parse_query('') == {}\nassert parse_query('flag') == {'flag':''}"),
  161. ("kth_largest", "kth_largest(xs, k)", "returns the k-th largest element (k=1 means the maximum), counting duplicates as distinct positions; returns None if k is out of range",
  162. "assert kth_largest([3,1,4,1,5], 2) == 4\nassert kth_largest([3,3,3], 3) == 3\nassert kth_largest([1], 2) is None"),
  163. ("spiral", "spiral(m)", "returns the elements of a rectangular matrix in clockwise spiral order starting from the top-left",
  164. "assert spiral([[1,2,3],[4,5,6],[7,8,9]]) == [1,2,3,6,9,8,7,4,5]\n"
  165. "assert spiral([]) == []\nassert spiral([[1,2]]) == [1,2]"),
  166. ("word_wrap", "word_wrap(text, width)", "greedily wraps a space-separated string into lines of at most `width` characters without splitting words; returns a list of lines",
  167. "assert word_wrap('the quick brown fox', 10) == ['the quick','brown fox']\n"
  168. "assert word_wrap('', 5) == []\nassert word_wrap('abc', 2) == ['abc']"),
  169. ("interval_intersect", "interval_intersect(a, b)", "returns the intersection of two lists of disjoint sorted closed integer intervals",
  170. "assert interval_intersect([[0,2],[5,10]],[[1,5],[8,12]]) == [[1,2],[5,5],[8,10]]\n"
  171. "assert interval_intersect([],[[1,2]]) == []"),
  172. ("base_to_int", "base_to_int(s, base)", "converts a string in the given base (2-36, digits 0-9 then a-z, case-insensitive) to an int; raises ValueError on an invalid digit",
  173. "assert base_to_int('ff', 16) == 255\nassert base_to_int('1010', 2) == 10\nassert base_to_int('z', 36) == 35\n"
  174. "try:\n base_to_int('2', 2); raise AssertionError('should have raised')\nexcept ValueError: pass"),
  175. ]
  176. for tid, sig, desc, harness in _CODE:
  177. add(f"code_{tid}", "code", False, 420,
  178. f"Write a Python function `{sig}` that {desc}. Handle empty input sensibly. "
  179. f"Return ONLY the function in a single ```python code block, no explanation, no tests, "
  180. f"no imports beyond the standard library.",
  181. code_task(harness))
  182. add("json_manifest", "json", False, 320,
  183. 'Return ONLY a JSON object (no prose, no code fence text outside it) with exactly these keys: '
  184. '"name" (string), "version" (string of the form X.Y.Z), "deps" (array of exactly 3 strings), '
  185. '"stable" (boolean), "max_workers" (integer between 2 and 8). Invent plausible values for a build tool.',
  186. json_task(lambda o: (
  187. (set(o) == {"name", "version", "deps", "stable", "max_workers"}
  188. and isinstance(o["name"], str) and re.fullmatch(r"\d+\.\d+\.\d+", str(o["version"])) is not None
  189. and isinstance(o["deps"], list) and len(o["deps"]) == 3 and all(isinstance(x, str) for x in o["deps"])
  190. and isinstance(o["stable"], bool)
  191. and isinstance(o["max_workers"], int) and 2 <= o["max_workers"] <= 8),
  192. f"got keys {sorted(o)}")))
  193. add("json_nested", "json", False, 360,
  194. 'Return ONLY a JSON object describing a server: key "host" (string), "port" (integer 1024-65535), '
  195. '"tls" (object with keys "enabled" (bool) and "min_version" (string)), '
  196. '"routes" (array of exactly 2 objects, each with "path" (string starting with /) and "methods" (array of strings)).',
  197. json_task(lambda o: (
  198. (isinstance(o.get("host"), str) and isinstance(o.get("port"), int) and 1024 <= o["port"] <= 65535
  199. and isinstance(o.get("tls"), dict) and isinstance(o["tls"].get("enabled"), bool)
  200. and isinstance(o["tls"].get("min_version"), str)
  201. and isinstance(o.get("routes"), list) and len(o["routes"]) == 2
  202. and all(isinstance(r, dict) and str(r.get("path", "")).startswith("/")
  203. and isinstance(r.get("methods"), list) for r in o["routes"])),
  204. "schema mismatch")))
  205. add("json_toolcall", "json", False, 280,
  206. 'The user says: "book a table for four at 7pm tomorrow at Kappa". Return ONLY a JSON object '
  207. '{"name": "<tool name>", "arguments": {...}} for a restaurant booking tool. The arguments must '
  208. 'include a party size as an integer 4 and a time as a string.',
  209. json_task(lambda o: (
  210. (isinstance(o.get("name"), str) and isinstance(o.get("arguments"), dict)
  211. and 4 in [v for v in o["arguments"].values() if isinstance(v, int)]
  212. and any(isinstance(v, str) and re.search(r"\d", v) for v in o["arguments"].values())),
  213. "no int 4 and/or no time-ish string in arguments")))
  214. add("json_escape", "json", False, 280,
  215. 'Return ONLY a JSON object with key "text" whose value is exactly this sentence including the '
  216. 'quotes and backslash: She said "hi\\there" — twice. And a key "len" whose integer value is the '
  217. 'number of characters in that string.',
  218. json_task(lambda o: (("text" in o and isinstance(o.get("len"), int)
  219. and o["len"] == len(o["text"])), "len does not match text length")))
  220. add("json_sort", "json", False, 320,
  221. 'Return ONLY a JSON array of the following objects sorted by "score" descending, then "name" '
  222. 'ascending: [{"name":"bo","score":3},{"name":"al","score":7},{"name":"cy","score":3},{"name":"di","score":9}]',
  223. lambda text: ((lambda o: (o == [{"name": "di", "score": 9}, {"name": "al", "score": 7},
  224. {"name": "bo", "score": 3}, {"name": "cy", "score": 3}], "wrong order"))
  225. (json.loads(re.search(r"\[.*\]", text, re.S).group(0))) if re.search(r"\[.*\]", text, re.S)
  226. else (False, "no JSON array")))
  227. add("json_types", "json", False, 260,
  228. 'Return ONLY a JSON object with keys "a" (the integer 0), "b" (the boolean false), '
  229. '"c" (null), "d" (the empty array), "e" (the empty string). Nothing else.',
  230. json_task(lambda o: ((o == {"a": 0, "b": False, "c": None, "d": [], "e": ""}), f"got {o}")))
  231. add("json_count", "json", False, 300,
  232. 'Count the vowels (a,e,i,o,u, case-insensitive) in the string "Encyclopaedia Britannica" and '
  233. 'return ONLY {"vowels": <integer>}.',
  234. json_task(lambda o: ((o.get("vowels") == 10), f"got {o.get('vowels')}, want 10")))
  235. add("json_strict_empty", "json", False, 260,
  236. 'Return ONLY a JSON object mapping each of the words "alpha", "beta", "gamma" to its length '
  237. 'as an integer. No other keys.',
  238. json_task(lambda o: ((o == {"alpha": 5, "beta": 4, "gamma": 5}), f"got {o}")))
  239. _MATH = [
  240. ("m1", "Compute 17 * 243 + 5**4 - 1000. Show your steps, then give the final number on its own line.", 17 * 243 + 625 - 1000),
  241. ("m2", "A train travels 315 km in 3.5 hours, then 210 km in 2 hours. What is its average speed in km/h over the whole journey? Give the final number on its own line.", (315 + 210) / 5.5),
  242. ("m3", "What is the sum of all integers from 1 to 200 that are divisible by 7? Final number on its own line.", sum(i for i in range(1, 201) if i % 7 == 0)),
  243. ("m4", "A rectangle has perimeter 46 and area 120. What is the length of its longer side? Final number on its own line.", 15),
  244. ("m5", "Compute the greatest common divisor of 1071 and 462. Final number on its own line.", 21),
  245. ("m6", "If 3 painters paint 5 rooms in 8 hours, how many hours do 5 painters need for 12 rooms at the same rate? Final number on its own line.", 11.52),
  246. ("m7", "What is 2**20 - 3**10? Final number on its own line.", 2 ** 20 - 3 ** 10),
  247. ("m8", "A shirt costs 80 after a 20% discount and then a further 10% off at the till. What was the original price? Final number on its own line.", 80 / (0.8 * 0.9)),
  248. ("m9", "How many distinct 4-letter strings can be made from the letters of BANANA (using each letter at most as often as it appears)? Final number on its own line.", 38),
  249. ("m10", "The sequence is 2, 6, 12, 20, 30, ... What is the 12th term? Final number on its own line.", 12 * 13),
  250. ]
  251. _MATH += [
  252. ("m11", "What is 7**100 mod 13? Final number on its own line.", 9),
  253. ("m12", "In how many distinct ways can 8 people be seated around a round table, treating rotations as identical? Final number on its own line.", 5040),
  254. ("m13", "A cone has radius 3 and height 4. What is its total surface area, using pi = 3.14159, to two decimal places? Final number on its own line.", 75.40),
  255. ("m14", "Two pipes fill a tank in 12 and 18 minutes respectively. A drain empties it in 36 minutes. With all three open, how many minutes to fill the tank? Final number on its own line.", 9),
  256. ]
  257. for tid, prompt, want in _MATH:
  258. add(f"math_{tid}", "math", False, 640, prompt, number_task(float(want), tol=0.01))
  259. _REASON = [
  260. ("r1", "A farmer has 17 sheep. All but 9 run away. He then buys twice as many as remain, and sells 4. How many sheep does he have? Give the final number on its own line.", 23),
  261. ("r2", "Three boxes are labelled APPLES, ORANGES and MIXED. Every label is wrong. You may draw one fruit from one box. From which labelled box should you draw to deduce all three contents? Answer with the single label word in capitals on its own line.", "MIXED"),
  262. ("r3", "It takes 5 machines 5 minutes to make 5 widgets. How many minutes would 100 machines take to make 100 widgets? Final number on its own line.", 5),
  263. ("r4", "A bat and a ball cost 1.10 together. The bat costs 1.00 more than the ball. What does the ball cost? Final number on its own line.", 0.05),
  264. ("r5", "In a race you overtake the runner in second place. What place are you in now? Final number on its own line.", 2),
  265. ("r6", "A lily pad patch doubles in size every day and covers the lake on day 48. On which day did it cover half the lake? Final number on its own line.", 47),
  266. ("r7", "You have 8 identical-looking balls, one heavier. Using a balance scale, what is the minimum number of weighings guaranteed to find it? Final number on its own line.", 2),
  267. ("r8", "Anna is twice as old as Ben was when Anna was as old as Ben is now. Anna is 24. How old is Ben? Final number on its own line.", 18),
  268. ("r9", "A clock shows 3:15. What is the angle in degrees between the hour and minute hands? Final number on its own line.", 7.5),
  269. ("r10", "Five houses in a row. The green house is immediately right of the ivory one. The Norwegian lives in the first house. Milk is drunk in the middle house. If the green house is the fourth, which position is the ivory house in? Final number on its own line.", 3),
  270. ]
  271. _REASON += [
  272. ("r11", "A man must ferry a wolf, a goat and a cabbage across a river. The boat holds him and one item. The wolf eats the goat if left alone with it; the goat eats the cabbage if left alone with it. What is the minimum number of river crossings (counting each one-way trip) to get all three across? Final number on its own line.", 7),
  273. ("r12", "You have 12 identical-looking balls, exactly one of which has a different weight (you do not know whether heavier or lighter). Using a balance scale, what is the minimum number of weighings that is guaranteed to identify it? Final number on its own line.", 3),
  274. ("r13", "On an island every inhabitant is either a knight who always tells the truth or a knave who always lies. A says 'B is a knave'. B says 'A and I are of the same type'. How many knights are there among A and B? Final number on its own line.", 1),
  275. ("r14", "A bag holds 3 red and 5 blue marbles. Two are drawn without replacement. What is the probability that both are red? Give the answer as a decimal to three places on its own line.", 0.107),
  276. ("r15", "Today is Wednesday. What day of the week will it be in 1000 days? Answer with the single day name in capitals on its own line.", "TUESDAY"),
  277. ("r16", "Four people must cross a bridge at night with one torch; the bridge holds two at a time and whoever crosses must carry the torch. Their individual crossing times are 1, 2, 7 and 10 minutes; a pair moves at the slower person's pace. What is the minimum total time in minutes? Final number on its own line.", 17),
  278. ]
  279. for tid, prompt, want in _REASON:
  280. add(f"reason_{tid}", "reason", True, 1400, prompt,
  281. number_task(float(want), tol=0.01) if isinstance(want, (int, float))
  282. else contains_task(want))
  283. add("fmt_table", "format", False, 420,
  284. "Produce a markdown table with exactly the columns | step | command | why | and exactly three "
  285. "data rows, describing how to open an SSH tunnel to a remote API on port 8888. Output the table "
  286. "and nothing else.",
  287. lambda t: ((len([l for l in t.strip().splitlines() if l.strip().startswith("|")]) == 5,
  288. f"{len([l for l in t.strip().splitlines() if l.strip().startswith('|')])} pipe lines, want 5")))
  289. add("fmt_bullets", "format", False, 360,
  290. "List exactly five differences between TCP and UDP as markdown bullets starting with '- '. "
  291. "No heading, no intro, no closing sentence.",
  292. lambda t: ((len([l for l in t.strip().splitlines() if l.strip().startswith("- ")]) == 5,
  293. "wrong bullet count")))
  294. add("fmt_nocode", "format", False, 300,
  295. "Explain what a mutex is in exactly two sentences. Do not use any code, any markdown formatting, "
  296. "or the word 'lock'.",
  297. lambda t: ((len([s for s in re.split(r"(?<=[.!?])\s+", t.strip()) if s.strip()]) == 2
  298. and "lock" not in t.lower() and "`" not in t and "*" not in t, "constraint violated")))
  299. add("fmt_uppercase", "format", False, 260,
  300. "Reply with the five largest planets of the Solar System, largest first, as a single "
  301. "comma-separated line in ALL CAPS. Nothing else.",
  302. lambda t: ((t.strip().upper() == t.strip() and t.strip().count(",") == 4
  303. and "JUPITER" in t.upper() and t.strip().upper().startswith("JUPITER"), "format/content wrong")))
  304. add("fmt_exactword", "format", False, 300,
  305. "Answer with exactly one word: what is the capital of Australia?",
  306. lambda t: ((len(t.strip().split()) == 1 and t.strip().strip(".").lower() == "canberra",
  307. f"got {t.strip()!r}")))
  308. add("fmt_json_only", "format", False, 280,
  309. "Output ONLY the JSON array [1, 2, 3]. No fence, no prose.",
  310. lambda t: ((t.strip() == "[1, 2, 3]" or t.strip() == "[1,2,3]", f"got {t.strip()[:40]!r}")))
  311. add("fmt_linecount", "format", False, 360,
  312. "Write exactly four lines, each being a single English word that rhymes with 'light'. "
  313. "No numbering, no punctuation, nothing else.",
  314. lambda t: ((len([l for l in t.strip().splitlines() if l.strip()]) == 4
  315. and all(len(l.split()) == 1 for l in t.strip().splitlines() if l.strip()), "not four bare words")))
  316. _PROSE = [
  317. ("p1", "Write one paragraph of about 120 words describing a harbour at dawn. Prose only, no headings.", 80, 190),
  318. ("p2", "Tell a 150-word story about a clockmaker who refuses to sell her last clock. Prose only.", 100, 230),
  319. ("p3", "In about 100 words, explain to a non-programmer what a cache is, using one everyday analogy. Prose only.", 70, 165),
  320. ("p4", "Write about 130 words of continuous prose arguing that maps are always political. No headings, no bullets.", 90, 200),
  321. ("p5", "Describe, in roughly 120 words of plain prose, the experience of reading a letter written forty years ago.", 80, 190),
  322. ]
  323. for tid, prompt, lo, hi in _PROSE:
  324. add(f"prose_{tid}", "prose", False, 460, prompt, degeneration_check(lo, hi))
  325. TASKS = T