agentprdiff 0.1.0__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.
- agentprdiff-0.1.0/.gitignore +32 -0
- agentprdiff-0.1.0/CHANGELOG.md +37 -0
- agentprdiff-0.1.0/LICENSE +21 -0
- agentprdiff-0.1.0/PKG-INFO +200 -0
- agentprdiff-0.1.0/README.md +163 -0
- agentprdiff-0.1.0/examples/quickstart/README.md +22 -0
- agentprdiff-0.1.0/pyproject.toml +80 -0
- agentprdiff-0.1.0/src/agentprdiff/__init__.py +81 -0
- agentprdiff-0.1.0/src/agentprdiff/cli.py +124 -0
- agentprdiff-0.1.0/src/agentprdiff/core.py +217 -0
- agentprdiff-0.1.0/src/agentprdiff/differ.py +161 -0
- agentprdiff-0.1.0/src/agentprdiff/graders/__init__.py +38 -0
- agentprdiff-0.1.0/src/agentprdiff/graders/deterministic.py +186 -0
- agentprdiff-0.1.0/src/agentprdiff/graders/semantic.py +180 -0
- agentprdiff-0.1.0/src/agentprdiff/loader.py +47 -0
- agentprdiff-0.1.0/src/agentprdiff/reporters.py +127 -0
- agentprdiff-0.1.0/src/agentprdiff/runner.py +130 -0
- agentprdiff-0.1.0/src/agentprdiff/store.py +80 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
build/
|
|
8
|
+
dist/
|
|
9
|
+
*.egg-info/
|
|
10
|
+
*.egg
|
|
11
|
+
.pytest_cache/
|
|
12
|
+
.coverage
|
|
13
|
+
.coverage.*
|
|
14
|
+
htmlcov/
|
|
15
|
+
.mypy_cache/
|
|
16
|
+
.ruff_cache/
|
|
17
|
+
|
|
18
|
+
# Virtual envs
|
|
19
|
+
.venv/
|
|
20
|
+
venv/
|
|
21
|
+
env/
|
|
22
|
+
|
|
23
|
+
# Editor / OS
|
|
24
|
+
.DS_Store
|
|
25
|
+
.idea/
|
|
26
|
+
.vscode/
|
|
27
|
+
*.swp
|
|
28
|
+
|
|
29
|
+
# agentguard runtime
|
|
30
|
+
# Users SHOULD commit .agentguard/baselines; they should NOT commit run artifacts.
|
|
31
|
+
.agentguard/runs/
|
|
32
|
+
.agentguard/cache/
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `agentprdiff` are documented in this file. Originally
|
|
4
|
+
prototyped under the name `tracediff`; renamed before first public release.
|
|
5
|
+
|
|
6
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
7
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
8
|
+
|
|
9
|
+
## [0.1.0] — 2026-04-22
|
|
10
|
+
|
|
11
|
+
Initial public release.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Core `Suite` / `Case` / `Trace` model for defining agent regression tests.
|
|
16
|
+
- Deterministic graders: `contains`, `contains_any`, `regex_match`, `tool_called`,
|
|
17
|
+
`tool_sequence`, `output_length_lt`, `latency_lt_ms`, `cost_lt_usd`,
|
|
18
|
+
`no_tool_called`.
|
|
19
|
+
- Semantic grader (`semantic`) with a pluggable `judge` callable and built-in
|
|
20
|
+
fake judge for CI environments without API keys.
|
|
21
|
+
- Baseline store (JSON files under `.agentprdiff/baselines/`) designed to be
|
|
22
|
+
committed to version control.
|
|
23
|
+
- Trace diff engine producing a structured `TraceDelta` (assertion pass/fail
|
|
24
|
+
changes, cost delta, latency delta, tool-call sequence changes, output
|
|
25
|
+
change).
|
|
26
|
+
- CLI: `agentprdiff init`, `agentprdiff record`, `agentprdiff check`, `agentprdiff diff`.
|
|
27
|
+
- Rich-formatted terminal reporter and machine-readable JSON reporter for CI.
|
|
28
|
+
- Quickstart example with a mock agent that runs without any API keys.
|
|
29
|
+
- Pytest test suite covering graders, runner, differ, store, and CLI smoke.
|
|
30
|
+
- GitHub Actions CI workflow.
|
|
31
|
+
|
|
32
|
+
### Known limitations
|
|
33
|
+
|
|
34
|
+
- Only a manual instrumentation API for provider SDKs is shipped in 0.1.0.
|
|
35
|
+
Drop-in wrappers for OpenAI / Anthropic / Vercel AI SDK are planned for 0.2.
|
|
36
|
+
- The semantic grader's built-in judge supports OpenAI and Anthropic via user-
|
|
37
|
+
supplied API keys; hosted judge endpoints are not yet offered.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vinoth Nageshwaran
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agentprdiff
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Guard your LLM agents in CI. Snapshot tests that catch behavioral regressions when models, prompts, or vendors change.
|
|
5
|
+
Project-URL: Homepage, https://github.com/vnageshwaran-de/agentprdiff
|
|
6
|
+
Project-URL: Documentation, https://github.com/vnageshwaran-de/agentprdiff#readme
|
|
7
|
+
Project-URL: Issues, https://github.com/vnageshwaran-de/agentprdiff/issues
|
|
8
|
+
Project-URL: Repository, https://github.com/vnageshwaran-de/agentprdiff
|
|
9
|
+
Author-email: Vinoth Nageshwaran <vnageshwaran@gmail.com>
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: agent,ci,evaluation,llm,observability,regression-testing,snapshot-testing
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Classifier: Topic :: Software Development :: Testing
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Requires-Dist: click>=8.1
|
|
24
|
+
Requires-Dist: pydantic>=2.0
|
|
25
|
+
Requires-Dist: pyyaml>=6.0
|
|
26
|
+
Requires-Dist: rich>=13.0
|
|
27
|
+
Provides-Extra: anthropic
|
|
28
|
+
Requires-Dist: anthropic>=0.30; extra == 'anthropic'
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: mypy>=1.8; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest-cov>=4.1; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest>=7.4; extra == 'dev'
|
|
33
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
34
|
+
Provides-Extra: openai
|
|
35
|
+
Requires-Dist: openai>=1.0; extra == 'openai'
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
|
|
38
|
+
# agentprdiff
|
|
39
|
+
|
|
40
|
+
**Guard your LLM agents in CI.** Snapshot tests that catch behavioral regressions when models, prompts, or vendors change.
|
|
41
|
+
|
|
42
|
+
> You upgraded Claude. You tweaked a system prompt. You swapped `gpt-4o` for `gpt-4o-mini` in the cheap path. Which of your agent's behaviors just changed? `agentprdiff` tells you — before the PR merges.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install agentprdiff
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
[](https://github.com/vnageshwaran-de/agentprdiff/actions/workflows/ci.yml)
|
|
49
|
+
[](https://pypi.org/project/agentprdiff/)
|
|
50
|
+
[](https://pypi.org/project/agentprdiff/)
|
|
51
|
+
[](./LICENSE)
|
|
52
|
+
|
|
53
|
+
## Why
|
|
54
|
+
|
|
55
|
+
Unit tests assume determinism. Agents aren't deterministic, but they do have *behaviors you rely on* — a specific tool gets called, a refund amount is quoted, a latency budget is respected, a safety guardrail fires. When a model or prompt changes, those behaviors drift. Today most teams find out in production.
|
|
56
|
+
|
|
57
|
+
`agentprdiff` turns those behaviors into versioned, diffable baselines you check into git, and a CI command that fails the build when they regress.
|
|
58
|
+
|
|
59
|
+
It is **not** a framework. Your agent stays exactly the way it is. `agentprdiff` records what it did, lets you assert what should be true about what it did, and compares runs across time.
|
|
60
|
+
|
|
61
|
+
## 10-line hello world
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
# suite.py
|
|
65
|
+
from agentprdiff import case, suite
|
|
66
|
+
from agentprdiff.graders import contains, tool_called, latency_lt_ms, semantic
|
|
67
|
+
from my_agent import run # your agent — unchanged
|
|
68
|
+
|
|
69
|
+
support = suite(
|
|
70
|
+
name="customer_support",
|
|
71
|
+
agent=run,
|
|
72
|
+
cases=[
|
|
73
|
+
case(
|
|
74
|
+
name="refund_happy_path",
|
|
75
|
+
input="I want a refund for order #1234",
|
|
76
|
+
expect=[
|
|
77
|
+
contains("refund"),
|
|
78
|
+
tool_called("lookup_order"),
|
|
79
|
+
semantic("agent acknowledges the refund and explains the timeline"),
|
|
80
|
+
latency_lt_ms(10_000),
|
|
81
|
+
],
|
|
82
|
+
),
|
|
83
|
+
],
|
|
84
|
+
)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
agentprdiff init
|
|
89
|
+
agentprdiff record suite.py # save this run as the baseline
|
|
90
|
+
agentprdiff check suite.py # in CI: diff vs baseline, exit 1 on regression
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
That's the whole product. Four CLI commands. One Python file. Zero framework lock-in.
|
|
94
|
+
|
|
95
|
+
## What's in the box
|
|
96
|
+
|
|
97
|
+
- **Case + Suite model** — tiny, opinionated, no magic.
|
|
98
|
+
- **10 batteries-included graders** — `contains`, `contains_any`, `regex_match`, `tool_called`, `tool_sequence`, `no_tool_called`, `output_length_lt`, `latency_lt_ms`, `cost_lt_usd`, `semantic` (LLM-as-judge with pluggable backend).
|
|
99
|
+
- **Baseline store** — JSON files under `.agentprdiff/baselines/`, meant to be **committed**. Reviewers see trace changes in pull requests.
|
|
100
|
+
- **Diff engine** — per-case `TraceDelta` with assertion pass/fail changes, cost delta, latency delta, tool-sequence changes, and a unified output diff.
|
|
101
|
+
- **CI-ready CLI** — exit 1 on regression, `--json-out` for artifact archiving, Rich-formatted terminal output.
|
|
102
|
+
- **Zero SDK lock-in** — works with OpenAI, Anthropic, Gemini, Bedrock, LangChain, LangGraph, LlamaIndex, Vercel AI SDK, custom wrappers — if you can wrap your agent in a function, `agentprdiff` can test it.
|
|
103
|
+
|
|
104
|
+
## How it compares
|
|
105
|
+
|
|
106
|
+
| | Unit tests | LLM-as-judge eval | `agentprdiff` |
|
|
107
|
+
|---|---|---|---|
|
|
108
|
+
| Deterministic pass/fail | yes | no | **yes** (when assertions are deterministic) |
|
|
109
|
+
| Catches behavioral drift | no | yes | **yes** |
|
|
110
|
+
| Runs in CI on every PR | yes | too expensive | **yes** |
|
|
111
|
+
| Human-readable diff of what changed | n/a | rare | **yes** |
|
|
112
|
+
| Works without API keys | yes | no | **yes** (deterministic graders + fake judge) |
|
|
113
|
+
|
|
114
|
+
The value is in the combination: deterministic assertions for the 80% of behaviors you can encode as rules ("this tool was called", "this word appeared", "cost stayed under $0.02"), plus a semantic grader for the 20% that need a judge — with a fake-judge fallback so your CI stays green and free when API keys aren't available.
|
|
115
|
+
|
|
116
|
+
## The workflow
|
|
117
|
+
|
|
118
|
+
1. Write a `Suite` alongside your agent code.
|
|
119
|
+
2. Run `agentprdiff record` once on a known-good version. Commit the resulting `.agentprdiff/baselines/` directory.
|
|
120
|
+
3. In CI, on every PR, run `agentprdiff check`. If any assertion regresses, or cost/latency budgets are breached, the job fails.
|
|
121
|
+
4. When behavior intentionally changes, the PR author re-runs `agentprdiff record`, commits the new baseline, and explains the change in the PR description. Reviewers see the before/after in the diff.
|
|
122
|
+
|
|
123
|
+
This is the same loop as Jest snapshot tests or VCR cassettes — applied to LLM agents.
|
|
124
|
+
|
|
125
|
+
## Instrumenting your agent
|
|
126
|
+
|
|
127
|
+
`agentprdiff` doesn't monkey-patch anything. Your agent returns `(output, Trace)`:
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
from agentprdiff import Trace, LLMCall, ToolCall
|
|
131
|
+
|
|
132
|
+
def my_agent(query: str) -> tuple[str, Trace]:
|
|
133
|
+
trace = Trace(suite_name="", case_name="", input=query)
|
|
134
|
+
|
|
135
|
+
# ... call your model, record what happened ...
|
|
136
|
+
trace.record_llm_call(LLMCall(
|
|
137
|
+
provider="anthropic",
|
|
138
|
+
model="claude-sonnet-4-6",
|
|
139
|
+
prompt_tokens=120, completion_tokens=80,
|
|
140
|
+
cost_usd=0.0012, latency_ms=340,
|
|
141
|
+
))
|
|
142
|
+
|
|
143
|
+
# ... call a tool, record what happened ...
|
|
144
|
+
trace.record_tool_call(ToolCall(name="lookup_order", arguments={"id": "1234"}))
|
|
145
|
+
|
|
146
|
+
return final_output, trace
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Agents that return just an output still work — `agentprdiff` wraps them and captures wall-clock latency. You can backfill richer instrumentation incrementally, assertion by assertion.
|
|
150
|
+
|
|
151
|
+
## CI integration
|
|
152
|
+
|
|
153
|
+
```yaml
|
|
154
|
+
# .github/workflows/agents.yml
|
|
155
|
+
name: agent-regression
|
|
156
|
+
on: [pull_request]
|
|
157
|
+
jobs:
|
|
158
|
+
agentprdiff:
|
|
159
|
+
runs-on: ubuntu-latest
|
|
160
|
+
steps:
|
|
161
|
+
- uses: actions/checkout@v4
|
|
162
|
+
- uses: actions/setup-python@v5
|
|
163
|
+
with: { python-version: "3.11" }
|
|
164
|
+
- run: pip install -e ".[dev]"
|
|
165
|
+
- run: agentprdiff check suites/*.py --json-out artifacts/agentprdiff.json
|
|
166
|
+
- uses: actions/upload-artifact@v4
|
|
167
|
+
if: always()
|
|
168
|
+
with: { name: agentprdiff, path: artifacts/ }
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
See [`docs/ci-integration.md`](./docs/ci-integration.md) for GitLab, CircleCI, and Buildkite.
|
|
172
|
+
|
|
173
|
+
## Quickstart
|
|
174
|
+
|
|
175
|
+
A runnable end-to-end demo, no API keys needed:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
git clone https://github.com/vnageshwaran-de/agentprdiff
|
|
179
|
+
cd agentprdiff
|
|
180
|
+
pip install -e ".[dev]"
|
|
181
|
+
|
|
182
|
+
cd examples/quickstart
|
|
183
|
+
agentprdiff init
|
|
184
|
+
agentprdiff record suite.py
|
|
185
|
+
agentprdiff check suite.py # exit 0
|
|
186
|
+
|
|
187
|
+
# now break the agent and watch agentprdiff catch it
|
|
188
|
+
sed -i "s/refund/noundr/g" agent.py
|
|
189
|
+
agentprdiff check suite.py # exit 1; see the diff
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Status
|
|
193
|
+
|
|
194
|
+
`agentprdiff` is **alpha** (0.1.0). The core model and CLI are stable; provider-specific SDK wrappers and a LangChain/LangGraph integration are on the 0.2 roadmap. See [`CHANGELOG.md`](./CHANGELOG.md).
|
|
195
|
+
|
|
196
|
+
Feedback, bug reports, and PRs extremely welcome. Open an issue or @ me.
|
|
197
|
+
|
|
198
|
+
## License
|
|
199
|
+
|
|
200
|
+
MIT. See [`LICENSE`](./LICENSE).
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# agentprdiff
|
|
2
|
+
|
|
3
|
+
**Guard your LLM agents in CI.** Snapshot tests that catch behavioral regressions when models, prompts, or vendors change.
|
|
4
|
+
|
|
5
|
+
> You upgraded Claude. You tweaked a system prompt. You swapped `gpt-4o` for `gpt-4o-mini` in the cheap path. Which of your agent's behaviors just changed? `agentprdiff` tells you — before the PR merges.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install agentprdiff
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
[](https://github.com/vnageshwaran-de/agentprdiff/actions/workflows/ci.yml)
|
|
12
|
+
[](https://pypi.org/project/agentprdiff/)
|
|
13
|
+
[](https://pypi.org/project/agentprdiff/)
|
|
14
|
+
[](./LICENSE)
|
|
15
|
+
|
|
16
|
+
## Why
|
|
17
|
+
|
|
18
|
+
Unit tests assume determinism. Agents aren't deterministic, but they do have *behaviors you rely on* — a specific tool gets called, a refund amount is quoted, a latency budget is respected, a safety guardrail fires. When a model or prompt changes, those behaviors drift. Today most teams find out in production.
|
|
19
|
+
|
|
20
|
+
`agentprdiff` turns those behaviors into versioned, diffable baselines you check into git, and a CI command that fails the build when they regress.
|
|
21
|
+
|
|
22
|
+
It is **not** a framework. Your agent stays exactly the way it is. `agentprdiff` records what it did, lets you assert what should be true about what it did, and compares runs across time.
|
|
23
|
+
|
|
24
|
+
## 10-line hello world
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
# suite.py
|
|
28
|
+
from agentprdiff import case, suite
|
|
29
|
+
from agentprdiff.graders import contains, tool_called, latency_lt_ms, semantic
|
|
30
|
+
from my_agent import run # your agent — unchanged
|
|
31
|
+
|
|
32
|
+
support = suite(
|
|
33
|
+
name="customer_support",
|
|
34
|
+
agent=run,
|
|
35
|
+
cases=[
|
|
36
|
+
case(
|
|
37
|
+
name="refund_happy_path",
|
|
38
|
+
input="I want a refund for order #1234",
|
|
39
|
+
expect=[
|
|
40
|
+
contains("refund"),
|
|
41
|
+
tool_called("lookup_order"),
|
|
42
|
+
semantic("agent acknowledges the refund and explains the timeline"),
|
|
43
|
+
latency_lt_ms(10_000),
|
|
44
|
+
],
|
|
45
|
+
),
|
|
46
|
+
],
|
|
47
|
+
)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
agentprdiff init
|
|
52
|
+
agentprdiff record suite.py # save this run as the baseline
|
|
53
|
+
agentprdiff check suite.py # in CI: diff vs baseline, exit 1 on regression
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
That's the whole product. Four CLI commands. One Python file. Zero framework lock-in.
|
|
57
|
+
|
|
58
|
+
## What's in the box
|
|
59
|
+
|
|
60
|
+
- **Case + Suite model** — tiny, opinionated, no magic.
|
|
61
|
+
- **10 batteries-included graders** — `contains`, `contains_any`, `regex_match`, `tool_called`, `tool_sequence`, `no_tool_called`, `output_length_lt`, `latency_lt_ms`, `cost_lt_usd`, `semantic` (LLM-as-judge with pluggable backend).
|
|
62
|
+
- **Baseline store** — JSON files under `.agentprdiff/baselines/`, meant to be **committed**. Reviewers see trace changes in pull requests.
|
|
63
|
+
- **Diff engine** — per-case `TraceDelta` with assertion pass/fail changes, cost delta, latency delta, tool-sequence changes, and a unified output diff.
|
|
64
|
+
- **CI-ready CLI** — exit 1 on regression, `--json-out` for artifact archiving, Rich-formatted terminal output.
|
|
65
|
+
- **Zero SDK lock-in** — works with OpenAI, Anthropic, Gemini, Bedrock, LangChain, LangGraph, LlamaIndex, Vercel AI SDK, custom wrappers — if you can wrap your agent in a function, `agentprdiff` can test it.
|
|
66
|
+
|
|
67
|
+
## How it compares
|
|
68
|
+
|
|
69
|
+
| | Unit tests | LLM-as-judge eval | `agentprdiff` |
|
|
70
|
+
|---|---|---|---|
|
|
71
|
+
| Deterministic pass/fail | yes | no | **yes** (when assertions are deterministic) |
|
|
72
|
+
| Catches behavioral drift | no | yes | **yes** |
|
|
73
|
+
| Runs in CI on every PR | yes | too expensive | **yes** |
|
|
74
|
+
| Human-readable diff of what changed | n/a | rare | **yes** |
|
|
75
|
+
| Works without API keys | yes | no | **yes** (deterministic graders + fake judge) |
|
|
76
|
+
|
|
77
|
+
The value is in the combination: deterministic assertions for the 80% of behaviors you can encode as rules ("this tool was called", "this word appeared", "cost stayed under $0.02"), plus a semantic grader for the 20% that need a judge — with a fake-judge fallback so your CI stays green and free when API keys aren't available.
|
|
78
|
+
|
|
79
|
+
## The workflow
|
|
80
|
+
|
|
81
|
+
1. Write a `Suite` alongside your agent code.
|
|
82
|
+
2. Run `agentprdiff record` once on a known-good version. Commit the resulting `.agentprdiff/baselines/` directory.
|
|
83
|
+
3. In CI, on every PR, run `agentprdiff check`. If any assertion regresses, or cost/latency budgets are breached, the job fails.
|
|
84
|
+
4. When behavior intentionally changes, the PR author re-runs `agentprdiff record`, commits the new baseline, and explains the change in the PR description. Reviewers see the before/after in the diff.
|
|
85
|
+
|
|
86
|
+
This is the same loop as Jest snapshot tests or VCR cassettes — applied to LLM agents.
|
|
87
|
+
|
|
88
|
+
## Instrumenting your agent
|
|
89
|
+
|
|
90
|
+
`agentprdiff` doesn't monkey-patch anything. Your agent returns `(output, Trace)`:
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from agentprdiff import Trace, LLMCall, ToolCall
|
|
94
|
+
|
|
95
|
+
def my_agent(query: str) -> tuple[str, Trace]:
|
|
96
|
+
trace = Trace(suite_name="", case_name="", input=query)
|
|
97
|
+
|
|
98
|
+
# ... call your model, record what happened ...
|
|
99
|
+
trace.record_llm_call(LLMCall(
|
|
100
|
+
provider="anthropic",
|
|
101
|
+
model="claude-sonnet-4-6",
|
|
102
|
+
prompt_tokens=120, completion_tokens=80,
|
|
103
|
+
cost_usd=0.0012, latency_ms=340,
|
|
104
|
+
))
|
|
105
|
+
|
|
106
|
+
# ... call a tool, record what happened ...
|
|
107
|
+
trace.record_tool_call(ToolCall(name="lookup_order", arguments={"id": "1234"}))
|
|
108
|
+
|
|
109
|
+
return final_output, trace
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Agents that return just an output still work — `agentprdiff` wraps them and captures wall-clock latency. You can backfill richer instrumentation incrementally, assertion by assertion.
|
|
113
|
+
|
|
114
|
+
## CI integration
|
|
115
|
+
|
|
116
|
+
```yaml
|
|
117
|
+
# .github/workflows/agents.yml
|
|
118
|
+
name: agent-regression
|
|
119
|
+
on: [pull_request]
|
|
120
|
+
jobs:
|
|
121
|
+
agentprdiff:
|
|
122
|
+
runs-on: ubuntu-latest
|
|
123
|
+
steps:
|
|
124
|
+
- uses: actions/checkout@v4
|
|
125
|
+
- uses: actions/setup-python@v5
|
|
126
|
+
with: { python-version: "3.11" }
|
|
127
|
+
- run: pip install -e ".[dev]"
|
|
128
|
+
- run: agentprdiff check suites/*.py --json-out artifacts/agentprdiff.json
|
|
129
|
+
- uses: actions/upload-artifact@v4
|
|
130
|
+
if: always()
|
|
131
|
+
with: { name: agentprdiff, path: artifacts/ }
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
See [`docs/ci-integration.md`](./docs/ci-integration.md) for GitLab, CircleCI, and Buildkite.
|
|
135
|
+
|
|
136
|
+
## Quickstart
|
|
137
|
+
|
|
138
|
+
A runnable end-to-end demo, no API keys needed:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
git clone https://github.com/vnageshwaran-de/agentprdiff
|
|
142
|
+
cd agentprdiff
|
|
143
|
+
pip install -e ".[dev]"
|
|
144
|
+
|
|
145
|
+
cd examples/quickstart
|
|
146
|
+
agentprdiff init
|
|
147
|
+
agentprdiff record suite.py
|
|
148
|
+
agentprdiff check suite.py # exit 0
|
|
149
|
+
|
|
150
|
+
# now break the agent and watch agentprdiff catch it
|
|
151
|
+
sed -i "s/refund/noundr/g" agent.py
|
|
152
|
+
agentprdiff check suite.py # exit 1; see the diff
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Status
|
|
156
|
+
|
|
157
|
+
`agentprdiff` is **alpha** (0.1.0). The core model and CLI are stable; provider-specific SDK wrappers and a LangChain/LangGraph integration are on the 0.2 roadmap. See [`CHANGELOG.md`](./CHANGELOG.md).
|
|
158
|
+
|
|
159
|
+
Feedback, bug reports, and PRs extremely welcome. Open an issue or @ me.
|
|
160
|
+
|
|
161
|
+
## License
|
|
162
|
+
|
|
163
|
+
MIT. See [`LICENSE`](./LICENSE).
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# agentprdiff quickstart
|
|
2
|
+
|
|
3
|
+
A minimal, runnable example. No API keys required.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
# from the repo root:
|
|
7
|
+
pip install -e .
|
|
8
|
+
cd examples/quickstart
|
|
9
|
+
|
|
10
|
+
agentprdiff init
|
|
11
|
+
agentprdiff record suite.py # records baselines for every case
|
|
12
|
+
agentprdiff check suite.py # re-runs every case, diffs against the baseline
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The first `check` after `record` should exit 0 with all cases passing. Now
|
|
16
|
+
edit `agent.py` — change the refund wording or remove the `lookup_order`
|
|
17
|
+
tool call for a particular input — and re-run `agentprdiff check`. You'll see
|
|
18
|
+
the specific grader that now fails, a cost and latency delta, and a unified
|
|
19
|
+
diff of the output.
|
|
20
|
+
|
|
21
|
+
That's the whole loop. Swap `support_agent` for whatever production agent
|
|
22
|
+
you want to guard.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "agentprdiff"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Guard your LLM agents in CI. Snapshot tests that catch behavioral regressions when models, prompts, or vendors change."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Vinoth Nageshwaran", email = "vnageshwaran@gmail.com" }]
|
|
13
|
+
keywords = [
|
|
14
|
+
"llm",
|
|
15
|
+
"agent",
|
|
16
|
+
"evaluation",
|
|
17
|
+
"regression-testing",
|
|
18
|
+
"snapshot-testing",
|
|
19
|
+
"ci",
|
|
20
|
+
"observability",
|
|
21
|
+
]
|
|
22
|
+
classifiers = [
|
|
23
|
+
"Development Status :: 3 - Alpha",
|
|
24
|
+
"Intended Audience :: Developers",
|
|
25
|
+
"License :: OSI Approved :: MIT License",
|
|
26
|
+
"Programming Language :: Python :: 3",
|
|
27
|
+
"Programming Language :: Python :: 3.10",
|
|
28
|
+
"Programming Language :: Python :: 3.11",
|
|
29
|
+
"Programming Language :: Python :: 3.12",
|
|
30
|
+
"Topic :: Software Development :: Testing",
|
|
31
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
32
|
+
]
|
|
33
|
+
dependencies = [
|
|
34
|
+
"click>=8.1",
|
|
35
|
+
"rich>=13.0",
|
|
36
|
+
"pydantic>=2.0",
|
|
37
|
+
"pyyaml>=6.0",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[project.optional-dependencies]
|
|
41
|
+
openai = ["openai>=1.0"]
|
|
42
|
+
anthropic = ["anthropic>=0.30"]
|
|
43
|
+
dev = [
|
|
44
|
+
"pytest>=7.4",
|
|
45
|
+
"pytest-cov>=4.1",
|
|
46
|
+
"ruff>=0.5",
|
|
47
|
+
"mypy>=1.8",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
[project.urls]
|
|
51
|
+
Homepage = "https://github.com/vnageshwaran-de/agentprdiff"
|
|
52
|
+
Documentation = "https://github.com/vnageshwaran-de/agentprdiff#readme"
|
|
53
|
+
Issues = "https://github.com/vnageshwaran-de/agentprdiff/issues"
|
|
54
|
+
Repository = "https://github.com/vnageshwaran-de/agentprdiff"
|
|
55
|
+
|
|
56
|
+
[project.scripts]
|
|
57
|
+
agentprdiff = "agentprdiff.cli:main"
|
|
58
|
+
|
|
59
|
+
[tool.hatch.build.targets.wheel]
|
|
60
|
+
packages = ["src/agentprdiff"]
|
|
61
|
+
|
|
62
|
+
[tool.hatch.build.targets.sdist]
|
|
63
|
+
include = ["src/agentprdiff", "README.md", "LICENSE", "CHANGELOG.md"]
|
|
64
|
+
|
|
65
|
+
[tool.pytest.ini_options]
|
|
66
|
+
testpaths = ["tests"]
|
|
67
|
+
addopts = "-ra -q"
|
|
68
|
+
|
|
69
|
+
[tool.ruff]
|
|
70
|
+
line-length = 100
|
|
71
|
+
target-version = "py310"
|
|
72
|
+
|
|
73
|
+
[tool.ruff.lint]
|
|
74
|
+
select = ["E", "F", "W", "I", "UP", "B", "SIM"]
|
|
75
|
+
ignore = ["E501"]
|
|
76
|
+
|
|
77
|
+
[tool.mypy]
|
|
78
|
+
python_version = "3.10"
|
|
79
|
+
strict = false
|
|
80
|
+
ignore_missing_imports = true
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""agentprdiff — snapshot testing for LLM agents.
|
|
2
|
+
|
|
3
|
+
The one-happy-path public API:
|
|
4
|
+
|
|
5
|
+
from agentprdiff import suite, case
|
|
6
|
+
from agentprdiff.graders import contains, tool_called, latency_lt_ms, semantic
|
|
7
|
+
|
|
8
|
+
def my_agent(query: str) -> str:
|
|
9
|
+
...
|
|
10
|
+
|
|
11
|
+
billing_suite = suite(
|
|
12
|
+
name="billing",
|
|
13
|
+
agent=my_agent,
|
|
14
|
+
cases=[
|
|
15
|
+
case(
|
|
16
|
+
name="refund_happy_path",
|
|
17
|
+
input="I want a refund for order #1234",
|
|
18
|
+
expect=[
|
|
19
|
+
contains("refund"),
|
|
20
|
+
tool_called("lookup_order"),
|
|
21
|
+
semantic("agent acknowledges the refund and provides next steps"),
|
|
22
|
+
latency_lt_ms(10_000),
|
|
23
|
+
],
|
|
24
|
+
),
|
|
25
|
+
],
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
Run from the shell::
|
|
29
|
+
|
|
30
|
+
agentprdiff init
|
|
31
|
+
agentprdiff record path/to/my_suite.py # save baselines
|
|
32
|
+
agentprdiff check path/to/my_suite.py # diff against baselines; exit 1 on regression
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
from .core import (
|
|
38
|
+
AgentFn,
|
|
39
|
+
Case,
|
|
40
|
+
Grader,
|
|
41
|
+
GradeResult,
|
|
42
|
+
LLMCall,
|
|
43
|
+
Suite,
|
|
44
|
+
ToolCall,
|
|
45
|
+
Trace,
|
|
46
|
+
case,
|
|
47
|
+
run_agent,
|
|
48
|
+
suite,
|
|
49
|
+
)
|
|
50
|
+
from .differ import AssertionChange, TraceDelta, diff_traces
|
|
51
|
+
from .runner import CaseReport, Runner, RunReport
|
|
52
|
+
from .store import BaselineStore
|
|
53
|
+
|
|
54
|
+
__version__ = "0.1.0"
|
|
55
|
+
|
|
56
|
+
__all__ = [
|
|
57
|
+
# core
|
|
58
|
+
"Suite",
|
|
59
|
+
"Case",
|
|
60
|
+
"Trace",
|
|
61
|
+
"LLMCall",
|
|
62
|
+
"ToolCall",
|
|
63
|
+
"Grader",
|
|
64
|
+
"GradeResult",
|
|
65
|
+
"AgentFn",
|
|
66
|
+
"suite",
|
|
67
|
+
"case",
|
|
68
|
+
"run_agent",
|
|
69
|
+
# diffing
|
|
70
|
+
"TraceDelta",
|
|
71
|
+
"AssertionChange",
|
|
72
|
+
"diff_traces",
|
|
73
|
+
# runner
|
|
74
|
+
"Runner",
|
|
75
|
+
"RunReport",
|
|
76
|
+
"CaseReport",
|
|
77
|
+
# storage
|
|
78
|
+
"BaselineStore",
|
|
79
|
+
# version
|
|
80
|
+
"__version__",
|
|
81
|
+
]
|