synth-ai 0.2.4.dev7__py3-none-any.whl → 0.2.4.dev9__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.

Files changed (154) hide show
  1. synth_ai/__init__.py +1 -1
  2. synth_ai/cli/__init__.py +6 -0
  3. synth_ai/cli/balance.py +3 -15
  4. synth_ai/cli/demo.py +68 -9
  5. synth_ai/cli/rl_demo.py +137 -0
  6. synth_ai/cli/root.py +65 -0
  7. synth_ai/config/base_url.py +47 -0
  8. synth_ai/demos/core/__init__.py +1 -0
  9. synth_ai/demos/core/cli.py +621 -0
  10. synth_ai/demos/demo_task_apps/__init__.py +1 -0
  11. synth_ai/demos/demo_task_apps/core.py +374 -0
  12. synth_ai/demos/demo_task_apps/math/__init__.py +1 -0
  13. synth_ai/demos/demo_task_apps/math/app.py +37 -0
  14. synth_ai/demos/demo_task_apps/math/config.toml +44 -0
  15. synth_ai/demos/demo_task_apps/math/deploy_modal.py +60 -0
  16. synth_ai/demos/demo_task_apps/math/deploy_task_app.sh +22 -0
  17. synth_ai/environments/examples/bandit/__init__.py +33 -0
  18. synth_ai/environments/examples/bandit/engine.py +294 -0
  19. synth_ai/environments/examples/bandit/environment.py +194 -0
  20. synth_ai/environments/examples/bandit/taskset.py +200 -0
  21. synth_ai/environments/examples/crafter_classic/agent_demos/analyze_semantic_words_markdown.py +250 -0
  22. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_comprehensive_evaluation.py +59 -0
  23. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_evaluation_browser.py +152 -0
  24. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_evaluation_config.toml +24 -0
  25. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_evaluation_framework.py +1194 -0
  26. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/crafter_synth_config.toml +56 -0
  27. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/filter_config_modal.toml +32 -0
  28. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/filter_traces_sft_turso.py +724 -0
  29. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/kick_off_ft_modal.py +384 -0
  30. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/analyze_action_results.py +53 -0
  31. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/analyze_agent_actions.py +178 -0
  32. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/analyze_latest_run.py +222 -0
  33. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/analyze_lm_traces.py +183 -0
  34. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/analyze_no_rewards.py +210 -0
  35. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/analyze_trace_issue.py +206 -0
  36. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/check_db_schema.py +49 -0
  37. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/check_latest_results.py +64 -0
  38. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/debug_agent_responses.py +88 -0
  39. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/quick_trace_check.py +77 -0
  40. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/compare_experiments.py +324 -0
  41. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/filter_traces_sft_turso.py +580 -0
  42. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/kick_off_ft_oai.py +362 -0
  43. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/multi_model_config.toml +49 -0
  44. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/analyze_enhanced_hooks.py +332 -0
  45. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/analyze_hook_events.py +97 -0
  46. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/analyze_hook_results.py +217 -0
  47. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/check_hook_storage.py +87 -0
  48. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/check_seeds.py +88 -0
  49. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/compare_seed_performance.py +195 -0
  50. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/custom_eval_pipelines.py +400 -0
  51. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/plot_hook_frequency.py +195 -0
  52. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/seed_analysis_summary.py +56 -0
  53. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/run_rollouts_for_models_and_compare_v3.py +858 -0
  54. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_quick_evaluation.py +52 -0
  55. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_react_agent.py +874 -0
  56. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_trace_evaluation.py +1412 -0
  57. synth_ai/environments/examples/crafter_classic/agent_demos/example_v3_usage.py +216 -0
  58. synth_ai/environments/examples/crafter_classic/agent_demos/old/compare_traces.py +296 -0
  59. synth_ai/environments/examples/crafter_classic/agent_demos/old/crafter_comprehensive_evaluation.py +58 -0
  60. synth_ai/environments/examples/crafter_classic/agent_demos/old/crafter_env_serialization.py +464 -0
  61. synth_ai/environments/examples/crafter_classic/agent_demos/old/crafter_evaluation_browser.py +152 -0
  62. synth_ai/environments/examples/crafter_classic/agent_demos/old/crafter_quick_evaluation.py +51 -0
  63. synth_ai/environments/examples/crafter_classic/agent_demos/old/crafter_trace_evaluation.py +1412 -0
  64. synth_ai/environments/examples/crafter_classic/agent_demos/old/debug_player_loss.py +112 -0
  65. synth_ai/environments/examples/crafter_classic/agent_demos/old/diagnose_service.py +203 -0
  66. synth_ai/environments/examples/crafter_classic/agent_demos/old/diagnose_slowness.py +305 -0
  67. synth_ai/environments/examples/crafter_classic/agent_demos/old/eval_by_difficulty.py +126 -0
  68. synth_ai/environments/examples/crafter_classic/agent_demos/old/eval_example.py +94 -0
  69. synth_ai/environments/examples/crafter_classic/agent_demos/old/explore_saved_states.py +142 -0
  70. synth_ai/environments/examples/crafter_classic/agent_demos/old/filter_traces_sft.py +26 -0
  71. synth_ai/environments/examples/crafter_classic/agent_demos/old/filter_traces_sft_OLD.py +984 -0
  72. synth_ai/environments/examples/crafter_classic/agent_demos/old/generate_ft_data_gemini.py +724 -0
  73. synth_ai/environments/examples/crafter_classic/agent_demos/old/generate_ft_data_modal.py +386 -0
  74. synth_ai/environments/examples/crafter_classic/agent_demos/old/generate_ft_metadata.py +205 -0
  75. synth_ai/environments/examples/crafter_classic/agent_demos/old/kick_off_ft_gemini.py +150 -0
  76. synth_ai/environments/examples/crafter_classic/agent_demos/old/kick_off_ft_modal.py +283 -0
  77. synth_ai/environments/examples/crafter_classic/agent_demos/old/prepare_vertex_ft.py +280 -0
  78. synth_ai/environments/examples/crafter_classic/agent_demos/old/profile_env_slowness.py +456 -0
  79. synth_ai/environments/examples/crafter_classic/agent_demos/old/replicate_issue.py +166 -0
  80. synth_ai/environments/examples/crafter_classic/agent_demos/old/run_and_eval.py +102 -0
  81. synth_ai/environments/examples/crafter_classic/agent_demos/old/run_comparison.py +128 -0
  82. synth_ai/environments/examples/crafter_classic/agent_demos/old/run_qwen_rollouts.py +655 -0
  83. synth_ai/environments/examples/crafter_classic/agent_demos/old/trace_eval_OLD.py +202 -0
  84. synth_ai/environments/examples/crafter_classic/agent_demos/old/validate_openai_format.py +166 -0
  85. synth_ai/environments/examples/crafter_classic/environment.py +41 -2
  86. synth_ai/environments/examples/crafter_custom/agent_demos/__init__.py +1 -0
  87. synth_ai/environments/examples/crafter_custom/agent_demos/trace_eval.py +202 -0
  88. synth_ai/environments/examples/crafter_custom/old/analyze_diamond_issue.py +159 -0
  89. synth_ai/environments/examples/crafter_custom/old/analyze_diamond_spawning.py +158 -0
  90. synth_ai/environments/examples/crafter_custom/old/compare_worlds.py +71 -0
  91. synth_ai/environments/examples/crafter_custom/old/dataset_stats.py +105 -0
  92. synth_ai/environments/examples/crafter_custom/old/diamond_spawning_summary.py +119 -0
  93. synth_ai/environments/examples/crafter_custom/old/example_dataset_usage.py +52 -0
  94. synth_ai/environments/examples/enron/units/keyword_stats.py +112 -0
  95. synth_ai/environments/examples/minigrid/agent_demos/minigrid_evaluation_framework.py +1188 -0
  96. synth_ai/environments/examples/minigrid/agent_demos/minigrid_quick_evaluation.py +48 -0
  97. synth_ai/environments/examples/minigrid/agent_demos/minigrid_react_agent.py +562 -0
  98. synth_ai/environments/examples/minigrid/agent_demos/minigrid_trace_evaluation.py +221 -0
  99. synth_ai/environments/examples/nethack/agent_demos/nethack_evaluation_framework.py +981 -0
  100. synth_ai/environments/examples/nethack/agent_demos/nethack_quick_evaluation.py +74 -0
  101. synth_ai/environments/examples/nethack/agent_demos/nethack_react_agent.py +831 -0
  102. synth_ai/environments/examples/red/agent_demos/__init__.py +1 -0
  103. synth_ai/environments/examples/red/units/__init__.py +1 -0
  104. synth_ai/environments/examples/sokoban/agent_demos/sokoban_full_eval.py +899 -0
  105. synth_ai/environments/examples/sokoban/units/astar_common.py +95 -0
  106. synth_ai/environments/service/app.py +8 -0
  107. synth_ai/http.py +102 -0
  108. synth_ai/inference/__init__.py +7 -0
  109. synth_ai/inference/client.py +20 -0
  110. synth_ai/install_sqld.sh +40 -0
  111. synth_ai/jobs/client.py +246 -0
  112. synth_ai/learning/__init__.py +24 -0
  113. synth_ai/learning/client.py +149 -0
  114. synth_ai/learning/config.py +43 -0
  115. synth_ai/learning/constants.py +29 -0
  116. synth_ai/learning/ft_client.py +59 -0
  117. synth_ai/learning/health.py +43 -0
  118. synth_ai/learning/jobs.py +205 -0
  119. synth_ai/learning/rl_client.py +256 -0
  120. synth_ai/learning/sse.py +58 -0
  121. synth_ai/learning/validators.py +48 -0
  122. synth_ai/lm/core/main_v3.py +13 -0
  123. synth_ai/lm/core/synth_models.py +48 -0
  124. synth_ai/lm/core/vendor_clients.py +9 -6
  125. synth_ai/lm/vendors/core/openai_api.py +31 -3
  126. synth_ai/lm/vendors/openai_standard.py +45 -14
  127. synth_ai/lm/vendors/supported/custom_endpoint.py +12 -2
  128. synth_ai/lm/vendors/synth_client.py +372 -28
  129. synth_ai/rl/__init__.py +30 -0
  130. synth_ai/rl/contracts.py +32 -0
  131. synth_ai/rl/env_keys.py +137 -0
  132. synth_ai/rl/secrets.py +19 -0
  133. synth_ai/scripts/verify_rewards.py +100 -0
  134. synth_ai/task/__init__.py +10 -0
  135. synth_ai/task/contracts.py +120 -0
  136. synth_ai/task/health.py +28 -0
  137. synth_ai/task/validators.py +12 -0
  138. synth_ai/tracing_v3/hooks.py +3 -1
  139. synth_ai/tracing_v3/session_tracer.py +123 -2
  140. synth_ai/tracing_v3/turso/manager.py +218 -0
  141. synth_ai/tracing_v3/turso/models.py +53 -0
  142. synth_ai-0.2.4.dev9.dist-info/METADATA +91 -0
  143. {synth_ai-0.2.4.dev7.dist-info → synth_ai-0.2.4.dev9.dist-info}/RECORD +147 -30
  144. {synth_ai-0.2.4.dev7.dist-info → synth_ai-0.2.4.dev9.dist-info}/entry_points.txt +1 -0
  145. synth_ai/tui/__init__.py +0 -1
  146. synth_ai/tui/__main__.py +0 -13
  147. synth_ai/tui/cli/__init__.py +0 -1
  148. synth_ai/tui/cli/query_experiments.py +0 -164
  149. synth_ai/tui/cli/query_experiments_v3.py +0 -164
  150. synth_ai/tui/dashboard.py +0 -340
  151. synth_ai-0.2.4.dev7.dist-info/METADATA +0 -193
  152. {synth_ai-0.2.4.dev7.dist-info → synth_ai-0.2.4.dev9.dist-info}/WHEEL +0 -0
  153. {synth_ai-0.2.4.dev7.dist-info → synth_ai-0.2.4.dev9.dist-info}/licenses/LICENSE +0 -0
  154. {synth_ai-0.2.4.dev7.dist-info → synth_ai-0.2.4.dev9.dist-info}/top_level.txt +0 -0
@@ -30,6 +30,7 @@ import pandas as pd
30
30
  from sqlalchemy import select, text, update
31
31
  from sqlalchemy.exc import IntegrityError
32
32
  from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, create_async_engine
33
+ from sqlalchemy import event
33
34
  from sqlalchemy.orm import selectinload, sessionmaker
34
35
  from sqlalchemy.pool import NullPool
35
36
 
@@ -59,6 +60,12 @@ from .models import (
59
60
  from .models import (
60
61
  SessionTrace as DBSessionTrace,
61
62
  )
63
+ from .models import (
64
+ OutcomeReward as DBOutcomeReward,
65
+ )
66
+ from .models import (
67
+ EventReward as DBEventReward,
68
+ )
62
69
 
63
70
  logger = logging.getLogger(__name__)
64
71
 
@@ -125,6 +132,18 @@ class AsyncSQLTraceManager:
125
132
  connect_args=connect_args,
126
133
  echo=CONFIG.echo_sql,
127
134
  )
135
+ # Ensure PRAGMA foreign_keys=ON for every connection
136
+ try:
137
+ @event.listens_for(self.engine.sync_engine, "connect")
138
+ def _set_sqlite_pragma(dbapi_connection, connection_record): # type: ignore[no-redef]
139
+ try:
140
+ cursor = dbapi_connection.cursor()
141
+ cursor.execute("PRAGMA foreign_keys=ON")
142
+ cursor.close()
143
+ except Exception:
144
+ pass
145
+ except Exception:
146
+ pass
128
147
  else:
129
148
  connect_args = CONFIG.get_connect_args()
130
149
  engine_kwargs = CONFIG.get_engine_kwargs()
@@ -538,3 +557,202 @@ class AsyncSQLTraceManager:
538
557
  self.engine = None
539
558
  self.SessionLocal = None
540
559
  self._schema_ready = False
560
+
561
+ # -------------------------------
562
+ # Incremental insert helpers
563
+ # -------------------------------
564
+
565
+ async def ensure_session(self, session_id: str, *, created_at: datetime | None = None, metadata: dict[str, Any] | None = None):
566
+ """Ensure a DB session row exists for session_id."""
567
+ async with self.session() as sess:
568
+ result = await sess.execute(select(DBSessionTrace).where(DBSessionTrace.session_id == session_id))
569
+ existing = result.scalar_one_or_none()
570
+ if existing:
571
+ return
572
+ row = DBSessionTrace(
573
+ session_id=session_id,
574
+ created_at=created_at or datetime.utcnow(),
575
+ num_timesteps=0,
576
+ num_events=0,
577
+ num_messages=0,
578
+ session_metadata=metadata or {},
579
+ )
580
+ sess.add(row)
581
+ await sess.commit()
582
+
583
+ async def ensure_timestep(self, session_id: str, *, step_id: str, step_index: int, turn_number: int | None = None, started_at: datetime | None = None, completed_at: datetime | None = None, metadata: dict[str, Any] | None = None) -> int:
584
+ """Ensure a timestep row exists; return its DB id."""
585
+ async with self.session() as sess:
586
+ result = await sess.execute(
587
+ select(DBSessionTimestep).where(DBSessionTimestep.session_id == session_id, DBSessionTimestep.step_id == step_id)
588
+ )
589
+ row = result.scalar_one_or_none()
590
+ if row:
591
+ return row.id
592
+ row = DBSessionTimestep(
593
+ session_id=session_id,
594
+ step_id=step_id,
595
+ step_index=step_index,
596
+ turn_number=turn_number,
597
+ started_at=started_at or datetime.utcnow(),
598
+ completed_at=completed_at,
599
+ num_events=0,
600
+ num_messages=0,
601
+ step_metadata=metadata or {},
602
+ )
603
+ sess.add(row)
604
+ await sess.flush()
605
+ # increment session num_timesteps
606
+ await sess.execute(
607
+ update(DBSessionTrace)
608
+ .where(DBSessionTrace.session_id == session_id)
609
+ .values(num_timesteps=DBSessionTrace.num_timesteps + 1)
610
+ )
611
+ await sess.commit()
612
+ return row.id
613
+
614
+ async def insert_message_row(self, session_id: str, *, timestep_db_id: int | None, message_type: str, content: str, event_time: float | None = None, message_time: int | None = None, metadata: dict[str, Any] | None = None) -> int:
615
+ """Insert a message and return its id."""
616
+ async with self.session() as sess:
617
+ db_msg = DBMessage(
618
+ session_id=session_id,
619
+ timestep_id=timestep_db_id,
620
+ message_type=message_type,
621
+ content=content,
622
+ event_time=event_time,
623
+ message_time=message_time,
624
+ message_metadata=metadata or {},
625
+ )
626
+ sess.add(db_msg)
627
+ await sess.flush()
628
+ # increment session num_messages
629
+ await sess.execute(
630
+ update(DBSessionTrace)
631
+ .where(DBSessionTrace.session_id == session_id)
632
+ .values(num_messages=DBSessionTrace.num_messages + 1)
633
+ )
634
+ await sess.commit()
635
+ return db_msg.id
636
+
637
+ async def insert_event_row(self, session_id: str, *, timestep_db_id: int | None, event: EnvironmentEvent | LMCAISEvent | RuntimeEvent, metadata_override: dict[str, Any] | None = None) -> int:
638
+ """Insert an event and return its id."""
639
+ def to_cents(cost: float | None) -> int | None:
640
+ return int(cost * 100) if cost is not None else None
641
+
642
+ event_data: dict[str, Any] = {
643
+ "session_id": session_id,
644
+ "timestep_id": timestep_db_id,
645
+ "system_instance_id": event.system_instance_id,
646
+ "event_time": event.time_record.event_time,
647
+ "message_time": event.time_record.message_time,
648
+ "event_metadata_json": metadata_override or event.metadata or {},
649
+ "event_extra_metadata": getattr(event, "event_metadata", None),
650
+ }
651
+ if isinstance(event, LMCAISEvent):
652
+ call_records_data = None
653
+ if getattr(event, "call_records", None):
654
+ from dataclasses import asdict
655
+
656
+ call_records_data = [asdict(record) for record in event.call_records]
657
+ event_data.update({
658
+ "event_type": "cais",
659
+ "model_name": event.model_name,
660
+ "provider": event.provider,
661
+ "input_tokens": event.input_tokens,
662
+ "output_tokens": event.output_tokens,
663
+ "total_tokens": event.total_tokens,
664
+ "cost_usd": to_cents(event.cost_usd),
665
+ "latency_ms": event.latency_ms,
666
+ "span_id": event.span_id,
667
+ "trace_id": event.trace_id,
668
+ "system_state_before": event.system_state_before,
669
+ "system_state_after": event.system_state_after,
670
+ "call_records": call_records_data,
671
+ })
672
+ elif isinstance(event, EnvironmentEvent):
673
+ event_data.update({
674
+ "event_type": "environment",
675
+ "reward": event.reward,
676
+ "terminated": event.terminated,
677
+ "truncated": event.truncated,
678
+ "system_state_before": event.system_state_before,
679
+ "system_state_after": event.system_state_after,
680
+ })
681
+ elif isinstance(event, RuntimeEvent):
682
+ event_data.update({
683
+ "event_type": "runtime",
684
+ "event_metadata_json": {**(event.metadata or {}), "actions": event.actions},
685
+ })
686
+ else:
687
+ event_data["event_type"] = event.__class__.__name__.lower()
688
+
689
+ async with self.session() as sess:
690
+ db_event = DBEvent(**event_data)
691
+ sess.add(db_event)
692
+ await sess.flush()
693
+ # increment session num_events
694
+ await sess.execute(
695
+ update(DBSessionTrace)
696
+ .where(DBSessionTrace.session_id == session_id)
697
+ .values(num_events=DBSessionTrace.num_events + 1)
698
+ )
699
+ await sess.commit()
700
+ return db_event.id
701
+
702
+ # -------------------------------
703
+ # Reward helpers
704
+ # -------------------------------
705
+
706
+ async def insert_outcome_reward(self, session_id: str, *, total_reward: int, achievements_count: int, total_steps: int) -> int:
707
+ async with self.session() as sess:
708
+ row = DBOutcomeReward(
709
+ session_id=session_id,
710
+ total_reward=total_reward,
711
+ achievements_count=achievements_count,
712
+ total_steps=total_steps,
713
+ )
714
+ sess.add(row)
715
+ await sess.flush()
716
+ await sess.commit()
717
+ return row.id
718
+
719
+ async def insert_event_reward(self, session_id: str, *, event_id: int, message_id: int | None = None, turn_number: int | None = None, reward_value: float = 0.0, reward_type: str | None = None, key: str | None = None, annotation: dict[str, Any] | None = None, source: str | None = None) -> int:
720
+ async with self.session() as sess:
721
+ row = DBEventReward(
722
+ event_id=event_id,
723
+ session_id=session_id,
724
+ message_id=message_id,
725
+ turn_number=turn_number,
726
+ reward_value=reward_value,
727
+ reward_type=reward_type,
728
+ key=key,
729
+ annotation=annotation or {},
730
+ source=source,
731
+ )
732
+ sess.add(row)
733
+ await sess.flush()
734
+ await sess.commit()
735
+ return row.id
736
+
737
+ async def get_outcome_rewards(self) -> list[dict[str, Any]]:
738
+ async with self.session() as sess:
739
+ result = await sess.execute(select(DBOutcomeReward))
740
+ rows = result.scalars().all()
741
+ return [
742
+ {
743
+ "id": r.id,
744
+ "session_id": r.session_id,
745
+ "total_reward": r.total_reward,
746
+ "achievements_count": r.achievements_count,
747
+ "total_steps": r.total_steps,
748
+ "created_at": r.created_at,
749
+ }
750
+ for r in rows
751
+ ]
752
+
753
+ async def get_outcome_rewards_by_min_reward(self, min_reward: int) -> list[str]:
754
+ async with self.session() as sess:
755
+ result = await sess.execute(
756
+ select(DBOutcomeReward.session_id).where(DBOutcomeReward.total_reward >= min_reward)
757
+ )
758
+ return [row[0] for row in result.all()]
@@ -408,3 +408,56 @@ analytics_views = {
408
408
  GROUP BY e.experiment_id
409
409
  """,
410
410
  }
411
+
412
+
413
+ # Reward persistence tables
414
+
415
+
416
+ class OutcomeReward(Base):
417
+ """Episode-level rewards/outcomes per session.
418
+
419
+ Stores per-episode summary including total_reward (e.g., unique achievements),
420
+ achievements_count, and total_steps. Used for filtering episodes by outcome.
421
+ """
422
+
423
+ __tablename__ = "outcome_rewards"
424
+
425
+ id = Column(Integer, primary_key=True, autoincrement=True)
426
+ session_id = Column(String, ForeignKey("session_traces.session_id"), nullable=False)
427
+ total_reward = Column(Integer, nullable=False)
428
+ achievements_count = Column(Integer, nullable=False, default=0)
429
+ total_steps = Column(Integer, nullable=False, default=0)
430
+ created_at = Column(DateTime, default=func.current_timestamp(), nullable=False)
431
+
432
+ __table_args__ = (
433
+ Index("idx_outcome_rewards_session", "session_id"),
434
+ Index("idx_outcome_rewards_total", "total_reward"),
435
+ )
436
+
437
+
438
+ class EventReward(Base):
439
+ """First-class event-level rewards with annotations.
440
+
441
+ Links to an event and session. `message_id` is optional.
442
+ """
443
+
444
+ __tablename__ = "event_rewards"
445
+
446
+ id = Column(Integer, primary_key=True, autoincrement=True)
447
+ event_id = Column(Integer, ForeignKey("events.id"), nullable=False)
448
+ session_id = Column(String, ForeignKey("session_traces.session_id"), nullable=False)
449
+ message_id = Column(Integer, ForeignKey("messages.id"), nullable=True)
450
+ turn_number = Column(Integer, nullable=True)
451
+ reward_value = Column(Float, nullable=False, default=0.0)
452
+ reward_type = Column(String, nullable=True) # shaped | sparse | achievement | penalty | evaluator | human
453
+ key = Column(String, nullable=True) # e.g., achievement name
454
+ annotation = Column(JSONText) # free-form JSON
455
+ source = Column(String, nullable=True) # environment | runner | evaluator | human
456
+ created_at = Column(DateTime, default=func.current_timestamp(), nullable=False)
457
+
458
+ __table_args__ = (
459
+ Index("idx_event_rewards_session", "session_id"),
460
+ Index("idx_event_rewards_event", "event_id"),
461
+ Index("idx_event_rewards_type", "reward_type"),
462
+ Index("idx_event_rewards_key", "key"),
463
+ )
@@ -0,0 +1,91 @@
1
+ Metadata-Version: 2.4
2
+ Name: synth-ai
3
+ Version: 0.2.4.dev9
4
+ Summary: Software for aiding the best and multiplying the will - Core AI functionality and tracing
5
+ Author-email: Synth AI <josh@usesynth.ai>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/synth-laboratories/synth-ai
8
+ Project-URL: Repository, https://github.com/synth-laboratories/synth-ai
9
+ Project-URL: Issues, https://github.com/synth-laboratories/synth-ai/issues
10
+ Requires-Python: >=3.11
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: pydantic>=2.0.0
14
+ Requires-Dist: python-dotenv>=1.0.1
15
+ Requires-Dist: requests>=2.32.3
16
+ Requires-Dist: urllib3>=2.3.0
17
+ Requires-Dist: tqdm>=4.66.4
18
+ Requires-Dist: jsonschema>=4.23.0
19
+ Requires-Dist: backoff>=2.0.0
20
+ Requires-Dist: typing_extensions>=4.0.0
21
+ Requires-Dist: openai>=1.99.0
22
+ Requires-Dist: anthropic>=0.42.0
23
+ Requires-Dist: langfuse<3.0.0,>=2.53.9
24
+ Requires-Dist: opentelemetry-api<1.27.0,>=1.26.0
25
+ Requires-Dist: opentelemetry-sdk<1.27.0,>=1.26.0
26
+ Requires-Dist: diskcache>=5.6.3
27
+ Requires-Dist: groq>=0.30.0
28
+ Requires-Dist: google-genai>=1.26.0
29
+ Requires-Dist: together>=1.5.21
30
+ Requires-Dist: mistralai>=1.9.2
31
+ Requires-Dist: fastapi>=0.115.12
32
+ Requires-Dist: uvicorn>=0.34.2
33
+ Requires-Dist: numpy>=2.2.3
34
+ Requires-Dist: networkx>=3.4.2
35
+ Requires-Dist: redis>=6.2.0
36
+ Requires-Dist: duckdb>=1.0.0
37
+ Requires-Dist: pandas>=2.2.3
38
+ Requires-Dist: ty>=0.0.1a5
39
+ Requires-Dist: toml>=0.10.2
40
+ Requires-Dist: sqlalchemy>=2.0.42
41
+ Requires-Dist: aiosqlite>=0.21.0
42
+ Requires-Dist: greenlet>=3.2.3
43
+ Requires-Dist: libsql>=0.1.8
44
+ Requires-Dist: pynacl>=1.5.0
45
+ Requires-Dist: google-api-core>=2.25.1
46
+ Requires-Dist: google-generativeai>=0.8.5
47
+ Requires-Dist: crafter>=1.8.3
48
+ Requires-Dist: click>=8.1.0
49
+ Requires-Dist: textual>=1.1.0
50
+ Requires-Dist: openai-harmony>=0.0.1
51
+ Requires-Dist: asyncpg>=0.30.0
52
+ Requires-Dist: aiohttp>=3.8.0
53
+ Requires-Dist: datasets>=4.0.0
54
+ Requires-Dist: transformers>=4.56.1
55
+ Requires-Dist: modal>=1.1.4
56
+ Provides-Extra: dev
57
+ Requires-Dist: build>=1.2.2.post1; extra == "dev"
58
+ Requires-Dist: twine>=4.0.0; extra == "dev"
59
+ Requires-Dist: keyring>=24.0.0; extra == "dev"
60
+ Requires-Dist: pytest>=8.3.3; extra == "dev"
61
+ Requires-Dist: pytest-asyncio>=0.24.0; extra == "dev"
62
+ Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
63
+ Requires-Dist: pyright>=1.1.350; extra == "dev"
64
+ Requires-Dist: coverage[toml]>=7.3.0; extra == "dev"
65
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
66
+ Provides-Extra: research
67
+ Requires-Dist: crafter>=1.8.3; extra == "research"
68
+ Requires-Dist: datasets>=4.0.0; extra == "research"
69
+ Provides-Extra: all
70
+ Requires-Dist: crafter>=1.8.3; extra == "all"
71
+ Requires-Dist: datasets>=4.0.0; extra == "all"
72
+ Dynamic: license-file
73
+
74
+ # Synth-AI
75
+
76
+ [![Python](https://img.shields.io/badge/python-3.11+-blue)](https://www.python.org/)
77
+ [![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)
78
+ [![PyPI](https://img.shields.io/badge/PyPI-0.2.4.dev8-orange)](https://pypi.org/project/synth-ai/)
79
+ ![Coverage](https://img.shields.io/badge/coverage-0.0%25-red)
80
+ ![Tests](https://img.shields.io/badge/tests-17%2F17%20passing-brightgreen)
81
+
82
+ Docs: [Synth‑AI Documentation](https://docs.usesynth.ai/synth-ai/introduction)
83
+
84
+ Fast and effective reinforcement learning for agents, via an API
85
+
86
+ # Highlights
87
+
88
+ - Easily scale gpu topologies - train on 3 a10gs or 8 H100s (multi-node available upon request)
89
+ - Requires only a thin fastapi wrapper to integrate with existing agent software.
90
+ - Supports the best OSS models like Qwen3. (gpt-oss available upon request, GA soon)
91
+ - Own your trained models