Skip to content

Commit 789c249

Browse files
refactor: extract _get_tracker() helper and consolidate duplicated format_duration
- __init__.py: Replace 10 repeated 'if _tracker is None: raise RuntimeError' guards with a single _get_tracker() helper that centralizes the initialization check and produces operation-specific error messages. - _utils.py: New shared internal utilities module with format_duration() that handles None->dash, ms/s/m/h formatting with consistent behavior. - cli_common.py: format_duration now re-exported from _utils instead of being defined locally. Public API unchanged. - exporter.py: _duration_human replaced with import alias from _utils, eliminating the duplicated implementation that lacked hour-level formatting.
1 parent a58d394 commit 789c249

4 files changed

Lines changed: 73 additions & 53 deletions

File tree

sdk/agentlens/__init__.py

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,23 @@
241241
_tracker: AgentTracker | None = None
242242

243243

244+
def _get_tracker(operation: str = "this operation") -> AgentTracker:
245+
"""Return the global tracker, raising if the SDK is not initialized.
246+
247+
Centralises the guard clause that was previously copy-pasted in every
248+
module-level convenience function.
249+
250+
Args:
251+
operation: Name shown in the error message (e.g. ``"track"``).
252+
253+
Raises:
254+
RuntimeError: If :func:`init` has not been called yet.
255+
"""
256+
if _tracker is None:
257+
raise RuntimeError(f"Call agentlens.init() before {operation}()")
258+
return _tracker
259+
260+
244261
def init(api_key: str = "default", endpoint: str = "http://localhost:3000") -> AgentTracker:
245262
"""Initialize the AgentLens SDK.
246263
@@ -279,16 +296,12 @@ def start_session(agent_name: str = "default-agent", metadata: dict | None = Non
279296
Returns:
280297
A Session object.
281298
"""
282-
if _tracker is None:
283-
raise RuntimeError("Call agentlens.init() before start_session()")
284-
return _tracker.start_session(agent_name=agent_name, metadata=metadata)
299+
return _get_tracker("start_session").start_session(agent_name=agent_name, metadata=metadata)
285300

286301

287302
def end_session(session_id: str | None = None) -> None:
288303
"""End the current or specified session and flush pending events."""
289-
if _tracker is None:
290-
raise RuntimeError("Call agentlens.init() before end_session()")
291-
_tracker.end_session(session_id=session_id)
304+
_get_tracker("end_session").end_session(session_id=session_id)
292305

293306

294307
def track(
@@ -309,9 +322,7 @@ def track(
309322
Returns:
310323
The created AgentEvent.
311324
"""
312-
if _tracker is None:
313-
raise RuntimeError("Call agentlens.init() before track()")
314-
return _tracker.track(
325+
return _get_tracker("track").track(
315326
event_type=event_type,
316327
input_data=input_data,
317328
output_data=output_data,
@@ -332,9 +343,7 @@ def explain(session_id: str | None = None) -> str:
332343
Returns:
333344
A string explanation.
334345
"""
335-
if _tracker is None:
336-
raise RuntimeError("Call agentlens.init() before explain()")
337-
return _tracker.explain(session_id=session_id)
346+
return _get_tracker("explain").explain(session_id=session_id)
338347

339348

340349
def export_session(session_id: str | None = None, format: str = "json"):
@@ -352,9 +361,7 @@ def export_session(session_id: str | None = None, format: str = "json"):
352361
A dict (for JSON) or a string (for CSV) with session data, events,
353362
and summary statistics.
354363
"""
355-
if _tracker is None:
356-
raise RuntimeError("Call agentlens.init() before export_session()")
357-
return _tracker.export_session(session_id=session_id, format=format)
364+
return _get_tracker("export_session").export_session(session_id=session_id, format=format)
358365

359366

360367
def compare_sessions(session_a: str, session_b: str) -> dict:
@@ -371,9 +378,7 @@ def compare_sessions(session_a: str, session_b: str) -> dict:
371378
A dict with ``session_a`` metrics, ``session_b`` metrics,
372379
``deltas``, and ``shared`` breakdowns.
373380
"""
374-
if _tracker is None:
375-
raise RuntimeError("Call agentlens.init() before compare_sessions()")
376-
return _tracker.compare_sessions(session_a=session_a, session_b=session_b)
381+
return _get_tracker("compare_sessions").compare_sessions(session_a=session_a, session_b=session_b)
377382

378383

379384
def get_costs(session_id: str | None = None) -> dict:
@@ -388,9 +393,7 @@ def get_costs(session_id: str | None = None) -> dict:
388393
A dict with ``total_cost``, ``total_input_cost``, ``total_output_cost``,
389394
``model_costs``, ``event_costs``, ``currency``, and ``unmatched_models``.
390395
"""
391-
if _tracker is None:
392-
raise RuntimeError("Call agentlens.init() before get_costs()")
393-
return _tracker.get_costs(session_id=session_id)
396+
return _get_tracker("get_costs").get_costs(session_id=session_id)
394397

395398

396399
def get_pricing() -> dict:
@@ -399,9 +402,7 @@ def get_pricing() -> dict:
399402
Returns:
400403
A dict with ``pricing`` (current prices) and ``defaults`` (built-in defaults).
401404
"""
402-
if _tracker is None:
403-
raise RuntimeError("Call agentlens.init() before get_pricing()")
404-
return _tracker.get_pricing()
405+
return _get_tracker("get_pricing").get_pricing()
405406

406407

407408
def set_pricing(pricing: dict) -> dict:
@@ -414,6 +415,4 @@ def set_pricing(pricing: dict) -> dict:
414415
Returns:
415416
A dict with ``status`` and ``updated`` count.
416417
"""
417-
if _tracker is None:
418-
raise RuntimeError("Call agentlens.init() before set_pricing()")
419-
return _tracker.set_pricing(pricing=pricing)
418+
return _get_tracker("set_pricing").set_pricing(pricing=pricing)

sdk/agentlens/_utils.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"""Shared internal utilities for the AgentLens SDK.
2+
3+
Small helpers that are used by multiple modules (e.g. ``exporter``,
4+
``cli_common``) live here to avoid copy-paste duplication.
5+
"""
6+
7+
from __future__ import annotations
8+
9+
from typing import Any
10+
11+
__all__ = ["format_duration"]
12+
13+
14+
def format_duration(ms: Any) -> str:
15+
"""Format milliseconds into a human-readable duration string.
16+
17+
Returns ``"—"`` for ``None`` values. Handles the full range from
18+
sub-second to hours::
19+
20+
>>> format_duration(42)
21+
'42ms'
22+
>>> format_duration(1500)
23+
'1.5s'
24+
>>> format_duration(90_000)
25+
'1.5m'
26+
>>> format_duration(7_200_000)
27+
'2.0h'
28+
>>> format_duration(None)
29+
'—'
30+
"""
31+
if ms is None:
32+
return "\u2014"
33+
ms = float(ms)
34+
if ms < 1000:
35+
return f"{ms:.0f}ms"
36+
secs = ms / 1000
37+
if secs < 60:
38+
return f"{secs:.1f}s"
39+
mins = secs / 60
40+
if mins < 60:
41+
return f"{mins:.1f}m"
42+
hours = mins / 60
43+
return f"{hours:.1f}h"

sdk/agentlens/cli_common.py

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
import httpx
1616

17+
from agentlens._utils import format_duration # noqa: F401 — re-exported
18+
1719
__all__ = [
1820
"get_client",
1921
"get_client_only",
@@ -58,21 +60,7 @@ def print_json(data: Any) -> None:
5860
print(json.dumps(data, indent=2, default=str))
5961

6062

61-
def format_duration(ms: Any) -> str:
62-
"""Format milliseconds into a human-readable duration string."""
63-
if ms is None:
64-
return "\u2014"
65-
ms = float(ms)
66-
if ms < 1000:
67-
return f"{ms:.0f}ms"
68-
secs = ms / 1000
69-
if secs < 60:
70-
return f"{secs:.1f}s"
71-
mins = secs / 60
72-
if mins < 60:
73-
return f"{mins:.1f}m"
74-
hours = mins / 60
75-
return f"{hours:.1f}h"
63+
# format_duration is imported from agentlens._utils and re-exported above.
7664

7765

7866
def fetch_sessions(client: httpx.Client, limit: int = 200) -> list[dict]:

sdk/agentlens/exporter.py

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -80,17 +80,7 @@ def _iso(dt: datetime | None) -> str | None:
8080
return dt.isoformat() if dt else None
8181

8282

83-
def _duration_human(ms: float | None) -> str:
84-
"""Format milliseconds as human-readable duration."""
85-
if ms is None:
86-
return "—"
87-
if ms < 1000:
88-
return f"{ms:.0f}ms"
89-
secs = ms / 1000
90-
if secs < 60:
91-
return f"{secs:.1f}s"
92-
mins = secs / 60
93-
return f"{mins:.1f}m"
83+
from agentlens._utils import format_duration as _duration_human # consolidated
9484

9585

9686
def _session_stats(session: Session) -> dict[str, Any]:

0 commit comments

Comments
 (0)