runledger 0.2.0__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {runledger-0.2.0 → runledger-0.2.2}/PKG-INFO +53 -17
- runledger-0.2.0/src/runledger.egg-info/PKG-INFO → runledger-0.2.2/README.md +48 -40
- {runledger-0.2.0 → runledger-0.2.2}/pyproject.toml +7 -1
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/__init__.py +1 -1
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/cli.py +228 -118
- runledger-0.2.2/src/runledger/reporting/__init__.py +17 -0
- runledger-0.2.2/src/runledger/reporting/failures.py +278 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/runner/subprocess.py +64 -8
- runledger-0.2.0/README.md → runledger-0.2.2/src/runledger.egg-info/PKG-INFO +76 -16
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger.egg-info/SOURCES.txt +2 -0
- {runledger-0.2.0 → runledger-0.2.2}/LICENSE +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/setup.cfg +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/__main__.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/artifacts/__init__.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/artifacts/junit.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/artifacts/report.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/artifacts/run_log.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/artifacts/summary.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/assertions/__init__.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/assertions/base.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/assertions/engine.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/assertions/json_schema.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/assertions/required_fields.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/assertions/tool_contract.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/baseline/__init__.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/baseline/io.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/baseline/models.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/cassette/__init__.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/cassette/loader.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/cassette/match.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/cassette/models.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/cassette/writer.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/config/__init__.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/config/loader.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/config/models.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/protocol/__init__.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/protocol/jsonl.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/protocol/messages.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/regression/__init__.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/regression/engine.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/runner/__init__.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/runner/budgets.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/runner/engine.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/runner/models.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/tools/__init__.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/tools/builtin.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/tools/registry.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/util/__init__.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/util/canonical_json.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/util/normalize.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger/util/redaction.py +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger.egg-info/dependency_links.txt +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger.egg-info/entry_points.txt +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger.egg-info/requires.txt +0 -0
- {runledger-0.2.0 → runledger-0.2.2}/src/runledger.egg-info/top_level.txt +0 -0
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: runledger
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: CI for tool-using agents: deterministic eval suites with record/replay, hard assertions, budgets, and PR regression gates.
|
|
5
5
|
Author: RunLedger
|
|
6
6
|
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://runledger.io
|
|
8
|
+
Project-URL: Repository, https://github.com/runledger/Runledger
|
|
9
|
+
Project-URL: Documentation, https://github.com/runledger/Runledger/tree/main/docs
|
|
10
|
+
Project-URL: Changelog, https://github.com/runledger/Runledger/blob/main/CHANGELOG.md
|
|
7
11
|
Keywords: llm,agents,evaluation,ci,github-actions,replay,testing,junit,jsonschema
|
|
8
12
|
Requires-Python: >=3.9
|
|
9
13
|
Description-Content-Type: text/markdown
|
|
@@ -30,25 +34,57 @@ Dynamic: license-file
|
|
|
30
34
|
|
|
31
35
|
Website: https://runledger.io
|
|
32
36
|
|
|
33
|
-
**Deterministic CI regression checks for tool-using agents.** Record tool calls once, replay in CI, and fail
|
|
37
|
+
**Deterministic CI regression checks for tool-using agents.** Record tool calls once, replay them in CI, and fail the run when a schema, tool-order, or tool-call budget gate fails.
|
|
34
38
|
|
|
35
|
-
- Demo
|
|
39
|
+
- Demo: https://github.com/ZackMitchell910/runledger-demo/pull/1 (intentionally left open to show RunLedger failing a bad agent change)
|
|
36
40
|
- If this is useful, please star the repo (it helps a lot).
|
|
37
41
|
|
|
38
|
-
|
|
39
|
-
- **DeepEval-style tools** help you *score* behavior.
|
|
40
|
-
- **RunLedger** helps you *ship safely* by making agent tests deterministic and merge-gated.
|
|
42
|
+
## Five-minute quickstart
|
|
41
43
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
## Try it now (60 seconds)
|
|
44
|
+
From an empty directory, with Python 3.9+:
|
|
45
45
|
|
|
46
46
|
```bash
|
|
47
|
-
|
|
47
|
+
python3 -m pip install runledger
|
|
48
48
|
runledger init
|
|
49
49
|
runledger run ./evals/demo --mode replay --baseline baselines/demo.json
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
+
`runledger init` writes:
|
|
53
|
+
|
|
54
|
+
- `evals/demo/` — replay suite, cassette, JSON Schema, and a small agent that calls `search_docs`
|
|
55
|
+
- `baselines/demo.json` — baseline from that passing run
|
|
56
|
+
- `.github/workflows/runledger.yml` — pull-request workflow that runs the same command
|
|
57
|
+
|
|
58
|
+
A second `runledger init` leaves those files unchanged. `--force` regenerates them.
|
|
59
|
+
|
|
60
|
+
The demo exits non-zero unless all three of these hold:
|
|
61
|
+
|
|
62
|
+
- **Schema.** The final output matches `evals/demo/schema.json` (`category` and `reply`).
|
|
63
|
+
- **Tool order.** The agent calls `search_docs` (`call_order` in `evals/demo/cases/t1.yaml`).
|
|
64
|
+
- **Tool-call budget.** The run stays within `max_tool_calls: 1`.
|
|
65
|
+
|
|
66
|
+
A passing run prints `PASS` and writes `runledger_out/demo/<run_id>/` (`summary.json`, `junit.xml`, `report.html`, `run.jsonl`). Commit the init files to run that gate on pull requests.
|
|
67
|
+
|
|
68
|
+
The same check as a GitHub Action. `suite.yaml` already points at `baselines/demo.json`, so the action compares against that baseline:
|
|
69
|
+
|
|
70
|
+
```yaml
|
|
71
|
+
- id: evals
|
|
72
|
+
uses: runledger/Runledger@v0.2
|
|
73
|
+
with:
|
|
74
|
+
path: ./evals/demo
|
|
75
|
+
mode: replay
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
This repo's `action.yml` also accepts a `baseline` input and sets `result` (`pass` or `fail`), `exit-code`, and `artifacts-path`. Those fields ship when a release containing this file is tagged. The in-repo self-test calls the action with `uses: ./`.
|
|
79
|
+
|
|
80
|
+
Page copy for runledger.io lives in `docs/site-quickstart.md`.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
RunLedger is a **CI harness**, not an "eval metrics framework":
|
|
85
|
+
- **DeepEval-style tools** help you *score* behavior.
|
|
86
|
+
- **RunLedger** helps you *ship safely* by making agent tests deterministic and merge-gated.
|
|
87
|
+
|
|
52
88
|
## Integrations (merged)
|
|
53
89
|
|
|
54
90
|
- hoangsonww/Agentic-AI-Pipeline - https://github.com/hoangsonww/Agentic-AI-Pipeline/pull/24
|
|
@@ -73,7 +109,7 @@ RunLedger stops regressions by shifting from "vibes-based" evaluation to determi
|
|
|
73
109
|
|
|
74
110
|
- **Record & replay:** record tool outputs once; replay them in CI for instant, stable tests.
|
|
75
111
|
- **Strict contracts:** enforce tool schemas, calling order, and allowlists.
|
|
76
|
-
- **Budget gating:** fail
|
|
112
|
+
- **Budget gating:** fail the run when tool-call count exceeds `max_tool_calls`. Wall time and tool-error caps are separate gates, configured with `max_wall_ms` and `max_tool_errors`.
|
|
77
113
|
|
|
78
114
|
### RunLedger vs evaluation frameworks
|
|
79
115
|
|
|
@@ -352,18 +388,18 @@ jobs:
|
|
|
352
388
|
- uses: actions/checkout@v4
|
|
353
389
|
|
|
354
390
|
- name: Run deterministic evals (replay)
|
|
391
|
+
id: evals
|
|
355
392
|
uses: runledger/Runledger@v0.2
|
|
356
393
|
with:
|
|
357
394
|
path: ./evals/demo
|
|
358
395
|
mode: replay
|
|
359
396
|
|
|
360
|
-
- name:
|
|
361
|
-
|
|
362
|
-
with:
|
|
363
|
-
name: agent-eval-artifacts
|
|
364
|
-
path: runledger_out/**
|
|
397
|
+
- name: Show the action result
|
|
398
|
+
run: echo "result=${{ steps.evals.outputs.result }} exit=${{ steps.evals.outputs.exit-code }}"
|
|
365
399
|
```
|
|
366
400
|
|
|
401
|
+
`runledger init` writes `.github/workflows/runledger.yml`, which installs the CLI and runs the suite with `--baseline`. The action above is the Marketplace entry in `action.yml`. It uploads `runledger_out/**` itself. On a release that contains this `action.yml`, the step also sets `result`, `exit-code`, and `artifacts-path`, and you can pass `baseline:` to override `suite.yaml`.
|
|
402
|
+
|
|
367
403
|
---
|
|
368
404
|
|
|
369
405
|
## Determinism guide (rules of the road)
|
|
@@ -386,6 +422,7 @@ jobs:
|
|
|
386
422
|
|
|
387
423
|
## Docs
|
|
388
424
|
|
|
425
|
+
- `docs/site-quickstart.md` -- five-minute copy for runledger.io
|
|
389
426
|
- `docs/quickstart.md` -- install + first run
|
|
390
427
|
- `docs/assertions.md` -- assertions, tool contracts, budgets
|
|
391
428
|
- `docs/baselines.md` -- baselines, diffing, promotion
|
|
@@ -398,7 +435,6 @@ jobs:
|
|
|
398
435
|
## Roadmap
|
|
399
436
|
|
|
400
437
|
* `init` templates for Python (OpenAI SDK), LangGraph/LangChain, and Node/TS
|
|
401
|
-
* richer budgets (tokens/cost) via optional `task_metrics`
|
|
402
438
|
* PR comments bot for regression summaries
|
|
403
439
|
* plugin system for custom assertions
|
|
404
440
|
* HTML report trace viewer improvements
|
|
@@ -1,27 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: runledger
|
|
3
|
-
Version: 0.2.0
|
|
4
|
-
Summary: CI for tool-using agents: deterministic eval suites with record/replay, hard assertions, budgets, and PR regression gates.
|
|
5
|
-
Author: RunLedger
|
|
6
|
-
License-Expression: MIT
|
|
7
|
-
Keywords: llm,agents,evaluation,ci,github-actions,replay,testing,junit,jsonschema
|
|
8
|
-
Requires-Python: >=3.9
|
|
9
|
-
Description-Content-Type: text/markdown
|
|
10
|
-
License-File: LICENSE
|
|
11
|
-
Requires-Dist: typer>=0.9.0
|
|
12
|
-
Requires-Dist: rich>=13.0.0
|
|
13
|
-
Requires-Dist: pydantic>=2.0.0
|
|
14
|
-
Requires-Dist: pyyaml>=6.0.0
|
|
15
|
-
Requires-Dist: jsonschema>=4.0.0
|
|
16
|
-
Requires-Dist: jinja2>=3.1.0
|
|
17
|
-
Requires-Dist: eval_type_backport>=0.2.0; python_version < "3.10"
|
|
18
|
-
Provides-Extra: dev
|
|
19
|
-
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
20
|
-
Requires-Dist: pytest-timeout>=2.1.0; extra == "dev"
|
|
21
|
-
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
22
|
-
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
23
|
-
Dynamic: license-file
|
|
24
|
-
|
|
25
1
|
# RunLedger
|
|
26
2
|
|
|
27
3
|
[](https://github.com/runledger/Runledger/actions/workflows/ci.yml)
|
|
@@ -30,25 +6,57 @@ Dynamic: license-file
|
|
|
30
6
|
|
|
31
7
|
Website: https://runledger.io
|
|
32
8
|
|
|
33
|
-
**Deterministic CI regression checks for tool-using agents.** Record tool calls once, replay in CI, and fail
|
|
9
|
+
**Deterministic CI regression checks for tool-using agents.** Record tool calls once, replay them in CI, and fail the run when a schema, tool-order, or tool-call budget gate fails.
|
|
34
10
|
|
|
35
|
-
- Demo
|
|
11
|
+
- Demo: https://github.com/ZackMitchell910/runledger-demo/pull/1 (intentionally left open to show RunLedger failing a bad agent change)
|
|
36
12
|
- If this is useful, please star the repo (it helps a lot).
|
|
37
13
|
|
|
38
|
-
|
|
39
|
-
- **DeepEval-style tools** help you *score* behavior.
|
|
40
|
-
- **RunLedger** helps you *ship safely* by making agent tests deterministic and merge-gated.
|
|
14
|
+
## Five-minute quickstart
|
|
41
15
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
## Try it now (60 seconds)
|
|
16
|
+
From an empty directory, with Python 3.9+:
|
|
45
17
|
|
|
46
18
|
```bash
|
|
47
|
-
|
|
19
|
+
python3 -m pip install runledger
|
|
48
20
|
runledger init
|
|
49
21
|
runledger run ./evals/demo --mode replay --baseline baselines/demo.json
|
|
50
22
|
```
|
|
51
23
|
|
|
24
|
+
`runledger init` writes:
|
|
25
|
+
|
|
26
|
+
- `evals/demo/` — replay suite, cassette, JSON Schema, and a small agent that calls `search_docs`
|
|
27
|
+
- `baselines/demo.json` — baseline from that passing run
|
|
28
|
+
- `.github/workflows/runledger.yml` — pull-request workflow that runs the same command
|
|
29
|
+
|
|
30
|
+
A second `runledger init` leaves those files unchanged. `--force` regenerates them.
|
|
31
|
+
|
|
32
|
+
The demo exits non-zero unless all three of these hold:
|
|
33
|
+
|
|
34
|
+
- **Schema.** The final output matches `evals/demo/schema.json` (`category` and `reply`).
|
|
35
|
+
- **Tool order.** The agent calls `search_docs` (`call_order` in `evals/demo/cases/t1.yaml`).
|
|
36
|
+
- **Tool-call budget.** The run stays within `max_tool_calls: 1`.
|
|
37
|
+
|
|
38
|
+
A passing run prints `PASS` and writes `runledger_out/demo/<run_id>/` (`summary.json`, `junit.xml`, `report.html`, `run.jsonl`). Commit the init files to run that gate on pull requests.
|
|
39
|
+
|
|
40
|
+
The same check as a GitHub Action. `suite.yaml` already points at `baselines/demo.json`, so the action compares against that baseline:
|
|
41
|
+
|
|
42
|
+
```yaml
|
|
43
|
+
- id: evals
|
|
44
|
+
uses: runledger/Runledger@v0.2
|
|
45
|
+
with:
|
|
46
|
+
path: ./evals/demo
|
|
47
|
+
mode: replay
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
This repo's `action.yml` also accepts a `baseline` input and sets `result` (`pass` or `fail`), `exit-code`, and `artifacts-path`. Those fields ship when a release containing this file is tagged. The in-repo self-test calls the action with `uses: ./`.
|
|
51
|
+
|
|
52
|
+
Page copy for runledger.io lives in `docs/site-quickstart.md`.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
RunLedger is a **CI harness**, not an "eval metrics framework":
|
|
57
|
+
- **DeepEval-style tools** help you *score* behavior.
|
|
58
|
+
- **RunLedger** helps you *ship safely* by making agent tests deterministic and merge-gated.
|
|
59
|
+
|
|
52
60
|
## Integrations (merged)
|
|
53
61
|
|
|
54
62
|
- hoangsonww/Agentic-AI-Pipeline - https://github.com/hoangsonww/Agentic-AI-Pipeline/pull/24
|
|
@@ -73,7 +81,7 @@ RunLedger stops regressions by shifting from "vibes-based" evaluation to determi
|
|
|
73
81
|
|
|
74
82
|
- **Record & replay:** record tool outputs once; replay them in CI for instant, stable tests.
|
|
75
83
|
- **Strict contracts:** enforce tool schemas, calling order, and allowlists.
|
|
76
|
-
- **Budget gating:** fail
|
|
84
|
+
- **Budget gating:** fail the run when tool-call count exceeds `max_tool_calls`. Wall time and tool-error caps are separate gates, configured with `max_wall_ms` and `max_tool_errors`.
|
|
77
85
|
|
|
78
86
|
### RunLedger vs evaluation frameworks
|
|
79
87
|
|
|
@@ -352,18 +360,18 @@ jobs:
|
|
|
352
360
|
- uses: actions/checkout@v4
|
|
353
361
|
|
|
354
362
|
- name: Run deterministic evals (replay)
|
|
363
|
+
id: evals
|
|
355
364
|
uses: runledger/Runledger@v0.2
|
|
356
365
|
with:
|
|
357
366
|
path: ./evals/demo
|
|
358
367
|
mode: replay
|
|
359
368
|
|
|
360
|
-
- name:
|
|
361
|
-
|
|
362
|
-
with:
|
|
363
|
-
name: agent-eval-artifacts
|
|
364
|
-
path: runledger_out/**
|
|
369
|
+
- name: Show the action result
|
|
370
|
+
run: echo "result=${{ steps.evals.outputs.result }} exit=${{ steps.evals.outputs.exit-code }}"
|
|
365
371
|
```
|
|
366
372
|
|
|
373
|
+
`runledger init` writes `.github/workflows/runledger.yml`, which installs the CLI and runs the suite with `--baseline`. The action above is the Marketplace entry in `action.yml`. It uploads `runledger_out/**` itself. On a release that contains this `action.yml`, the step also sets `result`, `exit-code`, and `artifacts-path`, and you can pass `baseline:` to override `suite.yaml`.
|
|
374
|
+
|
|
367
375
|
---
|
|
368
376
|
|
|
369
377
|
## Determinism guide (rules of the road)
|
|
@@ -386,6 +394,7 @@ jobs:
|
|
|
386
394
|
|
|
387
395
|
## Docs
|
|
388
396
|
|
|
397
|
+
- `docs/site-quickstart.md` -- five-minute copy for runledger.io
|
|
389
398
|
- `docs/quickstart.md` -- install + first run
|
|
390
399
|
- `docs/assertions.md` -- assertions, tool contracts, budgets
|
|
391
400
|
- `docs/baselines.md` -- baselines, diffing, promotion
|
|
@@ -398,7 +407,6 @@ jobs:
|
|
|
398
407
|
## Roadmap
|
|
399
408
|
|
|
400
409
|
* `init` templates for Python (OpenAI SDK), LangGraph/LangChain, and Node/TS
|
|
401
|
-
* richer budgets (tokens/cost) via optional `task_metrics`
|
|
402
410
|
* PR comments bot for regression summaries
|
|
403
411
|
* plugin system for custom assertions
|
|
404
412
|
* HTML report trace viewer improvements
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "runledger"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.2"
|
|
8
8
|
description = "CI for tool-using agents: deterministic eval suites with record/replay, hard assertions, budgets, and PR regression gates."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -34,6 +34,12 @@ dependencies = [
|
|
|
34
34
|
"eval_type_backport>=0.2.0; python_version < \"3.10\"",
|
|
35
35
|
]
|
|
36
36
|
|
|
37
|
+
[project.urls]
|
|
38
|
+
Homepage = "https://runledger.io"
|
|
39
|
+
Repository = "https://github.com/runledger/Runledger"
|
|
40
|
+
Documentation = "https://github.com/runledger/Runledger/tree/main/docs"
|
|
41
|
+
Changelog = "https://github.com/runledger/Runledger/blob/main/CHANGELOG.md"
|
|
42
|
+
|
|
37
43
|
[project.optional-dependencies]
|
|
38
44
|
dev = [
|
|
39
45
|
"pytest>=7.0.0",
|