copela 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.
- copela-0.1.0/LICENSE +21 -0
- copela-0.1.0/PKG-INFO +185 -0
- copela-0.1.0/README.md +144 -0
- copela-0.1.0/pyproject.toml +73 -0
- copela-0.1.0/setup.cfg +4 -0
- copela-0.1.0/src/copela/__init__.py +61 -0
- copela-0.1.0/src/copela/budget.py +84 -0
- copela-0.1.0/src/copela/cli.py +158 -0
- copela-0.1.0/src/copela/ledger.py +259 -0
- copela-0.1.0/src/copela/oracles/__init__.py +1 -0
- copela-0.1.0/src/copela/oracles/properties.py +317 -0
- copela-0.1.0/src/copela/providers/__init__.py +38 -0
- copela-0.1.0/src/copela/providers/base.py +140 -0
- copela-0.1.0/src/copela/providers/hosted.py +272 -0
- copela-0.1.0/src/copela/py.typed +0 -0
- copela-0.1.0/src/copela/report.py +177 -0
- copela-0.1.0/src/copela/solvers/__init__.py +1 -0
- copela-0.1.0/src/copela/solvers/highs.py +158 -0
- copela-0.1.0/src/copela/sweep.py +286 -0
- copela-0.1.0/src/copela/verdicts.py +201 -0
- copela-0.1.0/src/copela.egg-info/PKG-INFO +185 -0
- copela-0.1.0/src/copela.egg-info/SOURCES.txt +30 -0
- copela-0.1.0/src/copela.egg-info/dependency_links.txt +1 -0
- copela-0.1.0/src/copela.egg-info/entry_points.txt +2 -0
- copela-0.1.0/src/copela.egg-info/requires.txt +21 -0
- copela-0.1.0/src/copela.egg-info/top_level.txt +1 -0
- copela-0.1.0/tests/test_budget.py +87 -0
- copela-0.1.0/tests/test_ledger.py +221 -0
- copela-0.1.0/tests/test_properties.py +129 -0
- copela-0.1.0/tests/test_providers.py +121 -0
- copela-0.1.0/tests/test_report.py +248 -0
- copela-0.1.0/tests/test_solvers.py +83 -0
copela-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Felipe Santibanez-Leal
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
copela-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: copela
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Run narrative-to-formal translation across many language models and score it with oracles that are not language models: executable, structural and property layers, reported separately.
|
|
5
|
+
Author-email: Felipe Santibanez-Leal <fsantibanez@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/fsantibanezleal/CAOS_Copela
|
|
8
|
+
Project-URL: Source, https://github.com/fsantibanezleal/CAOS_Copela
|
|
9
|
+
Project-URL: Issues, https://github.com/fsantibanezleal/CAOS_Copela/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/fsantibanezleal/CAOS_Copela/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: evaluation,benchmark,optimization,formalization,autoformalization,metamorphic-testing,llm
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: planteo>=0.1.0
|
|
25
|
+
Provides-Extra: solvers
|
|
26
|
+
Requires-Dist: pyomo>=6.7; extra == "solvers"
|
|
27
|
+
Requires-Dist: highspy>=1.7; extra == "solvers"
|
|
28
|
+
Provides-Extra: anthropic
|
|
29
|
+
Requires-Dist: anthropic>=0.40; extra == "anthropic"
|
|
30
|
+
Provides-Extra: openai
|
|
31
|
+
Requires-Dist: openai>=1.50; extra == "openai"
|
|
32
|
+
Provides-Extra: all
|
|
33
|
+
Requires-Dist: pyomo>=6.7; extra == "all"
|
|
34
|
+
Requires-Dist: highspy>=1.7; extra == "all"
|
|
35
|
+
Requires-Dist: anthropic>=0.40; extra == "all"
|
|
36
|
+
Requires-Dist: openai>=1.50; extra == "all"
|
|
37
|
+
Provides-Extra: dev
|
|
38
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
39
|
+
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
40
|
+
Dynamic: license-file
|
|
41
|
+
|
|
42
|
+
# copela
|
|
43
|
+
|
|
44
|
+
[](https://pypi.org/project/copela/)
|
|
45
|
+
[](LICENSE)
|
|
46
|
+
|
|
47
|
+
Run narrative-to-formal translation across many language models, and score the result with oracles
|
|
48
|
+
that are **not** language models.
|
|
49
|
+
|
|
50
|
+
The name is the cupel used in fire assay: the vessel that separates the metal from the lead. That is
|
|
51
|
+
the job here, separating a formalization that is faithful from one that merely runs.
|
|
52
|
+
|
|
53
|
+
## The number this produces
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
gap report
|
|
57
|
+
============================================================
|
|
58
|
+
|
|
59
|
+
anthropic/claude-sonnet-5 [optimization] ran 0.940 [0.832, 0.981] n=50 faithful 0.720 [0.584, 0.826] n=50 gap +0.220
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**ran** is what the field reports. **faithful** is what was asked for. The gap between them is the
|
|
63
|
+
output, and no source found reports it across target families.
|
|
64
|
+
|
|
65
|
+
That gap is not hypothetical. Where it has been measured carefully, in natural-language to Lean
|
|
66
|
+
formalization, it runs [3.0 to 29.0 percentage points](https://arxiv.org/abs/2606.31002), and the
|
|
67
|
+
strongest system measured had the largest gap: 89.5% compiling, 60.5% faithful.
|
|
68
|
+
|
|
69
|
+
## Four layers, never merged
|
|
70
|
+
|
|
71
|
+
| Layer | Asks | Strength |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| **executable** | did it run, solve, compile | necessary, weak, the layer the field over-reports |
|
|
74
|
+
| **structural** | is it the same model as the reference | strong where it applies |
|
|
75
|
+
| **property** | do the invariants of this class hold | strong, catches what structure misses |
|
|
76
|
+
| **judge** | what would a model say | a labelled screening aggregate, never truth |
|
|
77
|
+
|
|
78
|
+
There is deliberately **no combined score**, and a test fails if anyone adds one. A single number
|
|
79
|
+
lets a high "it ran" rate conceal a low "it was right" rate, which is the distance this exists to
|
|
80
|
+
show.
|
|
81
|
+
|
|
82
|
+
The judge layer is reported because the literature reports it and comparability matters. It carries
|
|
83
|
+
a label on every record saying it is not an oracle, because the study that calibrated a two-judge
|
|
84
|
+
consensus against human majority states exactly that.
|
|
85
|
+
|
|
86
|
+
## The property layer
|
|
87
|
+
|
|
88
|
+
Metamorphic relations: instead of checking an exact output, check how the output **must** change
|
|
89
|
+
when the input changes in a controlled way.
|
|
90
|
+
|
|
91
|
+
| Relation | Guarantee |
|
|
92
|
+
|---|---|
|
|
93
|
+
| scale the objective by `k > 0` | the argmin cannot move |
|
|
94
|
+
| add a redundant constraint | the feasible set cannot change |
|
|
95
|
+
| tighten a constraint | the optimum cannot improve |
|
|
96
|
+
|
|
97
|
+
The standard objection to metamorphic testing is that the relations must be authored per problem
|
|
98
|
+
class and so do not generalise to arbitrary programs. That is the design here: the target families
|
|
99
|
+
are narrow typed classes, so the relations are written once per class.
|
|
100
|
+
|
|
101
|
+
A candidate that solves and then fails one of these is wrong in a way no solver would have reported.
|
|
102
|
+
|
|
103
|
+
## Install
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
pip install copela # the harness
|
|
107
|
+
pip install "copela[solvers]" # plus Pyomo and HiGHS
|
|
108
|
+
pip install "copela[all]" # plus the provider SDKs
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Use
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from copela import Budget, Case, Ledger, Sweep, Target, build
|
|
115
|
+
from copela.providers import get
|
|
116
|
+
from copela.solvers.highs import make_solver
|
|
117
|
+
|
|
118
|
+
sweep = Sweep(
|
|
119
|
+
ledger=Ledger("runs.jsonl"),
|
|
120
|
+
budget=Budget(limit_usd=5.00, max_consecutive_failures=10),
|
|
121
|
+
providers={"anthropic": get("anthropic"), "ollama": get("ollama")},
|
|
122
|
+
build_prompt=my_prompt, # the prompt strategy is what a study varies
|
|
123
|
+
parse_response=my_parser,
|
|
124
|
+
solve=make_solver(),
|
|
125
|
+
repeats=5,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
sweep.run(cases, [Target("anthropic", "claude-sonnet-5"),
|
|
129
|
+
Target("ollama", "qwen3:8b")])
|
|
130
|
+
|
|
131
|
+
print(build(Ledger("runs.jsonl")).to_text())
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
From the shell:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
copela models # what each provider serves, and what it costs
|
|
138
|
+
copela solve problem.json # solve one formalization, no model involved
|
|
139
|
+
copela report runs.jsonl # the gap
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Why many models, not one
|
|
143
|
+
|
|
144
|
+
Because a benchmark of AI-assisted modelling and simulation reports that
|
|
145
|
+
[no single model dominates across engine types](https://arxiv.org/abs/2605.28994), with
|
|
146
|
+
task-specific tradeoffs between speed and accuracy. A ranking claimed from one model contradicts a
|
|
147
|
+
published result, so the provider seam is a requirement rather than tidiness: Anthropic, Groq and
|
|
148
|
+
local models through Ollama, behind one interface, with no vendor name anywhere outside
|
|
149
|
+
`copela/providers/`. A test enforces that.
|
|
150
|
+
|
|
151
|
+
## What reproducibility means here
|
|
152
|
+
|
|
153
|
+
Temperature zero does not make hosted inference deterministic. The dominant cause is the batch-size
|
|
154
|
+
dependence of reduction kernels rather than floating-point non-associativity, and bitwise
|
|
155
|
+
determinism costs a third to two thirds of throughput and cannot be bought over a hosted API.
|
|
156
|
+
|
|
157
|
+
So the harness pins what it can (model, version, temperature, seed, provider fingerprint), records
|
|
158
|
+
`n` repeats, and reports a **rate with a Wilson interval**. It never presents a single run as the
|
|
159
|
+
result, and 5 of 5 is not reported as certainly 1.0.
|
|
160
|
+
|
|
161
|
+
## The ledger
|
|
162
|
+
|
|
163
|
+
One append-only JSONL record per call, carrying its full provenance. A record is never edited,
|
|
164
|
+
because a ledger that can be rewritten is not evidence. It is also the resume mechanism: a sweep
|
|
165
|
+
reads it and skips the calls already done.
|
|
166
|
+
|
|
167
|
+
## Cost
|
|
168
|
+
|
|
169
|
+
Every sweep declares a budget and a kill criterion before it runs, and the guard refuses the call
|
|
170
|
+
that **would** exceed the ceiling rather than noticing afterwards.
|
|
171
|
+
|
|
172
|
+
## Documentation
|
|
173
|
+
|
|
174
|
+
The wiki is in [`docs/`](docs/). The design document, written before the code, is
|
|
175
|
+
[`docs/design/SDD.md`](docs/design/SDD.md); each of its twelve requirements names the test that
|
|
176
|
+
verifies it.
|
|
177
|
+
|
|
178
|
+
## Related
|
|
179
|
+
|
|
180
|
+
[`planteo`](https://github.com/fsantibanezleal/CAOS_Planteo) is the representation this consumes: a
|
|
181
|
+
typed problem with dimensions on every quantity and provenance on every element.
|
|
182
|
+
|
|
183
|
+
## License
|
|
184
|
+
|
|
185
|
+
MIT. See [LICENSE](LICENSE).
|
copela-0.1.0/README.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# copela
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/copela/)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
|
|
6
|
+
Run narrative-to-formal translation across many language models, and score the result with oracles
|
|
7
|
+
that are **not** language models.
|
|
8
|
+
|
|
9
|
+
The name is the cupel used in fire assay: the vessel that separates the metal from the lead. That is
|
|
10
|
+
the job here, separating a formalization that is faithful from one that merely runs.
|
|
11
|
+
|
|
12
|
+
## The number this produces
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
gap report
|
|
16
|
+
============================================================
|
|
17
|
+
|
|
18
|
+
anthropic/claude-sonnet-5 [optimization] ran 0.940 [0.832, 0.981] n=50 faithful 0.720 [0.584, 0.826] n=50 gap +0.220
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**ran** is what the field reports. **faithful** is what was asked for. The gap between them is the
|
|
22
|
+
output, and no source found reports it across target families.
|
|
23
|
+
|
|
24
|
+
That gap is not hypothetical. Where it has been measured carefully, in natural-language to Lean
|
|
25
|
+
formalization, it runs [3.0 to 29.0 percentage points](https://arxiv.org/abs/2606.31002), and the
|
|
26
|
+
strongest system measured had the largest gap: 89.5% compiling, 60.5% faithful.
|
|
27
|
+
|
|
28
|
+
## Four layers, never merged
|
|
29
|
+
|
|
30
|
+
| Layer | Asks | Strength |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| **executable** | did it run, solve, compile | necessary, weak, the layer the field over-reports |
|
|
33
|
+
| **structural** | is it the same model as the reference | strong where it applies |
|
|
34
|
+
| **property** | do the invariants of this class hold | strong, catches what structure misses |
|
|
35
|
+
| **judge** | what would a model say | a labelled screening aggregate, never truth |
|
|
36
|
+
|
|
37
|
+
There is deliberately **no combined score**, and a test fails if anyone adds one. A single number
|
|
38
|
+
lets a high "it ran" rate conceal a low "it was right" rate, which is the distance this exists to
|
|
39
|
+
show.
|
|
40
|
+
|
|
41
|
+
The judge layer is reported because the literature reports it and comparability matters. It carries
|
|
42
|
+
a label on every record saying it is not an oracle, because the study that calibrated a two-judge
|
|
43
|
+
consensus against human majority states exactly that.
|
|
44
|
+
|
|
45
|
+
## The property layer
|
|
46
|
+
|
|
47
|
+
Metamorphic relations: instead of checking an exact output, check how the output **must** change
|
|
48
|
+
when the input changes in a controlled way.
|
|
49
|
+
|
|
50
|
+
| Relation | Guarantee |
|
|
51
|
+
|---|---|
|
|
52
|
+
| scale the objective by `k > 0` | the argmin cannot move |
|
|
53
|
+
| add a redundant constraint | the feasible set cannot change |
|
|
54
|
+
| tighten a constraint | the optimum cannot improve |
|
|
55
|
+
|
|
56
|
+
The standard objection to metamorphic testing is that the relations must be authored per problem
|
|
57
|
+
class and so do not generalise to arbitrary programs. That is the design here: the target families
|
|
58
|
+
are narrow typed classes, so the relations are written once per class.
|
|
59
|
+
|
|
60
|
+
A candidate that solves and then fails one of these is wrong in a way no solver would have reported.
|
|
61
|
+
|
|
62
|
+
## Install
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pip install copela # the harness
|
|
66
|
+
pip install "copela[solvers]" # plus Pyomo and HiGHS
|
|
67
|
+
pip install "copela[all]" # plus the provider SDKs
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Use
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from copela import Budget, Case, Ledger, Sweep, Target, build
|
|
74
|
+
from copela.providers import get
|
|
75
|
+
from copela.solvers.highs import make_solver
|
|
76
|
+
|
|
77
|
+
sweep = Sweep(
|
|
78
|
+
ledger=Ledger("runs.jsonl"),
|
|
79
|
+
budget=Budget(limit_usd=5.00, max_consecutive_failures=10),
|
|
80
|
+
providers={"anthropic": get("anthropic"), "ollama": get("ollama")},
|
|
81
|
+
build_prompt=my_prompt, # the prompt strategy is what a study varies
|
|
82
|
+
parse_response=my_parser,
|
|
83
|
+
solve=make_solver(),
|
|
84
|
+
repeats=5,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
sweep.run(cases, [Target("anthropic", "claude-sonnet-5"),
|
|
88
|
+
Target("ollama", "qwen3:8b")])
|
|
89
|
+
|
|
90
|
+
print(build(Ledger("runs.jsonl")).to_text())
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
From the shell:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
copela models # what each provider serves, and what it costs
|
|
97
|
+
copela solve problem.json # solve one formalization, no model involved
|
|
98
|
+
copela report runs.jsonl # the gap
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Why many models, not one
|
|
102
|
+
|
|
103
|
+
Because a benchmark of AI-assisted modelling and simulation reports that
|
|
104
|
+
[no single model dominates across engine types](https://arxiv.org/abs/2605.28994), with
|
|
105
|
+
task-specific tradeoffs between speed and accuracy. A ranking claimed from one model contradicts a
|
|
106
|
+
published result, so the provider seam is a requirement rather than tidiness: Anthropic, Groq and
|
|
107
|
+
local models through Ollama, behind one interface, with no vendor name anywhere outside
|
|
108
|
+
`copela/providers/`. A test enforces that.
|
|
109
|
+
|
|
110
|
+
## What reproducibility means here
|
|
111
|
+
|
|
112
|
+
Temperature zero does not make hosted inference deterministic. The dominant cause is the batch-size
|
|
113
|
+
dependence of reduction kernels rather than floating-point non-associativity, and bitwise
|
|
114
|
+
determinism costs a third to two thirds of throughput and cannot be bought over a hosted API.
|
|
115
|
+
|
|
116
|
+
So the harness pins what it can (model, version, temperature, seed, provider fingerprint), records
|
|
117
|
+
`n` repeats, and reports a **rate with a Wilson interval**. It never presents a single run as the
|
|
118
|
+
result, and 5 of 5 is not reported as certainly 1.0.
|
|
119
|
+
|
|
120
|
+
## The ledger
|
|
121
|
+
|
|
122
|
+
One append-only JSONL record per call, carrying its full provenance. A record is never edited,
|
|
123
|
+
because a ledger that can be rewritten is not evidence. It is also the resume mechanism: a sweep
|
|
124
|
+
reads it and skips the calls already done.
|
|
125
|
+
|
|
126
|
+
## Cost
|
|
127
|
+
|
|
128
|
+
Every sweep declares a budget and a kill criterion before it runs, and the guard refuses the call
|
|
129
|
+
that **would** exceed the ceiling rather than noticing afterwards.
|
|
130
|
+
|
|
131
|
+
## Documentation
|
|
132
|
+
|
|
133
|
+
The wiki is in [`docs/`](docs/). The design document, written before the code, is
|
|
134
|
+
[`docs/design/SDD.md`](docs/design/SDD.md); each of its twelve requirements names the test that
|
|
135
|
+
verifies it.
|
|
136
|
+
|
|
137
|
+
## Related
|
|
138
|
+
|
|
139
|
+
[`planteo`](https://github.com/fsantibanezleal/CAOS_Planteo) is the representation this consumes: a
|
|
140
|
+
typed problem with dimensions on every quantity and provenance on every element.
|
|
141
|
+
|
|
142
|
+
## License
|
|
143
|
+
|
|
144
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=69", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "copela"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Run narrative-to-formal translation across many language models and score it with oracles that are not language models: executable, structural and property layers, reported separately."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Felipe Santibanez-Leal", email = "fsantibanez@gmail.com" }]
|
|
13
|
+
keywords = [
|
|
14
|
+
"evaluation",
|
|
15
|
+
"benchmark",
|
|
16
|
+
"optimization",
|
|
17
|
+
"formalization",
|
|
18
|
+
"autoformalization",
|
|
19
|
+
"metamorphic-testing",
|
|
20
|
+
"llm",
|
|
21
|
+
]
|
|
22
|
+
classifiers = [
|
|
23
|
+
"Development Status :: 3 - Alpha",
|
|
24
|
+
"Intended Audience :: Science/Research",
|
|
25
|
+
"License :: OSI Approved :: MIT License",
|
|
26
|
+
"Programming Language :: Python :: 3",
|
|
27
|
+
"Programming Language :: Python :: 3.11",
|
|
28
|
+
"Programming Language :: Python :: 3.12",
|
|
29
|
+
"Programming Language :: Python :: 3.13",
|
|
30
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
31
|
+
"Typing :: Typed",
|
|
32
|
+
]
|
|
33
|
+
dependencies = ["planteo>=0.1.0"]
|
|
34
|
+
|
|
35
|
+
[project.optional-dependencies]
|
|
36
|
+
solvers = ["pyomo>=6.7", "highspy>=1.7"]
|
|
37
|
+
anthropic = ["anthropic>=0.40"]
|
|
38
|
+
openai = ["openai>=1.50"]
|
|
39
|
+
all = ["pyomo>=6.7", "highspy>=1.7", "anthropic>=0.40", "openai>=1.50"]
|
|
40
|
+
dev = ["pytest>=8.0", "ruff>=0.6"]
|
|
41
|
+
|
|
42
|
+
[project.scripts]
|
|
43
|
+
copela = "copela.cli:main"
|
|
44
|
+
|
|
45
|
+
[project.urls]
|
|
46
|
+
Homepage = "https://github.com/fsantibanezleal/CAOS_Copela"
|
|
47
|
+
Source = "https://github.com/fsantibanezleal/CAOS_Copela"
|
|
48
|
+
Issues = "https://github.com/fsantibanezleal/CAOS_Copela/issues"
|
|
49
|
+
Changelog = "https://github.com/fsantibanezleal/CAOS_Copela/blob/main/CHANGELOG.md"
|
|
50
|
+
|
|
51
|
+
[tool.setuptools.packages.find]
|
|
52
|
+
where = ["src"]
|
|
53
|
+
|
|
54
|
+
[tool.setuptools.package-data]
|
|
55
|
+
copela = ["py.typed"]
|
|
56
|
+
|
|
57
|
+
[tool.pytest.ini_options]
|
|
58
|
+
testpaths = ["tests"]
|
|
59
|
+
addopts = "-q"
|
|
60
|
+
pythonpath = ["."]
|
|
61
|
+
|
|
62
|
+
[tool.ruff]
|
|
63
|
+
line-length = 100
|
|
64
|
+
target-version = "py311"
|
|
65
|
+
|
|
66
|
+
[tool.ruff.lint]
|
|
67
|
+
select = ["E", "F", "I", "UP", "B"]
|
|
68
|
+
ignore = [
|
|
69
|
+
"E501",
|
|
70
|
+
# UP042 wants StrEnum. These enums inherit str deliberately: their .value is what lands in the
|
|
71
|
+
# JSONL ledger, and StrEnum changes str() behaviour in a way that would alter what is written.
|
|
72
|
+
"UP042",
|
|
73
|
+
]
|
copela-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""copela: run narrative-to-formal translation across many models, and score it with oracles
|
|
2
|
+
that are not language models.
|
|
3
|
+
|
|
4
|
+
The name is the cupel used in fire assay, the vessel that separates the metal from the lead. That is
|
|
5
|
+
the job here: separating a formalization that is faithful from one that merely runs.
|
|
6
|
+
|
|
7
|
+
Four layers, reported separately and never merged into one score:
|
|
8
|
+
|
|
9
|
+
1. executable, did it run, solve, compile
|
|
10
|
+
2. structural, is it the same model as the reference
|
|
11
|
+
3. property, do the invariants of this class hold
|
|
12
|
+
4. judge, what a model says, recorded as a labelled screening aggregate and never as truth
|
|
13
|
+
|
|
14
|
+
The headline is the subtraction: how often the artifact ran, minus how often it was right.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from .budget import Budget, BudgetExceeded, estimate
|
|
20
|
+
from .ledger import CallKey, Ledger, LedgerError, Record
|
|
21
|
+
from .providers import Provider, ProviderError, StubProvider
|
|
22
|
+
from .report import Cell, Report, build
|
|
23
|
+
from .sweep import Case, Sweep, Target
|
|
24
|
+
from .verdicts import (
|
|
25
|
+
JUDGE_LABEL,
|
|
26
|
+
CandidateVerdict,
|
|
27
|
+
Layer,
|
|
28
|
+
LayerResult,
|
|
29
|
+
Outcome,
|
|
30
|
+
Rate,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
__version__ = "0.1.0"
|
|
34
|
+
__display_version__ = "0.01.000"
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
"JUDGE_LABEL",
|
|
38
|
+
"Budget",
|
|
39
|
+
"BudgetExceeded",
|
|
40
|
+
"CallKey",
|
|
41
|
+
"CandidateVerdict",
|
|
42
|
+
"Case",
|
|
43
|
+
"Cell",
|
|
44
|
+
"Layer",
|
|
45
|
+
"LayerResult",
|
|
46
|
+
"Ledger",
|
|
47
|
+
"LedgerError",
|
|
48
|
+
"Outcome",
|
|
49
|
+
"Provider",
|
|
50
|
+
"ProviderError",
|
|
51
|
+
"Rate",
|
|
52
|
+
"Record",
|
|
53
|
+
"Report",
|
|
54
|
+
"StubProvider",
|
|
55
|
+
"Sweep",
|
|
56
|
+
"Target",
|
|
57
|
+
"__display_version__",
|
|
58
|
+
"__version__",
|
|
59
|
+
"build",
|
|
60
|
+
"estimate",
|
|
61
|
+
]
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""The budget guard: stop before the limit, not after it.
|
|
2
|
+
|
|
3
|
+
A sweep is cases times models times repeats, and each cell costs money. The guard exists because the
|
|
4
|
+
failure it prevents has happened on this account: an unattended job consumed a week of quota in
|
|
5
|
+
about a day.
|
|
6
|
+
|
|
7
|
+
The rule is that the guard refuses the call that *would* exceed the budget, rather than noticing
|
|
8
|
+
afterwards. A guard that reports an overrun is an accountant, not a guard.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class BudgetExceeded(RuntimeError):
|
|
17
|
+
"""Raised when a call would take the sweep past its declared budget."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class Budget:
|
|
22
|
+
"""A spend ceiling and a kill criterion, both declared before the sweep runs.
|
|
23
|
+
|
|
24
|
+
``limit_usd`` is the hard ceiling. ``max_consecutive_failures`` is the kill criterion: a sweep
|
|
25
|
+
whose calls are all failing is buying nothing, and continuing to the ceiling is waste.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
limit_usd: float
|
|
29
|
+
max_consecutive_failures: int = 10
|
|
30
|
+
spent_usd: float = 0.0
|
|
31
|
+
consecutive_failures: int = 0
|
|
32
|
+
calls: int = 0
|
|
33
|
+
|
|
34
|
+
def __post_init__(self) -> None:
|
|
35
|
+
if self.limit_usd < 0:
|
|
36
|
+
raise ValueError("a budget cannot be negative")
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def remaining_usd(self) -> float:
|
|
40
|
+
return max(0.0, self.limit_usd - self.spent_usd)
|
|
41
|
+
|
|
42
|
+
def check(self, estimated_usd: float) -> None:
|
|
43
|
+
"""Raise if this call would exceed the ceiling. Call BEFORE spending."""
|
|
44
|
+
if self.spent_usd + estimated_usd > self.limit_usd:
|
|
45
|
+
raise BudgetExceeded(
|
|
46
|
+
f"this call is estimated at {estimated_usd:.4f} USD and "
|
|
47
|
+
f"{self.spent_usd:.4f} of {self.limit_usd:.4f} is already spent; "
|
|
48
|
+
"stopping before the budget rather than after it"
|
|
49
|
+
)
|
|
50
|
+
if self.consecutive_failures >= self.max_consecutive_failures:
|
|
51
|
+
raise BudgetExceeded(
|
|
52
|
+
f"{self.consecutive_failures} consecutive failures reached the kill criterion; "
|
|
53
|
+
"a sweep that is failing every call is buying nothing"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def charge(self, actual_usd: float, *, failed: bool = False) -> None:
|
|
57
|
+
"""Record what a completed call actually cost."""
|
|
58
|
+
self.spent_usd += actual_usd
|
|
59
|
+
self.calls += 1
|
|
60
|
+
self.consecutive_failures = self.consecutive_failures + 1 if failed else 0
|
|
61
|
+
|
|
62
|
+
def describe(self) -> str:
|
|
63
|
+
return (
|
|
64
|
+
f"{self.spent_usd:.4f} of {self.limit_usd:.4f} USD over {self.calls} call(s), "
|
|
65
|
+
f"{self.remaining_usd:.4f} remaining"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def estimate(
|
|
70
|
+
prompt: str,
|
|
71
|
+
expected_output_tokens: int,
|
|
72
|
+
input_per_mtok: float,
|
|
73
|
+
output_per_mtok: float,
|
|
74
|
+
) -> float:
|
|
75
|
+
"""A cost estimate before the call, from a crude token count.
|
|
76
|
+
|
|
77
|
+
Four characters per token is a rough English average and it is deliberately not refined: the
|
|
78
|
+
estimate exists to keep the guard conservative, and a guard that under-estimates is worse than
|
|
79
|
+
one that stops slightly early.
|
|
80
|
+
"""
|
|
81
|
+
input_tokens = max(1, len(prompt) // 4)
|
|
82
|
+
return (
|
|
83
|
+
input_tokens * input_per_mtok + expected_output_tokens * output_per_mtok
|
|
84
|
+
) / 1_000_000
|