agent-trajectory-diff 0.2.0__tar.gz → 0.2.2__tar.gz
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.2.0 → agent_trajectory_diff-0.2.2}/.gitignore +5 -0
- {agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/PKG-INFO +2 -2
- {agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/README.md +1 -1
- {agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/__init__.py +1 -1
- {agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/adapters/langfuse.py +34 -7
- {agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/adapters/openai_agents.py +5 -1
- {agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/adapters/openinference.py +12 -2
- {agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/testing/assertions.py +1 -0
- {agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/LICENSE +0 -0
- {agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/pyproject.toml +0 -0
- {agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/__main__.py +0 -0
- {agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/adapters/__init__.py +0 -0
- {agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/adapters/_iso.py +0 -0
- {agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/adapters/base.py +0 -0
- {agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/adapters/generic.py +0 -0
- {agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/adapters/langsmith.py +0 -0
- {agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/ci/baseline.py +0 -0
- {agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/ci/github.py +0 -0
- {agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/cli.py +0 -0
- {agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/config.py +0 -0
- {agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/engine/__init__.py +0 -0
- {agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/engine/aligner.py +0 -0
- {agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/engine/comparator.py +0 -0
- {agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/engine/explanations.py +0 -0
- {agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/engine/loop_detector.py +0 -0
- {agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/engine/metrics.py +0 -0
- {agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/engine/tree.py +0 -0
- {agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/loader.py +0 -0
- {agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/models/__init__.py +0 -0
- {agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/models/report.py +0 -0
- {agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/models/step.py +0 -0
- {agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/models/trace.py +0 -0
- {agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/py.typed +0 -0
- {agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/pytest_plugin.py +0 -0
- {agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/reporters/__init__.py +0 -0
- {agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/reporters/markdown.py +0 -0
- {agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/reporters/pr.py +0 -0
- {agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/reporters/terminal.py +0 -0
- {agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/testing/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: agent-trajectory-diff
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: A developer-first package to evaluate and find regressions in agent trajectories
|
|
5
5
|
Project-URL: Homepage, https://github.com/lostmartian/agentdiff
|
|
6
6
|
Project-URL: Repository, https://github.com/lostmartian/agentdiff
|
|
@@ -153,7 +153,7 @@ jobs:
|
|
|
153
153
|
- uses: actions/setup-python@v5
|
|
154
154
|
with:
|
|
155
155
|
python-version: "3.11"
|
|
156
|
-
- uses: lostmartian/agentdiff/.github/actions/agentdiff-check@v0.2.
|
|
156
|
+
- uses: lostmartian/agentdiff/.github/actions/agentdiff-check@v0.2.2
|
|
157
157
|
with:
|
|
158
158
|
baseline: traces/baseline.json # committed baseline trace
|
|
159
159
|
candidate: traces/candidate.json # generated by an earlier step
|
|
@@ -133,7 +133,7 @@ jobs:
|
|
|
133
133
|
- uses: actions/setup-python@v5
|
|
134
134
|
with:
|
|
135
135
|
python-version: "3.11"
|
|
136
|
-
- uses: lostmartian/agentdiff/.github/actions/agentdiff-check@v0.2.
|
|
136
|
+
- uses: lostmartian/agentdiff/.github/actions/agentdiff-check@v0.2.2
|
|
137
137
|
with:
|
|
138
138
|
baseline: traces/baseline.json # committed baseline trace
|
|
139
139
|
candidate: traces/candidate.json # generated by an earlier step
|
{agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/adapters/langfuse.py
RENAMED
|
@@ -6,6 +6,14 @@ from agentdiff.models.step import StepStatus, StepType, TokenUsage, TraceStep
|
|
|
6
6
|
from agentdiff.models.trace import AgentTrace
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
def _pick(mapping: dict[str, Any], *keys: str) -> Any:
|
|
10
|
+
"""Return the first present value among the given key variants."""
|
|
11
|
+
for key in keys:
|
|
12
|
+
if mapping.get(key) is not None:
|
|
13
|
+
return mapping[key]
|
|
14
|
+
return None
|
|
15
|
+
|
|
16
|
+
|
|
9
17
|
class LangfuseAdapter(BaseAdapter):
|
|
10
18
|
@classmethod
|
|
11
19
|
def from_dict(cls, data: dict[str, Any]) -> AgentTrace:
|
|
@@ -26,6 +34,21 @@ class LangfuseAdapter(BaseAdapter):
|
|
|
26
34
|
observations = data.get("observations")
|
|
27
35
|
if not isinstance(observations, list):
|
|
28
36
|
observations = []
|
|
37
|
+
|
|
38
|
+
# The API may return observations in non-chronological order; sort by
|
|
39
|
+
# start time so step order reflects the actual execution timeline.
|
|
40
|
+
def _start_key(obs):
|
|
41
|
+
raw = _pick(obs, "startTime", "start_time")
|
|
42
|
+
ts = parse_iso_timestamp(raw)
|
|
43
|
+
if ts is not None:
|
|
44
|
+
return ts
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
keyed = [(obs, _start_key(obs)) for obs in observations]
|
|
48
|
+
keyed = [(obs, k) for obs, k in keyed if isinstance(obs, dict)]
|
|
49
|
+
keyed.sort(key=lambda pair: (pair[1] is None, pair[1] or 0))
|
|
50
|
+
observations = [obs for obs, _ in keyed]
|
|
51
|
+
|
|
29
52
|
steps: list[TraceStep] = []
|
|
30
53
|
|
|
31
54
|
for idx, obs in enumerate(observations):
|
|
@@ -34,12 +57,16 @@ class LangfuseAdapter(BaseAdapter):
|
|
|
34
57
|
f"Langfuse observation at index {idx} is not an object"
|
|
35
58
|
)
|
|
36
59
|
obs_id = obs.get("id") or f"obs_{idx}"
|
|
37
|
-
parent_id = obs
|
|
60
|
+
parent_id = _pick(obs, "parentObservationId", "parent_observation_id")
|
|
38
61
|
|
|
39
62
|
# Map Langfuse type to StepType
|
|
40
63
|
obs_type = str(obs.get("type", "")).upper()
|
|
41
|
-
if obs_type
|
|
64
|
+
if obs_type in ("GENERATION", "EMBEDDING"):
|
|
42
65
|
step_type = StepType.LLM_CALL
|
|
66
|
+
elif obs_type in ("TOOL", "RETRIEVER"):
|
|
67
|
+
step_type = StepType.TOOL_CALL
|
|
68
|
+
elif obs_type in ("AGENT", "CHAIN", "GUARDRAIL", "EVALUATOR"):
|
|
69
|
+
step_type = StepType.ROUTING
|
|
43
70
|
elif obs_type == "SPAN":
|
|
44
71
|
name_lower = str(obs.get("name", "")).lower()
|
|
45
72
|
if "tool" in name_lower or "call" in name_lower:
|
|
@@ -83,8 +110,8 @@ class LangfuseAdapter(BaseAdapter):
|
|
|
83
110
|
|
|
84
111
|
# Latency
|
|
85
112
|
latency_ms = 0.0
|
|
86
|
-
start_str = obs
|
|
87
|
-
end_str = obs
|
|
113
|
+
start_str = _pick(obs, "startTime", "start_time")
|
|
114
|
+
end_str = _pick(obs, "endTime", "end_time")
|
|
88
115
|
if start_str and end_str:
|
|
89
116
|
t1 = parse_iso_timestamp(start_str)
|
|
90
117
|
t2 = parse_iso_timestamp(end_str)
|
|
@@ -92,7 +119,7 @@ class LangfuseAdapter(BaseAdapter):
|
|
|
92
119
|
latency_ms = max(0.0, (t2 - t1).total_seconds() * 1000.0)
|
|
93
120
|
if latency_ms == 0.0:
|
|
94
121
|
latency_ms = (
|
|
95
|
-
cls._as_float(obs
|
|
122
|
+
cls._as_float(_pick(obs, "latency_ms", "latency"))
|
|
96
123
|
or cls._as_float(obs.get("duration") or 0.0) * 1000.0
|
|
97
124
|
)
|
|
98
125
|
|
|
@@ -100,9 +127,9 @@ class LangfuseAdapter(BaseAdapter):
|
|
|
100
127
|
level = str(obs.get("level", "")).upper()
|
|
101
128
|
status = StepStatus.SUCCESS
|
|
102
129
|
error_message = None
|
|
103
|
-
if "ERROR" in level or obs
|
|
130
|
+
if "ERROR" in level or _pick(obs, "statusMessage", "status_message"):
|
|
104
131
|
status = StepStatus.ERROR
|
|
105
|
-
error_message = obs
|
|
132
|
+
error_message = _pick(obs, "statusMessage", "status_message")
|
|
106
133
|
|
|
107
134
|
step = TraceStep(
|
|
108
135
|
step_id=obs_id,
|
{agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/adapters/openai_agents.py
RENAMED
|
@@ -127,7 +127,11 @@ class OpenAIAgentsAdapter(BaseAdapter):
|
|
|
127
127
|
if not isinstance(span_data, dict):
|
|
128
128
|
span_data = {}
|
|
129
129
|
stype = str(span_data.get("type") or span.get("type") or "").lower()
|
|
130
|
-
|
|
130
|
+
sname = str(span_data.get("name") or "").lower()
|
|
131
|
+
# Real OpenAI Agents traces mark the task/turn bookkeeping spans as
|
|
132
|
+
# type="custom" with name="task"/"turn"; older/fixture shapes used a
|
|
133
|
+
# dedicated type. Skip both so they don't surface as steps.
|
|
134
|
+
if stype in _SKIPPED_TYPES or (stype == "custom" and sname in _SKIPPED_TYPES):
|
|
131
135
|
return None
|
|
132
136
|
|
|
133
137
|
step_type = _TYPE_MAP.get(stype, StepType.THOUGHT)
|
{agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/adapters/openinference.py
RENAMED
|
@@ -31,8 +31,12 @@ class OpenInferenceAdapter(BaseAdapter):
|
|
|
31
31
|
context = span.get("context")
|
|
32
32
|
if not isinstance(context, dict):
|
|
33
33
|
context = {}
|
|
34
|
-
span_id =
|
|
34
|
+
span_id = str(
|
|
35
|
+
context.get("span_id") or span.get("span_id") or f"span_{idx}"
|
|
36
|
+
)
|
|
35
37
|
parent_id = span.get("parent_span_id") or span.get("parent_id")
|
|
38
|
+
if parent_id is not None:
|
|
39
|
+
parent_id = str(parent_id)
|
|
36
40
|
|
|
37
41
|
attrs = span.get("attributes")
|
|
38
42
|
if not isinstance(attrs, dict):
|
|
@@ -100,7 +104,12 @@ class OpenInferenceAdapter(BaseAdapter):
|
|
|
100
104
|
if isinstance(start_time, (int, float)) and isinstance(
|
|
101
105
|
end_time, (int, float)
|
|
102
106
|
):
|
|
103
|
-
|
|
107
|
+
# OTel spans carry nanoseconds (~1e18); the legacy shape
|
|
108
|
+
# uses seconds. Detect ns by magnitude and normalize.
|
|
109
|
+
scale = 1.0
|
|
110
|
+
if abs(end_time) > 1e11:
|
|
111
|
+
scale = 1e-9
|
|
112
|
+
latency_ms = max(0.0, (end_time - start_time) * scale * 1000.0)
|
|
104
113
|
else:
|
|
105
114
|
t1 = parse_iso_timestamp(start_time)
|
|
106
115
|
t2 = parse_iso_timestamp(end_time)
|
|
@@ -155,6 +164,7 @@ class OpenInferenceAdapter(BaseAdapter):
|
|
|
155
164
|
or first_span.get("trace_id")
|
|
156
165
|
or trace_id
|
|
157
166
|
)
|
|
167
|
+
trace_id = str(trace_id)
|
|
158
168
|
|
|
159
169
|
# Task input / Final output from root span if available
|
|
160
170
|
task_input = {}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/adapters/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/adapters/generic.py
RENAMED
|
File without changes
|
{agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/adapters/langsmith.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/engine/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/engine/comparator.py
RENAMED
|
File without changes
|
{agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/engine/explanations.py
RENAMED
|
File without changes
|
{agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/engine/loop_detector.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/models/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/reporters/__init__.py
RENAMED
|
File without changes
|
{agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/reporters/markdown.py
RENAMED
|
File without changes
|
|
File without changes
|
{agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/reporters/terminal.py
RENAMED
|
File without changes
|
{agent_trajectory_diff-0.2.0 → agent_trajectory_diff-0.2.2}/src/agentdiff/testing/__init__.py
RENAMED
|
File without changes
|