synth-ai 0.2.14__py3-none-any.whl → 0.2.17__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.

Potentially problematic release.


This version of synth-ai might be problematic. Click here for more details.

Files changed (354) hide show
  1. examples/README.md +1 -0
  2. examples/analyze_semantic_words.sh +2 -2
  3. examples/blog_posts/pokemon_vl/README.md +98 -0
  4. examples/blog_posts/pokemon_vl/configs/eval_qwen3_vl.toml +25 -0
  5. examples/blog_posts/pokemon_vl/configs/eval_rl_final.toml +24 -0
  6. examples/blog_posts/pokemon_vl/configs/filter_high_reward.toml +10 -0
  7. examples/blog_posts/pokemon_vl/configs/train_rl_from_sft.toml +42 -0
  8. examples/blog_posts/pokemon_vl/configs/train_sft_qwen4b_vl.toml +40 -0
  9. examples/blog_posts/warming_up_to_rl/README.md +158 -0
  10. examples/blog_posts/warming_up_to_rl/configs/eval_ft_qwen4b.toml +25 -0
  11. examples/blog_posts/warming_up_to_rl/configs/eval_groq_qwen32b.toml +25 -0
  12. examples/blog_posts/warming_up_to_rl/configs/eval_openai_gpt_oss_120b.toml +29 -0
  13. examples/blog_posts/warming_up_to_rl/configs/filter_high_reward_dataset.toml +10 -0
  14. examples/blog_posts/warming_up_to_rl/configs/train_rl_from_sft.toml +41 -0
  15. examples/blog_posts/warming_up_to_rl/configs/train_sft_qwen4b.toml +40 -0
  16. examples/dev/qwen3_32b_qlora_4xh100.toml +5 -0
  17. examples/multi_step/SFT_README.md +147 -0
  18. examples/multi_step/configs/crafter_rl_outcome.toml +1 -1
  19. examples/multi_step/configs/crafter_rl_stepwise_hosted_judge.toml +73 -115
  20. examples/multi_step/configs/crafter_rl_stepwise_shaped.toml +1 -1
  21. examples/multi_step/configs/crafter_rl_stepwise_simple.toml +1 -1
  22. examples/multi_step/configs/crafter_rl_stepwise_simple_NEW_FORMAT.toml +105 -0
  23. examples/multi_step/configs/crafter_sft_qwen30b_lora.toml +62 -0
  24. examples/multi_step/configs/verilog_rl_lora.toml +80 -123
  25. examples/multi_step/convert_traces_to_sft.py +84 -0
  26. examples/multi_step/run_sft_qwen30b.sh +45 -0
  27. examples/qwen_coder/configs/coder_lora_30b.toml +1 -2
  28. examples/qwen_coder/configs/coder_lora_4b.toml +5 -1
  29. examples/qwen_coder/configs/coder_lora_small.toml +1 -2
  30. examples/qwen_vl/BUGS_AND_FIXES.md +232 -0
  31. examples/qwen_vl/IMAGE_VALIDATION_COMPLETE.md +271 -0
  32. examples/qwen_vl/IMAGE_VALIDATION_SUMMARY.md +260 -0
  33. examples/qwen_vl/INFERENCE_SFT_TESTS.md +412 -0
  34. examples/qwen_vl/NEXT_STEPS_2B.md +325 -0
  35. examples/qwen_vl/QUICKSTART.md +327 -0
  36. examples/qwen_vl/QUICKSTART_RL_VISION.md +110 -0
  37. examples/qwen_vl/README.md +152 -0
  38. examples/qwen_vl/RL_VISION_COMPLETE.md +475 -0
  39. examples/qwen_vl/RL_VISION_TESTING.md +333 -0
  40. examples/qwen_vl/SDK_VISION_INTEGRATION.md +328 -0
  41. examples/qwen_vl/SETUP_COMPLETE.md +274 -0
  42. examples/qwen_vl/VISION_TESTS_COMPLETE.md +489 -0
  43. examples/qwen_vl/VLM_PIPELINE_COMPLETE.md +242 -0
  44. examples/qwen_vl/__init__.py +2 -0
  45. examples/qwen_vl/collect_data_via_cli.md +415 -0
  46. examples/qwen_vl/collect_vision_traces.py +368 -0
  47. examples/qwen_vl/configs/crafter_rl_vision_qwen3vl4b.toml +110 -0
  48. examples/qwen_vl/configs/crafter_vlm_sft_example.toml +59 -0
  49. examples/qwen_vl/configs/eval_gpt4o_mini_vision.toml +26 -0
  50. examples/qwen_vl/configs/eval_gpt4o_vision_proper.toml +29 -0
  51. examples/qwen_vl/configs/eval_gpt5nano_vision.toml +26 -0
  52. examples/qwen_vl/configs/eval_qwen3vl_vision.toml +26 -0
  53. examples/qwen_vl/configs/filter_qwen3vl_sft.toml +49 -0
  54. examples/qwen_vl/configs/filter_vision_sft.toml +52 -0
  55. examples/qwen_vl/configs/filter_vision_test.toml +8 -0
  56. examples/qwen_vl/configs/sft_qwen3_vl_2b_test.toml +54 -0
  57. examples/qwen_vl/crafter_gpt5nano_agent.py +308 -0
  58. examples/qwen_vl/crafter_qwen_vl_agent.py +300 -0
  59. examples/qwen_vl/run_vision_comparison.sh +61 -0
  60. examples/qwen_vl/run_vision_sft_pipeline.sh +175 -0
  61. examples/qwen_vl/test_image_validation.py +201 -0
  62. examples/qwen_vl/test_sft_vision_data.py +110 -0
  63. examples/rl/README.md +6 -6
  64. examples/rl/configs/eval_base_qwen.toml +17 -0
  65. examples/rl/configs/eval_rl_qwen.toml +13 -0
  66. examples/rl/configs/rl_from_base_qwen.toml +62 -0
  67. examples/rl/configs/rl_from_base_qwen17.toml +79 -0
  68. examples/rl/configs/rl_from_ft_qwen.toml +37 -0
  69. examples/rl/run_eval.py +436 -0
  70. examples/rl/run_rl_and_save.py +111 -0
  71. examples/rl/task_app/README.md +21 -0
  72. examples/rl/task_app/math_single_step.py +990 -0
  73. examples/rl/task_app/math_task_app.py +111 -0
  74. examples/run_crafter_demo.sh +2 -2
  75. examples/sft/README.md +6 -6
  76. examples/sft/configs/crafter_fft_qwen0p6b.toml +7 -2
  77. examples/sft/configs/crafter_lora_qwen0p6b.toml +7 -3
  78. examples/sft/evaluate.py +2 -4
  79. examples/sft/export_dataset.py +7 -4
  80. examples/swe/task_app/README.md +33 -3
  81. examples/swe/task_app/grpo_swe_mini.py +4 -1
  82. examples/swe/task_app/grpo_swe_mini_task_app.py +0 -12
  83. examples/swe/task_app/hosted/envs/crafter/react_agent.py +1 -1
  84. examples/swe/task_app/hosted/envs/mini_swe/environment.py +50 -23
  85. examples/swe/task_app/hosted/inference/openai_client.py +4 -4
  86. examples/swe/task_app/hosted/policy_routes.py +0 -2
  87. examples/swe/task_app/hosted/rollout.py +0 -8
  88. examples/swe/task_app/morph_backend.py +178 -0
  89. examples/task_apps/crafter/task_app/README.md +1 -1
  90. examples/task_apps/crafter/task_app/grpo_crafter.py +70 -10
  91. examples/task_apps/crafter/task_app/grpo_crafter_task_app.py +1 -1
  92. examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/policy.py +63 -27
  93. examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/react_agent.py +1 -2
  94. examples/task_apps/crafter/task_app/synth_envs_hosted/inference/openai_client.py +48 -50
  95. examples/task_apps/crafter/task_app/synth_envs_hosted/policy_routes.py +75 -36
  96. examples/task_apps/crafter/task_app/synth_envs_hosted/rollout.py +31 -15
  97. examples/task_apps/enron/__init__.py +1 -0
  98. examples/task_apps/enron/task_app/grpo_enron_task_app.py +1 -1
  99. examples/task_apps/math/README.md +1 -2
  100. examples/task_apps/pokemon_red/README.md +3 -4
  101. examples/task_apps/pokemon_red/eval_image_only_gpt4o.toml +6 -5
  102. examples/task_apps/pokemon_red/eval_pokemon_red_policy.py +1 -2
  103. examples/task_apps/pokemon_red/task_app.py +36 -5
  104. examples/task_apps/sokoban/README.md +2 -3
  105. examples/task_apps/verilog/eval_groq_qwen32b.toml +12 -14
  106. examples/task_apps/verilog/task_app/grpo_verilog_task_app.py +1 -1
  107. examples/vlm/README.md +3 -3
  108. examples/vlm/configs/crafter_vlm_gpt4o.toml +5 -0
  109. examples/vlm/crafter_openai_vlm_agent.py +3 -5
  110. examples/vlm/filter_image_rows.py +1 -1
  111. examples/vlm/run_crafter_vlm_benchmark.py +2 -2
  112. examples/warming_up_to_rl/_utils.py +92 -0
  113. examples/warming_up_to_rl/analyze_trace_db.py +1 -1
  114. examples/warming_up_to_rl/configs/crafter_fft.toml +5 -0
  115. examples/warming_up_to_rl/configs/eval_fft_qwen4b.toml +2 -0
  116. examples/warming_up_to_rl/configs/eval_groq_qwen32b.toml +2 -0
  117. examples/warming_up_to_rl/configs/eval_modal_qwen4b.toml +2 -1
  118. examples/warming_up_to_rl/configs/rl_from_base_qwen4b.toml +2 -1
  119. examples/warming_up_to_rl/configs/rl_from_ft.toml +2 -0
  120. examples/warming_up_to_rl/export_trace_sft.py +174 -60
  121. examples/warming_up_to_rl/readme.md +63 -132
  122. examples/warming_up_to_rl/run_fft_and_save.py +1 -1
  123. examples/warming_up_to_rl/run_local_rollout_traced.py +1 -1
  124. examples/warming_up_to_rl/run_rl_and_save.py +1 -1
  125. examples/warming_up_to_rl/task_app/README.md +42 -0
  126. examples/warming_up_to_rl/task_app/grpo_crafter.py +827 -0
  127. examples/warming_up_to_rl/task_app/grpo_crafter_task_app.py +135 -0
  128. examples/warming_up_to_rl/task_app/synth_envs_hosted/README.md +173 -0
  129. examples/warming_up_to_rl/task_app/synth_envs_hosted/__init__.py +5 -0
  130. examples/warming_up_to_rl/task_app/synth_envs_hosted/branching.py +143 -0
  131. examples/warming_up_to_rl/task_app/synth_envs_hosted/environment_routes.py +1226 -0
  132. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/__init__.py +1 -0
  133. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/__init__.py +6 -0
  134. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/app.py +1 -0
  135. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/environment.py +522 -0
  136. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/policy.py +454 -0
  137. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/react_agent.py +108 -0
  138. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/shared.py +305 -0
  139. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/tools.py +47 -0
  140. examples/warming_up_to_rl/task_app/synth_envs_hosted/hosted_app.py +204 -0
  141. examples/warming_up_to_rl/task_app/synth_envs_hosted/inference/__init__.py +5 -0
  142. examples/warming_up_to_rl/task_app/synth_envs_hosted/inference/openai_client.py +618 -0
  143. examples/warming_up_to_rl/task_app/synth_envs_hosted/main.py +100 -0
  144. examples/warming_up_to_rl/task_app/synth_envs_hosted/policy_routes.py +1084 -0
  145. examples/warming_up_to_rl/task_app/synth_envs_hosted/registry.py +195 -0
  146. examples/warming_up_to_rl/task_app/synth_envs_hosted/rollout.py +1861 -0
  147. examples/warming_up_to_rl/task_app/synth_envs_hosted/storage/__init__.py +5 -0
  148. examples/warming_up_to_rl/task_app/synth_envs_hosted/storage/volume.py +211 -0
  149. examples/warming_up_to_rl/task_app/synth_envs_hosted/test_agents.py +161 -0
  150. examples/warming_up_to_rl/task_app/synth_envs_hosted/test_service.py +137 -0
  151. examples/warming_up_to_rl/task_app/synth_envs_hosted/utils.py +62 -0
  152. examples/workflows/math_rl/configs/rl_from_base_qwen.toml +27 -0
  153. examples/workflows/math_rl/configs/rl_from_base_qwen17.toml +5 -0
  154. synth_ai/__init__.py +44 -30
  155. synth_ai/_utils/__init__.py +47 -0
  156. synth_ai/_utils/base_url.py +10 -0
  157. synth_ai/_utils/http.py +10 -0
  158. synth_ai/_utils/prompts.py +10 -0
  159. synth_ai/_utils/task_app_state.py +12 -0
  160. synth_ai/_utils/user_config.py +10 -0
  161. synth_ai/api/models/supported.py +144 -7
  162. synth_ai/api/train/__init__.py +13 -1
  163. synth_ai/api/train/builders.py +9 -3
  164. synth_ai/api/train/cli.py +155 -17
  165. synth_ai/api/train/config_finder.py +18 -11
  166. synth_ai/api/train/configs/__init__.py +8 -1
  167. synth_ai/api/train/configs/rl.py +32 -7
  168. synth_ai/api/train/configs/sft.py +6 -2
  169. synth_ai/api/train/configs/shared.py +59 -2
  170. synth_ai/api/train/env_resolver.py +13 -10
  171. synth_ai/auth/credentials.py +119 -0
  172. synth_ai/cli/__init__.py +61 -69
  173. synth_ai/cli/_modal_wrapper.py +7 -5
  174. synth_ai/cli/_typer_patch.py +0 -2
  175. synth_ai/cli/_validate_task_app.py +22 -4
  176. synth_ai/cli/commands/__init__.py +17 -0
  177. synth_ai/cli/commands/demo/__init__.py +6 -0
  178. synth_ai/cli/commands/demo/core.py +163 -0
  179. synth_ai/cli/commands/deploy/__init__.py +23 -0
  180. synth_ai/cli/commands/deploy/core.py +614 -0
  181. synth_ai/cli/commands/deploy/errors.py +72 -0
  182. synth_ai/cli/commands/deploy/validation.py +11 -0
  183. synth_ai/cli/commands/eval/__init__.py +19 -0
  184. synth_ai/cli/commands/eval/core.py +1109 -0
  185. synth_ai/cli/commands/eval/errors.py +81 -0
  186. synth_ai/cli/commands/eval/validation.py +133 -0
  187. synth_ai/cli/commands/filter/__init__.py +12 -0
  188. synth_ai/cli/commands/filter/core.py +388 -0
  189. synth_ai/cli/commands/filter/errors.py +55 -0
  190. synth_ai/cli/commands/filter/validation.py +77 -0
  191. synth_ai/cli/commands/help/__init__.py +177 -0
  192. synth_ai/cli/commands/help/core.py +73 -0
  193. synth_ai/cli/commands/status/__init__.py +64 -0
  194. synth_ai/cli/commands/status/client.py +192 -0
  195. synth_ai/cli/commands/status/config.py +92 -0
  196. synth_ai/cli/commands/status/errors.py +20 -0
  197. synth_ai/cli/commands/status/formatters.py +164 -0
  198. synth_ai/cli/commands/status/subcommands/__init__.py +9 -0
  199. synth_ai/cli/commands/status/subcommands/files.py +79 -0
  200. synth_ai/cli/commands/status/subcommands/jobs.py +334 -0
  201. synth_ai/cli/commands/status/subcommands/models.py +79 -0
  202. synth_ai/cli/commands/status/subcommands/runs.py +81 -0
  203. synth_ai/cli/commands/status/subcommands/summary.py +47 -0
  204. synth_ai/cli/commands/status/utils.py +114 -0
  205. synth_ai/cli/commands/train/__init__.py +53 -0
  206. synth_ai/cli/commands/train/core.py +21 -0
  207. synth_ai/cli/commands/train/errors.py +117 -0
  208. synth_ai/cli/commands/train/judge_schemas.py +199 -0
  209. synth_ai/cli/commands/train/judge_validation.py +304 -0
  210. synth_ai/cli/commands/train/validation.py +443 -0
  211. synth_ai/cli/demo.py +2 -162
  212. synth_ai/cli/deploy/__init__.py +28 -0
  213. synth_ai/cli/deploy/core.py +5 -0
  214. synth_ai/cli/deploy/errors.py +23 -0
  215. synth_ai/cli/deploy/validation.py +5 -0
  216. synth_ai/cli/eval/__init__.py +36 -0
  217. synth_ai/cli/eval/core.py +5 -0
  218. synth_ai/cli/eval/errors.py +31 -0
  219. synth_ai/cli/eval/validation.py +5 -0
  220. synth_ai/cli/filter/__init__.py +28 -0
  221. synth_ai/cli/filter/core.py +5 -0
  222. synth_ai/cli/filter/errors.py +23 -0
  223. synth_ai/cli/filter/validation.py +5 -0
  224. synth_ai/cli/legacy_root_backup.py +3 -1
  225. synth_ai/cli/lib/__init__.py +10 -0
  226. synth_ai/cli/lib/task_app_discovery.py +7 -0
  227. synth_ai/cli/lib/task_app_env.py +518 -0
  228. synth_ai/cli/modal_serve/__init__.py +12 -0
  229. synth_ai/cli/modal_serve/core.py +14 -0
  230. synth_ai/cli/modal_serve/errors.py +8 -0
  231. synth_ai/cli/modal_serve/validation.py +11 -0
  232. synth_ai/cli/recent.py +2 -1
  233. synth_ai/cli/serve/__init__.py +12 -0
  234. synth_ai/cli/serve/core.py +14 -0
  235. synth_ai/cli/serve/errors.py +8 -0
  236. synth_ai/cli/serve/validation.py +11 -0
  237. synth_ai/cli/setup.py +21 -0
  238. synth_ai/cli/status.py +7 -126
  239. synth_ai/cli/task_app_deploy.py +7 -0
  240. synth_ai/cli/task_app_list.py +25 -0
  241. synth_ai/cli/task_app_modal_serve.py +11 -0
  242. synth_ai/cli/task_app_serve.py +11 -0
  243. synth_ai/cli/task_apps.py +110 -1499
  244. synth_ai/cli/traces.py +1 -1
  245. synth_ai/cli/train/__init__.py +12 -0
  246. synth_ai/cli/train/core.py +21 -0
  247. synth_ai/cli/train/errors.py +8 -0
  248. synth_ai/cli/train/validation.py +24 -0
  249. synth_ai/cli/train.py +5 -0
  250. synth_ai/cli/turso.py +1 -1
  251. synth_ai/cli/watch.py +1 -1
  252. synth_ai/demos/__init__.py +10 -0
  253. synth_ai/demos/core/__init__.py +28 -1
  254. synth_ai/demos/crafter/__init__.py +1 -0
  255. synth_ai/demos/crafter/crafter_fft_4b.toml +55 -0
  256. synth_ai/demos/crafter/grpo_crafter_task_app.py +185 -0
  257. synth_ai/demos/crafter/rl_from_base_qwen4b.toml +74 -0
  258. synth_ai/demos/demo_registry.py +176 -0
  259. synth_ai/demos/demo_task_apps/crafter/grpo_crafter_task_app.py +1 -1
  260. synth_ai/demos/math/__init__.py +1 -0
  261. synth_ai/demos/math/_common.py +16 -0
  262. synth_ai/demos/math/app.py +38 -0
  263. synth_ai/demos/math/config.toml +76 -0
  264. synth_ai/demos/math/deploy_modal.py +54 -0
  265. synth_ai/demos/math/modal_task_app.py +702 -0
  266. synth_ai/demos/math/task_app_entry.py +51 -0
  267. synth_ai/environments/environment/core.py +7 -1
  268. synth_ai/environments/examples/bandit/engine.py +0 -1
  269. synth_ai/environments/examples/bandit/environment.py +0 -1
  270. synth_ai/environments/examples/red/engine.py +33 -12
  271. synth_ai/environments/examples/red/engine_helpers/reward_components.py +151 -179
  272. synth_ai/environments/examples/red/environment.py +26 -0
  273. synth_ai/environments/examples/red/trace_hooks_v3.py +168 -0
  274. synth_ai/environments/examples/wordle/environment.py +0 -1
  275. synth_ai/evals/base.py +16 -5
  276. synth_ai/evals/client.py +1 -1
  277. synth_ai/http.py +8 -22
  278. synth_ai/inference/client.py +1 -1
  279. synth_ai/judge_schemas.py +4 -5
  280. synth_ai/learning/client.py +1 -1
  281. synth_ai/learning/health.py +1 -1
  282. synth_ai/learning/jobs.py +1 -1
  283. synth_ai/learning/rl/client.py +4 -2
  284. synth_ai/learning/rl/env_keys.py +1 -1
  285. synth_ai/learning/rl/secrets.py +1 -1
  286. synth_ai/learning/sft/client.py +1 -1
  287. synth_ai/learning/sft/data.py +407 -4
  288. synth_ai/learning/validators.py +4 -1
  289. synth_ai/streaming/__init__.py +29 -0
  290. synth_ai/streaming/config.py +94 -0
  291. synth_ai/streaming/handlers.py +469 -0
  292. synth_ai/streaming/streamer.py +301 -0
  293. synth_ai/streaming/types.py +95 -0
  294. synth_ai/task/apps/__init__.py +4 -2
  295. synth_ai/task/config.py +6 -4
  296. synth_ai/task/rubrics/__init__.py +1 -2
  297. synth_ai/task/rubrics/loaders.py +14 -10
  298. synth_ai/task/rubrics.py +219 -0
  299. synth_ai/task/trace_correlation_helpers.py +24 -11
  300. synth_ai/task/tracing_utils.py +14 -3
  301. synth_ai/task/validators.py +0 -1
  302. synth_ai/tracing_v3/abstractions.py +3 -3
  303. synth_ai/tracing_v3/config.py +15 -13
  304. synth_ai/tracing_v3/constants.py +21 -0
  305. synth_ai/tracing_v3/db_config.py +3 -1
  306. synth_ai/tracing_v3/decorators.py +10 -7
  307. synth_ai/tracing_v3/llm_call_record_helpers.py +5 -5
  308. synth_ai/tracing_v3/migration_helper.py +1 -2
  309. synth_ai/tracing_v3/session_tracer.py +7 -7
  310. synth_ai/tracing_v3/storage/base.py +29 -29
  311. synth_ai/tracing_v3/storage/config.py +3 -3
  312. synth_ai/tracing_v3/turso/daemon.py +8 -9
  313. synth_ai/tracing_v3/turso/native_manager.py +80 -72
  314. synth_ai/tracing_v3/utils.py +2 -2
  315. synth_ai/utils/__init__.py +101 -0
  316. synth_ai/utils/base_url.py +94 -0
  317. synth_ai/utils/cli.py +131 -0
  318. synth_ai/utils/env.py +294 -0
  319. synth_ai/utils/http.py +172 -0
  320. synth_ai/utils/modal.py +308 -0
  321. synth_ai/utils/process.py +212 -0
  322. synth_ai/utils/prompts.py +39 -0
  323. synth_ai/utils/sqld.py +122 -0
  324. synth_ai/utils/task_app_discovery.py +882 -0
  325. synth_ai/utils/task_app_env.py +186 -0
  326. synth_ai/utils/task_app_state.py +318 -0
  327. synth_ai/utils/user_config.py +137 -0
  328. synth_ai/v0/config/__init__.py +1 -5
  329. synth_ai/v0/config/base_url.py +1 -7
  330. synth_ai/v0/tracing/config.py +1 -1
  331. synth_ai/v0/tracing/decorators.py +1 -1
  332. synth_ai/v0/tracing/upload.py +1 -1
  333. synth_ai/v0/tracing_v1/config.py +1 -1
  334. synth_ai/v0/tracing_v1/decorators.py +1 -1
  335. synth_ai/v0/tracing_v1/upload.py +1 -1
  336. {synth_ai-0.2.14.dist-info → synth_ai-0.2.17.dist-info}/METADATA +91 -32
  337. {synth_ai-0.2.14.dist-info → synth_ai-0.2.17.dist-info}/RECORD +341 -154
  338. synth_ai/cli/man.py +0 -106
  339. synth_ai/cli/tui.py +0 -57
  340. synth_ai/compound/cais.py +0 -0
  341. synth_ai/core/experiment.py +0 -13
  342. synth_ai/core/system.py +0 -15
  343. synth_ai/demo_registry.py +0 -295
  344. synth_ai/handshake.py +0 -109
  345. synth_ai/tui/__init__.py +0 -5
  346. synth_ai/tui/__main__.py +0 -13
  347. synth_ai/tui/cli/__init__.py +0 -1
  348. synth_ai/tui/cli/query_experiments.py +0 -164
  349. synth_ai/tui/cli/query_experiments_v3.py +0 -164
  350. synth_ai/tui/dashboard.py +0 -906
  351. {synth_ai-0.2.14.dist-info → synth_ai-0.2.17.dist-info}/WHEEL +0 -0
  352. {synth_ai-0.2.14.dist-info → synth_ai-0.2.17.dist-info}/entry_points.txt +0 -0
  353. {synth_ai-0.2.14.dist-info → synth_ai-0.2.17.dist-info}/licenses/LICENSE +0 -0
  354. {synth_ai-0.2.14.dist-info → synth_ai-0.2.17.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,81 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+
6
+ class EvalCliError(RuntimeError):
7
+ """Base exception for eval CLI failures."""
8
+
9
+
10
+ @dataclass(slots=True)
11
+ class TomlUnavailableError(EvalCliError):
12
+ hint: str | None = None
13
+
14
+
15
+ @dataclass(slots=True)
16
+ class EvalConfigNotFoundError(EvalCliError):
17
+ path: str
18
+
19
+
20
+ @dataclass(slots=True)
21
+ class EvalConfigParseError(EvalCliError):
22
+ path: str
23
+ detail: str
24
+
25
+
26
+ @dataclass(slots=True)
27
+ class MissingEvalTableError(EvalCliError):
28
+ """Raised when the eval config lacks an [eval] table."""
29
+
30
+
31
+ @dataclass(slots=True)
32
+ class InvalidEvalConfigError(EvalCliError):
33
+ detail: str
34
+
35
+
36
+ @dataclass(slots=True)
37
+ class SeedParseError(EvalCliError):
38
+ value: str
39
+
40
+
41
+ @dataclass(slots=True)
42
+ class MetadataFilterFormatError(EvalCliError):
43
+ entry: str
44
+
45
+
46
+ @dataclass(slots=True)
47
+ class TaskInfoUnavailableError(EvalCliError):
48
+ """Raised when metadata filters require task info but the task app does not expose it."""
49
+
50
+
51
+ @dataclass(slots=True)
52
+ class NoSeedsMatchedError(EvalCliError):
53
+ hint: str | None = None
54
+
55
+
56
+ @dataclass(slots=True)
57
+ class MetadataSQLExecutionError(EvalCliError):
58
+ query: str
59
+ detail: str
60
+
61
+
62
+ @dataclass(slots=True)
63
+ class MetadataSQLResultError(EvalCliError):
64
+ query: str
65
+ detail: str
66
+
67
+
68
+ __all__ = [
69
+ "EvalCliError",
70
+ "TomlUnavailableError",
71
+ "EvalConfigNotFoundError",
72
+ "EvalConfigParseError",
73
+ "MissingEvalTableError",
74
+ "InvalidEvalConfigError",
75
+ "SeedParseError",
76
+ "MetadataFilterFormatError",
77
+ "TaskInfoUnavailableError",
78
+ "NoSeedsMatchedError",
79
+ "MetadataSQLExecutionError",
80
+ "MetadataSQLResultError",
81
+ ]
@@ -0,0 +1,133 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from collections.abc import MutableMapping
5
+ from typing import Any
6
+
7
+ __all__ = ["validate_eval_options"]
8
+
9
+ _SEED_RANGE = re.compile(r"^\s*(-?\d+)\s*-\s*(-?\d+)\s*$")
10
+
11
+
12
+ def _coerce_bool(value: Any) -> bool:
13
+ if isinstance(value, str):
14
+ return value.strip().lower() in {"1", "true", "yes", "on"}
15
+ return bool(value)
16
+
17
+
18
+ def _coerce_int(value: Any) -> int | None:
19
+ if value is None or value == "":
20
+ return None
21
+ return int(value)
22
+
23
+
24
+ def _parse_seeds(value: Any) -> list[int]:
25
+ if value is None:
26
+ return []
27
+ if isinstance(value, str):
28
+ chunks = [chunk.strip() for chunk in value.split(",") if chunk.strip()]
29
+ elif isinstance(value, list | tuple | set):
30
+ chunks = list(value)
31
+ else:
32
+ chunks = [value]
33
+ seeds: list[int] = []
34
+ for chunk in chunks:
35
+ if isinstance(chunk, int):
36
+ seeds.append(chunk)
37
+ else:
38
+ text = str(chunk).strip()
39
+ if not text:
40
+ continue
41
+ match = _SEED_RANGE.match(text)
42
+ if match:
43
+ start = int(match.group(1))
44
+ end = int(match.group(2))
45
+ if start > end:
46
+ raise ValueError(f"Invalid seed range '{text}': start must be <= end")
47
+ seeds.extend(range(start, end + 1))
48
+ else:
49
+ seeds.append(int(text))
50
+ return seeds
51
+
52
+
53
+ def _normalize_metadata(value: Any) -> dict[str, str]:
54
+ if value is None:
55
+ return {}
56
+ if isinstance(value, MutableMapping):
57
+ return {str(k): str(v) for k, v in value.items()}
58
+ if isinstance(value, list | tuple):
59
+ result: dict[str, str] = {}
60
+ for item in value:
61
+ if isinstance(item, str) and "=" in item:
62
+ key, val = item.split("=", 1)
63
+ result[key.strip()] = val.strip()
64
+ return result
65
+ if isinstance(value, str) and "=" in value:
66
+ key, val = value.split("=", 1)
67
+ return {key.strip(): val.strip()}
68
+ return {}
69
+
70
+
71
+ def _ensure_list(value: Any) -> list[str] | None:
72
+ if value is None:
73
+ return None
74
+ if isinstance(value, list | tuple | set):
75
+ return [str(item) for item in value]
76
+ return [str(value)]
77
+
78
+
79
+ def _ensure_dict(value: Any) -> dict[str, Any]:
80
+ if isinstance(value, MutableMapping):
81
+ return dict(value)
82
+ return {}
83
+
84
+
85
+ def validate_eval_options(options: MutableMapping[str, Any]) -> MutableMapping[str, Any]:
86
+ """Validate and normalise eval configuration options."""
87
+
88
+ result: dict[str, Any] = dict(options)
89
+
90
+ if "seeds" in result:
91
+ result["seeds"] = _parse_seeds(result.get("seeds"))
92
+
93
+ for field in ("max_turns", "max_llm_calls", "concurrency"):
94
+ try:
95
+ result[field] = _coerce_int(result.get(field))
96
+ except Exception as exc:
97
+ raise ValueError(f"Invalid value for {field}: {result.get(field)}") from exc
98
+
99
+ if result.get("max_llm_calls") is None:
100
+ result["max_llm_calls"] = 10
101
+ if result.get("concurrency") is None:
102
+ result["concurrency"] = 1
103
+
104
+ if "return_trace" in result:
105
+ result["return_trace"] = _coerce_bool(result.get("return_trace"))
106
+
107
+ metadata_value = result.get("metadata")
108
+ result["metadata"] = _normalize_metadata(metadata_value)
109
+
110
+ if "ops" in result:
111
+ ops_list = _ensure_list(result.get("ops"))
112
+ result["ops"] = ops_list
113
+
114
+ result["env_config"] = _ensure_dict(result.get("env_config"))
115
+ result["policy_config"] = _ensure_dict(result.get("policy_config"))
116
+
117
+ trace_format = result.get("trace_format")
118
+ if trace_format is not None:
119
+ result["trace_format"] = str(trace_format)
120
+
121
+ metadata_sql = result.get("metadata_sql")
122
+ if metadata_sql is not None and not isinstance(metadata_sql, str):
123
+ result["metadata_sql"] = str(metadata_sql)
124
+
125
+ model = result.get("model")
126
+ if model is not None:
127
+ result["model"] = str(model)
128
+
129
+ app_id = result.get("app_id")
130
+ if app_id is not None:
131
+ result["app_id"] = str(app_id)
132
+
133
+ return result
@@ -0,0 +1,12 @@
1
+ from __future__ import annotations
2
+
3
+ from .core import command, get_command
4
+ from .errors import FilterCliError
5
+ from .validation import validate_filter_options
6
+
7
+ __all__ = [
8
+ "command",
9
+ "get_command",
10
+ "FilterCliError",
11
+ "validate_filter_options",
12
+ ]
@@ -0,0 +1,388 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import json
5
+ from datetime import UTC, datetime
6
+ from pathlib import Path
7
+ from typing import Any, Sequence
8
+
9
+ import click
10
+
11
+ try: # Python 3.11+
12
+ import tomllib as _toml # type: ignore[attr-defined]
13
+ except Exception: # pragma: no cover
14
+ _toml = None # type: ignore[assignment]
15
+
16
+ from synth_ai.task.config import FilterConfig
17
+ from synth_ai.tracing_v3 import SessionTracer # type: ignore[import-untyped]
18
+
19
+ from .errors import (
20
+ FilterCliError,
21
+ FilterConfigNotFoundError,
22
+ FilterConfigParseError,
23
+ InvalidFilterConfigError,
24
+ MissingFilterTableError,
25
+ NoSessionsMatchedError,
26
+ NoTracesFoundError,
27
+ TomlUnavailableError,
28
+ )
29
+ from .validation import validate_filter_options
30
+
31
+ __all__ = ["command", "get_command", "filter_command"]
32
+
33
+
34
+ def _parse_datetime_for_trace(value: Any) -> datetime | None:
35
+ if isinstance(value, datetime):
36
+ return value if value.tzinfo else value.replace(tzinfo=UTC)
37
+ if isinstance(value, str):
38
+ value = value.replace("Z", "+00:00")
39
+ try:
40
+ dt = datetime.fromisoformat(value)
41
+ except ValueError:
42
+ try:
43
+ dt = datetime.fromtimestamp(float(value), tz=UTC)
44
+ except Exception:
45
+ return None
46
+ return dt if dt.tzinfo else dt.replace(tzinfo=UTC)
47
+ if isinstance(value, int | float):
48
+ try:
49
+ return datetime.fromtimestamp(float(value), tz=UTC)
50
+ except Exception:
51
+ return None
52
+ return None
53
+
54
+
55
+ def _score_ok(value: Any, min_val: Any, max_val: Any) -> bool:
56
+ try:
57
+ if value is None:
58
+ return min_val is None
59
+ value = float(value)
60
+ except Exception:
61
+ return False
62
+ if min_val is not None and value < float(min_val):
63
+ return False
64
+ return not (max_val is not None and value > float(max_val))
65
+
66
+
67
+ def _load_filter_config(config_path: Path) -> tuple[FilterConfig, dict[str, Any]]:
68
+ if _toml is None:
69
+ raise TomlUnavailableError(hint="Install tomli or use Python 3.11+")
70
+
71
+ if not config_path.exists():
72
+ raise FilterConfigNotFoundError(path=str(config_path))
73
+
74
+ try:
75
+ config_data = _toml.loads(config_path.read_text(encoding="utf-8"))
76
+ except Exception as exc: # pragma: no cover - validation tests cover common cases
77
+ raise FilterConfigParseError(path=str(config_path), detail=str(exc)) from exc
78
+
79
+ filter_cfg_dict = config_data.get("filter") if isinstance(config_data, dict) else None
80
+ if not isinstance(filter_cfg_dict, dict):
81
+ raise MissingFilterTableError()
82
+
83
+ try:
84
+ normalized = validate_filter_options(filter_cfg_dict)
85
+ normalized_dict = dict(normalized)
86
+ filter_cfg = FilterConfig.from_dict(normalized_dict)
87
+ except (ValueError, TypeError) as validation_error:
88
+ raise InvalidFilterConfigError(detail=str(validation_error)) from validation_error
89
+
90
+ click.echo(
91
+ f"✓ Config validated: db={filter_cfg.db}, output={filter_cfg.output}"
92
+ )
93
+ if filter_cfg.min_official_score is not None:
94
+ click.echo(
95
+ f" → Filtering for official score >= {filter_cfg.min_official_score}"
96
+ )
97
+ if filter_cfg.limit:
98
+ click.echo(f" → Limiting to {filter_cfg.limit} examples")
99
+
100
+ return filter_cfg, normalized_dict
101
+
102
+
103
+ def _extract_content(content: Any) -> Any:
104
+ if isinstance(content, dict) and "content" in content:
105
+ return content["content"]
106
+ return content
107
+
108
+
109
+ def _extract_text(content: Any) -> str:
110
+ if isinstance(content, str):
111
+ return content
112
+ if isinstance(content, dict):
113
+ payload = content.get("payload") if isinstance(content.get("payload"), dict) else None
114
+ if payload and "content" in payload:
115
+ return _extract_text(payload["content"])
116
+ for key in ("text", "content", "content_text"):
117
+ if key in content:
118
+ value = content[key]
119
+ if isinstance(value, str):
120
+ return value
121
+ try:
122
+ return json.dumps(content)
123
+ except Exception: # pragma: no cover - defensive
124
+ return str(content)
125
+ if isinstance(content, list):
126
+ parts = []
127
+ for item in content:
128
+ if isinstance(item, dict) and item.get("type") == "text":
129
+ parts.append(item.get("text", ""))
130
+ return " ".join(parts) if parts else str(content)
131
+ return str(content)
132
+
133
+
134
+ def _select_messages(message_rows: Sequence[dict[str, Any]]) -> list[dict[str, Any]]:
135
+ records: list[dict[str, Any]] = []
136
+ for index, msg_row in enumerate(message_rows):
137
+ msg_type = msg_row.get("message_type")
138
+ content_raw = msg_row.get("content")
139
+ if msg_type not in {"user", "policy_user_prompt"}:
140
+ continue
141
+
142
+ assistant_msg = None
143
+ for follow in range(index + 1, len(message_rows)):
144
+ next_type = message_rows[follow].get("message_type")
145
+ if next_type in {"assistant", "policy_system_prompt"}:
146
+ if next_type == "assistant":
147
+ assistant_msg = message_rows[follow]
148
+ break
149
+
150
+ try:
151
+ user_content = json.loads(content_raw) if isinstance(content_raw, str) else content_raw
152
+ except Exception:
153
+ user_content = content_raw
154
+
155
+ user_content = _extract_content(user_content)
156
+ user_text = _extract_text(user_content)
157
+ if not user_text:
158
+ continue
159
+
160
+ assistant_content = None
161
+ if assistant_msg is not None:
162
+ raw = assistant_msg.get("content")
163
+ try:
164
+ assistant_content = json.loads(raw) if isinstance(raw, str) else raw
165
+ except Exception:
166
+ assistant_content = raw
167
+ assistant_content = _extract_content(assistant_content)
168
+
169
+ assistant_text = _extract_text(assistant_content) if assistant_content is not None else ""
170
+ user_payload = user_content if isinstance(user_content, list) else user_text
171
+ assistant_payload = (
172
+ assistant_content
173
+ if isinstance(assistant_content, list)
174
+ else (assistant_text or "[no response recorded]")
175
+ )
176
+
177
+ records.append(
178
+ {
179
+ "messages": [
180
+ {"role": "user", "content": user_payload},
181
+ {"role": "assistant", "content": assistant_payload},
182
+ ]
183
+ }
184
+ )
185
+ return records
186
+
187
+
188
+ @click.command(
189
+ "filter",
190
+ help="Export filtered tracing sessions to SFT-ready JSONL based on a TOML config.",
191
+ )
192
+ @click.option(
193
+ "--config",
194
+ "config_path",
195
+ type=click.Path(),
196
+ required=True,
197
+ help="Path to TOML config describing the input trace DB, score thresholds, and output JSONL.",
198
+ )
199
+ def filter_command(config_path: str) -> None:
200
+ try:
201
+ filter_cfg, raw_cfg = _load_filter_config(Path(config_path))
202
+ except FilterCliError as exc:
203
+ raise click.ClickException(_format_filter_error(exc)) from exc
204
+
205
+ db_url = filter_cfg.get_db_url()
206
+ output_path = filter_cfg.get_output_path()
207
+
208
+ splits = set(filter_cfg.splits)
209
+ task_ids = set(filter_cfg.task_ids)
210
+ models = set(filter_cfg.models)
211
+ min_official = filter_cfg.min_official_score
212
+ max_official = filter_cfg.max_official_score
213
+ min_judge_scores = filter_cfg.min_judge_scores
214
+ max_judge_scores = filter_cfg.max_judge_scores
215
+ min_created = _parse_datetime_for_trace(raw_cfg.get("min_created_at"))
216
+ max_created = _parse_datetime_for_trace(raw_cfg.get("max_created_at"))
217
+ limit = filter_cfg.limit
218
+
219
+ async def _run() -> None:
220
+ tracer = SessionTracer(db_url=db_url, auto_save=False)
221
+ await tracer.initialize()
222
+ assert tracer.db is not None, "Database should be initialized"
223
+
224
+ df = await tracer.db.query_traces(
225
+ "SELECT session_id, created_at, metadata FROM session_traces ORDER BY created_at"
226
+ )
227
+ if getattr(df, "empty", True):
228
+ raise NoTracesFoundError(db_url=db_url)
229
+
230
+ sessions = df.to_dict("records")
231
+ accepted: list[dict[str, Any]] = []
232
+
233
+ for row in sessions:
234
+ metadata_raw = row.get("metadata")
235
+ if isinstance(metadata_raw, str):
236
+ try:
237
+ metadata = json.loads(metadata_raw)
238
+ except Exception:
239
+ metadata = {}
240
+ elif isinstance(metadata_raw, dict):
241
+ metadata = dict(metadata_raw)
242
+ else:
243
+ metadata = {}
244
+
245
+ created_at_raw = row.get("created_at")
246
+ created_at_dt = _parse_datetime_for_trace(created_at_raw)
247
+ session_id = row.get("session_id")
248
+
249
+ if splits and metadata.get("task_split") not in splits:
250
+ continue
251
+ if task_ids and metadata.get("task_id") not in task_ids:
252
+ continue
253
+ if models and metadata.get("model") not in models:
254
+ continue
255
+
256
+ if min_created and (created_at_dt is None or created_at_dt < min_created):
257
+ continue
258
+ if max_created and (created_at_dt is None or created_at_dt > max_created):
259
+ continue
260
+
261
+ total_reward = None
262
+ achievements_count = None
263
+ if min_official is not None or max_official is not None:
264
+ reward_rows = await tracer.db.query_traces(
265
+ "SELECT total_reward, achievements_count FROM outcome_rewards WHERE session_id = :session_id",
266
+ {"session_id": session_id},
267
+ )
268
+ reward_records = (
269
+ reward_rows.to_dict("records")
270
+ if hasattr(reward_rows, "to_dict")
271
+ else []
272
+ )
273
+ if reward_records:
274
+ total_reward = reward_records[0].get("total_reward")
275
+ achievements_count = reward_records[0].get("achievements_count")
276
+ if not _score_ok(total_reward, min_official, max_official):
277
+ continue
278
+ elif min_official is not None:
279
+ continue
280
+
281
+ judge_scores = metadata.get("judge_scores") or {}
282
+ include = True
283
+ for judge_name, threshold in (min_judge_scores or {}).items():
284
+ if not _score_ok(judge_scores.get(judge_name), threshold, None):
285
+ include = False
286
+ break
287
+ if not include:
288
+ continue
289
+ for judge_name, threshold in (max_judge_scores or {}).items():
290
+ if not _score_ok(judge_scores.get(judge_name), None, threshold):
291
+ include = False
292
+ break
293
+ if not include:
294
+ continue
295
+
296
+ messages_query = (
297
+ "\n SELECT message_type, content, timestamp \n FROM messages \n WHERE session_id = :session_id\n ORDER BY timestamp ASC, id ASC\n "
298
+ )
299
+ msg_df = await tracer.db.query_traces(messages_query, {"session_id": session_id})
300
+ message_rows = (
301
+ msg_df.to_dict("records") if hasattr(msg_df, "to_dict") else []
302
+ )
303
+
304
+ if not message_rows:
305
+ prompt = metadata.get("prompt") or ""
306
+ completion = metadata.get("completion") or ""
307
+ if prompt and completion:
308
+ accepted.append(
309
+ {
310
+ "messages": [
311
+ {"role": "user", "content": str(prompt)},
312
+ {"role": "assistant", "content": str(completion)},
313
+ ],
314
+ "metadata": {
315
+ "session_id": session_id,
316
+ "env_name": metadata.get("env_name"),
317
+ "policy_name": metadata.get("policy_name"),
318
+ "seed": metadata.get("seed"),
319
+ "total_reward": total_reward,
320
+ "achievements_count": achievements_count,
321
+ "model": metadata.get("model"),
322
+ "created_at": created_at_dt.isoformat()
323
+ if created_at_dt
324
+ else created_at_raw,
325
+ },
326
+ }
327
+ )
328
+ continue
329
+
330
+ for record in _select_messages(message_rows):
331
+ record["metadata"] = {
332
+ "session_id": session_id,
333
+ "env_name": metadata.get("env_name"),
334
+ "policy_name": metadata.get("policy_name"),
335
+ "seed": metadata.get("seed"),
336
+ "total_reward": total_reward,
337
+ "achievements_count": achievements_count,
338
+ "model": metadata.get("model"),
339
+ "created_at": created_at_dt.isoformat() if created_at_dt else created_at_raw,
340
+ }
341
+ accepted.append(record)
342
+
343
+ if not accepted:
344
+ raise NoSessionsMatchedError()
345
+
346
+ if limit is not None and limit > 0:
347
+ accepted[:] = accepted[:limit]
348
+
349
+ output_path.parent.mkdir(parents=True, exist_ok=True)
350
+ with output_path.open("w", encoding="utf-8") as handle:
351
+ for item in accepted:
352
+ handle.write(json.dumps(item, ensure_ascii=False))
353
+ handle.write("\n")
354
+
355
+ click.echo(f"Wrote {len(accepted)} examples -> {output_path}")
356
+ await tracer.db.close()
357
+
358
+ try:
359
+ asyncio.run(_run())
360
+ except FilterCliError as exc:
361
+ raise click.ClickException(_format_filter_error(exc)) from exc
362
+
363
+
364
+ def _format_filter_error(err: FilterCliError) -> str:
365
+ if isinstance(err, TomlUnavailableError):
366
+ hint = err.hint or "Install tomli or use Python 3.11+."
367
+ return f"TOML parser not available. {hint}"
368
+ if isinstance(err, FilterConfigNotFoundError):
369
+ return f"Filter config not found: {err.path}"
370
+ if isinstance(err, FilterConfigParseError):
371
+ return f"Failed to parse TOML '{err.path}': {err.detail}"
372
+ if isinstance(err, MissingFilterTableError):
373
+ return "Config must contain a [filter] table."
374
+ if isinstance(err, InvalidFilterConfigError):
375
+ return f"Invalid filter config: {err.detail}"
376
+ if isinstance(err, NoTracesFoundError):
377
+ return f"No traces found in database ({err.db_url})."
378
+ if isinstance(err, NoSessionsMatchedError):
379
+ hint = err.hint or "Adjust the filter thresholds or choose a different dataset."
380
+ return f"No sessions matched the provided filters. {hint}"
381
+ return str(err)
382
+
383
+
384
+ command = filter_command
385
+
386
+
387
+ def get_command() -> click.Command:
388
+ return command
@@ -0,0 +1,55 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+
6
+ class FilterCliError(RuntimeError):
7
+ """Base exception for filter CLI failures."""
8
+
9
+
10
+ @dataclass(slots=True)
11
+ class TomlUnavailableError(FilterCliError):
12
+ hint: str | None = None
13
+
14
+
15
+ @dataclass(slots=True)
16
+ class FilterConfigNotFoundError(FilterCliError):
17
+ path: str
18
+
19
+
20
+ @dataclass(slots=True)
21
+ class FilterConfigParseError(FilterCliError):
22
+ path: str
23
+ detail: str
24
+
25
+
26
+ @dataclass(slots=True)
27
+ class MissingFilterTableError(FilterCliError):
28
+ """Raised when the filter config lacks a [filter] table."""
29
+
30
+
31
+ @dataclass(slots=True)
32
+ class InvalidFilterConfigError(FilterCliError):
33
+ detail: str
34
+
35
+
36
+ @dataclass(slots=True)
37
+ class NoTracesFoundError(FilterCliError):
38
+ db_url: str
39
+
40
+
41
+ @dataclass(slots=True)
42
+ class NoSessionsMatchedError(FilterCliError):
43
+ hint: str | None = None
44
+
45
+
46
+ __all__ = [
47
+ "FilterCliError",
48
+ "TomlUnavailableError",
49
+ "FilterConfigNotFoundError",
50
+ "FilterConfigParseError",
51
+ "MissingFilterTableError",
52
+ "InvalidFilterConfigError",
53
+ "NoTracesFoundError",
54
+ "NoSessionsMatchedError",
55
+ ]