synth-ai 0.2.9.dev4__py3-none-any.whl → 0.2.9.dev6__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 (353) hide show
  1. examples/__init__.py +16 -0
  2. examples/crafter_debug_render.py +23 -17
  3. examples/qwen_coder/README.md +102 -0
  4. examples/qwen_coder/_shared.py +113 -0
  5. examples/qwen_coder/configs/coder_lora_30b.toml +61 -0
  6. examples/qwen_coder/configs/coder_lora_4b.toml +57 -0
  7. examples/qwen_coder/configs/coder_lora_small.toml +58 -0
  8. examples/qwen_coder/generate_dataset.py +98 -0
  9. examples/qwen_coder/infer_ft_smoke.py +64 -0
  10. examples/qwen_coder/infer_prod_proxy.py +73 -0
  11. examples/qwen_coder/infer_via_synth.py +87 -0
  12. examples/qwen_coder/scripts/infer_coder.sh +18 -0
  13. examples/qwen_coder/scripts/train_coder_30b.sh +21 -0
  14. examples/qwen_coder/sft_full_17b.py +103 -0
  15. examples/qwen_coder/sft_lora_30b.py +110 -0
  16. examples/qwen_coder/subset_jsonl.py +38 -0
  17. examples/qwen_coder/validate_jsonl.py +59 -0
  18. examples/rl/configs/eval_base_qwen.toml +1 -1
  19. examples/rl/configs/rl_from_base_qwen17.toml +1 -1
  20. examples/rl/download_dataset.py +26 -10
  21. examples/rl/run_eval.py +53 -52
  22. examples/rl/run_rl_and_save.py +29 -12
  23. examples/rl/task_app/math_single_step.py +180 -41
  24. examples/rl/task_app/math_task_app.py +14 -6
  25. examples/sft/README.md +139 -0
  26. examples/sft/configs/crafter_fft_qwen0p6b.toml +44 -0
  27. examples/sft/configs/crafter_lora_qwen0p6b.toml +45 -0
  28. examples/sft/evaluate.py +117 -0
  29. examples/sft/export_dataset.py +117 -0
  30. examples/sft/generate_traces.py +162 -0
  31. examples/swe/__init__.py +12 -0
  32. examples/swe/task_app/README.md +105 -0
  33. examples/swe/task_app/__init__.py +2 -0
  34. examples/swe/task_app/grpo_swe_mini.py +571 -0
  35. examples/swe/task_app/grpo_swe_mini_task_app.py +136 -0
  36. examples/swe/task_app/hosted/README.md +173 -0
  37. examples/swe/task_app/hosted/__init__.py +5 -0
  38. examples/swe/task_app/hosted/branching.py +143 -0
  39. examples/swe/task_app/hosted/environment_routes.py +1289 -0
  40. examples/swe/task_app/hosted/envs/__init__.py +1 -0
  41. examples/swe/task_app/hosted/envs/crafter/__init__.py +6 -0
  42. examples/swe/task_app/hosted/envs/crafter/app.py +1 -0
  43. examples/swe/task_app/hosted/envs/crafter/environment.py +522 -0
  44. examples/swe/task_app/hosted/envs/crafter/policy.py +478 -0
  45. examples/swe/task_app/hosted/envs/crafter/react_agent.py +108 -0
  46. examples/swe/task_app/hosted/envs/crafter/shared.py +305 -0
  47. examples/swe/task_app/hosted/envs/crafter/tools.py +47 -0
  48. examples/swe/task_app/hosted/envs/mini_swe/__init__.py +8 -0
  49. examples/swe/task_app/hosted/envs/mini_swe/environment.py +1164 -0
  50. examples/swe/task_app/hosted/envs/mini_swe/policy.py +355 -0
  51. examples/swe/task_app/hosted/envs/mini_swe/shared.py +83 -0
  52. examples/swe/task_app/hosted/envs/mini_swe/tools.py +96 -0
  53. examples/swe/task_app/hosted/hosted_app.py +204 -0
  54. examples/swe/task_app/hosted/inference/__init__.py +5 -0
  55. examples/swe/task_app/hosted/inference/openai_client.py +618 -0
  56. examples/swe/task_app/hosted/main.py +100 -0
  57. examples/swe/task_app/hosted/policy_routes.py +1079 -0
  58. examples/swe/task_app/hosted/registry.py +195 -0
  59. examples/swe/task_app/hosted/rollout.py +1869 -0
  60. examples/swe/task_app/hosted/storage/__init__.py +5 -0
  61. examples/swe/task_app/hosted/storage/volume.py +211 -0
  62. examples/swe/task_app/hosted/test_agents.py +161 -0
  63. examples/swe/task_app/hosted/test_service.py +137 -0
  64. examples/swe/task_app/hosted/utils.py +62 -0
  65. examples/vlm/README.md +68 -0
  66. examples/vlm/configs/crafter_vlm_gpt4o.toml +44 -0
  67. examples/vlm/crafter_image_only_agent.py +207 -0
  68. examples/vlm/crafter_openai_vlm_agent.py +277 -0
  69. examples/vlm/filter_image_rows.py +63 -0
  70. examples/vlm/run_crafter_vlm_benchmark.py +316 -0
  71. examples/warming_up_to_rl/analyze_trace_db.py +12 -10
  72. examples/warming_up_to_rl/configs/rl_from_base_qwen4b.toml +11 -1
  73. examples/warming_up_to_rl/export_trace_sft.py +218 -36
  74. examples/warming_up_to_rl/groq_test.py +15 -8
  75. examples/warming_up_to_rl/manage_secrets.py +29 -25
  76. examples/warming_up_to_rl/readme.md +9 -2
  77. examples/warming_up_to_rl/run_eval.py +137 -61
  78. examples/warming_up_to_rl/run_fft_and_save.py +131 -60
  79. examples/warming_up_to_rl/run_local_rollout.py +88 -39
  80. examples/warming_up_to_rl/run_local_rollout_modal.py +114 -28
  81. examples/warming_up_to_rl/run_local_rollout_parallel.py +81 -20
  82. examples/warming_up_to_rl/run_local_rollout_traced.py +126 -23
  83. examples/warming_up_to_rl/run_rl_and_save.py +35 -12
  84. examples/warming_up_to_rl/run_rollout_remote.py +44 -19
  85. examples/warming_up_to_rl/task_app/README.md +6 -2
  86. examples/warming_up_to_rl/task_app/grpo_crafter.py +319 -57
  87. examples/warming_up_to_rl/task_app/grpo_crafter_task_app.py +11 -30
  88. examples/warming_up_to_rl/task_app/synth_envs_hosted/__init__.py +1 -1
  89. examples/warming_up_to_rl/task_app/synth_envs_hosted/branching.py +9 -11
  90. examples/warming_up_to_rl/task_app/synth_envs_hosted/environment_routes.py +137 -182
  91. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/__init__.py +1 -1
  92. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/__init__.py +1 -1
  93. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/app.py +1 -1
  94. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/environment.py +150 -57
  95. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/policy.py +105 -69
  96. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/react_agent.py +19 -7
  97. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/shared.py +45 -42
  98. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/tools.py +1 -1
  99. examples/warming_up_to_rl/task_app/synth_envs_hosted/hosted_app.py +47 -45
  100. examples/warming_up_to_rl/task_app/synth_envs_hosted/inference/__init__.py +1 -1
  101. examples/warming_up_to_rl/task_app/synth_envs_hosted/inference/openai_client.py +198 -92
  102. examples/warming_up_to_rl/task_app/synth_envs_hosted/main.py +0 -2
  103. examples/warming_up_to_rl/task_app/synth_envs_hosted/policy_routes.py +361 -263
  104. examples/warming_up_to_rl/task_app/synth_envs_hosted/registry.py +21 -23
  105. examples/warming_up_to_rl/task_app/synth_envs_hosted/rollout.py +394 -274
  106. examples/warming_up_to_rl/task_app/synth_envs_hosted/storage/__init__.py +1 -1
  107. examples/warming_up_to_rl/task_app/synth_envs_hosted/storage/volume.py +56 -62
  108. examples/warming_up_to_rl/task_app/synth_envs_hosted/test_agents.py +1 -0
  109. examples/warming_up_to_rl/task_app/synth_envs_hosted/test_service.py +6 -15
  110. examples/warming_up_to_rl/task_app/synth_envs_hosted/utils.py +4 -3
  111. synth/__init__.py +14 -0
  112. synth_ai/__init__.py +20 -4
  113. synth_ai/api/models/supported.py +376 -0
  114. synth_ai/api/train/builders.py +157 -26
  115. synth_ai/api/train/cli.py +213 -57
  116. synth_ai/api/train/config_finder.py +65 -5
  117. synth_ai/api/train/env_resolver.py +33 -15
  118. synth_ai/api/train/pollers.py +13 -4
  119. synth_ai/api/train/supported_algos.py +139 -0
  120. synth_ai/api/train/task_app.py +5 -3
  121. synth_ai/api/train/utils.py +33 -48
  122. synth_ai/cli/__init__.py +19 -4
  123. synth_ai/cli/_modal_wrapper.py +28 -0
  124. synth_ai/cli/_typer_patch.py +49 -0
  125. synth_ai/cli/balance.py +2 -3
  126. synth_ai/cli/calc.py +1 -1
  127. synth_ai/cli/demo.py +21 -6
  128. synth_ai/cli/recent.py +2 -2
  129. synth_ai/cli/rl_demo.py +77 -17
  130. synth_ai/cli/root.py +116 -39
  131. synth_ai/cli/status.py +2 -2
  132. synth_ai/cli/task_apps.py +1709 -243
  133. synth_ai/cli/traces.py +7 -4
  134. synth_ai/cli/turso.py +73 -0
  135. synth_ai/cli/watch.py +12 -18
  136. synth_ai/core/experiment.py +0 -2
  137. synth_ai/demo_registry.py +68 -31
  138. synth_ai/demos/core/cli.py +516 -194
  139. synth_ai/demos/demo_task_apps/__init__.py +3 -3
  140. synth_ai/demos/demo_task_apps/core.py +64 -28
  141. synth_ai/demos/demo_task_apps/crafter/configs/crafter_fft_4b.toml +2 -3
  142. synth_ai/demos/demo_task_apps/crafter/grpo_crafter_task_app.py +37 -30
  143. synth_ai/demos/demo_task_apps/math/_common.py +1 -2
  144. synth_ai/demos/demo_task_apps/math/app.py +2 -1
  145. synth_ai/demos/demo_task_apps/math/deploy_modal.py +3 -6
  146. synth_ai/demos/demo_task_apps/math/modal_task_app.py +183 -82
  147. synth_ai/demos/demo_task_apps/math/task_app_entry.py +0 -2
  148. synth_ai/environments/examples/bandit/engine.py +12 -4
  149. synth_ai/environments/examples/bandit/taskset.py +4 -4
  150. synth_ai/environments/examples/crafter_classic/environment.py +76 -1
  151. synth_ai/environments/reproducibility/tree.py +5 -6
  152. synth_ai/environments/service/app.py +11 -12
  153. synth_ai/environments/service/core_routes.py +10 -9
  154. synth_ai/environments/stateful/engine.py +1 -1
  155. synth_ai/environments/tasks/core.py +1 -0
  156. synth_ai/environments/tasks/filters.py +5 -6
  157. synth_ai/environments/tasks/utils.py +4 -5
  158. synth_ai/evals/base.py +0 -2
  159. synth_ai/handshake.py +11 -9
  160. synth_ai/http.py +1 -1
  161. synth_ai/http_client.py +43 -11
  162. synth_ai/inference/__init__.py +0 -2
  163. synth_ai/inference/client.py +20 -6
  164. synth_ai/jobs/client.py +103 -78
  165. synth_ai/learning/__init__.py +41 -6
  166. synth_ai/learning/algorithms.py +14 -0
  167. synth_ai/learning/client.py +121 -29
  168. synth_ai/learning/config.py +2 -40
  169. synth_ai/learning/constants.py +0 -2
  170. synth_ai/learning/ft_client.py +4 -56
  171. synth_ai/learning/health.py +13 -7
  172. synth_ai/learning/jobs.py +43 -47
  173. synth_ai/{rl → learning/rl}/__init__.py +14 -5
  174. synth_ai/learning/rl/client.py +267 -0
  175. synth_ai/learning/rl/config.py +31 -0
  176. synth_ai/{rl → learning/rl}/contracts.py +5 -10
  177. synth_ai/{rl → learning/rl}/env_keys.py +45 -16
  178. synth_ai/learning/rl/secrets.py +13 -0
  179. synth_ai/learning/rl_client.py +2 -253
  180. synth_ai/learning/sft/__init__.py +29 -0
  181. synth_ai/learning/sft/client.py +68 -0
  182. synth_ai/learning/sft/config.py +270 -0
  183. synth_ai/learning/sft/data.py +295 -0
  184. synth_ai/learning/sse.py +25 -26
  185. synth_ai/learning/validators.py +25 -24
  186. synth_ai/lm/__init__.py +21 -47
  187. synth_ai/task/__init__.py +26 -27
  188. synth_ai/task/apps/__init__.py +18 -19
  189. synth_ai/task/auth.py +35 -23
  190. synth_ai/task/client.py +15 -13
  191. synth_ai/task/contracts.py +37 -35
  192. synth_ai/task/datasets.py +9 -6
  193. synth_ai/task/errors.py +11 -10
  194. synth_ai/task/health.py +17 -11
  195. synth_ai/task/json.py +58 -24
  196. synth_ai/task/proxy.py +15 -14
  197. synth_ai/task/rubrics.py +22 -15
  198. synth_ai/task/server.py +43 -17
  199. synth_ai/task/tracing_utils.py +12 -7
  200. synth_ai/task/validators.py +0 -1
  201. synth_ai/task/vendors.py +5 -7
  202. synth_ai/tracing_v3/__init__.py +2 -0
  203. synth_ai/tracing_v3/abstractions.py +21 -4
  204. synth_ai/tracing_v3/db_config.py +26 -1
  205. synth_ai/tracing_v3/decorators.py +18 -15
  206. synth_ai/tracing_v3/examples/basic_usage.py +3 -2
  207. synth_ai/tracing_v3/hooks.py +6 -4
  208. synth_ai/tracing_v3/llm_call_record_helpers.py +6 -6
  209. synth_ai/tracing_v3/replica_sync.py +1 -0
  210. synth_ai/tracing_v3/session_tracer.py +63 -16
  211. synth_ai/tracing_v3/storage/base.py +89 -1
  212. synth_ai/tracing_v3/storage/config.py +21 -8
  213. synth_ai/tracing_v3/storage/factory.py +10 -8
  214. synth_ai/tracing_v3/storage/utils.py +4 -2
  215. synth_ai/tracing_v3/turso/daemon.py +7 -2
  216. synth_ai/tracing_v3/turso/models.py +5 -2
  217. synth_ai/tracing_v3/turso/native_manager.py +1173 -0
  218. synth_ai/tracing_v3/utils.py +4 -3
  219. synth_ai/v0/api/__init__.py +8 -0
  220. synth_ai/v0/api/models/__init__.py +8 -0
  221. synth_ai/v0/api/models/supported.py +8 -0
  222. synth_ai/v0/config/__init__.py +15 -0
  223. synth_ai/v0/config/base_url.py +12 -0
  224. synth_ai/v0/lm/__init__.py +51 -0
  225. synth_ai/{lm → v0/lm}/caching/ephemeral.py +3 -5
  226. synth_ai/{lm → v0/lm}/caching/handler.py +4 -4
  227. synth_ai/{lm → v0/lm}/caching/initialize.py +1 -1
  228. synth_ai/{lm → v0/lm}/caching/persistent.py +1 -1
  229. synth_ai/{lm → v0/lm}/config.py +6 -1
  230. synth_ai/{lm → v0/lm}/core/all.py +9 -9
  231. synth_ai/{lm → v0/lm}/core/exceptions.py +0 -2
  232. synth_ai/{lm → v0/lm}/core/main.py +19 -7
  233. synth_ai/{lm → v0/lm}/core/main_v3.py +10 -10
  234. synth_ai/{lm → v0/lm}/core/synth_models.py +2 -15
  235. synth_ai/{lm → v0/lm}/core/vendor_clients.py +6 -4
  236. synth_ai/{lm → v0/lm}/overrides.py +4 -4
  237. synth_ai/{lm → v0/lm}/provider_support/anthropic.py +4 -4
  238. synth_ai/{lm → v0/lm}/provider_support/openai.py +5 -5
  239. synth_ai/{lm → v0/lm}/structured_outputs/handler.py +5 -5
  240. synth_ai/{lm → v0/lm}/structured_outputs/rehabilitate.py +1 -1
  241. synth_ai/{lm → v0/lm}/vendors/core/anthropic_api.py +16 -16
  242. synth_ai/{lm → v0/lm}/vendors/core/gemini_api.py +5 -5
  243. synth_ai/{lm → v0/lm}/vendors/core/mistral_api.py +5 -5
  244. synth_ai/{lm → v0/lm}/vendors/core/openai_api.py +12 -10
  245. synth_ai/{lm → v0/lm}/vendors/openai_standard.py +11 -9
  246. synth_ai/{lm → v0/lm}/vendors/openai_standard_responses.py +8 -5
  247. synth_ai/{lm → v0/lm}/vendors/supported/custom_endpoint.py +4 -6
  248. synth_ai/{lm → v0/lm}/vendors/supported/deepseek.py +2 -2
  249. synth_ai/{lm → v0/lm}/vendors/supported/grok.py +2 -2
  250. synth_ai/{lm → v0/lm}/vendors/supported/groq.py +1 -1
  251. synth_ai/{lm → v0/lm}/vendors/supported/ollama.py +1 -1
  252. synth_ai/{lm → v0/lm}/vendors/supported/openrouter.py +3 -3
  253. synth_ai/{lm → v0/lm}/vendors/supported/together.py +1 -1
  254. synth_ai/{lm → v0/lm}/vendors/synth_client.py +38 -11
  255. synth_ai/v0/tracing/upload.py +32 -135
  256. synth_ai/v0/tracing_v3/__init__.py +10 -0
  257. synth_ai/v0/tracing_v3/abstractions.py +3 -0
  258. synth_ai/v0/tracing_v3/decorators.py +3 -0
  259. synth_ai/v0/tracing_v3/llm_call_record_helpers.py +3 -0
  260. synth_ai/v0/tracing_v3/session_tracer.py +3 -0
  261. synth_ai-0.2.9.dev6.dist-info/METADATA +191 -0
  262. {synth_ai-0.2.9.dev4.dist-info → synth_ai-0.2.9.dev6.dist-info}/RECORD +291 -264
  263. {synth_ai-0.2.9.dev4.dist-info → synth_ai-0.2.9.dev6.dist-info}/top_level.txt +1 -0
  264. examples/common_old/backend.py +0 -21
  265. examples/evals_old/README.md +0 -98
  266. examples/evals_old/__init__.py +0 -6
  267. examples/evals_old/compare_models.py +0 -1037
  268. examples/evals_old/example_log.md +0 -145
  269. examples/evals_old/run_demo.sh +0 -126
  270. examples/evals_old/trace_analysis.py +0 -270
  271. examples/finetuning_old/_backup_synth_qwen/config.toml +0 -29
  272. examples/finetuning_old/_backup_synth_qwen/example_log.md +0 -324
  273. examples/finetuning_old/_backup_synth_qwen/filter_traces.py +0 -60
  274. examples/finetuning_old/_backup_synth_qwen/filter_traces_achievements.py +0 -239
  275. examples/finetuning_old/_backup_synth_qwen/purge_v3_traces.py +0 -109
  276. examples/finetuning_old/_backup_synth_qwen/react_agent_lm.py +0 -1924
  277. examples/finetuning_old/_backup_synth_qwen/readme.md +0 -49
  278. examples/finetuning_old/_backup_synth_qwen/run_crafter_qwen4b.py +0 -114
  279. examples/finetuning_old/_backup_synth_qwen/run_demo.sh +0 -195
  280. examples/finetuning_old/_backup_synth_qwen/sft_kickoff.py +0 -118
  281. examples/finetuning_old/synth_qwen_v1/README.md +0 -68
  282. examples/finetuning_old/synth_qwen_v1/filter_traces.py +0 -60
  283. examples/finetuning_old/synth_qwen_v1/filter_traces_achievements.py +0 -239
  284. examples/finetuning_old/synth_qwen_v1/finetune.py +0 -46
  285. examples/finetuning_old/synth_qwen_v1/hello_ft_model.py +0 -71
  286. examples/finetuning_old/synth_qwen_v1/infer.py +0 -37
  287. examples/finetuning_old/synth_qwen_v1/poll.py +0 -44
  288. examples/finetuning_old/synth_qwen_v1/prepare_data.py +0 -35
  289. examples/finetuning_old/synth_qwen_v1/purge_v3_traces.py +0 -109
  290. examples/finetuning_old/synth_qwen_v1/react_agent_lm.py +0 -1932
  291. examples/finetuning_old/synth_qwen_v1/run_crafter_sft_job.py +0 -207
  292. examples/finetuning_old/synth_qwen_v1/run_ft_job.py +0 -232
  293. examples/finetuning_old/synth_qwen_v1/upload_data.py +0 -34
  294. examples/finetuning_old/synth_qwen_v1/util.py +0 -147
  295. examples/rl_old/task_app.py +0 -962
  296. examples/warming_up_to_rl/old/event_rewards.md +0 -234
  297. examples/warming_up_to_rl/old/notes.md +0 -73
  298. examples/warming_up_to_rl/task_app/synth_envs_hosted/test_stepwise_rewards.py +0 -58
  299. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/filter_traces_sft_turso.py +0 -738
  300. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/filter_traces_sft_turso.py +0 -580
  301. synth_ai/environments/examples/sokoban/units/astar_common.py +0 -95
  302. synth_ai/experimental/synth_oss.py +0 -446
  303. synth_ai/install_sqld.sh +0 -40
  304. synth_ai/learning/filtering.py +0 -0
  305. synth_ai/learning/offline/dpo.py +0 -0
  306. synth_ai/learning/offline/providers.py +0 -7
  307. synth_ai/learning/offline/sft.py +0 -0
  308. synth_ai/learning/offline/shared.py +0 -0
  309. synth_ai/learning/online/grpo.py +0 -0
  310. synth_ai/learning/online/irft.py +0 -0
  311. synth_ai/learning/prompts/banking77_injection_eval.py +0 -168
  312. synth_ai/learning/prompts/gepa.py +0 -0
  313. synth_ai/learning/prompts/hello_world_in_context_injection_ex.py +0 -213
  314. synth_ai/learning/prompts/mipro.py +0 -289
  315. synth_ai/learning/prompts/random_search.py +0 -246
  316. synth_ai/learning/prompts/run_mipro_banking77.py +0 -172
  317. synth_ai/learning/prompts/run_random_search_banking77.py +0 -324
  318. synth_ai/rl/secrets.py +0 -19
  319. synth_ai/scripts/verify_rewards.py +0 -100
  320. synth_ai/tracing/__init__.py +0 -30
  321. synth_ai/tracing_v1/__init__.py +0 -33
  322. synth_ai/tracing_v3/turso/__init__.py +0 -25
  323. synth_ai/tracing_v3/turso/manager.py +0 -774
  324. synth_ai/zyk/__init__.py +0 -30
  325. synth_ai-0.2.9.dev4.dist-info/METADATA +0 -131
  326. /synth_ai/{lm → v0/lm}/caching/__init__.py +0 -0
  327. /synth_ai/{lm → v0/lm}/caching/constants.py +0 -0
  328. /synth_ai/{lm → v0/lm}/caching/dbs.py +0 -0
  329. /synth_ai/{lm → v0/lm}/constants.py +0 -0
  330. /synth_ai/{lm → v0/lm}/core/__init__.py +0 -0
  331. /synth_ai/{lm → v0/lm}/cost/__init__.py +0 -0
  332. /synth_ai/{lm → v0/lm}/cost/monitor.py +0 -0
  333. /synth_ai/{lm → v0/lm}/cost/statefulness.py +0 -0
  334. /synth_ai/{lm → v0/lm}/injection.py +0 -0
  335. /synth_ai/{lm → v0/lm}/provider_support/__init__.py +0 -0
  336. /synth_ai/{lm → v0/lm}/provider_support/suppress_logging.py +0 -0
  337. /synth_ai/{lm → v0/lm}/structured_outputs/__init__.py +0 -0
  338. /synth_ai/{lm → v0/lm}/structured_outputs/inject.py +0 -0
  339. /synth_ai/{lm → v0/lm}/tools/__init__.py +0 -0
  340. /synth_ai/{lm → v0/lm}/tools/base.py +0 -0
  341. /synth_ai/{lm → v0/lm}/unified_interface.py +0 -0
  342. /synth_ai/{lm → v0/lm}/vendors/__init__.py +0 -0
  343. /synth_ai/{lm → v0/lm}/vendors/base.py +0 -0
  344. /synth_ai/{lm → v0/lm}/vendors/core/__init__.py +0 -0
  345. /synth_ai/{lm → v0/lm}/vendors/core/synth_dev_api.py +0 -0
  346. /synth_ai/{lm → v0/lm}/vendors/local/__init__.py +0 -0
  347. /synth_ai/{lm → v0/lm}/vendors/local/ollama.py +0 -0
  348. /synth_ai/{lm → v0/lm}/vendors/retries.py +0 -0
  349. /synth_ai/{lm → v0/lm}/vendors/supported/__init__.py +0 -0
  350. /synth_ai/{lm → v0/lm}/warmup.py +0 -0
  351. {synth_ai-0.2.9.dev4.dist-info → synth_ai-0.2.9.dev6.dist-info}/WHEEL +0 -0
  352. {synth_ai-0.2.9.dev4.dist-info → synth_ai-0.2.9.dev6.dist-info}/entry_points.txt +0 -0
  353. {synth_ai-0.2.9.dev4.dist-info → synth_ai-0.2.9.dev6.dist-info}/licenses/LICENSE +0 -0
@@ -1,8 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
+ import contextlib
4
5
  import logging
5
- from typing import Any, Dict, Optional
6
+ from typing import Any
6
7
 
7
8
  import httpx
8
9
 
@@ -11,22 +12,24 @@ logger = logging.getLogger(__name__)
11
12
 
12
13
  class OpenAIClient:
13
14
  """Async HTTP client for OpenAI-compatible inference servers (vLLM)."""
14
-
15
+
15
16
  def __init__(
16
17
  self,
17
18
  base_url: str,
18
- api_key: Optional[str] = None,
19
+ api_key: str | None = None,
19
20
  timeout_s: float = 120.0,
20
21
  ) -> None:
21
22
  self.base_url = base_url.rstrip("/")
22
23
  self.api_key = api_key
23
24
  self.timeout_s = timeout_s
24
25
  self.headers = {}
25
-
26
+
26
27
  if api_key:
27
28
  self.headers["Authorization"] = f"Bearer {api_key}"
28
29
 
29
- def _fix_model_parameters(self, request: Dict[str, Any], target_url: Optional[str] = None) -> Dict[str, Any]:
30
+ def _fix_model_parameters(
31
+ self, request: dict[str, Any], target_url: str | None = None
32
+ ) -> dict[str, Any]:
30
33
  """
31
34
  Fix parameter compatibility for newer OpenAI models.
32
35
 
@@ -75,7 +78,9 @@ class OpenAIClient:
75
78
  if "max_tokens" in fixed_request:
76
79
  if "max_completion_tokens" not in fixed_request:
77
80
  fixed_request["max_completion_tokens"] = fixed_request.pop("max_tokens")
78
- logger.info(f"Converted max_tokens to max_completion_tokens for model {model}")
81
+ logger.info(
82
+ f"Converted max_tokens to max_completion_tokens for model {model}"
83
+ )
79
84
  else:
80
85
  fixed_request.pop("max_tokens")
81
86
  logger.info(f"Removed conflicting max_tokens parameter for model {model}")
@@ -87,9 +92,25 @@ class OpenAIClient:
87
92
  try:
88
93
  tools = fixed_request.get("tools")
89
94
  if isinstance(tools, list) and tools:
95
+ # Choose the first provided function name from tools schema (e.g., run_command)
96
+ func_name = None
97
+ for t in tools:
98
+ try:
99
+ cand = None
100
+ if isinstance(t, dict):
101
+ f = t.get("function")
102
+ if isinstance(f, dict):
103
+ cand = f.get("name")
104
+ if isinstance(cand, str) and cand:
105
+ func_name = cand
106
+ break
107
+ except Exception:
108
+ continue
109
+ if not func_name:
110
+ func_name = "run_command"
90
111
  fixed_request["tool_choice"] = {
91
112
  "type": "function",
92
- "function": {"name": "interact_many"},
113
+ "function": {"name": func_name},
93
114
  }
94
115
  fixed_request["parallel_tool_calls"] = False
95
116
  except Exception:
@@ -99,11 +120,11 @@ class OpenAIClient:
99
120
 
100
121
  async def generate(
101
122
  self,
102
- request: Dict[str, Any],
103
- base_url: Optional[str] = None,
104
- timeout_s: Optional[float] = None,
105
- extra_headers: Optional[Dict[str, str]] = None,
106
- ) -> Dict[str, Any]:
123
+ request: dict[str, Any],
124
+ base_url: str | None = None,
125
+ timeout_s: float | None = None,
126
+ extra_headers: dict[str, str] | None = None,
127
+ ) -> dict[str, Any]:
107
128
  """
108
129
  Send a chat completion request to the inference server.
109
130
 
@@ -131,21 +152,20 @@ class OpenAIClient:
131
152
  logger.info(f"Inference POST target: {url}")
132
153
  if extra_headers:
133
154
  logger.info(f"Extra headers: {extra_headers}")
134
- try:
135
- keys_preview = sorted(list(processed_request.keys()))
155
+ with contextlib.suppress(Exception):
156
+ keys_preview = sorted(processed_request.keys())
136
157
  logger.info(f"Request keys: {keys_preview}")
137
- except Exception:
138
- pass
139
158
 
140
159
  # Final hard-guard for OpenAI: ensure unsupported field is not present
141
160
  try:
142
- if "openai" in url.lower():
143
- if "stop_after_tool_calls" in processed_request:
144
- processed_request.pop("stop_after_tool_calls", None)
145
- logger.info("Removed stop_after_tool_calls for OpenAI request")
161
+ if "openai" in url.lower() and "stop_after_tool_calls" in processed_request:
162
+ processed_request.pop("stop_after_tool_calls", None)
163
+ logger.info("Removed stop_after_tool_calls for OpenAI request")
146
164
  # Groq-specific requirement: when using JSON mode, one of the messages must contain the word 'json'
147
165
  low_url = url.lower()
148
- if ("groq.com" in low_url or "/openai" in low_url) and isinstance(processed_request, dict):
166
+ if ("groq.com" in low_url or "/openai" in low_url) and isinstance(
167
+ processed_request, dict
168
+ ):
149
169
  rf = processed_request.get("response_format")
150
170
  rf_type = None
151
171
  if isinstance(rf, dict):
@@ -164,7 +184,9 @@ class OpenAIClient:
164
184
  # Join any text segments
165
185
  parts = []
166
186
  for seg in content:
167
- if isinstance(seg, dict) and isinstance(seg.get("text"), str):
187
+ if isinstance(seg, dict) and isinstance(
188
+ seg.get("text"), str
189
+ ):
168
190
  parts.append(seg["text"])
169
191
  text = "\n".join(parts)
170
192
  if isinstance(text, str) and ("json" in text.lower()):
@@ -174,13 +196,17 @@ class OpenAIClient:
174
196
  continue
175
197
  if not has_json_word:
176
198
  try:
177
- instruction = "Respond in strict JSON only. Output a single valid JSON object."
199
+ instruction = (
200
+ "Respond in strict JSON only. Output a single valid JSON object."
201
+ )
178
202
  if not isinstance(msgs, list):
179
203
  msgs = []
180
204
  # Prepend a system message to satisfy Groq requirement without changing user intent
181
205
  prepend = {"role": "system", "content": instruction}
182
206
  processed_request["messages"] = [prepend] + list(msgs)
183
- logger.info("Injected JSON-mode system instruction for Groq response_format compliance")
207
+ logger.info(
208
+ "Injected JSON-mode system instruction for Groq response_format compliance"
209
+ )
184
210
  except Exception:
185
211
  pass
186
212
  except Exception:
@@ -194,7 +220,7 @@ class OpenAIClient:
194
220
  headers=headers,
195
221
  )
196
222
  response.raise_for_status()
197
-
223
+
198
224
  # Rich response diagnostics
199
225
  content_type = response.headers.get("content-type")
200
226
  body_text = response.text
@@ -203,12 +229,14 @@ class OpenAIClient:
203
229
  )
204
230
  if body_text:
205
231
  preview_len = min(800, len(body_text))
206
- logger.info(f"Inference response preview ({preview_len} bytes): {body_text[:preview_len]}")
232
+ logger.info(
233
+ f"Inference response preview ({preview_len} bytes): {body_text[:preview_len]}"
234
+ )
207
235
 
208
236
  result = response.json()
209
237
  logger.info(f"Inference response parsed_type={type(result).__name__}")
210
238
  return result
211
-
239
+
212
240
  except httpx.TimeoutException:
213
241
  logger.error(f"Request to {url} timed out after {timeout}s")
214
242
  raise
@@ -217,12 +245,14 @@ class OpenAIClient:
217
245
  text = e.response.text if e.response is not None else str(e)
218
246
  # Log full body for debugging remote failures
219
247
  try:
220
- logger.error({
221
- "openai_http_error": True,
222
- "status": status,
223
- "url": url,
224
- "body": text,
225
- })
248
+ logger.error(
249
+ {
250
+ "openai_http_error": True,
251
+ "status": status,
252
+ "url": url,
253
+ "body": text,
254
+ }
255
+ )
226
256
  except Exception:
227
257
  logger.error(f"HTTP error from {url}: {status} - {text}")
228
258
  # For 4xx/5xx, print full sanitized request to aid debugging (especially Groq 400s)
@@ -230,13 +260,15 @@ class OpenAIClient:
230
260
  redacted_headers = dict(headers)
231
261
  if "Authorization" in redacted_headers:
232
262
  redacted_headers["Authorization"] = "***REDACTED***"
233
- logger.error({
234
- "request_debug": True,
235
- "status": status,
236
- "target": url,
237
- "headers": redacted_headers,
238
- "payload": processed_request,
239
- })
263
+ logger.error(
264
+ {
265
+ "request_debug": True,
266
+ "status": status,
267
+ "target": url,
268
+ "headers": redacted_headers,
269
+ "payload": processed_request,
270
+ }
271
+ )
240
272
  except Exception:
241
273
  pass
242
274
  # Special case: token budget exceeded (OpenAI-compatible error schema)
@@ -266,27 +298,42 @@ class OpenAIClient:
266
298
  processed_request.pop(k, None)
267
299
  # Force structured tool choice
268
300
  if processed_request.get("tool_choice") == "required":
269
- func_name = "interact_many"
301
+ func_name = "run_command"
270
302
  try:
271
303
  tools_arr = processed_request.get("tools") or []
272
304
  if isinstance(tools_arr, list) and tools_arr:
273
- f = tools_arr[0].get("function") if isinstance(tools_arr[0], dict) else None
274
- cand = (f or {}).get("name") if isinstance(f, dict) else None
305
+ f = (
306
+ tools_arr[0].get("function")
307
+ if isinstance(tools_arr[0], dict)
308
+ else None
309
+ )
310
+ cand = (
311
+ (f or {}).get("name")
312
+ if isinstance(f, dict)
313
+ else None
314
+ )
275
315
  if isinstance(cand, str) and cand:
276
316
  func_name = cand
277
317
  except Exception:
278
318
  pass
279
- processed_request["tool_choice"] = {"type": "function", "function": {"name": func_name}}
319
+ processed_request["tool_choice"] = {
320
+ "type": "function",
321
+ "function": {"name": func_name},
322
+ }
280
323
  processed_request["parallel_tool_calls"] = False
281
- logger.warning({
282
- "token_budget_recovery": True,
283
- "messages_tokens": messages_tokens,
284
- "model_limit": model_limit,
285
- "retry_max_tokens": new_max,
286
- })
324
+ logger.warning(
325
+ {
326
+ "token_budget_recovery": True,
327
+ "messages_tokens": messages_tokens,
328
+ "model_limit": model_limit,
329
+ "retry_max_tokens": new_max,
330
+ }
331
+ )
287
332
  # Retry once with reduced budget
288
333
  async with httpx.AsyncClient(timeout=timeout) as client2:
289
- r2 = await client2.post(url, json=processed_request, headers=headers)
334
+ r2 = await client2.post(
335
+ url, json=processed_request, headers=headers
336
+ )
290
337
  r2.raise_for_status()
291
338
  return r2.json()
292
339
  except Exception:
@@ -302,14 +349,17 @@ class OpenAIClient:
302
349
  err = e.response.json()
303
350
  except Exception:
304
351
  err = {"error": "unprocessable", "detail": (text or "")[:200]}
305
- logger.warning({
306
- "inference_422_recovered": True,
307
- "detail": err,
308
- })
352
+ logger.warning(
353
+ {
354
+ "inference_422_recovered": True,
355
+ "detail": err,
356
+ }
357
+ )
309
358
  except Exception:
310
359
  pass
311
360
  # Return a minimal OpenAI-compatible response with no tool_calls/content
312
361
  import time as _t
362
+
313
363
  return {
314
364
  "id": f"cmpl-{int(_t.time())}",
315
365
  "object": "chat.completion",
@@ -328,25 +378,25 @@ class OpenAIClient:
328
378
  except Exception as e:
329
379
  logger.error(f"Unexpected error calling {url}: {e}")
330
380
  raise
331
-
381
+
332
382
  async def check_health(
333
383
  self,
334
- base_url: Optional[str] = None,
335
- timeout_s: Optional[float] = None,
336
- ) -> Dict[str, Any]:
384
+ base_url: str | None = None,
385
+ timeout_s: float | None = None,
386
+ ) -> dict[str, Any]:
337
387
  """
338
388
  Check if the inference service is healthy.
339
-
389
+
340
390
  Args:
341
391
  base_url: Override base URL for this request
342
392
  timeout_s: Override timeout for this request
343
-
393
+
344
394
  Returns:
345
395
  Health status dict with 'status' field
346
396
  """
347
397
  url = (base_url or self.base_url).rstrip("/") + "/health"
348
398
  timeout = timeout_s or 10.0
349
-
399
+
350
400
  try:
351
401
  async with httpx.AsyncClient(timeout=timeout) as client:
352
402
  response = await client.get(url, headers=self.headers)
@@ -364,19 +414,19 @@ class OpenAIClient:
364
414
  return {"status": "unhealthy", "error": str(e)}
365
415
  except Exception as e:
366
416
  return {"status": "unhealthy", "error": str(e)}
367
-
417
+
368
418
  async def generate_with_retries(
369
419
  self,
370
- request: Dict[str, Any],
371
- base_url: Optional[str] = None,
372
- timeout_s: Optional[float] = None,
420
+ request: dict[str, Any],
421
+ base_url: str | None = None,
422
+ timeout_s: float | None = None,
373
423
  max_retries: int = 4,
374
424
  backoff_factor: float = 2.0,
375
- extra_headers: Optional[Dict[str, str]] = None,
376
- ) -> Dict[str, Any]:
425
+ extra_headers: dict[str, str] | None = None,
426
+ ) -> dict[str, Any]:
377
427
  """
378
428
  Generate with exponential backoff retries for transient errors.
379
-
429
+
380
430
  Args:
381
431
  request: OpenAI-compatible chat completion request
382
432
  base_url: Override base URL
@@ -384,13 +434,13 @@ class OpenAIClient:
384
434
  max_retries: Maximum number of retry attempts
385
435
  backoff_factor: Exponential backoff multiplier
386
436
  extra_headers: Additional headers to include (e.g., X-Policy-Name)
387
-
437
+
388
438
  Returns:
389
439
  OpenAI-compatible chat completion response
390
440
  """
391
441
  last_error = None
392
442
  wait_time = 1.0
393
-
443
+
394
444
  for attempt in range(max_retries + 1):
395
445
  try:
396
446
  # Apply parameter fixes to the request
@@ -417,7 +467,9 @@ class OpenAIClient:
417
467
  retry_after = response_data.get("retry_after", 1)
418
468
  # Use the suggested retry_after time instead of exponential backoff for overload
419
469
  wait_time = max(wait_time, float(retry_after))
420
- logger.warning(f"Inference service overloaded (400). {response_data} Retrying after {wait_time}s...")
470
+ logger.warning(
471
+ f"Inference service overloaded (400). {response_data} Retrying after {wait_time}s..."
472
+ )
421
473
  else:
422
474
  # This is a different type of 400 error, don't retry
423
475
  try:
@@ -428,13 +480,15 @@ class OpenAIClient:
428
480
  redacted_headers["Authorization"] = "***REDACTED***"
429
481
  except Exception:
430
482
  redacted_headers = {}
431
- logger.error({
432
- "non_overload_400": True,
433
- "target": (base_url or self.base_url),
434
- "payload": processed_request,
435
- "headers": redacted_headers,
436
- "body": e.response.text if e.response is not None else None,
437
- })
483
+ logger.error(
484
+ {
485
+ "non_overload_400": True,
486
+ "target": (base_url or self.base_url),
487
+ "payload": processed_request,
488
+ "headers": redacted_headers,
489
+ "body": e.response.text if e.response is not None else None,
490
+ }
491
+ )
438
492
  except Exception:
439
493
  pass
440
494
  raise RuntimeError(
@@ -442,14 +496,14 @@ class OpenAIClient:
442
496
  ) from e
443
497
  except Exception:
444
498
  # If we can't parse the response, don't retry 400 errors
445
- try:
446
- logger.error({
447
- "non_overload_400_unparsed": True,
448
- "target": (base_url or self.base_url),
449
- "payload": processed_request,
450
- })
451
- except Exception:
452
- pass
499
+ with contextlib.suppress(Exception):
500
+ logger.error(
501
+ {
502
+ "non_overload_400_unparsed": True,
503
+ "target": (base_url or self.base_url),
504
+ "payload": processed_request,
505
+ }
506
+ )
453
507
  raise RuntimeError(
454
508
  f"Inference 400 response (unparsed): {e.response.text if e.response is not None else 'Bad Request'}"
455
509
  ) from e
@@ -472,7 +526,7 @@ class OpenAIClient:
472
526
  )
473
527
  except httpx.TimeoutException as e:
474
528
  last_error = e
475
-
529
+
476
530
  if attempt < max_retries:
477
531
  logger.warning(
478
532
  f"Inference request failed (attempt {attempt + 1}/{max_retries + 1}), "
@@ -480,21 +534,21 @@ class OpenAIClient:
480
534
  )
481
535
  await asyncio.sleep(wait_time)
482
536
  wait_time *= backoff_factor
483
-
537
+
484
538
  raise last_error
485
539
 
486
540
 
487
541
  def create_inference_client(
488
542
  task_app: Any,
489
- api_key: Optional[str] = None,
543
+ api_key: str | None = None,
490
544
  ) -> OpenAIClient:
491
545
  """
492
546
  Create an inference client using TaskApp configuration.
493
-
547
+
494
548
  Args:
495
549
  task_app: TaskApp instance with vllm_base_url
496
550
  api_key: Optional API key for authentication
497
-
551
+
498
552
  Returns:
499
553
  Configured OpenAIClient instance
500
554
  """
@@ -502,10 +556,62 @@ def create_inference_client(
502
556
  if api_key is None:
503
557
  try:
504
558
  import os as _os # local import to avoid module-level side effects
559
+
505
560
  api_key = _os.getenv("OPENAI_API_KEY") or getattr(task_app, "openai_api_key", None)
506
561
  except Exception:
507
562
  api_key = None
508
563
 
564
+ import json as _json
565
+ import os as _os
566
+ import time as _time
567
+
568
+ if _os.getenv("SYNTH_FAKE_INFERENCE", "").strip():
569
+
570
+ class _DummyClient:
571
+ async def generate_with_retries(
572
+ self,
573
+ request: dict[str, Any],
574
+ base_url: str | None = None,
575
+ max_retries: int = 0,
576
+ backoff_factor: float = 1.0,
577
+ extra_headers: dict[str, str] | None = None,
578
+ ) -> dict[str, Any]:
579
+ tool_call = {
580
+ "id": "call_dummy",
581
+ "type": "function",
582
+ "function": {
583
+ "name": "interact_many",
584
+ "arguments": _json.dumps({"actions": ["move_right"]}),
585
+ },
586
+ }
587
+ return {
588
+ "id": f"cmpl-{int(_time.time())}",
589
+ "object": "chat.completion",
590
+ "created": int(_time.time()),
591
+ "model": request.get("model") or "dummy-model",
592
+ "choices": [
593
+ {
594
+ "index": 0,
595
+ "message": {
596
+ "role": "assistant",
597
+ "content": "",
598
+ "tool_calls": [tool_call],
599
+ },
600
+ "finish_reason": "tool_calls",
601
+ }
602
+ ],
603
+ "usage": {"prompt_tokens": 10, "completion_tokens": 5, "total_tokens": 15},
604
+ }
605
+
606
+ async def check_health(
607
+ self,
608
+ base_url: str | None = None,
609
+ timeout_s: float | None = None,
610
+ ) -> dict[str, Any]:
611
+ return {"status": "ok", "dummy": True}
612
+
613
+ return _DummyClient()
614
+
509
615
  return OpenAIClient(
510
616
  base_url=task_app.vllm_base_url,
511
617
  api_key=api_key,
@@ -12,7 +12,6 @@ For Modal deployment:
12
12
  from __future__ import annotations
13
13
 
14
14
  import os
15
- from typing import Optional
16
15
 
17
16
  import modal
18
17
 
@@ -26,7 +25,6 @@ except ImportError:
26
25
 
27
26
  from synth_envs_hosted.hosted_app import create_app
28
27
 
29
-
30
28
  # Local development mode
31
29
  if __name__ == "__main__":
32
30
  import uvicorn