digline 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.
- digline-0.1.0/.gitignore +40 -0
- digline-0.1.0/CLAUDE.md +126 -0
- digline-0.1.0/LICENSE +201 -0
- digline-0.1.0/PKG-INFO +254 -0
- digline-0.1.0/README.md +226 -0
- digline-0.1.0/docs/adr/0001-verdict-not-score.md +301 -0
- digline-0.1.0/docs/adr/0002-three-worlds-and-where-the-data-lives.md +368 -0
- digline-0.1.0/docs/adr/0003-artifacts-travel-only-when-the-suite-says-so.md +173 -0
- digline-0.1.0/docs/api.md +789 -0
- digline-0.1.0/docs/guide.md +1411 -0
- digline-0.1.0/docs/metrics.md +568 -0
- digline-0.1.0/docs/migrate.md +67 -0
- digline-0.1.0/docs/view.md +86 -0
- digline-0.1.0/examples/quickstart/app.py +50 -0
- digline-0.1.0/examples/quickstart/suite.py +83 -0
- digline-0.1.0/pyproject.toml +106 -0
- digline-0.1.0/src/digline/__init__.py +7 -0
- digline-0.1.0/src/digline/cli/__init__.py +26 -0
- digline-0.1.0/src/digline/cli/__main__.py +5 -0
- digline-0.1.0/src/digline/cli/environment.py +65 -0
- digline-0.1.0/src/digline/cli/loader.py +230 -0
- digline-0.1.0/src/digline/cli/main.py +634 -0
- digline-0.1.0/src/digline/cli/view.py +265 -0
- digline-0.1.0/src/digline/core/__init__.py +169 -0
- digline-0.1.0/src/digline/core/adapters.py +96 -0
- digline-0.1.0/src/digline/core/aggregate.py +354 -0
- digline-0.1.0/src/digline/core/assertions.py +1059 -0
- digline-0.1.0/src/digline/core/compare.py +409 -0
- digline-0.1.0/src/digline/core/pii.py +192 -0
- digline-0.1.0/src/digline/core/protocols.py +109 -0
- digline-0.1.0/src/digline/core/ratio.py +77 -0
- digline-0.1.0/src/digline/core/run.py +564 -0
- digline-0.1.0/src/digline/core/sampling.py +261 -0
- digline-0.1.0/src/digline/core/types.py +432 -0
- digline-0.1.0/src/digline/py.typed +0 -0
- digline-0.1.0/src/digline/report/__init__.py +60 -0
- digline-0.1.0/src/digline/report/history.py +102 -0
- digline-0.1.0/src/digline/report/pages.py +777 -0
- digline-0.1.0/src/digline/report/render.py +780 -0
- digline-0.1.0/src/digline/report/text.py +383 -0
- digline-0.1.0/src/digline/run/__init__.py +28 -0
- digline-0.1.0/src/digline/run/driver.py +284 -0
- digline-0.1.0/src/digline/run/suite.py +225 -0
- digline-0.1.0/src/digline/store/__init__.py +34 -0
- digline-0.1.0/src/digline/store/file_store.py +263 -0
- digline-0.1.0/src/digline/store/migrate.py +193 -0
- digline-0.1.0/src/digline/store/protocol.py +150 -0
- digline-0.1.0/src/digline/targets/__init__.py +26 -0
- digline-0.1.0/src/digline/targets/pricing.py +116 -0
- digline-0.1.0/src/digline/targets/provider.py +149 -0
- digline-0.1.0/src/digline/targets/template.py +115 -0
- digline-0.1.0/tests/_docs.py +188 -0
- digline-0.1.0/tests/_helpers.py +85 -0
- digline-0.1.0/tests/conftest.py +33 -0
- digline-0.1.0/tests/test_adapters.py +76 -0
- digline-0.1.0/tests/test_aggregate.py +498 -0
- digline-0.1.0/tests/test_artifacts.py +515 -0
- digline-0.1.0/tests/test_assertions.py +921 -0
- digline-0.1.0/tests/test_cli.py +1009 -0
- digline-0.1.0/tests/test_compare.py +330 -0
- digline-0.1.0/tests/test_driver.py +312 -0
- digline-0.1.0/tests/test_end_to_end.py +182 -0
- digline-0.1.0/tests/test_examples.py +147 -0
- digline-0.1.0/tests/test_guide.py +96 -0
- digline-0.1.0/tests/test_layering.py +153 -0
- digline-0.1.0/tests/test_metrics.py +122 -0
- digline-0.1.0/tests/test_readme.py +245 -0
- digline-0.1.0/tests/test_redaction.py +272 -0
- digline-0.1.0/tests/test_report.py +530 -0
- digline-0.1.0/tests/test_run.py +308 -0
- digline-0.1.0/tests/test_sampling.py +552 -0
- digline-0.1.0/tests/test_store.py +353 -0
- digline-0.1.0/tests/test_suspension.py +230 -0
- digline-0.1.0/tests/test_targets.py +313 -0
- digline-0.1.0/tests/test_types.py +225 -0
- digline-0.1.0/tests/test_view.py +767 -0
digline-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Python bytecode and build output
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
build/
|
|
5
|
+
dist/
|
|
6
|
+
*.egg-info/
|
|
7
|
+
|
|
8
|
+
# Environment. Recreated by `uv sync`; it pins absolute paths, so it must
|
|
9
|
+
# never be committed.
|
|
10
|
+
.venv/
|
|
11
|
+
.env
|
|
12
|
+
|
|
13
|
+
# Tool caches. Each already drops its own `.gitignore`; listed here so a
|
|
14
|
+
# fresh clone is clean before the tools have run once.
|
|
15
|
+
.pytest_cache/
|
|
16
|
+
.ruff_cache/
|
|
17
|
+
.mypy_cache/
|
|
18
|
+
|
|
19
|
+
# IDE. Excluded because the project files carry machine-specific SDK paths
|
|
20
|
+
# (`digline.iml` names the interpreter by absolute path). Drop these two lines
|
|
21
|
+
# to version the shared part, and keep ignoring `.idea/workspace.xml`.
|
|
22
|
+
.idea/
|
|
23
|
+
*.iml
|
|
24
|
+
|
|
25
|
+
# Local Claude Code settings. `.claude/settings.json`, if it appears, is shared
|
|
26
|
+
# and stays versioned. `CLAUDE.local.md` is the personal working agreement —
|
|
27
|
+
# how I want to be worked with — as against `CLAUDE.md`, which is the project.
|
|
28
|
+
.claude/settings.local.json
|
|
29
|
+
CLAUDE.local.md
|
|
30
|
+
|
|
31
|
+
# macOS
|
|
32
|
+
.DS_Store
|
|
33
|
+
|
|
34
|
+
# Working material that stays local and is not part of the package.
|
|
35
|
+
private/
|
|
36
|
+
|
|
37
|
+
# NOT ignored: `.digline/`. Decision 2 — baselines are versioned, run
|
|
38
|
+
# artifacts are not — and the split is enforced one level down, by the
|
|
39
|
+
# `.gitignore` the store itself writes into `.digline/` (`*/runs/`).
|
|
40
|
+
# Ignoring `.digline/` here would take the baselines out of git with it.
|
digline-0.1.0/CLAUDE.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# digline
|
|
2
|
+
|
|
3
|
+
Python-native evaluation engine for LLM output. Starting reference: promptfoo
|
|
4
|
+
(analysis in private/promptfoo-analysis.md). Not a clone: the decisions below
|
|
5
|
+
correct its structural mistakes and are not negotiable.
|
|
6
|
+
|
|
7
|
+
## Architectural decisions (fixed)
|
|
8
|
+
|
|
9
|
+
1. **One assertion engine, two drivers.** Assertions are pure functions
|
|
10
|
+
`(EvaluatorInputs) -> Verdict` in `digline.core`, with no I/O, callable on
|
|
11
|
+
their own (amended by ADR 0001: it used to be `(output, context) -> Score`).
|
|
12
|
+
The offline driver (prompt × provider × test matrix) and the online one
|
|
13
|
+
(stream of production responses) use the same code. If a change to the core
|
|
14
|
+
makes an assertion callable only inside a runner, it is wrong.
|
|
15
|
+
2. **Per-project storage.** Everything lives in `.digline/<tenant>/` inside the
|
|
16
|
+
user's repo: config and baselines versioned in git, run artifacts
|
|
17
|
+
gitignored. Behind the `ResultStore` protocol, file-based implementation by
|
|
18
|
+
default. Never a DB in the home directory, never global state on the machine.
|
|
19
|
+
3. **No vacuously green assertion.** Every assertion has a mandatory threshold
|
|
20
|
+
or a default that can fail. A default of 0 that always passes is a bug.
|
|
21
|
+
4. **Cost and latency are budgets, not metrics.** A declared ceiling fails the
|
|
22
|
+
run.
|
|
23
|
+
5. **Zero telemetry, zero phone-home.** No network call the user has not
|
|
24
|
+
explicitly configured.
|
|
25
|
+
6. **Providers as plugins** (entry points), not vendored into the repo.
|
|
26
|
+
7. **The core must accept a single response**, not only a matrix: the reactive
|
|
27
|
+
side (shadow path / in-path) is not decided yet, but must not be precluded.
|
|
28
|
+
8. **The tenant is the perimeter.** `Run.tenant` is mandatory and non-empty.
|
|
29
|
+
`compare()` and `promote_baseline` raise if the tenants differ. The tenant is
|
|
30
|
+
a directory in the layout — `.digline/<tenant>/` — so that the separation is
|
|
31
|
+
enforced by the filesystem, not by a field inside a document.
|
|
32
|
+
**No sub-perimeter**: `Run.environment` (mandatory, no default) says where
|
|
33
|
+
inside the perimeter the run happened, does not enter the layout, and
|
|
34
|
+
`compare()` reports it without constraining — comparing staging against the
|
|
35
|
+
production baseline is the pre-release check. (ADR 0002)
|
|
36
|
+
9. **The payload stays where it is born, the verdict travels.** These cross a
|
|
37
|
+
boundary: name, `assertion_id`, status, score, threshold, tolerance, and the
|
|
38
|
+
metadata *measured by an assertion*. These do not: the `reason` and any
|
|
39
|
+
metadata not covered by a `Disclosure` declared in code. Redaction is a
|
|
40
|
+
function on the value (`redact`), not a serializer option; in the document
|
|
41
|
+
the payload fields are absent, not emptied, and `"redacted": true` declares
|
|
42
|
+
it. (ADR 0002)
|
|
43
|
+
The **artifacts** a suite declares — the prompt is the thing under test — are
|
|
44
|
+
recorded in every run and cross a boundary only under
|
|
45
|
+
`Disclosure(artifacts=True)`: a prompt carries the end company's rules, so
|
|
46
|
+
the prudent default holds here too. (ADR 0003)
|
|
47
|
+
|
|
48
|
+
## Structure
|
|
49
|
+
|
|
50
|
+
src/digline/core/ pure domain: Score, Verdict, assertions, protocols. No imports from other packages.
|
|
51
|
+
src/digline/store/ ResultStore and its implementations (file-based, inside the repo)
|
|
52
|
+
src/digline/targets/ prompt template, pricing, the ProviderTarget base. No SDK, ever;
|
|
53
|
+
real providers are separate packages under packages/
|
|
54
|
+
src/digline/run/ offline driver
|
|
55
|
+
src/digline/report/ the document for world 3: pure functions, self-contained HTML, mandatory locale
|
|
56
|
+
src/digline/production/ [planned] production store, Postgres first, mandatory retention
|
|
57
|
+
src/digline/bridge/ [planned] production → repo: mandatory anonymization, generated case_id
|
|
58
|
+
src/digline/online/ production driver
|
|
59
|
+
src/digline/cli/ last layer: the **only** one allowed to read the clock and git
|
|
60
|
+
(the *clock*, meaning wall time: `created_at` is passed in so a
|
|
61
|
+
run is reproducible. A **duration** is not a clock — it cannot
|
|
62
|
+
say what time it is — so `perf_counter` for `latency_ms` in a
|
|
63
|
+
target is allowed and is what fills `Response.latency_ms`.)
|
|
64
|
+
docs/ public documentation: API reference, decisions (numbered ADRs)
|
|
65
|
+
|
|
66
|
+
Allowed dependencies: cli → targets → run/report/bridge/online →
|
|
67
|
+
store/production → core. Never the other way round, and nothing under `src/`
|
|
68
|
+
ever imports a plugin from `packages/`.
|
|
69
|
+
|
|
70
|
+
Build order: offline driver → report → store and CLI. **Nothing online before
|
|
71
|
+
the report**: it is what world 3 sees, and it is the only one of the three
|
|
72
|
+
artifacts that today exists in none of the audited competitors.
|
|
73
|
+
|
|
74
|
+
## Conventions
|
|
75
|
+
|
|
76
|
+
- Python 3.12+, uv, ruff, pyright strict, pytest. Types everywhere, `Protocol`
|
|
77
|
+
for abstractions, frozen dataclasses for values.
|
|
78
|
+
- **The whole repository is in English**: comments, docstrings, test and
|
|
79
|
+
variable names, error messages and runtime strings (`Verdict.reason` ends up
|
|
80
|
+
in the committed baseline, which is a public format), plus `docs/`, the ADRs
|
|
81
|
+
and this file. Italian only in conversation and in `private/`, which is not
|
|
82
|
+
committed.
|
|
83
|
+
**The one declared exception: `digline/report/text.py`.** The report is not a
|
|
84
|
+
runtime string, it is a document with a recipient who did not choose English.
|
|
85
|
+
`TEXT` is the per-locale table; `render_html` and `headline` take a mandatory
|
|
86
|
+
`locale` with no default, like `environment`. ISO dates and the decimal point
|
|
87
|
+
are not localized: two reports of the same run must stay comparable line by
|
|
88
|
+
line.
|
|
89
|
+
In the CLI the distinction is between *document* and *terminal*:
|
|
90
|
+
`report --locale` is mandatory, `compare --locale` defaults to `en` like every
|
|
91
|
+
other terminal output. Consistency between the two sentences is guaranteed by
|
|
92
|
+
`headline()`, not by the user.
|
|
93
|
+
- Every assertion has tests with at least one failing case.
|
|
94
|
+
- `tests/test_layering.py` is a **mandatory gate**, not a style test: it guarantees
|
|
95
|
+
that the core stays pure and importable from Plumbline without dragging storage
|
|
96
|
+
along. It must not be weakened or made optional; if it fails, the change is
|
|
97
|
+
wrong, not the test.
|
|
98
|
+
- Every decision touching the "fixed" section requires an ADR in docs/adr/
|
|
99
|
+
before the code.
|
|
100
|
+
- Small commits, message in English, imperative.
|
|
101
|
+
|
|
102
|
+
## Relationship with Plumbline
|
|
103
|
+
|
|
104
|
+
Plumbline (CLI `plumb`) is the methodology for preventive verification of the
|
|
105
|
+
development process; digline verifies the model's output. digline.core must be
|
|
106
|
+
importable from Plumbline as a library. Plumbline's wall/friction dichotomy maps
|
|
107
|
+
onto in-path/shadow-path here: use the same terms.
|
|
108
|
+
|
|
109
|
+
## The three worlds (ADR 0002)
|
|
110
|
+
|
|
111
|
+
1. **Developer** — works in the repo, writes the assertions, sees everything.
|
|
112
|
+
2. **Software house** — maintains N customers, must see the signal **without
|
|
113
|
+
holding the production data** of any of them.
|
|
114
|
+
3. **End company** — owns the data, does not read code, is entitled to an
|
|
115
|
+
understandable verdict and to its data not leaving.
|
|
116
|
+
|
|
117
|
+
The tenant (decision 8) separates customers from each other; the
|
|
118
|
+
payload/verdict boundary (decision 9) separates what the end company may send
|
|
119
|
+
from what it must not.
|
|
120
|
+
|
|
121
|
+
## Where the working material lives
|
|
122
|
+
|
|
123
|
+
The frictions log — what tripped a real user, in order of discovery — lives
|
|
124
|
+
in `private/`, which is a **separate repository** and is gitignored here.
|
|
125
|
+
Commits to it are made there, not in this repo. It is the record of use, so
|
|
126
|
+
it is written in whatever language the using happened in.
|
digline-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright 2026 Alessandro Prandini
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
digline-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: digline
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python-native evaluation engine for LLM output. The verdict lives in your repo.
|
|
5
|
+
Project-URL: Documentation, https://github.com/digline/digline/blob/main/docs/api.md
|
|
6
|
+
Project-URL: Repository, https://github.com/digline/digline
|
|
7
|
+
Project-URL: Issues, https://github.com/digline/digline/issues
|
|
8
|
+
Author-email: Alessandro Prandini <alessandro.prandini@ict-group.it>
|
|
9
|
+
License-Expression: Apache-2.0
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: assertions,baseline,eval,evaluation,llm,regression,testing
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
22
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
23
|
+
Classifier: Topic :: Software Development :: Testing
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.12
|
|
26
|
+
Requires-Dist: jsonschema>=4.21
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# digline
|
|
30
|
+
|
|
31
|
+
**Regression testing for LLM applications — with the baseline in your repository, not on someone's server.**
|
|
32
|
+
|
|
33
|
+
[](pyproject.toml)
|
|
34
|
+
[](LICENSE)
|
|
35
|
+
|
|
36
|
+
Your prompt worked on Tuesday. On Thursday it works a little less — not enough
|
|
37
|
+
to break, enough for a user to notice in two weeks. No ordinary test catches it,
|
|
38
|
+
because there is no correct output to compare against, only a better or a worse
|
|
39
|
+
one.
|
|
40
|
+
|
|
41
|
+
digline gives you an **approved reference** — the baseline — and on every change
|
|
42
|
+
tells you whether you are below it: which case, which check, by how much. The
|
|
43
|
+
baseline is a JSON file in your repository, so it goes through code review and
|
|
44
|
+
it rolls back with `git`. No server, no account, no network call you have not
|
|
45
|
+
configured yourself.
|
|
46
|
+
|
|
47
|
+
```console
|
|
48
|
+
$ digline compare --suite suite.py --run latest
|
|
49
|
+
2 checks got worse compared with the reference. Every case could be judged. No case is suspended. The configuration is the same as the reference.
|
|
50
|
+
|
|
51
|
+
how-do-i-return · llm_rubric · Score fell from 1.000000 to 0.700000.
|
|
52
|
+
how-do-i-return · contains · Went from passing to failing (1.000000 → 0.000000).
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Why digline
|
|
56
|
+
|
|
57
|
+
Most evaluation tools tell you whether an output is below a threshold. digline
|
|
58
|
+
also tells you whether it is *worse than it was* — the drift from 0.91 to 0.78
|
|
59
|
+
that trips no threshold and is the first thing a user feels.
|
|
60
|
+
|
|
61
|
+
The suite is **Python, not YAML**: a judge is an object, a target is a function,
|
|
62
|
+
and what may leave a perimeter is declared in code — none of which a
|
|
63
|
+
configuration file expresses without reinventing a language. Built for teams
|
|
64
|
+
shipping LLM features for someone else, who have to show a customer what was
|
|
65
|
+
tested, when, under which commit, and who approved it.
|
|
66
|
+
|
|
67
|
+
## Quickstart
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
uv sync # inside a clone; not published to PyPI yet
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`suite.py` — complete and runnable, no API key:
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
"""suite.py — complete and runnable: no API key, nothing else to install."""
|
|
77
|
+
|
|
78
|
+
from digline.core import Contains, CostBudget, JudgeReply, LlmRubric
|
|
79
|
+
from digline.run import Case, Response, Suite
|
|
80
|
+
|
|
81
|
+
ANSWERS = {
|
|
82
|
+
"where-is-my-order": "Order 4821 ships Thursday. — Northwind Support",
|
|
83
|
+
"how-do-i-return": "Any item, within 30 days, unused. — Northwind Support",
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def judge(prompt: str) -> JudgeReply:
|
|
88
|
+
"""Your judge. digline composes `prompt` from the rubric, the question and
|
|
89
|
+
the answer; it wants a score in [0, 1] and a reason back."""
|
|
90
|
+
signed = "Northwind Support" in prompt
|
|
91
|
+
concise = len(prompt.split()) <= 60
|
|
92
|
+
return JudgeReply(
|
|
93
|
+
score=0.4 + 0.3 * signed + 0.3 * concise,
|
|
94
|
+
reason=f"signed={signed}, concise={concise}",
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def target(case: Case) -> Response:
|
|
99
|
+
"""Your application, called once per case. Canned here so this runs as is."""
|
|
100
|
+
text = ANSWERS[case.id]
|
|
101
|
+
return Response(output=text, cost_usd=0.004 + 0.001 * len(text) / 100)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
suite = Suite(
|
|
105
|
+
tenant="northwind",
|
|
106
|
+
environment="staging",
|
|
107
|
+
name="support",
|
|
108
|
+
assertions=[
|
|
109
|
+
Contains(needle="Northwind Support"),
|
|
110
|
+
LlmRubric(
|
|
111
|
+
rubric="Does the reply answer the question in at most three sentences?",
|
|
112
|
+
judge=judge,
|
|
113
|
+
threshold=0.7,
|
|
114
|
+
tolerance=0.05,
|
|
115
|
+
),
|
|
116
|
+
CostBudget(max_usd=0.02, tolerance=0.05),
|
|
117
|
+
],
|
|
118
|
+
cases=[Case(id="where-is-my-order"), Case(id="how-do-i-return")],
|
|
119
|
+
)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
```console
|
|
123
|
+
$ digline run --suite suite.py
|
|
124
|
+
2026-08-26T15-44-09-282929-00-00-e7421ec503ccefe8
|
|
125
|
+
|
|
126
|
+
$ digline promote --suite suite.py --run latest
|
|
127
|
+
support baseline set to 2026-08-26T15-44-09-282929-00-00-e7421ec503ccefe8
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Now change the prompt, the model, an answer — anything — and ask again:
|
|
131
|
+
|
|
132
|
+
```console
|
|
133
|
+
$ digline run --suite suite.py
|
|
134
|
+
2026-08-26T15-44-09-492722-00-00-e7421ec503ccefe8
|
|
135
|
+
|
|
136
|
+
$ digline compare --suite suite.py --run latest
|
|
137
|
+
2 checks got worse compared with the reference. Every case could be judged. No case is suspended. The configuration is the same as the reference.
|
|
138
|
+
|
|
139
|
+
how-do-i-return · llm_rubric · Score fell from 1.000000 to 0.700000.
|
|
140
|
+
how-do-i-return · contains · Went from passing to failing (1.000000 → 0.000000).
|
|
141
|
+
|
|
142
|
+
$ echo $?
|
|
143
|
+
1
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The exit code is the answer: `0` fine, `1` got worse, `2` could not be judged.
|
|
147
|
+
Everything lands in `.digline/<tenant>/` — `baselines/` committed, `runs/`
|
|
148
|
+
git-ignored through a `.gitignore` digline writes for you.
|
|
149
|
+
|
|
150
|
+
## What it checks
|
|
151
|
+
|
|
152
|
+
**Per case** — pure functions `(inputs) -> Verdict`, no I/O, callable on their own:
|
|
153
|
+
|
|
154
|
+
| Assertion | Use it when |
|
|
155
|
+
|---|---|
|
|
156
|
+
| `Equals`, `Contains`, `NotContains`, `Affix`, `Regex` | the output must, or must not, contain something specific |
|
|
157
|
+
| `IsJson`, `JsonSchema` | the output is structured |
|
|
158
|
+
| `Length` | answers are growing, or must fit a channel |
|
|
159
|
+
| `Levenshtein` | "close enough" to `Case.expected`, graded rather than binary |
|
|
160
|
+
| `LlmRubric` | the criterion is a judgement — is it polite, does it stay on policy |
|
|
161
|
+
| `Faithfulness` | RAG: is the answer supported by the retrieved context |
|
|
162
|
+
| `FromAutoevals` | you already have an `autoevals` scorer and want it under a baseline |
|
|
163
|
+
| `PiiAbsent` | the output reaches a person — IBAN, codice fiscale, partita IVA, email, phone, checksum-verified where one exists |
|
|
164
|
+
| `CostBudget`, `LatencyBudget` | always. Graded, so a cost creeping up *within* budget is still visible |
|
|
165
|
+
| `Repeated` | the judge oscillates: grade the same output `n` times and fold the votes |
|
|
166
|
+
|
|
167
|
+
**Per run** — one verdict on the whole suite, the kind that goes in a contract:
|
|
168
|
+
|
|
169
|
+
| Aggregate | Use it when |
|
|
170
|
+
|---|---|
|
|
171
|
+
| `Precision` | false positives are what your users see |
|
|
172
|
+
| `Recall` | what is missed is what your users miss |
|
|
173
|
+
| `Accuracy`, `F1` | you need a single number for both |
|
|
174
|
+
|
|
175
|
+
Every assertion carries a **threshold that can fail** — there is no default that
|
|
176
|
+
passes vacuously, and `Contains("")` is a `ValueError` when the suite loads
|
|
177
|
+
rather than a green run — and a **tolerance** below which a difference from the
|
|
178
|
+
baseline is noise. Where a number is really "k out of n", write it as one:
|
|
179
|
+
`min_agreement="2/3"`, and a float no `k/n` can produce is refused at
|
|
180
|
+
construction.
|
|
181
|
+
|
|
182
|
+
One card each — parameters, typical values, what to watch out for — in
|
|
183
|
+
[`docs/metrics.md`](docs/metrics.md). Custom assertion? Subclass
|
|
184
|
+
`AssertionBase`, or `RunAssertionBase` for an aggregate: [`docs/api.md`](docs/api.md).
|
|
185
|
+
|
|
186
|
+
## How it thinks
|
|
187
|
+
|
|
188
|
+
- **The judge is yours.** digline never calls a model API: you inject a
|
|
189
|
+
function, and in your tests you inject a deterministic one.
|
|
190
|
+
- **Three states, not two** — `pass`, `fail`, `error`. An error is neither green
|
|
191
|
+
nor a regression: it means *could not judge*, and a run containing one cannot
|
|
192
|
+
become the baseline.
|
|
193
|
+
- **Two kinds of noise, two answers.** `Suite.samples` asks the target more than
|
|
194
|
+
once — the same input answered differently. `Repeated` grades the same output
|
|
195
|
+
more than once — the judge changing its mind. `min_agreement` becomes
|
|
196
|
+
mandatory as soon as you sample.
|
|
197
|
+
- **Set the threshold where the system measurably is**, not where you want it:
|
|
198
|
+
the gate protects against getting worse, and raising the bar is a visible
|
|
199
|
+
change in a pull request.
|
|
200
|
+
- **Promote the median of several runs**, not the first green one — `digline
|
|
201
|
+
view` is the table you pick it from. Cases diagnose, aggregates gate.
|
|
202
|
+
|
|
203
|
+
Worked through with real numbers in [`docs/guide.md`](docs/guide.md); the
|
|
204
|
+
reasoning behind every fixed decision is in [`docs/adr/`](docs/adr/).
|
|
205
|
+
|
|
206
|
+
## Commands
|
|
207
|
+
|
|
208
|
+
| Command | |
|
|
209
|
+
|---|---|
|
|
210
|
+
| `digline run` | execute the suite, write the run, print its key |
|
|
211
|
+
| `digline compare` | headline plus the lines that got worse; `--json`, `--json full` for CI |
|
|
212
|
+
| `digline promote` | make a run the baseline — refused if the tenant differs, the configuration changed, or any check errored |
|
|
213
|
+
| `digline report` | self-contained HTML for readers who do not read code; `--locale` mandatory, `--redacted` keeps the verdicts and drops the payload |
|
|
214
|
+
| `digline list` | stored runs, newest first, baseline marked |
|
|
215
|
+
| `digline view` | local browser UI — [`docs/view.md`](docs/view.md) |
|
|
216
|
+
| `digline migrate` | bring stored runs forward across schema versions — [`docs/migrate.md`](docs/migrate.md) |
|
|
217
|
+
|
|
218
|
+
## What digline is not
|
|
219
|
+
|
|
220
|
+
- **Not an observability platform.** Dashboards over production traces are a
|
|
221
|
+
served market. What is designed and not yet built is narrower: evaluating
|
|
222
|
+
production responses inside *your* perimeter, and turning a failure into a
|
|
223
|
+
committed test case.
|
|
224
|
+
- **Not a red-teaming tool.** digline generates no attacks. Once one is found,
|
|
225
|
+
it becomes a `Case`, and the suite makes sure it never works again.
|
|
226
|
+
- **Not YAML.** Cases are data and may come from files; the suite is Python.
|
|
227
|
+
|
|
228
|
+
## Status
|
|
229
|
+
|
|
230
|
+
`0.1.0`, alpha. The offline cycle — write the suite, run, promote, compare,
|
|
231
|
+
report — is complete, covered by tests, and used daily on a real project. The
|
|
232
|
+
production store, the bridge from production failures back to committed cases,
|
|
233
|
+
and the reactive side are designed in
|
|
234
|
+
[ADR 0002](docs/adr/0002-three-worlds-and-where-the-data-lives.md) and not
|
|
235
|
+
written yet.
|
|
236
|
+
|
|
237
|
+
Python 3.12+. One runtime dependency: `jsonschema`.
|
|
238
|
+
|
|
239
|
+
## Docs
|
|
240
|
+
|
|
241
|
+
- [`docs/guide.md`](docs/guide.md) — how to reason with digline, in eight chapters
|
|
242
|
+
and the order the problems arrive: baseline, judge noise, sampling, tolerance,
|
|
243
|
+
threshold, which run to promote, what to gate on, what to maintain
|
|
244
|
+
- [`docs/metrics.md`](docs/metrics.md) — a card per assertion and aggregate: when
|
|
245
|
+
to reach for it, what it produces, what it will do to you if you are not looking
|
|
246
|
+
- [`docs/api.md`](docs/api.md) — what is imported from where, every assertion
|
|
247
|
+
and its parameters, custom assertions, and the complete example in
|
|
248
|
+
[`examples/quickstart/`](examples/quickstart/), which a test runs on every build
|
|
249
|
+
- [`docs/view.md`](docs/view.md) · [`docs/migrate.md`](docs/migrate.md) — the two commands with a surface of their own
|
|
250
|
+
- [`docs/adr/`](docs/adr/) — the architectural decisions, numbered, with the reasoning
|
|
251
|
+
|
|
252
|
+
## License
|
|
253
|
+
|
|
254
|
+
Apache-2.0.
|