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.
- examples/__init__.py +16 -0
- examples/crafter_debug_render.py +23 -17
- examples/qwen_coder/README.md +102 -0
- examples/qwen_coder/_shared.py +113 -0
- examples/qwen_coder/configs/coder_lora_30b.toml +61 -0
- examples/qwen_coder/configs/coder_lora_4b.toml +57 -0
- examples/qwen_coder/configs/coder_lora_small.toml +58 -0
- examples/qwen_coder/generate_dataset.py +98 -0
- examples/qwen_coder/infer_ft_smoke.py +64 -0
- examples/qwen_coder/infer_prod_proxy.py +73 -0
- examples/qwen_coder/infer_via_synth.py +87 -0
- examples/qwen_coder/scripts/infer_coder.sh +18 -0
- examples/qwen_coder/scripts/train_coder_30b.sh +21 -0
- examples/qwen_coder/sft_full_17b.py +103 -0
- examples/qwen_coder/sft_lora_30b.py +110 -0
- examples/qwen_coder/subset_jsonl.py +38 -0
- examples/qwen_coder/validate_jsonl.py +59 -0
- examples/rl/configs/eval_base_qwen.toml +1 -1
- examples/rl/configs/rl_from_base_qwen17.toml +1 -1
- examples/rl/download_dataset.py +26 -10
- examples/rl/run_eval.py +53 -52
- examples/rl/run_rl_and_save.py +29 -12
- examples/rl/task_app/math_single_step.py +180 -41
- examples/rl/task_app/math_task_app.py +14 -6
- examples/sft/README.md +139 -0
- examples/sft/configs/crafter_fft_qwen0p6b.toml +44 -0
- examples/sft/configs/crafter_lora_qwen0p6b.toml +45 -0
- examples/sft/evaluate.py +117 -0
- examples/sft/export_dataset.py +117 -0
- examples/sft/generate_traces.py +162 -0
- examples/swe/__init__.py +12 -0
- examples/swe/task_app/README.md +105 -0
- examples/swe/task_app/__init__.py +2 -0
- examples/swe/task_app/grpo_swe_mini.py +571 -0
- examples/swe/task_app/grpo_swe_mini_task_app.py +136 -0
- examples/swe/task_app/hosted/README.md +173 -0
- examples/swe/task_app/hosted/__init__.py +5 -0
- examples/swe/task_app/hosted/branching.py +143 -0
- examples/swe/task_app/hosted/environment_routes.py +1289 -0
- examples/swe/task_app/hosted/envs/__init__.py +1 -0
- examples/swe/task_app/hosted/envs/crafter/__init__.py +6 -0
- examples/swe/task_app/hosted/envs/crafter/app.py +1 -0
- examples/swe/task_app/hosted/envs/crafter/environment.py +522 -0
- examples/swe/task_app/hosted/envs/crafter/policy.py +478 -0
- examples/swe/task_app/hosted/envs/crafter/react_agent.py +108 -0
- examples/swe/task_app/hosted/envs/crafter/shared.py +305 -0
- examples/swe/task_app/hosted/envs/crafter/tools.py +47 -0
- examples/swe/task_app/hosted/envs/mini_swe/__init__.py +8 -0
- examples/swe/task_app/hosted/envs/mini_swe/environment.py +1164 -0
- examples/swe/task_app/hosted/envs/mini_swe/policy.py +355 -0
- examples/swe/task_app/hosted/envs/mini_swe/shared.py +83 -0
- examples/swe/task_app/hosted/envs/mini_swe/tools.py +96 -0
- examples/swe/task_app/hosted/hosted_app.py +204 -0
- examples/swe/task_app/hosted/inference/__init__.py +5 -0
- examples/swe/task_app/hosted/inference/openai_client.py +618 -0
- examples/swe/task_app/hosted/main.py +100 -0
- examples/swe/task_app/hosted/policy_routes.py +1079 -0
- examples/swe/task_app/hosted/registry.py +195 -0
- examples/swe/task_app/hosted/rollout.py +1869 -0
- examples/swe/task_app/hosted/storage/__init__.py +5 -0
- examples/swe/task_app/hosted/storage/volume.py +211 -0
- examples/swe/task_app/hosted/test_agents.py +161 -0
- examples/swe/task_app/hosted/test_service.py +137 -0
- examples/swe/task_app/hosted/utils.py +62 -0
- examples/vlm/README.md +68 -0
- examples/vlm/configs/crafter_vlm_gpt4o.toml +44 -0
- examples/vlm/crafter_image_only_agent.py +207 -0
- examples/vlm/crafter_openai_vlm_agent.py +277 -0
- examples/vlm/filter_image_rows.py +63 -0
- examples/vlm/run_crafter_vlm_benchmark.py +316 -0
- examples/warming_up_to_rl/analyze_trace_db.py +12 -10
- examples/warming_up_to_rl/configs/rl_from_base_qwen4b.toml +11 -1
- examples/warming_up_to_rl/export_trace_sft.py +218 -36
- examples/warming_up_to_rl/groq_test.py +15 -8
- examples/warming_up_to_rl/manage_secrets.py +29 -25
- examples/warming_up_to_rl/readme.md +9 -2
- examples/warming_up_to_rl/run_eval.py +137 -61
- examples/warming_up_to_rl/run_fft_and_save.py +131 -60
- examples/warming_up_to_rl/run_local_rollout.py +88 -39
- examples/warming_up_to_rl/run_local_rollout_modal.py +114 -28
- examples/warming_up_to_rl/run_local_rollout_parallel.py +81 -20
- examples/warming_up_to_rl/run_local_rollout_traced.py +126 -23
- examples/warming_up_to_rl/run_rl_and_save.py +35 -12
- examples/warming_up_to_rl/run_rollout_remote.py +44 -19
- examples/warming_up_to_rl/task_app/README.md +6 -2
- examples/warming_up_to_rl/task_app/grpo_crafter.py +319 -57
- examples/warming_up_to_rl/task_app/grpo_crafter_task_app.py +11 -30
- examples/warming_up_to_rl/task_app/synth_envs_hosted/__init__.py +1 -1
- examples/warming_up_to_rl/task_app/synth_envs_hosted/branching.py +9 -11
- examples/warming_up_to_rl/task_app/synth_envs_hosted/environment_routes.py +137 -182
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/__init__.py +1 -1
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/__init__.py +1 -1
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/app.py +1 -1
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/environment.py +150 -57
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/policy.py +105 -69
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/react_agent.py +19 -7
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/shared.py +45 -42
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/tools.py +1 -1
- examples/warming_up_to_rl/task_app/synth_envs_hosted/hosted_app.py +47 -45
- examples/warming_up_to_rl/task_app/synth_envs_hosted/inference/__init__.py +1 -1
- examples/warming_up_to_rl/task_app/synth_envs_hosted/inference/openai_client.py +198 -92
- examples/warming_up_to_rl/task_app/synth_envs_hosted/main.py +0 -2
- examples/warming_up_to_rl/task_app/synth_envs_hosted/policy_routes.py +361 -263
- examples/warming_up_to_rl/task_app/synth_envs_hosted/registry.py +21 -23
- examples/warming_up_to_rl/task_app/synth_envs_hosted/rollout.py +394 -274
- examples/warming_up_to_rl/task_app/synth_envs_hosted/storage/__init__.py +1 -1
- examples/warming_up_to_rl/task_app/synth_envs_hosted/storage/volume.py +56 -62
- examples/warming_up_to_rl/task_app/synth_envs_hosted/test_agents.py +1 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/test_service.py +6 -15
- examples/warming_up_to_rl/task_app/synth_envs_hosted/utils.py +4 -3
- synth/__init__.py +14 -0
- synth_ai/__init__.py +20 -4
- synth_ai/api/models/supported.py +376 -0
- synth_ai/api/train/builders.py +157 -26
- synth_ai/api/train/cli.py +213 -57
- synth_ai/api/train/config_finder.py +65 -5
- synth_ai/api/train/env_resolver.py +33 -15
- synth_ai/api/train/pollers.py +13 -4
- synth_ai/api/train/supported_algos.py +139 -0
- synth_ai/api/train/task_app.py +5 -3
- synth_ai/api/train/utils.py +33 -48
- synth_ai/cli/__init__.py +19 -4
- synth_ai/cli/_modal_wrapper.py +28 -0
- synth_ai/cli/_typer_patch.py +49 -0
- synth_ai/cli/balance.py +2 -3
- synth_ai/cli/calc.py +1 -1
- synth_ai/cli/demo.py +21 -6
- synth_ai/cli/recent.py +2 -2
- synth_ai/cli/rl_demo.py +77 -17
- synth_ai/cli/root.py +116 -39
- synth_ai/cli/status.py +2 -2
- synth_ai/cli/task_apps.py +1709 -243
- synth_ai/cli/traces.py +7 -4
- synth_ai/cli/turso.py +73 -0
- synth_ai/cli/watch.py +12 -18
- synth_ai/core/experiment.py +0 -2
- synth_ai/demo_registry.py +68 -31
- synth_ai/demos/core/cli.py +516 -194
- synth_ai/demos/demo_task_apps/__init__.py +3 -3
- synth_ai/demos/demo_task_apps/core.py +64 -28
- synth_ai/demos/demo_task_apps/crafter/configs/crafter_fft_4b.toml +2 -3
- synth_ai/demos/demo_task_apps/crafter/grpo_crafter_task_app.py +37 -30
- synth_ai/demos/demo_task_apps/math/_common.py +1 -2
- synth_ai/demos/demo_task_apps/math/app.py +2 -1
- synth_ai/demos/demo_task_apps/math/deploy_modal.py +3 -6
- synth_ai/demos/demo_task_apps/math/modal_task_app.py +183 -82
- synth_ai/demos/demo_task_apps/math/task_app_entry.py +0 -2
- synth_ai/environments/examples/bandit/engine.py +12 -4
- synth_ai/environments/examples/bandit/taskset.py +4 -4
- synth_ai/environments/examples/crafter_classic/environment.py +76 -1
- synth_ai/environments/reproducibility/tree.py +5 -6
- synth_ai/environments/service/app.py +11 -12
- synth_ai/environments/service/core_routes.py +10 -9
- synth_ai/environments/stateful/engine.py +1 -1
- synth_ai/environments/tasks/core.py +1 -0
- synth_ai/environments/tasks/filters.py +5 -6
- synth_ai/environments/tasks/utils.py +4 -5
- synth_ai/evals/base.py +0 -2
- synth_ai/handshake.py +11 -9
- synth_ai/http.py +1 -1
- synth_ai/http_client.py +43 -11
- synth_ai/inference/__init__.py +0 -2
- synth_ai/inference/client.py +20 -6
- synth_ai/jobs/client.py +103 -78
- synth_ai/learning/__init__.py +41 -6
- synth_ai/learning/algorithms.py +14 -0
- synth_ai/learning/client.py +121 -29
- synth_ai/learning/config.py +2 -40
- synth_ai/learning/constants.py +0 -2
- synth_ai/learning/ft_client.py +4 -56
- synth_ai/learning/health.py +13 -7
- synth_ai/learning/jobs.py +43 -47
- synth_ai/{rl → learning/rl}/__init__.py +14 -5
- synth_ai/learning/rl/client.py +267 -0
- synth_ai/learning/rl/config.py +31 -0
- synth_ai/{rl → learning/rl}/contracts.py +5 -10
- synth_ai/{rl → learning/rl}/env_keys.py +45 -16
- synth_ai/learning/rl/secrets.py +13 -0
- synth_ai/learning/rl_client.py +2 -253
- synth_ai/learning/sft/__init__.py +29 -0
- synth_ai/learning/sft/client.py +68 -0
- synth_ai/learning/sft/config.py +270 -0
- synth_ai/learning/sft/data.py +295 -0
- synth_ai/learning/sse.py +25 -26
- synth_ai/learning/validators.py +25 -24
- synth_ai/lm/__init__.py +21 -47
- synth_ai/task/__init__.py +26 -27
- synth_ai/task/apps/__init__.py +18 -19
- synth_ai/task/auth.py +35 -23
- synth_ai/task/client.py +15 -13
- synth_ai/task/contracts.py +37 -35
- synth_ai/task/datasets.py +9 -6
- synth_ai/task/errors.py +11 -10
- synth_ai/task/health.py +17 -11
- synth_ai/task/json.py +58 -24
- synth_ai/task/proxy.py +15 -14
- synth_ai/task/rubrics.py +22 -15
- synth_ai/task/server.py +43 -17
- synth_ai/task/tracing_utils.py +12 -7
- synth_ai/task/validators.py +0 -1
- synth_ai/task/vendors.py +5 -7
- synth_ai/tracing_v3/__init__.py +2 -0
- synth_ai/tracing_v3/abstractions.py +21 -4
- synth_ai/tracing_v3/db_config.py +26 -1
- synth_ai/tracing_v3/decorators.py +18 -15
- synth_ai/tracing_v3/examples/basic_usage.py +3 -2
- synth_ai/tracing_v3/hooks.py +6 -4
- synth_ai/tracing_v3/llm_call_record_helpers.py +6 -6
- synth_ai/tracing_v3/replica_sync.py +1 -0
- synth_ai/tracing_v3/session_tracer.py +63 -16
- synth_ai/tracing_v3/storage/base.py +89 -1
- synth_ai/tracing_v3/storage/config.py +21 -8
- synth_ai/tracing_v3/storage/factory.py +10 -8
- synth_ai/tracing_v3/storage/utils.py +4 -2
- synth_ai/tracing_v3/turso/daemon.py +7 -2
- synth_ai/tracing_v3/turso/models.py +5 -2
- synth_ai/tracing_v3/turso/native_manager.py +1173 -0
- synth_ai/tracing_v3/utils.py +4 -3
- synth_ai/v0/api/__init__.py +8 -0
- synth_ai/v0/api/models/__init__.py +8 -0
- synth_ai/v0/api/models/supported.py +8 -0
- synth_ai/v0/config/__init__.py +15 -0
- synth_ai/v0/config/base_url.py +12 -0
- synth_ai/v0/lm/__init__.py +51 -0
- synth_ai/{lm → v0/lm}/caching/ephemeral.py +3 -5
- synth_ai/{lm → v0/lm}/caching/handler.py +4 -4
- synth_ai/{lm → v0/lm}/caching/initialize.py +1 -1
- synth_ai/{lm → v0/lm}/caching/persistent.py +1 -1
- synth_ai/{lm → v0/lm}/config.py +6 -1
- synth_ai/{lm → v0/lm}/core/all.py +9 -9
- synth_ai/{lm → v0/lm}/core/exceptions.py +0 -2
- synth_ai/{lm → v0/lm}/core/main.py +19 -7
- synth_ai/{lm → v0/lm}/core/main_v3.py +10 -10
- synth_ai/{lm → v0/lm}/core/synth_models.py +2 -15
- synth_ai/{lm → v0/lm}/core/vendor_clients.py +6 -4
- synth_ai/{lm → v0/lm}/overrides.py +4 -4
- synth_ai/{lm → v0/lm}/provider_support/anthropic.py +4 -4
- synth_ai/{lm → v0/lm}/provider_support/openai.py +5 -5
- synth_ai/{lm → v0/lm}/structured_outputs/handler.py +5 -5
- synth_ai/{lm → v0/lm}/structured_outputs/rehabilitate.py +1 -1
- synth_ai/{lm → v0/lm}/vendors/core/anthropic_api.py +16 -16
- synth_ai/{lm → v0/lm}/vendors/core/gemini_api.py +5 -5
- synth_ai/{lm → v0/lm}/vendors/core/mistral_api.py +5 -5
- synth_ai/{lm → v0/lm}/vendors/core/openai_api.py +12 -10
- synth_ai/{lm → v0/lm}/vendors/openai_standard.py +11 -9
- synth_ai/{lm → v0/lm}/vendors/openai_standard_responses.py +8 -5
- synth_ai/{lm → v0/lm}/vendors/supported/custom_endpoint.py +4 -6
- synth_ai/{lm → v0/lm}/vendors/supported/deepseek.py +2 -2
- synth_ai/{lm → v0/lm}/vendors/supported/grok.py +2 -2
- synth_ai/{lm → v0/lm}/vendors/supported/groq.py +1 -1
- synth_ai/{lm → v0/lm}/vendors/supported/ollama.py +1 -1
- synth_ai/{lm → v0/lm}/vendors/supported/openrouter.py +3 -3
- synth_ai/{lm → v0/lm}/vendors/supported/together.py +1 -1
- synth_ai/{lm → v0/lm}/vendors/synth_client.py +38 -11
- synth_ai/v0/tracing/upload.py +32 -135
- synth_ai/v0/tracing_v3/__init__.py +10 -0
- synth_ai/v0/tracing_v3/abstractions.py +3 -0
- synth_ai/v0/tracing_v3/decorators.py +3 -0
- synth_ai/v0/tracing_v3/llm_call_record_helpers.py +3 -0
- synth_ai/v0/tracing_v3/session_tracer.py +3 -0
- synth_ai-0.2.9.dev6.dist-info/METADATA +191 -0
- {synth_ai-0.2.9.dev4.dist-info → synth_ai-0.2.9.dev6.dist-info}/RECORD +291 -264
- {synth_ai-0.2.9.dev4.dist-info → synth_ai-0.2.9.dev6.dist-info}/top_level.txt +1 -0
- examples/common_old/backend.py +0 -21
- examples/evals_old/README.md +0 -98
- examples/evals_old/__init__.py +0 -6
- examples/evals_old/compare_models.py +0 -1037
- examples/evals_old/example_log.md +0 -145
- examples/evals_old/run_demo.sh +0 -126
- examples/evals_old/trace_analysis.py +0 -270
- examples/finetuning_old/_backup_synth_qwen/config.toml +0 -29
- examples/finetuning_old/_backup_synth_qwen/example_log.md +0 -324
- examples/finetuning_old/_backup_synth_qwen/filter_traces.py +0 -60
- examples/finetuning_old/_backup_synth_qwen/filter_traces_achievements.py +0 -239
- examples/finetuning_old/_backup_synth_qwen/purge_v3_traces.py +0 -109
- examples/finetuning_old/_backup_synth_qwen/react_agent_lm.py +0 -1924
- examples/finetuning_old/_backup_synth_qwen/readme.md +0 -49
- examples/finetuning_old/_backup_synth_qwen/run_crafter_qwen4b.py +0 -114
- examples/finetuning_old/_backup_synth_qwen/run_demo.sh +0 -195
- examples/finetuning_old/_backup_synth_qwen/sft_kickoff.py +0 -118
- examples/finetuning_old/synth_qwen_v1/README.md +0 -68
- examples/finetuning_old/synth_qwen_v1/filter_traces.py +0 -60
- examples/finetuning_old/synth_qwen_v1/filter_traces_achievements.py +0 -239
- examples/finetuning_old/synth_qwen_v1/finetune.py +0 -46
- examples/finetuning_old/synth_qwen_v1/hello_ft_model.py +0 -71
- examples/finetuning_old/synth_qwen_v1/infer.py +0 -37
- examples/finetuning_old/synth_qwen_v1/poll.py +0 -44
- examples/finetuning_old/synth_qwen_v1/prepare_data.py +0 -35
- examples/finetuning_old/synth_qwen_v1/purge_v3_traces.py +0 -109
- examples/finetuning_old/synth_qwen_v1/react_agent_lm.py +0 -1932
- examples/finetuning_old/synth_qwen_v1/run_crafter_sft_job.py +0 -207
- examples/finetuning_old/synth_qwen_v1/run_ft_job.py +0 -232
- examples/finetuning_old/synth_qwen_v1/upload_data.py +0 -34
- examples/finetuning_old/synth_qwen_v1/util.py +0 -147
- examples/rl_old/task_app.py +0 -962
- examples/warming_up_to_rl/old/event_rewards.md +0 -234
- examples/warming_up_to_rl/old/notes.md +0 -73
- examples/warming_up_to_rl/task_app/synth_envs_hosted/test_stepwise_rewards.py +0 -58
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/filter_traces_sft_turso.py +0 -738
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/filter_traces_sft_turso.py +0 -580
- synth_ai/environments/examples/sokoban/units/astar_common.py +0 -95
- synth_ai/experimental/synth_oss.py +0 -446
- synth_ai/install_sqld.sh +0 -40
- synth_ai/learning/filtering.py +0 -0
- synth_ai/learning/offline/dpo.py +0 -0
- synth_ai/learning/offline/providers.py +0 -7
- synth_ai/learning/offline/sft.py +0 -0
- synth_ai/learning/offline/shared.py +0 -0
- synth_ai/learning/online/grpo.py +0 -0
- synth_ai/learning/online/irft.py +0 -0
- synth_ai/learning/prompts/banking77_injection_eval.py +0 -168
- synth_ai/learning/prompts/gepa.py +0 -0
- synth_ai/learning/prompts/hello_world_in_context_injection_ex.py +0 -213
- synth_ai/learning/prompts/mipro.py +0 -289
- synth_ai/learning/prompts/random_search.py +0 -246
- synth_ai/learning/prompts/run_mipro_banking77.py +0 -172
- synth_ai/learning/prompts/run_random_search_banking77.py +0 -324
- synth_ai/rl/secrets.py +0 -19
- synth_ai/scripts/verify_rewards.py +0 -100
- synth_ai/tracing/__init__.py +0 -30
- synth_ai/tracing_v1/__init__.py +0 -33
- synth_ai/tracing_v3/turso/__init__.py +0 -25
- synth_ai/tracing_v3/turso/manager.py +0 -774
- synth_ai/zyk/__init__.py +0 -30
- synth_ai-0.2.9.dev4.dist-info/METADATA +0 -131
- /synth_ai/{lm → v0/lm}/caching/__init__.py +0 -0
- /synth_ai/{lm → v0/lm}/caching/constants.py +0 -0
- /synth_ai/{lm → v0/lm}/caching/dbs.py +0 -0
- /synth_ai/{lm → v0/lm}/constants.py +0 -0
- /synth_ai/{lm → v0/lm}/core/__init__.py +0 -0
- /synth_ai/{lm → v0/lm}/cost/__init__.py +0 -0
- /synth_ai/{lm → v0/lm}/cost/monitor.py +0 -0
- /synth_ai/{lm → v0/lm}/cost/statefulness.py +0 -0
- /synth_ai/{lm → v0/lm}/injection.py +0 -0
- /synth_ai/{lm → v0/lm}/provider_support/__init__.py +0 -0
- /synth_ai/{lm → v0/lm}/provider_support/suppress_logging.py +0 -0
- /synth_ai/{lm → v0/lm}/structured_outputs/__init__.py +0 -0
- /synth_ai/{lm → v0/lm}/structured_outputs/inject.py +0 -0
- /synth_ai/{lm → v0/lm}/tools/__init__.py +0 -0
- /synth_ai/{lm → v0/lm}/tools/base.py +0 -0
- /synth_ai/{lm → v0/lm}/unified_interface.py +0 -0
- /synth_ai/{lm → v0/lm}/vendors/__init__.py +0 -0
- /synth_ai/{lm → v0/lm}/vendors/base.py +0 -0
- /synth_ai/{lm → v0/lm}/vendors/core/__init__.py +0 -0
- /synth_ai/{lm → v0/lm}/vendors/core/synth_dev_api.py +0 -0
- /synth_ai/{lm → v0/lm}/vendors/local/__init__.py +0 -0
- /synth_ai/{lm → v0/lm}/vendors/local/ollama.py +0 -0
- /synth_ai/{lm → v0/lm}/vendors/retries.py +0 -0
- /synth_ai/{lm → v0/lm}/vendors/supported/__init__.py +0 -0
- /synth_ai/{lm → v0/lm}/warmup.py +0 -0
- {synth_ai-0.2.9.dev4.dist-info → synth_ai-0.2.9.dev6.dist-info}/WHEEL +0 -0
- {synth_ai-0.2.9.dev4.dist-info → synth_ai-0.2.9.dev6.dist-info}/entry_points.txt +0 -0
- {synth_ai-0.2.9.dev4.dist-info → synth_ai-0.2.9.dev6.dist-info}/licenses/LICENSE +0 -0
synth_ai/jobs/client.py
CHANGED
|
@@ -1,22 +1,36 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import Any
|
|
3
|
+
from typing import Any
|
|
4
4
|
|
|
5
|
+
from synth_ai.api.models.supported import normalize_model_identifier
|
|
5
6
|
from synth_ai.http import AsyncHttpClient
|
|
7
|
+
from synth_ai.learning.sft.config import prepare_sft_job_payload
|
|
6
8
|
|
|
7
9
|
|
|
8
10
|
class FilesApi:
|
|
9
11
|
def __init__(self, http: AsyncHttpClient) -> None:
|
|
10
12
|
self._http = http
|
|
11
13
|
|
|
12
|
-
async def upload(
|
|
14
|
+
async def upload(
|
|
15
|
+
self,
|
|
16
|
+
*,
|
|
17
|
+
filename: str,
|
|
18
|
+
content: bytes,
|
|
19
|
+
purpose: str,
|
|
20
|
+
content_type: str | None = None,
|
|
21
|
+
idempotency_key: str | None = None,
|
|
22
|
+
) -> dict[str, Any]:
|
|
13
23
|
data = {"purpose": purpose}
|
|
14
24
|
files = {"file": (filename, content, content_type)}
|
|
15
25
|
headers = {"Idempotency-Key": idempotency_key} if idempotency_key else None
|
|
16
|
-
return await self._http.post_multipart(
|
|
26
|
+
return await self._http.post_multipart(
|
|
27
|
+
"/api/files", data=data, files=files, headers=headers
|
|
28
|
+
)
|
|
17
29
|
|
|
18
|
-
async def list(
|
|
19
|
-
|
|
30
|
+
async def list(
|
|
31
|
+
self, *, purpose: str | None = None, after: str | None = None, limit: int = 20
|
|
32
|
+
) -> dict[str, Any]:
|
|
33
|
+
params: dict[str, Any] = {}
|
|
20
34
|
if purpose is not None:
|
|
21
35
|
params["purpose"] = purpose
|
|
22
36
|
if after is not None:
|
|
@@ -24,14 +38,16 @@ class FilesApi:
|
|
|
24
38
|
params["limit"] = limit
|
|
25
39
|
return await self._http.get("/api/files", params=params)
|
|
26
40
|
|
|
27
|
-
async def retrieve(self, file_id: str) ->
|
|
41
|
+
async def retrieve(self, file_id: str) -> dict[str, Any]:
|
|
28
42
|
return await self._http.get(f"/api/files/{file_id}")
|
|
29
43
|
|
|
30
44
|
async def delete(self, file_id: str) -> Any:
|
|
31
45
|
return await self._http.delete(f"/api/files/{file_id}")
|
|
32
46
|
|
|
33
|
-
async def list_jobs(
|
|
34
|
-
|
|
47
|
+
async def list_jobs(
|
|
48
|
+
self, file_id: str, *, after: str | None = None, limit: int = 20
|
|
49
|
+
) -> dict[str, Any]:
|
|
50
|
+
params: dict[str, Any] = {"limit": limit}
|
|
35
51
|
if after is not None:
|
|
36
52
|
params["after"] = after
|
|
37
53
|
return await self._http.get(f"/api/files/{file_id}/jobs", params=params)
|
|
@@ -46,42 +62,40 @@ class SftJobsApi:
|
|
|
46
62
|
*,
|
|
47
63
|
training_file: str,
|
|
48
64
|
model: str,
|
|
49
|
-
validation_file:
|
|
50
|
-
hyperparameters:
|
|
51
|
-
suffix:
|
|
52
|
-
integrations:
|
|
53
|
-
metadata:
|
|
54
|
-
idempotency_key:
|
|
55
|
-
) ->
|
|
56
|
-
payload
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if metadata is not None:
|
|
69
|
-
payload["metadata"] = metadata
|
|
65
|
+
validation_file: str | None = None,
|
|
66
|
+
hyperparameters: dict[str, Any] | None = None,
|
|
67
|
+
suffix: str | None = None,
|
|
68
|
+
integrations: dict[str, Any] | None = None,
|
|
69
|
+
metadata: dict[str, Any] | None = None,
|
|
70
|
+
idempotency_key: str | None = None,
|
|
71
|
+
) -> dict[str, Any]:
|
|
72
|
+
payload = prepare_sft_job_payload(
|
|
73
|
+
model=model,
|
|
74
|
+
training_file=training_file,
|
|
75
|
+
hyperparameters=hyperparameters,
|
|
76
|
+
metadata=metadata,
|
|
77
|
+
training_type=None,
|
|
78
|
+
validation_file=validation_file,
|
|
79
|
+
suffix=suffix,
|
|
80
|
+
integrations=integrations,
|
|
81
|
+
training_file_field="training_file",
|
|
82
|
+
require_training_file=True,
|
|
83
|
+
)
|
|
70
84
|
headers = {"Idempotency-Key": idempotency_key} if idempotency_key else None
|
|
71
85
|
return await self._http.post_json("/api/sft/jobs", json=payload, headers=headers)
|
|
72
86
|
|
|
73
87
|
async def list(
|
|
74
88
|
self,
|
|
75
89
|
*,
|
|
76
|
-
status:
|
|
77
|
-
model:
|
|
78
|
-
file_id:
|
|
79
|
-
created_after:
|
|
80
|
-
created_before:
|
|
81
|
-
after:
|
|
90
|
+
status: str | None = None,
|
|
91
|
+
model: str | None = None,
|
|
92
|
+
file_id: str | None = None,
|
|
93
|
+
created_after: int | None = None,
|
|
94
|
+
created_before: int | None = None,
|
|
95
|
+
after: str | None = None,
|
|
82
96
|
limit: int = 20,
|
|
83
|
-
) ->
|
|
84
|
-
params:
|
|
97
|
+
) -> dict[str, Any]:
|
|
98
|
+
params: dict[str, Any] = {"limit": limit}
|
|
85
99
|
if status is not None:
|
|
86
100
|
params["status"] = status
|
|
87
101
|
if model is not None:
|
|
@@ -96,18 +110,22 @@ class SftJobsApi:
|
|
|
96
110
|
params["after"] = after
|
|
97
111
|
return await self._http.get("/api/sft/jobs", params=params)
|
|
98
112
|
|
|
99
|
-
async def retrieve(self, job_id: str) ->
|
|
113
|
+
async def retrieve(self, job_id: str) -> dict[str, Any]:
|
|
100
114
|
return await self._http.get(f"/api/sft/jobs/{job_id}")
|
|
101
115
|
|
|
102
|
-
async def cancel(self, job_id: str) ->
|
|
116
|
+
async def cancel(self, job_id: str) -> dict[str, Any]:
|
|
103
117
|
return await self._http.post_json(f"/api/sft/jobs/{job_id}/cancel", json={})
|
|
104
118
|
|
|
105
|
-
async def list_events(
|
|
119
|
+
async def list_events(
|
|
120
|
+
self, job_id: str, *, since_seq: int = 0, limit: int = 200
|
|
121
|
+
) -> dict[str, Any]:
|
|
106
122
|
params = {"since_seq": since_seq, "limit": limit}
|
|
107
123
|
return await self._http.get(f"/api/sft/jobs/{job_id}/events", params=params)
|
|
108
124
|
|
|
109
|
-
async def checkpoints(
|
|
110
|
-
|
|
125
|
+
async def checkpoints(
|
|
126
|
+
self, job_id: str, *, after: str | None = None, limit: int = 10
|
|
127
|
+
) -> dict[str, Any]:
|
|
128
|
+
params: dict[str, Any] = {"limit": limit}
|
|
111
129
|
if after is not None:
|
|
112
130
|
params["after"] = after
|
|
113
131
|
return await self._http.get(f"/api/sft/jobs/{job_id}/checkpoints", params=params)
|
|
@@ -123,14 +141,14 @@ class RlJobsApi:
|
|
|
123
141
|
model: str,
|
|
124
142
|
endpoint_base_url: str,
|
|
125
143
|
trainer_id: str,
|
|
126
|
-
trainer:
|
|
127
|
-
job_config_id:
|
|
128
|
-
config:
|
|
129
|
-
metadata:
|
|
130
|
-
idempotency_key:
|
|
131
|
-
) ->
|
|
132
|
-
payload:
|
|
133
|
-
"model": model,
|
|
144
|
+
trainer: dict[str, Any] | None = None,
|
|
145
|
+
job_config_id: str | None = None,
|
|
146
|
+
config: dict[str, Any] | None = None,
|
|
147
|
+
metadata: dict[str, Any] | None = None,
|
|
148
|
+
idempotency_key: str | None = None,
|
|
149
|
+
) -> dict[str, Any]:
|
|
150
|
+
payload: dict[str, Any] = {
|
|
151
|
+
"model": normalize_model_identifier(model),
|
|
134
152
|
"endpoint_base_url": endpoint_base_url,
|
|
135
153
|
"trainer_id": trainer_id,
|
|
136
154
|
}
|
|
@@ -148,14 +166,14 @@ class RlJobsApi:
|
|
|
148
166
|
async def list(
|
|
149
167
|
self,
|
|
150
168
|
*,
|
|
151
|
-
status:
|
|
152
|
-
model:
|
|
153
|
-
created_after:
|
|
154
|
-
created_before:
|
|
155
|
-
after:
|
|
169
|
+
status: str | None = None,
|
|
170
|
+
model: str | None = None,
|
|
171
|
+
created_after: int | None = None,
|
|
172
|
+
created_before: int | None = None,
|
|
173
|
+
after: str | None = None,
|
|
156
174
|
limit: int = 20,
|
|
157
|
-
) ->
|
|
158
|
-
params:
|
|
175
|
+
) -> dict[str, Any]:
|
|
176
|
+
params: dict[str, Any] = {"limit": limit}
|
|
159
177
|
if status is not None:
|
|
160
178
|
params["status"] = status
|
|
161
179
|
if model is not None:
|
|
@@ -168,17 +186,21 @@ class RlJobsApi:
|
|
|
168
186
|
params["after"] = after
|
|
169
187
|
return await self._http.get("/api/rl/jobs", params=params)
|
|
170
188
|
|
|
171
|
-
async def retrieve(self, job_id: str) ->
|
|
189
|
+
async def retrieve(self, job_id: str) -> dict[str, Any]:
|
|
172
190
|
return await self._http.get(f"/api/rl/jobs/{job_id}")
|
|
173
191
|
|
|
174
|
-
async def cancel(self, job_id: str) ->
|
|
192
|
+
async def cancel(self, job_id: str) -> dict[str, Any]:
|
|
175
193
|
return await self._http.post_json(f"/api/rl/jobs/{job_id}/cancel", json={})
|
|
176
194
|
|
|
177
|
-
async def list_events(
|
|
195
|
+
async def list_events(
|
|
196
|
+
self, job_id: str, *, since_seq: int = 0, limit: int = 200
|
|
197
|
+
) -> dict[str, Any]:
|
|
178
198
|
params = {"since_seq": since_seq, "limit": limit}
|
|
179
199
|
return await self._http.get(f"/api/rl/jobs/{job_id}/events", params=params)
|
|
180
200
|
|
|
181
|
-
async def metrics(
|
|
201
|
+
async def metrics(
|
|
202
|
+
self, job_id: str, *, after_step: int = -1, limit: int = 200
|
|
203
|
+
) -> dict[str, Any]:
|
|
182
204
|
params = {"after_step": after_step, "limit": limit}
|
|
183
205
|
return await self._http.get(f"/api/rl/jobs/{job_id}/metrics", params=params)
|
|
184
206
|
|
|
@@ -190,13 +212,13 @@ class ModelsApi:
|
|
|
190
212
|
async def list(
|
|
191
213
|
self,
|
|
192
214
|
*,
|
|
193
|
-
source:
|
|
194
|
-
base_model:
|
|
195
|
-
status:
|
|
196
|
-
after:
|
|
215
|
+
source: str | None = None,
|
|
216
|
+
base_model: str | None = None,
|
|
217
|
+
status: str | None = None,
|
|
218
|
+
after: str | None = None,
|
|
197
219
|
limit: int = 20,
|
|
198
|
-
) ->
|
|
199
|
-
params:
|
|
220
|
+
) -> dict[str, Any]:
|
|
221
|
+
params: dict[str, Any] = {"limit": limit}
|
|
200
222
|
if source is not None:
|
|
201
223
|
params["source"] = source
|
|
202
224
|
if base_model is not None:
|
|
@@ -207,28 +229,31 @@ class ModelsApi:
|
|
|
207
229
|
params["after"] = after
|
|
208
230
|
return await self._http.get("/api/models", params=params)
|
|
209
231
|
|
|
210
|
-
async def retrieve(self, model_id: str) ->
|
|
232
|
+
async def retrieve(self, model_id: str) -> dict[str, Any]:
|
|
211
233
|
return await self._http.get(f"/api/models/{model_id}")
|
|
212
234
|
|
|
213
235
|
async def delete(self, model_id: str) -> Any:
|
|
214
236
|
return await self._http.delete(f"/api/models/{model_id}")
|
|
215
237
|
|
|
216
|
-
async def list_jobs(
|
|
217
|
-
|
|
238
|
+
async def list_jobs(
|
|
239
|
+
self, model_id: str, *, after: str | None = None, limit: int = 20
|
|
240
|
+
) -> dict[str, Any]:
|
|
241
|
+
params: dict[str, Any] = {"limit": limit}
|
|
218
242
|
if after is not None:
|
|
219
243
|
params["after"] = after
|
|
220
244
|
return await self._http.get(f"/api/models/{model_id}/jobs", params=params)
|
|
221
245
|
|
|
222
246
|
|
|
223
247
|
class JobsClient:
|
|
224
|
-
"""High-level client aggregating job APIs.
|
|
248
|
+
"""High-level client aggregating job APIs."""
|
|
225
249
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
250
|
+
def __init__(
|
|
251
|
+
self,
|
|
252
|
+
base_url: str,
|
|
253
|
+
api_key: str,
|
|
254
|
+
timeout: float = 30.0,
|
|
255
|
+
http: AsyncHttpClient | None = None,
|
|
256
|
+
) -> None:
|
|
232
257
|
self._base_url = base_url
|
|
233
258
|
self._api_key = api_key
|
|
234
259
|
self._timeout = timeout
|
|
@@ -238,7 +263,7 @@ class JobsClient:
|
|
|
238
263
|
self.rl = RlJobsApi(self._http)
|
|
239
264
|
self.models = ModelsApi(self._http)
|
|
240
265
|
|
|
241
|
-
async def __aenter__(self) ->
|
|
266
|
+
async def __aenter__(self) -> JobsClient:
|
|
242
267
|
await self._http.__aenter__()
|
|
243
268
|
return self
|
|
244
269
|
|
synth_ai/learning/__init__.py
CHANGED
|
@@ -1,16 +1,51 @@
|
|
|
1
|
+
from synth_ai.task import task_app_health, validate_task_app_url
|
|
2
|
+
|
|
1
3
|
from .client import LearningClient
|
|
2
|
-
from .
|
|
3
|
-
from .ft_client import FtClient
|
|
4
|
-
from .validators import validate_training_jsonl, validate_trainer_cfg_rl
|
|
5
|
-
from synth_ai.task import validate_task_app_url, task_app_health
|
|
6
|
-
from .health import backend_health, pricing_preflight, balance_autumn_normalized
|
|
7
|
-
from .sse import stream_events as stream_job_events
|
|
4
|
+
from .health import backend_health, balance_autumn_normalized, pricing_preflight
|
|
8
5
|
from .jobs import JobHandle, JobsApiResolver
|
|
6
|
+
from .rl import (
|
|
7
|
+
MAX_ENVIRONMENT_API_KEY_BYTES,
|
|
8
|
+
RlClient,
|
|
9
|
+
RLJobConfig,
|
|
10
|
+
RolloutEnvSpec,
|
|
11
|
+
RolloutMetrics,
|
|
12
|
+
RolloutPolicySpec,
|
|
13
|
+
RolloutRecordConfig,
|
|
14
|
+
RolloutRequest,
|
|
15
|
+
RolloutResponse,
|
|
16
|
+
RolloutSafetyConfig,
|
|
17
|
+
RolloutStep,
|
|
18
|
+
RolloutTrajectory,
|
|
19
|
+
encrypt_for_backend,
|
|
20
|
+
mint_environment_api_key,
|
|
21
|
+
setup_environment_api_key,
|
|
22
|
+
)
|
|
23
|
+
from .sft import FtClient
|
|
24
|
+
from .sft.config import SFTJobConfig, prepare_sft_job_payload
|
|
25
|
+
from .sse import stream_events as stream_job_events
|
|
26
|
+
from .validators import validate_trainer_cfg_rl, validate_training_jsonl
|
|
9
27
|
|
|
10
28
|
__all__ = [
|
|
11
29
|
"LearningClient",
|
|
12
30
|
"RlClient",
|
|
31
|
+
"RLJobConfig",
|
|
13
32
|
"FtClient",
|
|
33
|
+
"SFTJobConfig",
|
|
34
|
+
"prepare_sft_job_payload",
|
|
35
|
+
"RolloutEnvSpec",
|
|
36
|
+
"RolloutPolicySpec",
|
|
37
|
+
"RolloutRecordConfig",
|
|
38
|
+
"RolloutSafetyConfig",
|
|
39
|
+
"RolloutRequest",
|
|
40
|
+
"RolloutStep",
|
|
41
|
+
"RolloutTrajectory",
|
|
42
|
+
"RolloutMetrics",
|
|
43
|
+
"RolloutResponse",
|
|
44
|
+
"mint_environment_api_key",
|
|
45
|
+
"encrypt_for_backend",
|
|
46
|
+
"setup_environment_api_key",
|
|
47
|
+
"MAX_ENVIRONMENT_API_KEY_BYTES",
|
|
48
|
+
# convenience re-export for typing
|
|
14
49
|
"validate_training_jsonl",
|
|
15
50
|
"validate_trainer_cfg_rl",
|
|
16
51
|
"validate_task_app_url",
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# class LearningModality(str, enum.Enum):
|
|
2
|
+
# """Modality of learning."""
|
|
3
|
+
|
|
4
|
+
# online_on_policy = "online_on_policy"
|
|
5
|
+
# online_off_policy = "online_off_policy"
|
|
6
|
+
# offline = "offline"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# class LearningAlgorithm(str, enum.Enum):
|
|
10
|
+
# """Algorithm of learning."""
|
|
11
|
+
|
|
12
|
+
# gspo = "gspo"
|
|
13
|
+
# reinforce = "reinforce"
|
|
14
|
+
# sft = "sft"
|
synth_ai/learning/client.py
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from contextlib import suppress
|
|
3
5
|
from pathlib import Path
|
|
4
|
-
from typing import Any,
|
|
6
|
+
from typing import Any, TypedDict
|
|
7
|
+
|
|
8
|
+
from synth_ai.api.models.supported import (
|
|
9
|
+
UnsupportedModelError,
|
|
10
|
+
normalize_model_identifier,
|
|
11
|
+
)
|
|
12
|
+
from synth_ai.learning.sft.config import prepare_sft_job_payload
|
|
5
13
|
|
|
6
14
|
from ..http import AsyncHttpClient, HTTPError, sleep
|
|
7
15
|
|
|
@@ -20,7 +28,12 @@ class LearningClient:
|
|
|
20
28
|
files = {"file": (p.name, content, _infer_content_type(p.name))}
|
|
21
29
|
js = await http.post_multipart("/api/learning/files", data=data, files=files)
|
|
22
30
|
if not isinstance(js, dict) or "id" not in js:
|
|
23
|
-
raise HTTPError(
|
|
31
|
+
raise HTTPError(
|
|
32
|
+
status=500,
|
|
33
|
+
url="/api/learning/files",
|
|
34
|
+
message="invalid_upload_response",
|
|
35
|
+
body_snippet=str(js)[:200],
|
|
36
|
+
)
|
|
24
37
|
return str(js["id"])
|
|
25
38
|
|
|
26
39
|
async def create_job(
|
|
@@ -29,28 +42,56 @@ class LearningClient:
|
|
|
29
42
|
training_type: str,
|
|
30
43
|
model: str,
|
|
31
44
|
training_file_id: str,
|
|
32
|
-
hyperparameters:
|
|
33
|
-
metadata:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
|
|
45
|
+
hyperparameters: dict[str, Any] | None = None,
|
|
46
|
+
metadata: dict[str, Any] | None = None,
|
|
47
|
+
validation_file: str | None = None,
|
|
48
|
+
) -> dict[str, Any]:
|
|
49
|
+
lower_type = (training_type or "").strip().lower()
|
|
50
|
+
require_base = (
|
|
51
|
+
lower_type.startswith("sft")
|
|
52
|
+
or lower_type.startswith("fft")
|
|
53
|
+
or lower_type.startswith("qft")
|
|
54
|
+
)
|
|
55
|
+
try:
|
|
56
|
+
normalized_model = normalize_model_identifier(
|
|
57
|
+
model, allow_finetuned_prefixes=not require_base
|
|
58
|
+
)
|
|
59
|
+
except UnsupportedModelError as exc:
|
|
60
|
+
raise ValueError(str(exc)) from exc
|
|
61
|
+
|
|
62
|
+
if lower_type.startswith("sft") or lower_type in {"fft", "qft"}:
|
|
63
|
+
body = prepare_sft_job_payload(
|
|
64
|
+
model=model,
|
|
65
|
+
training_file=training_file_id,
|
|
66
|
+
hyperparameters=hyperparameters,
|
|
67
|
+
metadata=metadata,
|
|
68
|
+
training_type=training_type or "sft_offline",
|
|
69
|
+
validation_file=validation_file,
|
|
70
|
+
training_file_field="training_file_id",
|
|
71
|
+
require_training_file=True,
|
|
72
|
+
)
|
|
73
|
+
else:
|
|
74
|
+
body = {
|
|
75
|
+
"training_type": training_type,
|
|
76
|
+
"model": normalized_model,
|
|
77
|
+
"training_file_id": training_file_id,
|
|
78
|
+
"hyperparameters": hyperparameters or {},
|
|
79
|
+
"metadata": metadata or {},
|
|
80
|
+
}
|
|
42
81
|
async with AsyncHttpClient(self._base_url, self._api_key, timeout=self._timeout) as http:
|
|
43
82
|
return await http.post_json("/api/learning/jobs", json=body)
|
|
44
83
|
|
|
45
|
-
async def start_job(self, job_id: str) ->
|
|
84
|
+
async def start_job(self, job_id: str) -> dict[str, Any]:
|
|
46
85
|
async with AsyncHttpClient(self._base_url, self._api_key, timeout=self._timeout) as http:
|
|
47
86
|
return await http.post_json(f"/api/learning/jobs/{job_id}/start", json={})
|
|
48
87
|
|
|
49
|
-
async def get_job(self, job_id: str) ->
|
|
88
|
+
async def get_job(self, job_id: str) -> dict[str, Any]:
|
|
50
89
|
async with AsyncHttpClient(self._base_url, self._api_key, timeout=self._timeout) as http:
|
|
51
90
|
return await http.get(f"/api/learning/jobs/{job_id}")
|
|
52
91
|
|
|
53
|
-
async def get_events(
|
|
92
|
+
async def get_events(
|
|
93
|
+
self, job_id: str, *, since_seq: int = 0, limit: int = 200
|
|
94
|
+
) -> list[dict[str, Any]]:
|
|
54
95
|
params = {"since_seq": since_seq, "limit": limit}
|
|
55
96
|
async with AsyncHttpClient(self._base_url, self._api_key, timeout=self._timeout) as http:
|
|
56
97
|
js = await http.get(f"/api/learning/jobs/{job_id}/events", params=params)
|
|
@@ -58,8 +99,16 @@ class LearningClient:
|
|
|
58
99
|
return js["events"]
|
|
59
100
|
return []
|
|
60
101
|
|
|
61
|
-
async def get_metrics(
|
|
62
|
-
|
|
102
|
+
async def get_metrics(
|
|
103
|
+
self,
|
|
104
|
+
job_id: str,
|
|
105
|
+
*,
|
|
106
|
+
name: str | None = None,
|
|
107
|
+
after_step: int | None = None,
|
|
108
|
+
limit: int = 500,
|
|
109
|
+
run_id: str | None = None,
|
|
110
|
+
) -> list[dict[str, Any]]:
|
|
111
|
+
params: dict[str, Any] = {"limit": limit}
|
|
63
112
|
if name is not None:
|
|
64
113
|
params["name"] = name
|
|
65
114
|
if after_step is not None:
|
|
@@ -72,7 +121,7 @@ class LearningClient:
|
|
|
72
121
|
return js["points"]
|
|
73
122
|
return []
|
|
74
123
|
|
|
75
|
-
async def get_timeline(self, job_id: str, *, limit: int = 200) ->
|
|
124
|
+
async def get_timeline(self, job_id: str, *, limit: int = 200) -> list[dict[str, Any]]:
|
|
76
125
|
params = {"limit": limit}
|
|
77
126
|
async with AsyncHttpClient(self._base_url, self._api_key, timeout=self._timeout) as http:
|
|
78
127
|
js = await http.get(f"/api/learning/jobs/{job_id}/timeline", params=params)
|
|
@@ -86,8 +135,8 @@ class LearningClient:
|
|
|
86
135
|
*,
|
|
87
136
|
interval_seconds: float = 2.0,
|
|
88
137
|
max_seconds: float | None = 3600,
|
|
89
|
-
on_event: Callable[[
|
|
90
|
-
) ->
|
|
138
|
+
on_event: Callable[[dict[str, Any]], None] | None = None,
|
|
139
|
+
) -> dict[str, Any]:
|
|
91
140
|
last_seq = 0
|
|
92
141
|
elapsed = 0.0
|
|
93
142
|
while True:
|
|
@@ -97,10 +146,8 @@ class LearningClient:
|
|
|
97
146
|
if isinstance(e, dict) and isinstance(e.get("seq"), int):
|
|
98
147
|
last_seq = max(last_seq, int(e["seq"]))
|
|
99
148
|
if on_event:
|
|
100
|
-
|
|
149
|
+
with suppress(Exception):
|
|
101
150
|
on_event(e)
|
|
102
|
-
except Exception:
|
|
103
|
-
pass
|
|
104
151
|
|
|
105
152
|
# Status
|
|
106
153
|
job = await self.get_job(job_id)
|
|
@@ -115,7 +162,9 @@ class LearningClient:
|
|
|
115
162
|
raise TimeoutError(f"Polling timed out after {elapsed} seconds for job {job_id}")
|
|
116
163
|
|
|
117
164
|
# --- Optional diagnostics ---
|
|
118
|
-
async def pricing_preflight(
|
|
165
|
+
async def pricing_preflight(
|
|
166
|
+
self, *, job_type: str, gpu_type: str, estimated_seconds: float, container_count: int
|
|
167
|
+
) -> dict[str, Any]:
|
|
119
168
|
body = {
|
|
120
169
|
"job_type": job_type,
|
|
121
170
|
"gpu_type": gpu_type,
|
|
@@ -125,17 +174,62 @@ class LearningClient:
|
|
|
125
174
|
async with AsyncHttpClient(self._base_url, self._api_key, timeout=self._timeout) as http:
|
|
126
175
|
js = await http.post_json("/api/v1/pricing/preflight", json=body)
|
|
127
176
|
if not isinstance(js, dict):
|
|
128
|
-
raise HTTPError(
|
|
177
|
+
raise HTTPError(
|
|
178
|
+
status=500,
|
|
179
|
+
url="/api/v1/pricing/preflight",
|
|
180
|
+
message="invalid_preflight_response",
|
|
181
|
+
body_snippet=str(js)[:200],
|
|
182
|
+
)
|
|
129
183
|
return js
|
|
130
184
|
|
|
131
|
-
async def balance_autumn_normalized(self) ->
|
|
185
|
+
async def balance_autumn_normalized(self) -> dict[str, Any]:
|
|
132
186
|
async with AsyncHttpClient(self._base_url, self._api_key, timeout=self._timeout) as http:
|
|
133
187
|
js = await http.get("/api/v1/balance/autumn-normalized")
|
|
134
188
|
if not isinstance(js, dict):
|
|
135
|
-
raise HTTPError(
|
|
189
|
+
raise HTTPError(
|
|
190
|
+
status=500,
|
|
191
|
+
url="/api/v1/balance/autumn-normalized",
|
|
192
|
+
message="invalid_balance_response",
|
|
193
|
+
body_snippet=str(js)[:200],
|
|
194
|
+
)
|
|
136
195
|
return js
|
|
137
196
|
|
|
138
197
|
|
|
198
|
+
class FineTunedModelInfo(TypedDict, total=False):
|
|
199
|
+
id: str
|
|
200
|
+
base_model: str | None
|
|
201
|
+
created_at: int | None
|
|
202
|
+
job_id: str | None
|
|
203
|
+
status: str | None
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class LearningClient(LearningClient): # type: ignore[misc]
|
|
207
|
+
async def list_fine_tuned_models(self) -> list[FineTunedModelInfo]:
|
|
208
|
+
"""Return completed fine‑tuned models for the caller's organization.
|
|
209
|
+
|
|
210
|
+
Calls backend route `/api/learning/models` and returns a compact list.
|
|
211
|
+
"""
|
|
212
|
+
async with AsyncHttpClient(self._base_url, self._api_key, timeout=self._timeout) as http:
|
|
213
|
+
js = await http.get("/api/learning/models")
|
|
214
|
+
if isinstance(js, dict) and isinstance(js.get("data"), list):
|
|
215
|
+
out: list[FineTunedModelInfo] = []
|
|
216
|
+
for item in js["data"]:
|
|
217
|
+
if not isinstance(item, dict):
|
|
218
|
+
continue
|
|
219
|
+
rec: FineTunedModelInfo = {
|
|
220
|
+
"id": str(item.get("id")),
|
|
221
|
+
"base_model": item.get("base_model"),
|
|
222
|
+
"created_at": item.get("created_at"),
|
|
223
|
+
"job_id": item.get("job_id"),
|
|
224
|
+
"status": item.get("status"),
|
|
225
|
+
}
|
|
226
|
+
if rec.get("id"):
|
|
227
|
+
out.append(rec)
|
|
228
|
+
return out
|
|
229
|
+
# Fallback: empty list on unexpected shape
|
|
230
|
+
return []
|
|
231
|
+
|
|
232
|
+
|
|
139
233
|
def _infer_content_type(filename: str) -> str:
|
|
140
234
|
name = filename.lower()
|
|
141
235
|
if name.endswith(".jsonl"):
|
|
@@ -145,5 +239,3 @@ def _infer_content_type(filename: str) -> str:
|
|
|
145
239
|
if name.endswith(".txt"):
|
|
146
240
|
return "text/plain"
|
|
147
241
|
return "application/octet-stream"
|
|
148
|
-
|
|
149
|
-
|
synth_ai/learning/config.py
CHANGED
|
@@ -1,43 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from
|
|
4
|
-
from typing import Any, Dict, Optional
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
@dataclass
|
|
8
|
-
class FTJobConfig:
|
|
9
|
-
model: str
|
|
10
|
-
training_file_id: str
|
|
11
|
-
n_epochs: int = 1
|
|
12
|
-
batch_size: int = 1
|
|
13
|
-
upload_to_wasabi: bool = True
|
|
14
|
-
|
|
15
|
-
def hyperparameters(self) -> Dict[str, Any]:
|
|
16
|
-
if self.n_epochs < 1:
|
|
17
|
-
raise ValueError("n_epochs must be >= 1")
|
|
18
|
-
if self.batch_size < 1:
|
|
19
|
-
raise ValueError("batch_size must be >= 1")
|
|
20
|
-
return {"n_epochs": int(self.n_epochs), "batch_size": int(self.batch_size)}
|
|
21
|
-
|
|
22
|
-
def metadata(self) -> Dict[str, Any]: # type: ignore[override]
|
|
23
|
-
return {"upload_to_wasabi": bool(self.upload_to_wasabi)}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
@dataclass
|
|
27
|
-
class RLJobConfig:
|
|
28
|
-
model: str
|
|
29
|
-
task_app_url: str
|
|
30
|
-
trainer_id: str
|
|
31
|
-
batch_size: int = 1
|
|
32
|
-
group_size: int = 2
|
|
33
|
-
job_config_id: Optional[str] = None
|
|
34
|
-
inline_config: Optional[Dict[str, Any]] = None
|
|
35
|
-
|
|
36
|
-
def trainer_dict(self) -> Dict[str, Any]:
|
|
37
|
-
if self.batch_size < 1:
|
|
38
|
-
raise ValueError("batch_size must be >= 1")
|
|
39
|
-
if self.group_size < 2:
|
|
40
|
-
raise ValueError("group_size must be >= 2")
|
|
41
|
-
return {"batch_size": int(self.batch_size), "group_size": int(self.group_size)}
|
|
42
|
-
|
|
3
|
+
from .rl.config import RLJobConfig
|
|
43
4
|
|
|
5
|
+
__all__ = ["RLJobConfig"]
|