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
synth_ai/__init__.py CHANGED
@@ -1,12 +1,22 @@
1
- """
2
- Synth AI - Software for aiding the best and multiplying the will.
3
- """
4
-
5
1
  from __future__ import annotations
6
2
 
3
+ import importlib
7
4
  from importlib import metadata as _metadata
8
5
  from importlib.metadata import PackageNotFoundError
9
6
  from pathlib import Path
7
+ from typing import Any, cast
8
+
9
+ import synth_ai.environments as environments # expose module name for __all__
10
+ from synth_ai.environments import * # noqa
11
+ from synth_ai.judge_schemas import (
12
+ CriterionScorePayload,
13
+ JudgeOptions,
14
+ JudgeScoreRequest,
15
+ JudgeScoreResponse,
16
+ JudgeTaskApp,
17
+ JudgeTracePayload,
18
+ ReviewPayload,
19
+ )
10
20
 
11
21
  try: # Prefer the installed package metadata when available
12
22
  __version__ = _metadata.version("synth-ai")
@@ -24,35 +34,39 @@ except PackageNotFoundError: # Fallback to pyproject version for editable insta
24
34
  except Exception:
25
35
  __version__ = "0.0.0.dev0"
26
36
 
27
- # Environment exports - moved from synth-env
28
- from synth_ai.environments import * # noqa
29
- import synth_ai.environments as environments # expose module name for __all__
37
+ def _optional_import(module_path: str) -> Any | None:
38
+ try:
39
+ return importlib.import_module(module_path)
40
+ except Exception:
41
+ return None
30
42
 
31
- try:
32
- from synth_ai.lm.core.main import LM # Moved from zyk to lm for better organization
33
- except Exception: # allow minimal imports (e.g., tracing) without LM stack
34
- LM = None # type: ignore
35
- try:
36
- from synth_ai.lm.provider_support.anthropic import Anthropic, AsyncAnthropic
37
- except Exception: # optional in minimal environments
38
- Anthropic = AsyncAnthropic = None # type: ignore
39
43
 
40
- # Provider support exports - moved from synth-sdk to synth_ai/lm
41
- try:
42
- from synth_ai.lm.provider_support.openai import AsyncOpenAI, OpenAI
43
- except Exception:
44
- AsyncOpenAI = OpenAI = None # type: ignore
44
+ _lm_module = _optional_import("synth_ai.lm.core.main")
45
+ LM = cast(Any, _lm_module).LM if _lm_module and hasattr(_lm_module, "LM") else None # type: ignore[attr-defined]
45
46
 
46
- # Judge API contract schemas
47
- from synth_ai.judge_schemas import (
48
- CriterionScorePayload,
49
- JudgeOptions,
50
- JudgeScoreRequest,
51
- JudgeScoreResponse,
52
- JudgeTaskApp,
53
- JudgeTracePayload,
54
- ReviewPayload,
55
- )
47
+ _anthropic_module = _optional_import("synth_ai.lm.provider_support.anthropic")
48
+ Anthropic = (
49
+ cast(Any, _anthropic_module).Anthropic
50
+ if _anthropic_module and hasattr(_anthropic_module, "Anthropic")
51
+ else None
52
+ ) # type: ignore[attr-defined]
53
+ AsyncAnthropic = (
54
+ cast(Any, _anthropic_module).AsyncAnthropic
55
+ if _anthropic_module and hasattr(_anthropic_module, "AsyncAnthropic")
56
+ else None
57
+ ) # type: ignore[attr-defined]
58
+
59
+ _openai_module = _optional_import("synth_ai.lm.provider_support.openai")
60
+ AsyncOpenAI = (
61
+ cast(Any, _openai_module).AsyncOpenAI
62
+ if _openai_module and hasattr(_openai_module, "AsyncOpenAI")
63
+ else None
64
+ ) # type: ignore[attr-defined]
65
+ OpenAI = (
66
+ cast(Any, _openai_module).OpenAI
67
+ if _openai_module and hasattr(_openai_module, "OpenAI")
68
+ else None
69
+ ) # type: ignore[attr-defined]
56
70
 
57
71
  # Legacy tracing v1 is not required for v3 usage and can be unavailable in minimal envs.
58
72
  tracing = None # type: ignore
@@ -0,0 +1,47 @@
1
+ """
2
+ Compatibility shims for legacy `synth_ai._utils.*` imports.
3
+
4
+ The modern codebase exposes these helpers under ``synth_ai.utils``. These
5
+ modules re-export the public symbols so existing downstream code (and our own
6
+ older examples/tests) continue to work without modification.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from importlib import import_module
12
+ from types import ModuleType
13
+
14
+ _MAPPING = {
15
+ "base_url": "synth_ai.utils.base_url",
16
+ "http": "synth_ai.utils.http",
17
+ "prompts": "synth_ai.utils.prompts",
18
+ "task_app_state": "synth_ai.utils.task_app_state",
19
+ "user_config": "synth_ai.utils.user_config",
20
+ }
21
+
22
+ __all__ = sorted(_MAPPING.keys())
23
+
24
+
25
+ def __getattr__(name: str) -> ModuleType:
26
+ target = _MAPPING.get(name)
27
+ if not target:
28
+ raise AttributeError(f"module 'synth_ai._utils' has no attribute '{name}'")
29
+ module = import_module(target)
30
+ globals()[name] = module
31
+ return module
32
+
33
+
34
+ def __dir__() -> list[str]:
35
+ return sorted(set(globals()) | set(__all__))
36
+
37
+
38
+ def _export(module_name: str) -> None:
39
+ module = import_module(_MAPPING[module_name])
40
+ globals().setdefault(module_name, module)
41
+ if hasattr(module, "__all__"):
42
+ for attr in module.__all__: # type: ignore[attr-defined]
43
+ globals().setdefault(attr, getattr(module, attr))
44
+
45
+
46
+ for _name in __all__:
47
+ _export(_name)
@@ -0,0 +1,10 @@
1
+ from __future__ import annotations
2
+
3
+ from synth_ai.utils.base_url import * # noqa: F401,F403
4
+
5
+ try:
6
+ from synth_ai.utils.base_url import __all__ as __wrapped_all__ # type: ignore[attr-defined]
7
+ except ImportError: # pragma: no cover - defensive
8
+ __wrapped_all__ = []
9
+
10
+ __all__ = list(__wrapped_all__)
@@ -0,0 +1,10 @@
1
+ from __future__ import annotations
2
+
3
+ from synth_ai.utils.http import * # noqa: F401,F403
4
+
5
+ try:
6
+ from synth_ai.utils.http import __all__ as __wrapped_all__ # type: ignore[attr-defined]
7
+ except ImportError: # pragma: no cover - defensive
8
+ __wrapped_all__ = []
9
+
10
+ __all__ = list(__wrapped_all__)
@@ -0,0 +1,10 @@
1
+ from __future__ import annotations
2
+
3
+ from synth_ai.utils.prompts import * # noqa: F401,F403
4
+
5
+ try:
6
+ from synth_ai.utils.prompts import __all__ as __wrapped_all__ # type: ignore[attr-defined]
7
+ except ImportError: # pragma: no cover - defensive
8
+ __wrapped_all__ = []
9
+
10
+ __all__ = list(__wrapped_all__)
@@ -0,0 +1,12 @@
1
+ from __future__ import annotations
2
+
3
+ from synth_ai.utils.task_app_state import * # noqa: F401,F403
4
+
5
+ try:
6
+ from synth_ai.utils.task_app_state import (
7
+ __all__ as __wrapped_all__, # type: ignore[attr-defined]
8
+ )
9
+ except ImportError: # pragma: no cover - defensive
10
+ __wrapped_all__ = []
11
+
12
+ __all__ = list(__wrapped_all__)
@@ -0,0 +1,10 @@
1
+ from __future__ import annotations
2
+
3
+ from synth_ai.utils.user_config import * # noqa: F401,F403
4
+
5
+ try:
6
+ from synth_ai.utils.user_config import __all__ as __wrapped_all__ # type: ignore[attr-defined]
7
+ except ImportError: # pragma: no cover - defensive
8
+ __wrapped_all__ = []
9
+
10
+ __all__ = list(__wrapped_all__)
@@ -20,35 +20,106 @@ QWEN3_MODELS: list[str] = [
20
20
  "Qwen/Qwen3-14B",
21
21
  "Qwen/Qwen3-30B-A3B",
22
22
  "Qwen/Qwen3-32B",
23
- # Include 4B-2507 and Thinking variants used in RL
23
+ # 2507 baseline models
24
+ "Qwen/Qwen3-4B-2507",
25
+ # Instruct variants (no <think> tags)
26
+ "Qwen/Qwen3-4B-Instruct-2507",
27
+ "Qwen/Qwen3-4B-Instruct-2507-FP8",
28
+ "Qwen/Qwen3-30B-A3B-Instruct-2507",
29
+ "Qwen/Qwen3-30B-A3B-Instruct-2507-FP8",
30
+ "Qwen/Qwen3-235B-A22B-Instruct-2507",
31
+ "Qwen/Qwen3-235B-A22B-Instruct-2507-FP8",
32
+ # Thinking variants (with <think> tags)
24
33
  "Qwen/Qwen3-4B-Thinking-2507",
34
+ "Qwen/Qwen3-4B-Thinking-2507-FP8",
25
35
  "Qwen/Qwen3-30B-A3B-Thinking-2507",
36
+ "Qwen/Qwen3-30B-A3B-Thinking-2507-FP8",
26
37
  "Qwen/Qwen3-235B-A22B-Thinking-2507",
38
+ "Qwen/Qwen3-235B-A22B-Thinking-2507-FP8",
27
39
  ]
28
40
 
29
41
  # Qwen3 Coder family (backend-supported); text-only, SFT/inference
30
42
  QWEN3_CODER_MODELS: list[str] = [
31
- # Instruct variants used for coding tasks
43
+ # Instruct variants used for coding tasks (no <think> tags)
32
44
  "Qwen/Qwen3-Coder-30B-A3B-Instruct",
45
+ "Qwen/Qwen3-Coder-30B-A3B-Instruct-FP8",
33
46
  "Qwen/Qwen3-Coder-480B-A35B-Instruct",
47
+ "Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8",
48
+ ]
49
+
50
+ # Qwen3-VL family (vision-language models); multimodal, SFT/inference
51
+ QWEN3_VL_MODELS: list[str] = [
52
+ # Vision-Language Models (Qwen3-VL)
53
+ "Qwen/Qwen3-VL-2B-Instruct",
54
+ "Qwen/Qwen3-VL-2B-Thinking",
55
+ "Qwen/Qwen3-VL-4B-Instruct",
56
+ "Qwen/Qwen3-VL-4B-Thinking",
57
+ "Qwen/Qwen3-VL-8B-Instruct",
58
+ "Qwen/Qwen3-VL-8B-Thinking",
59
+ "Qwen/Qwen3-VL-30B-A3B-Instruct",
60
+ "Qwen/Qwen3-VL-30B-A3B-Thinking",
61
+ "Qwen/Qwen3-VL-32B-Instruct",
62
+ "Qwen/Qwen3-VL-32B-Thinking",
63
+ "Qwen/Qwen3-VL-235B-A22B-Instruct",
64
+ "Qwen/Qwen3-VL-235B-A22B-Thinking",
34
65
  ]
35
66
 
36
67
  # Training support sets
37
68
  RL_SUPPORTED_MODELS: frozenset[str] = frozenset(
38
69
  {
70
+ # Legacy base models
39
71
  "Qwen/Qwen3-0.6B",
40
72
  "Qwen/Qwen3-1.7B",
41
73
  "Qwen/Qwen3-4B",
42
- "Qwen/Qwen3-4B-Thinking-2507",
43
74
  "Qwen/Qwen3-8B",
44
75
  "Qwen/Qwen3-14B",
45
76
  "Qwen/Qwen3-30B-A3B",
77
+ # 2507 models - base
78
+ "Qwen/Qwen3-4B-2507",
79
+ # 2507 models - instruct (no <think> tags)
80
+ "Qwen/Qwen3-4B-Instruct-2507",
81
+ "Qwen/Qwen3-4B-Instruct-2507-FP8",
82
+ "Qwen/Qwen3-30B-A3B-Instruct-2507",
83
+ "Qwen/Qwen3-30B-A3B-Instruct-2507-FP8",
84
+ # 2507 models - thinking (with <think> tags)
85
+ "Qwen/Qwen3-4B-Thinking-2507",
86
+ "Qwen/Qwen3-4B-Thinking-2507-FP8",
46
87
  "Qwen/Qwen3-30B-A3B-Thinking-2507",
88
+ "Qwen/Qwen3-30B-A3B-Thinking-2507-FP8",
89
+ # Coder instruct models
90
+ "Qwen/Qwen3-Coder-30B-A3B-Instruct",
91
+ "Qwen/Qwen3-Coder-30B-A3B-Instruct-FP8",
92
+ # Vision-Language models (Qwen3-VL)
93
+ "Qwen/Qwen3-VL-2B-Instruct",
94
+ "Qwen/Qwen3-VL-2B-Thinking",
95
+ "Qwen/Qwen3-VL-4B-Instruct",
96
+ "Qwen/Qwen3-VL-4B-Thinking",
97
+ "Qwen/Qwen3-VL-8B-Instruct",
98
+ "Qwen/Qwen3-VL-8B-Thinking",
47
99
  }
48
100
  )
49
101
 
50
- # SFT allowlist includes core Qwen3 plus Coder family
51
- SFT_SUPPORTED_MODELS: frozenset[str] = frozenset([*QWEN3_MODELS, *QWEN3_CODER_MODELS])
102
+ # SFT allowlist includes core Qwen3 plus Coder and VL families
103
+ SFT_SUPPORTED_MODELS: frozenset[str] = frozenset([*QWEN3_MODELS, *QWEN3_CODER_MODELS, *QWEN3_VL_MODELS])
104
+
105
+ # Models that support <think> reasoning tags
106
+ THINKING_MODELS: frozenset[str] = frozenset(
107
+ {
108
+ "Qwen/Qwen3-4B-Thinking-2507",
109
+ "Qwen/Qwen3-4B-Thinking-2507-FP8",
110
+ "Qwen/Qwen3-30B-A3B-Thinking-2507",
111
+ "Qwen/Qwen3-30B-A3B-Thinking-2507-FP8",
112
+ "Qwen/Qwen3-235B-A22B-Thinking-2507",
113
+ "Qwen/Qwen3-235B-A22B-Thinking-2507-FP8",
114
+ # Vision-Language Thinking models
115
+ "Qwen/Qwen3-VL-2B-Thinking",
116
+ "Qwen/Qwen3-VL-4B-Thinking",
117
+ "Qwen/Qwen3-VL-8B-Thinking",
118
+ "Qwen/Qwen3-VL-30B-A3B-Thinking",
119
+ "Qwen/Qwen3-VL-32B-Thinking",
120
+ "Qwen/Qwen3-VL-235B-A22B-Thinking",
121
+ }
122
+ )
52
123
 
53
124
  # ------------------------------------------------------------------------------
54
125
  # Lifecycle classification (core vs experimental)
@@ -58,11 +129,17 @@ SFT_SUPPORTED_MODELS: frozenset[str] = frozenset([*QWEN3_MODELS, *QWEN3_CODER_MO
58
129
  _EXPERIMENTAL_DEFAULTS: frozenset[str] = frozenset(
59
130
  {
60
131
  # Larger (>= 64B) or bleeding-edge variants are experimental by default.
132
+ "Qwen/Qwen3-235B-A22B-Instruct-2507",
133
+ "Qwen/Qwen3-235B-A22B-Instruct-2507-FP8",
61
134
  "Qwen/Qwen3-235B-A22B-Thinking-2507",
135
+ "Qwen/Qwen3-235B-A22B-Thinking-2507-FP8",
62
136
  "Qwen/Qwen3-Coder-480B-A35B-Instruct",
137
+ "Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8",
63
138
  # Thinking variants can fluctuate more rapidly.
64
139
  "Qwen/Qwen3-30B-A3B-Thinking-2507",
140
+ "Qwen/Qwen3-30B-A3B-Thinking-2507-FP8",
65
141
  "Qwen/Qwen3-4B-Thinking-2507",
142
+ "Qwen/Qwen3-4B-Thinking-2507-FP8",
66
143
  }
67
144
  )
68
145
 
@@ -77,8 +154,8 @@ def _parse_experimental_env() -> frozenset[str]:
77
154
  # Final experimental set (defaults ∪ optional env override)
78
155
  EXPERIMENTAL_MODELS: frozenset[str] = frozenset(_EXPERIMENTAL_DEFAULTS | _parse_experimental_env())
79
156
 
80
- # Build catalog entries for both core and coder families under unified "Qwen3"
81
- _ALL_QWEN3_IDS: list[str] = [*QWEN3_MODELS, *QWEN3_CODER_MODELS]
157
+ # Build catalog entries for core, coder, and VL families under unified "Qwen3"
158
+ _ALL_QWEN3_IDS: list[str] = [*QWEN3_MODELS, *QWEN3_CODER_MODELS, *QWEN3_VL_MODELS]
82
159
 
83
160
  CORE_MODELS: frozenset[str] = frozenset(m for m in _ALL_QWEN3_IDS if m not in EXPERIMENTAL_MODELS)
84
161
 
@@ -120,6 +197,7 @@ class SupportedModel:
120
197
  modalities: tuple[str, ...] = ()
121
198
  training_modes: tuple[str, ...] = ()
122
199
  lifecycle: str = "core" # "core" | "experimental"
200
+ supports_thinking: bool = False # Whether model supports <think> reasoning tags
123
201
 
124
202
  def as_dict(self) -> dict[str, object]:
125
203
  data: dict[str, object] = {
@@ -127,6 +205,7 @@ class SupportedModel:
127
205
  "family": self.family,
128
206
  "provider": self.provider,
129
207
  "lifecycle": self.lifecycle,
208
+ "supports_thinking": self.supports_thinking,
130
209
  }
131
210
  if self.modalities:
132
211
  data["modalities"] = list(self.modalities)
@@ -150,6 +229,7 @@ SUPPORTED_MODELS: tuple[SupportedModel, ...] = tuple(
150
229
  )
151
230
  ),
152
231
  lifecycle=("experimental" if model in EXPERIMENTAL_MODELS else "core"),
232
+ supports_thinking=(model in THINKING_MODELS),
153
233
  )
154
234
  for model in _ALL_QWEN3_IDS
155
235
  )
@@ -347,11 +427,66 @@ def training_modes_for_model(model_id: str) -> tuple[str, ...]:
347
427
  return model.training_modes
348
428
 
349
429
 
430
+ def supports_thinking(model_id: str) -> bool:
431
+ """Return True if the model supports <think> reasoning tags.
432
+
433
+ Thinking models use structured <think>...</think> tags for reasoning.
434
+ Instruct models do not have these tags and should not use thinking-specific logic.
435
+
436
+ Args:
437
+ model_id: Model identifier (can include prefixes like 'rl:', 'fft:', etc.)
438
+
439
+ Returns:
440
+ True if the model supports thinking tags, False otherwise.
441
+ Returns False for unsupported models.
442
+
443
+ Example:
444
+ >>> supports_thinking("Qwen/Qwen3-4B-Thinking-2507")
445
+ True
446
+ >>> supports_thinking("Qwen/Qwen3-4B-Instruct-2507")
447
+ False
448
+ >>> supports_thinking("rl:Qwen/Qwen3-4B-Thinking-2507")
449
+ True
450
+ """
451
+ try:
452
+ canonical = ensure_supported_model(model_id, allow_finetuned_prefixes=True)
453
+ except UnsupportedModelError:
454
+ return False
455
+ model = _MODEL_BY_ID.get(canonical)
456
+ if not model:
457
+ return False
458
+ return model.supports_thinking
459
+
460
+
461
+ def get_model_metadata(model_id: str) -> SupportedModel | None:
462
+ """Return the full metadata for a supported model, or None if not supported.
463
+
464
+ Args:
465
+ model_id: Model identifier (can include prefixes like 'rl:', 'fft:', etc.)
466
+
467
+ Returns:
468
+ SupportedModel instance with full metadata, or None if model is not supported.
469
+
470
+ Example:
471
+ >>> meta = get_model_metadata("Qwen/Qwen3-4B-Instruct-2507")
472
+ >>> meta.supports_thinking
473
+ False
474
+ >>> meta.training_modes
475
+ ('rl', 'sft')
476
+ """
477
+ try:
478
+ canonical = ensure_supported_model(model_id, allow_finetuned_prefixes=True)
479
+ except UnsupportedModelError:
480
+ return None
481
+ return _MODEL_BY_ID.get(canonical)
482
+
483
+
350
484
  __all__ = [
351
485
  "QWEN3_MODELS",
352
486
  "QWEN3_CODER_MODELS",
353
487
  "RL_SUPPORTED_MODELS",
354
488
  "SFT_SUPPORTED_MODELS",
489
+ "THINKING_MODELS",
355
490
  "EXPERIMENTAL_MODELS",
356
491
  "CORE_MODELS",
357
492
  "ExperimentalWarning",
@@ -373,5 +508,7 @@ __all__ = [
373
508
  "core_model_ids",
374
509
  "format_supported_models",
375
510
  "training_modes_for_model",
511
+ "supports_thinking",
512
+ "get_model_metadata",
376
513
  ]
377
514
 
@@ -1,5 +1,17 @@
1
1
  from __future__ import annotations
2
2
 
3
- from .cli import register, train_command
3
+ from typing import Any
4
4
 
5
5
  __all__ = ["register", "train_command"]
6
+
7
+
8
+ def register(cli: Any) -> None:
9
+ from synth_ai.cli.train import register as _register # local import avoids circular dependency
10
+
11
+ _register(cli)
12
+
13
+
14
+ def train_command(*args: Any, **kwargs: Any) -> Any:
15
+ from synth_ai.cli.train import train_command as _train_command # local import avoids cycle
16
+
17
+ return _train_command(*args, **kwargs)
@@ -74,8 +74,14 @@ def build_rl_payload(
74
74
  idempotency: str | None,
75
75
  allow_experimental: bool | None = None,
76
76
  ) -> RLBuildResult:
77
+ # Load and validate config with SDK-level checks
78
+ from synth_ai.api.train.utils import load_toml
79
+ from synth_ai.cli.commands.train.validation import validate_rl_config
80
+
77
81
  try:
78
- rl_cfg = RLConfig.from_path(config_path)
82
+ raw_config = load_toml(config_path)
83
+ validated_config = validate_rl_config(raw_config) # Adds defaults & validates
84
+ rl_cfg = RLConfig.from_mapping(validated_config)
79
85
  except ValidationError as exc:
80
86
  raise click.ClickException(_format_validation_error(config_path, exc)) from exc
81
87
 
@@ -110,8 +116,8 @@ def build_rl_payload(
110
116
  "Task app URL required (provide --task-url or set services.task_url in TOML)"
111
117
  )
112
118
 
113
- model_source = (model_cfg.source or "").strip()
114
- model_base = (model_cfg.base or "").strip()
119
+ model_source = (model_cfg.source or "").strip() if model_cfg else ""
120
+ model_base = (model_cfg.base or "").strip() if model_cfg else ""
115
121
  override_model = (overrides.get("model") or "").strip()
116
122
  if override_model:
117
123
  model_source = override_model