Skip to content

Commit f759760

Browse files
committed
feat: add powerful CLI with rich terminal output
- Implement 'review' command for single prompt analysis - Add 'batch' command for processing multiple prompts - Add 'compare' command for multi-model evaluation - Add 'examples' command with sample prompts - Rich terminal formatting with colors, tables, and panels - JSON export support for programmatic use - Beautiful progress indicators and error messages CLI is now production-ready with uqlm-guard command
1 parent 8d6b624 commit f759760

File tree

2 files changed

+491
-0
lines changed

2 files changed

+491
-0
lines changed

uqlm_guard/cli/formatter.py

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
"""
2+
Rich terminal output formatting for uqlm-guard.
3+
"""
4+
from rich.console import Console
5+
from rich.table import Table
6+
from rich.panel import Panel
7+
from rich.progress import Progress, SpinnerColumn, TextColumn
8+
from rich.syntax import Syntax
9+
from rich.markdown import Markdown
10+
from rich import box
11+
from typing import List, Dict, Any
12+
13+
14+
console = Console()
15+
16+
17+
class OutputFormatter:
18+
"""Formats analysis results for terminal display."""
19+
20+
@staticmethod
21+
def print_header(prompt: str):
22+
"""Print analysis header."""
23+
console.print()
24+
console.print(Panel(
25+
f"[bold cyan]Analyzing Prompt[/bold cyan]\n{prompt}",
26+
border_style="cyan",
27+
box=box.ROUNDED
28+
))
29+
console.print()
30+
31+
@staticmethod
32+
def print_analyzing(num_samples: int, model: str):
33+
"""Show analyzing progress."""
34+
console.print(f"[yellow]⚡ Generating {num_samples} responses using {model}...[/yellow]")
35+
36+
@staticmethod
37+
def print_confidence_score(score: float, recommendation: str):
38+
"""Print confidence score with color coding."""
39+
console.print()
40+
41+
# Determine color and emoji
42+
if score >= 0.8:
43+
color = "green"
44+
emoji = "✅"
45+
level = "HIGH CONFIDENCE"
46+
elif score >= 0.6:
47+
color = "yellow"
48+
emoji = "⚠️"
49+
level = "MEDIUM CONFIDENCE"
50+
elif score >= 0.4:
51+
color = "orange1"
52+
emoji = "⚠️"
53+
level = "LOW CONFIDENCE"
54+
else:
55+
color = "red"
56+
emoji = "🔴"
57+
level = "VERY LOW CONFIDENCE"
58+
59+
# Create confidence panel
60+
confidence_text = f"""[bold {color}]{emoji} {level}[/bold {color}]
61+
62+
Confidence Score: [{color}]{score:.2f}/1.0[/{color}]
63+
64+
[italic]{recommendation}[/italic]"""
65+
66+
console.print(Panel(
67+
confidence_text,
68+
border_style=color,
69+
box=box.DOUBLE
70+
))
71+
console.print()
72+
73+
@staticmethod
74+
def print_inconsistencies(inconsistencies: List[Dict[str, Any]]):
75+
"""Print detected inconsistencies."""
76+
if not inconsistencies:
77+
console.print("[green]✓ No major inconsistencies detected[/green]\n")
78+
return
79+
80+
console.print("[bold red]⚠️ Detected Inconsistencies:[/bold red]\n")
81+
82+
for i, inc in enumerate(inconsistencies, 1):
83+
severity = inc.get("severity", "unknown")
84+
severity_color = {
85+
"low": "blue",
86+
"medium": "yellow",
87+
"high": "red",
88+
"critical": "red bold"
89+
}.get(severity, "white")
90+
91+
inc_type = inc.get("type", "unknown")
92+
description = inc.get("description", "No description")
93+
94+
panel_content = f"""[{severity_color}]Severity: {severity.upper()}[/{severity_color}]
95+
Type: {inc_type}
96+
97+
{description}"""
98+
99+
console.print(Panel(
100+
panel_content,
101+
title=f"[bold]Issue #{i}[/bold]",
102+
border_style=severity_color,
103+
box=box.ROUNDED
104+
))
105+
console.print()
106+
107+
@staticmethod
108+
def print_consensus(consensus_parts: List[str]):
109+
"""Print consensus parts."""
110+
if not consensus_parts:
111+
console.print("[yellow]⚠️ No clear consensus found across responses[/yellow]\n")
112+
return
113+
114+
console.print("[bold green]✓ Consensus Elements (present in all responses):[/bold green]\n")
115+
116+
for part in consensus_parts[:5]: # Show first 5
117+
if part.strip():
118+
console.print(f" • {part.strip()}")
119+
120+
if len(consensus_parts) > 5:
121+
console.print(f" [dim]... and {len(consensus_parts) - 5} more[/dim]")
122+
123+
console.print()
124+
125+
@staticmethod
126+
def print_divergence(divergences: List[Dict[str, Any]]):
127+
"""Print divergence information."""
128+
if not divergences:
129+
return
130+
131+
console.print("[bold]Response Divergence Analysis:[/bold]\n")
132+
133+
table = Table(show_header=True, header_style="bold cyan", box=box.ROUNDED)
134+
table.add_column("Response Pair", style="cyan")
135+
table.add_column("Similarity", justify="right")
136+
table.add_column("Diff Lines", justify="right")
137+
138+
for div in divergences[:10]: # Show top 10
139+
pair = div.get("response_pair", (0, 0))
140+
similarity = div.get("similarity", 0)
141+
diff_lines = div.get("diff_lines", 0)
142+
143+
similarity_color = "green" if similarity > 0.8 else "yellow" if similarity > 0.5 else "red"
144+
145+
table.add_row(
146+
f"#{pair[0]} vs #{pair[1]}",
147+
f"[{similarity_color}]{similarity:.2%}[/{similarity_color}]",
148+
str(diff_lines)
149+
)
150+
151+
console.print(table)
152+
console.print()
153+
154+
@staticmethod
155+
def print_responses(responses: List[str], show_full: bool = False):
156+
"""Print generated responses."""
157+
console.print("[bold]Generated Responses:[/bold]\n")
158+
159+
for i, response in enumerate(responses, 1):
160+
if show_full:
161+
console.print(Panel(
162+
response,
163+
title=f"[bold]Response #{i}[/bold]",
164+
border_style="blue",
165+
box=box.ROUNDED
166+
))
167+
else:
168+
# Show preview
169+
preview = response[:200] + "..." if len(response) > 200 else response
170+
console.print(f"[cyan]Response #{i}:[/cyan]")
171+
console.print(f"[dim]{preview}[/dim]\n")
172+
173+
@staticmethod
174+
def print_summary_stats(analysis):
175+
"""Print summary statistics."""
176+
console.print("[bold]Analysis Summary:[/bold]\n")
177+
178+
table = Table(show_header=False, box=box.SIMPLE)
179+
table.add_column("Metric", style="cyan")
180+
table.add_column("Value")
181+
182+
table.add_row("Model Used", analysis.model_used)
183+
table.add_row("Samples Generated", str(analysis.num_samples))
184+
table.add_row("Confidence Score", f"{analysis.confidence_score:.3f}")
185+
table.add_row("Inconsistencies Found", str(len(analysis.inconsistencies)))
186+
table.add_row("Consensus Elements", str(len(analysis.consensus_parts)))
187+
188+
console.print(table)
189+
console.print()
190+
191+
@staticmethod
192+
def print_benchmark_results(results: Dict[str, Any]):
193+
"""Print benchmark results."""
194+
console.print()
195+
console.print(Panel(
196+
"[bold cyan]Benchmark Results[/bold cyan]",
197+
border_style="cyan"
198+
))
199+
console.print()
200+
201+
table = Table(show_header=True, header_style="bold cyan", box=box.ROUNDED)
202+
table.add_column("Category", style="cyan")
203+
table.add_column("Tests", justify="right")
204+
table.add_column("Avg Confidence", justify="right")
205+
table.add_column("High (>0.8)", justify="right", style="green")
206+
table.add_column("Medium (0.6-0.8)", justify="right", style="yellow")
207+
table.add_column("Low (<0.6)", justify="right", style="red")
208+
209+
for category, data in results.items():
210+
table.add_row(
211+
category,
212+
str(data["total"]),
213+
f"{data['avg_confidence']:.2f}",
214+
str(data["high"]),
215+
str(data["medium"]),
216+
str(data["low"])
217+
)
218+
219+
console.print(table)
220+
console.print()
221+
222+
@staticmethod
223+
def print_error(message: str):
224+
"""Print error message."""
225+
console.print(f"\n[bold red]❌ Error:[/bold red] {message}\n")
226+
227+
@staticmethod
228+
def print_success(message: str):
229+
"""Print success message."""
230+
console.print(f"\n[bold green]✓[/bold green] {message}\n")

0 commit comments

Comments
 (0)