synth-ai 0.2.9.dev5__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 +1699 -259
- 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.dev5.dist-info → synth_ai-0.2.9.dev6.dist-info}/RECORD +291 -262
- {synth_ai-0.2.9.dev5.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
- 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/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.dev5.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.dev5.dist-info → synth_ai-0.2.9.dev6.dist-info}/WHEEL +0 -0
- {synth_ai-0.2.9.dev5.dist-info → synth_ai-0.2.9.dev6.dist-info}/entry_points.txt +0 -0
- {synth_ai-0.2.9.dev5.dist-info → synth_ai-0.2.9.dev6.dist-info}/licenses/LICENSE +0 -0
synth_ai/task/errors.py
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
1
|
"""Error helpers used across Task App implementations."""
|
|
4
2
|
|
|
5
|
-
from
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
6
|
|
|
7
7
|
from .json import to_jsonable
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
def error_payload(
|
|
11
|
-
|
|
10
|
+
def error_payload(
|
|
11
|
+
code: str, message: str, *, extra: dict[str, Any] | None = None
|
|
12
|
+
) -> dict[str, Any]:
|
|
13
|
+
payload: dict[str, Any] = {"error": {"code": code, "message": message}}
|
|
12
14
|
if extra:
|
|
13
15
|
payload["error"].update(extra)
|
|
14
16
|
return payload
|
|
@@ -19,8 +21,8 @@ def http_exception(
|
|
|
19
21
|
code: str,
|
|
20
22
|
message: str,
|
|
21
23
|
*,
|
|
22
|
-
extra:
|
|
23
|
-
headers:
|
|
24
|
+
extra: dict[str, Any] | None = None,
|
|
25
|
+
headers: dict[str, str] | None = None,
|
|
24
26
|
):
|
|
25
27
|
try:
|
|
26
28
|
from fastapi import HTTPException # type: ignore
|
|
@@ -36,8 +38,8 @@ def json_error_response(
|
|
|
36
38
|
code: str,
|
|
37
39
|
message: str,
|
|
38
40
|
*,
|
|
39
|
-
extra:
|
|
40
|
-
headers:
|
|
41
|
+
extra: dict[str, Any] | None = None,
|
|
42
|
+
headers: dict[str, str] | None = None,
|
|
41
43
|
):
|
|
42
44
|
try:
|
|
43
45
|
from fastapi.responses import JSONResponse # type: ignore
|
|
@@ -46,4 +48,3 @@ def json_error_response(
|
|
|
46
48
|
|
|
47
49
|
payload = error_payload(code, message, extra=extra)
|
|
48
50
|
return JSONResponse(status_code=status_code, content=to_jsonable(payload), headers=headers)
|
|
49
|
-
|
synth_ai/task/health.py
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
"""Helpers for probing Task App health endpoints."""
|
|
2
|
+
|
|
1
3
|
from __future__ import annotations
|
|
2
4
|
|
|
3
|
-
from typing import Any
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
4
7
|
import aiohttp
|
|
5
8
|
|
|
6
9
|
|
|
7
|
-
async def task_app_health(task_app_url: str) ->
|
|
10
|
+
async def task_app_health(task_app_url: str) -> dict[str, Any]:
|
|
8
11
|
"""Probe a Task App base URL for basic reachability.
|
|
9
12
|
|
|
10
13
|
Behavior:
|
|
@@ -12,17 +15,20 @@ async def task_app_health(task_app_url: str) -> Dict[str, Any]:
|
|
|
12
15
|
- Fallback to GET if HEAD is unsupported
|
|
13
16
|
- Returns {ok: bool, status?: int, error?: str}
|
|
14
17
|
"""
|
|
18
|
+
|
|
19
|
+
async def _try_request(session: aiohttp.ClientSession, method: str) -> dict[str, Any] | None:
|
|
20
|
+
request = getattr(session, method)
|
|
21
|
+
async with request(task_app_url, allow_redirects=True) as response:
|
|
22
|
+
if 200 <= response.status < 400:
|
|
23
|
+
return {"ok": True, "status": response.status}
|
|
24
|
+
return None
|
|
25
|
+
|
|
15
26
|
try:
|
|
16
27
|
async with aiohttp.ClientSession() as session:
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
async with session.get(task_app_url, allow_redirects=True) as r2:
|
|
22
|
-
if 200 <= r2.status < 400:
|
|
23
|
-
return {"ok": True, "status": r2.status}
|
|
28
|
+
for method in ("head", "get"):
|
|
29
|
+
result = await _try_request(session, method)
|
|
30
|
+
if result is not None:
|
|
31
|
+
return result
|
|
24
32
|
return {"ok": False, "status": None}
|
|
25
33
|
except Exception as e:
|
|
26
34
|
return {"ok": False, "error": f"{type(e).__name__}: {e}"}
|
|
27
|
-
|
|
28
|
-
|
synth_ai/task/json.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
1
|
"""Shared JSON sanitisation helpers for Task Apps."""
|
|
4
2
|
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
5
|
from collections.abc import Mapping, Sequence
|
|
6
|
-
from dataclasses import
|
|
6
|
+
from dataclasses import asdict, is_dataclass
|
|
7
7
|
from enum import Enum
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
@@ -13,13 +13,19 @@ except Exception: # pragma: no cover - handled at runtime
|
|
|
13
13
|
_np = None # type: ignore
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
def _mask_numpy_array(arr:
|
|
16
|
+
def _mask_numpy_array(arr: Any) -> str:
|
|
17
17
|
shape = getattr(arr, "shape", None)
|
|
18
18
|
dtype = getattr(arr, "dtype", None)
|
|
19
19
|
return f"<ndarray shape={shape} dtype={dtype}>"
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
def to_jsonable(
|
|
22
|
+
def to_jsonable(
|
|
23
|
+
value: Any,
|
|
24
|
+
*,
|
|
25
|
+
_visited: set[int] | None = None,
|
|
26
|
+
_depth: int = 0,
|
|
27
|
+
_max_depth: int = 32,
|
|
28
|
+
) -> Any:
|
|
23
29
|
"""Convert `value` into structures compatible with JSON serialisation.
|
|
24
30
|
|
|
25
31
|
- numpy scalars are converted to their Python counterparts
|
|
@@ -29,25 +35,33 @@ def to_jsonable(value: Any) -> Any:
|
|
|
29
35
|
- non-serialisable objects fall back to `repr`
|
|
30
36
|
"""
|
|
31
37
|
|
|
32
|
-
if
|
|
38
|
+
if _visited is None:
|
|
39
|
+
_visited = set()
|
|
40
|
+
|
|
41
|
+
if _depth > _max_depth:
|
|
42
|
+
return f"<max_depth type={type(value).__name__}>"
|
|
43
|
+
|
|
44
|
+
if value is None or isinstance(value, str | bool | int | float):
|
|
33
45
|
return value
|
|
34
46
|
|
|
35
47
|
# numpy scalars / arrays
|
|
36
48
|
if _np is not None:
|
|
37
|
-
if isinstance(value,
|
|
49
|
+
if isinstance(value, _np.integer):
|
|
38
50
|
return int(value)
|
|
39
|
-
if isinstance(value,
|
|
51
|
+
if isinstance(value, _np.floating):
|
|
40
52
|
return float(value)
|
|
41
|
-
if isinstance(value,
|
|
53
|
+
if isinstance(value, _np.bool_):
|
|
42
54
|
return bool(value)
|
|
43
|
-
if isinstance(value,
|
|
55
|
+
if isinstance(value, _np.ndarray):
|
|
44
56
|
return _mask_numpy_array(value)
|
|
45
57
|
|
|
46
58
|
if isinstance(value, Enum):
|
|
47
|
-
return to_jsonable(value.value)
|
|
59
|
+
return to_jsonable(value.value, _visited=_visited, _depth=_depth + 1, _max_depth=_max_depth)
|
|
48
60
|
|
|
49
61
|
if is_dataclass(value):
|
|
50
|
-
return to_jsonable(
|
|
62
|
+
return to_jsonable(
|
|
63
|
+
asdict(value), _visited=_visited, _depth=_depth + 1, _max_depth=_max_depth
|
|
64
|
+
)
|
|
51
65
|
|
|
52
66
|
# pydantic BaseModel / attrs objects
|
|
53
67
|
for attr in ("model_dump", "dict", "to_dict", "to_json"):
|
|
@@ -56,22 +70,42 @@ def to_jsonable(value: Any) -> Any:
|
|
|
56
70
|
dumped = getattr(value, attr)() # type: ignore[misc]
|
|
57
71
|
except TypeError:
|
|
58
72
|
dumped = getattr(value, attr)(exclude_none=False) # pragma: no cover
|
|
59
|
-
return to_jsonable(
|
|
73
|
+
return to_jsonable(
|
|
74
|
+
dumped, _visited=_visited, _depth=_depth + 1, _max_depth=_max_depth
|
|
75
|
+
)
|
|
60
76
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if isinstance(value, (set, tuple)):
|
|
65
|
-
return [to_jsonable(v) for v in value]
|
|
66
|
-
|
|
67
|
-
if isinstance(value, Sequence) and not isinstance(value, (str, bytes, bytearray)):
|
|
68
|
-
return [to_jsonable(v) for v in value]
|
|
77
|
+
obj_id = id(value)
|
|
78
|
+
if obj_id in _visited:
|
|
79
|
+
return f"<circular type={type(value).__name__}>"
|
|
69
80
|
|
|
70
|
-
if isinstance(value,
|
|
81
|
+
if isinstance(value, Mapping):
|
|
82
|
+
_visited.add(obj_id)
|
|
83
|
+
return {
|
|
84
|
+
str(k): to_jsonable(v, _visited=_visited, _depth=_depth + 1, _max_depth=_max_depth)
|
|
85
|
+
for k, v in value.items()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if isinstance(value, set | tuple):
|
|
89
|
+
_visited.add(obj_id)
|
|
90
|
+
return [
|
|
91
|
+
to_jsonable(v, _visited=_visited, _depth=_depth + 1, _max_depth=_max_depth)
|
|
92
|
+
for v in value
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
if isinstance(value, Sequence) and not isinstance(value, str | bytes | bytearray):
|
|
96
|
+
_visited.add(obj_id)
|
|
97
|
+
return [
|
|
98
|
+
to_jsonable(v, _visited=_visited, _depth=_depth + 1, _max_depth=_max_depth)
|
|
99
|
+
for v in value
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
if isinstance(value, bytes | bytearray):
|
|
71
103
|
return f"<bytes len={len(value)}>"
|
|
72
104
|
|
|
73
105
|
if hasattr(value, "__dict__"):
|
|
74
|
-
|
|
106
|
+
_visited.add(obj_id)
|
|
107
|
+
return to_jsonable(
|
|
108
|
+
vars(value), _visited=_visited, _depth=_depth + 1, _max_depth=_max_depth
|
|
109
|
+
)
|
|
75
110
|
|
|
76
111
|
return repr(value)
|
|
77
|
-
|
synth_ai/task/proxy.py
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
1
|
"""Shared helpers for Task App proxy endpoints (OpenAI, Groq, etc.)."""
|
|
4
2
|
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
5
|
import copy
|
|
6
6
|
import json
|
|
7
7
|
import re
|
|
8
|
-
from
|
|
9
|
-
|
|
8
|
+
from collections.abc import Iterable
|
|
9
|
+
from typing import Any
|
|
10
10
|
|
|
11
|
-
INTERACT_TOOL_SCHEMA:
|
|
11
|
+
INTERACT_TOOL_SCHEMA: list[dict[str, Any]] = [
|
|
12
12
|
{
|
|
13
13
|
"type": "function",
|
|
14
14
|
"function": {
|
|
@@ -80,9 +80,13 @@ def prepare_for_groq(model: str | None, payload: dict[str, Any]) -> dict[str, An
|
|
|
80
80
|
|
|
81
81
|
sanitized = prepare_for_openai(model, payload)
|
|
82
82
|
# Groq supports `max_tokens`; prefer their native parameter when present
|
|
83
|
-
if
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
if (
|
|
84
|
+
model
|
|
85
|
+
and "gpt-5" not in model
|
|
86
|
+
and "max_completion_tokens" in sanitized
|
|
87
|
+
and "max_tokens" not in payload
|
|
88
|
+
):
|
|
89
|
+
sanitized["max_tokens"] = sanitized.pop("max_completion_tokens")
|
|
86
90
|
return sanitized
|
|
87
91
|
|
|
88
92
|
|
|
@@ -146,7 +150,7 @@ def _parse_actions_from_json_candidate(candidate: Any) -> tuple[list[str], str]:
|
|
|
146
150
|
return actions, reasoning
|
|
147
151
|
|
|
148
152
|
|
|
149
|
-
def parse_tool_call_from_text(text: str) ->
|
|
153
|
+
def parse_tool_call_from_text(text: str) -> tuple[list[str], str]:
|
|
150
154
|
"""Derive tool-call actions and reasoning from assistant text."""
|
|
151
155
|
|
|
152
156
|
text = (text or "").strip()
|
|
@@ -179,7 +183,7 @@ def parse_tool_call_from_text(text: str) -> Tuple[list[str], str]:
|
|
|
179
183
|
if m:
|
|
180
184
|
items = [part.strip() for part in m.group(1).split(",") if part.strip()]
|
|
181
185
|
if items:
|
|
182
|
-
reasoning = text[:m.start()].strip()
|
|
186
|
+
reasoning = text[: m.start()].strip()
|
|
183
187
|
return items, reasoning
|
|
184
188
|
|
|
185
189
|
# Patterns like "Action 1: move_right"
|
|
@@ -242,9 +246,7 @@ def synthesize_tool_call_if_missing(openai_response: dict[str, Any]) -> dict[str
|
|
|
242
246
|
return openai_response
|
|
243
247
|
|
|
244
248
|
new_message = copy.deepcopy(message)
|
|
245
|
-
new_message["tool_calls"] = [
|
|
246
|
-
_build_tool_call(actions, reasoning)
|
|
247
|
-
]
|
|
249
|
+
new_message["tool_calls"] = [_build_tool_call(actions, reasoning)]
|
|
248
250
|
if "content" not in new_message:
|
|
249
251
|
new_message["content"] = None
|
|
250
252
|
|
|
@@ -255,4 +257,3 @@ def synthesize_tool_call_if_missing(openai_response: dict[str, Any]) -> dict[str
|
|
|
255
257
|
result = copy.deepcopy(openai_response)
|
|
256
258
|
result["choices"] = new_choices
|
|
257
259
|
return result
|
|
258
|
-
|
synth_ai/task/rubrics.py
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
1
|
"""Rubric schema, loading, and scoring helpers for Task Apps."""
|
|
4
2
|
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
5
|
import json
|
|
6
|
+
from collections.abc import Iterable
|
|
6
7
|
from pathlib import Path
|
|
7
|
-
from typing import Any
|
|
8
|
+
from typing import Any
|
|
8
9
|
|
|
9
10
|
from pydantic import BaseModel, Field, field_validator
|
|
10
11
|
|
|
@@ -48,14 +49,14 @@ class Rubric(BaseModel):
|
|
|
48
49
|
return criteria
|
|
49
50
|
|
|
50
51
|
|
|
51
|
-
def _load_text(source: str) -> tuple[str,
|
|
52
|
+
def _load_text(source: str) -> tuple[str, str | None]:
|
|
52
53
|
path = Path(source)
|
|
53
54
|
if path.exists():
|
|
54
55
|
return path.read_text(encoding="utf-8"), path.suffix.lower()
|
|
55
56
|
return source, None
|
|
56
57
|
|
|
57
58
|
|
|
58
|
-
def _parse_structured(text: str, suffix:
|
|
59
|
+
def _parse_structured(text: str, suffix: str | None) -> dict[str, Any]:
|
|
59
60
|
text = text.strip()
|
|
60
61
|
if not text:
|
|
61
62
|
raise ValueError("Rubric source is empty")
|
|
@@ -66,7 +67,7 @@ def _parse_structured(text: str, suffix: Optional[str]) -> Dict[str, Any]:
|
|
|
66
67
|
raise RuntimeError("PyYAML is required to load YAML rubrics") from exc
|
|
67
68
|
data = yaml.safe_load(text)
|
|
68
69
|
if not isinstance(data, dict):
|
|
69
|
-
raise ValueError("Rubric YAML must produce a mapping")
|
|
70
|
+
raise ValueError("Rubric YAML must produce a mapping") from None
|
|
70
71
|
return data
|
|
71
72
|
if text.startswith("{"):
|
|
72
73
|
return json.loads(text)
|
|
@@ -85,7 +86,7 @@ def _parse_structured(text: str, suffix: Optional[str]) -> Dict[str, Any]:
|
|
|
85
86
|
raise RuntimeError("PyYAML is required to load rubric text") from exc
|
|
86
87
|
data = yaml.safe_load(text)
|
|
87
88
|
if not isinstance(data, dict):
|
|
88
|
-
raise ValueError("Rubric text must decode to a mapping")
|
|
89
|
+
raise ValueError("Rubric text must decode to a mapping") from None
|
|
89
90
|
return data
|
|
90
91
|
|
|
91
92
|
|
|
@@ -148,17 +149,19 @@ def blend_rubrics(base: Rubric | None, override: Rubric | None) -> Rubric | None
|
|
|
148
149
|
)
|
|
149
150
|
|
|
150
151
|
|
|
151
|
-
def _as_float(value: Any) ->
|
|
152
|
+
def _as_float(value: Any) -> float | None:
|
|
152
153
|
try:
|
|
153
154
|
return float(value)
|
|
154
155
|
except Exception:
|
|
155
156
|
return None
|
|
156
157
|
|
|
157
158
|
|
|
158
|
-
def _score(
|
|
159
|
+
def _score(
|
|
160
|
+
criteria: Iterable[Criterion], values: dict[str, float], aggregation: str
|
|
161
|
+
) -> dict[str, Any]:
|
|
159
162
|
if aggregation == "inherit":
|
|
160
163
|
aggregation = "weighted_sum"
|
|
161
|
-
per_criterion:
|
|
164
|
+
per_criterion: dict[str, dict[str, Any]] = {}
|
|
162
165
|
total = 0.0
|
|
163
166
|
total_weight = 0.0
|
|
164
167
|
for criterion in criteria:
|
|
@@ -184,10 +187,12 @@ def _score(criteria: Iterable[Criterion], values: Dict[str, float], aggregation:
|
|
|
184
187
|
}
|
|
185
188
|
|
|
186
189
|
|
|
187
|
-
def score_events_against_rubric(
|
|
190
|
+
def score_events_against_rubric(
|
|
191
|
+
events: list[dict[str, Any]], rubric: Rubric | None
|
|
192
|
+
) -> dict[str, Any]:
|
|
188
193
|
if rubric is None:
|
|
189
194
|
return {"aggregation": "none", "score": None, "per_criterion": {}}
|
|
190
|
-
values:
|
|
195
|
+
values: dict[str, float] = {}
|
|
191
196
|
for event in events or []:
|
|
192
197
|
if not isinstance(event, dict):
|
|
193
198
|
continue
|
|
@@ -198,12 +203,14 @@ def score_events_against_rubric(events: list[dict[str, Any]], rubric: Rubric | N
|
|
|
198
203
|
return _score(rubric.criteria, values, rubric.aggregation)
|
|
199
204
|
|
|
200
205
|
|
|
201
|
-
def score_outcome_against_rubric(outcome: dict[str, Any], rubric: Rubric | None) ->
|
|
206
|
+
def score_outcome_against_rubric(outcome: dict[str, Any], rubric: Rubric | None) -> dict[str, Any]:
|
|
202
207
|
if rubric is None:
|
|
203
208
|
return {"aggregation": "none", "score": None, "per_criterion": {}}
|
|
204
|
-
values:
|
|
209
|
+
values: dict[str, float] = {}
|
|
205
210
|
if isinstance(outcome, dict):
|
|
206
|
-
candidates =
|
|
211
|
+
candidates = (
|
|
212
|
+
outcome.get("criteria") if isinstance(outcome.get("criteria"), dict) else outcome
|
|
213
|
+
)
|
|
207
214
|
if isinstance(candidates, dict):
|
|
208
215
|
for key, value in candidates.items():
|
|
209
216
|
score = _as_float(value)
|
synth_ai/task/server.py
CHANGED
|
@@ -1,39 +1,34 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
1
|
"""FastAPI scaffolding for Task Apps (local dev + deployment)."""
|
|
4
2
|
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
5
|
import asyncio
|
|
6
6
|
import inspect
|
|
7
7
|
import os
|
|
8
|
+
from collections.abc import Awaitable, Callable, Iterable, Mapping, MutableMapping, Sequence
|
|
8
9
|
from dataclasses import dataclass, field
|
|
9
10
|
from pathlib import Path
|
|
10
|
-
from typing import Any
|
|
11
|
+
from typing import Any
|
|
11
12
|
|
|
12
13
|
import httpx
|
|
13
14
|
from fastapi import APIRouter, Depends, FastAPI, Query, Request
|
|
14
15
|
from fastapi.middleware.cors import CORSMiddleware
|
|
15
|
-
from fastapi.responses import JSONResponse
|
|
16
16
|
from starlette.middleware import Middleware
|
|
17
17
|
|
|
18
|
-
from .auth import
|
|
19
|
-
is_api_key_header_authorized,
|
|
20
|
-
normalize_environment_api_key,
|
|
21
|
-
require_api_key_dependency,
|
|
22
|
-
)
|
|
18
|
+
from .auth import normalize_environment_api_key, require_api_key_dependency
|
|
23
19
|
from .contracts import RolloutRequest, RolloutResponse, TaskInfo
|
|
24
20
|
from .datasets import TaskDatasetRegistry
|
|
25
21
|
from .errors import http_exception
|
|
26
22
|
from .json import to_jsonable
|
|
27
23
|
from .proxy import (
|
|
24
|
+
inject_system_hint,
|
|
28
25
|
prepare_for_groq,
|
|
29
26
|
prepare_for_openai,
|
|
30
|
-
inject_system_hint,
|
|
31
27
|
synthesize_tool_call_if_missing,
|
|
32
28
|
)
|
|
33
29
|
from .rubrics import Rubric
|
|
34
30
|
from .vendors import get_groq_key_or_503, get_openai_key_or_503, normalize_vendor_keys
|
|
35
31
|
|
|
36
|
-
|
|
37
32
|
TasksetDescriptor = Callable[[], Mapping[str, Any] | Awaitable[Mapping[str, Any]]]
|
|
38
33
|
InstanceProvider = Callable[[Sequence[int]], Iterable[TaskInfo] | Awaitable[Iterable[TaskInfo]]]
|
|
39
34
|
RolloutExecutor = Callable[[RolloutRequest, Request], Any | Awaitable[Any]]
|
|
@@ -81,7 +76,7 @@ class TaskAppConfig:
|
|
|
81
76
|
startup_hooks: Sequence[Callable[[], None | Awaitable[None]]] = field(default_factory=tuple)
|
|
82
77
|
shutdown_hooks: Sequence[Callable[[], None | Awaitable[None]]] = field(default_factory=tuple)
|
|
83
78
|
|
|
84
|
-
def clone(self) ->
|
|
79
|
+
def clone(self) -> TaskAppConfig:
|
|
85
80
|
"""Return a shallow copy safe to mutate when wiring the app."""
|
|
86
81
|
|
|
87
82
|
return TaskAppConfig(
|
|
@@ -120,7 +115,9 @@ def _ensure_task_info(obj: Any) -> TaskInfo:
|
|
|
120
115
|
return obj
|
|
121
116
|
if isinstance(obj, MutableMapping):
|
|
122
117
|
return TaskInfo.model_validate(obj)
|
|
123
|
-
raise TypeError(
|
|
118
|
+
raise TypeError(
|
|
119
|
+
f"Task instance provider must yield TaskInfo-compatible objects (got {type(obj)!r})"
|
|
120
|
+
)
|
|
124
121
|
|
|
125
122
|
|
|
126
123
|
def _normalise_seeds(values: Sequence[int]) -> list[int]:
|
|
@@ -140,7 +137,9 @@ def _build_proxy_routes(
|
|
|
140
137
|
if not proxy:
|
|
141
138
|
return
|
|
142
139
|
|
|
143
|
-
async def _call_vendor(
|
|
140
|
+
async def _call_vendor(
|
|
141
|
+
url: str, payload: dict[str, Any], headers: dict[str, str]
|
|
142
|
+
) -> dict[str, Any]:
|
|
144
143
|
async with httpx.AsyncClient(timeout=httpx.Timeout(600.0), follow_redirects=True) as client:
|
|
145
144
|
response = await client.post(url, json=payload, headers=headers)
|
|
146
145
|
data = (
|
|
@@ -168,13 +167,17 @@ def _build_proxy_routes(
|
|
|
168
167
|
msg_count = len(messages) if isinstance(messages, list) else 0
|
|
169
168
|
tool_count = len(payload.get("tools") or []) if isinstance(payload, dict) else 0
|
|
170
169
|
model = payload.get("model") if isinstance(payload, dict) else None
|
|
171
|
-
print(
|
|
170
|
+
print(
|
|
171
|
+
f"[task:proxy:{route}] model={model} messages={msg_count} tools={tool_count}",
|
|
172
|
+
flush=True,
|
|
173
|
+
)
|
|
172
174
|
except Exception: # pragma: no cover - best effort logging
|
|
173
175
|
pass
|
|
174
176
|
|
|
175
177
|
system_hint = proxy.system_hint
|
|
176
178
|
|
|
177
179
|
if proxy.enable_openai:
|
|
180
|
+
|
|
178
181
|
@app.post("/proxy/v1/chat/completions", dependencies=[Depends(auth_dependency)])
|
|
179
182
|
async def proxy_openai(body: dict[str, Any], request: Request) -> Any: # type: ignore[no-redef]
|
|
180
183
|
key = get_openai_key_or_503()
|
|
@@ -187,6 +190,7 @@ def _build_proxy_routes(
|
|
|
187
190
|
return to_jsonable(sanitized)
|
|
188
191
|
|
|
189
192
|
if proxy.enable_groq:
|
|
193
|
+
|
|
190
194
|
@app.post("/proxy/groq/v1/chat/completions", dependencies=[Depends(auth_dependency)])
|
|
191
195
|
async def proxy_groq(body: dict[str, Any], request: Request) -> Any: # type: ignore[no-redef]
|
|
192
196
|
key = get_groq_key_or_503()
|
|
@@ -194,7 +198,9 @@ def _build_proxy_routes(
|
|
|
194
198
|
payload = prepare_for_groq(model, body)
|
|
195
199
|
payload = inject_system_hint(payload, system_hint or "")
|
|
196
200
|
_log_proxy("groq", payload)
|
|
197
|
-
data = await _call_vendor(
|
|
201
|
+
data = await _call_vendor(
|
|
202
|
+
proxy.groq_url.rstrip("/"), payload, {"Authorization": f"Bearer {key}"}
|
|
203
|
+
)
|
|
198
204
|
sanitized = synthesize_tool_call_if_missing(data)
|
|
199
205
|
return to_jsonable(sanitized)
|
|
200
206
|
|
|
@@ -278,7 +284,20 @@ def create_task_app(config: TaskAppConfig) -> FastAPI:
|
|
|
278
284
|
async def health(request: Request) -> Mapping[str, Any]:
|
|
279
285
|
# If we got here, auth_dependency already verified the key exactly matches
|
|
280
286
|
expected = normalize_environment_api_key()
|
|
281
|
-
return to_jsonable(
|
|
287
|
+
return to_jsonable(
|
|
288
|
+
{
|
|
289
|
+
"healthy": True,
|
|
290
|
+
"auth": {
|
|
291
|
+
"required": True,
|
|
292
|
+
"expected_prefix": (expected[:6] + "...") if expected else "<unset>",
|
|
293
|
+
},
|
|
294
|
+
}
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
@app.post("/done", dependencies=[Depends(auth_dependency)])
|
|
298
|
+
async def done() -> Mapping[str, Any]:
|
|
299
|
+
# Coordination endpoint for tests and automation; indicates app is reachable
|
|
300
|
+
return to_jsonable({"ok": True, "service": cfg.app_id})
|
|
282
301
|
|
|
283
302
|
@app.get("/info", dependencies=[Depends(auth_dependency)])
|
|
284
303
|
async def info() -> Mapping[str, Any]:
|
|
@@ -335,6 +354,7 @@ def create_task_app(config: TaskAppConfig) -> FastAPI:
|
|
|
335
354
|
raise TypeError("Rollout executor must return RolloutResponse or mapping")
|
|
336
355
|
|
|
337
356
|
if cfg.expose_debug_env:
|
|
357
|
+
|
|
338
358
|
@app.get("/debug/env", dependencies=[Depends(auth_dependency)])
|
|
339
359
|
async def debug_env() -> Mapping[str, Any]:
|
|
340
360
|
def _mask(value: str | None) -> str:
|
|
@@ -387,6 +407,12 @@ def run_task_app(
|
|
|
387
407
|
print(f"[task:server] Loaded environment from: {', '.join(loaded_files)}", flush=True)
|
|
388
408
|
|
|
389
409
|
config = config_factory()
|
|
410
|
+
# Defensive: ensure the factory produced a valid TaskAppConfig to avoid
|
|
411
|
+
# confusing attribute errors later in the boot sequence.
|
|
412
|
+
if not isinstance(config, TaskAppConfig): # type: ignore[arg-type]
|
|
413
|
+
raise TypeError(
|
|
414
|
+
f"Task app config_factory must return TaskAppConfig, got {type(config).__name__}"
|
|
415
|
+
)
|
|
390
416
|
app = create_task_app(config)
|
|
391
417
|
|
|
392
418
|
try:
|
synth_ai/task/tracing_utils.py
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
1
|
"""Utilities for wiring tracing_v3 into task apps."""
|
|
4
2
|
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
5
|
import os
|
|
6
|
-
import
|
|
6
|
+
from collections.abc import Callable
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import Any
|
|
8
|
+
from typing import Any
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
def tracing_env_enabled(default: bool = False) -> bool:
|
|
@@ -45,7 +45,9 @@ def resolve_tracing_db_url() -> str | None:
|
|
|
45
45
|
return f"sqlite+aiosqlite:///{fallback_path}"
|
|
46
46
|
|
|
47
47
|
|
|
48
|
-
def build_tracer_factory(
|
|
48
|
+
def build_tracer_factory(
|
|
49
|
+
make_tracer: Callable[..., Any], *, enabled: bool, db_url: str | None
|
|
50
|
+
) -> Callable[[], Any] | None:
|
|
49
51
|
"""Return a factory that instantiates a tracer when enabled, else None."""
|
|
50
52
|
|
|
51
53
|
if not enabled:
|
|
@@ -74,6 +76,9 @@ def resolve_sft_output_dir() -> str | None:
|
|
|
74
76
|
def unique_sft_path(base_dir: str, *, run_id: str) -> Path:
|
|
75
77
|
"""Return a unique JSONL path for an SFT record batch."""
|
|
76
78
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
+
from datetime import datetime
|
|
80
|
+
|
|
81
|
+
now = datetime.now()
|
|
82
|
+
timestamp = now.strftime("%Y-%m-%d_%H-%M-%S")
|
|
83
|
+
name = f"{run_id}_{timestamp}.jsonl"
|
|
79
84
|
return Path(base_dir) / name
|
synth_ai/task/validators.py
CHANGED
synth_ai/task/vendors.py
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
1
|
"""Vendor API key helpers shared by Task Apps."""
|
|
4
2
|
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
5
|
import os
|
|
6
|
-
from typing import Optional
|
|
7
6
|
|
|
8
7
|
from .errors import http_exception
|
|
9
8
|
|
|
@@ -20,7 +19,7 @@ def _mask(value: str, *, prefix: int = 4) -> str:
|
|
|
20
19
|
return f"{visible}{'…' if len(value) > prefix else ''}"
|
|
21
20
|
|
|
22
21
|
|
|
23
|
-
def _normalize_single(key: str) ->
|
|
22
|
+
def _normalize_single(key: str) -> str | None:
|
|
24
23
|
direct = os.getenv(key)
|
|
25
24
|
if direct:
|
|
26
25
|
return direct
|
|
@@ -37,10 +36,10 @@ def _normalize_single(key: str) -> Optional[str]:
|
|
|
37
36
|
return None
|
|
38
37
|
|
|
39
38
|
|
|
40
|
-
def normalize_vendor_keys() -> dict[str,
|
|
39
|
+
def normalize_vendor_keys() -> dict[str, str | None]:
|
|
41
40
|
"""Normalise known vendor keys from dev fallbacks and return the mapping."""
|
|
42
41
|
|
|
43
|
-
resolved: dict[str,
|
|
42
|
+
resolved: dict[str, str | None] = {}
|
|
44
43
|
for key in _VENDOR_KEYS:
|
|
45
44
|
resolved[key] = _normalize_single(key)
|
|
46
45
|
return resolved
|
|
@@ -58,4 +57,3 @@ def get_groq_key_or_503() -> str:
|
|
|
58
57
|
if not key:
|
|
59
58
|
raise http_exception(503, "missing_groq_api_key", "GROQ_API_KEY is not configured")
|
|
60
59
|
return key
|
|
61
|
-
|
synth_ai/tracing_v3/__init__.py
CHANGED
|
@@ -75,6 +75,7 @@ from .abstractions import (
|
|
|
75
75
|
EnvironmentEvent,
|
|
76
76
|
RuntimeEvent,
|
|
77
77
|
SessionEventMarkovBlanketMessage,
|
|
78
|
+
SessionMessageContent,
|
|
78
79
|
SessionTimeStep,
|
|
79
80
|
SessionTrace,
|
|
80
81
|
TimeRecord,
|
|
@@ -90,6 +91,7 @@ __all__ = [
|
|
|
90
91
|
"RuntimeEvent",
|
|
91
92
|
"EnvironmentEvent",
|
|
92
93
|
"SessionEventMarkovBlanketMessage",
|
|
94
|
+
"SessionMessageContent",
|
|
93
95
|
"TimeRecord",
|
|
94
96
|
"TursoConfig",
|
|
95
97
|
]
|