synth-ai 0.2.13.dev2__py3-none-any.whl → 0.2.16__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/README.md +1 -0
- examples/multi_step/SFT_README.md +147 -0
- examples/multi_step/configs/README_verilog_rl.md +77 -0
- examples/multi_step/configs/VERILOG_REWARDS.md +90 -0
- examples/multi_step/configs/VERILOG_RL_CHECKLIST.md +183 -0
- examples/multi_step/configs/crafter_eval_synth_qwen4b.toml +35 -0
- examples/multi_step/configs/crafter_eval_text_only_groq_qwen32b.toml +36 -0
- examples/multi_step/configs/crafter_rl_stepwise_hosted_judge.toml +12 -11
- examples/multi_step/configs/crafter_sft_qwen30b_lora.toml +62 -0
- examples/multi_step/configs/crafter_synth_backend.md +40 -0
- examples/multi_step/configs/verilog_eval_groq_qwen32b.toml +31 -0
- examples/multi_step/configs/verilog_eval_synth_qwen8b.toml +33 -0
- examples/multi_step/configs/verilog_rl_lora.toml +190 -0
- examples/multi_step/convert_traces_to_sft.py +84 -0
- examples/multi_step/judges/crafter_backend_judge.py +220 -0
- examples/multi_step/judges/verilog_backend_judge.py +234 -0
- examples/multi_step/readme.md +48 -0
- examples/multi_step/run_sft_qwen30b.sh +45 -0
- examples/multi_step/verilog_rl_lora.md +218 -0
- examples/qwen_coder/configs/coder_lora_30b.toml +3 -2
- examples/qwen_coder/configs/coder_lora_4b.toml +2 -1
- examples/qwen_coder/configs/coder_lora_small.toml +2 -1
- examples/qwen_vl/BUGS_AND_FIXES.md +232 -0
- examples/qwen_vl/IMAGE_VALIDATION_COMPLETE.md +271 -0
- examples/qwen_vl/IMAGE_VALIDATION_SUMMARY.md +260 -0
- examples/qwen_vl/INFERENCE_SFT_TESTS.md +412 -0
- examples/qwen_vl/NEXT_STEPS_2B.md +325 -0
- examples/qwen_vl/QUICKSTART.md +327 -0
- examples/qwen_vl/QUICKSTART_RL_VISION.md +110 -0
- examples/qwen_vl/README.md +154 -0
- examples/qwen_vl/RL_VISION_COMPLETE.md +475 -0
- examples/qwen_vl/RL_VISION_TESTING.md +333 -0
- examples/qwen_vl/SDK_VISION_INTEGRATION.md +328 -0
- examples/qwen_vl/SETUP_COMPLETE.md +275 -0
- examples/qwen_vl/VISION_TESTS_COMPLETE.md +490 -0
- examples/qwen_vl/VLM_PIPELINE_COMPLETE.md +242 -0
- examples/qwen_vl/__init__.py +2 -0
- examples/qwen_vl/collect_data_via_cli.md +423 -0
- examples/qwen_vl/collect_vision_traces.py +368 -0
- examples/qwen_vl/configs/crafter_rl_vision_qwen3vl4b.toml +127 -0
- examples/qwen_vl/configs/crafter_vlm_sft_example.toml +60 -0
- examples/qwen_vl/configs/eval_gpt4o_mini_vision.toml +43 -0
- examples/qwen_vl/configs/eval_gpt4o_vision_proper.toml +29 -0
- examples/qwen_vl/configs/eval_gpt5nano_vision.toml +45 -0
- examples/qwen_vl/configs/eval_qwen2vl_vision.toml +44 -0
- examples/qwen_vl/configs/filter_qwen2vl_sft.toml +50 -0
- examples/qwen_vl/configs/filter_vision_sft.toml +53 -0
- examples/qwen_vl/configs/filter_vision_test.toml +8 -0
- examples/qwen_vl/configs/sft_qwen3_vl_2b_test.toml +54 -0
- examples/qwen_vl/crafter_gpt5nano_agent.py +308 -0
- examples/qwen_vl/crafter_qwen_vl_agent.py +300 -0
- examples/qwen_vl/run_vision_comparison.sh +62 -0
- examples/qwen_vl/run_vision_sft_pipeline.sh +175 -0
- examples/qwen_vl/test_image_validation.py +201 -0
- examples/qwen_vl/test_sft_vision_data.py +110 -0
- examples/rl/README.md +1 -1
- examples/rl/configs/eval_base_qwen.toml +17 -0
- examples/rl/configs/eval_rl_qwen.toml +13 -0
- examples/rl/configs/rl_from_base_qwen.toml +37 -0
- examples/rl/configs/rl_from_base_qwen17.toml +76 -0
- examples/rl/configs/rl_from_ft_qwen.toml +37 -0
- examples/rl/run_eval.py +436 -0
- examples/rl/run_rl_and_save.py +111 -0
- examples/rl/task_app/README.md +22 -0
- examples/rl/task_app/math_single_step.py +990 -0
- examples/rl/task_app/math_task_app.py +111 -0
- examples/sft/README.md +5 -5
- examples/sft/configs/crafter_fft_qwen0p6b.toml +4 -2
- examples/sft/configs/crafter_lora_qwen0p6b.toml +4 -3
- examples/sft/evaluate.py +4 -4
- examples/sft/export_dataset.py +7 -4
- examples/sft/generate_traces.py +2 -0
- examples/swe/task_app/README.md +1 -1
- examples/swe/task_app/grpo_swe_mini.py +1 -1
- examples/swe/task_app/grpo_swe_mini_task_app.py +0 -12
- examples/swe/task_app/hosted/envs/mini_swe/environment.py +13 -13
- examples/swe/task_app/hosted/policy_routes.py +0 -2
- examples/swe/task_app/hosted/rollout.py +2 -8
- examples/task_apps/IMAGE_ONLY_EVAL_QUICKSTART.md +258 -0
- examples/task_apps/crafter/CREATE_SFT_DATASET.md +273 -0
- examples/task_apps/crafter/EVAL_IMAGE_ONLY_RESULTS.md +152 -0
- examples/task_apps/crafter/FILTER_COMMAND_STATUS.md +174 -0
- examples/task_apps/crafter/FILTER_COMMAND_SUCCESS.md +268 -0
- examples/task_apps/crafter/QUERY_EXAMPLES.md +203 -0
- examples/task_apps/crafter/README_IMAGE_ONLY_EVAL.md +316 -0
- examples/task_apps/crafter/eval_image_only_gpt4o.toml +28 -0
- examples/task_apps/crafter/eval_text_only_groq_llama.toml +36 -0
- examples/task_apps/crafter/filter_sft_dataset.toml +16 -0
- examples/task_apps/crafter/task_app/__init__.py +3 -0
- examples/task_apps/crafter/task_app/grpo_crafter.py +309 -14
- examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/environment.py +10 -0
- examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/policy.py +75 -4
- examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/react_agent.py +17 -2
- examples/task_apps/crafter/task_app/synth_envs_hosted/inference/openai_client.py +55 -3
- examples/task_apps/crafter/task_app/synth_envs_hosted/policy_routes.py +114 -32
- examples/task_apps/crafter/task_app/synth_envs_hosted/rollout.py +127 -27
- examples/task_apps/crafter/task_app/synth_envs_hosted/utils.py +156 -0
- examples/task_apps/enron/__init__.py +1 -0
- examples/task_apps/enron/filter_sft.toml +5 -0
- examples/task_apps/enron/tests/__init__.py +2 -0
- examples/task_apps/enron/tests/integration/__init__.py +2 -0
- examples/task_apps/enron/tests/integration/test_enron_eval.py +2 -0
- examples/task_apps/enron/tests/unit/__init__.py +2 -0
- examples/task_apps/pokemon_red/EVAL_IMAGE_ONLY_COMPLETE.md +283 -0
- examples/task_apps/pokemon_red/EVAL_IMAGE_ONLY_STATUS.md +155 -0
- examples/task_apps/pokemon_red/README_IMAGE_ONLY_EVAL.md +415 -0
- examples/task_apps/pokemon_red/eval_image_only_gpt4o.toml +29 -0
- examples/task_apps/pokemon_red/pallet_town_rl_config.toml +2 -0
- examples/task_apps/pokemon_red/task_app.py +199 -6
- examples/task_apps/pokemon_red/test_pallet_town_rewards.py +2 -0
- examples/task_apps/sokoban/filter_sft.toml +5 -0
- examples/task_apps/sokoban/tests/__init__.py +2 -0
- examples/task_apps/sokoban/tests/integration/__init__.py +2 -0
- examples/task_apps/sokoban/tests/unit/__init__.py +2 -0
- examples/task_apps/verilog/eval_groq_qwen32b.toml +8 -4
- examples/task_apps/verilog/filter_sft.toml +5 -0
- examples/task_apps/verilog/task_app/grpo_verilog.py +258 -23
- examples/task_apps/verilog/tests/__init__.py +2 -0
- examples/task_apps/verilog/tests/integration/__init__.py +2 -0
- examples/task_apps/verilog/tests/integration/test_verilog_eval.py +2 -0
- examples/task_apps/verilog/tests/unit/__init__.py +2 -0
- examples/vlm/README.md +3 -3
- examples/vlm/configs/crafter_vlm_gpt4o.toml +2 -0
- examples/vlm/crafter_openai_vlm_agent.py +3 -5
- examples/vlm/filter_image_rows.py +1 -1
- examples/vlm/run_crafter_vlm_benchmark.py +2 -2
- examples/warming_up_to_rl/_utils.py +92 -0
- examples/warming_up_to_rl/analyze_trace_db.py +1 -1
- examples/warming_up_to_rl/configs/crafter_fft.toml +2 -0
- examples/warming_up_to_rl/configs/crafter_fft_4b.toml +2 -0
- examples/warming_up_to_rl/configs/eval_fft_qwen4b.toml +2 -0
- examples/warming_up_to_rl/configs/eval_groq_qwen32b.toml +2 -0
- examples/warming_up_to_rl/configs/eval_modal_qwen4b.toml +2 -1
- examples/warming_up_to_rl/configs/rl_from_base_qwen4b.toml +2 -1
- examples/warming_up_to_rl/configs/rl_from_ft.toml +2 -0
- examples/warming_up_to_rl/export_trace_sft.py +174 -60
- examples/warming_up_to_rl/groq_test.py +2 -0
- examples/warming_up_to_rl/readme.md +63 -132
- examples/warming_up_to_rl/run_fft_and_save.py +1 -1
- examples/warming_up_to_rl/run_local_rollout.py +2 -0
- examples/warming_up_to_rl/run_local_rollout_modal.py +2 -0
- examples/warming_up_to_rl/run_local_rollout_parallel.py +2 -0
- examples/warming_up_to_rl/run_local_rollout_traced.py +2 -0
- examples/warming_up_to_rl/run_rl_and_save.py +1 -1
- examples/warming_up_to_rl/run_rollout_remote.py +2 -0
- examples/warming_up_to_rl/task_app/README.md +42 -0
- examples/warming_up_to_rl/task_app/grpo_crafter.py +696 -0
- examples/warming_up_to_rl/task_app/grpo_crafter_task_app.py +135 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/README.md +173 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/__init__.py +5 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/branching.py +143 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/environment_routes.py +1226 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/__init__.py +1 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/__init__.py +6 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/app.py +1 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/environment.py +522 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/policy.py +478 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/react_agent.py +108 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/shared.py +305 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/tools.py +47 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/hosted_app.py +204 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/inference/__init__.py +5 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/inference/openai_client.py +618 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/main.py +100 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/policy_routes.py +1081 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/registry.py +195 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/rollout.py +1861 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/storage/__init__.py +5 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/storage/volume.py +211 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/test_agents.py +161 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/test_service.py +137 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/utils.py +62 -0
- synth_ai/__init__.py +44 -30
- synth_ai/_utils/__init__.py +47 -0
- synth_ai/_utils/base_url.py +10 -0
- synth_ai/_utils/http.py +10 -0
- synth_ai/_utils/prompts.py +10 -0
- synth_ai/_utils/task_app_state.py +12 -0
- synth_ai/_utils/user_config.py +10 -0
- synth_ai/api/models/supported.py +145 -7
- synth_ai/api/train/__init__.py +13 -1
- synth_ai/api/train/cli.py +30 -7
- synth_ai/api/train/config_finder.py +18 -11
- synth_ai/api/train/env_resolver.py +13 -10
- synth_ai/cli/__init__.py +66 -49
- synth_ai/cli/_modal_wrapper.py +9 -6
- synth_ai/cli/_typer_patch.py +0 -2
- synth_ai/cli/_validate_task_app.py +22 -4
- synth_ai/cli/legacy_root_backup.py +3 -1
- synth_ai/cli/lib/__init__.py +10 -0
- synth_ai/cli/lib/task_app_discovery.py +7 -0
- synth_ai/cli/lib/task_app_env.py +518 -0
- synth_ai/cli/recent.py +1 -0
- synth_ai/cli/setup.py +266 -0
- synth_ai/cli/task_app_deploy.py +16 -0
- synth_ai/cli/task_app_list.py +25 -0
- synth_ai/cli/task_app_modal_serve.py +16 -0
- synth_ai/cli/task_app_serve.py +18 -0
- synth_ai/cli/task_apps.py +392 -141
- synth_ai/cli/train.py +18 -0
- synth_ai/cli/tui.py +62 -0
- synth_ai/demos/__init__.py +10 -0
- synth_ai/demos/core/__init__.py +28 -1
- synth_ai/demos/crafter/__init__.py +1 -0
- synth_ai/demos/crafter/crafter_fft_4b.toml +55 -0
- synth_ai/demos/crafter/grpo_crafter_task_app.py +185 -0
- synth_ai/demos/crafter/rl_from_base_qwen4b.toml +74 -0
- synth_ai/demos/demo_registry.py +176 -0
- synth_ai/demos/demo_task_apps/crafter/grpo_crafter_task_app.py +1 -1
- synth_ai/demos/math/__init__.py +1 -0
- synth_ai/demos/math/_common.py +16 -0
- synth_ai/demos/math/app.py +38 -0
- synth_ai/demos/math/config.toml +76 -0
- synth_ai/demos/math/deploy_modal.py +54 -0
- synth_ai/demos/math/modal_task_app.py +702 -0
- synth_ai/demos/math/task_app_entry.py +51 -0
- synth_ai/environments/environment/core.py +7 -1
- synth_ai/environments/examples/bandit/engine.py +0 -1
- synth_ai/environments/examples/bandit/environment.py +0 -1
- synth_ai/environments/examples/crafter_classic/environment.py +1 -1
- synth_ai/environments/examples/verilog/engine.py +76 -10
- synth_ai/environments/examples/wordle/environment.py +0 -1
- synth_ai/evals/base.py +16 -5
- synth_ai/evals/client.py +1 -1
- synth_ai/inference/client.py +1 -1
- synth_ai/learning/client.py +1 -1
- synth_ai/learning/health.py +1 -1
- synth_ai/learning/jobs.py +1 -1
- synth_ai/learning/rl/client.py +1 -1
- synth_ai/learning/rl/env_keys.py +1 -1
- synth_ai/learning/rl/secrets.py +1 -1
- synth_ai/learning/sft/client.py +1 -1
- synth_ai/learning/sft/data.py +407 -4
- synth_ai/learning/validators.py +4 -1
- synth_ai/task/__init__.py +11 -1
- synth_ai/task/apps/__init__.py +5 -2
- synth_ai/task/config.py +259 -0
- synth_ai/task/contracts.py +15 -2
- synth_ai/task/rubrics/__init__.py +4 -2
- synth_ai/task/rubrics/loaders.py +27 -4
- synth_ai/task/rubrics/scoring.py +3 -0
- synth_ai/task/rubrics.py +219 -0
- synth_ai/task/trace_correlation_helpers.py +328 -0
- synth_ai/task/tracing_utils.py +14 -3
- synth_ai/task/validators.py +145 -2
- synth_ai/tracing_v3/config.py +15 -13
- synth_ai/tracing_v3/constants.py +21 -0
- synth_ai/tracing_v3/db_config.py +3 -1
- synth_ai/tracing_v3/decorators.py +10 -7
- synth_ai/tracing_v3/session_tracer.py +10 -0
- synth_ai/tracing_v3/turso/daemon.py +2 -2
- synth_ai/tracing_v3/turso/native_manager.py +108 -77
- synth_ai/tracing_v3/utils.py +1 -1
- synth_ai/tui/__init__.py +5 -0
- synth_ai/tui/__main__.py +13 -0
- synth_ai/tui/cli/__init__.py +1 -0
- synth_ai/tui/cli/query_experiments.py +164 -0
- synth_ai/tui/cli/query_experiments_v3.py +164 -0
- synth_ai/tui/dashboard.py +911 -0
- synth_ai/utils/__init__.py +101 -0
- synth_ai/utils/base_url.py +94 -0
- synth_ai/utils/cli.py +131 -0
- synth_ai/utils/env.py +287 -0
- synth_ai/utils/http.py +169 -0
- synth_ai/utils/modal.py +308 -0
- synth_ai/utils/process.py +212 -0
- synth_ai/utils/prompts.py +39 -0
- synth_ai/utils/sqld.py +122 -0
- synth_ai/utils/task_app_discovery.py +882 -0
- synth_ai/utils/task_app_env.py +186 -0
- synth_ai/utils/task_app_state.py +318 -0
- synth_ai/utils/user_config.py +137 -0
- synth_ai/v0/config/__init__.py +1 -5
- synth_ai/v0/config/base_url.py +1 -7
- synth_ai/v0/tracing/config.py +1 -1
- synth_ai/v0/tracing/decorators.py +1 -1
- synth_ai/v0/tracing/upload.py +1 -1
- synth_ai/v0/tracing_v1/config.py +1 -1
- synth_ai/v0/tracing_v1/decorators.py +1 -1
- synth_ai/v0/tracing_v1/upload.py +1 -1
- {synth_ai-0.2.13.dev2.dist-info → synth_ai-0.2.16.dist-info}/METADATA +85 -31
- {synth_ai-0.2.13.dev2.dist-info → synth_ai-0.2.16.dist-info}/RECORD +286 -135
- synth_ai/cli/man.py +0 -106
- synth_ai/compound/cais.py +0 -0
- synth_ai/core/experiment.py +0 -13
- synth_ai/core/system.py +0 -15
- synth_ai/demo_registry.py +0 -295
- synth_ai/handshake.py +0 -109
- synth_ai/http.py +0 -26
- {synth_ai-0.2.13.dev2.dist-info → synth_ai-0.2.16.dist-info}/WHEEL +0 -0
- {synth_ai-0.2.13.dev2.dist-info → synth_ai-0.2.16.dist-info}/entry_points.txt +0 -0
- {synth_ai-0.2.13.dev2.dist-info → synth_ai-0.2.16.dist-info}/licenses/LICENSE +0 -0
- {synth_ai-0.2.13.dev2.dist-info → synth_ai-0.2.16.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
"""Helpers for trace correlation ID extraction and inclusion in task apps.
|
|
2
|
+
|
|
3
|
+
This module provides utilities for task apps to:
|
|
4
|
+
1. Extract trace_correlation_id from rollout requests
|
|
5
|
+
2. Include trace_correlation_id in rollout responses (3 required locations)
|
|
6
|
+
|
|
7
|
+
See monorepo/trace_creation_and_judgement.txt "Fatal Guards" section for requirements.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import importlib
|
|
11
|
+
import logging
|
|
12
|
+
from typing import Any, cast
|
|
13
|
+
from urllib.parse import parse_qs, urlparse
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def extract_trace_correlation_id(
|
|
19
|
+
policy_config: dict[str, Any],
|
|
20
|
+
inference_url: str | None = None,
|
|
21
|
+
mode: Any = None
|
|
22
|
+
) -> str | None:
|
|
23
|
+
"""
|
|
24
|
+
Extract trace_correlation_id from policy config or inference URL.
|
|
25
|
+
|
|
26
|
+
This is the standardized method for all task apps to extract the correlation ID
|
|
27
|
+
that the RL trainer generates and passes to the task app.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
policy_config: Policy configuration dict from RolloutRequest.policy.config
|
|
31
|
+
inference_url: Inference URL (optional, used as fallback)
|
|
32
|
+
mode: RolloutMode or string ("rl" or "eval"). Controls warning behavior -
|
|
33
|
+
warnings only logged for RL mode, not EVAL mode.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
trace_correlation_id if found, None otherwise
|
|
37
|
+
|
|
38
|
+
Extraction order:
|
|
39
|
+
1. policy_config["trace_correlation_id"] (preferred)
|
|
40
|
+
2. policy_config["trace"] (legacy fallback)
|
|
41
|
+
3. URL query param ?cid=... (fallback)
|
|
42
|
+
4. URL query param ?trace_correlation_id=... (fallback)
|
|
43
|
+
"""
|
|
44
|
+
# Try policy_config first (preferred method)
|
|
45
|
+
candidates: list[Any] = [
|
|
46
|
+
policy_config.get("trace_correlation_id"),
|
|
47
|
+
policy_config.get("trace"),
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
logger.debug(
|
|
51
|
+
"extract_trace_correlation_id: policy_cfg keys=%s candidates=%s",
|
|
52
|
+
sorted(policy_config.keys()),
|
|
53
|
+
candidates,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
for candidate in candidates:
|
|
57
|
+
if isinstance(candidate, str):
|
|
58
|
+
stripped = candidate.strip()
|
|
59
|
+
if stripped:
|
|
60
|
+
logger.info(
|
|
61
|
+
"extract_trace_correlation_id: extracted from policy_config=%s",
|
|
62
|
+
stripped
|
|
63
|
+
)
|
|
64
|
+
return stripped
|
|
65
|
+
|
|
66
|
+
# Determine if we're in EVAL mode (trace_correlation_id not required for eval)
|
|
67
|
+
rollout_mode_cls: Any | None = None
|
|
68
|
+
try:
|
|
69
|
+
contracts_module = importlib.import_module("synth_ai.task.contracts")
|
|
70
|
+
rollout_mode_cls = getattr(contracts_module, "RolloutMode", None)
|
|
71
|
+
except Exception:
|
|
72
|
+
rollout_mode_cls = None
|
|
73
|
+
|
|
74
|
+
is_eval_mode = False
|
|
75
|
+
if rollout_mode_cls is not None:
|
|
76
|
+
try:
|
|
77
|
+
is_eval_mode = (
|
|
78
|
+
mode == "eval"
|
|
79
|
+
or mode == rollout_mode_cls.EVAL
|
|
80
|
+
or getattr(mode, "value", None) == "eval"
|
|
81
|
+
)
|
|
82
|
+
except Exception:
|
|
83
|
+
is_eval_mode = mode == "eval"
|
|
84
|
+
else:
|
|
85
|
+
is_eval_mode = mode == "eval" or getattr(mode, "value", None) == "eval"
|
|
86
|
+
|
|
87
|
+
# Fallback: try to extract from inference_url query params
|
|
88
|
+
if not inference_url or not isinstance(inference_url, str):
|
|
89
|
+
if is_eval_mode:
|
|
90
|
+
logger.debug(
|
|
91
|
+
"extract_trace_correlation_id: no correlation ID found in policy_config "
|
|
92
|
+
"and no inference_url provided (EVAL mode - expected)"
|
|
93
|
+
)
|
|
94
|
+
else:
|
|
95
|
+
logger.warning(
|
|
96
|
+
"extract_trace_correlation_id: no correlation ID found in policy_config "
|
|
97
|
+
"and no inference_url provided"
|
|
98
|
+
)
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
parsed = urlparse(inference_url)
|
|
103
|
+
query_params = cast(dict[str, list[str]], parse_qs(parsed.query or ""))
|
|
104
|
+
# Try multiple possible query param names
|
|
105
|
+
for param_name in ["cid", "trace_correlation_id", "trace"]:
|
|
106
|
+
values = query_params.get(param_name)
|
|
107
|
+
if not values:
|
|
108
|
+
continue
|
|
109
|
+
for value in values:
|
|
110
|
+
if isinstance(value, str) and value.strip():
|
|
111
|
+
correlation_id = value.strip()
|
|
112
|
+
logger.info(
|
|
113
|
+
"extract_trace_correlation_id: extracted from URL param %s=%s",
|
|
114
|
+
param_name,
|
|
115
|
+
correlation_id,
|
|
116
|
+
)
|
|
117
|
+
return correlation_id
|
|
118
|
+
except Exception as e:
|
|
119
|
+
logger.warning(
|
|
120
|
+
"extract_trace_correlation_id: failed to parse inference_url=%s error=%s",
|
|
121
|
+
inference_url,
|
|
122
|
+
e,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
if is_eval_mode:
|
|
126
|
+
logger.debug(
|
|
127
|
+
"extract_trace_correlation_id: no trace_correlation_id found in "
|
|
128
|
+
"policy_config or inference_url=%s (EVAL mode - expected)",
|
|
129
|
+
inference_url,
|
|
130
|
+
)
|
|
131
|
+
else:
|
|
132
|
+
logger.warning(
|
|
133
|
+
"extract_trace_correlation_id: no trace_correlation_id found in "
|
|
134
|
+
"policy_config or inference_url=%s",
|
|
135
|
+
inference_url,
|
|
136
|
+
)
|
|
137
|
+
return None
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def validate_trace_correlation_id(
|
|
141
|
+
trace_correlation_id: str | None,
|
|
142
|
+
run_id: str,
|
|
143
|
+
policy_config: dict[str, Any],
|
|
144
|
+
fatal: bool = False
|
|
145
|
+
) -> str | None:
|
|
146
|
+
"""
|
|
147
|
+
Validate that trace_correlation_id was successfully extracted.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
trace_correlation_id: The extracted correlation ID (or None)
|
|
151
|
+
run_id: Rollout run_id for logging
|
|
152
|
+
policy_config: Policy configuration for debugging
|
|
153
|
+
fatal: If True, raise ValueError on missing ID. If False, log error only.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
trace_correlation_id if present, None if missing (when fatal=False)
|
|
157
|
+
|
|
158
|
+
Raises:
|
|
159
|
+
ValueError: If trace_correlation_id is missing and fatal=True
|
|
160
|
+
"""
|
|
161
|
+
if not trace_correlation_id:
|
|
162
|
+
error_msg = (
|
|
163
|
+
f"🚨 CRITICAL: Cannot extract trace_correlation_id!\n"
|
|
164
|
+
"\n"
|
|
165
|
+
f"Run ID: {run_id}\n"
|
|
166
|
+
f"Policy config keys: {sorted(policy_config.keys())}\n"
|
|
167
|
+
f"Inference URL: {policy_config.get('inference_url', 'NOT_SET')}\n"
|
|
168
|
+
"\n"
|
|
169
|
+
"Checked:\n"
|
|
170
|
+
f"1. policy_config['trace_correlation_id']: {policy_config.get('trace_correlation_id')}\n"
|
|
171
|
+
f"2. policy_config['trace']: {policy_config.get('trace')}\n"
|
|
172
|
+
f"3. inference_url query params\n"
|
|
173
|
+
"\n"
|
|
174
|
+
"Task app CANNOT proceed without trace_correlation_id.\n"
|
|
175
|
+
"This indicates the RL trainer is not sending it correctly.\n"
|
|
176
|
+
"\n"
|
|
177
|
+
"See monorepo/trace_creation_and_judgement.txt 'Fatal Guards' section.\n"
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
if fatal:
|
|
181
|
+
raise ValueError(error_msg)
|
|
182
|
+
else:
|
|
183
|
+
logger.error(error_msg)
|
|
184
|
+
|
|
185
|
+
return trace_correlation_id
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def include_trace_correlation_id_in_response(
|
|
189
|
+
response_data: dict[str, Any],
|
|
190
|
+
trace_correlation_id: str | None,
|
|
191
|
+
run_id: str
|
|
192
|
+
) -> dict[str, Any]:
|
|
193
|
+
"""
|
|
194
|
+
Include trace_correlation_id in all required locations of rollout response.
|
|
195
|
+
|
|
196
|
+
Required locations (per Fatal Guards section):
|
|
197
|
+
1. Top-level response["trace_correlation_id"]
|
|
198
|
+
2. response["pipeline_metadata"]["trace_correlation_id"]
|
|
199
|
+
3. Each trajectory["trace_correlation_id"]
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
response_data: RolloutResponse dict (from .model_dump())
|
|
203
|
+
trace_correlation_id: The correlation ID to include
|
|
204
|
+
run_id: Rollout run_id for logging
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
Modified response_data with trace_correlation_id in all required places
|
|
208
|
+
"""
|
|
209
|
+
if not trace_correlation_id:
|
|
210
|
+
logger.error(
|
|
211
|
+
"include_trace_correlation_id_in_response: missing trace_correlation_id "
|
|
212
|
+
"for run_id=%s - cannot include in response",
|
|
213
|
+
run_id
|
|
214
|
+
)
|
|
215
|
+
return response_data
|
|
216
|
+
|
|
217
|
+
# 1. Add to top-level (REQUIRED)
|
|
218
|
+
if "trace_correlation_id" not in response_data:
|
|
219
|
+
response_data["trace_correlation_id"] = trace_correlation_id
|
|
220
|
+
logger.info(
|
|
221
|
+
"include_trace_correlation_id: added to top-level run_id=%s cid=%s",
|
|
222
|
+
run_id,
|
|
223
|
+
trace_correlation_id
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
# 2. Add to pipeline_metadata (REQUIRED)
|
|
227
|
+
pipeline_meta = response_data.get("pipeline_metadata")
|
|
228
|
+
if not isinstance(pipeline_meta, dict):
|
|
229
|
+
pipeline_meta = {}
|
|
230
|
+
response_data["pipeline_metadata"] = pipeline_meta
|
|
231
|
+
|
|
232
|
+
if "trace_correlation_id" not in pipeline_meta:
|
|
233
|
+
pipeline_meta["trace_correlation_id"] = trace_correlation_id
|
|
234
|
+
logger.info(
|
|
235
|
+
"include_trace_correlation_id: added to pipeline_metadata run_id=%s cid=%s",
|
|
236
|
+
run_id,
|
|
237
|
+
trace_correlation_id
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
# 3. Add to each trajectory (REQUIRED)
|
|
241
|
+
trajectories = response_data.get("trajectories", [])
|
|
242
|
+
if isinstance(trajectories, list):
|
|
243
|
+
for idx, traj in enumerate(trajectories):
|
|
244
|
+
if isinstance(traj, dict) and "trace_correlation_id" not in traj:
|
|
245
|
+
traj["trace_correlation_id"] = trace_correlation_id
|
|
246
|
+
logger.debug(
|
|
247
|
+
"include_trace_correlation_id: added to trajectory[%d] run_id=%s cid=%s",
|
|
248
|
+
idx,
|
|
249
|
+
run_id,
|
|
250
|
+
trace_correlation_id
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
logger.info(
|
|
254
|
+
"include_trace_correlation_id: completed run_id=%s cid=%s "
|
|
255
|
+
"added to %d locations (top-level, metadata, %d trajectories)",
|
|
256
|
+
run_id,
|
|
257
|
+
trace_correlation_id,
|
|
258
|
+
2 + len(trajectories),
|
|
259
|
+
len(trajectories)
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
return response_data
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def verify_trace_correlation_id_in_response(
|
|
266
|
+
response_data: dict[str, Any],
|
|
267
|
+
expected_correlation_id: str | None,
|
|
268
|
+
run_id: str
|
|
269
|
+
) -> bool:
|
|
270
|
+
"""
|
|
271
|
+
Verify that trace_correlation_id is present in all required locations.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
response_data: RolloutResponse dict to verify
|
|
275
|
+
expected_correlation_id: The correlation ID that should be present
|
|
276
|
+
run_id: Rollout run_id for logging
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
True if all required locations have the correlation ID, False otherwise
|
|
280
|
+
"""
|
|
281
|
+
if not expected_correlation_id:
|
|
282
|
+
logger.error(
|
|
283
|
+
"verify_trace_correlation_id: no expected_correlation_id provided for run_id=%s",
|
|
284
|
+
run_id
|
|
285
|
+
)
|
|
286
|
+
return False
|
|
287
|
+
|
|
288
|
+
errors = []
|
|
289
|
+
|
|
290
|
+
# Check top-level
|
|
291
|
+
if response_data.get("trace_correlation_id") != expected_correlation_id:
|
|
292
|
+
errors.append(
|
|
293
|
+
f"Top-level missing or mismatch: "
|
|
294
|
+
f"expected={expected_correlation_id} actual={response_data.get('trace_correlation_id')}"
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
# Check pipeline_metadata
|
|
298
|
+
pipeline_meta = response_data.get("pipeline_metadata", {})
|
|
299
|
+
if not isinstance(pipeline_meta, dict) or pipeline_meta.get("trace_correlation_id") != expected_correlation_id:
|
|
300
|
+
errors.append(
|
|
301
|
+
f"pipeline_metadata missing or mismatch: "
|
|
302
|
+
f"expected={expected_correlation_id} actual={pipeline_meta.get('trace_correlation_id') if isinstance(pipeline_meta, dict) else 'NOT_A_DICT'}"
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
# Check trajectories
|
|
306
|
+
trajectories = response_data.get("trajectories", [])
|
|
307
|
+
if isinstance(trajectories, list):
|
|
308
|
+
for idx, traj in enumerate(trajectories):
|
|
309
|
+
if isinstance(traj, dict) and traj.get("trace_correlation_id") != expected_correlation_id:
|
|
310
|
+
errors.append(
|
|
311
|
+
f"trajectory[{idx}] missing or mismatch: "
|
|
312
|
+
f"expected={expected_correlation_id} actual={traj.get('trace_correlation_id')}"
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
if errors:
|
|
316
|
+
logger.error(
|
|
317
|
+
"verify_trace_correlation_id: FAILED run_id=%s\n%s",
|
|
318
|
+
run_id,
|
|
319
|
+
"\n".join(errors)
|
|
320
|
+
)
|
|
321
|
+
return False
|
|
322
|
+
|
|
323
|
+
logger.info(
|
|
324
|
+
"verify_trace_correlation_id: PASSED run_id=%s cid=%s",
|
|
325
|
+
run_id,
|
|
326
|
+
expected_correlation_id
|
|
327
|
+
)
|
|
328
|
+
return True
|
synth_ai/task/tracing_utils.py
CHANGED
|
@@ -4,9 +4,12 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
6
|
from collections.abc import Callable
|
|
7
|
+
from datetime import datetime
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
from typing import Any
|
|
9
10
|
|
|
11
|
+
from synth_ai.tracing_v3.constants import TRACE_DB_DIR, canonical_trace_db_name
|
|
12
|
+
|
|
10
13
|
|
|
11
14
|
def tracing_env_enabled(default: bool = False) -> bool:
|
|
12
15
|
"""Return True when tracing is enabled for task apps via environment variable."""
|
|
@@ -40,9 +43,17 @@ def resolve_tracing_db_url() -> str | None:
|
|
|
40
43
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
41
44
|
return f"sqlite+aiosqlite:///{path}"
|
|
42
45
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
existing = os.getenv("TASKAPP_TRACE_DB_PATH")
|
|
47
|
+
if existing:
|
|
48
|
+
path = Path(existing).expanduser()
|
|
49
|
+
else:
|
|
50
|
+
base_dir = TRACE_DB_DIR.expanduser()
|
|
51
|
+
base_dir.mkdir(parents=True, exist_ok=True)
|
|
52
|
+
path = base_dir / canonical_trace_db_name(timestamp=datetime.now())
|
|
53
|
+
os.environ["TASKAPP_TRACE_DB_PATH"] = str(path)
|
|
54
|
+
os.environ.setdefault("SQLD_DB_PATH", str(path))
|
|
55
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
56
|
+
return f"sqlite+aiosqlite:///{path}"
|
|
46
57
|
|
|
47
58
|
|
|
48
59
|
def build_tracer_factory(
|
synth_ai/task/validators.py
CHANGED
|
@@ -3,14 +3,157 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import re
|
|
6
|
-
from typing import Any
|
|
6
|
+
from typing import Any, cast
|
|
7
|
+
from urllib.parse import urlparse, urlunparse
|
|
7
8
|
|
|
8
9
|
import click
|
|
9
10
|
import httpx
|
|
10
|
-
|
|
11
11
|
from synth_ai.task.contracts import TaskAppEndpoints # type: ignore[attr-defined]
|
|
12
12
|
|
|
13
13
|
|
|
14
|
+
def validate_rollout_response_for_rl(response_data: dict[str, Any], *, warn_only: bool = False) -> list[str]:
|
|
15
|
+
"""Validate that a task app rollout response has required fields for RL training.
|
|
16
|
+
|
|
17
|
+
The backend RL trainer requires:
|
|
18
|
+
1. pipeline_metadata["inference_url"] at top level (with ?cid= for trace correlation)
|
|
19
|
+
2. Each step's info.meta["inference_url"] must be present (nested structure!)
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
response_data: The rollout response dict from task app
|
|
23
|
+
warn_only: If True, return warnings instead of raising exceptions
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
List of validation warnings/errors
|
|
27
|
+
|
|
28
|
+
Raises:
|
|
29
|
+
ValueError: If critical fields are missing (unless warn_only=True)
|
|
30
|
+
"""
|
|
31
|
+
issues = []
|
|
32
|
+
|
|
33
|
+
# Check pipeline_metadata
|
|
34
|
+
pipeline_metadata = response_data.get("pipeline_metadata")
|
|
35
|
+
if not isinstance(pipeline_metadata, dict):
|
|
36
|
+
issues.append("Missing or invalid 'pipeline_metadata' (required for RL training)")
|
|
37
|
+
else:
|
|
38
|
+
inference_url = pipeline_metadata.get("inference_url")
|
|
39
|
+
if not inference_url:
|
|
40
|
+
issues.append(
|
|
41
|
+
"pipeline_metadata['inference_url'] is missing. "
|
|
42
|
+
"RL trainer requires this field to extract traces."
|
|
43
|
+
)
|
|
44
|
+
elif not isinstance(inference_url, str):
|
|
45
|
+
issues.append(
|
|
46
|
+
f"pipeline_metadata['inference_url'] must be a string, got: {type(inference_url).__name__}"
|
|
47
|
+
)
|
|
48
|
+
elif "?cid=" not in inference_url:
|
|
49
|
+
issues.append(
|
|
50
|
+
f"pipeline_metadata['inference_url'] should contain '?cid=' for trace correlation. "
|
|
51
|
+
f"Got: {inference_url[:80]}..."
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Check trajectories and steps
|
|
55
|
+
trajectories = response_data.get("trajectories", [])
|
|
56
|
+
if not trajectories:
|
|
57
|
+
issues.append("No trajectories found in response")
|
|
58
|
+
|
|
59
|
+
for traj_idx, trajectory in enumerate(trajectories):
|
|
60
|
+
if not isinstance(trajectory, dict):
|
|
61
|
+
continue
|
|
62
|
+
|
|
63
|
+
steps = trajectory.get("steps", [])
|
|
64
|
+
for step_idx, step in enumerate(steps):
|
|
65
|
+
if not isinstance(step, dict):
|
|
66
|
+
continue
|
|
67
|
+
|
|
68
|
+
step_info = step.get("info", {})
|
|
69
|
+
if not isinstance(step_info, dict):
|
|
70
|
+
issues.append(
|
|
71
|
+
f"trajectory[{traj_idx}].steps[{step_idx}].info is not a dict"
|
|
72
|
+
)
|
|
73
|
+
continue
|
|
74
|
+
|
|
75
|
+
# Check for nested meta.inference_url (backend expects this structure!)
|
|
76
|
+
step_meta = step_info.get("meta", {})
|
|
77
|
+
if not isinstance(step_meta, dict):
|
|
78
|
+
issues.append(
|
|
79
|
+
f"trajectory[{traj_idx}].steps[{step_idx}].info.meta is missing or not a dict. "
|
|
80
|
+
f"RL trainer expects nested structure: info.meta.inference_url"
|
|
81
|
+
)
|
|
82
|
+
continue
|
|
83
|
+
|
|
84
|
+
step_inference_url = step_meta.get("inference_url")
|
|
85
|
+
if not step_inference_url:
|
|
86
|
+
issues.append(
|
|
87
|
+
f"trajectory[{traj_idx}].steps[{step_idx}].info.meta['inference_url'] is missing. "
|
|
88
|
+
f"RL trainer needs this for trace extraction (nested structure required!)"
|
|
89
|
+
)
|
|
90
|
+
elif not isinstance(step_inference_url, str):
|
|
91
|
+
issues.append(
|
|
92
|
+
f"trajectory[{traj_idx}].steps[{step_idx}].info.meta['inference_url'] must be a string, "
|
|
93
|
+
f"got: {type(step_inference_url).__name__}"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
if issues and not warn_only:
|
|
97
|
+
error_msg = "Task app response validation failed for RL training:\n" + "\n".join(
|
|
98
|
+
f" - {issue}" for issue in issues
|
|
99
|
+
)
|
|
100
|
+
raise ValueError(error_msg)
|
|
101
|
+
|
|
102
|
+
return issues
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def normalize_inference_url(url: str | None, *, default: str = "https://api.openai.com/v1/chat/completions") -> str:
|
|
106
|
+
"""Normalize an inference URL to include the /v1/chat/completions path.
|
|
107
|
+
|
|
108
|
+
This utility ensures inference URLs have the correct path structure for OpenAI-compatible
|
|
109
|
+
chat completions endpoints, while preserving query parameters (e.g., ?cid=trace_123)
|
|
110
|
+
that may be added for tracing.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
url: The inference URL to normalize (may be None or incomplete)
|
|
114
|
+
default: Default URL to use if url is None/empty
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
Normalized URL with proper path and preserved query parameters
|
|
118
|
+
|
|
119
|
+
Examples:
|
|
120
|
+
>>> normalize_inference_url("https://api.groq.com")
|
|
121
|
+
'https://api.groq.com/v1/chat/completions'
|
|
122
|
+
|
|
123
|
+
>>> normalize_inference_url("https://modal.host?cid=trace_123")
|
|
124
|
+
'https://modal.host/v1/chat/completions?cid=trace_123'
|
|
125
|
+
|
|
126
|
+
>>> normalize_inference_url("https://api.openai.com/v1")
|
|
127
|
+
'https://api.openai.com/v1/chat/completions'
|
|
128
|
+
|
|
129
|
+
>>> normalize_inference_url("https://api.groq.com/openai/v1/chat/completions")
|
|
130
|
+
'https://api.groq.com/openai/v1/chat/completions'
|
|
131
|
+
"""
|
|
132
|
+
candidate = (url or default).strip()
|
|
133
|
+
if not candidate:
|
|
134
|
+
candidate = default
|
|
135
|
+
|
|
136
|
+
# Parse the URL to separate path and query components
|
|
137
|
+
parsed = urlparse(candidate)
|
|
138
|
+
|
|
139
|
+
# Check if path already ends with a completions endpoint
|
|
140
|
+
path = parsed.path.rstrip('/')
|
|
141
|
+
if path.endswith("/v1/chat/completions") or path.endswith("/chat/completions"):
|
|
142
|
+
return candidate
|
|
143
|
+
|
|
144
|
+
# Determine what to append based on existing path
|
|
145
|
+
if path.endswith("/v1"):
|
|
146
|
+
new_path = f"{path}/chat/completions"
|
|
147
|
+
elif path.endswith("/chat"):
|
|
148
|
+
new_path = f"{path}/completions"
|
|
149
|
+
else:
|
|
150
|
+
# Default: append full path
|
|
151
|
+
new_path = f"{path}/v1/chat/completions" if path else "/v1/chat/completions"
|
|
152
|
+
|
|
153
|
+
# Reconstruct URL with new path and original query/fragment
|
|
154
|
+
return cast(str, urlunparse(parsed._replace(path=new_path)))
|
|
155
|
+
|
|
156
|
+
|
|
14
157
|
def validate_task_app_url(url: str | None) -> str:
|
|
15
158
|
"""Validate and normalize a task app URL.
|
|
16
159
|
|
synth_ai/tracing_v3/config.py
CHANGED
|
@@ -3,27 +3,29 @@
|
|
|
3
3
|
import os
|
|
4
4
|
from dataclasses import dataclass
|
|
5
5
|
|
|
6
|
+
from synth_ai.tracing_v3.constants import canonical_trace_db_path
|
|
7
|
+
|
|
8
|
+
DEFAULT_DB_FILE = str(canonical_trace_db_path())
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _default_sqlite_url() -> str:
|
|
12
|
+
base_path = os.path.abspath(os.getenv("SQLD_DB_PATH", DEFAULT_DB_FILE))
|
|
13
|
+
candidate = os.path.join(base_path, "dbs", "default", "data")
|
|
14
|
+
if os.path.isdir(base_path) and os.path.exists(candidate):
|
|
15
|
+
return f"sqlite+aiosqlite:///{candidate}"
|
|
16
|
+
return f"sqlite+aiosqlite:///{base_path}"
|
|
17
|
+
|
|
6
18
|
|
|
7
19
|
@dataclass
|
|
8
20
|
class TursoConfig:
|
|
9
21
|
"""Configuration for Turso/sqld connection."""
|
|
10
22
|
|
|
11
23
|
# Default values matching serve.sh
|
|
12
|
-
DEFAULT_DB_FILE =
|
|
24
|
+
DEFAULT_DB_FILE = DEFAULT_DB_FILE
|
|
13
25
|
DEFAULT_HTTP_PORT = 8080
|
|
14
26
|
|
|
15
|
-
# Local embedded database for async SQLAlchemy
|
|
16
|
-
# Resolve to the actual SQLite file used by sqld if the base path is a directory
|
|
17
|
-
def _resolve_sqlite_db_url() -> str: # type: ignore[no-redef]
|
|
18
|
-
base_path = os.path.abspath(os.getenv("SQLD_DB_PATH", "traces/v3/synth_ai.db"))
|
|
19
|
-
# If sqld is managing this DB, the real SQLite file lives under dbs/default/data
|
|
20
|
-
candidate = os.path.join(base_path, "dbs", "default", "data")
|
|
21
|
-
if os.path.isdir(base_path) and os.path.exists(candidate):
|
|
22
|
-
return f"sqlite+aiosqlite:///{candidate}"
|
|
23
|
-
return f"sqlite+aiosqlite:///{base_path}"
|
|
24
|
-
|
|
25
27
|
# Use env override if provided; otherwise resolve based on SQLD layout
|
|
26
|
-
db_url: str = os.getenv("TURSO_LOCAL_DB_URL",
|
|
28
|
+
db_url: str = os.getenv("TURSO_LOCAL_DB_URL", _default_sqlite_url())
|
|
27
29
|
|
|
28
30
|
# Remote database sync configuration
|
|
29
31
|
sync_url: str = os.getenv("TURSO_DATABASE_URL", "")
|
|
@@ -48,7 +50,7 @@ class TursoConfig:
|
|
|
48
50
|
|
|
49
51
|
# Daemon settings (for local sqld) - match serve.sh defaults
|
|
50
52
|
sqld_binary: str = os.getenv("SQLD_BINARY", "sqld")
|
|
51
|
-
sqld_db_path: str = os.getenv("SQLD_DB_PATH",
|
|
53
|
+
sqld_db_path: str = os.getenv("SQLD_DB_PATH", DEFAULT_DB_FILE)
|
|
52
54
|
sqld_http_port: int = int(os.getenv("SQLD_HTTP_PORT", "8080"))
|
|
53
55
|
sqld_idle_shutdown: int = int(os.getenv("SQLD_IDLE_SHUTDOWN", "0")) # 0 = no idle shutdown
|
|
54
56
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
TRACE_DB_DIR = Path("traces")
|
|
7
|
+
TRACE_DB_BASENAME = "task_app_traces"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def canonical_trace_db_name(*, timestamp: datetime | None = None) -> str:
|
|
11
|
+
"""Return the canonical trace database filename (with optional timestamp suffix)."""
|
|
12
|
+
|
|
13
|
+
if timestamp is None:
|
|
14
|
+
return f"{TRACE_DB_BASENAME}.db"
|
|
15
|
+
return f"{TRACE_DB_BASENAME}_{timestamp.strftime('%Y-%m-%d_%H-%M-%S')}.db"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def canonical_trace_db_path(*, timestamp: datetime | None = None) -> Path:
|
|
19
|
+
"""Return the canonical trace database path within the default trace directory."""
|
|
20
|
+
|
|
21
|
+
return TRACE_DB_DIR / canonical_trace_db_name(timestamp=timestamp)
|
synth_ai/tracing_v3/db_config.py
CHANGED
|
@@ -7,6 +7,8 @@ import os
|
|
|
7
7
|
import shutil
|
|
8
8
|
from typing import TYPE_CHECKING, Optional
|
|
9
9
|
|
|
10
|
+
from synth_ai.tracing_v3.constants import canonical_trace_db_path
|
|
11
|
+
|
|
10
12
|
if TYPE_CHECKING:
|
|
11
13
|
from .turso.daemon import SqldDaemon
|
|
12
14
|
|
|
@@ -17,7 +19,7 @@ class DatabaseConfig:
|
|
|
17
19
|
"""Centralized database configuration management."""
|
|
18
20
|
|
|
19
21
|
# Default values from serve.sh
|
|
20
|
-
DEFAULT_DB_FILE =
|
|
22
|
+
DEFAULT_DB_FILE = str(canonical_trace_db_path())
|
|
21
23
|
DEFAULT_HTTP_PORT = 8080
|
|
22
24
|
|
|
23
25
|
def __init__(
|
|
@@ -29,6 +29,7 @@ import contextvars
|
|
|
29
29
|
import functools
|
|
30
30
|
import time
|
|
31
31
|
from collections.abc import Awaitable, Callable, Mapping
|
|
32
|
+
from contextvars import Token
|
|
32
33
|
from typing import Any, TypeVar, cast, overload
|
|
33
34
|
|
|
34
35
|
from .abstractions import LMCAISEvent, TimeRecord
|
|
@@ -367,11 +368,11 @@ class SessionContext:
|
|
|
367
368
|
```
|
|
368
369
|
"""
|
|
369
370
|
|
|
370
|
-
def __init__(self, session_id: str, tracer=None):
|
|
371
|
+
def __init__(self, session_id: str, tracer: Any | None = None):
|
|
371
372
|
self.session_id = session_id
|
|
372
373
|
self.tracer = tracer
|
|
373
|
-
self._token = None
|
|
374
|
-
self._tracer_token = None
|
|
374
|
+
self._token: Token[str | None] | None = None
|
|
375
|
+
self._tracer_token: Token[Any] | None = None
|
|
375
376
|
|
|
376
377
|
def __enter__(self):
|
|
377
378
|
# Store tokens to restore previous context on exit
|
|
@@ -382,8 +383,9 @@ class SessionContext:
|
|
|
382
383
|
|
|
383
384
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
384
385
|
# Restore previous context - this is crucial for proper isolation
|
|
385
|
-
|
|
386
|
-
|
|
386
|
+
if self._token is not None:
|
|
387
|
+
_session_id_ctx.reset(self._token)
|
|
388
|
+
if self._tracer_token is not None:
|
|
387
389
|
_session_tracer_ctx.reset(self._tracer_token)
|
|
388
390
|
|
|
389
391
|
async def __aenter__(self):
|
|
@@ -393,6 +395,7 @@ class SessionContext:
|
|
|
393
395
|
return self
|
|
394
396
|
|
|
395
397
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
396
|
-
|
|
397
|
-
|
|
398
|
+
if self._token is not None:
|
|
399
|
+
_session_id_ctx.reset(self._token)
|
|
400
|
+
if self._tracer_token is not None:
|
|
398
401
|
_session_tracer_ctx.reset(self._tracer_token)
|
|
@@ -375,11 +375,21 @@ class SessionTracer:
|
|
|
375
375
|
|
|
376
376
|
# Save if requested
|
|
377
377
|
should_save = save if save is not None else self.auto_save
|
|
378
|
+
|
|
379
|
+
# Debug logging
|
|
380
|
+
import logging
|
|
381
|
+
_logger = logging.getLogger(__name__)
|
|
382
|
+
_logger.info(f"[TRACE_DEBUG] end_session: should_save={should_save}, self.db={self.db is not None}, auto_save={self.auto_save}")
|
|
383
|
+
|
|
378
384
|
if should_save and self.db:
|
|
385
|
+
_logger.info(f"[TRACE_DEBUG] Calling insert_session_trace with {len(self._current_trace.markov_blanket_message_history)} messages")
|
|
379
386
|
await self.db.insert_session_trace(self._current_trace)
|
|
387
|
+
_logger.info("[TRACE_DEBUG] insert_session_trace completed")
|
|
380
388
|
|
|
381
389
|
# Trigger post-save hooks
|
|
382
390
|
await self.hooks.trigger("after_save", session=self._current_trace)
|
|
391
|
+
else:
|
|
392
|
+
_logger.warning(f"[TRACE_DEBUG] Skipping save: should_save={should_save}, self.db={self.db is not None}")
|
|
383
393
|
|
|
384
394
|
# Trigger session end hooks
|
|
385
395
|
await self.hooks.trigger("session_end", session=self._current_trace)
|