agentprdiff 0.2.5__tar.gz → 0.3.1__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.2.5 → agentprdiff-0.3.1}/.gitignore +4 -0
- {agentprdiff-0.2.5 → agentprdiff-0.3.1}/CHANGELOG.md +57 -0
- {agentprdiff-0.2.5 → agentprdiff-0.3.1}/PKG-INFO +2 -2
- {agentprdiff-0.2.5 → agentprdiff-0.3.1}/README.md +1 -1
- {agentprdiff-0.2.5 → agentprdiff-0.3.1}/pyproject.toml +1 -1
- {agentprdiff-0.2.5 → agentprdiff-0.3.1}/src/agentprdiff/__init__.py +11 -1
- agentprdiff-0.3.1/src/agentprdiff/adapters/__init__.py +86 -0
- {agentprdiff-0.2.5 → agentprdiff-0.3.1}/src/agentprdiff/adapters/anthropic.py +9 -0
- {agentprdiff-0.2.5 → agentprdiff-0.3.1}/src/agentprdiff/adapters/openai.py +26 -0
- agentprdiff-0.3.1/studio/README.md +108 -0
- agentprdiff-0.3.1/studio/backend/README.md +34 -0
- agentprdiff-0.2.5/src/agentprdiff/adapters/__init__.py +0 -43
- {agentprdiff-0.2.5 → agentprdiff-0.3.1}/LICENSE +0 -0
- {agentprdiff-0.2.5 → agentprdiff-0.3.1}/examples/quickstart/README.md +0 -0
- {agentprdiff-0.2.5 → agentprdiff-0.3.1}/examples/regression-tour/README.md +0 -0
- {agentprdiff-0.2.5 → agentprdiff-0.3.1}/src/agentprdiff/adapters/pricing.py +0 -0
- {agentprdiff-0.2.5 → agentprdiff-0.3.1}/src/agentprdiff/cli.py +0 -0
- {agentprdiff-0.2.5 → agentprdiff-0.3.1}/src/agentprdiff/core.py +0 -0
- {agentprdiff-0.2.5 → agentprdiff-0.3.1}/src/agentprdiff/differ.py +0 -0
- {agentprdiff-0.2.5 → agentprdiff-0.3.1}/src/agentprdiff/filtering.py +0 -0
- {agentprdiff-0.2.5 → agentprdiff-0.3.1}/src/agentprdiff/graders/__init__.py +0 -0
- {agentprdiff-0.2.5 → agentprdiff-0.3.1}/src/agentprdiff/graders/deterministic.py +0 -0
- {agentprdiff-0.2.5 → agentprdiff-0.3.1}/src/agentprdiff/graders/semantic.py +0 -0
- {agentprdiff-0.2.5 → agentprdiff-0.3.1}/src/agentprdiff/loader.py +0 -0
- {agentprdiff-0.2.5 → agentprdiff-0.3.1}/src/agentprdiff/reporters.py +0 -0
- {agentprdiff-0.2.5 → agentprdiff-0.3.1}/src/agentprdiff/runner.py +0 -0
- {agentprdiff-0.2.5 → agentprdiff-0.3.1}/src/agentprdiff/scaffold.py +0 -0
- {agentprdiff-0.2.5 → agentprdiff-0.3.1}/src/agentprdiff/store.py +0 -0
|
@@ -8,6 +8,63 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [0.3.1] — 2026-05-17
|
|
12
|
+
|
|
13
|
+
Patch release. Fixes a long-standing drift between `pyproject.toml`'s
|
|
14
|
+
`version` field and the in-code `__version__` constant: the constant
|
|
15
|
+
in `src/agentprdiff/__init__.py` was hardcoded and got out of sync at
|
|
16
|
+
every release (0.3.0 shipped while still reporting "0.2.5" via
|
|
17
|
+
`agentprdiff.__version__`, even though `pip show` correctly read
|
|
18
|
+
"0.3.0" from the wheel metadata).
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- `agentprdiff.__version__` is now read from the installed package
|
|
23
|
+
metadata via `importlib.metadata.version("agentprdiff")` instead of
|
|
24
|
+
a hardcoded string. The runtime constant matches the wheel metadata
|
|
25
|
+
by construction; no human step can let them drift again. Falls back
|
|
26
|
+
to `"0+unknown"` when running from an uninstalled source checkout.
|
|
27
|
+
|
|
28
|
+
This is purely a metadata fix — no behavior change, no API additions,
|
|
29
|
+
no removed APIs. The `set_default_model()` hook from 0.3.0 is
|
|
30
|
+
unchanged.
|
|
31
|
+
|
|
32
|
+
## [0.3.0] — 2026-05-17
|
|
33
|
+
|
|
34
|
+
Adds a model-override hook on the adapters package. This unblocks
|
|
35
|
+
tooling that wants to run a single suite against multiple models — e.g.
|
|
36
|
+
the Studio's multi-model benchmark — without forcing user-written agent
|
|
37
|
+
code to take a `model` parameter.
|
|
38
|
+
|
|
39
|
+
The change is purely additive: no existing call site has to change, and
|
|
40
|
+
the 155-test suite passes unchanged. The override is module-level and
|
|
41
|
+
process-wide, read at call time inside the patched `create()` function.
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
|
|
45
|
+
- `agentprdiff.adapters.set_default_model(model: str | None) -> None`:
|
|
46
|
+
set a process-wide override that rewrites `kwargs["model"]` on every
|
|
47
|
+
subsequent `instrument_client`-patched `create()` call. Pass `None` to
|
|
48
|
+
clear.
|
|
49
|
+
- `agentprdiff.adapters.get_default_model() -> str | None`: read the
|
|
50
|
+
current override.
|
|
51
|
+
- The OpenAI adapter's sync + async patched-create paths and the
|
|
52
|
+
Anthropic adapter's patched-create now consult the override at call
|
|
53
|
+
time and rewrite kwargs before delegating to the SDK. No-op when no
|
|
54
|
+
override is active.
|
|
55
|
+
|
|
56
|
+
### Notes
|
|
57
|
+
|
|
58
|
+
- The override is process-wide. Sequencing `set_default_model("A"); run();
|
|
59
|
+
set_default_model("B"); run(); set_default_model(None)` works as
|
|
60
|
+
expected. Concurrent runs in the same process share the override —
|
|
61
|
+
isolate per-leg benchmarks in fresh subprocesses if you need
|
|
62
|
+
concurrency.
|
|
63
|
+
- Only rewrites when the caller actually passed a `model=` kwarg, so the
|
|
64
|
+
SDK's "no model provided" error path still surfaces correctly.
|
|
65
|
+
- The override is exported from `agentprdiff.adapters` so callers don't
|
|
66
|
+
need to reach into submodules.
|
|
67
|
+
|
|
11
68
|
## [0.2.5] — 2026-04-30
|
|
12
69
|
|
|
13
70
|
Infrastructure-only release. Code is identical to 0.2.4 — this exists
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentprdiff
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Guard your LLM agents in CI. Snapshot tests that catch behavioral regressions when models, prompts, or vendors change.
|
|
5
5
|
Project-URL: Homepage, https://agentprdiff.dev
|
|
6
6
|
Project-URL: Documentation, https://agentprdiff.dev
|
|
@@ -42,7 +42,7 @@ Description-Content-Type: text/markdown
|
|
|
42
42
|
|
|
43
43
|
**Guard your LLM agents in CI.** Snapshot tests that catch behavioral regressions when models, prompts, or vendors change.
|
|
44
44
|
|
|
45
|
-
📚 **[Documentation: agentprdiff.dev](https://agentprdiff.dev/)** · ⚡ [Quickstart](https://agentprdiff.dev/quickstart/) · 🤖 [AI-agent adoption](https://agentprdiff.dev/quickstart/#path-a
|
|
45
|
+
📚 **[Documentation: agentprdiff.dev](https://agentprdiff.dev/)** · ⚡ [Quickstart](https://agentprdiff.dev/quickstart/) · 🤖 [AI-agent adoption](https://agentprdiff.dev/quickstart/#path-a) · 📦 [PyPI](https://pypi.org/project/agentprdiff/)
|
|
46
46
|
|
|
47
47
|
> 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.
|
|
48
48
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Guard your LLM agents in CI.** Snapshot tests that catch behavioral regressions when models, prompts, or vendors change.
|
|
4
4
|
|
|
5
|
-
📚 **[Documentation: agentprdiff.dev](https://agentprdiff.dev/)** · ⚡ [Quickstart](https://agentprdiff.dev/quickstart/) · 🤖 [AI-agent adoption](https://agentprdiff.dev/quickstart/#path-a
|
|
5
|
+
📚 **[Documentation: agentprdiff.dev](https://agentprdiff.dev/)** · ⚡ [Quickstart](https://agentprdiff.dev/quickstart/) · 🤖 [AI-agent adoption](https://agentprdiff.dev/quickstart/#path-a) · 📦 [PyPI](https://pypi.org/project/agentprdiff/)
|
|
6
6
|
|
|
7
7
|
> 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.
|
|
8
8
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "agentprdiff"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.1"
|
|
8
8
|
description = "Guard your LLM agents in CI. Snapshot tests that catch behavioral regressions when models, prompts, or vendors change."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -50,6 +50,8 @@ See ``docs/adapters.md`` for the full reference.
|
|
|
50
50
|
|
|
51
51
|
from __future__ import annotations
|
|
52
52
|
|
|
53
|
+
from importlib.metadata import PackageNotFoundError, version as _pkg_version
|
|
54
|
+
|
|
53
55
|
from .core import (
|
|
54
56
|
AgentFn,
|
|
55
57
|
Case,
|
|
@@ -67,7 +69,15 @@ from .differ import AssertionChange, TraceDelta, diff_traces
|
|
|
67
69
|
from .runner import CaseReport, Runner, RunReport
|
|
68
70
|
from .store import BaselineStore
|
|
69
71
|
|
|
70
|
-
|
|
72
|
+
# Source of truth is pyproject.toml's [project] version. Reading it here
|
|
73
|
+
# means the runtime constant can never drift from the wheel metadata; the
|
|
74
|
+
# fallback covers the "running from a source checkout that wasn't pip
|
|
75
|
+
# installed" case (e.g. `python -m pytest` on a fresh clone before
|
|
76
|
+
# `pip install -e .`).
|
|
77
|
+
try:
|
|
78
|
+
__version__ = _pkg_version("agentprdiff")
|
|
79
|
+
except PackageNotFoundError: # pragma: no cover — only hit in source-tree runs
|
|
80
|
+
__version__ = "0+unknown"
|
|
71
81
|
|
|
72
82
|
__all__ = [
|
|
73
83
|
# core
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""SDK adapters for agentprdiff.
|
|
2
|
+
|
|
3
|
+
The adapters take an agent that uses a real LLM SDK (OpenAI, Anthropic, or any
|
|
4
|
+
OpenAI-compatible provider like Groq, Gemini's openai-compat endpoint,
|
|
5
|
+
OpenRouter, Ollama, or vLLM) and capture every model call as an `LLMCall` on a
|
|
6
|
+
`Trace` — without forcing the user to rewrite their agent loop.
|
|
7
|
+
|
|
8
|
+
The pattern is::
|
|
9
|
+
|
|
10
|
+
from agentprdiff.adapters.openai import instrument_client, instrument_tools
|
|
11
|
+
|
|
12
|
+
def my_agent(query: str):
|
|
13
|
+
client = OpenAI(...)
|
|
14
|
+
with instrument_client(client) as trace:
|
|
15
|
+
tools = instrument_tools(TOOL_MAP, trace)
|
|
16
|
+
# ... user's existing tool-calling loop, untouched ...
|
|
17
|
+
return final_text, trace
|
|
18
|
+
|
|
19
|
+
Submodules are imported lazily so the base `agentprdiff` install doesn't pull
|
|
20
|
+
in `openai` / `anthropic` unless the user opts in via the extras::
|
|
21
|
+
|
|
22
|
+
pip install "agentprdiff[openai]"
|
|
23
|
+
pip install "agentprdiff[anthropic]"
|
|
24
|
+
|
|
25
|
+
See `docs/adapters.md` for the full reference.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
# Re-export pricing helpers — these are pure-Python and have no SDK dependency.
|
|
31
|
+
from .pricing import (
|
|
32
|
+
DEFAULT_PRICES,
|
|
33
|
+
PriceTable,
|
|
34
|
+
estimate_cost_usd,
|
|
35
|
+
register_prices,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
# Optional global model override.
|
|
40
|
+
#
|
|
41
|
+
# When set via ``set_default_model("gpt-4o-mini")``, every subsequent
|
|
42
|
+
# ``instrument_client``-patched ``create()`` call rewrites the ``model``
|
|
43
|
+
# keyword argument before delegating to the underlying SDK. Pass ``None``
|
|
44
|
+
# to clear.
|
|
45
|
+
#
|
|
46
|
+
# Module-level + process-wide on purpose — this is a knob for tooling
|
|
47
|
+
# (Studio's multi-model benchmark, or anyone doing batch comparisons
|
|
48
|
+
# from a notebook), not for production agent code. Production agents
|
|
49
|
+
# should keep passing ``model=`` explicitly.
|
|
50
|
+
#
|
|
51
|
+
# Reads happen at call time (the patched_create looks up the current
|
|
52
|
+
# value each invocation), so a single-threaded sequence of:
|
|
53
|
+
#
|
|
54
|
+
# set_default_model("gpt-4o-mini"); run_suite(); \
|
|
55
|
+
# set_default_model("claude-haiku-4-5"); run_suite(); \
|
|
56
|
+
# set_default_model(None)
|
|
57
|
+
#
|
|
58
|
+
# does exactly what you'd expect. Concurrent runs in the same process
|
|
59
|
+
# share the override — if you need per-task isolation, run each in a
|
|
60
|
+
# fresh subprocess.
|
|
61
|
+
|
|
62
|
+
_DEFAULT_MODEL_OVERRIDE: str | None = None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def set_default_model(model: str | None) -> None:
|
|
66
|
+
"""Override the model on every subsequent patched ``create()`` call.
|
|
67
|
+
|
|
68
|
+
Pass ``None`` to clear. See module docstring for the semantics.
|
|
69
|
+
"""
|
|
70
|
+
global _DEFAULT_MODEL_OVERRIDE
|
|
71
|
+
_DEFAULT_MODEL_OVERRIDE = model
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def get_default_model() -> str | None:
|
|
75
|
+
"""Read the current model override (``None`` if not set)."""
|
|
76
|
+
return _DEFAULT_MODEL_OVERRIDE
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
__all__ = [
|
|
80
|
+
"DEFAULT_PRICES",
|
|
81
|
+
"PriceTable",
|
|
82
|
+
"estimate_cost_usd",
|
|
83
|
+
"register_prices",
|
|
84
|
+
"set_default_model",
|
|
85
|
+
"get_default_model",
|
|
86
|
+
]
|
|
@@ -106,6 +106,15 @@ def instrument_client(
|
|
|
106
106
|
instance_attr_value = vars(messages_attr).get("create")
|
|
107
107
|
|
|
108
108
|
def patched_create(*args: Any, **kwargs: Any) -> Any:
|
|
109
|
+
# Honor the global model override if set — same contract as the
|
|
110
|
+
# OpenAI adapter. Lookup happens at call time so the value reflects
|
|
111
|
+
# whatever set_default_model() last set.
|
|
112
|
+
from . import get_default_model
|
|
113
|
+
|
|
114
|
+
override = get_default_model()
|
|
115
|
+
if override is not None and "model" in kwargs:
|
|
116
|
+
kwargs = dict(kwargs)
|
|
117
|
+
kwargs["model"] = override
|
|
109
118
|
start = time.perf_counter()
|
|
110
119
|
try:
|
|
111
120
|
response = original_create(*args, **kwargs)
|
|
@@ -311,6 +311,30 @@ def _record_failure(
|
|
|
311
311
|
)
|
|
312
312
|
|
|
313
313
|
|
|
314
|
+
def _apply_model_override(kwargs: dict[str, Any]) -> dict[str, Any]:
|
|
315
|
+
"""If a global model override is set, return a new kwargs dict with
|
|
316
|
+
``model`` rewritten. Returns the original dict when no override is active.
|
|
317
|
+
|
|
318
|
+
The lookup happens at call time so multi-leg benchmarks can flip the
|
|
319
|
+
override between runs in the same process.
|
|
320
|
+
"""
|
|
321
|
+
# Import at call time to avoid an import cycle (adapters/__init__.py
|
|
322
|
+
# itself imports from .pricing, which is fine; this module imports from
|
|
323
|
+
# adapters at package level but we want the *live* value).
|
|
324
|
+
from . import get_default_model
|
|
325
|
+
|
|
326
|
+
override = get_default_model()
|
|
327
|
+
if override is None:
|
|
328
|
+
return kwargs
|
|
329
|
+
# Only rewrite if the caller actually passed a model — preserves the
|
|
330
|
+
# SDK's own error path when model is missing.
|
|
331
|
+
if "model" not in kwargs:
|
|
332
|
+
return kwargs
|
|
333
|
+
new_kwargs = dict(kwargs)
|
|
334
|
+
new_kwargs["model"] = override
|
|
335
|
+
return new_kwargs
|
|
336
|
+
|
|
337
|
+
|
|
314
338
|
def _make_sync_patched_create(
|
|
315
339
|
original_create: Callable[..., Any],
|
|
316
340
|
*,
|
|
@@ -319,6 +343,7 @@ def _make_sync_patched_create(
|
|
|
319
343
|
prices: PriceTable | None,
|
|
320
344
|
) -> Callable[..., Any]:
|
|
321
345
|
def patched_create(*args: Any, **kwargs: Any) -> Any:
|
|
346
|
+
kwargs = _apply_model_override(kwargs)
|
|
322
347
|
start = time.perf_counter()
|
|
323
348
|
try:
|
|
324
349
|
response = original_create(*args, **kwargs)
|
|
@@ -350,6 +375,7 @@ def _make_async_patched_create(
|
|
|
350
375
|
prices: PriceTable | None,
|
|
351
376
|
) -> Callable[..., Awaitable[Any]]:
|
|
352
377
|
async def patched_create(*args: Any, **kwargs: Any) -> Any:
|
|
378
|
+
kwargs = _apply_model_override(kwargs)
|
|
353
379
|
start = time.perf_counter()
|
|
354
380
|
try:
|
|
355
381
|
response = await original_create(*args, **kwargs)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# agentprdiff Studio
|
|
2
|
+
|
|
3
|
+
Web UI on top of the [agentprdiff](https://github.com/vnageshwaran-de/agentprdiff) engine.
|
|
4
|
+
|
|
5
|
+
The CLI (`pip install agentprdiff`, write `suite.py`, `agentprdiff check`) still works exactly the same. Studio is a parallel surface for non-dev users (PMs, QA, vibecoders) who want to trigger runs, watch progress live, diff cases, and approve baselines in a browser.
|
|
6
|
+
|
|
7
|
+
## Quick start (Docker)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# SQLite, single container, zero config:
|
|
11
|
+
docker compose up --build
|
|
12
|
+
|
|
13
|
+
# Open the UI:
|
|
14
|
+
open http://localhost:8080
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
That spins up one container, persists state to a `studio-data` volume, and serves both the API (under `/api/*`) and the SPA on port 8080.
|
|
18
|
+
|
|
19
|
+
For shared deployments swap to Postgres:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
docker compose --profile postgres up --build
|
|
23
|
+
# also export STUDIO_DATABASE_URL=postgresql+asyncpg://studio:studio@db:5432/studio
|
|
24
|
+
# (or set it in studio.environment in compose.yml).
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Run modes
|
|
28
|
+
|
|
29
|
+
| Mode | Storage | Best for |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| Default (SQLite) | `studio-data` volume → `/data/studio.db` | Solo / small team, single host |
|
|
32
|
+
| `--profile postgres` | Postgres container + `studio-pg` volume | Multiple users, durable history |
|
|
33
|
+
|
|
34
|
+
Plus the orthogonal **intake modes** for projects you create inside Studio:
|
|
35
|
+
|
|
36
|
+
* **git** — clone a remote repo, walk it for suites, run via subprocess+venv.
|
|
37
|
+
* **zip** — upload an archive, same execution path as git.
|
|
38
|
+
* **http** — point Studio at a deployed endpoint, author suites as JSON, runs go in-process via httpx (no Python execution, baselines stored in the DB).
|
|
39
|
+
|
|
40
|
+
## Local development (without Docker)
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Backend (Python).
|
|
44
|
+
pip install -e . # the engine, from the repo root
|
|
45
|
+
pip install -e studio/backend
|
|
46
|
+
uvicorn agentprdiff_studio.main:app --reload --port 8080
|
|
47
|
+
|
|
48
|
+
# Frontend (separate terminal).
|
|
49
|
+
cd studio/frontend
|
|
50
|
+
npm install
|
|
51
|
+
npm run dev # Vite on :5173, proxies /api → :8080
|
|
52
|
+
open http://localhost:5173
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
In dev the frontend lives on Vite. In Docker / prod the FastAPI app serves the built `dist/` on the same port as the API.
|
|
56
|
+
|
|
57
|
+
## Configuration
|
|
58
|
+
|
|
59
|
+
Everything is env-driven. The most useful knobs:
|
|
60
|
+
|
|
61
|
+
| Env var | Default | Notes |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| `STUDIO_DATA_DIR` | `./.studio-data` | DB + cloned repos + uploaded zips |
|
|
64
|
+
| `STUDIO_DATABASE_URL` | `sqlite+aiosqlite:///<data>/studio.db` | Set to `postgresql+asyncpg://…` for Postgres |
|
|
65
|
+
| `STUDIO_FRONTEND_DIR` | unset (dev) / `/opt/studio/frontend` (Docker) | If set + exists, FastAPI serves the SPA there |
|
|
66
|
+
| `STUDIO_SECRET_KEY` | generated and persisted to `<data>/.secret_key` | Fernet key for encrypting secrets at rest |
|
|
67
|
+
| `STUDIO_ENGINE_REQ` | `agentprdiff>=0.2.5` | What pip installs into per-project venvs |
|
|
68
|
+
| `STUDIO_RUN_WALLTIME_SECONDS` | `300` | Per-run hard wall-time |
|
|
69
|
+
| `STUDIO_RUN_MEMORY_MB` | `1024` | Per-run memory cap (POSIX rlimit) |
|
|
70
|
+
| `STUDIO_CORS_ORIGINS` | `["*"]` | Pass as JSON array via env if tightening |
|
|
71
|
+
|
|
72
|
+
## What's in the image
|
|
73
|
+
|
|
74
|
+
* Engine (`agentprdiff`) installed from PyPI (or a path, via `STUDIO_ENGINE_REQ`).
|
|
75
|
+
* Studio backend (FastAPI + SQLAlchemy + httpx + GitPython).
|
|
76
|
+
* Built SPA (Vite output) at `/opt/studio/frontend`.
|
|
77
|
+
* `git`, `build-essential`, `tini` for clean signal handling.
|
|
78
|
+
|
|
79
|
+
The image runs as `uvicorn agentprdiff_studio.main:app --host 0.0.0.0 --port 8080` under `tini`. A healthcheck hits `/api/health` every 30s.
|
|
80
|
+
|
|
81
|
+
## Persistence
|
|
82
|
+
|
|
83
|
+
All durable state lives under `/data` inside the container, mounted via the named volume `studio-data`:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
/data
|
|
87
|
+
├── studio.db # SQLite (default mode)
|
|
88
|
+
├── .secret_key # Fernet key (created on first boot)
|
|
89
|
+
└── projects/<id>/
|
|
90
|
+
├── repo/ # git intake
|
|
91
|
+
│ └── .studio-venv/ # per-project venv (provisioned on first run)
|
|
92
|
+
└── upload/ # zip intake
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
For Postgres deployments the DB lives in `studio-pg`; the `studio-data` volume still holds the per-project workspaces and venvs.
|
|
96
|
+
|
|
97
|
+
## Where things land in the repo
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
studio/
|
|
101
|
+
├── Dockerfile multi-stage: node build → python runtime
|
|
102
|
+
├── docker-compose.yml default + postgres profile
|
|
103
|
+
├── .dockerignore keeps the build context small
|
|
104
|
+
├── backend/
|
|
105
|
+
│ └── src/agentprdiff_studio/ FastAPI app + executor + DB layer
|
|
106
|
+
└── frontend/
|
|
107
|
+
└── src/ Vite + React + TS SPA
|
|
108
|
+
```
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# agentprdiff-studio (backend)
|
|
2
|
+
|
|
3
|
+
Web server for **agentprdiff Studio** — a browser UI on top of the [agentprdiff](https://github.com/vnageshwaran-de/agentprdiff) engine.
|
|
4
|
+
|
|
5
|
+
The CLI workflow (`pip install agentprdiff`, write `suite.py`, `agentprdiff check`) is unchanged. Studio is a parallel surface for non-dev users (PMs, QA, vibecoders) who want to trigger runs, review diffs, and approve baselines in a browser.
|
|
6
|
+
|
|
7
|
+
## Status
|
|
8
|
+
|
|
9
|
+
**M1** — backend skeleton + git intake + executor. No UI yet; everything is driven by `curl`.
|
|
10
|
+
|
|
11
|
+
## Run it locally
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
cd studio/backend
|
|
15
|
+
pip install -e ../.. # install the engine from the monorepo
|
|
16
|
+
pip install -e ".[dev]" # install studio backend + dev tools
|
|
17
|
+
uvicorn agentprdiff_studio.main:app --reload --port 8080
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Default config writes to `./.studio-data/` (SQLite + cloned project workspaces). Override with env vars — see `src/agentprdiff_studio/settings.py`.
|
|
21
|
+
|
|
22
|
+
## API (M1)
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
POST /api/projects create + git-clone
|
|
26
|
+
POST /api/projects/{id}/sync re-pull + rediscover suites
|
|
27
|
+
GET /api/projects/{id} detail
|
|
28
|
+
GET /api/projects/{id}/suites list discovered suites
|
|
29
|
+
POST /api/runs {project_id, suite_id, command}
|
|
30
|
+
GET /api/runs/{id} status + case summary
|
|
31
|
+
GET /api/runs/{id}/cases full per-case results
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Zip / HTTP intake, secrets, SSE, and the React UI come in M2–M5.
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
"""SDK adapters for agentprdiff.
|
|
2
|
-
|
|
3
|
-
The adapters take an agent that uses a real LLM SDK (OpenAI, Anthropic, or any
|
|
4
|
-
OpenAI-compatible provider like Groq, Gemini's openai-compat endpoint,
|
|
5
|
-
OpenRouter, Ollama, or vLLM) and capture every model call as an `LLMCall` on a
|
|
6
|
-
`Trace` — without forcing the user to rewrite their agent loop.
|
|
7
|
-
|
|
8
|
-
The pattern is::
|
|
9
|
-
|
|
10
|
-
from agentprdiff.adapters.openai import instrument_client, instrument_tools
|
|
11
|
-
|
|
12
|
-
def my_agent(query: str):
|
|
13
|
-
client = OpenAI(...)
|
|
14
|
-
with instrument_client(client) as trace:
|
|
15
|
-
tools = instrument_tools(TOOL_MAP, trace)
|
|
16
|
-
# ... user's existing tool-calling loop, untouched ...
|
|
17
|
-
return final_text, trace
|
|
18
|
-
|
|
19
|
-
Submodules are imported lazily so the base `agentprdiff` install doesn't pull
|
|
20
|
-
in `openai` / `anthropic` unless the user opts in via the extras::
|
|
21
|
-
|
|
22
|
-
pip install "agentprdiff[openai]"
|
|
23
|
-
pip install "agentprdiff[anthropic]"
|
|
24
|
-
|
|
25
|
-
See `docs/adapters.md` for the full reference.
|
|
26
|
-
"""
|
|
27
|
-
|
|
28
|
-
from __future__ import annotations
|
|
29
|
-
|
|
30
|
-
# Re-export pricing helpers — these are pure-Python and have no SDK dependency.
|
|
31
|
-
from .pricing import (
|
|
32
|
-
DEFAULT_PRICES,
|
|
33
|
-
PriceTable,
|
|
34
|
-
estimate_cost_usd,
|
|
35
|
-
register_prices,
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
__all__ = [
|
|
39
|
-
"DEFAULT_PRICES",
|
|
40
|
-
"PriceTable",
|
|
41
|
-
"estimate_cost_usd",
|
|
42
|
-
"register_prices",
|
|
43
|
-
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|