synth-ai 0.2.6.dev3__py3-none-any.whl → 0.2.6.dev5__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.

synth_ai/__init__.py CHANGED
@@ -25,7 +25,7 @@ tracing = None # type: ignore
25
25
  EventPartitionElement = RewardSignal = SystemTrace = TrainingQuestion = None # type: ignore
26
26
  trace_event_async = trace_event_sync = upload = None # type: ignore
27
27
 
28
- __version__ = "0.2.6.dev1"
28
+ __version__ = "0.2.6.dev4"
29
29
  __all__ = [
30
30
  "LM",
31
31
  "OpenAI",
@@ -224,8 +224,59 @@ def _popen_stream_capture(cmd: list[str], cwd: str | None = None, env: dict | No
224
224
  return int(proc.returncode or 0), "\n".join(buf_lines)
225
225
 
226
226
 
227
+ def _mask_secret_args(args: list[str]) -> list[str]:
228
+ masked: list[str] = []
229
+ for a in args:
230
+ if "=" in a and any(a.startswith(prefix) for prefix in ("ENVIRONMENT_API_KEY=", "OPENAI_API_KEY=", "SYNTH_API_KEY=")):
231
+ try:
232
+ key, value = a.split("=", 1)
233
+ tail = value[-5:] if len(value) >= 5 else value
234
+ masked.append(f"{key}=***{tail}")
235
+ except Exception:
236
+ masked.append("<masked>")
237
+ else:
238
+ masked.append(a)
239
+ return masked
240
+
241
+
242
+ def _ensure_modal_secret(
243
+ secret_name: str,
244
+ *,
245
+ values: dict[str, str],
246
+ label: str = "deploy",
247
+ replace: bool = False,
248
+ ) -> bool:
249
+ prefix = f"[{label}]"
250
+ if not secret_name.strip():
251
+ raise RuntimeError("Secret name is required")
252
+
253
+ if not values:
254
+ raise RuntimeError("No values provided to create Modal secret")
255
+
256
+ create_args = [f"{k}={v}" for k, v in values.items()]
257
+ create_cmd = ["uv", "run", "modal", "secret", "create", secret_name, *create_args]
258
+
259
+ if replace:
260
+ print(f"{prefix} Removing Modal secret '{secret_name}' (if present)…")
261
+ delete_cmd = ["bash", "-lc", f"printf 'y\\n' | uv run modal secret delete {secret_name}"]
262
+ print(f"{prefix} Command:", " ".join(delete_cmd))
263
+ delete_code = _popen_stream(delete_cmd)
264
+ if delete_code != 0:
265
+ print(f"{prefix} Warning: delete command exited with {delete_code}; continuing to create")
266
+
267
+ print(f"\n{prefix} Creating Modal secret '{secret_name}'…")
268
+ print(f"{prefix} Command:", " ".join(_mask_secret_args(create_cmd)))
269
+ code = _popen_stream(create_cmd)
270
+ if code != 0:
271
+ raise RuntimeError("Failed to provision Modal secret (see logs above)")
272
+
273
+ return True
274
+
275
+
227
276
  def cmd_deploy(args: argparse.Namespace) -> int:
228
277
  env = demo_core.load_env()
278
+ cwd_env_path = os.path.join(os.getcwd(), ".env")
279
+ local_env = demo_core.load_dotenv_file(cwd_env_path)
229
280
  url = ""
230
281
  app_name = env.task_app_name or ""
231
282
  try:
@@ -279,6 +330,51 @@ def cmd_deploy(args: argparse.Namespace) -> int:
279
330
  if not proceed:
280
331
  print("Aborted by user.")
281
332
  return 1
333
+
334
+ secret_name = (env.task_app_secret_name or "").strip() or f"{name_in}-secret"
335
+ env_key = (env.env_api_key or "").strip() or None
336
+ if env_key is None:
337
+ from synth_ai.rl.secrets import mint_environment_api_key
338
+
339
+ env_key = mint_environment_api_key()
340
+ demo_core.persist_env_api_key(env_key)
341
+ demo_core.persist_dotenv_values({"ENVIRONMENT_API_KEY": env_key})
342
+ os.environ["ENVIRONMENT_API_KEY"] = env_key
343
+ env.env_api_key = env_key
344
+ local_env["ENVIRONMENT_API_KEY"] = env_key
345
+ print("[deploy] Minted new ENVIRONMENT_API_KEY")
346
+
347
+ synth_key = (env.synth_api_key or os.environ.get("SYNTH_API_KEY") or local_env.get("SYNTH_API_KEY") or "").strip()
348
+ if not synth_key:
349
+ synth_key = input("Enter SYNTH_API_KEY for Modal secret (required): ").strip()
350
+ if not synth_key:
351
+ print("SYNTH_API_KEY is required to create the Modal secret.")
352
+ return 1
353
+ demo_core.persist_api_key(synth_key)
354
+ demo_core.persist_dotenv_values({"SYNTH_API_KEY": synth_key})
355
+ env.synth_api_key = synth_key
356
+
357
+ openai_key = (os.environ.get("OPENAI_API_KEY") or local_env.get("OPENAI_API_KEY") or "").strip()
358
+ if not openai_key:
359
+ openai_key = input("Enter OPENAI_API_KEY for Modal secret (required): ").strip()
360
+ if not openai_key:
361
+ print("OPENAI_API_KEY is required to create the Modal secret.")
362
+ return 1
363
+ demo_core.persist_dotenv_values({"OPENAI_API_KEY": openai_key})
364
+ local_env["OPENAI_API_KEY"] = openai_key
365
+
366
+ values = {"SYNTH_API_KEY": synth_key, "OPENAI_API_KEY": openai_key}
367
+ if env_key:
368
+ values["ENVIRONMENT_API_KEY"] = env_key
369
+
370
+ try:
371
+ created = _ensure_modal_secret(secret_name, values=values, label="deploy", replace=True)
372
+ except RuntimeError as secret_err:
373
+ print(f"Failed to prepare Modal secret '{secret_name}': {secret_err}")
374
+ return 2
375
+ if created:
376
+ print(f"[deploy] Modal secret '{secret_name}' provisioned.")
377
+
282
378
  deploy_cmd = ["uv", "run", "python", "-m", "modal", "deploy", "--name", name_in, app_path]
283
379
  print("\nStreaming Modal build/deploy logs (this can take several minutes on first run)…\n")
284
380
  code, deploy_logs = _popen_stream_capture(deploy_cmd)
@@ -346,8 +442,6 @@ def cmd_deploy(args: argparse.Namespace) -> int:
346
442
 
347
443
 
348
444
  def cmd_configure(args: argparse.Namespace) -> int:
349
- from synth_ai.rl.secrets import mint_environment_api_key
350
-
351
445
  env = demo_core.load_env()
352
446
  cwd_env_path = os.path.join(os.getcwd(), ".env")
353
447
  local_env = demo_core.load_dotenv_file(cwd_env_path)
@@ -362,13 +456,9 @@ def cmd_configure(args: argparse.Namespace) -> int:
362
456
  demo_core.persist_dotenv_values({"SYNTH_API_KEY": synth_key})
363
457
 
364
458
  env_key = env.env_api_key.strip()
365
- minted_env_key = False
366
459
  if not env_key:
367
- env_key = mint_environment_api_key()
368
- minted_env_key = True
369
- print("Minted new ENVIRONMENT_API_KEY")
370
- demo_core.persist_env_api_key(env_key)
371
- demo_core.persist_dotenv_values({"ENVIRONMENT_API_KEY": env_key})
460
+ print("ENVIRONMENT_API_KEY missing; run `uvx synth-ai rl_demo deploy` to mint and store one.")
461
+ return 1
372
462
 
373
463
  task_url = env.task_app_base_url
374
464
  if not task_url or not _is_modal_public_url(task_url):
@@ -418,43 +508,20 @@ def cmd_configure(args: argparse.Namespace) -> int:
418
508
  })
419
509
 
420
510
  # Ensure Modal secret has the environment API key (and optional extras).
421
- secret_args = [f"ENVIRONMENT_API_KEY={env_key}"]
422
511
  openai_key = (os.environ.get("OPENAI_API_KEY") or local_env.get("OPENAI_API_KEY") or "").strip()
423
- if openai_key:
424
- secret_args.append(f"OPENAI_API_KEY={openai_key}")
425
512
  synth_for_secret = synth_key
426
- if synth_for_secret:
427
- secret_args.append(f"SYNTH_API_KEY={synth_for_secret}")
428
513
 
429
- create_cmd = ["uv", "run", "modal", "secret", "create", secret_name, *secret_args]
430
- def _mask_args(args: list[str]) -> list[str]:
431
- masked: list[str] = []
432
- for a in args:
433
- if "=" in a and any(a.startswith(k + "=") for k in ("ENVIRONMENT_API_KEY", "OPENAI_API_KEY", "SYNTH_API_KEY")):
434
- try:
435
- k, v = a.split("=", 1)
436
- suf = v[-5:] if len(v) >= 5 else ""
437
- masked.append(f"{k}=***{suf}")
438
- except Exception:
439
- masked.append("<masked>")
440
- else:
441
- masked.append(a)
442
- return masked
514
+ secret_values: dict[str, str] = {"ENVIRONMENT_API_KEY": env_key}
515
+ if openai_key:
516
+ secret_values["OPENAI_API_KEY"] = openai_key
517
+ if synth_for_secret:
518
+ secret_values["SYNTH_API_KEY"] = synth_for_secret
443
519
 
444
- print("\n[configure] Creating Modal secret (streaming logs)…")
445
- print("[configure] Command:", " ".join(_mask_args(create_cmd)))
446
- code = _popen_stream(create_cmd)
447
- if code != 0:
448
- print("[configure] Secret create failed; attempting delete → create")
449
- delete_cmd = ["bash", "-lc", f"printf 'y\\n' | uv run modal secret delete {secret_name}"]
450
- print("[configure] Command:", " ".join(delete_cmd))
451
- _popen_stream(delete_cmd)
452
- print("[configure] Retrying secret create…")
453
- print("[configure] Command:", " ".join(_mask_args(create_cmd)))
454
- code = _popen_stream(create_cmd)
455
- if code != 0:
456
- print("[configure] Failed to provision Modal secret.")
457
- return 2
520
+ try:
521
+ _ensure_modal_secret(secret_name, values=secret_values, label="configure", replace=True)
522
+ except RuntimeError as err:
523
+ print(f"[configure] Failed to provision Modal secret: {err}")
524
+ return 2
458
525
 
459
526
  # Verify task app can read the secret by hitting rollout health with X-API-Key.
460
527
  rollout_url = task_url.rstrip("/") + "/health/rollout"
@@ -557,8 +624,6 @@ def cmd_configure(args: argparse.Namespace) -> int:
557
624
  "TASK_APP_NAME": app_name,
558
625
  "TASK_APP_SECRET_NAME": secret_name,
559
626
  }, indent=2))
560
- if minted_env_key:
561
- print(f"Stored minted ENVIRONMENT_API_KEY in {cwd_env_path}")
562
627
  print("Next: uvx synth-ai rl_demo run")
563
628
  return 0
564
629
 
@@ -938,96 +1003,6 @@ def cmd_run(args: argparse.Namespace) -> int:
938
1003
  return 0
939
1004
 
940
1005
 
941
- def cmd_eval(args: argparse.Namespace) -> int:
942
- env = demo_core.load_env()
943
- # Ensure required env
944
- if not env.task_app_base_url:
945
- print("Task app URL missing. Run: uvx synth-ai rl_demo deploy")
946
- return 1
947
- # Load config: prefer CWD demo_config.toml; else packaged default
948
- cfg_path: str | None = None
949
- if getattr(args, "config", None):
950
- p = os.path.abspath(args.config)
951
- if not os.path.isfile(p):
952
- print(f"Config not found: {p}")
953
- return 1
954
- cfg_path = p
955
- else:
956
- cwd_prepared = os.path.abspath(os.path.join(os.getcwd(), "demo_config.toml"))
957
- if os.path.isfile(cwd_prepared):
958
- cfg_path = cwd_prepared
959
- else:
960
- packaged = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "demo_task_apps", "math", "config.toml"))
961
- cfg_path = packaged if os.path.isfile(packaged) else None
962
- if not cfg_path:
963
- print("No config TOML found.")
964
- return 1
965
-
966
- import tomllib
967
- with open(cfg_path, "rb") as fh:
968
- inline_cfg = tomllib.load(fh)
969
-
970
- # Model selection prompt
971
- default_model = (args.model or (inline_cfg.get("model", {}) or {}).get("name") or "Qwen/Qwen3-0.6B")
972
- entered = input(f"Model to evaluate [{default_model}]: ").strip()
973
- model = entered or default_model
974
- confirm = (input(f"Use model '{model}'? [Y/n]: ").strip().lower() or "y").startswith("y")
975
- if not confirm:
976
- print("Aborted by user.")
977
- return 1
978
-
979
- # Build on-board rollout request to the Task App (no backend RL job)
980
- # Use Synth backend chat-completions proxy as inference URL (derive from DEV_BACKEND_URL)
981
- # Ensure /api suffix for backend, then use proxy prefix for chat completions
982
- backend_api = (env.dev_backend_url or "https://agent-learning.onrender.com/api").rstrip("/")
983
- if not backend_api.endswith("/api"):
984
- backend_api = f"{backend_api}/api"
985
- inference_url = f"{backend_api}/proxy"
986
- # ops: alternate agent/env for a small number of decisions (from config max_steps_per_episode if present)
987
- try:
988
- steps = int((inline_cfg.get("rollout", {}) or {}).get("max_steps_per_episode", 4))
989
- except Exception:
990
- steps = 4
991
- ops: list[str] = []
992
- for _ in range(max(1, steps // 2)):
993
- ops.extend(["agent", "env"])
994
- env_name = (inline_cfg.get("rollout", {}) or {}).get("env_name") or "math"
995
- policy_name = (inline_cfg.get("rollout", {}) or {}).get("policy_name") or "math-react"
996
- run_id = f"eval-{int(time.time())}"
997
- body: Dict[str, Any] = {
998
- "run_id": run_id,
999
- "env": {
1000
- "env_name": env_name,
1001
- "config": inline_cfg.get("rollout", {}) or {},
1002
- },
1003
- "policy": {
1004
- "policy_name": policy_name,
1005
- "config": {"model": model, "inference_url": inference_url},
1006
- },
1007
- "ops": ops,
1008
- "on_done": "terminate",
1009
- }
1010
- # POST to task app rollout endpoint
1011
- headers = {"Content-Type": "application/json"}
1012
- if env.env_api_key:
1013
- headers["X-API-Key"] = env.env_api_key
1014
- rc, resp = _http("POST", env.task_app_base_url.rstrip("/") + "/rollout", headers=headers, body=body)
1015
- if rc not in (200, 201) or not isinstance(resp, dict):
1016
- print("Eval rollout failed:", rc)
1017
- try:
1018
- print(json.dumps(resp, indent=2) if isinstance(resp, dict) else str(resp))
1019
- except Exception:
1020
- print(str(resp))
1021
- print("Request body was:\n" + json.dumps(body, indent=2))
1022
- return 2
1023
- metrics = (resp.get("metrics") if isinstance(resp, dict) else None) or {}
1024
- mean = metrics.get("mean_return")
1025
- if mean is not None:
1026
- print(f"eval.reward_mean={mean}")
1027
- else:
1028
- print(json.dumps(resp, indent=2))
1029
- return 0
1030
-
1031
1006
  def main(argv: list[str] | None = None) -> int:
1032
1007
  p = argparse.ArgumentParser(prog="synth-ai")
1033
1008
  sub = p.add_subparsers(dest="cmd")
@@ -1069,14 +1044,6 @@ def main(argv: list[str] | None = None) -> int:
1069
1044
 
1070
1045
  _add_parser(["rl_demo.run", "demo.run"], configure=_run_opts)
1071
1046
 
1072
- def _eval_opts(parser):
1073
- parser.add_argument("--config", type=str, default=None, help="Path to TOML config (optional)")
1074
- parser.add_argument("--model", type=str, default=None, help="Model to evaluate (default Qwen/Qwen3-0.6B)")
1075
- parser.add_argument("--timeout", type=int, default=300, help="Seconds to wait for metrics")
1076
- parser.set_defaults(func=cmd_eval)
1077
-
1078
- _add_parser(["rl_demo.eval", "demo.eval"], configure=_eval_opts)
1079
-
1080
1047
  args = p.parse_args(argv)
1081
1048
  if not hasattr(args, "func"):
1082
1049
  p.print_help()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: synth-ai
3
- Version: 0.2.6.dev3
3
+ Version: 0.2.6.dev5
4
4
  Summary: RL as a service SDK - Core AI functionality and tracing
5
5
  Author-email: Synth AI <josh@usesynth.ai>
6
6
  License-Expression: MIT
@@ -1,4 +1,4 @@
1
- synth_ai/__init__.py,sha256=1mIN_hDsscPcKTV1ciH5N12pONnczGV-RmR8EqroksI,1341
1
+ synth_ai/__init__.py,sha256=NixuXddy4lS2Wmj0F8eMt0HS_oYCTnq3iVVq5VYwWIc,1341
2
2
  synth_ai/__main__.py,sha256=Kh1xBKkTE5Vs2qNMtDuuOXerHUptMcOiF3YziOpC6DA,146
3
3
  synth_ai/http.py,sha256=lqjFXDmAP_xgfywK_rDSOVxuMy4rDH9S3Rtu9k1tLmk,1028
4
4
  synth_ai/http_client.py,sha256=_9J8rUGoItUMnJLGZw7r0uXiJeLWR939kByRkvtP1XM,4429
@@ -20,7 +20,7 @@ synth_ai/config/base_url.py,sha256=Bk7Bd9jKJP-LF0SW--WE01JhMfvOB6NUkFMRgPMnJuQ,3
20
20
  synth_ai/core/experiment.py,sha256=hLkPtzUFA7iY3-QpeJ5K8YjvQeyfqnjab5P2CFaojys,236
21
21
  synth_ai/core/system.py,sha256=s-Z7np2ISYmYc1r9YN-y2yb3cgRlOalrh0iaqnxeo84,206
22
22
  synth_ai/demos/core/__init__.py,sha256=A2FjhY7KXGtyzdQXqeTPCkEhHfrH-eQg6bvP8HaYhZM,36
23
- synth_ai/demos/core/cli.py,sha256=Q7vgPuDRSb2V_TWsiBtzEIn8ujr3awDu3X02wBRovYk,46052
23
+ synth_ai/demos/core/cli.py,sha256=MuJELXFRxtyucek9b05Oo54pmJs5QGo45SV3hDZTFO0,44729
24
24
  synth_ai/demos/demo_task_apps/__init__.py,sha256=8aUGEGpWUw11GRb3wQXRAmQ99yjAt5qd5FCTDJpXWjI,44
25
25
  synth_ai/demos/demo_task_apps/core.py,sha256=3-C2dGdaqVqrVjnsxU2n6kGcuaprwuszBcTHePBypwo,13580
26
26
  synth_ai/demos/demo_task_apps/math/__init__.py,sha256=WBzpZwSn7pRarBmhopQi34i9bEm05-71eM3siboOavY,43
@@ -411,9 +411,9 @@ synth_ai/v0/tracing_v1/events/manage.py,sha256=ZDXXP-ZwLH9LCsmw7Ru9o55d7bl_diPtJ
411
411
  synth_ai/v0/tracing_v1/events/scope.py,sha256=BuBkhSpVHUJt8iGT9HJZF82rbb88mQcd2vM2shg-w2I,2550
412
412
  synth_ai/v0/tracing_v1/events/store.py,sha256=0342lvAcalyJbVEIzQFaPuMQGgwiFm7M5rE6gr-G0E8,9041
413
413
  synth_ai/zyk/__init__.py,sha256=htVLnzTYQ5rxzYpzSYBm7_o6uNKZ3pB_PrqkBrgTRS4,771
414
- synth_ai-0.2.6.dev3.dist-info/licenses/LICENSE,sha256=ynhjRQUfqA_RdGRATApfFA_fBAy9cno04sLtLUqxVFM,1069
415
- synth_ai-0.2.6.dev3.dist-info/METADATA,sha256=oXQEoe8kmLWZ79-GLOG9uJCc38TMoquqHTwyihkd-W0,3980
416
- synth_ai-0.2.6.dev3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
417
- synth_ai-0.2.6.dev3.dist-info/entry_points.txt,sha256=Neq-3bT7TAijjgOIR77pKL-WYg6TWBDeO8pp_nL4vGY,91
418
- synth_ai-0.2.6.dev3.dist-info/top_level.txt,sha256=fBmtZyVHuKaGa29oHBaaUkrUIWTqSpoVMPiVdCDP3k8,9
419
- synth_ai-0.2.6.dev3.dist-info/RECORD,,
414
+ synth_ai-0.2.6.dev5.dist-info/licenses/LICENSE,sha256=ynhjRQUfqA_RdGRATApfFA_fBAy9cno04sLtLUqxVFM,1069
415
+ synth_ai-0.2.6.dev5.dist-info/METADATA,sha256=zOL3OaxOqOJIBabHtKPJqdEvEoAEYalzrOFHTxHu3N8,3980
416
+ synth_ai-0.2.6.dev5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
417
+ synth_ai-0.2.6.dev5.dist-info/entry_points.txt,sha256=Neq-3bT7TAijjgOIR77pKL-WYg6TWBDeO8pp_nL4vGY,91
418
+ synth_ai-0.2.6.dev5.dist-info/top_level.txt,sha256=fBmtZyVHuKaGa29oHBaaUkrUIWTqSpoVMPiVdCDP3k8,9
419
+ synth_ai-0.2.6.dev5.dist-info/RECORD,,