-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpyproject.toml
More file actions
302 lines (286 loc) · 12 KB
/
pyproject.toml
File metadata and controls
302 lines (286 loc) · 12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
[build-system]
requires = ["maturin>=1.13,<2.0"]
build-backend = "maturin"
[project]
# PyPI distribution name. The generic "shadow" was already taken on
# PyPI (an unrelated btrfs snapshot utility), so the distribution is
# published as "shadow-diff". The Python import path stays `shadow`,
# the CLI stays `shadow`, and the repo stays Shadow — only the
# `pip install` name is different.
name = "shadow-diff"
version = "3.1.0"
description = "Behavior contracts for AI agents — tested in your PR, enforced at runtime."
license = "Apache-2.0"
# Pin the license-files list explicitly so maturin's auto-detection
# doesn't propose files (LICENSE / LICENSE-MIT / etc.) that aren't
# packaged into the wheel. PyPI's PEP 639 validator (strict since
# 2025) rejects any wheel whose METADATA declares a License-File
# entry that the wheel itself doesn't contain.
license-files = ["LICENSE-APACHE"]
readme = "README.md"
requires-python = ">=3.11"
authors = [{ name = "manav8498" }]
keywords = [
"llm",
"agents",
"testing",
"observability",
"diff",
"regression-testing",
"causal-attribution",
]
classifiers = [
"Development Status :: 4 - Beta",
"License :: OSI Approved :: Apache Software License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Rust",
"Topic :: Software Development :: Libraries",
"Topic :: Software Development :: Testing",
"Topic :: Software Development :: Quality Assurance",
"Topic :: Scientific/Engineering :: Artificial Intelligence",
"Operating System :: POSIX :: Linux",
"Operating System :: MacOS",
"Operating System :: Microsoft :: Windows",
"Typing :: Typed",
]
dependencies = [
# Runtime deps — permissive ranges so shadow co-installs cleanly with
# whatever version the user's project already has. Exact pins here used
# to force conflicts in any non-trivial environment. Lower bounds are
# the oldest we've verified against; upper bounds cap the next major.
"typer>=0.15,<1.0",
"pydantic>=2.10,<3",
"httpx>=0.27,<1",
"rich>=13.9,<15",
"scikit-learn>=1.6,<2",
"numpy>=2.2,<3",
"pyyaml>=6.0,<7",
# Used by the must_match_json_schema policy rule to validate
# structured outputs and by `shadow certify` for the certificate
# schema. Pulled by several optional extras transitively already
# (anthropic, openai, otel) — pinning it as a direct dep makes the
# core install path deterministic.
"jsonschema>=4.0,<5",
]
[project.urls]
Homepage = "https://github.com/manav8498/Shadow"
Documentation = "https://github.com/manav8498/Shadow#readme"
Repository = "https://github.com/manav8498/Shadow"
Issues = "https://github.com/manav8498/Shadow/issues"
Changelog = "https://github.com/manav8498/Shadow/blob/main/CHANGELOG.md"
Discussions = "https://github.com/manav8498/Shadow/discussions"
"Source Code" = "https://github.com/manav8498/Shadow"
Specification = "https://github.com/manav8498/Shadow/blob/main/SPEC.md"
[project.optional-dependencies]
# Provider SDKs. Upper bounds are the next major above the latest
# version we run in CI (anthropic 0.40.x, openai 2.x); the auto-
# instrument layer in `shadow.sdk` patches `.create` on `Messages` /
# `AsyncMessages` / `Completions` / `AsyncCompletions` / `Responses`
# / `AsyncResponses`, and a major-version bump in either SDK is the
# point at which those class paths or signatures can move and our
# patches silently break. Users on a new major can lift the pin in
# their own pyproject and report breakage.
anthropic = ["anthropic>=0.40,<1"]
openai = ["openai>=1.58,<3"]
embeddings = ["sentence-transformers>=3.3,<6"]
otel = ["opentelemetry-sdk>=1.27,<2"]
serve = [
"fastapi>=0.115,<1",
"uvicorn>=0.32,<1",
"websockets>=13.1,<16",
]
mcp = ["mcp>=0.9,<2"]
# Optional perceptual-hashing dependency for the v0.2 blob_ref record
# kind. shadow.v02_records.compute_phash_dhash64 quietly returns None
# when these aren't installed, so the dep is genuinely optional — the
# extra is here so users opting in to multimodal traces have a single
# `pip install shadow-diff[multimodal]` instead of two manual installs.
multimodal = ["Pillow>=10,<13", "imagehash>=4.3,<5"]
# Cosign / sigstore signing for Agent Behavior Certificates. Enables
# `shadow certify --sign` and `shadow verify-cert --verify-signature`.
# Optional because sigstore pulls a non-trivial transitive tree
# (cryptography, securesystemslib, tuf, rfc3161-client) that most
# users running Shadow in tests don't need.
sign = ["sigstore>=3.0,<5"]
# Framework adapters. Each pulls in the minimum surface needed for the
# matching shadow.adapters.<framework> module. LangGraph's extra
# additionally brings `langchain-openai` since that's what ~80% of
# LangGraph users pick as their chat provider; users on Anthropic,
# Bedrock, etc. can layer in `langchain-anthropic` / `langchain-aws`
# alongside without conflicts — the adapter itself is provider-neutral.
langgraph = [
# langchain-core upper bound was `<1` historically. Current
# langgraph 1.x and langchain-openai 1.x both pull in
# langchain-core>=1, so the old cap broke fresh installs of
# `shadow-diff[langgraph]`. Lifted to `<3` to track both 1.x
# and an eventual 2.x without thrashing.
"langchain-core>=0.3,<3",
"langgraph>=1.0.2,<2",
"langchain-openai>=0.3,<2",
]
crewai = ["crewai>=1.14,<2"]
ag2 = ["ag2>=0.9,<2"]
# Convenience meta-extra: install every optional integration. Useful for
# 'try the full feature surface' but pulls in heavy ML deps you may not
# need (sentence-transformers ~2GB on first download). For production
# installs, pick the specific extras you actually use.
all = [
"anthropic>=0.40,<1",
"openai>=1.58,<3",
"sentence-transformers>=3.3,<6",
"opentelemetry-sdk>=1.27,<2",
"fastapi>=0.115,<1",
"uvicorn>=0.32,<1",
"websockets>=13.1,<16",
"mcp>=0.9,<2",
"Pillow>=10,<13",
"imagehash>=4.3,<5",
"sigstore>=3.0,<5",
"langgraph>=1.0.2,<2",
"langchain-openai>=0.3,<2",
"crewai>=1.14,<2",
"ag2>=0.9,<2",
]
dev = [
"hypothesis==6.122.1",
"mypy==1.14.0",
"ruff==0.8.4",
# pytest 8.4.2 has CVE-2025-71176; the fix is in 9.0.3. The
# plugin chain has to bump alongside it: pytest-asyncio 0.25.x
# caps at pytest<9, and pytest-cov 6.x is similarly old-bound.
# Bumped together to versions that all advertise pytest<10
# support so the dev environment stays internally consistent.
"pytest==9.0.3",
"pytest-asyncio==1.3.0",
"pytest-cov==7.1.0",
"maturin==1.13.1",
"types-PyYAML==6.0.12.20240917",
]
[project.scripts]
shadow = "shadow.cli.app:main"
[tool.maturin]
manifest-path = "../crates/shadow-core/Cargo.toml"
module-name = "shadow._core"
# `extension` enables PyO3-facing code; `pyo3/extension-module` tells
# PyO3 to omit libpython link directives so the wheel cdylib loads
# cleanly into a Python interpreter at import time. The
# `pyo3/extension-module` flag is set HERE rather than as part of the
# local `extension` feature on shadow-core so that
# `cargo test --workspace --all-features` keeps linking on every
# platform — the test binary needs libpython linked normally, the
# wheel doesn't.
features = ["extension", "pyo3/extension-module"]
python-source = "src"
# The project's `license-files` field above tells maturin which
# license files to package into both the sdist and wheel. We don't
# need an explicit `include` rule for license files anymore —
# maturin honours `license-files` for both formats. Keep this block
# empty so future include rules (test fixtures, etc.) have a clear
# home.
[tool.ruff]
line-length = 100
target-version = "py311"
src = ["src"]
[tool.ruff.lint]
select = ["E", "F", "W", "I", "N", "UP", "B", "A", "C4", "PT", "SIM", "RUF"]
ignore = []
[tool.ruff.lint.per-file-ignores]
"tests/**" = ["B", "PT"]
# Statistical and LTL modules use math notation (Greek letters, Unicode
# operators) in docstrings and comments — suppress ambiguous-character
# warnings that are false positives for domain-specific mathematical prose.
"src/shadow/statistical/**" = ["RUF001", "RUF002", "RUF003"]
"src/shadow/ltl/**" = ["RUF001", "RUF002", "RUF003"]
"src/shadow/conformal.py" = ["RUF001", "RUF002", "RUF003"]
"src/shadow/report/github_pr.py" = ["RUF001"]
"src/shadow/causal/**" = ["RUF001", "RUF002", "RUF003"]
"tests/test_statistical_validation.py" = ["RUF001", "RUF002", "RUF003"]
"tests/test_refund_audit_scenario.py" = ["RUF001", "RUF002", "RUF003"]
"tests/test_production_e2e.py" = ["RUF001", "RUF002", "RUF003"]
"tests/test_ltl.py" = ["RUF001", "RUF002", "RUF003"]
"tests/test_statistical.py" = ["RUF001", "RUF002", "RUF003"]
"tests/test_conformal.py" = ["RUF001", "RUF002", "RUF003"]
"tests/test_canary_monitor.py" = ["RUF001", "RUF002", "RUF003"]
"tests/test_statistical_edge_cases.py" = ["RUF001", "RUF002", "RUF003"]
"tests/test_statistical_vs_reference.py" = ["RUF001", "RUF002", "RUF003"]
"tests/test_diff_scenarios.py" = ["RUF001", "RUF002", "RUF003"]
[tool.mypy]
strict = true
python_version = "3.11"
files = ["src/shadow"]
# Modules that depend on optional extras (fastapi / starlette / uvicorn /
# sigstore / mcp / langgraph / crewai / ag2) not installed in the default
# CI venv. When a user opts into those extras, mypy can check these too —
# but the default install shouldn't fail on missing stubs for deps the
# user explicitly didn't install. The [sign] extra additionally requires
# --prerelease=allow at install time (sigstore's dependencies pull
# pre-release wheels), which the default CI doesn't pass; certify_sign
# goes here for the same reason.
#
# G-1..G-6 from technical-debt plan: removing modules from this list is
# blocked on those modules' optional deps. The work to un-exclude them
# is to either:
# (a) make the default CI install pull all extras (rejected — slow,
# and pulls heavy ML deps that aren't actually needed for the
# core test loop), or
# (b) introduce a separate mypy config file used by an extras-only
# CI job that lifts these overrides (deferred — needs a second
# config file maintained alongside pyproject.toml).
# Until one of (a) or (b) lands, the override is correct. Type errors
# in these modules are caught only at install time by users who pip
# install the relevant extra.
[[tool.mypy.overrides]]
module = [
"shadow.enterprise.*",
"shadow.serve.*",
"shadow.mcp_server",
"shadow.adapters.*",
"shadow.tools.sandbox",
"shadow.certify_sign",
"shadow.diagnose_pr.dashboard",
]
ignore_errors = true
# `jsonschema` ships its own type info but mypy --strict still flags
# library stubs as missing on first import (the package's stub layout
# doesn't survive PEP 561 detection in some setups). Suppress the
# unhelpful "install types-jsonschema" hint — we use jsonschema's
# runtime API only and do not depend on its types in our public surface.
[[tool.mypy.overrides]]
module = ["jsonschema", "jsonschema.*"]
ignore_missing_imports = true
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-ra --strict-markers"
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "function"
markers = [
"network: tests that hit real LLM APIs (gated by SHADOW_RUN_NETWORK_TESTS=1)",
"slow: statistical-property simulations (run with `pytest -m slow`)",
]
[tool.coverage.run]
# Exclude modules whose tests require optional extras not installed in the
# default dev venv (pytest skips them, but their source would otherwise count
# against the denominator and push coverage below the 85% gate). Users who
# install the relevant extra and run pytest themselves still get coverage
# on these paths.
omit = [
"*/shadow/enterprise/*",
"*/shadow/serve/*",
"*/shadow/embeddings/*",
"*/shadow/otel.py",
"*/shadow/llm/anthropic_backend.py",
"*/shadow/llm/openai_backend.py",
"*/shadow/importers/otel_json.py",
"*/shadow/adapters/*",
"*/shadow/mcp_server.py",
]
[tool.coverage.report]
# Same exclusions apply to the final report for consistency.
exclude_also = [
"raise NotImplementedError",
"if TYPE_CHECKING:",
"@(abc\\.)?abstractmethod",
]