tracellm-cli 0.1.0__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.
tracellm/startup.py ADDED
@@ -0,0 +1,121 @@
1
+ import os
2
+ import subprocess
3
+ import sys
4
+ import time
5
+ from pathlib import Path
6
+
7
+ import httpx
8
+ from dotenv import load_dotenv
9
+ from rich.panel import Panel
10
+ from rich.progress import BarColumn, Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
11
+ from rich.table import Table
12
+
13
+ from tracellm.banner import render_banner
14
+ from tracellm.mascot import MascotState, message
15
+ from tracellm.utils import console
16
+
17
+ load_dotenv()
18
+
19
+
20
+ def _check_mongodb() -> bool:
21
+ try:
22
+ import pymongo
23
+ mongo_url = os.getenv("MONGO_URL")
24
+ if not mongo_url:
25
+ console.print("[yellow]MONGO_URL not set — traces won't be persisted[/yellow]")
26
+ return False
27
+ client = pymongo.MongoClient(mongo_url, serverSelectionTimeoutMS=3000)
28
+ client.admin.command("ping")
29
+ client.close()
30
+ return True
31
+ except Exception:
32
+ console.print("[yellow]MongoDB not reachable — traces won't be persisted[/yellow]")
33
+ return False
34
+
35
+
36
+ def _start_fastapi(port: int) -> subprocess.Popen:
37
+ backend_dir = Path(__file__).resolve().parent.parent
38
+ env = os.environ.copy()
39
+ env["PYTHONPATH"] = str(backend_dir)
40
+
41
+ process = subprocess.Popen(
42
+ [sys.executable, "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", str(port), "--reload"],
43
+ cwd=backend_dir,
44
+ env=env,
45
+ stdout=subprocess.PIPE,
46
+ stderr=subprocess.STDOUT,
47
+ )
48
+ return process
49
+
50
+
51
+ def _health_check(port: int, timeout: int = 15) -> bool:
52
+ start = time.time()
53
+ with console.status("[bold white]Waiting for API server...[/bold white]"):
54
+ while time.time() - start < timeout:
55
+ try:
56
+ response = httpx.get(f"http://127.0.0.1:{port}/", timeout=3)
57
+ if response.status_code == 200:
58
+ return True
59
+ except (httpx.ConnectError, httpx.TimeoutException):
60
+ pass
61
+ time.sleep(0.5)
62
+ return False
63
+
64
+
65
+ def _render_status(mongodb_ok: bool, api_ok: bool, port: int, dashboard_port: int, launch_dashboard: bool) -> None:
66
+ table = Table.grid(padding=(0, 2))
67
+ table.add_column()
68
+ table.add_column()
69
+
70
+ table.add_row(
71
+ "API Server",
72
+ f"[green]●[/green] http://127.0.0.1:{port}" if api_ok else "[red]●[/red] Failed",
73
+ )
74
+ table.add_row(
75
+ "MongoDB",
76
+ "[green]● Connected[/green]" if mongodb_ok else "[yellow]● Skipped[/yellow]",
77
+ )
78
+ table.add_row(
79
+ "Dashboard",
80
+ f"[green]● http://localhost:{dashboard_port}[/green]" if launch_dashboard else "[dim]● Not launched[/dim]",
81
+ )
82
+ table.add_row(
83
+ "WebSocket",
84
+ f"[dim]ws://127.0.0.1:{port}/ws[/dim]",
85
+ )
86
+
87
+ console.print()
88
+ console.print(
89
+ Panel.fit(table, title="TraceLLM Stack", border_style="bright_black", padding=(1, 3))
90
+ )
91
+ console.print()
92
+
93
+
94
+ def run_start(port: int = 8000, dashboard_port: int = 3000, launch_dashboard: bool = False) -> None:
95
+ console.print()
96
+ console.print(render_banner())
97
+ console.print()
98
+ console.print(message("TraceLLM starting...", MascotState.LOADING))
99
+ console.print()
100
+
101
+ mongodb_ok = _check_mongodb()
102
+ console.print(f" [green]✓[/green] MongoDB connected" if mongodb_ok else f" [red]✗[/red] MongoDB unavailable")
103
+
104
+ api_process = _start_fastapi(port)
105
+ api_ok = _health_check(port)
106
+ console.print(f" [green]✓[/green] API ready" if api_ok else f" [red]✗[/red] API failed")
107
+ console.print(f" [green]✓[/green] WebSocket ready" if api_ok else f" [red]✗[/red] WebSocket unavailable")
108
+
109
+ if launch_dashboard and api_ok:
110
+ import webbrowser
111
+ webbrowser.open(f"http://localhost:{dashboard_port}")
112
+
113
+ _render_status(mongodb_ok, api_ok, port, dashboard_port, launch_dashboard)
114
+
115
+ try:
116
+ api_process.wait()
117
+ except KeyboardInterrupt:
118
+ console.print("\n[yellow]Shutting down...[/yellow]")
119
+ api_process.terminate()
120
+ api_process.wait()
121
+ console.print("[green]Stopped.[/green]")
tracellm/summary.py ADDED
@@ -0,0 +1,53 @@
1
+ """Premium trace summary card."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from rich.panel import Panel
8
+ from rich.table import Table
9
+ from rich.text import Text
10
+
11
+ from tracellm.utils import render_status_badge
12
+
13
+
14
+ def render_summary(trace_data: dict[str, Any]) -> Panel:
15
+ """Render a professional trace summary card."""
16
+ status = str(trace_data["status"])
17
+ badge = render_status_badge(status)
18
+ border = "green" if status == "success" else "yellow" if status == "warning" else "red"
19
+
20
+ table = Table.grid(padding=(0, 3))
21
+ table.add_column(style="bright_black", width=14)
22
+ table.add_column(style="white")
23
+
24
+ table.add_row("Model", str(trace_data.get("model_name", "unknown")))
25
+ table.add_row("Latency", f"{float(trace_data['latency']):.2f} ms")
26
+ table.add_row("Tokens", f"{trace_data['token_count']:,}")
27
+ table.add_row("Retries", str(trace_data["retry_count"]))
28
+ table.add_row("Status", badge)
29
+ created = str(trace_data.get("created_at", ""))
30
+ if created:
31
+ try:
32
+ from datetime import datetime
33
+ dt = datetime.fromisoformat(created)
34
+ created = dt.strftime("%Y-%m-%d %H:%M:%S")
35
+ except (ValueError, TypeError):
36
+ pass
37
+ table.add_row("Timestamp", created)
38
+ table.add_row("Trace ID", str(trace_data["trace_id"]))
39
+
40
+ return Panel(
41
+ table,
42
+ title="\U0001f996 Trace Complete",
43
+ border_style=border,
44
+ padding=(1, 3),
45
+ )
46
+
47
+
48
+ def print_summary(trace_data: dict[str, Any]) -> None:
49
+ """Print the trace summary card to console."""
50
+ from tracellm.utils import console
51
+ console.print()
52
+ console.print(render_summary(trace_data))
53
+ console.print()
@@ -0,0 +1,68 @@
1
+ """Live event stream for trace execution."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime
6
+ from typing import Any
7
+
8
+ from rich.live import Live
9
+ from rich.panel import Panel
10
+ from rich.table import Table
11
+ from rich.text import Text
12
+
13
+ from tracellm.utils import console
14
+
15
+
16
+ class TraceStream:
17
+ """Realtime event stream that renders timestamped trace events."""
18
+
19
+ def __init__(self, prompt: str, model_name: str) -> None:
20
+ self.prompt = prompt
21
+ self.model_name = model_name
22
+ self.events: list[tuple[str, str, str]] = []
23
+ self._start = datetime.now()
24
+ self._live = Live(console=console, refresh_per_second=10, transient=True)
25
+
26
+ def __enter__(self) -> TraceStream:
27
+ self.emit("trace.start")
28
+ self._live.__enter__()
29
+ return self
30
+
31
+ def __exit__(self, *args: Any) -> None:
32
+ self.emit("trace.complete")
33
+ self._live.__exit__(*args)
34
+
35
+ def emit(self, event: str, detail: str = "") -> None:
36
+ """Emit a trace event and update the live display."""
37
+ ts = datetime.now().strftime("%H:%M:%S")
38
+ self.events.append((ts, event, detail))
39
+ self._live.update(self._render())
40
+
41
+ def _render(self) -> Panel:
42
+ now = datetime.now()
43
+ elapsed = (now - self._start).total_seconds()
44
+
45
+ event_table = Table.grid(padding=(0, 2))
46
+ event_table.add_column(style="bright_black", width=10)
47
+ event_table.add_column(style="cyan")
48
+ event_table.add_column(style="dim", width=30)
49
+
50
+ for ts, evt, det in self.events:
51
+ event_table.add_row(f"[{ts}]", evt, det)
52
+
53
+ summary = Table.grid(padding=(0, 2))
54
+ summary.add_column(style="bright_black")
55
+ summary.add_column(style="white")
56
+ summary.add_row("Prompt", self.prompt[:60])
57
+ summary.add_row("Model", self.model_name)
58
+ summary.add_row("Elapsed", f"{elapsed:.1f}s")
59
+ summary.add_row("Events", str(len(self.events)))
60
+
61
+ body = Table.grid(padding=(0, 1))
62
+ body.add_column()
63
+ body.add_row(Text("Event Stream", style="bold white"))
64
+ body.add_row(event_table)
65
+ body.add_row(Text(""))
66
+ body.add_row(summary)
67
+
68
+ return Panel(body, title="Live Trace", border_style="bright_black", padding=(1, 2))