synth-ai 0.2.9.dev5__py3-none-any.whl → 0.2.9.dev7__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/common_old/backend.py +0 -1
- examples/crafter_debug_render.py +15 -6
- examples/evals_old/compare_models.py +1 -0
- examples/finetuning_old/_backup_synth_qwen/filter_traces_achievements.py +6 -2
- examples/finetuning_old/_backup_synth_qwen/react_agent_lm.py +4 -4
- examples/finetuning_old/_backup_synth_qwen/sft_kickoff.py +4 -3
- examples/finetuning_old/synth_qwen_v1/filter_traces_achievements.py +6 -2
- examples/finetuning_old/synth_qwen_v1/finetune.py +1 -1
- examples/finetuning_old/synth_qwen_v1/hello_ft_model.py +4 -4
- examples/finetuning_old/synth_qwen_v1/infer.py +1 -2
- examples/finetuning_old/synth_qwen_v1/poll.py +4 -2
- examples/finetuning_old/synth_qwen_v1/prepare_data.py +8 -8
- examples/finetuning_old/synth_qwen_v1/react_agent_lm.py +5 -4
- examples/finetuning_old/synth_qwen_v1/run_crafter_sft_job.py +11 -8
- examples/finetuning_old/synth_qwen_v1/run_ft_job.py +17 -12
- examples/finetuning_old/synth_qwen_v1/upload_data.py +1 -1
- examples/finetuning_old/synth_qwen_v1/util.py +7 -2
- 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 +17 -15
- examples/rl/run_rl_and_save.py +24 -7
- examples/rl/task_app/math_single_step.py +128 -11
- examples/rl/task_app/math_task_app.py +11 -3
- examples/rl_old/task_app.py +222 -53
- examples/warming_up_to_rl/analyze_trace_db.py +7 -5
- examples/warming_up_to_rl/export_trace_sft.py +141 -16
- examples/warming_up_to_rl/groq_test.py +11 -4
- examples/warming_up_to_rl/manage_secrets.py +15 -6
- examples/warming_up_to_rl/readme.md +9 -2
- examples/warming_up_to_rl/run_eval.py +108 -30
- examples/warming_up_to_rl/run_fft_and_save.py +128 -52
- examples/warming_up_to_rl/run_local_rollout.py +87 -36
- examples/warming_up_to_rl/run_local_rollout_modal.py +113 -25
- examples/warming_up_to_rl/run_local_rollout_parallel.py +80 -16
- examples/warming_up_to_rl/run_local_rollout_traced.py +125 -20
- examples/warming_up_to_rl/run_rl_and_save.py +31 -7
- examples/warming_up_to_rl/run_rollout_remote.py +37 -10
- examples/warming_up_to_rl/task_app/grpo_crafter.py +90 -27
- examples/warming_up_to_rl/task_app/grpo_crafter_task_app.py +9 -27
- examples/warming_up_to_rl/task_app/synth_envs_hosted/environment_routes.py +46 -108
- 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 +50 -17
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/policy.py +35 -21
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/react_agent.py +8 -4
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/shared.py +29 -26
- 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 +17 -13
- 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 +106 -63
- examples/warming_up_to_rl/task_app/synth_envs_hosted/policy_routes.py +82 -84
- examples/warming_up_to_rl/task_app/synth_envs_hosted/rollout.py +76 -59
- 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 +43 -49
- examples/warming_up_to_rl/task_app/synth_envs_hosted/test_service.py +5 -15
- synth_ai/__init__.py +1 -0
- synth_ai/api/train/builders.py +34 -10
- synth_ai/api/train/cli.py +172 -32
- synth_ai/api/train/config_finder.py +59 -4
- synth_ai/api/train/env_resolver.py +32 -14
- synth_ai/api/train/pollers.py +11 -3
- synth_ai/api/train/task_app.py +4 -1
- synth_ai/api/train/utils.py +20 -4
- synth_ai/cli/__init__.py +11 -4
- synth_ai/cli/balance.py +1 -1
- synth_ai/cli/demo.py +19 -5
- synth_ai/cli/rl_demo.py +75 -16
- synth_ai/cli/root.py +116 -37
- synth_ai/cli/task_apps.py +1276 -186
- synth_ai/cli/traces.py +1 -0
- synth_ai/cli/turso.py +73 -0
- synth_ai/core/experiment.py +0 -2
- synth_ai/demo_registry.py +67 -30
- synth_ai/demos/core/cli.py +493 -164
- synth_ai/demos/demo_task_apps/core.py +50 -6
- 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 +36 -28
- synth_ai/demos/demo_task_apps/math/_common.py +1 -2
- synth_ai/demos/demo_task_apps/math/deploy_modal.py +0 -2
- synth_ai/demos/demo_task_apps/math/modal_task_app.py +168 -65
- synth_ai/demos/demo_task_apps/math/task_app_entry.py +0 -1
- synth_ai/environments/examples/bandit/engine.py +12 -4
- synth_ai/environments/examples/bandit/taskset.py +4 -4
- synth_ai/environments/reproducibility/tree.py +3 -1
- synth_ai/environments/service/core_routes.py +6 -2
- synth_ai/evals/base.py +0 -2
- synth_ai/experimental/synth_oss.py +11 -12
- synth_ai/handshake.py +3 -1
- synth_ai/http_client.py +31 -7
- synth_ai/inference/__init__.py +0 -2
- synth_ai/inference/client.py +8 -4
- synth_ai/jobs/client.py +40 -10
- synth_ai/learning/client.py +33 -8
- synth_ai/learning/config.py +0 -2
- synth_ai/learning/constants.py +0 -2
- synth_ai/learning/ft_client.py +6 -3
- synth_ai/learning/health.py +9 -2
- synth_ai/learning/jobs.py +17 -5
- synth_ai/learning/prompts/hello_world_in_context_injection_ex.py +1 -3
- synth_ai/learning/prompts/random_search.py +4 -1
- synth_ai/learning/prompts/run_random_search_banking77.py +6 -1
- synth_ai/learning/rl_client.py +42 -14
- synth_ai/learning/sse.py +0 -2
- synth_ai/learning/validators.py +6 -2
- synth_ai/lm/caching/ephemeral.py +1 -3
- synth_ai/lm/core/exceptions.py +0 -2
- synth_ai/lm/core/main.py +13 -1
- synth_ai/lm/core/synth_models.py +0 -1
- synth_ai/lm/core/vendor_clients.py +4 -2
- synth_ai/lm/overrides.py +2 -2
- synth_ai/lm/vendors/core/anthropic_api.py +7 -7
- synth_ai/lm/vendors/core/openai_api.py +2 -0
- synth_ai/lm/vendors/openai_standard.py +3 -1
- synth_ai/lm/vendors/openai_standard_responses.py +6 -3
- synth_ai/lm/vendors/supported/custom_endpoint.py +1 -3
- synth_ai/lm/vendors/synth_client.py +37 -10
- synth_ai/rl/__init__.py +0 -1
- synth_ai/rl/contracts.py +0 -2
- synth_ai/rl/env_keys.py +6 -1
- synth_ai/task/__init__.py +1 -0
- synth_ai/task/apps/__init__.py +11 -11
- synth_ai/task/auth.py +29 -17
- synth_ai/task/client.py +3 -1
- synth_ai/task/contracts.py +1 -0
- synth_ai/task/datasets.py +3 -1
- synth_ai/task/errors.py +3 -2
- synth_ai/task/health.py +0 -2
- synth_ai/task/json.py +0 -1
- synth_ai/task/proxy.py +2 -5
- synth_ai/task/rubrics.py +9 -3
- synth_ai/task/server.py +31 -5
- synth_ai/task/tracing_utils.py +8 -3
- synth_ai/task/validators.py +0 -1
- synth_ai/task/vendors.py +0 -1
- synth_ai/tracing_v3/db_config.py +26 -1
- synth_ai/tracing_v3/decorators.py +1 -0
- synth_ai/tracing_v3/examples/basic_usage.py +3 -2
- synth_ai/tracing_v3/hooks.py +2 -0
- synth_ai/tracing_v3/replica_sync.py +1 -0
- synth_ai/tracing_v3/session_tracer.py +24 -3
- synth_ai/tracing_v3/storage/base.py +4 -1
- synth_ai/tracing_v3/storage/factory.py +0 -1
- synth_ai/tracing_v3/turso/manager.py +102 -38
- synth_ai/tracing_v3/turso/models.py +4 -1
- synth_ai/tracing_v3/utils.py +1 -0
- synth_ai/v0/tracing/upload.py +32 -135
- {synth_ai-0.2.9.dev5.dist-info → synth_ai-0.2.9.dev7.dist-info}/METADATA +1 -1
- {synth_ai-0.2.9.dev5.dist-info → synth_ai-0.2.9.dev7.dist-info}/RECORD +154 -154
- synth_ai/install_sqld.sh +0 -40
- {synth_ai-0.2.9.dev5.dist-info → synth_ai-0.2.9.dev7.dist-info}/WHEEL +0 -0
- {synth_ai-0.2.9.dev5.dist-info → synth_ai-0.2.9.dev7.dist-info}/entry_points.txt +0 -0
- {synth_ai-0.2.9.dev5.dist-info → synth_ai-0.2.9.dev7.dist-info}/licenses/LICENSE +0 -0
- {synth_ai-0.2.9.dev5.dist-info → synth_ai-0.2.9.dev7.dist-info}/top_level.txt +0 -0
examples/common_old/backend.py
CHANGED
examples/crafter_debug_render.py
CHANGED
|
@@ -10,6 +10,7 @@ Quick local Crafter observation inspector.
|
|
|
10
10
|
Run:
|
|
11
11
|
uv run python examples/crafter_debug_render.py --base-url http://localhost:8901 --seed 1
|
|
12
12
|
"""
|
|
13
|
+
|
|
13
14
|
import argparse
|
|
14
15
|
import math
|
|
15
16
|
import os
|
|
@@ -117,13 +118,17 @@ def format_semantic_map_view(obs: Dict[str, Any], view_size: int = 7) -> str:
|
|
|
117
118
|
row_cells.append("void")
|
|
118
119
|
lines.append(" ".join(row_cells))
|
|
119
120
|
|
|
120
|
-
legend =
|
|
121
|
+
legend = (
|
|
122
|
+
f"Visible items: {', '.join(sorted(visible))}" if visible else "No notable items visible"
|
|
123
|
+
)
|
|
121
124
|
return "\n".join(lines) + "\n" + legend
|
|
122
125
|
|
|
123
126
|
|
|
124
127
|
async def main():
|
|
125
128
|
parser = argparse.ArgumentParser()
|
|
126
|
-
parser.add_argument(
|
|
129
|
+
parser.add_argument(
|
|
130
|
+
"--base-url", default=os.getenv("CRAFTER_BASE_URL", "http://localhost:8901")
|
|
131
|
+
)
|
|
127
132
|
parser.add_argument("--seed", type=int, default=1)
|
|
128
133
|
args = parser.parse_args()
|
|
129
134
|
|
|
@@ -145,7 +150,11 @@ async def main():
|
|
|
145
150
|
print(f"Health: {obs.get('health', 10)}/10")
|
|
146
151
|
print(f"Hunger: {obs.get('food', 10)}/10")
|
|
147
152
|
print(f"Energy: {obs.get('energy', 10)}/10")
|
|
148
|
-
inv_items =
|
|
153
|
+
inv_items = (
|
|
154
|
+
", ".join([f"{k}: {v}" for k, v in inv.items() if v])
|
|
155
|
+
if isinstance(inv, dict)
|
|
156
|
+
else str(inv)
|
|
157
|
+
)
|
|
149
158
|
print(f"Inventory: {inv_items if inv_items else 'empty'}")
|
|
150
159
|
if isinstance(ach, dict):
|
|
151
160
|
unlocked = sum(1 for v in ach.values() if v)
|
|
@@ -167,7 +176,9 @@ async def main():
|
|
|
167
176
|
|
|
168
177
|
# Cleanup
|
|
169
178
|
try:
|
|
170
|
-
await client.post(
|
|
179
|
+
await client.post(
|
|
180
|
+
f"{args.base_url}/env/CrafterClassic/terminate", json={"env_id": env_id}
|
|
181
|
+
)
|
|
171
182
|
except Exception:
|
|
172
183
|
pass
|
|
173
184
|
|
|
@@ -176,5 +187,3 @@ if __name__ == "__main__":
|
|
|
176
187
|
import asyncio
|
|
177
188
|
|
|
178
189
|
asyncio.run(main())
|
|
179
|
-
|
|
180
|
-
|
|
@@ -34,7 +34,8 @@ except Exception: # pragma: no cover
|
|
|
34
34
|
)
|
|
35
35
|
except Exception as _import_err: # pragma: no cover
|
|
36
36
|
raise ImportError(
|
|
37
|
-
"Could not import FinetuningDataExtractorV3 from synth_ai."
|
|
37
|
+
"Could not import FinetuningDataExtractorV3 from synth_ai."
|
|
38
|
+
) from _import_err
|
|
38
39
|
|
|
39
40
|
|
|
40
41
|
def env_list(name: str) -> list[str]:
|
|
@@ -133,6 +134,7 @@ async def main() -> None:
|
|
|
133
134
|
try:
|
|
134
135
|
import numpy as _np
|
|
135
136
|
from collections import Counter as _Counter
|
|
137
|
+
|
|
136
138
|
async with FinetuningDataExtractorV3(db_url) as _ex:
|
|
137
139
|
_sessions = await _ex.get_all_sessions()
|
|
138
140
|
_ach_counts: _Counter[str] = _Counter()
|
|
@@ -155,7 +157,9 @@ async def main() -> None:
|
|
|
155
157
|
if _rewards:
|
|
156
158
|
_r = _np.array(_rewards, dtype=float)
|
|
157
159
|
print("\nReward stats:")
|
|
158
|
-
print(
|
|
160
|
+
print(
|
|
161
|
+
f" min={_r.min():.2f} median={_np.median(_r):.2f} mean={_r.mean():.2f} max={_r.max():.2f}"
|
|
162
|
+
)
|
|
159
163
|
except Exception:
|
|
160
164
|
pass
|
|
161
165
|
|
|
@@ -5,6 +5,7 @@ This demonstrates using the LM class with Synth models through native integratio
|
|
|
5
5
|
|
|
6
6
|
This version uses the new tracing_v3 system with async Turso/SQLite backend.
|
|
7
7
|
"""
|
|
8
|
+
|
|
8
9
|
import argparse
|
|
9
10
|
import asyncio
|
|
10
11
|
import contextlib
|
|
@@ -100,13 +101,13 @@ HTTP_TIMEOUT = (
|
|
|
100
101
|
MAX_RETRIES = 3
|
|
101
102
|
RETRY_DELAY = 1.0
|
|
102
103
|
|
|
104
|
+
|
|
103
105
|
# Use the backend
|
|
104
106
|
@asynccontextmanager
|
|
105
107
|
async def _noop_async_context():
|
|
106
108
|
yield
|
|
107
109
|
|
|
108
110
|
|
|
109
|
-
|
|
110
111
|
async def create_experiment_context(
|
|
111
112
|
db_manager: AsyncSQLTraceManager, experiment_name: str, description: str
|
|
112
113
|
) -> dict[str, Any]:
|
|
@@ -1266,9 +1267,8 @@ async def run_episode(
|
|
|
1266
1267
|
info = step_data.get("info", {})
|
|
1267
1268
|
|
|
1268
1269
|
# Calculate achievement reward if not provided by service
|
|
1269
|
-
if (
|
|
1270
|
-
|
|
1271
|
-
and ("achievements_status" in obs and "achievements_status" in prev_obs)
|
|
1270
|
+
if (reward == 0 or reward is None) and (
|
|
1271
|
+
"achievements_status" in obs and "achievements_status" in prev_obs
|
|
1272
1272
|
):
|
|
1273
1273
|
prev_achievements = prev_obs["achievements_status"]
|
|
1274
1274
|
curr_achievements = obs["achievements_status"]
|
|
@@ -67,9 +67,10 @@ async def create_job(file_id: str) -> str:
|
|
|
67
67
|
"upload_to_wasabi": bool(scfg.get("upload_to_wasabi", True)),
|
|
68
68
|
}
|
|
69
69
|
headers = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
|
|
70
|
-
async with
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
async with (
|
|
71
|
+
aiohttp.ClientSession() as session,
|
|
72
|
+
session.post(f"{API_URL}/fine_tuning/jobs", json=body, headers=headers) as resp,
|
|
73
|
+
):
|
|
73
74
|
assert resp.status == 200, await resp.text()
|
|
74
75
|
data = await resp.json()
|
|
75
76
|
return data["id"]
|
|
@@ -34,7 +34,8 @@ except Exception: # pragma: no cover
|
|
|
34
34
|
)
|
|
35
35
|
except Exception as _import_err: # pragma: no cover
|
|
36
36
|
raise ImportError(
|
|
37
|
-
"Could not import FinetuningDataExtractorV3 from synth_ai."
|
|
37
|
+
"Could not import FinetuningDataExtractorV3 from synth_ai."
|
|
38
|
+
) from _import_err
|
|
38
39
|
|
|
39
40
|
|
|
40
41
|
def env_list(name: str) -> list[str]:
|
|
@@ -133,6 +134,7 @@ async def main() -> None:
|
|
|
133
134
|
try:
|
|
134
135
|
import numpy as _np
|
|
135
136
|
from collections import Counter as _Counter
|
|
137
|
+
|
|
136
138
|
async with FinetuningDataExtractorV3(db_url) as _ex:
|
|
137
139
|
_sessions = await _ex.get_all_sessions()
|
|
138
140
|
_ach_counts: _Counter[str] = _Counter()
|
|
@@ -155,7 +157,9 @@ async def main() -> None:
|
|
|
155
157
|
if _rewards:
|
|
156
158
|
_r = _np.array(_rewards, dtype=float)
|
|
157
159
|
print("\nReward stats:")
|
|
158
|
-
print(
|
|
160
|
+
print(
|
|
161
|
+
f" min={_r.min():.2f} median={_np.median(_r):.2f} mean={_r.mean():.2f} max={_r.max():.2f}"
|
|
162
|
+
)
|
|
159
163
|
except Exception:
|
|
160
164
|
pass
|
|
161
165
|
|
|
@@ -4,6 +4,7 @@ import asyncio
|
|
|
4
4
|
|
|
5
5
|
import sys
|
|
6
6
|
import os
|
|
7
|
+
|
|
7
8
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))))
|
|
8
9
|
|
|
9
10
|
from synth_ai.learning import FtClient
|
|
@@ -43,4 +44,3 @@ def main() -> None:
|
|
|
43
44
|
|
|
44
45
|
if __name__ == "__main__":
|
|
45
46
|
main()
|
|
46
|
-
|
|
@@ -44,6 +44,7 @@ async def main() -> None:
|
|
|
44
44
|
print("===== End Response =====\n")
|
|
45
45
|
except Exception as e: # always print full failure context
|
|
46
46
|
import traceback
|
|
47
|
+
|
|
47
48
|
print("\n===== Inference Error =====")
|
|
48
49
|
print(f"Type: {type(e).__name__}")
|
|
49
50
|
print(f"Repr: {repr(e)}")
|
|
@@ -51,14 +52,15 @@ async def main() -> None:
|
|
|
51
52
|
print(traceback.format_exc())
|
|
52
53
|
try:
|
|
53
54
|
from synth_ai.http import HTTPError # type: ignore
|
|
55
|
+
|
|
54
56
|
if isinstance(e, HTTPError):
|
|
55
57
|
print("HTTPError details:")
|
|
56
58
|
print(f" status={e.status}")
|
|
57
59
|
print(f" url={e.url}")
|
|
58
60
|
print(f" message={e.message}")
|
|
59
|
-
if getattr(e,
|
|
61
|
+
if getattr(e, "detail", None) is not None:
|
|
60
62
|
print(f" detail={e.detail}")
|
|
61
|
-
if getattr(e,
|
|
63
|
+
if getattr(e, "body_snippet", None):
|
|
62
64
|
print(f" body_snippet={e.body_snippet}")
|
|
63
65
|
except Exception:
|
|
64
66
|
pass
|
|
@@ -67,5 +69,3 @@ async def main() -> None:
|
|
|
67
69
|
|
|
68
70
|
if __name__ == "__main__":
|
|
69
71
|
asyncio.run(main())
|
|
70
|
-
|
|
71
|
-
|
|
@@ -4,6 +4,7 @@ import asyncio
|
|
|
4
4
|
|
|
5
5
|
import sys
|
|
6
6
|
import os
|
|
7
|
+
|
|
7
8
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))))
|
|
8
9
|
|
|
9
10
|
from synth_ai.inference import InferenceClient
|
|
@@ -33,5 +34,3 @@ def main() -> None:
|
|
|
33
34
|
|
|
34
35
|
if __name__ == "__main__":
|
|
35
36
|
main()
|
|
36
|
-
|
|
37
|
-
|
|
@@ -5,6 +5,7 @@ from typing import Dict
|
|
|
5
5
|
|
|
6
6
|
import sys
|
|
7
7
|
import os
|
|
8
|
+
|
|
8
9
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))))
|
|
9
10
|
|
|
10
11
|
from synth_ai.learning import JobHandle
|
|
@@ -27,7 +28,9 @@ async def _run(mode: str | None) -> None:
|
|
|
27
28
|
|
|
28
29
|
# Use shared JobHandle poller abstraction (strict=True for FT)
|
|
29
30
|
handle = JobHandle(base, key, job_id, strict=True)
|
|
30
|
-
final = await handle.poll_until_terminal(
|
|
31
|
+
final = await handle.poll_until_terminal(
|
|
32
|
+
interval_seconds=2.0, max_seconds=1800, on_event=_print_event
|
|
33
|
+
)
|
|
31
34
|
print(f"final_status={final.get('status')}")
|
|
32
35
|
ft = final.get("fine_tuned_model")
|
|
33
36
|
if ft:
|
|
@@ -41,4 +44,3 @@ def main() -> None:
|
|
|
41
44
|
|
|
42
45
|
if __name__ == "__main__":
|
|
43
46
|
main()
|
|
44
|
-
|
|
@@ -17,12 +17,14 @@ def main() -> None:
|
|
|
17
17
|
|
|
18
18
|
# Minimal single-example JSONL
|
|
19
19
|
lines = [
|
|
20
|
-
json.dumps(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
json.dumps(
|
|
21
|
+
{
|
|
22
|
+
"messages": [
|
|
23
|
+
{"role": "user", "content": "Write a short greeting."},
|
|
24
|
+
{"role": "assistant", "content": "Hello there!"},
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
)
|
|
26
28
|
]
|
|
27
29
|
out_path.write_text("\n".join(lines) + "\n")
|
|
28
30
|
validate_jsonl(out_path)
|
|
@@ -31,5 +33,3 @@ def main() -> None:
|
|
|
31
33
|
|
|
32
34
|
if __name__ == "__main__":
|
|
33
35
|
main()
|
|
34
|
-
|
|
35
|
-
|
|
@@ -5,6 +5,7 @@ This demonstrates using the LM class with Synth models through native integratio
|
|
|
5
5
|
|
|
6
6
|
This version uses the new tracing_v3 system with async Turso/SQLite backend.
|
|
7
7
|
"""
|
|
8
|
+
|
|
8
9
|
import argparse
|
|
9
10
|
import asyncio
|
|
10
11
|
import contextlib
|
|
@@ -38,6 +39,7 @@ def _resolve_backend_default() -> str:
|
|
|
38
39
|
base = base.rstrip("/")
|
|
39
40
|
return base if base.endswith("/api") else f"{base}/api"
|
|
40
41
|
|
|
42
|
+
|
|
41
43
|
# Disable httpx logging immediately
|
|
42
44
|
logging.getLogger("httpx").setLevel(logging.ERROR)
|
|
43
45
|
logging.getLogger("httpcore").setLevel(logging.ERROR)
|
|
@@ -108,13 +110,13 @@ HTTP_TIMEOUT = (
|
|
|
108
110
|
MAX_RETRIES = 3
|
|
109
111
|
RETRY_DELAY = 1.0
|
|
110
112
|
|
|
113
|
+
|
|
111
114
|
# Use the backend
|
|
112
115
|
@asynccontextmanager
|
|
113
116
|
async def _noop_async_context():
|
|
114
117
|
yield
|
|
115
118
|
|
|
116
119
|
|
|
117
|
-
|
|
118
120
|
async def create_experiment_context(
|
|
119
121
|
db_manager: AsyncSQLTraceManager, experiment_name: str, description: str
|
|
120
122
|
) -> dict[str, Any]:
|
|
@@ -1274,9 +1276,8 @@ async def run_episode(
|
|
|
1274
1276
|
info = step_data.get("info", {})
|
|
1275
1277
|
|
|
1276
1278
|
# Calculate achievement reward if not provided by service
|
|
1277
|
-
if (
|
|
1278
|
-
|
|
1279
|
-
and ("achievements_status" in obs and "achievements_status" in prev_obs)
|
|
1279
|
+
if (reward == 0 or reward is None) and (
|
|
1280
|
+
"achievements_status" in obs and "achievements_status" in prev_obs
|
|
1280
1281
|
):
|
|
1281
1282
|
prev_achievements = prev_obs["achievements_status"]
|
|
1282
1283
|
curr_achievements = obs["achievements_status"]
|
|
@@ -37,13 +37,12 @@ def parse_args() -> argparse.Namespace:
|
|
|
37
37
|
p.add_argument("--mode", choices=["local", "dev", "prod"], default=None)
|
|
38
38
|
p.add_argument(
|
|
39
39
|
"--db",
|
|
40
|
-
default=str(
|
|
41
|
-
Path(__file__).resolve().parents[3]
|
|
42
|
-
/ "traces/v3/synth_ai.db/dbs/default/data"
|
|
43
|
-
),
|
|
40
|
+
default=str(Path(__file__).resolve().parents[3] / "traces/v3/synth_ai.db/dbs/default/data"),
|
|
44
41
|
help="Path to sqld internal data file or sqlite+aiosqlite URL",
|
|
45
42
|
)
|
|
46
|
-
p.add_argument(
|
|
43
|
+
p.add_argument(
|
|
44
|
+
"--output", default=str(Path(__file__).parent / "data" / "training_crafter.jsonl")
|
|
45
|
+
)
|
|
47
46
|
p.add_argument("--min-achievements", type=int, default=2)
|
|
48
47
|
p.add_argument("--max-cost", type=float, default=10.0)
|
|
49
48
|
p.add_argument("--max-tokens", type=int, default=100000)
|
|
@@ -72,12 +71,14 @@ async def extract_jsonl_from_traces(db_url: str, output_path: str, cfg: dict[str
|
|
|
72
71
|
from synth_ai.environments.examples.crafter_classic.agent_demos.crafter_modal_ft.filter_traces_sft_turso import ( # type: ignore
|
|
73
72
|
FinetuningDataExtractorV3 as _Ex,
|
|
74
73
|
)
|
|
74
|
+
|
|
75
75
|
Extractor = _Ex
|
|
76
76
|
except Exception:
|
|
77
77
|
try:
|
|
78
78
|
from synth_ai.environments.examples.crafter_classic.agent_demos.crafter_openai_ft.filter_traces_sft_turso import ( # type: ignore
|
|
79
79
|
FinetuningDataExtractorV3 as _Ex,
|
|
80
80
|
)
|
|
81
|
+
|
|
81
82
|
Extractor = _Ex
|
|
82
83
|
except Exception as e:
|
|
83
84
|
raise ImportError("FinetuningDataExtractorV3 not available in current build") from e
|
|
@@ -110,7 +111,11 @@ async def extract_jsonl_from_traces(db_url: str, output_path: str, cfg: dict[str
|
|
|
110
111
|
""",
|
|
111
112
|
{"session_id": sid},
|
|
112
113
|
)
|
|
113
|
-
session_models =
|
|
114
|
+
session_models = (
|
|
115
|
+
model_df["model_name"].tolist()
|
|
116
|
+
if model_df is not None and not model_df.empty
|
|
117
|
+
else []
|
|
118
|
+
)
|
|
114
119
|
if not any(m in session_models for m in models):
|
|
115
120
|
continue
|
|
116
121
|
ach = await ex.get_session_achievements(sid) or []
|
|
@@ -203,5 +208,3 @@ def main() -> None:
|
|
|
203
208
|
|
|
204
209
|
if __name__ == "__main__":
|
|
205
210
|
main()
|
|
206
|
-
|
|
207
|
-
|
|
@@ -65,12 +65,14 @@ def ensure_training_jsonl(path: Path) -> Path:
|
|
|
65
65
|
if not path.exists():
|
|
66
66
|
# Minimal JSONL with a single example
|
|
67
67
|
lines: list[str] = [
|
|
68
|
-
json.dumps(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
68
|
+
json.dumps(
|
|
69
|
+
{
|
|
70
|
+
"messages": [
|
|
71
|
+
{"role": "user", "content": "Write a short greeting."},
|
|
72
|
+
{"role": "assistant", "content": "Hello there!"},
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
)
|
|
74
76
|
]
|
|
75
77
|
path.write_text("\n".join(lines) + "\n")
|
|
76
78
|
# Validate using shared SDK validator
|
|
@@ -83,7 +85,9 @@ async def run(args: argparse.Namespace) -> None:
|
|
|
83
85
|
base_url, api_key = load_env(args.mode)
|
|
84
86
|
# Force canonical prod base when prod mode (or override) is selected
|
|
85
87
|
try:
|
|
86
|
-
if (args.mode == "prod") or (
|
|
88
|
+
if (args.mode == "prod") or (
|
|
89
|
+
os.getenv("SYNTH_BACKEND_URL_OVERRIDE", "").strip().lower() == "prod"
|
|
90
|
+
):
|
|
87
91
|
base_url = _resolve_backend_default()
|
|
88
92
|
# Also export for any downstream helpers that read env
|
|
89
93
|
os.environ["PROD_BACKEND_URL"] = base_url
|
|
@@ -123,8 +127,8 @@ async def run(args: argparse.Namespace) -> None:
|
|
|
123
127
|
"gpu_type": "A10G",
|
|
124
128
|
"container_count": 1,
|
|
125
129
|
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
130
|
+
},
|
|
131
|
+
},
|
|
128
132
|
}
|
|
129
133
|
|
|
130
134
|
create_resp = await ft.create_sft_job(
|
|
@@ -194,6 +198,7 @@ async def run(args: argparse.Namespace) -> None:
|
|
|
194
198
|
except Exception as e:
|
|
195
199
|
# Always print full error details and traceback
|
|
196
200
|
import traceback
|
|
201
|
+
|
|
197
202
|
try:
|
|
198
203
|
from synth_ai.http import HTTPError # type: ignore
|
|
199
204
|
except Exception: # pragma: no cover - fallback if import shape changes
|
|
@@ -206,7 +211,7 @@ async def run(args: argparse.Namespace) -> None:
|
|
|
206
211
|
print("Traceback:")
|
|
207
212
|
print(tb)
|
|
208
213
|
# If HTTP error from backend, surface structured fields
|
|
209
|
-
if
|
|
214
|
+
if "HTTPError" in str(type(e)) or (isinstance((), tuple) and False):
|
|
210
215
|
pass
|
|
211
216
|
try:
|
|
212
217
|
if HTTPError and isinstance(e, HTTPError): # type: ignore[arg-type]
|
|
@@ -214,9 +219,9 @@ async def run(args: argparse.Namespace) -> None:
|
|
|
214
219
|
print(f" status={e.status}")
|
|
215
220
|
print(f" url={e.url}")
|
|
216
221
|
print(f" message={e.message}")
|
|
217
|
-
if getattr(e,
|
|
222
|
+
if getattr(e, "detail", None) is not None:
|
|
218
223
|
print(f" detail={e.detail}")
|
|
219
|
-
if getattr(e,
|
|
224
|
+
if getattr(e, "body_snippet", None):
|
|
220
225
|
print(f" body_snippet={e.body_snippet}")
|
|
221
226
|
except Exception:
|
|
222
227
|
pass
|
|
@@ -6,6 +6,7 @@ import asyncio
|
|
|
6
6
|
|
|
7
7
|
import sys
|
|
8
8
|
import os
|
|
9
|
+
|
|
9
10
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))))
|
|
10
11
|
|
|
11
12
|
from synth_ai.learning import FtClient, validate_training_jsonl
|
|
@@ -31,4 +32,3 @@ def main() -> None:
|
|
|
31
32
|
|
|
32
33
|
if __name__ == "__main__":
|
|
33
34
|
main()
|
|
34
|
-
|
|
@@ -11,6 +11,7 @@ from synth_ai.config.base_url import get_backend_from_env
|
|
|
11
11
|
try:
|
|
12
12
|
from dotenv import load_dotenv # type: ignore[reportMissingImports]
|
|
13
13
|
except Exception: # pragma: no cover
|
|
14
|
+
|
|
14
15
|
def load_dotenv(*args, **kwargs): # type: ignore[no-redef]
|
|
15
16
|
return False
|
|
16
17
|
|
|
@@ -71,7 +72,9 @@ def load_env(mode: str | None = None) -> tuple[str, str]:
|
|
|
71
72
|
or os.getenv("TESTING_LOCAL_SYNTH_API_KEY", "").strip()
|
|
72
73
|
)
|
|
73
74
|
if not base_url or not api_key:
|
|
74
|
-
raise RuntimeError(
|
|
75
|
+
raise RuntimeError(
|
|
76
|
+
"Missing LOCAL_BACKEND_URL or DEV_SYNTH_API_KEY/TESTING_LOCAL_SYNTH_API_KEY in environment/.env"
|
|
77
|
+
)
|
|
75
78
|
elif mode == "dev":
|
|
76
79
|
base_url = os.getenv("DEV_BACKEND_URL", "").strip()
|
|
77
80
|
api_key = os.getenv("DEV_SYNTH_API_KEY", "").strip()
|
|
@@ -85,7 +88,9 @@ def load_env(mode: str | None = None) -> tuple[str, str]:
|
|
|
85
88
|
or os.getenv("SYNTH_API_KEY", "").strip()
|
|
86
89
|
)
|
|
87
90
|
if not api_key:
|
|
88
|
-
raise RuntimeError(
|
|
91
|
+
raise RuntimeError(
|
|
92
|
+
"Missing PROD_SYNTH_API_KEY/TESTING_PROD_SYNTH_API_KEY/SYNTH_API_KEY in environment/.env"
|
|
93
|
+
)
|
|
89
94
|
base_url = base_url.rstrip("/")
|
|
90
95
|
print(f"SYNTH backend: {base_url} (mode={mode})")
|
|
91
96
|
# Also print masked API key and source
|
examples/rl/download_dataset.py
CHANGED
|
@@ -20,10 +20,12 @@ def extract_examples(dataset: Any, *, limit: int | None) -> list[dict[str, str]]
|
|
|
20
20
|
solution = item.get("solution") or ""
|
|
21
21
|
if isinstance(solution, list):
|
|
22
22
|
solution = "\n".join(str(part) for part in solution)
|
|
23
|
-
examples.append(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
examples.append(
|
|
24
|
+
{
|
|
25
|
+
"problem": problem,
|
|
26
|
+
"solution": solution,
|
|
27
|
+
}
|
|
28
|
+
)
|
|
27
29
|
return examples
|
|
28
30
|
|
|
29
31
|
|
|
@@ -35,12 +37,26 @@ def write_jsonl(path: Path, rows: list[dict[str, str]]) -> None:
|
|
|
35
37
|
|
|
36
38
|
|
|
37
39
|
def main() -> None:
|
|
38
|
-
parser = argparse.ArgumentParser(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
parser.add_argument(
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
parser = argparse.ArgumentParser(
|
|
41
|
+
description="Download MATH dataset splits to JSONL for offline use"
|
|
42
|
+
)
|
|
43
|
+
parser.add_argument(
|
|
44
|
+
"--output-dir", default="examples/rl/data", help="Directory to write <split>.jsonl files"
|
|
45
|
+
)
|
|
46
|
+
parser.add_argument(
|
|
47
|
+
"--dataset",
|
|
48
|
+
default="nlile/hendrycks-MATH-benchmark",
|
|
49
|
+
help="Hugging Face dataset identifier",
|
|
50
|
+
)
|
|
51
|
+
parser.add_argument(
|
|
52
|
+
"--config", default="algebra", help="Hugging Face dataset config (if required)"
|
|
53
|
+
)
|
|
54
|
+
parser.add_argument(
|
|
55
|
+
"--splits", nargs="*", default=["train", "validation", "test"], help="Splits to download"
|
|
56
|
+
)
|
|
57
|
+
parser.add_argument(
|
|
58
|
+
"--limit", type=int, default=None, help="Optional cap on examples per split"
|
|
59
|
+
)
|
|
44
60
|
args = parser.parse_args()
|
|
45
61
|
|
|
46
62
|
output_dir = Path(args.output_dir).expanduser()
|
examples/rl/run_eval.py
CHANGED
|
@@ -25,7 +25,10 @@ class TaskAppClient:
|
|
|
25
25
|
async def __aenter__(self) -> "TaskAppClient":
|
|
26
26
|
headers = {"X-API-Key": self.api_key} if self.api_key else {}
|
|
27
27
|
self._client = httpx.AsyncClient(
|
|
28
|
-
base_url=self.base_url,
|
|
28
|
+
base_url=self.base_url,
|
|
29
|
+
headers=headers,
|
|
30
|
+
timeout=httpx.Timeout(120.0),
|
|
31
|
+
follow_redirects=True,
|
|
29
32
|
)
|
|
30
33
|
return self
|
|
31
34
|
|
|
@@ -39,7 +42,10 @@ class TaskAppClient:
|
|
|
39
42
|
if self._client is None:
|
|
40
43
|
headers = {"X-API-Key": self.api_key} if self.api_key else {}
|
|
41
44
|
self._client = httpx.AsyncClient(
|
|
42
|
-
base_url=self.base_url,
|
|
45
|
+
base_url=self.base_url,
|
|
46
|
+
headers=headers,
|
|
47
|
+
timeout=httpx.Timeout(120.0),
|
|
48
|
+
follow_redirects=True,
|
|
43
49
|
)
|
|
44
50
|
return self._client
|
|
45
51
|
|
|
@@ -103,8 +109,7 @@ def _math_tool_schema() -> List[Dict[str, Any]]:
|
|
|
103
109
|
"answer": {
|
|
104
110
|
"type": "string",
|
|
105
111
|
"description": "Final answer in simplest form",
|
|
106
|
-
}
|
|
107
|
-
,
|
|
112
|
+
},
|
|
108
113
|
"explanation": {
|
|
109
114
|
"type": "string",
|
|
110
115
|
"description": "Optional explanation of reasoning",
|
|
@@ -203,9 +208,7 @@ async def _choose_actions(
|
|
|
203
208
|
|
|
204
209
|
if provider == "groq":
|
|
205
210
|
# Task app proxies Groq requests; reuse existing headers on the client
|
|
206
|
-
response = await client.client.post(
|
|
207
|
-
"/proxy/groq/v1/chat/completions", json=payload
|
|
208
|
-
)
|
|
211
|
+
response = await client.client.post("/proxy/groq/v1/chat/completions", json=payload)
|
|
209
212
|
response.raise_for_status()
|
|
210
213
|
body = response.json()
|
|
211
214
|
else:
|
|
@@ -223,18 +226,15 @@ async def _choose_actions(
|
|
|
223
226
|
headers=headers or None,
|
|
224
227
|
)
|
|
225
228
|
except httpx.ReadTimeout as exc:
|
|
226
|
-
raise RuntimeError(
|
|
227
|
-
"Inference request timed out. Check the inference service." ) from exc
|
|
229
|
+
raise RuntimeError("Inference request timed out. Check the inference service.") from exc
|
|
228
230
|
try:
|
|
229
231
|
body = response.json()
|
|
230
232
|
except Exception:
|
|
231
233
|
body = {"raw": response.text[:800]}
|
|
232
234
|
if response.status_code >= 500:
|
|
233
|
-
raise RuntimeError(
|
|
234
|
-
f"Inference server error {response.status_code}: {body}")
|
|
235
|
+
raise RuntimeError(f"Inference server error {response.status_code}: {body}")
|
|
235
236
|
if response.status_code >= 400:
|
|
236
|
-
raise RuntimeError(
|
|
237
|
-
f"Inference request invalid ({response.status_code}): {body}")
|
|
237
|
+
raise RuntimeError(f"Inference request invalid ({response.status_code}): {body}")
|
|
238
238
|
tool_calls = _parse_tool_calls(body)
|
|
239
239
|
return tool_calls, body
|
|
240
240
|
|
|
@@ -371,7 +371,9 @@ async def main() -> None:
|
|
|
371
371
|
seed_start = int(cfg.get("seed_start") or 0)
|
|
372
372
|
|
|
373
373
|
policy_cfg = _default_policy_cfg(cfg)
|
|
374
|
-
provider_hint =
|
|
374
|
+
provider_hint = (
|
|
375
|
+
cfg.get("provider") or cfg.get("policy", {}).get("provider") or policy_cfg.get("provider")
|
|
376
|
+
)
|
|
375
377
|
provider = _detect_provider(model, provider_hint)
|
|
376
378
|
policy_cfg.pop("provider", None)
|
|
377
379
|
|
|
@@ -412,7 +414,7 @@ async def main() -> None:
|
|
|
412
414
|
problem = data.get("problem")
|
|
413
415
|
tool_calls = data.get("tool_calls") or []
|
|
414
416
|
print(
|
|
415
|
-
f"Episode {episode+1}/{episodes} seed={seed} status={status} reward={data.get('reward')}\n"
|
|
417
|
+
f"Episode {episode + 1}/{episodes} seed={seed} status={status} reward={data.get('reward')}\n"
|
|
416
418
|
f" problem: {problem!r}\n"
|
|
417
419
|
f" tool : {tool_calls!r}\n"
|
|
418
420
|
f" answer : {answer!r}\n expected: {expected!r}",
|