synth-ai 0.2.9.dev7__py3-none-any.whl → 0.2.9.dev8__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 +8 -11
- 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/run_eval.py +36 -37
- examples/rl/run_rl_and_save.py +5 -5
- examples/rl/task_app/math_single_step.py +65 -43
- examples/rl/task_app/math_task_app.py +3 -3
- 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 +5 -5
- examples/warming_up_to_rl/configs/rl_from_base_qwen4b.toml +11 -1
- examples/warming_up_to_rl/export_trace_sft.py +78 -21
- examples/warming_up_to_rl/groq_test.py +4 -4
- examples/warming_up_to_rl/manage_secrets.py +13 -18
- examples/warming_up_to_rl/run_eval.py +42 -44
- examples/warming_up_to_rl/run_fft_and_save.py +11 -16
- examples/warming_up_to_rl/run_local_rollout.py +1 -3
- examples/warming_up_to_rl/run_local_rollout_modal.py +2 -4
- examples/warming_up_to_rl/run_local_rollout_parallel.py +1 -4
- examples/warming_up_to_rl/run_local_rollout_traced.py +3 -5
- examples/warming_up_to_rl/run_rl_and_save.py +5 -6
- examples/warming_up_to_rl/run_rollout_remote.py +8 -10
- examples/warming_up_to_rl/task_app/README.md +6 -2
- examples/warming_up_to_rl/task_app/grpo_crafter.py +234 -35
- examples/warming_up_to_rl/task_app/grpo_crafter_task_app.py +2 -3
- 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 +131 -114
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/environment.py +101 -41
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/policy.py +73 -51
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/react_agent.py +14 -6
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/shared.py +16 -16
- examples/warming_up_to_rl/task_app/synth_envs_hosted/hosted_app.py +32 -34
- examples/warming_up_to_rl/task_app/synth_envs_hosted/inference/openai_client.py +94 -31
- 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 +303 -203
- 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 +328 -225
- examples/warming_up_to_rl/task_app/synth_envs_hosted/storage/volume.py +13 -13
- 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 +1 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/utils.py +4 -3
- synth/__init__.py +14 -0
- synth_ai/__init__.py +26 -4
- synth_ai/api/models/supported.py +376 -0
- synth_ai/api/train/builders.py +128 -21
- synth_ai/api/train/cli.py +80 -64
- synth_ai/api/train/config_finder.py +7 -2
- synth_ai/api/train/env_resolver.py +1 -1
- synth_ai/api/train/pollers.py +2 -1
- synth_ai/api/train/supported_algos.py +139 -0
- synth_ai/api/train/task_app.py +1 -2
- synth_ai/api/train/utils.py +13 -44
- synth_ai/cli/__init__.py +8 -0
- synth_ai/cli/_modal_wrapper.py +28 -0
- synth_ai/cli/_typer_patch.py +49 -0
- synth_ai/cli/balance.py +1 -2
- synth_ai/cli/calc.py +1 -1
- synth_ai/cli/demo.py +2 -1
- synth_ai/cli/recent.py +2 -2
- synth_ai/cli/rl_demo.py +2 -1
- synth_ai/cli/root.py +11 -13
- synth_ai/cli/status.py +2 -2
- synth_ai/cli/task_apps.py +529 -179
- synth_ai/cli/traces.py +6 -4
- synth_ai/cli/watch.py +12 -18
- synth_ai/demo_registry.py +1 -1
- synth_ai/demos/core/cli.py +36 -43
- synth_ai/demos/demo_task_apps/__init__.py +3 -3
- synth_ai/demos/demo_task_apps/core.py +17 -25
- synth_ai/demos/demo_task_apps/crafter/grpo_crafter_task_app.py +3 -4
- synth_ai/demos/demo_task_apps/math/app.py +2 -1
- synth_ai/demos/demo_task_apps/math/deploy_modal.py +3 -4
- synth_ai/demos/demo_task_apps/math/modal_task_app.py +16 -18
- synth_ai/demos/demo_task_apps/math/task_app_entry.py +0 -1
- synth_ai/environments/examples/crafter_classic/environment.py +76 -1
- synth_ai/environments/reproducibility/tree.py +2 -5
- synth_ai/environments/service/app.py +11 -12
- synth_ai/environments/service/core_routes.py +4 -7
- 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/handshake.py +9 -9
- synth_ai/http.py +1 -1
- synth_ai/http_client.py +18 -10
- synth_ai/inference/client.py +15 -5
- synth_ai/jobs/client.py +78 -83
- synth_ai/learning/__init__.py +41 -6
- synth_ai/learning/algorithms.py +14 -0
- synth_ai/learning/client.py +91 -24
- synth_ai/learning/config.py +2 -38
- synth_ai/learning/ft_client.py +4 -59
- synth_ai/learning/health.py +5 -6
- synth_ai/learning/jobs.py +31 -47
- synth_ai/{rl → learning/rl}/__init__.py +14 -4
- synth_ai/learning/rl/client.py +267 -0
- synth_ai/learning/rl/config.py +31 -0
- synth_ai/{rl → learning/rl}/contracts.py +5 -8
- synth_ai/{rl → learning/rl}/env_keys.py +39 -15
- synth_ai/learning/rl/secrets.py +13 -0
- synth_ai/learning/rl_client.py +2 -281
- 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 -24
- synth_ai/learning/validators.py +25 -28
- synth_ai/lm/__init__.py +21 -47
- synth_ai/main.py +4 -0
- synth_ai/task/__init__.py +25 -27
- synth_ai/task/apps/__init__.py +7 -8
- synth_ai/task/auth.py +8 -8
- synth_ai/task/client.py +14 -14
- synth_ai/task/contracts.py +36 -35
- synth_ai/task/datasets.py +6 -5
- synth_ai/task/errors.py +10 -10
- synth_ai/task/health.py +17 -9
- synth_ai/task/json.py +58 -23
- synth_ai/task/proxy.py +13 -9
- synth_ai/task/rubrics.py +16 -15
- synth_ai/task/server.py +12 -12
- synth_ai/task/tracing_utils.py +4 -4
- synth_ai/task/vendors.py +5 -6
- synth_ai/tracing_v3/__init__.py +2 -0
- synth_ai/tracing_v3/abstractions.py +21 -4
- synth_ai/tracing_v3/decorators.py +18 -16
- synth_ai/tracing_v3/hooks.py +5 -5
- synth_ai/tracing_v3/llm_call_record_helpers.py +6 -6
- synth_ai/tracing_v3/session_tracer.py +40 -14
- synth_ai/tracing_v3/storage/base.py +85 -0
- synth_ai/tracing_v3/storage/config.py +21 -8
- synth_ai/tracing_v3/storage/factory.py +10 -7
- 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 +2 -2
- synth_ai/tracing_v3/turso/native_manager.py +1173 -0
- synth_ai/tracing_v3/utils.py +4 -4
- 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 +2 -2
- 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/main.py +6 -6
- synth_ai/{lm → v0/lm}/core/main_v3.py +10 -10
- synth_ai/{lm → v0/lm}/core/synth_models.py +2 -14
- synth_ai/{lm → v0/lm}/core/vendor_clients.py +2 -2
- synth_ai/{lm → v0/lm}/overrides.py +2 -2
- 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 +9 -9
- 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 +10 -10
- synth_ai/{lm → v0/lm}/vendors/openai_standard.py +8 -8
- synth_ai/{lm → v0/lm}/vendors/openai_standard_responses.py +2 -2
- synth_ai/{lm → v0/lm}/vendors/supported/custom_endpoint.py +3 -3
- 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 +1 -1
- 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.dev8.dist-info/METADATA +191 -0
- {synth_ai-0.2.9.dev7.dist-info → synth_ai-0.2.9.dev8.dist-info}/RECORD +268 -238
- {synth_ai-0.2.9.dev7.dist-info → synth_ai-0.2.9.dev8.dist-info}/top_level.txt +1 -0
- examples/common_old/backend.py +0 -20
- examples/evals_old/README.md +0 -98
- examples/evals_old/__init__.py +0 -6
- examples/evals_old/compare_models.py +0 -1038
- 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 -243
- 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 -119
- 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 -243
- 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 -36
- examples/finetuning_old/synth_qwen_v1/poll.py +0 -46
- 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 -1933
- examples/finetuning_old/synth_qwen_v1/run_crafter_sft_job.py +0 -210
- examples/finetuning_old/synth_qwen_v1/run_ft_job.py +0 -237
- examples/finetuning_old/synth_qwen_v1/upload_data.py +0 -34
- examples/finetuning_old/synth_qwen_v1/util.py +0 -152
- examples/rl_old/task_app.py +0 -1131
- 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 -445
- 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 -211
- synth_ai/learning/prompts/mipro.py +0 -289
- synth_ai/learning/prompts/random_search.py +0 -249
- synth_ai/learning/prompts/run_mipro_banking77.py +0 -172
- synth_ai/learning/prompts/run_random_search_banking77.py +0 -329
- 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 -838
- synth_ai/zyk/__init__.py +0 -30
- synth_ai-0.2.9.dev7.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}/core/exceptions.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.dev7.dist-info → synth_ai-0.2.9.dev8.dist-info}/WHEEL +0 -0
- {synth_ai-0.2.9.dev7.dist-info → synth_ai-0.2.9.dev8.dist-info}/entry_points.txt +0 -0
- {synth_ai-0.2.9.dev7.dist-info → synth_ai-0.2.9.dev8.dist-info}/licenses/LICENSE +0 -0
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,21 +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(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return {str(k): to_jsonable(v) for k, v in value.items()}
|
|
73
|
+
return to_jsonable(
|
|
74
|
+
dumped, _visited=_visited, _depth=_depth + 1, _max_depth=_max_depth
|
|
75
|
+
)
|
|
63
76
|
|
|
64
|
-
|
|
65
|
-
|
|
77
|
+
obj_id = id(value)
|
|
78
|
+
if obj_id in _visited:
|
|
79
|
+
return f"<circular type={type(value).__name__}>"
|
|
66
80
|
|
|
67
|
-
if isinstance(value,
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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)
|
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()
|
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,7 +149,7 @@ 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:
|
|
@@ -156,11 +157,11 @@ def _as_float(value: Any) -> Optional[float]:
|
|
|
156
157
|
|
|
157
158
|
|
|
158
159
|
def _score(
|
|
159
|
-
criteria: Iterable[Criterion], values:
|
|
160
|
-
) ->
|
|
160
|
+
criteria: Iterable[Criterion], values: dict[str, float], aggregation: str
|
|
161
|
+
) -> dict[str, Any]:
|
|
161
162
|
if aggregation == "inherit":
|
|
162
163
|
aggregation = "weighted_sum"
|
|
163
|
-
per_criterion:
|
|
164
|
+
per_criterion: dict[str, dict[str, Any]] = {}
|
|
164
165
|
total = 0.0
|
|
165
166
|
total_weight = 0.0
|
|
166
167
|
for criterion in criteria:
|
|
@@ -188,10 +189,10 @@ def _score(
|
|
|
188
189
|
|
|
189
190
|
def score_events_against_rubric(
|
|
190
191
|
events: list[dict[str, Any]], rubric: Rubric | None
|
|
191
|
-
) ->
|
|
192
|
+
) -> dict[str, Any]:
|
|
192
193
|
if rubric is None:
|
|
193
194
|
return {"aggregation": "none", "score": None, "per_criterion": {}}
|
|
194
|
-
values:
|
|
195
|
+
values: dict[str, float] = {}
|
|
195
196
|
for event in events or []:
|
|
196
197
|
if not isinstance(event, dict):
|
|
197
198
|
continue
|
|
@@ -202,10 +203,10 @@ def score_events_against_rubric(
|
|
|
202
203
|
return _score(rubric.criteria, values, rubric.aggregation)
|
|
203
204
|
|
|
204
205
|
|
|
205
|
-
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]:
|
|
206
207
|
if rubric is None:
|
|
207
208
|
return {"aggregation": "none", "score": None, "per_criterion": {}}
|
|
208
|
-
values:
|
|
209
|
+
values: dict[str, float] = {}
|
|
209
210
|
if isinstance(outcome, dict):
|
|
210
211
|
candidates = (
|
|
211
212
|
outcome.get("criteria") if isinstance(outcome.get("criteria"), dict) else outcome
|
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(
|
|
@@ -299,6 +294,11 @@ def create_task_app(config: TaskAppConfig) -> FastAPI:
|
|
|
299
294
|
}
|
|
300
295
|
)
|
|
301
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})
|
|
301
|
+
|
|
302
302
|
@app.get("/info", dependencies=[Depends(auth_dependency)])
|
|
303
303
|
async def info() -> Mapping[str, Any]:
|
|
304
304
|
dataset_meta = cfg.base_task_info.dataset
|
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:
|
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
|
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
|
]
|
|
@@ -37,7 +37,7 @@ Concepts:
|
|
|
37
37
|
from __future__ import annotations
|
|
38
38
|
|
|
39
39
|
from dataclasses import asdict, dataclass, field
|
|
40
|
-
from datetime import datetime
|
|
40
|
+
from datetime import UTC, datetime
|
|
41
41
|
from typing import Any
|
|
42
42
|
|
|
43
43
|
from .lm_call_record_abstractions import LLMCallRecord
|
|
@@ -61,6 +61,23 @@ class TimeRecord:
|
|
|
61
61
|
message_time: int | None = None
|
|
62
62
|
|
|
63
63
|
|
|
64
|
+
@dataclass(frozen=True)
|
|
65
|
+
class SessionMessageContent:
|
|
66
|
+
"""Normalized payload stored alongside session messages."""
|
|
67
|
+
|
|
68
|
+
text: str | None = None
|
|
69
|
+
json_payload: str | None = None
|
|
70
|
+
|
|
71
|
+
def as_text(self) -> str:
|
|
72
|
+
return self.text or (self.json_payload or "")
|
|
73
|
+
|
|
74
|
+
def has_json(self) -> bool:
|
|
75
|
+
return self.json_payload is not None
|
|
76
|
+
|
|
77
|
+
def __str__(self) -> str: # pragma: no cover - convenience for logging
|
|
78
|
+
return self.as_text()
|
|
79
|
+
|
|
80
|
+
|
|
64
81
|
@dataclass
|
|
65
82
|
class SessionEventMarkovBlanketMessage:
|
|
66
83
|
"""Message crossing Markov blanket boundaries between systems in a session.
|
|
@@ -97,7 +114,7 @@ class SessionEventMarkovBlanketMessage:
|
|
|
97
114
|
- 'causal_influence': Direction of causal flow
|
|
98
115
|
"""
|
|
99
116
|
|
|
100
|
-
content:
|
|
117
|
+
content: SessionMessageContent
|
|
101
118
|
message_type: str
|
|
102
119
|
time_record: TimeRecord
|
|
103
120
|
metadata: dict[str, Any] = field(default_factory=dict)
|
|
@@ -232,7 +249,7 @@ class SessionTimeStep:
|
|
|
232
249
|
|
|
233
250
|
step_id: str = ""
|
|
234
251
|
step_index: int = 0
|
|
235
|
-
timestamp: datetime = field(default_factory=datetime.
|
|
252
|
+
timestamp: datetime = field(default_factory=lambda: datetime.now(UTC))
|
|
236
253
|
turn_number: int | None = None
|
|
237
254
|
events: list[BaseEvent] = field(default_factory=list)
|
|
238
255
|
markov_blanket_messages: list[SessionEventMarkovBlanketMessage] = field(default_factory=list)
|
|
@@ -266,7 +283,7 @@ class SessionTrace:
|
|
|
266
283
|
"""
|
|
267
284
|
|
|
268
285
|
session_id: str = ""
|
|
269
|
-
created_at: datetime = field(default_factory=datetime.
|
|
286
|
+
created_at: datetime = field(default_factory=lambda: datetime.now(UTC))
|
|
270
287
|
session_time_steps: list[SessionTimeStep] = field(default_factory=list)
|
|
271
288
|
event_history: list[BaseEvent] = field(default_factory=list)
|
|
272
289
|
markov_blanket_message_history: list[SessionEventMarkovBlanketMessage] = field(
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
1
|
"""Async-aware decorators for tracing v3.
|
|
4
2
|
|
|
5
3
|
This module provides decorators and context management utilities for the tracing
|
|
@@ -24,6 +22,8 @@ The decorators support both sync and async functions where appropriate,
|
|
|
24
22
|
though async is preferred for consistency with the rest of the system.
|
|
25
23
|
"""
|
|
26
24
|
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
27
|
import asyncio
|
|
28
28
|
import contextvars
|
|
29
29
|
import functools
|
|
@@ -37,12 +37,8 @@ from .utils import calculate_cost, detect_provider
|
|
|
37
37
|
# Context variables for session and turn tracking
|
|
38
38
|
# These variables automatically propagate across async call boundaries,
|
|
39
39
|
# allowing deeply nested code to access tracing context without explicit passing
|
|
40
|
-
_session_id_ctx: contextvars.ContextVar[str | None] = contextvars.ContextVar(
|
|
41
|
-
|
|
42
|
-
)
|
|
43
|
-
_turn_number_ctx: contextvars.ContextVar[int | None] = contextvars.ContextVar(
|
|
44
|
-
"turn_number", default=None
|
|
45
|
-
)
|
|
40
|
+
_session_id_ctx: contextvars.ContextVar[str | None] = contextvars.ContextVar("session_id")
|
|
41
|
+
_turn_number_ctx: contextvars.ContextVar[int | None] = contextvars.ContextVar("turn_number")
|
|
46
42
|
_session_tracer_ctx: contextvars.ContextVar[Any | None] = contextvars.ContextVar(
|
|
47
43
|
"session_tracer", default=None
|
|
48
44
|
)
|
|
@@ -120,7 +116,9 @@ def with_session(require: bool = True):
|
|
|
120
116
|
async def async_wrapper(*args, **kwargs):
|
|
121
117
|
session_id = get_session_id()
|
|
122
118
|
if require and session_id is None:
|
|
123
|
-
raise RuntimeError(
|
|
119
|
+
raise RuntimeError(
|
|
120
|
+
f"No active session for {getattr(fn, '__name__', 'unknown')}"
|
|
121
|
+
)
|
|
124
122
|
return await fn(*args, **kwargs)
|
|
125
123
|
|
|
126
124
|
return async_wrapper
|
|
@@ -130,7 +128,9 @@ def with_session(require: bool = True):
|
|
|
130
128
|
def sync_wrapper(*args, **kwargs):
|
|
131
129
|
session_id = get_session_id()
|
|
132
130
|
if require and session_id is None:
|
|
133
|
-
raise RuntimeError(
|
|
131
|
+
raise RuntimeError(
|
|
132
|
+
f"No active session for {getattr(fn, '__name__', 'unknown')}"
|
|
133
|
+
)
|
|
134
134
|
return fn(*args, **kwargs)
|
|
135
135
|
|
|
136
136
|
return sync_wrapper
|
|
@@ -139,7 +139,7 @@ def with_session(require: bool = True):
|
|
|
139
139
|
|
|
140
140
|
|
|
141
141
|
def trace_llm_call(
|
|
142
|
-
model_name: str = None,
|
|
142
|
+
model_name: str | None = None,
|
|
143
143
|
system_id: str = "llm",
|
|
144
144
|
extract_tokens: bool = True,
|
|
145
145
|
extract_cost: bool = True,
|
|
@@ -209,14 +209,16 @@ def trace_llm_call(
|
|
|
209
209
|
input_tokens=input_tokens,
|
|
210
210
|
output_tokens=output_tokens,
|
|
211
211
|
total_tokens=total_tokens,
|
|
212
|
-
cost_usd=calculate_cost(
|
|
212
|
+
cost_usd=calculate_cost(
|
|
213
|
+
actual_model or "unknown", input_tokens or 0, output_tokens or 0
|
|
214
|
+
)
|
|
213
215
|
if extract_cost
|
|
214
216
|
else None,
|
|
215
217
|
latency_ms=latency_ms,
|
|
216
218
|
system_state_before=system_state_before,
|
|
217
219
|
system_state_after=kwargs.get("state_after", {}),
|
|
218
220
|
metadata={
|
|
219
|
-
"function": fn
|
|
221
|
+
"function": getattr(fn, "__name__", "unknown"),
|
|
220
222
|
"step_id": kwargs.get("step_id"),
|
|
221
223
|
},
|
|
222
224
|
)
|
|
@@ -235,7 +237,7 @@ def trace_llm_call(
|
|
|
235
237
|
provider=detect_provider(model_name),
|
|
236
238
|
latency_ms=int((time.time() - start_time) * 1000),
|
|
237
239
|
metadata={
|
|
238
|
-
"function": fn
|
|
240
|
+
"function": getattr(fn, "__name__", "unknown"),
|
|
239
241
|
"error": str(e),
|
|
240
242
|
"error_type": type(e).__name__,
|
|
241
243
|
},
|
|
@@ -250,7 +252,7 @@ def trace_llm_call(
|
|
|
250
252
|
return decorator
|
|
251
253
|
|
|
252
254
|
|
|
253
|
-
def trace_method(event_type: str = "runtime", system_id: str = None):
|
|
255
|
+
def trace_method(event_type: str = "runtime", system_id: str | None = None):
|
|
254
256
|
"""Generic method tracing decorator.
|
|
255
257
|
|
|
256
258
|
Traces any method call by recording it as a RuntimeEvent. Supports both
|
|
@@ -289,7 +291,7 @@ def trace_method(event_type: str = "runtime", system_id: str = None):
|
|
|
289
291
|
time_record=TimeRecord(event_time=time.time()),
|
|
290
292
|
actions=[], # Can be overridden in metadata
|
|
291
293
|
metadata={
|
|
292
|
-
"method": fn
|
|
294
|
+
"method": getattr(fn, "__name__", "unknown"),
|
|
293
295
|
"args": str(args)[:100], # Truncate for safety
|
|
294
296
|
"step_id": kwargs.get("step_id"),
|
|
295
297
|
},
|
synth_ai/tracing_v3/hooks.py
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
1
|
"""Hook system for extending tracing functionality.
|
|
4
2
|
|
|
5
3
|
The hook system provides a flexible way to extend the tracing system without
|
|
@@ -34,6 +32,8 @@ Common Use Cases:
|
|
|
34
32
|
- Custom filtering and sampling
|
|
35
33
|
"""
|
|
36
34
|
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
37
|
import asyncio
|
|
38
38
|
from collections.abc import Callable
|
|
39
39
|
from dataclasses import dataclass
|
|
@@ -89,9 +89,9 @@ class HookManager:
|
|
|
89
89
|
self,
|
|
90
90
|
event: str,
|
|
91
91
|
callback: Callable,
|
|
92
|
-
name: str = None,
|
|
92
|
+
name: str | None = None,
|
|
93
93
|
priority: int = 0,
|
|
94
|
-
event_types: list[str] = None,
|
|
94
|
+
event_types: list[str] | None = None,
|
|
95
95
|
) -> Hook:
|
|
96
96
|
"""Register a new hook.
|
|
97
97
|
|
|
@@ -115,7 +115,7 @@ class HookManager:
|
|
|
115
115
|
raise ValueError(f"Unknown hook event: {event}")
|
|
116
116
|
|
|
117
117
|
hook = Hook(
|
|
118
|
-
name=name or callback
|
|
118
|
+
name=name or getattr(callback, "__name__", "unknown"),
|
|
119
119
|
callback=callback,
|
|
120
120
|
event_types=event_types,
|
|
121
121
|
priority=priority,
|
|
@@ -5,10 +5,9 @@ format and compute aggregates from call records.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import uuid
|
|
8
|
-
from datetime import datetime
|
|
8
|
+
from datetime import UTC, datetime
|
|
9
9
|
from typing import Any
|
|
10
10
|
|
|
11
|
-
from synth_ai.lm.vendors.base import BaseLMResponse
|
|
12
11
|
from synth_ai.tracing_v3.lm_call_record_abstractions import (
|
|
13
12
|
LLMCallRecord,
|
|
14
13
|
LLMChunk,
|
|
@@ -18,6 +17,7 @@ from synth_ai.tracing_v3.lm_call_record_abstractions import (
|
|
|
18
17
|
LLMUsage,
|
|
19
18
|
ToolCallSpec,
|
|
20
19
|
)
|
|
20
|
+
from synth_ai.v0.lm.vendors.base import BaseLMResponse
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
def create_llm_call_record_from_response(
|
|
@@ -161,8 +161,8 @@ def create_llm_call_record_from_response(
|
|
|
161
161
|
api_type=api_type,
|
|
162
162
|
provider=provider,
|
|
163
163
|
model_name=model_name,
|
|
164
|
-
started_at=started_at or datetime.
|
|
165
|
-
completed_at=completed_at or datetime.
|
|
164
|
+
started_at=started_at or datetime.now(UTC),
|
|
165
|
+
completed_at=completed_at or datetime.now(UTC),
|
|
166
166
|
latency_ms=latency_ms,
|
|
167
167
|
request_params=params,
|
|
168
168
|
input_messages=input_messages,
|
|
@@ -322,8 +322,8 @@ def create_llm_call_record_from_streaming(
|
|
|
322
322
|
api_type="responses", # Streaming typically from Responses API
|
|
323
323
|
provider=provider,
|
|
324
324
|
model_name=model_name,
|
|
325
|
-
started_at=started_at or datetime.
|
|
326
|
-
completed_at=completed_at or datetime.
|
|
325
|
+
started_at=started_at or datetime.now(UTC),
|
|
326
|
+
completed_at=completed_at or datetime.now(UTC),
|
|
327
327
|
latency_ms=latency_ms,
|
|
328
328
|
request_params=params,
|
|
329
329
|
input_messages=input_messages,
|