marin-taskcompendium 0.2.160.dev38062449553__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. marin_taskcompendium-0.2.160.dev38062449553.dist-info/METADATA +16 -0
  2. marin_taskcompendium-0.2.160.dev38062449553.dist-info/RECORD +81 -0
  3. marin_taskcompendium-0.2.160.dev38062449553.dist-info/WHEEL +4 -0
  4. taskcompendium/__init__.py +2 -0
  5. taskcompendium/chat.py +93 -0
  6. taskcompendium/convert/__init__.py +4 -0
  7. taskcompendium/convert/answers.py +210 -0
  8. taskcompendium/convert/code.py +78 -0
  9. taskcompendium/convert/conversation.py +44 -0
  10. taskcompendium/convert/delivery.py +34 -0
  11. taskcompendium/convert/environment.py +16 -0
  12. taskcompendium/convert/executable.py +348 -0
  13. taskcompendium/convert/json_schema.py +32 -0
  14. taskcompendium/convert/nemotron_ultra.py +225 -0
  15. taskcompendium/convert/preference.py +87 -0
  16. taskcompendium/convert/script_grader.py +61 -0
  17. taskcompendium/convert/tasktrove.py +184 -0
  18. taskcompendium/convert/tasktrove_code_contests.py +99 -0
  19. taskcompendium/convert/tasktrove_codeforces.py +117 -0
  20. taskcompendium/convert/tasktrove_converted_task.py +88 -0
  21. taskcompendium/convert/tasktrove_json_schemas.py +133 -0
  22. taskcompendium/convert/tasktrove_nemotron_data.py +25 -0
  23. taskcompendium/convert/tasktrove_nemotron_structured_outputs.py +284 -0
  24. taskcompendium/convert/tasktrove_nl2bash.py +168 -0
  25. taskcompendium/convert/tasktrove_python_unit_tests.py +100 -0
  26. taskcompendium/convert/tasktrove_stdio_cases.py +91 -0
  27. taskcompendium/convert/tasktrove_taco.py +88 -0
  28. taskcompendium/convert/verifyit_build.py +62 -0
  29. taskcompendium/direct_chat.py +40 -0
  30. taskcompendium/grader.py +42 -0
  31. taskcompendium/grading.py +118 -0
  32. taskcompendium/grading_result.py +37 -0
  33. taskcompendium/harbor/__init__.py +4 -0
  34. taskcompendium/harbor/compare.py +255 -0
  35. taskcompendium/harbor/export.py +644 -0
  36. taskcompendium/harbor/records.py +79 -0
  37. taskcompendium/harbor/snapshots.py +122 -0
  38. taskcompendium/harbor/tasktrove.py +72 -0
  39. taskcompendium/importers/__init__.py +4 -0
  40. taskcompendium/importers/nemo_predicted_action.py +200 -0
  41. taskcompendium/importers/swe.py +78 -0
  42. taskcompendium/importers/tasktrove/__init__.py +4 -0
  43. taskcompendium/importers/tasktrove/convert.py +70 -0
  44. taskcompendium/importers/tasktrove/mcqa.py +80 -0
  45. taskcompendium/importers/tasktrove/models.py +30 -0
  46. taskcompendium/models.py +891 -0
  47. taskcompendium/pipeline/README.md +223 -0
  48. taskcompendium/pipeline/__init__.py +4 -0
  49. taskcompendium/pipeline/audit_schema.py +119 -0
  50. taskcompendium/pipeline/chat_requests.py +100 -0
  51. taskcompendium/pipeline/controls.py +270 -0
  52. taskcompendium/pipeline/conversion.py +148 -0
  53. taskcompendium/pipeline/environment_inventory.py +49 -0
  54. taskcompendium/pipeline/execution_telemetry.py +141 -0
  55. taskcompendium/pipeline/filtering.py +37 -0
  56. taskcompendium/pipeline/fingerprints.py +110 -0
  57. taskcompendium/pipeline/inputs.py +98 -0
  58. taskcompendium/pipeline/models.py +325 -0
  59. taskcompendium/pipeline/query_cache.py +259 -0
  60. taskcompendium/pipeline/review.py +785 -0
  61. taskcompendium/pipeline/review_requests.py +285 -0
  62. taskcompendium/pipeline/sampling.py +44 -0
  63. taskcompendium/pipeline/shard_outputs.py +58 -0
  64. taskcompendium/pipeline/source_processing.py +1072 -0
  65. taskcompendium/pipeline/source_quality.py +283 -0
  66. taskcompendium/pipeline/source_verification.py +634 -0
  67. taskcompendium/pipeline/sources.py +210 -0
  68. taskcompendium/pipeline/stages.py +845 -0
  69. taskcompendium/pipeline/transforms.py +221 -0
  70. taskcompendium/pipeline/verification.py +192 -0
  71. taskcompendium/runtime/__init__.py +7 -0
  72. taskcompendium/runtime/environment.py +27 -0
  73. taskcompendium/runtime/episode.py +98 -0
  74. taskcompendium/runtime/grading.py +649 -0
  75. taskcompendium/runtime/local.py +219 -0
  76. taskcompendium/runtime/models.py +82 -0
  77. taskcompendium/runtime/output_capture.py +113 -0
  78. taskcompendium/runtime/resources.py +18 -0
  79. taskcompendium/runtime/shell.py +286 -0
  80. taskcompendium/runtime/task_grading.py +61 -0
  81. taskcompendium/submission.py +188 -0
@@ -0,0 +1,16 @@
1
+ Metadata-Version: 2.5
2
+ Name: marin-taskcompendium
3
+ Version: 0.2.160.dev38062449553
4
+ Summary: Semantic task contracts, submission extraction, and shared grading
5
+ Requires-Python: <3.14,>=3.12
6
+ Requires-Dist: marin-rigging==0.2.160.dev38062449553
7
+ Requires-Dist: marin-shellbox==0.2.160.dev38062449553
8
+ Requires-Dist: marin-verifyit[judge,math,reasoning-gym,schema]==0.2.160.dev38062449553
9
+ Requires-Dist: pydantic>=2.0
10
+ Requires-Dist: tomlkit>=0.13
11
+ Provides-Extra: pipeline
12
+ Requires-Dist: datasets<5.0.0,>=3.1.0; extra == 'pipeline'
13
+ Requires-Dist: marin-finestore==0.2.160.dev38062449553; extra == 'pipeline'
14
+ Requires-Dist: marin-zephyr==0.2.160.dev38062449553; extra == 'pipeline'
15
+ Requires-Dist: numpy>=1.26; extra == 'pipeline'
16
+ Requires-Dist: pyarrow>=15.0; extra == 'pipeline'
@@ -0,0 +1,81 @@
1
+ taskcompendium/__init__.py,sha256=YCa8ajV8oMVGzdM-0ADm_Y7gL7bP-E4DDjDz4V7pBA0,68
2
+ taskcompendium/chat.py,sha256=IrkvB-hvvZzI4E39NKkJzJnjBJ5pTmzZux883o9_A4g,3194
3
+ taskcompendium/direct_chat.py,sha256=BRQYWrXl7NTH9abN_NovNSgLv6zKj1S8Wjg5RcZFK5c,1788
4
+ taskcompendium/grader.py,sha256=oOqvfBzw_vPOiFRvzfTtL0CEF1HZXKXerJVCUb-oFXE,1613
5
+ taskcompendium/grading.py,sha256=AL0uK07DN4UO8_sb3VOUOWgxkNVH8z6VYaeGfZN1jyY,5497
6
+ taskcompendium/grading_result.py,sha256=ZOc3_YsACp_IqnN2iVn9PMYQDh5jlFMWLJ7nKGul_q8,935
7
+ taskcompendium/models.py,sha256=UIqffR2yzu0VjB1ynND-7zmx3crYZaJsNI6NHt6eYSI,34910
8
+ taskcompendium/submission.py,sha256=l96cBGXc4qJysitVlUx4f6_VhMTuuMAnFWkEd0kLeZs,7539
9
+ taskcompendium/convert/__init__.py,sha256=AuRWrOcOqcm2s5NNLnBcPkluFLRcSxv9UnmesSeEaXM,146
10
+ taskcompendium/convert/answers.py,sha256=04G-n8VdVGeVntkr6D58hXF9imEG14vHSSnkweBe2XY,7965
11
+ taskcompendium/convert/code.py,sha256=XK1X24br7hZeo8UPQSARkEVOZcn6-lDgV5fZwcKKtxs,3846
12
+ taskcompendium/convert/conversation.py,sha256=55S2jGI52XFxGRQ_zPvyfP7O0YiH-HdSBov_8tQUgwM,1349
13
+ taskcompendium/convert/delivery.py,sha256=dLyrMXlC5ZH-dkDvT4QDtk0UmvKiIqCNv0EuSujrdyc,1418
14
+ taskcompendium/convert/environment.py,sha256=zKSh8aIzx4Y9CkyPVDhdpp2wRY8ngidZmsH00VI0nxU,618
15
+ taskcompendium/convert/executable.py,sha256=oRaBaMaHwkNKree_ZCxS8aVkAv6csgdbCBNH2-7e4WA,14309
16
+ taskcompendium/convert/json_schema.py,sha256=6jdI79YTVeX0jRAhFAEwtduR5J-63xIyJVk6TULVcSo,1332
17
+ taskcompendium/convert/nemotron_ultra.py,sha256=GKNEojj4aoh8GaGrurqwh4chZaIR0cMtDaz-pctPsbQ,8961
18
+ taskcompendium/convert/preference.py,sha256=QfqfjrX4EexIKtFfLCUMAZi0jAYSOBvlGWqeILzf3Bk,3942
19
+ taskcompendium/convert/script_grader.py,sha256=3uwzujkg3glbzVBXXl5KKHh_ZrQT48dB99iKEIZsVng,2420
20
+ taskcompendium/convert/tasktrove.py,sha256=zojE0PfC3Hd9EWpgLBeepB0cYZfvfIGL5rCPKmn8kLY,7537
21
+ taskcompendium/convert/tasktrove_code_contests.py,sha256=x8cXcpeWnbWSR28fgSVfywXeo4nci0nc3i0-vBKBMKo,3786
22
+ taskcompendium/convert/tasktrove_codeforces.py,sha256=1YH6x_LaVWFYoMEJ39x0aWzV8G5wG5KzvUOYlZHoFZc,4217
23
+ taskcompendium/convert/tasktrove_converted_task.py,sha256=Aq2v5xF-LTore-9y9HqCfZU_C0aspMLT5rIWmokeqD0,3542
24
+ taskcompendium/convert/tasktrove_json_schemas.py,sha256=eYgzfinpVg-TUY-XxD0ZHhdWyqJqrhtD0BwHmZA-JvA,5284
25
+ taskcompendium/convert/tasktrove_nemotron_data.py,sha256=FM3fJXSo3Z6HCM8tea7IxguoWergmo7XSIYS7T5VhQk,827
26
+ taskcompendium/convert/tasktrove_nemotron_structured_outputs.py,sha256=4Jm93oo1yeuZzvggyZU1cbxSUEtWsdz7SrerSNo0mE0,12239
27
+ taskcompendium/convert/tasktrove_nl2bash.py,sha256=Eevf7G96sPA2MUdA-xZ7yOMLs8LAMv5UWbFwQejK3D4,7028
28
+ taskcompendium/convert/tasktrove_python_unit_tests.py,sha256=b_tI3wfjz3vgORE0E4klemSYSQ3YpNNOv66Gxs8rZ7E,3841
29
+ taskcompendium/convert/tasktrove_stdio_cases.py,sha256=EF4u7t-4eSoE2A7uk3AJ7PhlWuH-F6qFaFWFhWt9-DY,4146
30
+ taskcompendium/convert/tasktrove_taco.py,sha256=e2ynxAbYBJZQO906fMJaGBy8tH9IDkaiQBZVP2rwvbY,3757
31
+ taskcompendium/convert/verifyit_build.py,sha256=WYxrr7ycjNsym1nAiN5RXVmowhMz_0J7QkLDXsF61DE,2817
32
+ taskcompendium/harbor/__init__.py,sha256=FGKy0KOvEejGv3g0Ov1LgJx1RHqPo8o8MYprTPahN8Q,118
33
+ taskcompendium/harbor/compare.py,sha256=CafzeTfgPSkin8BNziETdoQQk3CKd9ruyEUdkZh3-WE,10771
34
+ taskcompendium/harbor/export.py,sha256=j71GQtaSB9W9AAGK3gmbpd5nSsOaGm6XGTo1yOSwnkc,28192
35
+ taskcompendium/harbor/records.py,sha256=KFO8XTnvblnOLXwV09ut4AiH-YFn7ziTBoMc_xVJvwk,3328
36
+ taskcompendium/harbor/snapshots.py,sha256=C5mhctpFg4A6qkAkiMu_JdqKBk3oBXQ58yncyN29f5A,4011
37
+ taskcompendium/harbor/tasktrove.py,sha256=8SW2PGEFOhKxIbSyBic9lExj_RVv-sac9y1BHRXXi_4,3426
38
+ taskcompendium/importers/__init__.py,sha256=Sx3uJyho-XnsgGKm4f3oBceCu6N7NzB8lkY3-sH134Y,142
39
+ taskcompendium/importers/nemo_predicted_action.py,sha256=CEPAEQzAdt5KKiQBT9dmqHR41xdL9PetYebBow4nenA,9627
40
+ taskcompendium/importers/swe.py,sha256=KicdqOUbs8Y8Uz-VwclrpeOZm5Y4prVrZLwl7c4Wq4E,2730
41
+ taskcompendium/importers/tasktrove/__init__.py,sha256=zIJ4TMWYbzc3cs7aDWIAaw4RjB-ikKwek5GN3Smf7f0,128
42
+ taskcompendium/importers/tasktrove/convert.py,sha256=WN5nYZv1F1QzmyIFJW0con9wk4LU9IUHmHNzxSQ8B7s,3018
43
+ taskcompendium/importers/tasktrove/mcqa.py,sha256=nGXGLgDeBh33i8UfNkZsC3DQqdW7iofoWgXYgf_XMEo,3611
44
+ taskcompendium/importers/tasktrove/models.py,sha256=1BLxl8tn-Gjz_4QEx4sSowgAU2uKxXDfROxCS0tdLtg,755
45
+ taskcompendium/pipeline/README.md,sha256=yXoaVqf0jvdDoGkN44FKV7AW1C1F_zPcDeVHDfGe9wQ,13711
46
+ taskcompendium/pipeline/__init__.py,sha256=EVYEzJis0ke3RzUcOEYCC2O-am3_o5vdRMMkrDFzyVI,135
47
+ taskcompendium/pipeline/audit_schema.py,sha256=5ignj28PQr-oVsqu91icVsVil2W-pz02l7euCCBS674,4781
48
+ taskcompendium/pipeline/chat_requests.py,sha256=VUC3K_anr-gfOT_837KRI4TngETHOePeFxkPl-QhB_k,3875
49
+ taskcompendium/pipeline/controls.py,sha256=qr1dQgrYKkv8y3Dk17pGtyx_cJkEluXvPDjvSh1t22I,10897
50
+ taskcompendium/pipeline/conversion.py,sha256=Md2sMl49Y65fIeGwiOG8xTsWd2l_mPOOQqDL-ILMT4M,5920
51
+ taskcompendium/pipeline/environment_inventory.py,sha256=67kkb0KxD8bFu5tv3DbPaUTt-UDLz-nfwLToyr9yRDk,1879
52
+ taskcompendium/pipeline/execution_telemetry.py,sha256=bD5Kf_jgqKlz_qC0i2PdAlT4t9wj8c1REbAtHCboj58,4651
53
+ taskcompendium/pipeline/filtering.py,sha256=WKIK_7MXHdjpkc4LMawRhjkfsmzXtYcQlqDcnz5ylU0,1817
54
+ taskcompendium/pipeline/fingerprints.py,sha256=4kLN5XYuK8imZrrF7ZrdIdJyfrpOPVsNKBUh6DtJCWg,4531
55
+ taskcompendium/pipeline/inputs.py,sha256=136QHYa4ZWU01uDArHUoVLfnRsu2Ifrw6Hf1mZ0eDgE,3625
56
+ taskcompendium/pipeline/models.py,sha256=vm2HIZZhhWJSCqXIkuP6CjCaHnjq3IboLNV5NP507ZM,9205
57
+ taskcompendium/pipeline/query_cache.py,sha256=1hld3ddGs60KWpHPuXSLDOEE0BtDv6-cbPmmrWjFgAk,9633
58
+ taskcompendium/pipeline/review.py,sha256=eRB2pRf-HnI9d90yPyCxUkXCMIXh0CUBOg0HaBa-9jY,34450
59
+ taskcompendium/pipeline/review_requests.py,sha256=P9x6k5ZiLe5hiht5Dpob2_CHMnzTO3D3GGuI8MLu-4U,10359
60
+ taskcompendium/pipeline/sampling.py,sha256=Zxai94IJctH6QrpeCFzBYJoISkRHXBM0FPXrfy53G7U,1416
61
+ taskcompendium/pipeline/shard_outputs.py,sha256=Iov0RWinu0MVG35KZfUIyATCUY5oM3U_UZR7dfG1wTY,2205
62
+ taskcompendium/pipeline/source_processing.py,sha256=PLDKMuHrBnVR7NQfYIOnZnc-UKw4Ta-4wAM_DxEttQo,43793
63
+ taskcompendium/pipeline/source_quality.py,sha256=QAtdeLt22wfXySIWug0vURwTH_VAhWI_8OhITEwlv68,11553
64
+ taskcompendium/pipeline/source_verification.py,sha256=YLNADkr47D8dZ17qSrY72_axIJ56mR-7YeQ46EhQFqc,25644
65
+ taskcompendium/pipeline/sources.py,sha256=irN4Rv66yOGAGmwEQb-1g09N9R2FybWaGhgPgHS2OvU,9026
66
+ taskcompendium/pipeline/stages.py,sha256=nQXzG1OjWpYRExjad5B5h9ykBXKyT2w-gi9O4h8MHhA,34348
67
+ taskcompendium/pipeline/transforms.py,sha256=KbnvSu6zfU6lM4qxe3V83adGztH-Y5smO8O2zbWF7lw,8334
68
+ taskcompendium/pipeline/verification.py,sha256=UgfxdopckCkfcgXTJdo-c6qub09UFvzN4WIOFssOyqI,8480
69
+ taskcompendium/runtime/__init__.py,sha256=o67I1ai4r_ZEZSVdlrHdxWzabhAXKCw5N_IqYCqnj38,229
70
+ taskcompendium/runtime/environment.py,sha256=ts3uM0zp5DSqevXKiNneyD3-wdTaEorRTyA9He7O0Io,951
71
+ taskcompendium/runtime/episode.py,sha256=iBO6-rtNbELTIVzqgCjHW9QUqqgmGy7hSQJ9NLH3stI,3452
72
+ taskcompendium/runtime/grading.py,sha256=Adoc1qrIB86PuIaWfvRKNIQ4ssG8KkcGsdviPHBD4qc,28257
73
+ taskcompendium/runtime/local.py,sha256=UMJ2sVxHMIHAjzV3n7WXA-H9AdqQ1-XVBUKGhQ6V3Sw,8518
74
+ taskcompendium/runtime/models.py,sha256=4PeIzEMuNuwJgXpdHqGTqXjqwq4WiCFM3JSyMjYyduE,2258
75
+ taskcompendium/runtime/output_capture.py,sha256=nsc60DTTrpZVdr7hqo5xtZPopxZXtEUS_4xOAC527PQ,5314
76
+ taskcompendium/runtime/resources.py,sha256=-LgVikv0pjBLpu1y_BXEvCJC2trGqaEJgzGkcRqQDYk,618
77
+ taskcompendium/runtime/shell.py,sha256=gMUBDBz9Whnr4qac62sdXxG13xIHIDdmIib_Z05esw8,11696
78
+ taskcompendium/runtime/task_grading.py,sha256=GJKxkVShu2ZmZbKYhDt7b9zsl5HS4tMGJT_J40EPNLw,2252
79
+ marin_taskcompendium-0.2.160.dev38062449553.dist-info/METADATA,sha256=1bBUIOzI8lGveU4g7_DUz-oZaWR9sUQ1nwB0i68nz6s,770
80
+ marin_taskcompendium-0.2.160.dev38062449553.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
81
+ marin_taskcompendium-0.2.160.dev38062449553.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ # Copyright The Marin Authors
2
+ # SPDX-License-Identifier: Apache-2.0
taskcompendium/chat.py ADDED
@@ -0,0 +1,93 @@
1
+ # Copyright The Marin Authors
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Convert OpenAI chat messages to typed conversation evidence."""
5
+
6
+ import json
7
+ from typing import Any, Literal
8
+
9
+ from pydantic import BaseModel, ConfigDict, Field, JsonValue, field_validator
10
+ from verifyit.json_objects import unique_object
11
+
12
+ from taskcompendium.models import (
13
+ AssistantToolCalls,
14
+ ConversationEvent,
15
+ ConversationInput,
16
+ ConversationToolCall,
17
+ ConversationTrace,
18
+ TextMessage,
19
+ ToolResult,
20
+ )
21
+
22
+
23
+ class ChatFunction(BaseModel):
24
+ model_config = ConfigDict(strict=True, allow_inf_nan=False)
25
+
26
+ name: str = Field(min_length=1)
27
+ arguments: dict[str, JsonValue]
28
+
29
+ @field_validator("arguments", mode="before")
30
+ @classmethod
31
+ def decode_arguments(cls, value: str) -> dict[str, JsonValue]:
32
+ if not isinstance(value, str):
33
+ raise ValueError("Provider tool arguments must be a JSON string")
34
+ return json.loads(value, object_pairs_hook=unique_object)
35
+
36
+
37
+ class ChatToolCall(BaseModel):
38
+ model_config = ConfigDict(strict=True)
39
+
40
+ id: str = Field(min_length=1)
41
+ type: Literal["function"]
42
+ function: ChatFunction
43
+
44
+
45
+ class ChatAssistantMessage(BaseModel):
46
+ model_config = ConfigDict(strict=True)
47
+
48
+ role: Literal["assistant"]
49
+ content: str | None = None
50
+ tool_calls: list[ChatToolCall] | None = None
51
+
52
+
53
+ def assistant_message(message: dict[str, Any]) -> TextMessage | AssistantToolCalls:
54
+ """Validate chat wire data and return protocol-independent submission evidence.
55
+
56
+ Malformed protocol data raises at this ingestion boundary. A valid text reply,
57
+ wrong function name, or wrong arguments remain available for grading.
58
+ """
59
+ validated = ChatAssistantMessage.model_validate(message)
60
+ if validated.tool_calls:
61
+ return AssistantToolCalls(
62
+ calls=tuple(
63
+ ConversationToolCall(call_id=call.id, name=call.function.name, arguments=call.function.arguments)
64
+ for call in validated.tool_calls
65
+ ),
66
+ content=validated.content,
67
+ )
68
+ if validated.content is None:
69
+ raise ValueError("Chat response requires assistant content or function calls")
70
+ return TextMessage(role="assistant", content=validated.content)
71
+
72
+
73
+ def _conversation_events(messages: list[dict[str, Any]]) -> tuple[ConversationEvent, ...]:
74
+ events: list[ConversationEvent] = []
75
+ for message in messages:
76
+ role = message.get("role")
77
+ if role == "assistant":
78
+ events.append(assistant_message(message))
79
+ elif role == "tool":
80
+ events.append(ToolResult(call_id=message["tool_call_id"], content=message["content"]))
81
+ else:
82
+ events.append(TextMessage.model_validate(message))
83
+ return tuple(events)
84
+
85
+
86
+ def chat_input(messages: list[dict[str, Any]]) -> ConversationInput:
87
+ """Normalize a public task prefix before model inference."""
88
+ return ConversationInput(events=_conversation_events(messages))
89
+
90
+
91
+ def chat_conversation(messages: list[dict[str, Any]]) -> ConversationTrace:
92
+ """Normalize a complete chat transcript for a TaskCompendium verifier."""
93
+ return ConversationTrace(events=_conversation_events(messages))
@@ -0,0 +1,4 @@
1
+ # Copyright The Marin Authors
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Build task specifications, with their graders fixed, from source rows."""
@@ -0,0 +1,210 @@
1
+ # Copyright The Marin Authors
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Conversation tasks whose final answer a verifyit mode grades in process.
5
+
6
+ Each helper takes the public prompt and reference values that a declaration extracts from its row,
7
+ checks that they are usable, and returns a task with its grader set, or a rejection naming the
8
+ defect. ``evidence`` is source material kept with the grader for review, never shown to the solver.
9
+ """
10
+
11
+ import json
12
+ from collections.abc import Mapping
13
+ from typing import Any
14
+
15
+ from verifyit.modes.extract import extract_boxed
16
+ from verifyit.numeric import numeric_literal
17
+ from verifyit.spec import (
18
+ Constraint,
19
+ ExactSpec,
20
+ IfevalSpec,
21
+ JsonSchemaSpec,
22
+ MathSpec,
23
+ MathType,
24
+ McqSpec,
25
+ NumericSpec,
26
+ SchemaFormat,
27
+ Spec,
28
+ )
29
+
30
+ from taskcompendium.grader import verifyit_package
31
+ from taskcompendium.models import (
32
+ AnswerType,
33
+ ConversationInput,
34
+ EnvironmentRequirements,
35
+ PlainText,
36
+ ResourceGroups,
37
+ TaskResource,
38
+ TaskSpec,
39
+ TextMessage,
40
+ )
41
+ from taskcompendium.pipeline.models import ImportFailureKind, ImportRejection, RawRow
42
+ from taskcompendium.runtime.resources import inline_resource
43
+
44
+ EVIDENCE_PATH = "reference/source-evidence.json"
45
+
46
+
47
+ def source_defect(reason: str, detail: str) -> ImportRejection:
48
+ return ImportRejection(kind=ImportFailureKind.SOURCE_DEFECT, reason=reason, detail=detail)
49
+
50
+
51
+ def unsupported(reason: str, detail: str) -> ImportRejection:
52
+ return ImportRejection(kind=ImportFailureKind.UNSUPPORTED, reason=reason, detail=detail)
53
+
54
+
55
+ def _text(value: Any) -> str | None:
56
+ return value.strip() if isinstance(value, str) and value.strip() else None
57
+
58
+
59
+ def evidence_resource(evidence: Mapping[str, Any]) -> TaskResource:
60
+ """Source fields retained beside the grader for review and audit."""
61
+ return inline_resource(EVIDENCE_PATH, json.dumps(dict(evidence), ensure_ascii=False, sort_keys=True).encode())
62
+
63
+
64
+ def answer_task(
65
+ row: RawRow,
66
+ *,
67
+ prompt: str,
68
+ spec: Spec,
69
+ answer_type: AnswerType = AnswerType.TEXT,
70
+ evidence: Mapping[str, Any] | None = None,
71
+ resources: tuple[TaskResource, ...] = (),
72
+ ) -> TaskSpec:
73
+ """A one-message conversation task graded in process by ``spec``."""
74
+ verifier = (*resources, *((evidence_resource(evidence),) if evidence else ()))
75
+ package = verifyit_package(spec, verifier)
76
+ return TaskSpec(
77
+ id=row.id,
78
+ source=row.source,
79
+ context=ConversationInput(events=(TextMessage(role="user", content=prompt),)),
80
+ environment_requirements=EnvironmentRequirements(),
81
+ resources=ResourceGroups(verifier=package.resources),
82
+ answer_type=answer_type,
83
+ answer_format=PlainText(),
84
+ grader=package.grader,
85
+ )
86
+
87
+
88
+ def math_type(expected: str) -> MathType:
89
+ """Keep structured mathematical answers distinct from scalar values."""
90
+ if expected.startswith("[") and expected.endswith("]"):
91
+ return MathType.LIST
92
+ if expected.startswith(("[", "(")) and expected.endswith(("]", ")")) and "," in expected:
93
+ return MathType.TUPLE if expected.startswith("(") and expected.endswith(")") else MathType.INTERVAL
94
+ if expected.startswith((r"\{", "{")):
95
+ return MathType.SET
96
+ if "=" in expected or r"\approx" in expected:
97
+ return MathType.EQUATION
98
+ return MathType.SCALAR
99
+
100
+
101
+ def math_answer_task(
102
+ row: RawRow, *, prompt: Any, answer: Any, evidence: Mapping[str, Any] | None = None
103
+ ) -> TaskSpec | ImportRejection:
104
+ """Grade a symbolic math answer; a boxed reference is unwrapped."""
105
+ problem, reference = _text(prompt), _text(answer)
106
+ if problem is None:
107
+ return source_defect("missing_prompt", "The problem must be a nonempty string")
108
+ if reference is None:
109
+ return source_defect("invalid_reference", "The reference answer must be a nonempty string")
110
+ expected = extract_boxed(reference) or reference
111
+ return answer_task(
112
+ row, prompt=problem, spec=MathSpec(expected=expected, math_type=math_type(expected)), evidence=evidence
113
+ )
114
+
115
+
116
+ def numeric_answer_task(
117
+ row: RawRow,
118
+ *,
119
+ prompt: Any,
120
+ answer: Any,
121
+ tolerance_abs: float,
122
+ tolerance_rel: float,
123
+ evidence: Mapping[str, Any] | None = None,
124
+ ) -> TaskSpec | ImportRejection:
125
+ """Grade a numeric literal within absolute and relative tolerances."""
126
+ problem = _text(prompt)
127
+ if problem is None:
128
+ return source_defect("missing_prompt", "The problem must be a nonempty string")
129
+ reference = answer.strip() if isinstance(answer, str) else str(answer) if isinstance(answer, int | float) else None
130
+ try:
131
+ if reference is None:
132
+ raise ValueError("missing")
133
+ numeric_literal(reference)
134
+ except ValueError:
135
+ return source_defect("invalid_reference", f"The reference answer is not numeric: {answer!r}")
136
+ return answer_task(
137
+ row,
138
+ prompt=problem,
139
+ spec=NumericSpec(reference, tolerance_abs=tolerance_abs, tolerance_rel=tolerance_rel),
140
+ answer_type=AnswerType.NUMBER,
141
+ evidence=evidence,
142
+ )
143
+
144
+
145
+ def mcq_task(
146
+ row: RawRow, *, prompt: Any, answer: Any, options: int, evidence: Mapping[str, Any] | None = None
147
+ ) -> TaskSpec | ImportRejection:
148
+ """Grade an option letter; ``prompt`` already lists the ``options`` labeled choices."""
149
+ question, letter = _text(prompt), _text(answer)
150
+ if question is None:
151
+ return source_defect("missing_prompt", "The question must be a nonempty string")
152
+ if letter is None or len(letter) != 1 or not "A" <= letter.upper() < chr(65 + options):
153
+ return source_defect("invalid_reference", f"The key must be one of {options} option letters: {answer!r}")
154
+ return answer_task(row, prompt=question, spec=McqSpec(letter.upper(), options=options), evidence=evidence)
155
+
156
+
157
+ def exact_answer_task(
158
+ row: RawRow,
159
+ *,
160
+ prompt: Any,
161
+ answers: tuple[str, ...],
162
+ ignore_case: bool,
163
+ evidence: Mapping[str, Any] | None = None,
164
+ ) -> TaskSpec | ImportRejection:
165
+ """Grade an answer equal to one of ``answers`` after whitespace normalization."""
166
+ question = _text(prompt)
167
+ if question is None:
168
+ return source_defect("missing_prompt", "The question must be a nonempty string")
169
+ if not answers or not all(_text(item) for item in answers):
170
+ return source_defect("invalid_reference", "Accepted answers must be nonempty strings")
171
+ return answer_task(row, prompt=question, spec=ExactSpec(tuple(answers), ignore_case=ignore_case), evidence=evidence)
172
+
173
+
174
+ def ifeval_task(
175
+ row: RawRow,
176
+ *,
177
+ prompt: Any,
178
+ constraints: tuple[Constraint, ...],
179
+ evidence: Mapping[str, Any] | None = None,
180
+ ) -> TaskSpec | ImportRejection:
181
+ """Grade the fraction of instruction-following constraints the reply satisfies."""
182
+ instruction = _text(prompt)
183
+ if instruction is None:
184
+ return source_defect("missing_prompt", "The instruction must be a nonempty string")
185
+ if not constraints:
186
+ return unsupported("invalid_constraints", "At least one constraint is required")
187
+ return answer_task(row, prompt=instruction, spec=IfevalSpec(constraints=constraints), evidence=evidence)
188
+
189
+
190
+ def json_schema_task(
191
+ row: RawRow,
192
+ *,
193
+ prompt: Any,
194
+ schema: Any,
195
+ schema_format: SchemaFormat,
196
+ evidence: Mapping[str, Any] | None = None,
197
+ ) -> TaskSpec | ImportRejection:
198
+ """Grade a reply against ``schema`` (serialized schema text), shipped as the grader's ``schema.json``."""
199
+ instruction, document = _text(prompt), _text(schema)
200
+ if instruction is None:
201
+ return source_defect("missing_prompt", "The instruction must be a nonempty string")
202
+ if document is None:
203
+ return source_defect("invalid_schema", "The schema must be nonempty text")
204
+ return answer_task(
205
+ row,
206
+ prompt=instruction,
207
+ spec=JsonSchemaSpec(schema="schema.json", format=schema_format),
208
+ evidence=evidence,
209
+ resources=(inline_resource("schema.json", document.encode()),),
210
+ )
@@ -0,0 +1,78 @@
1
+ # Copyright The Marin Authors
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Checks and grading settings for code tasks whose hidden tests use the LiveCodeBench test-case layout."""
5
+
6
+ import ast
7
+ import re
8
+ from collections.abc import Mapping
9
+ from typing import Any
10
+
11
+ CODE_GRADER_MEMORY_MB = 5120
12
+ """The LiveCodeBench child process may use 4 GiB; the grading machine also needs room for its runtime."""
13
+ THREAD_ENVIRONMENT = {"OPENBLAS_NUM_THREADS": "1", "OMP_NUM_THREADS": "1"}
14
+ """Run each test's numeric libraries on one thread."""
15
+ CODE_BLOCK = re.compile(r"```(?:\w+)?\n(.*?)```", re.DOTALL)
16
+ """A fenced code block as the source scorers match one; they run the last block in a reply."""
17
+
18
+
19
+ def validate_code_cases(cases: Mapping[str, Any] | list[Any]) -> None:
20
+ """Reject decoded test layouts the LiveCodeBench scorer cannot normalize."""
21
+ if isinstance(cases, Mapping) and "test_cases" in cases:
22
+ if cases.get("language") not in (None, "python"):
23
+ raise ValueError("LCB supports only Python verification specs")
24
+ cases = cases["test_cases"]
25
+ elif isinstance(cases, Mapping):
26
+ inputs, outputs = cases.get("inputs"), cases.get("outputs")
27
+ if not isinstance(inputs, list) or not isinstance(outputs, list) or not inputs or len(inputs) != len(outputs):
28
+ raise ValueError("LCB requires aligned nonempty input and output lists")
29
+ kind = "functional" if cases.get("fn_name") is not None else "stdin"
30
+ cases = [
31
+ {"input": item, "output": result, "testtype": kind, "fn_name": cases.get("fn_name")}
32
+ for item, result in zip(inputs, outputs, strict=True)
33
+ ]
34
+ if not isinstance(cases, list) or not cases or not all(isinstance(case, Mapping) for case in cases):
35
+ raise ValueError("LCB requires a nonempty list of test cases")
36
+ modes = set()
37
+ names = set()
38
+ for case in cases:
39
+ kind = case.get("testtype", case.get("type"))
40
+ if kind in ("stdin", "stdin_stdout"):
41
+ if not isinstance(case.get("input"), str) or not isinstance(case.get("output"), str):
42
+ raise ValueError("Standard-input LCB cases require string input and output")
43
+ modes.add("stdin")
44
+ elif kind in ("functional", "call_based"):
45
+ metadata = case.get("metadata")
46
+ name = case.get("fn_name") or (metadata.get("func_name") if isinstance(metadata, Mapping) else None)
47
+ if not isinstance(name, str) or not name:
48
+ raise ValueError("Functional LCB cases require a function name")
49
+ if not isinstance(case.get("input"), str | list):
50
+ raise ValueError("Functional LCB input must be a list or encoded string")
51
+ modes.add("functional")
52
+ names.add(name)
53
+ else:
54
+ raise ValueError("Unsupported LCB test-case type")
55
+ if len(modes) != 1 or len(names) > 1:
56
+ raise ValueError("LCB cases must share one execution mode and function name")
57
+
58
+
59
+ def has_code_block(response: str) -> bool:
60
+ """Match a fenced code block the way the source scorers extract one."""
61
+ return CODE_BLOCK.search(response) is not None
62
+
63
+
64
+ def python_reply(solution: Any) -> str | None:
65
+ """A source solution as a reply the scorers extract code from.
66
+
67
+ Returns ``None`` when there is no solution or when the program the scorers would run is not
68
+ Python 3. Some sources keep Python 2 solutions; the graders run Python 3, so such a solution
69
+ cannot serve as a known-correct submission.
70
+ """
71
+ if not isinstance(solution, str) or not solution.strip():
72
+ return None
73
+ reply = solution if has_code_block(solution) else f"```python\n{solution}\n```"
74
+ try:
75
+ ast.parse(CODE_BLOCK.findall(reply)[-1].strip())
76
+ except SyntaxError: # Includes TabError from Python 2 tab indentation.
77
+ return None
78
+ return reply
@@ -0,0 +1,44 @@
1
+ # Copyright The Marin Authors
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Conversation tasks whose final reply a packaged grader scores."""
5
+
6
+ from collections.abc import Mapping, Sequence
7
+ from typing import Any
8
+
9
+ from taskcompendium.convert.answers import evidence_resource
10
+ from taskcompendium.grader import GraderPackage
11
+ from taskcompendium.models import (
12
+ AnswerType,
13
+ ConversationEvent,
14
+ ConversationInput,
15
+ EnvironmentRequirements,
16
+ PlainText,
17
+ ResourceGroups,
18
+ TaskSpec,
19
+ )
20
+ from taskcompendium.pipeline.models import RawRow
21
+
22
+
23
+ def conversation_task(
24
+ row: RawRow,
25
+ *,
26
+ events: Sequence[ConversationEvent],
27
+ package: GraderPackage,
28
+ evidence: Mapping[str, Any] | None = None,
29
+ ) -> TaskSpec:
30
+ """A task whose plain-text reply to ``events`` the packaged grader scores.
31
+
32
+ ``evidence`` is source material kept beside the grader for review, never shown to the solver.
33
+ """
34
+ verifier = (*package.resources, *((evidence_resource(evidence),) if evidence else ()))
35
+ return TaskSpec(
36
+ id=row.id,
37
+ source=row.source,
38
+ context=ConversationInput(events=tuple(events)),
39
+ environment_requirements=EnvironmentRequirements(),
40
+ resources=ResourceGroups(verifier=verifier),
41
+ answer_type=AnswerType.TEXT,
42
+ answer_format=PlainText(),
43
+ grader=package.grader,
44
+ )
@@ -0,0 +1,34 @@
1
+ # Copyright The Marin Authors
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Move a source's file-delivery wording into the assistant reply and record the rewrite.
5
+
6
+ Many sources were written for a shell agent that writes its answer to a file. A conversation task
7
+ asks for the same answer in its reply instead; the rewrite is kept as a normalization change, so the
8
+ audit retains the source's original wording.
9
+ """
10
+
11
+ from collections.abc import Sequence
12
+
13
+ from taskcompendium.models import TaskSpec, TextMessage
14
+ from taskcompendium.pipeline.models import NormalizationChange, NormalizedTask
15
+
16
+
17
+ def replace_phrases(text: str, phrases: Sequence[tuple[str, str]]) -> str:
18
+ """Apply each ``(original, replacement)`` rewrite in order."""
19
+ for original, replacement in phrases:
20
+ text = text.replace(original, replacement)
21
+ return text
22
+
23
+
24
+ def rewritten_task(task: TaskSpec, *, original: str, reason: str) -> TaskSpec | NormalizedTask:
25
+ """``task``, with a change record when its first message differs from the source's ``original``.
26
+
27
+ Stripping outer whitespace alone is not recorded as a change.
28
+ """
29
+ prompt = task.context.events[0]
30
+ assert isinstance(prompt, TextMessage)
31
+ if prompt.content.strip() == original.strip():
32
+ return task
33
+ change = NormalizationChange(field="instruction", reason=reason, original=original, replacement=prompt.content)
34
+ return NormalizedTask(task, (change,))
@@ -0,0 +1,16 @@
1
+ # Copyright The Marin Authors
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Environment requirements for graders that run an image in a fresh machine."""
5
+
6
+ from shellbox.machine import Backend
7
+
8
+ from taskcompendium.models import EnvironmentRequirements
9
+
10
+ IMAGE_BACKENDS = (Backend.GVISOR, Backend.DOCKER)
11
+ """Backends that start a fresh machine from a digest-pinned image."""
12
+
13
+
14
+ def grading_environment(image: str) -> EnvironmentRequirements:
15
+ """A grader environment that runs the digest-pinned ``image`` under gVisor or Docker."""
16
+ return EnvironmentRequirements(docker_image=image, compatible_backends=IMAGE_BACKENDS)