synth-ai 0.2.4.dev7__py3-none-any.whl → 0.2.4.dev8__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.
Files changed (50) hide show
  1. synth_ai/__init__.py +1 -1
  2. synth_ai/cli/balance.py +3 -15
  3. synth_ai/config/base_url.py +47 -0
  4. synth_ai/http.py +102 -0
  5. synth_ai/inference/__init__.py +7 -0
  6. synth_ai/inference/client.py +20 -0
  7. synth_ai/jobs/client.py +246 -0
  8. synth_ai/learning/__init__.py +24 -0
  9. synth_ai/learning/client.py +149 -0
  10. synth_ai/learning/config.py +43 -0
  11. synth_ai/learning/constants.py +29 -0
  12. synth_ai/learning/ft_client.py +59 -0
  13. synth_ai/learning/health.py +43 -0
  14. synth_ai/learning/jobs.py +205 -0
  15. synth_ai/learning/rl_client.py +256 -0
  16. synth_ai/learning/sse.py +58 -0
  17. synth_ai/learning/validators.py +48 -0
  18. synth_ai/lm/core/main_v3.py +13 -0
  19. synth_ai/lm/core/synth_models.py +48 -0
  20. synth_ai/lm/core/vendor_clients.py +9 -6
  21. synth_ai/lm/vendors/core/openai_api.py +31 -3
  22. synth_ai/lm/vendors/openai_standard.py +45 -14
  23. synth_ai/lm/vendors/supported/custom_endpoint.py +12 -2
  24. synth_ai/lm/vendors/synth_client.py +372 -28
  25. synth_ai/rl/__init__.py +30 -0
  26. synth_ai/rl/contracts.py +32 -0
  27. synth_ai/rl/env_keys.py +137 -0
  28. synth_ai/rl/secrets.py +19 -0
  29. synth_ai/scripts/verify_rewards.py +100 -0
  30. synth_ai/task/__init__.py +10 -0
  31. synth_ai/task/contracts.py +120 -0
  32. synth_ai/task/health.py +28 -0
  33. synth_ai/task/validators.py +12 -0
  34. synth_ai/tracing_v3/hooks.py +3 -1
  35. synth_ai/tracing_v3/session_tracer.py +123 -2
  36. synth_ai/tracing_v3/turso/manager.py +218 -0
  37. synth_ai/tracing_v3/turso/models.py +53 -0
  38. synth_ai-0.2.4.dev8.dist-info/METADATA +635 -0
  39. {synth_ai-0.2.4.dev7.dist-info → synth_ai-0.2.4.dev8.dist-info}/RECORD +43 -25
  40. synth_ai/tui/__init__.py +0 -1
  41. synth_ai/tui/__main__.py +0 -13
  42. synth_ai/tui/cli/__init__.py +0 -1
  43. synth_ai/tui/cli/query_experiments.py +0 -164
  44. synth_ai/tui/cli/query_experiments_v3.py +0 -164
  45. synth_ai/tui/dashboard.py +0 -340
  46. synth_ai-0.2.4.dev7.dist-info/METADATA +0 -193
  47. {synth_ai-0.2.4.dev7.dist-info → synth_ai-0.2.4.dev8.dist-info}/WHEEL +0 -0
  48. {synth_ai-0.2.4.dev7.dist-info → synth_ai-0.2.4.dev8.dist-info}/entry_points.txt +0 -0
  49. {synth_ai-0.2.4.dev7.dist-info → synth_ai-0.2.4.dev8.dist-info}/licenses/LICENSE +0 -0
  50. {synth_ai-0.2.4.dev7.dist-info → synth_ai-0.2.4.dev8.dist-info}/top_level.txt +0 -0
synth_ai/tui/dashboard.py DELETED
@@ -1,340 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Interactive TUI Dashboard for Synth AI experiments.
4
-
5
- Launch with: python -m synth_ai.tui.dashboard
6
- """
7
-
8
- import logging
9
- from datetime import datetime
10
- from urllib.parse import urlparse
11
-
12
- from textual import on
13
- from textual.app import App, ComposeResult
14
- from textual.binding import Binding
15
- from textual.containers import Container
16
- from textual.reactive import reactive
17
- from textual.timer import Timer
18
- from textual.widgets import (
19
- DataTable,
20
- Footer,
21
- Header,
22
- Static,
23
- )
24
-
25
- from ..tracing_v3.turso.manager import AsyncSQLTraceManager
26
-
27
-
28
- class ExperimentRow:
29
- """Data structure for experiment display."""
30
-
31
- def __init__(
32
- self,
33
- exp_id: str,
34
- name: str,
35
- description: str,
36
- created_at: datetime,
37
- sessions: int,
38
- events: int,
39
- messages: int,
40
- cost: float,
41
- tokens: int,
42
- ):
43
- self.exp_id = exp_id
44
- self.name = name or "Unnamed"
45
- self.description = description or ""
46
- self.created_at = created_at
47
- self.sessions = sessions
48
- self.events = events
49
- self.messages = messages
50
- self.cost = cost
51
- self.tokens = tokens
52
-
53
- def to_row(self) -> list[str]:
54
- """Convert to table row format."""
55
- return [
56
- self.exp_id[:8], # Shortened ID
57
- self.name[:20], # Truncated name
58
- str(self.sessions),
59
- str(self.events),
60
- str(self.messages),
61
- f"${self.cost:.4f}",
62
- f"{self.tokens:,}",
63
- self.created_at.strftime("%H:%M"),
64
- ]
65
-
66
-
67
- class ExperimentTable(DataTable):
68
- """Custom DataTable for experiments with refresh capability."""
69
-
70
- def __init__(self, **kwargs):
71
- super().__init__(**kwargs)
72
- self.experiments: list[ExperimentRow] = []
73
- self.selected_exp_id: str | None = None
74
-
75
- def setup_table(self):
76
- """Initialize table columns."""
77
- self.add_columns("ID", "Name", "Sessions", "Events", "Messages", "Cost", "Tokens", "Time")
78
-
79
- async def refresh_data(self, db_manager: AsyncSQLTraceManager):
80
- """Refresh experiment data from database."""
81
- try:
82
- # Get experiment list with stats using raw query
83
- df = await db_manager.query_traces("""
84
- SELECT
85
- e.experiment_id,
86
- e.name,
87
- e.description,
88
- e.created_at,
89
- COUNT(DISTINCT st.session_id) as num_sessions,
90
- COUNT(DISTINCT ev.id) as num_events,
91
- COUNT(DISTINCT m.id) as num_messages,
92
- SUM(CASE WHEN ev.event_type = 'cais' THEN ev.cost_usd ELSE 0 END) / 100.0 as total_cost,
93
- SUM(CASE WHEN ev.event_type = 'cais' THEN ev.total_tokens ELSE 0 END) as total_tokens
94
- FROM experiments e
95
- LEFT JOIN session_traces st ON e.experiment_id = st.experiment_id
96
- LEFT JOIN events ev ON st.session_id = ev.session_id
97
- LEFT JOIN messages m ON st.session_id = m.session_id
98
- GROUP BY e.experiment_id, e.name, e.description, e.created_at
99
- ORDER BY e.created_at DESC
100
- """)
101
-
102
- self.experiments.clear()
103
- self.clear()
104
-
105
- if not df.empty:
106
- for _, row in df.iterrows():
107
- exp_row = ExperimentRow(
108
- exp_id=row["experiment_id"],
109
- name=row["name"],
110
- description=row["description"],
111
- created_at=row["created_at"],
112
- sessions=int(row["num_sessions"] or 0),
113
- events=int(row["num_events"] or 0),
114
- messages=int(row["num_messages"] or 0),
115
- cost=float(row["total_cost"] or 0.0),
116
- tokens=int(row["total_tokens"] or 0),
117
- )
118
- self.experiments.append(exp_row)
119
- self.add_row(*exp_row.to_row(), key=exp_row.exp_id)
120
-
121
- except Exception as e:
122
- logging.error(f"Failed to refresh experiments: {e}")
123
-
124
- def get_selected_experiment(self) -> ExperimentRow | None:
125
- """Get currently selected experiment."""
126
- if self.cursor_row >= 0 and self.cursor_row < len(self.experiments):
127
- return self.experiments[self.cursor_row]
128
- return None
129
-
130
-
131
- class ExperimentDetail(Static):
132
- """Detailed view of selected experiment."""
133
-
134
- def __init__(self, **kwargs):
135
- super().__init__(**kwargs)
136
- self.current_experiment: ExperimentRow | None = None
137
-
138
- def update_experiment(self, experiment: ExperimentRow | None):
139
- """Update the displayed experiment details."""
140
- self.current_experiment = experiment
141
- if experiment:
142
- details = f"""
143
- 🔬 **{experiment.name}**
144
- ID: {experiment.exp_id}
145
- Description: {experiment.description or "No description"}
146
-
147
- 📊 **Statistics**
148
- Sessions: {experiment.sessions}
149
- Events: {experiment.events}
150
- Messages: {experiment.messages}
151
- Cost: ${experiment.cost:.4f}
152
- Tokens: {experiment.tokens:,}
153
-
154
- 🕒 **Created**: {experiment.created_at.strftime("%Y-%m-%d %H:%M:%S")}
155
- """.strip()
156
- else:
157
- details = "Select an experiment to view details"
158
-
159
- self.update(details)
160
-
161
-
162
- class DatabaseStatus(Static):
163
- """Display database connection status."""
164
-
165
- connection_status = reactive("🔴 Disconnected")
166
-
167
- def __init__(self, **kwargs):
168
- super().__init__(**kwargs)
169
-
170
- def render(self) -> str:
171
- return f"Database: {self.connection_status}"
172
-
173
- def set_connected(self, url: str):
174
- parsed = urlparse(url)
175
- host_info = f"{parsed.hostname}:{parsed.port}" if parsed.port else str(parsed.hostname)
176
- self.connection_status = f"🟢 Connected ({host_info})"
177
-
178
- def set_disconnected(self, error: str = ""):
179
- error_text = f" - {error}" if error else ""
180
- self.connection_status = f"🔴 Disconnected{error_text}"
181
-
182
-
183
- class SynthDashboard(App):
184
- """Main Synth AI TUI Dashboard application."""
185
-
186
- CSS = """
187
- Screen {
188
- layout: grid;
189
- grid-size: 2 3;
190
- grid-gutter: 1;
191
- }
192
-
193
- #header {
194
- column-span: 2;
195
- height: 3;
196
- }
197
-
198
- #experiments-table {
199
- row-span: 2;
200
- }
201
-
202
- #experiment-detail {
203
- height: 1fr;
204
- }
205
-
206
- #status-bar {
207
- column-span: 2;
208
- height: 3;
209
- }
210
-
211
- ExperimentTable {
212
- height: 100%;
213
- }
214
-
215
- ExperimentDetail {
216
- border: solid $primary;
217
- padding: 1;
218
- height: 100%;
219
- }
220
-
221
- DatabaseStatus {
222
- height: 1;
223
- padding: 0 1;
224
- }
225
- """
226
-
227
- BINDINGS = [
228
- Binding("q", "quit", "Quit"),
229
- Binding("r", "refresh", "Refresh"),
230
- Binding("d", "toggle_debug", "Debug"),
231
- ("ctrl+c", "quit", "Quit"),
232
- ]
233
-
234
- def __init__(self, db_url: str = "sqlite+aiosqlite:///./synth_ai.db/dbs/default/data"):
235
- super().__init__()
236
- self.db_url = db_url
237
- self.db_manager: AsyncSQLTraceManager | None = None
238
- self.refresh_timer: Timer | None = None
239
-
240
- def compose(self) -> ComposeResult:
241
- """Create the UI layout."""
242
- yield Header(show_clock=True)
243
-
244
- with Container(id="experiments-table"):
245
- yield Static("🧪 Experiments", classes="section-title")
246
- yield ExperimentTable(id="experiments")
247
-
248
- with Container(id="experiment-detail"):
249
- yield Static("📋 Details", classes="section-title")
250
- yield ExperimentDetail(id="detail")
251
-
252
- with Container(id="status-bar"):
253
- yield DatabaseStatus(id="db-status")
254
- yield Footer()
255
-
256
- async def on_mount(self) -> None:
257
- """Initialize the app when mounted."""
258
- # Setup database connection
259
- try:
260
- self.db_manager = AsyncSQLTraceManager(self.db_url)
261
- await self.db_manager.initialize()
262
-
263
- db_status = self.query_one("#db-status", DatabaseStatus)
264
- db_status.set_connected(self.db_url)
265
-
266
- except Exception as e:
267
- logging.error(f"Failed to connect to database: {e}")
268
- db_status = self.query_one("#db-status", DatabaseStatus)
269
- db_status.set_disconnected(str(e))
270
-
271
- # Setup experiment table
272
- exp_table = self.query_one("#experiments", ExperimentTable)
273
- exp_table.setup_table()
274
-
275
- # Initial data load
276
- await self.action_refresh()
277
-
278
- # Start auto-refresh timer (every 5 seconds)
279
- self.refresh_timer = self.set_interval(5.0, self._auto_refresh)
280
-
281
- async def _auto_refresh(self) -> None:
282
- """Auto-refresh data periodically."""
283
- if self.db_manager:
284
- exp_table = self.query_one("#experiments", ExperimentTable)
285
- await exp_table.refresh_data(self.db_manager)
286
-
287
- async def action_refresh(self) -> None:
288
- """Manual refresh action."""
289
- if self.db_manager:
290
- exp_table = self.query_one("#experiments", ExperimentTable)
291
- await exp_table.refresh_data(self.db_manager)
292
-
293
- async def action_quit(self) -> None:
294
- """Quit the application."""
295
- if self.refresh_timer:
296
- self.refresh_timer.stop()
297
- if self.db_manager:
298
- await self.db_manager.close()
299
- self.exit()
300
-
301
- def action_toggle_debug(self) -> None:
302
- """Toggle debug mode."""
303
- # Could add debug panel or logging level toggle
304
- pass
305
-
306
- @on(DataTable.RowHighlighted, "#experiments")
307
- def on_experiment_selected(self, event: DataTable.RowHighlighted) -> None:
308
- """Handle experiment selection."""
309
- exp_table = self.query_one("#experiments", ExperimentTable)
310
- selected_exp = exp_table.get_selected_experiment()
311
-
312
- detail_panel = self.query_one("#detail", ExperimentDetail)
313
- detail_panel.update_experiment(selected_exp)
314
-
315
-
316
- def main():
317
- """Main entry point for the dashboard."""
318
- import argparse
319
-
320
- parser = argparse.ArgumentParser(description="Synth AI Interactive Dashboard")
321
- parser.add_argument(
322
- "-u",
323
- "--url",
324
- default="sqlite+libsql://http://127.0.0.1:8080",
325
- help="Database URL (default: sqlite+libsql://http://127.0.0.1:8080)",
326
- )
327
- parser.add_argument("--debug", action="store_true", help="Enable debug logging")
328
-
329
- args = parser.parse_args()
330
-
331
- if args.debug:
332
- logging.basicConfig(level=logging.DEBUG)
333
-
334
- # Run the dashboard
335
- app = SynthDashboard(db_url=args.url)
336
- app.run()
337
-
338
-
339
- if __name__ == "__main__":
340
- main()
@@ -1,193 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: synth-ai
3
- Version: 0.2.4.dev7
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: google-api-core>=2.25.1
45
- Requires-Dist: google-generativeai>=0.8.5
46
- Requires-Dist: crafter>=1.8.3
47
- Requires-Dist: click>=8.1.0
48
- Requires-Dist: textual>=1.1.0
49
- Requires-Dist: openai-harmony>=0.0.1
50
- Requires-Dist: asyncpg>=0.30.0
51
- Requires-Dist: aiohttp>=3.8.0
52
- Requires-Dist: datasets>=4.0.0
53
- Provides-Extra: dev
54
- Requires-Dist: build>=1.2.2.post1; extra == "dev"
55
- Requires-Dist: twine>=4.0.0; extra == "dev"
56
- Requires-Dist: keyring>=24.0.0; extra == "dev"
57
- Requires-Dist: pytest>=8.3.3; extra == "dev"
58
- Requires-Dist: pytest-asyncio>=0.24.0; extra == "dev"
59
- Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
60
- Requires-Dist: pyright>=1.1.350; extra == "dev"
61
- Requires-Dist: coverage[toml]>=7.3.0; extra == "dev"
62
- Requires-Dist: ruff>=0.1.0; extra == "dev"
63
- Provides-Extra: research
64
- Requires-Dist: crafter>=1.8.3; extra == "research"
65
- Requires-Dist: datasets>=4.0.0; extra == "research"
66
- Provides-Extra: all
67
- Requires-Dist: crafter>=1.8.3; extra == "all"
68
- Requires-Dist: datasets>=4.0.0; extra == "all"
69
- Dynamic: license-file
70
-
71
- # Synth AI
72
-
73
- Modern Compound AI System Development
74
-
75
- **Comprehensive AI Framework for Language Models, Environments, and Observability**
76
-
77
- [![Python](https://img.shields.io/badge/python-3.11+-blue)](https://www.python.org/)
78
- [![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)
79
- [![PyPI](https://img.shields.io/badge/PyPI-0.2.3.dev0-orange)](https://pypi.org/project/synth-ai/)
80
- ![Coverage](https://img.shields.io/badge/coverage-0.0%25-red)
81
- ![Tests](https://img.shields.io/badge/tests-17%2F17%20passing-brightgreen)
82
-
83
- A unified framework combining language model capabilities, synthetic environments, and comprehensive tracing for building and evaluating AI agents.
84
-
85
- ## 🚀 Quick Start
86
-
87
- ### Installation
88
-
89
- ```bash
90
- # Basic installation
91
- pip install synth-ai
92
-
93
- # With research environments (includes game environments)
94
- pip install synth-ai[research]
95
-
96
- # Full installation with all providers
97
- pip install synth-ai[all]
98
- ```
99
-
100
- ### Spinning Up
101
-
102
- Start the Synth AI service daemon (includes sqld database + environment service):
103
-
104
- ```bash
105
- # Start both database daemon (port 8080) and environment service (port 8901)
106
- uvx synth-ai serve
107
- ```
108
-
109
- #### Service Command Options
110
-
111
- ```bash
112
- uvx synth-ai serve [OPTIONS]
113
- ```
114
-
115
- **Available Options:**
116
- - `--db-file` - Database file path (default: "synth_ai.db")
117
- - `--sqld-port` - Port for sqld HTTP interface (default: 8080)
118
- - `--env-port` - Port for environment service (default: 8901)
119
- - `--no-sqld` - Skip starting sqld database daemon
120
- - `--no-env` - Skip starting environment service
121
-
122
- **Examples:**
123
- ```bash
124
- # Start with custom ports
125
- uvx synth-ai serve --sqld-port 8081 --env-port 8902
126
-
127
- # Start only the environment service
128
- uvx synth-ai serve --no-sqld
129
-
130
- # Start only the database service
131
- uvx synth-ai serve --no-env
132
- ```
133
-
134
- #### What the Serve Command Provides
135
-
136
- **sqld Database Service (port 8080)**
137
- - Local SQLite-compatible database server with HTTP API
138
- - Automatically downloads and installs sqld binary if needed
139
- - Provides persistent storage for agent interactions and traces
140
-
141
- **Environment Service (port 8901)**
142
- - FastAPI service for managing AI environments and tasks
143
- - Built-in environments: Crafter, Sokoban, MiniGrid, TicTacToe, Verilog, NetHack, Enron
144
- - RESTful API for environment initialization, stepping, and termination
145
- - Dynamic environment registry for custom environments
146
-
147
- In another terminal, run your first example:
148
-
149
- ```bash
150
- # Run a Crafter agent demo with Gemini
151
- ./examples/run_crafter_demo.sh
152
- ```
153
-
154
- This will:
155
- - Start the sqld database daemon with HTTP API on port 8080
156
- - Launch the environment service API on port 8901
157
- - Run a reactive agent in the Crafter environment using Gemini 1.5 Flash
158
-
159
- #### Demos (Eval + Finetuning)
160
-
161
- You can run interactive demos from the repo without remembering exact commands:
162
-
163
- ```bash
164
- # Lists all available demos under examples/, then prompts you to choose
165
- uvx synth-ai demo
166
- ```
167
-
168
- Today this includes:
169
- - Eval demo: `examples/evals/run_demo.sh`
170
- - Prompts for models, episodes, etc.
171
- - Runs Crafter rollouts with v3 tracing, then analyzes and filters traces
172
- - Writes a JSONL like `ft_data/evals_filtered.jsonl` for downstream use
173
- - Finetuning demo: `examples/finetuning/synth_qwen/run_demo.sh`
174
- - Guides you through: rollouts → filter v3 traces → prepare SFT JSONL
175
- - Pair with `uvpm examples.finetuning.synth_qwen.sft_kickoff` to start an SFT job when ready
176
-
177
- Notes:
178
- - Ensure the service is running (`uvx synth-ai serve`) so v3 traces are recorded locally.
179
- - Set API configuration for finetuning:
180
- - `export LEARNING_V2_BASE_URL="http://localhost:8000/api"` (or your proxy)
181
- - `export SYNTH_API_KEY="sk_live_..."`
182
- - v3 trace data is stored under `traces/v3/synth_ai.db/` by default. Inspect with `uvx synth-ai traces`.
183
- - LM tracing: all model calls (prompts, outputs, tool calls, token usage, latency, cost) are automatically captured via v3 tracing and stored locally; inspect with `uvx synth-ai traces`.
184
-
185
- ### One-Command Demos
186
-
187
- Quickly browse and launch interactive demos under `examples/`:
188
-
189
- ```bash
190
- uvx synth-ai demo
191
- ```
192
-
193
- This lists all `run_demo.sh` scripts found in the repo (e.g., eval comparisons, finetuning flows) and lets you pick one to run.