langfuse-agent-cli 0.3.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.
@@ -0,0 +1,50 @@
1
+ """Terminal visualizer and timeline renderer for Langfuse traces."""
2
+ from __future__ import annotations
3
+
4
+ from typing import Any, Dict, List
5
+
6
+ MARKS = {"ERROR": "✗ [ERROR]", "WARNING": "⚠ [WARN]"}
7
+
8
+
9
+ def format_duration(seconds: float) -> str:
10
+ if seconds < 1.0:
11
+ return f"{seconds * 1000:.0f}ms"
12
+ return f"{seconds:.2f}s"
13
+
14
+
15
+ def render_trace_timeline(trace_id: str, observations: List[Dict[str, Any]]) -> str:
16
+ """One trace as an observation tree: children under their parent, siblings by start time."""
17
+ ids = {o.get("id") for o in observations}
18
+ children: Dict[Any, List[Dict[str, Any]]] = {}
19
+ roots = []
20
+ for obs in sorted(observations, key=lambda o: o.get("startTime") or ""):
21
+ parent = obs.get("parentObservationId")
22
+ if parent in ids:
23
+ children.setdefault(parent, []).append(obs)
24
+ else:
25
+ roots.append(obs) # no parent, or the parent is not part of this response
26
+
27
+ errors = sum(1 for o in observations if o.get("level") == "ERROR")
28
+ lines = [f"Trace {trace_id}: {len(observations)} observations, {errors} error{'' if errors == 1 else 's'}"]
29
+
30
+ def walk(obs: Dict[str, Any], depth: int) -> None:
31
+ indent = " " * depth
32
+ level = obs.get("level") or "DEFAULT"
33
+ line = f"{indent}{MARKS.get(level, '✓')} {obs.get('type') or 'SPAN'} {obs.get('name') or 'unnamed'}"
34
+ if obs.get("latency") is not None:
35
+ line += f" [{format_duration(float(obs['latency']))}]"
36
+ if obs.get("model"):
37
+ line += f" (model: {obs['model']})"
38
+ lines.append(line)
39
+ if level in MARKS and obs.get("statusMessage"):
40
+ lines.append(f"{indent} Message: {obs['statusMessage']}")
41
+ for key in ("input", "output"):
42
+ value = obs.get(key)
43
+ if isinstance(value, str) and value.strip() and len(value) < 120:
44
+ lines.append(f"{indent} {key.capitalize()}: {value.strip()}")
45
+ for child in children.get(obs.get("id"), []):
46
+ walk(child, depth + 1)
47
+
48
+ for root in roots:
49
+ walk(root, 0)
50
+ return "\n".join(lines)