synth-ai 0.2.9.dev5__py3-none-any.whl → 0.2.10__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/dev/qwen3_32b_qlora_4xh100.toml +40 -0
- examples/multi_step/crafter_rl_lora.md +29 -0
- 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 +65 -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 +19 -0
- examples/qwen_coder/scripts/train_coder_30b.sh +22 -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 +39 -0
- examples/qwen_coder/todos.md +38 -0
- examples/qwen_coder/validate_jsonl.py +60 -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/PROPOSAL.md +53 -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_ai/__init__.py +1 -0
- 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.dev5.dist-info → synth_ai-0.2.10.dist-info}/METADATA +10 -7
- {synth_ai-0.2.9.dev5.dist-info → synth_ai-0.2.10.dist-info}/RECORD +294 -258
- 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
- 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/{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.10.dist-info}/WHEEL +0 -0
- {synth_ai-0.2.9.dev5.dist-info → synth_ai-0.2.10.dist-info}/entry_points.txt +0 -0
- {synth_ai-0.2.9.dev5.dist-info → synth_ai-0.2.10.dist-info}/licenses/LICENSE +0 -0
- {synth_ai-0.2.9.dev5.dist-info → synth_ai-0.2.10.dist-info}/top_level.txt +0 -0
|
@@ -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(
|
synth_ai/tracing_v3/db_config.py
CHANGED
|
@@ -4,6 +4,7 @@ Centralized database configuration for v3 tracing.
|
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
6
|
import os
|
|
7
|
+
import shutil
|
|
7
8
|
from typing import TYPE_CHECKING, Optional
|
|
8
9
|
|
|
9
10
|
if TYPE_CHECKING:
|
|
@@ -30,7 +31,7 @@ class DatabaseConfig:
|
|
|
30
31
|
http_port: HTTP port for sqld daemon. If None, uses DEFAULT_HTTP_PORT from serve.sh.
|
|
31
32
|
use_sqld: Whether to use sqld daemon or direct SQLite.
|
|
32
33
|
"""
|
|
33
|
-
self.use_sqld = use_sqld
|
|
34
|
+
self.use_sqld = use_sqld and self._sqld_binary_available()
|
|
34
35
|
self.http_port = http_port or int(os.getenv("SQLD_HTTP_PORT", self.DEFAULT_HTTP_PORT))
|
|
35
36
|
self._daemon: SqldDaemon | None = None
|
|
36
37
|
|
|
@@ -70,6 +71,30 @@ class DatabaseConfig:
|
|
|
70
71
|
# SQLite URLs need 3 slashes for absolute paths
|
|
71
72
|
return f"sqlite+aiosqlite:///{actual_db_path}"
|
|
72
73
|
|
|
74
|
+
def _sqld_binary_available(self) -> bool:
|
|
75
|
+
"""Check if the sqld (Turso) binary is available on PATH."""
|
|
76
|
+
# Respect explicit SQLD_BINARY override when present
|
|
77
|
+
binary_override = os.getenv("SQLD_BINARY")
|
|
78
|
+
candidates = [binary_override, "sqld", "libsql-server"]
|
|
79
|
+
|
|
80
|
+
for candidate in candidates:
|
|
81
|
+
if candidate and shutil.which(candidate):
|
|
82
|
+
return True
|
|
83
|
+
|
|
84
|
+
if binary_override:
|
|
85
|
+
logger.warning(
|
|
86
|
+
"Configured SQLD_BINARY='%s' but the executable was not found on PATH. "
|
|
87
|
+
"Falling back to direct SQLite.",
|
|
88
|
+
binary_override,
|
|
89
|
+
)
|
|
90
|
+
else:
|
|
91
|
+
logger.warning(
|
|
92
|
+
"sqld binary not detected; falling back to SQLite-only mode. "
|
|
93
|
+
"Install Turso's sqld or set SQLD_BINARY to enable the Turso daemon."
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
return False
|
|
97
|
+
|
|
73
98
|
def start_daemon(self, wait_time: float = 2.0):
|
|
74
99
|
"""
|
|
75
100
|
Start the sqld daemon if configured.
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
1
|
"""Async-aware decorators for tracing v3.
|
|
3
2
|
|
|
4
3
|
This module provides decorators and context management utilities for the tracing
|
|
@@ -23,6 +22,8 @@ The decorators support both sync and async functions where appropriate,
|
|
|
23
22
|
though async is preferred for consistency with the rest of the system.
|
|
24
23
|
"""
|
|
25
24
|
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
26
27
|
import asyncio
|
|
27
28
|
import contextvars
|
|
28
29
|
import functools
|
|
@@ -36,12 +37,8 @@ from .utils import calculate_cost, detect_provider
|
|
|
36
37
|
# Context variables for session and turn tracking
|
|
37
38
|
# These variables automatically propagate across async call boundaries,
|
|
38
39
|
# allowing deeply nested code to access tracing context without explicit passing
|
|
39
|
-
_session_id_ctx: contextvars.ContextVar[str | None] = contextvars.ContextVar(
|
|
40
|
-
|
|
41
|
-
)
|
|
42
|
-
_turn_number_ctx: contextvars.ContextVar[int | None] = contextvars.ContextVar(
|
|
43
|
-
"turn_number", default=None
|
|
44
|
-
)
|
|
40
|
+
_session_id_ctx: contextvars.ContextVar[str | None] = contextvars.ContextVar("session_id")
|
|
41
|
+
_turn_number_ctx: contextvars.ContextVar[int | None] = contextvars.ContextVar("turn_number")
|
|
45
42
|
_session_tracer_ctx: contextvars.ContextVar[Any | None] = contextvars.ContextVar(
|
|
46
43
|
"session_tracer", default=None
|
|
47
44
|
)
|
|
@@ -119,7 +116,9 @@ def with_session(require: bool = True):
|
|
|
119
116
|
async def async_wrapper(*args, **kwargs):
|
|
120
117
|
session_id = get_session_id()
|
|
121
118
|
if require and session_id is None:
|
|
122
|
-
raise RuntimeError(
|
|
119
|
+
raise RuntimeError(
|
|
120
|
+
f"No active session for {getattr(fn, '__name__', 'unknown')}"
|
|
121
|
+
)
|
|
123
122
|
return await fn(*args, **kwargs)
|
|
124
123
|
|
|
125
124
|
return async_wrapper
|
|
@@ -129,7 +128,9 @@ def with_session(require: bool = True):
|
|
|
129
128
|
def sync_wrapper(*args, **kwargs):
|
|
130
129
|
session_id = get_session_id()
|
|
131
130
|
if require and session_id is None:
|
|
132
|
-
raise RuntimeError(
|
|
131
|
+
raise RuntimeError(
|
|
132
|
+
f"No active session for {getattr(fn, '__name__', 'unknown')}"
|
|
133
|
+
)
|
|
133
134
|
return fn(*args, **kwargs)
|
|
134
135
|
|
|
135
136
|
return sync_wrapper
|
|
@@ -138,7 +139,7 @@ def with_session(require: bool = True):
|
|
|
138
139
|
|
|
139
140
|
|
|
140
141
|
def trace_llm_call(
|
|
141
|
-
model_name: str = None,
|
|
142
|
+
model_name: str | None = None,
|
|
142
143
|
system_id: str = "llm",
|
|
143
144
|
extract_tokens: bool = True,
|
|
144
145
|
extract_cost: bool = True,
|
|
@@ -208,14 +209,16 @@ def trace_llm_call(
|
|
|
208
209
|
input_tokens=input_tokens,
|
|
209
210
|
output_tokens=output_tokens,
|
|
210
211
|
total_tokens=total_tokens,
|
|
211
|
-
cost_usd=calculate_cost(
|
|
212
|
+
cost_usd=calculate_cost(
|
|
213
|
+
actual_model or "unknown", input_tokens or 0, output_tokens or 0
|
|
214
|
+
)
|
|
212
215
|
if extract_cost
|
|
213
216
|
else None,
|
|
214
217
|
latency_ms=latency_ms,
|
|
215
218
|
system_state_before=system_state_before,
|
|
216
219
|
system_state_after=kwargs.get("state_after", {}),
|
|
217
220
|
metadata={
|
|
218
|
-
"function": fn
|
|
221
|
+
"function": getattr(fn, "__name__", "unknown"),
|
|
219
222
|
"step_id": kwargs.get("step_id"),
|
|
220
223
|
},
|
|
221
224
|
)
|
|
@@ -234,7 +237,7 @@ def trace_llm_call(
|
|
|
234
237
|
provider=detect_provider(model_name),
|
|
235
238
|
latency_ms=int((time.time() - start_time) * 1000),
|
|
236
239
|
metadata={
|
|
237
|
-
"function": fn
|
|
240
|
+
"function": getattr(fn, "__name__", "unknown"),
|
|
238
241
|
"error": str(e),
|
|
239
242
|
"error_type": type(e).__name__,
|
|
240
243
|
},
|
|
@@ -249,7 +252,7 @@ def trace_llm_call(
|
|
|
249
252
|
return decorator
|
|
250
253
|
|
|
251
254
|
|
|
252
|
-
def trace_method(event_type: str = "runtime", system_id: str = None):
|
|
255
|
+
def trace_method(event_type: str = "runtime", system_id: str | None = None):
|
|
253
256
|
"""Generic method tracing decorator.
|
|
254
257
|
|
|
255
258
|
Traces any method call by recording it as a RuntimeEvent. Supports both
|
|
@@ -288,7 +291,7 @@ def trace_method(event_type: str = "runtime", system_id: str = None):
|
|
|
288
291
|
time_record=TimeRecord(event_time=time.time()),
|
|
289
292
|
actions=[], # Can be overridden in metadata
|
|
290
293
|
metadata={
|
|
291
|
-
"method": fn
|
|
294
|
+
"method": getattr(fn, "__name__", "unknown"),
|
|
292
295
|
"args": str(args)[:100], # Truncate for safety
|
|
293
296
|
"step_id": kwargs.get("step_id"),
|
|
294
297
|
},
|
|
@@ -166,8 +166,9 @@ async def main():
|
|
|
166
166
|
|
|
167
167
|
tracer.hooks.register("event_recorded", count_events, name="event_counter")
|
|
168
168
|
|
|
169
|
-
async with
|
|
170
|
-
"
|
|
169
|
+
async with (
|
|
170
|
+
tracer.session(metadata={"example": "hooks"}) as session_id,
|
|
171
|
+
tracer.timestep("hook_test"),
|
|
171
172
|
):
|
|
172
173
|
for i in range(3):
|
|
173
174
|
event = RuntimeEvent(
|
synth_ai/tracing_v3/hooks.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
1
|
"""Hook system for extending tracing functionality.
|
|
3
2
|
|
|
4
3
|
The hook system provides a flexible way to extend the tracing system without
|
|
@@ -33,6 +32,8 @@ Common Use Cases:
|
|
|
33
32
|
- Custom filtering and sampling
|
|
34
33
|
"""
|
|
35
34
|
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
36
37
|
import asyncio
|
|
37
38
|
from collections.abc import Callable
|
|
38
39
|
from dataclasses import dataclass
|
|
@@ -88,9 +89,9 @@ class HookManager:
|
|
|
88
89
|
self,
|
|
89
90
|
event: str,
|
|
90
91
|
callback: Callable,
|
|
91
|
-
name: str = None,
|
|
92
|
+
name: str | None = None,
|
|
92
93
|
priority: int = 0,
|
|
93
|
-
event_types: list[str] = None,
|
|
94
|
+
event_types: list[str] | None = None,
|
|
94
95
|
) -> Hook:
|
|
95
96
|
"""Register a new hook.
|
|
96
97
|
|
|
@@ -114,7 +115,7 @@ class HookManager:
|
|
|
114
115
|
raise ValueError(f"Unknown hook event: {event}")
|
|
115
116
|
|
|
116
117
|
hook = Hook(
|
|
117
|
-
name=name or callback
|
|
118
|
+
name=name or getattr(callback, "__name__", "unknown"),
|
|
118
119
|
callback=callback,
|
|
119
120
|
event_types=event_types,
|
|
120
121
|
priority=priority,
|
|
@@ -202,6 +203,7 @@ def create_default_hooks() -> HookManager:
|
|
|
202
203
|
# Example: Log session starts - useful for debugging and monitoring
|
|
203
204
|
async def log_session_start(session_id: str, metadata: dict[str, Any]):
|
|
204
205
|
import os
|
|
206
|
+
|
|
205
207
|
if os.getenv("SYNTH_TRACE_VERBOSE", "0") in ("1", "true", "True"):
|
|
206
208
|
print(f"Session started: {session_id}")
|
|
207
209
|
|
|
@@ -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,
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
1
|
"""Main SessionTracer class for tracing v3."""
|
|
3
2
|
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
4
5
|
import asyncio
|
|
6
|
+
import json
|
|
5
7
|
from contextlib import asynccontextmanager
|
|
6
|
-
from datetime import datetime
|
|
8
|
+
from datetime import UTC, datetime
|
|
7
9
|
from typing import Any
|
|
8
10
|
|
|
9
11
|
from .abstractions import (
|
|
10
12
|
BaseEvent,
|
|
11
13
|
SessionEventMarkovBlanketMessage,
|
|
14
|
+
SessionMessageContent,
|
|
12
15
|
SessionTimeStep,
|
|
13
16
|
SessionTrace,
|
|
14
17
|
TimeRecord,
|
|
@@ -16,7 +19,9 @@ from .abstractions import (
|
|
|
16
19
|
from .config import CONFIG
|
|
17
20
|
from .decorators import set_session_id, set_session_tracer, set_turn_number
|
|
18
21
|
from .hooks import GLOBAL_HOOKS, HookManager
|
|
19
|
-
from .
|
|
22
|
+
from .storage.base import TraceStorage
|
|
23
|
+
from .storage.config import StorageConfig
|
|
24
|
+
from .storage.factory import create_storage
|
|
20
25
|
from .utils import generate_session_id
|
|
21
26
|
|
|
22
27
|
|
|
@@ -28,6 +33,8 @@ class SessionTracer:
|
|
|
28
33
|
hooks: HookManager | None = None,
|
|
29
34
|
db_url: str | None = None,
|
|
30
35
|
auto_save: bool = True,
|
|
36
|
+
storage: TraceStorage | None = None,
|
|
37
|
+
storage_config: StorageConfig | None = None,
|
|
31
38
|
):
|
|
32
39
|
"""Initialize session tracer.
|
|
33
40
|
|
|
@@ -40,7 +47,8 @@ class SessionTracer:
|
|
|
40
47
|
self._current_trace: SessionTrace | None = None
|
|
41
48
|
self._lock = asyncio.Lock()
|
|
42
49
|
self.db_url = db_url or CONFIG.db_url
|
|
43
|
-
self.
|
|
50
|
+
self._storage_config = storage_config
|
|
51
|
+
self.db: TraceStorage | None = storage
|
|
44
52
|
self.auto_save = auto_save
|
|
45
53
|
self._current_step: SessionTimeStep | None = None
|
|
46
54
|
|
|
@@ -57,7 +65,8 @@ class SessionTracer:
|
|
|
57
65
|
async def initialize(self):
|
|
58
66
|
"""Initialize the database connection."""
|
|
59
67
|
if self.db is None:
|
|
60
|
-
self.
|
|
68
|
+
config = self._storage_config or StorageConfig(connection_string=self.db_url)
|
|
69
|
+
self.db = create_storage(config)
|
|
61
70
|
await self.db.initialize()
|
|
62
71
|
|
|
63
72
|
async def start_session(
|
|
@@ -97,7 +106,7 @@ class SessionTracer:
|
|
|
97
106
|
|
|
98
107
|
self._current_trace = SessionTrace(
|
|
99
108
|
session_id=session_id,
|
|
100
|
-
created_at=datetime.
|
|
109
|
+
created_at=datetime.now(UTC),
|
|
101
110
|
session_time_steps=[],
|
|
102
111
|
event_history=[],
|
|
103
112
|
markov_blanket_message_history=[],
|
|
@@ -110,7 +119,9 @@ class SessionTracer:
|
|
|
110
119
|
|
|
111
120
|
# Ensure session row exists for incremental writes
|
|
112
121
|
if self.db:
|
|
113
|
-
await self.db.ensure_session(
|
|
122
|
+
await self.db.ensure_session(
|
|
123
|
+
session_id, created_at=self._current_trace.created_at, metadata=metadata or {}
|
|
124
|
+
)
|
|
114
125
|
|
|
115
126
|
# Trigger hooks
|
|
116
127
|
await self.hooks.trigger(
|
|
@@ -141,7 +152,7 @@ class SessionTracer:
|
|
|
141
152
|
step = SessionTimeStep(
|
|
142
153
|
step_id=step_id,
|
|
143
154
|
step_index=len(self._current_trace.session_time_steps),
|
|
144
|
-
timestamp=datetime.
|
|
155
|
+
timestamp=datetime.now(UTC),
|
|
145
156
|
turn_number=turn_number,
|
|
146
157
|
step_metadata=metadata or {},
|
|
147
158
|
)
|
|
@@ -186,7 +197,7 @@ class SessionTracer:
|
|
|
186
197
|
step = self._current_step
|
|
187
198
|
|
|
188
199
|
if step and step.completed_at is None:
|
|
189
|
-
step.completed_at = datetime.
|
|
200
|
+
step.completed_at = datetime.now(UTC)
|
|
190
201
|
|
|
191
202
|
# Trigger hooks
|
|
192
203
|
await self.hooks.trigger(
|
|
@@ -259,7 +270,7 @@ class SessionTracer:
|
|
|
259
270
|
|
|
260
271
|
async def record_message(
|
|
261
272
|
self,
|
|
262
|
-
content:
|
|
273
|
+
content: Any,
|
|
263
274
|
message_type: str,
|
|
264
275
|
event_time: float | None = None,
|
|
265
276
|
message_time: int | None = None,
|
|
@@ -277,11 +288,13 @@ class SessionTracer:
|
|
|
277
288
|
if self._current_trace is None:
|
|
278
289
|
raise RuntimeError("No active session")
|
|
279
290
|
|
|
291
|
+
normalised_content, content_str = self._normalise_message_content(content)
|
|
292
|
+
|
|
280
293
|
msg = SessionEventMarkovBlanketMessage(
|
|
281
|
-
content=
|
|
294
|
+
content=normalised_content,
|
|
282
295
|
message_type=message_type,
|
|
283
296
|
time_record=TimeRecord(
|
|
284
|
-
event_time=event_time or datetime.
|
|
297
|
+
event_time=event_time or datetime.now(UTC).timestamp(), message_time=message_time
|
|
285
298
|
),
|
|
286
299
|
metadata=metadata or {},
|
|
287
300
|
)
|
|
@@ -315,7 +328,7 @@ class SessionTracer:
|
|
|
315
328
|
self._current_trace.session_id,
|
|
316
329
|
timestep_db_id=timestep_db_id,
|
|
317
330
|
message_type=message_type,
|
|
318
|
-
content=
|
|
331
|
+
content=content_str,
|
|
319
332
|
event_time=msg.time_record.event_time,
|
|
320
333
|
message_time=msg.time_record.message_time,
|
|
321
334
|
metadata=msg.metadata,
|
|
@@ -323,6 +336,22 @@ class SessionTracer:
|
|
|
323
336
|
return message_id
|
|
324
337
|
return None
|
|
325
338
|
|
|
339
|
+
@staticmethod
|
|
340
|
+
def _normalise_message_content(content: Any) -> tuple[SessionMessageContent, str]:
|
|
341
|
+
if isinstance(content, SessionMessageContent):
|
|
342
|
+
return content, content.as_text()
|
|
343
|
+
if isinstance(content, str):
|
|
344
|
+
payload = SessionMessageContent(text=content)
|
|
345
|
+
return payload, payload.as_text()
|
|
346
|
+
try:
|
|
347
|
+
serialized = json.dumps(content, ensure_ascii=False)
|
|
348
|
+
payload = SessionMessageContent(json_payload=serialized)
|
|
349
|
+
return payload, serialized
|
|
350
|
+
except (TypeError, ValueError):
|
|
351
|
+
text = str(content)
|
|
352
|
+
payload = SessionMessageContent(text=text)
|
|
353
|
+
return payload, text
|
|
354
|
+
|
|
326
355
|
async def end_session(self, save: bool | None = None) -> SessionTrace:
|
|
327
356
|
"""End the current session.
|
|
328
357
|
|
|
@@ -339,7 +368,7 @@ class SessionTracer:
|
|
|
339
368
|
# End any open timesteps
|
|
340
369
|
for step in self._current_trace.session_time_steps:
|
|
341
370
|
if step.completed_at is None:
|
|
342
|
-
step.completed_at = datetime.
|
|
371
|
+
step.completed_at = datetime.now(UTC)
|
|
343
372
|
|
|
344
373
|
# Trigger pre-save hooks
|
|
345
374
|
await self.hooks.trigger("before_save", session=self._current_trace)
|
|
@@ -435,7 +464,14 @@ class SessionTracer:
|
|
|
435
464
|
# Reward recording helpers
|
|
436
465
|
# -------------------------------
|
|
437
466
|
|
|
438
|
-
async def record_outcome_reward(
|
|
467
|
+
async def record_outcome_reward(
|
|
468
|
+
self,
|
|
469
|
+
*,
|
|
470
|
+
total_reward: int,
|
|
471
|
+
achievements_count: int,
|
|
472
|
+
total_steps: int,
|
|
473
|
+
reward_metadata: dict[str, Any] | None = None,
|
|
474
|
+
) -> int | None:
|
|
439
475
|
"""Record an episode-level outcome reward for the current session."""
|
|
440
476
|
if self._current_trace is None:
|
|
441
477
|
raise RuntimeError("No active session")
|
|
@@ -462,7 +498,18 @@ class SessionTracer:
|
|
|
462
498
|
|
|
463
499
|
# StepMetrics removed in favor of event_rewards; use record_event_reward for per-turn shaped values
|
|
464
500
|
|
|
465
|
-
async def record_event_reward(
|
|
501
|
+
async def record_event_reward(
|
|
502
|
+
self,
|
|
503
|
+
*,
|
|
504
|
+
event_id: int,
|
|
505
|
+
message_id: int | None = None,
|
|
506
|
+
turn_number: int | None = None,
|
|
507
|
+
reward_value: float = 0.0,
|
|
508
|
+
reward_type: str | None = None,
|
|
509
|
+
key: str | None = None,
|
|
510
|
+
annotation: dict[str, Any] | None = None,
|
|
511
|
+
source: str | None = None,
|
|
512
|
+
) -> int | None:
|
|
466
513
|
"""Record a first-class event-level reward with optional annotations."""
|
|
467
514
|
if self._current_trace is None:
|
|
468
515
|
raise RuntimeError("No active session")
|
|
@@ -54,7 +54,10 @@ class TraceStorage(ABC):
|
|
|
54
54
|
|
|
55
55
|
@abstractmethod
|
|
56
56
|
async def get_model_usage(
|
|
57
|
-
self,
|
|
57
|
+
self,
|
|
58
|
+
start_date: datetime | None = None,
|
|
59
|
+
end_date: datetime | None = None,
|
|
60
|
+
model_name: str | None = None,
|
|
58
61
|
) -> Any:
|
|
59
62
|
"""Get model usage statistics.
|
|
60
63
|
|
|
@@ -85,6 +88,91 @@ class TraceStorage(ABC):
|
|
|
85
88
|
"""Close the storage connection."""
|
|
86
89
|
pass
|
|
87
90
|
|
|
91
|
+
# Incremental helpers -------------------------------------------------
|
|
92
|
+
|
|
93
|
+
@abstractmethod
|
|
94
|
+
async def ensure_session(
|
|
95
|
+
self,
|
|
96
|
+
session_id: str,
|
|
97
|
+
*,
|
|
98
|
+
created_at: datetime | None = None,
|
|
99
|
+
metadata: dict[str, Any] | None = None,
|
|
100
|
+
) -> None:
|
|
101
|
+
"""Ensure a session row exists for the given session id."""
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
@abstractmethod
|
|
105
|
+
async def ensure_timestep(
|
|
106
|
+
self,
|
|
107
|
+
session_id: str,
|
|
108
|
+
*,
|
|
109
|
+
step_id: str,
|
|
110
|
+
step_index: int,
|
|
111
|
+
turn_number: int | None = None,
|
|
112
|
+
started_at: datetime | None = None,
|
|
113
|
+
completed_at: datetime | None = None,
|
|
114
|
+
metadata: dict[str, Any] | None = None,
|
|
115
|
+
) -> int:
|
|
116
|
+
"""Ensure a timestep row exists and return its database id."""
|
|
117
|
+
pass
|
|
118
|
+
|
|
119
|
+
@abstractmethod
|
|
120
|
+
async def insert_event_row(
|
|
121
|
+
self,
|
|
122
|
+
session_id: str,
|
|
123
|
+
*,
|
|
124
|
+
timestep_db_id: int | None,
|
|
125
|
+
event: Any,
|
|
126
|
+
metadata_override: dict[str, Any] | None = None,
|
|
127
|
+
) -> int:
|
|
128
|
+
"""Insert an event and return its database id."""
|
|
129
|
+
pass
|
|
130
|
+
|
|
131
|
+
@abstractmethod
|
|
132
|
+
async def insert_message_row(
|
|
133
|
+
self,
|
|
134
|
+
session_id: str,
|
|
135
|
+
*,
|
|
136
|
+
timestep_db_id: int | None,
|
|
137
|
+
message_type: str,
|
|
138
|
+
content: Any,
|
|
139
|
+
event_time: float | None = None,
|
|
140
|
+
message_time: int | None = None,
|
|
141
|
+
metadata: dict[str, Any] | None = None,
|
|
142
|
+
) -> int:
|
|
143
|
+
"""Insert a message row linked to a session/timestep."""
|
|
144
|
+
pass
|
|
145
|
+
|
|
146
|
+
@abstractmethod
|
|
147
|
+
async def insert_outcome_reward(
|
|
148
|
+
self,
|
|
149
|
+
session_id: str,
|
|
150
|
+
*,
|
|
151
|
+
total_reward: int,
|
|
152
|
+
achievements_count: int,
|
|
153
|
+
total_steps: int,
|
|
154
|
+
reward_metadata: dict | None = None,
|
|
155
|
+
) -> int:
|
|
156
|
+
"""Record an outcome reward for a session."""
|
|
157
|
+
pass
|
|
158
|
+
|
|
159
|
+
@abstractmethod
|
|
160
|
+
async def insert_event_reward(
|
|
161
|
+
self,
|
|
162
|
+
session_id: str,
|
|
163
|
+
*,
|
|
164
|
+
event_id: int,
|
|
165
|
+
message_id: int | None = None,
|
|
166
|
+
turn_number: int | None = None,
|
|
167
|
+
reward_value: float = 0.0,
|
|
168
|
+
reward_type: str | None = None,
|
|
169
|
+
key: str | None = None,
|
|
170
|
+
annotation: dict[str, Any] | None = None,
|
|
171
|
+
source: str | None = None,
|
|
172
|
+
) -> int:
|
|
173
|
+
"""Record a reward tied to a specific event."""
|
|
174
|
+
pass
|
|
175
|
+
|
|
88
176
|
# Optional experiment management methods
|
|
89
177
|
async def create_experiment(
|
|
90
178
|
self,
|
|
@@ -9,16 +9,22 @@ from typing import Any
|
|
|
9
9
|
class StorageBackend(str, Enum):
|
|
10
10
|
"""Supported storage backends."""
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
TURSO_NATIVE = "turso_native"
|
|
13
13
|
SQLITE = "sqlite"
|
|
14
14
|
POSTGRES = "postgres" # Future support
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
def _is_enabled(value: str | None) -> bool:
|
|
18
|
+
if value is None:
|
|
19
|
+
return False
|
|
20
|
+
return value.lower() in {"1", "true", "yes", "on"}
|
|
21
|
+
|
|
22
|
+
|
|
17
23
|
@dataclass
|
|
18
24
|
class StorageConfig:
|
|
19
25
|
"""Configuration for storage backend."""
|
|
20
26
|
|
|
21
|
-
backend: StorageBackend = StorageBackend.
|
|
27
|
+
backend: StorageBackend = StorageBackend.TURSO_NATIVE
|
|
22
28
|
connection_string: str | None = None
|
|
23
29
|
|
|
24
30
|
# Turso-specific settings
|
|
@@ -34,23 +40,30 @@ class StorageConfig:
|
|
|
34
40
|
enable_compression: bool = os.getenv("STORAGE_COMPRESSION", "false").lower() == "true"
|
|
35
41
|
max_content_length: int = int(os.getenv("STORAGE_MAX_CONTENT_LENGTH", "1000000")) # 1MB
|
|
36
42
|
|
|
43
|
+
def __post_init__(self):
|
|
44
|
+
# Allow legacy override while keeping compatibility with existing TURSO_NATIVE env flag
|
|
45
|
+
native_env = os.getenv("TURSO_NATIVE")
|
|
46
|
+
native_flag = _is_enabled(native_env) if native_env is not None else None
|
|
47
|
+
|
|
48
|
+
if native_flag is False:
|
|
49
|
+
self.backend = StorageBackend.SQLITE
|
|
50
|
+
|
|
37
51
|
def get_connection_string(self) -> str:
|
|
38
52
|
"""Get the appropriate connection string for the backend."""
|
|
39
53
|
if self.connection_string:
|
|
40
54
|
return self.connection_string
|
|
41
55
|
|
|
42
|
-
if self.backend == StorageBackend.
|
|
56
|
+
if self.backend == StorageBackend.TURSO_NATIVE:
|
|
43
57
|
return self.turso_url
|
|
44
|
-
|
|
58
|
+
if self.backend == StorageBackend.SQLITE:
|
|
45
59
|
return "sqlite+aiosqlite:///traces.db"
|
|
46
|
-
|
|
60
|
+
if self.backend == StorageBackend.POSTGRES:
|
|
47
61
|
return os.getenv("POSTGRES_URL", "postgresql+asyncpg://localhost/traces")
|
|
48
|
-
|
|
49
|
-
raise ValueError(f"Unknown backend: {self.backend}")
|
|
62
|
+
raise ValueError(f"Unknown backend: {self.backend}")
|
|
50
63
|
|
|
51
64
|
def get_backend_config(self) -> dict[str, Any]:
|
|
52
65
|
"""Get backend-specific configuration."""
|
|
53
|
-
if self.backend == StorageBackend.
|
|
66
|
+
if self.backend == StorageBackend.TURSO_NATIVE:
|
|
54
67
|
config = {}
|
|
55
68
|
if self.turso_auth_token:
|
|
56
69
|
config["auth_token"] = self.turso_auth_token
|