agent-trajectory-diff 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.
- agent_trajectory_diff-0.1.0.dist-info/METADATA +112 -0
- agent_trajectory_diff-0.1.0.dist-info/RECORD +30 -0
- agent_trajectory_diff-0.1.0.dist-info/WHEEL +4 -0
- agent_trajectory_diff-0.1.0.dist-info/entry_points.txt +2 -0
- agent_trajectory_diff-0.1.0.dist-info/licenses/LICENSE +622 -0
- agentdiff/__init__.py +40 -0
- agentdiff/__main__.py +4 -0
- agentdiff/adapters/__init__.py +13 -0
- agentdiff/adapters/base.py +20 -0
- agentdiff/adapters/deepeval.py +134 -0
- agentdiff/adapters/generic.py +11 -0
- agentdiff/adapters/langfuse.py +145 -0
- agentdiff/adapters/openinference.py +211 -0
- agentdiff/cli.py +120 -0
- agentdiff/engine/__init__.py +17 -0
- agentdiff/engine/aligner.py +146 -0
- agentdiff/engine/comparator.py +68 -0
- agentdiff/engine/loop_detector.py +110 -0
- agentdiff/engine/metrics.py +31 -0
- agentdiff/loader.py +65 -0
- agentdiff/models/__init__.py +14 -0
- agentdiff/models/report.py +74 -0
- agentdiff/models/step.py +40 -0
- agentdiff/models/trace.py +33 -0
- agentdiff/py.typed +0 -0
- agentdiff/reporters/__init__.py +7 -0
- agentdiff/reporters/markdown.py +78 -0
- agentdiff/reporters/terminal.py +107 -0
- agentdiff/testing/__init__.py +5 -0
- agentdiff/testing/assertions.py +50 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
from rich.console import Console
|
|
2
|
+
from rich.panel import Panel
|
|
3
|
+
from rich.table import Table
|
|
4
|
+
from rich.text import Text
|
|
5
|
+
|
|
6
|
+
from agentdiff.models.report import DiffReport, StepDiffStatus
|
|
7
|
+
from agentdiff.models.step import StepStatus
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def render_diff_table(report: DiffReport) -> Table:
|
|
11
|
+
"""Renders the step-by-step diff as a Rich Table."""
|
|
12
|
+
table = Table(
|
|
13
|
+
title="Trajectory Comparison Details",
|
|
14
|
+
show_header=True,
|
|
15
|
+
header_style="bold magenta",
|
|
16
|
+
)
|
|
17
|
+
table.add_column("Index", style="dim", width=6)
|
|
18
|
+
table.add_column("Step Name", width=25)
|
|
19
|
+
table.add_column("Status", width=12)
|
|
20
|
+
table.add_column("Baseline (Lat/Tkn/Cost)", width=28)
|
|
21
|
+
table.add_column("Candidate (Lat/Tkn/Cost)", width=28)
|
|
22
|
+
|
|
23
|
+
for idx, sd in enumerate(report.step_diffs):
|
|
24
|
+
status_text = Text(sd.diff_status.value.upper())
|
|
25
|
+
row_style = ""
|
|
26
|
+
|
|
27
|
+
if sd.diff_status == StepDiffStatus.MATCHED:
|
|
28
|
+
status_text.stylize("dim green")
|
|
29
|
+
row_style = "dim"
|
|
30
|
+
elif sd.diff_status == StepDiffStatus.ADDED:
|
|
31
|
+
status_text.stylize("bold cyan")
|
|
32
|
+
row_style = "bold cyan"
|
|
33
|
+
elif sd.diff_status == StepDiffStatus.REMOVED:
|
|
34
|
+
status_text.stylize("bold red")
|
|
35
|
+
row_style = "bold red"
|
|
36
|
+
elif sd.diff_status == StepDiffStatus.MODIFIED:
|
|
37
|
+
status_text.stylize("bold yellow")
|
|
38
|
+
row_style = "bold yellow"
|
|
39
|
+
|
|
40
|
+
base_info = "-"
|
|
41
|
+
if sd.baseline_step:
|
|
42
|
+
s = sd.baseline_step
|
|
43
|
+
base_info = f"{s.latency_ms:.0f}ms / {s.tokens.total_tokens}t / ${s.tokens.estimated_cost_usd:.4f}"
|
|
44
|
+
if s.status != StepStatus.SUCCESS:
|
|
45
|
+
base_info += f" ({s.status.value.upper()})"
|
|
46
|
+
|
|
47
|
+
cand_info = "-"
|
|
48
|
+
if sd.candidate_step:
|
|
49
|
+
s = sd.candidate_step
|
|
50
|
+
cand_info = f"{s.latency_ms:.0f}ms / {s.tokens.total_tokens}t / ${s.tokens.estimated_cost_usd:.4f}"
|
|
51
|
+
if s.status != StepStatus.SUCCESS:
|
|
52
|
+
cand_info += f" ({s.status.value.upper()})"
|
|
53
|
+
|
|
54
|
+
table.add_row(
|
|
55
|
+
str(idx + 1),
|
|
56
|
+
sd.step_name,
|
|
57
|
+
status_text,
|
|
58
|
+
base_info,
|
|
59
|
+
cand_info,
|
|
60
|
+
style=row_style,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
return table
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def print_report(report: DiffReport):
|
|
67
|
+
"""Outputs the complete DiffReport to the terminal."""
|
|
68
|
+
console = Console()
|
|
69
|
+
|
|
70
|
+
status_str = (
|
|
71
|
+
"[bold green]PASSED[/bold green]"
|
|
72
|
+
if report.passed
|
|
73
|
+
else "[bold red]FAILED[/bold red]"
|
|
74
|
+
)
|
|
75
|
+
summary_text = (
|
|
76
|
+
f"[bold]Baseline Trace:[/bold] {report.baseline_id}\n"
|
|
77
|
+
f"[bold]Candidate Trace:[/bold] {report.candidate_id}\n"
|
|
78
|
+
f"[bold]Comparison Status:[/bold] {status_str}\n\n"
|
|
79
|
+
f"[bold]Trajectory Divergence Index (TDI):[/bold] {report.trajectory_divergence_index:.4f}\n"
|
|
80
|
+
f"[bold]Baseline Wasted Effort (WEI):[/bold] {report.baseline_wei:.4f}\n"
|
|
81
|
+
f"[bold]Candidate Wasted Effort (WEI):[/bold] {report.candidate_wei:.4f}\n\n"
|
|
82
|
+
f"[bold]Resource Deltas:[/bold]\n"
|
|
83
|
+
f" • Latency Delta: {report.latency_delta_percentage:+.2f}%\n"
|
|
84
|
+
f" • Token Delta: {report.token_delta_percentage:+.2f}%\n"
|
|
85
|
+
f" • Cost Delta: {report.cost_delta_percentage:+.2f}%"
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
panel = Panel(
|
|
89
|
+
summary_text,
|
|
90
|
+
title="[bold cyan]AgentDiff Comparison Summary[/bold cyan]",
|
|
91
|
+
border_style="cyan",
|
|
92
|
+
)
|
|
93
|
+
console.print(panel)
|
|
94
|
+
|
|
95
|
+
if report.loops_detected:
|
|
96
|
+
loop_text = ""
|
|
97
|
+
for idx, loop in enumerate(report.loops_detected):
|
|
98
|
+
stagnant_str = " (Stagnant state changes)" if loop.get("stagnant") else ""
|
|
99
|
+
loop_text += f"[bold red]Loop #{idx + 1}:[/bold red] Repeated {loop['steps']} {loop['iterations']} times{stagnant_str}\n"
|
|
100
|
+
loop_panel = Panel(
|
|
101
|
+
loop_text.strip(),
|
|
102
|
+
title="[bold red]Warnings: Loops Detected[/bold red]",
|
|
103
|
+
border_style="red",
|
|
104
|
+
)
|
|
105
|
+
console.print(loop_panel)
|
|
106
|
+
|
|
107
|
+
console.print(render_diff_table(report))
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from agentdiff.models.report import DiffReport
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def assert_no_regressions(
|
|
5
|
+
report: DiffReport,
|
|
6
|
+
max_divergence: float = 0.25,
|
|
7
|
+
max_cost_increase_pct: float = 5.0,
|
|
8
|
+
allow_loops: bool = False,
|
|
9
|
+
max_wasted_effort: float = 0.10,
|
|
10
|
+
):
|
|
11
|
+
"""Expressive regression assertion helper for pytest suites.
|
|
12
|
+
|
|
13
|
+
Raises an AssertionError with descriptive failure outputs if any threshold is violated.
|
|
14
|
+
"""
|
|
15
|
+
errors = []
|
|
16
|
+
|
|
17
|
+
# 1. Divergence Check
|
|
18
|
+
if report.trajectory_divergence_index > max_divergence:
|
|
19
|
+
errors.append(
|
|
20
|
+
f"Trajectory Divergence Index (TDI) of {report.trajectory_divergence_index:.4f} "
|
|
21
|
+
f"exceeded threshold of {max_divergence:.4f}."
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# 2. Cost Check
|
|
25
|
+
if report.cost_delta_percentage > max_cost_increase_pct:
|
|
26
|
+
errors.append(
|
|
27
|
+
f"Cost increase of {report.cost_delta_percentage:+.2f}% "
|
|
28
|
+
f"exceeded threshold of {max_cost_increase_pct:+.2f}%."
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# 3. Loops Check
|
|
32
|
+
if not allow_loops and report.loops_detected:
|
|
33
|
+
errors.append(
|
|
34
|
+
f"Detected {len(report.loops_detected)} loops in the candidate run, "
|
|
35
|
+
f"but allow_loops is False."
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# 4. Wasted Effort Check
|
|
39
|
+
if report.candidate_wei > max_wasted_effort:
|
|
40
|
+
errors.append(
|
|
41
|
+
f"Candidate Wasted Effort Index (WEI) of {report.candidate_wei:.4f} "
|
|
42
|
+
f"exceeded threshold of {max_wasted_effort:.4f}."
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
if errors:
|
|
46
|
+
errors_str = "\n".join(f" - {err}" for err in errors)
|
|
47
|
+
summary_str = report.summary()
|
|
48
|
+
raise AssertionError(
|
|
49
|
+
f"AgentDiff Regression Verification Failed:\n{errors_str}\n\n{summary_str}"
|
|
50
|
+
)
|