weakincentives 0.9.0__py3-none-any.whl
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.
- weakincentives/__init__.py +67 -0
- weakincentives/adapters/__init__.py +37 -0
- weakincentives/adapters/_names.py +32 -0
- weakincentives/adapters/_provider_protocols.py +69 -0
- weakincentives/adapters/_tool_messages.py +80 -0
- weakincentives/adapters/core.py +102 -0
- weakincentives/adapters/litellm.py +254 -0
- weakincentives/adapters/openai.py +254 -0
- weakincentives/adapters/shared.py +1021 -0
- weakincentives/cli/__init__.py +23 -0
- weakincentives/cli/wink.py +58 -0
- weakincentives/dbc/__init__.py +412 -0
- weakincentives/deadlines.py +58 -0
- weakincentives/prompt/__init__.py +105 -0
- weakincentives/prompt/_generic_params_specializer.py +64 -0
- weakincentives/prompt/_normalization.py +48 -0
- weakincentives/prompt/_overrides_protocols.py +33 -0
- weakincentives/prompt/_types.py +34 -0
- weakincentives/prompt/chapter.py +146 -0
- weakincentives/prompt/composition.py +281 -0
- weakincentives/prompt/errors.py +57 -0
- weakincentives/prompt/markdown.py +108 -0
- weakincentives/prompt/overrides/__init__.py +59 -0
- weakincentives/prompt/overrides/_fs.py +164 -0
- weakincentives/prompt/overrides/inspection.py +141 -0
- weakincentives/prompt/overrides/local_store.py +275 -0
- weakincentives/prompt/overrides/validation.py +534 -0
- weakincentives/prompt/overrides/versioning.py +269 -0
- weakincentives/prompt/prompt.py +353 -0
- weakincentives/prompt/protocols.py +103 -0
- weakincentives/prompt/registry.py +375 -0
- weakincentives/prompt/rendering.py +288 -0
- weakincentives/prompt/response_format.py +60 -0
- weakincentives/prompt/section.py +166 -0
- weakincentives/prompt/structured_output.py +179 -0
- weakincentives/prompt/tool.py +397 -0
- weakincentives/prompt/tool_result.py +30 -0
- weakincentives/py.typed +0 -0
- weakincentives/runtime/__init__.py +82 -0
- weakincentives/runtime/events/__init__.py +126 -0
- weakincentives/runtime/events/_types.py +110 -0
- weakincentives/runtime/logging.py +284 -0
- weakincentives/runtime/session/__init__.py +46 -0
- weakincentives/runtime/session/_slice_types.py +24 -0
- weakincentives/runtime/session/_types.py +55 -0
- weakincentives/runtime/session/dataclasses.py +29 -0
- weakincentives/runtime/session/protocols.py +34 -0
- weakincentives/runtime/session/reducer_context.py +40 -0
- weakincentives/runtime/session/reducers.py +82 -0
- weakincentives/runtime/session/selectors.py +56 -0
- weakincentives/runtime/session/session.py +387 -0
- weakincentives/runtime/session/snapshots.py +310 -0
- weakincentives/serde/__init__.py +19 -0
- weakincentives/serde/_utils.py +240 -0
- weakincentives/serde/dataclass_serde.py +55 -0
- weakincentives/serde/dump.py +189 -0
- weakincentives/serde/parse.py +417 -0
- weakincentives/serde/schema.py +260 -0
- weakincentives/tools/__init__.py +154 -0
- weakincentives/tools/_context.py +38 -0
- weakincentives/tools/asteval.py +853 -0
- weakincentives/tools/errors.py +26 -0
- weakincentives/tools/planning.py +831 -0
- weakincentives/tools/podman.py +1655 -0
- weakincentives/tools/subagents.py +346 -0
- weakincentives/tools/vfs.py +1390 -0
- weakincentives/types/__init__.py +35 -0
- weakincentives/types/json.py +45 -0
- weakincentives-0.9.0.dist-info/METADATA +775 -0
- weakincentives-0.9.0.dist-info/RECORD +73 -0
- weakincentives-0.9.0.dist-info/WHEEL +4 -0
- weakincentives-0.9.0.dist-info/entry_points.txt +2 -0
- weakincentives-0.9.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
2
|
+
# you may not use this file except in compliance with the License.
|
|
3
|
+
# You may obtain a copy of the License at
|
|
4
|
+
#
|
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
#
|
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
10
|
+
# See the License for the specific language governing permissions and
|
|
11
|
+
# limitations under the License.
|
|
12
|
+
|
|
13
|
+
"""Command line interfaces for the weakincentives package."""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from . import wink
|
|
18
|
+
|
|
19
|
+
__all__ = ["wink"]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def __dir__() -> list[str]:
|
|
23
|
+
return sorted({*globals().keys(), *(__all__)})
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
2
|
+
# you may not use this file except in compliance with the License.
|
|
3
|
+
# You may obtain a copy of the License at
|
|
4
|
+
#
|
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
#
|
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
10
|
+
# See the License for the specific language governing permissions and
|
|
11
|
+
# limitations under the License.
|
|
12
|
+
|
|
13
|
+
"""Command line entry points for the ``wink`` executable."""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
from collections.abc import Sequence
|
|
19
|
+
|
|
20
|
+
from ..runtime.logging import configure_logging, get_logger
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
24
|
+
"""Run the wink CLI."""
|
|
25
|
+
|
|
26
|
+
parser = _build_parser()
|
|
27
|
+
args = parser.parse_args(list(argv) if argv is not None else None)
|
|
28
|
+
|
|
29
|
+
configure_logging(level=args.log_level, json_mode=args.json_logs)
|
|
30
|
+
logger = get_logger(__name__)
|
|
31
|
+
|
|
32
|
+
logger.info(
|
|
33
|
+
"wink CLI placeholder executed.",
|
|
34
|
+
event="wink.placeholder",
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
return 0
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
41
|
+
parser = argparse.ArgumentParser(
|
|
42
|
+
prog="wink",
|
|
43
|
+
description="Placeholder command line interface for future wink features.",
|
|
44
|
+
)
|
|
45
|
+
_ = parser.add_argument(
|
|
46
|
+
"--log-level",
|
|
47
|
+
choices=("CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG", "NOTSET"),
|
|
48
|
+
default=None,
|
|
49
|
+
help="Override the log level emitted by the CLI.",
|
|
50
|
+
)
|
|
51
|
+
_ = parser.add_argument(
|
|
52
|
+
"--json-logs",
|
|
53
|
+
action=argparse.BooleanOptionalAction,
|
|
54
|
+
default=True,
|
|
55
|
+
help="Emit structured JSON logs (disable with --no-json-logs).",
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
return parser
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
2
|
+
# you may not use this file except in compliance with the License.
|
|
3
|
+
# You may obtain a copy of the License at
|
|
4
|
+
#
|
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
#
|
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
10
|
+
# See the License for the specific language governing permissions and
|
|
11
|
+
# limitations under the License.
|
|
12
|
+
|
|
13
|
+
"""Design by contract utilities for :mod:`weakincentives`."""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import builtins
|
|
18
|
+
import copy
|
|
19
|
+
import logging
|
|
20
|
+
import os
|
|
21
|
+
from collections.abc import Callable, Iterator, Mapping, Sequence
|
|
22
|
+
from contextlib import ExitStack, contextmanager
|
|
23
|
+
from functools import wraps
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import ParamSpec, TypeVar, cast
|
|
26
|
+
|
|
27
|
+
from ..types import ContractResult
|
|
28
|
+
|
|
29
|
+
P = ParamSpec("P")
|
|
30
|
+
Q = ParamSpec("Q")
|
|
31
|
+
R = TypeVar("R")
|
|
32
|
+
S = TypeVar("S")
|
|
33
|
+
T = TypeVar("T", bound=object)
|
|
34
|
+
|
|
35
|
+
ContractCallable = Callable[..., ContractResult | object]
|
|
36
|
+
|
|
37
|
+
_ENV_FLAG = "WEAKINCENTIVES_DBC"
|
|
38
|
+
_forced_state: bool | None = None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _coerce_flag(value: str | None) -> bool:
|
|
42
|
+
if value is None:
|
|
43
|
+
return False
|
|
44
|
+
lowered = value.strip().lower()
|
|
45
|
+
return lowered not in {"", "0", "false", "off", "no"}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def dbc_active() -> bool:
|
|
49
|
+
"""Return ``True`` when DbC checks should run."""
|
|
50
|
+
|
|
51
|
+
if _forced_state is not None:
|
|
52
|
+
return _forced_state
|
|
53
|
+
return _coerce_flag(os.getenv(_ENV_FLAG))
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _qualname(target: object) -> str:
|
|
57
|
+
return getattr(target, "__qualname__", repr(target))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def enable_dbc() -> None:
|
|
61
|
+
"""Force DbC enforcement on."""
|
|
62
|
+
|
|
63
|
+
global _forced_state
|
|
64
|
+
_forced_state = True
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def disable_dbc() -> None:
|
|
68
|
+
"""Force DbC enforcement off."""
|
|
69
|
+
|
|
70
|
+
global _forced_state
|
|
71
|
+
_forced_state = False
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@contextmanager
|
|
75
|
+
def dbc_enabled(active: bool = True) -> Iterator[None]:
|
|
76
|
+
"""Temporarily set the DbC flag inside a ``with`` block."""
|
|
77
|
+
|
|
78
|
+
global _forced_state
|
|
79
|
+
previous = _forced_state
|
|
80
|
+
_forced_state = active
|
|
81
|
+
try:
|
|
82
|
+
yield
|
|
83
|
+
finally:
|
|
84
|
+
_forced_state = previous
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _normalize_contract_result(
|
|
88
|
+
result: ContractResult | object,
|
|
89
|
+
) -> tuple[bool, str | None]:
|
|
90
|
+
if isinstance(result, tuple):
|
|
91
|
+
sequence_result = cast(Sequence[object], result)
|
|
92
|
+
if not sequence_result:
|
|
93
|
+
msg = "Contract callables must not return empty tuples"
|
|
94
|
+
raise TypeError(msg)
|
|
95
|
+
outcome = bool(sequence_result[0])
|
|
96
|
+
message = None if len(sequence_result) == 1 else str(sequence_result[1])
|
|
97
|
+
return outcome, message
|
|
98
|
+
if isinstance(result, bool):
|
|
99
|
+
return result, None
|
|
100
|
+
if result is None:
|
|
101
|
+
return False, None
|
|
102
|
+
return bool(result), None
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _contract_failure_message(
|
|
106
|
+
*,
|
|
107
|
+
kind: str,
|
|
108
|
+
func: Callable[..., object],
|
|
109
|
+
predicate: ContractCallable,
|
|
110
|
+
args: tuple[object, ...],
|
|
111
|
+
kwargs: Mapping[str, object],
|
|
112
|
+
detail: str | None,
|
|
113
|
+
) -> str:
|
|
114
|
+
predicate_name = getattr(predicate, "__name__", repr(predicate))
|
|
115
|
+
base = (
|
|
116
|
+
f"{kind} contract for {_qualname(func)} failed via {predicate_name}."
|
|
117
|
+
f" Args={args!r} Kwargs={kwargs!r}"
|
|
118
|
+
)
|
|
119
|
+
if detail:
|
|
120
|
+
return f"{base} Details: {detail}"
|
|
121
|
+
return base
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _evaluate_contract(
|
|
125
|
+
*,
|
|
126
|
+
kind: str,
|
|
127
|
+
func: Callable[..., object],
|
|
128
|
+
predicate: ContractCallable,
|
|
129
|
+
args: tuple[object, ...],
|
|
130
|
+
kwargs: Mapping[str, object],
|
|
131
|
+
) -> None:
|
|
132
|
+
try:
|
|
133
|
+
result = predicate(*args, **kwargs)
|
|
134
|
+
except AssertionError:
|
|
135
|
+
raise
|
|
136
|
+
except Exception as exc: # pragma: no cover - diagnostics are important
|
|
137
|
+
msg = (
|
|
138
|
+
f"{kind} contract for {_qualname(func)} raised {type(exc).__name__}: {exc}"
|
|
139
|
+
)
|
|
140
|
+
raise AssertionError(msg) from exc
|
|
141
|
+
outcome, detail = _normalize_contract_result(result)
|
|
142
|
+
if not outcome:
|
|
143
|
+
raise AssertionError(
|
|
144
|
+
_contract_failure_message(
|
|
145
|
+
kind=kind,
|
|
146
|
+
func=func,
|
|
147
|
+
predicate=predicate,
|
|
148
|
+
args=args,
|
|
149
|
+
kwargs=kwargs,
|
|
150
|
+
detail=detail,
|
|
151
|
+
)
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def require(
|
|
156
|
+
*predicates: ContractCallable,
|
|
157
|
+
) -> Callable[[Callable[P, R]], Callable[P, R]]:
|
|
158
|
+
"""Validate preconditions before invoking the wrapped callable."""
|
|
159
|
+
|
|
160
|
+
if not predicates:
|
|
161
|
+
msg = "@require expects at least one predicate"
|
|
162
|
+
raise ValueError(msg)
|
|
163
|
+
|
|
164
|
+
def decorator(func: Callable[P, R]) -> Callable[P, R]:
|
|
165
|
+
@wraps(func)
|
|
166
|
+
def wrapped(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
167
|
+
if dbc_active():
|
|
168
|
+
for predicate in predicates:
|
|
169
|
+
_evaluate_contract(
|
|
170
|
+
kind="require",
|
|
171
|
+
func=func,
|
|
172
|
+
predicate=predicate,
|
|
173
|
+
args=tuple(args),
|
|
174
|
+
kwargs=dict(kwargs),
|
|
175
|
+
)
|
|
176
|
+
return func(*args, **kwargs)
|
|
177
|
+
|
|
178
|
+
return wrapped
|
|
179
|
+
|
|
180
|
+
return decorator
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def ensure(*predicates: ContractCallable) -> Callable[[Callable[P, R]], Callable[P, R]]:
|
|
184
|
+
"""Validate postconditions once the callable returns or raises."""
|
|
185
|
+
|
|
186
|
+
if not predicates:
|
|
187
|
+
msg = "@ensure expects at least one predicate"
|
|
188
|
+
raise ValueError(msg)
|
|
189
|
+
|
|
190
|
+
def decorator(func: Callable[P, R]) -> Callable[P, R]:
|
|
191
|
+
@wraps(func)
|
|
192
|
+
def wrapped(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
193
|
+
if not dbc_active():
|
|
194
|
+
return func(*args, **kwargs)
|
|
195
|
+
|
|
196
|
+
try:
|
|
197
|
+
result = func(*args, **kwargs)
|
|
198
|
+
except BaseException as exc:
|
|
199
|
+
for predicate in predicates:
|
|
200
|
+
_evaluate_contract(
|
|
201
|
+
kind="ensure",
|
|
202
|
+
func=func,
|
|
203
|
+
predicate=predicate,
|
|
204
|
+
args=tuple(args),
|
|
205
|
+
kwargs={**kwargs, "exception": exc},
|
|
206
|
+
)
|
|
207
|
+
raise
|
|
208
|
+
|
|
209
|
+
for predicate in predicates:
|
|
210
|
+
_evaluate_contract(
|
|
211
|
+
kind="ensure",
|
|
212
|
+
func=func,
|
|
213
|
+
predicate=predicate,
|
|
214
|
+
args=tuple(args),
|
|
215
|
+
kwargs={**kwargs, "result": result},
|
|
216
|
+
)
|
|
217
|
+
return result
|
|
218
|
+
|
|
219
|
+
return wrapped
|
|
220
|
+
|
|
221
|
+
return decorator
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def skip_invariant(func: Callable[Q, S]) -> Callable[Q, S]: # noqa: UP047
|
|
225
|
+
"""Mark a method so invariants are not evaluated around it."""
|
|
226
|
+
|
|
227
|
+
func.__dbc_skip_invariant__ = True # type: ignore[attr-defined]
|
|
228
|
+
return func
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _check_invariants(
|
|
232
|
+
predicates: tuple[ContractCallable, ...],
|
|
233
|
+
*,
|
|
234
|
+
instance: object,
|
|
235
|
+
func: Callable[..., object],
|
|
236
|
+
) -> None:
|
|
237
|
+
for predicate in predicates:
|
|
238
|
+
_evaluate_contract(
|
|
239
|
+
kind="invariant",
|
|
240
|
+
func=func,
|
|
241
|
+
predicate=predicate,
|
|
242
|
+
args=(instance,),
|
|
243
|
+
kwargs={},
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def invariant(*predicates: ContractCallable) -> Callable[[type[T]], type[T]]:
|
|
248
|
+
"""Enforce invariants before and after public method calls."""
|
|
249
|
+
|
|
250
|
+
if not predicates:
|
|
251
|
+
msg = "@invariant expects at least one predicate"
|
|
252
|
+
raise ValueError(msg)
|
|
253
|
+
|
|
254
|
+
predicate_tuple = tuple(predicates)
|
|
255
|
+
|
|
256
|
+
def decorator(cls: type[T]) -> type[T]:
|
|
257
|
+
original_init = cls.__init__
|
|
258
|
+
|
|
259
|
+
@wraps(original_init)
|
|
260
|
+
def init_wrapper(self: T, *args: object, **kwargs: object) -> None:
|
|
261
|
+
original_init(self, *args, **kwargs)
|
|
262
|
+
if dbc_active():
|
|
263
|
+
_check_invariants(predicate_tuple, instance=self, func=original_init)
|
|
264
|
+
|
|
265
|
+
type.__setattr__(cls, "__init__", init_wrapper)
|
|
266
|
+
|
|
267
|
+
for attribute_name, attribute in list(cls.__dict__.items()):
|
|
268
|
+
if attribute_name.startswith("_"):
|
|
269
|
+
continue
|
|
270
|
+
if getattr(attribute, "__dbc_skip_invariant__", False):
|
|
271
|
+
continue
|
|
272
|
+
if isinstance(attribute, (staticmethod, classmethod)):
|
|
273
|
+
continue
|
|
274
|
+
if not callable(attribute):
|
|
275
|
+
continue
|
|
276
|
+
|
|
277
|
+
def make_wrapper(method: Callable[..., object]) -> Callable[..., object]:
|
|
278
|
+
@wraps(method)
|
|
279
|
+
def wrapper(self: T, *args: object, **kwargs: object) -> object:
|
|
280
|
+
if not dbc_active():
|
|
281
|
+
return method(self, *args, **kwargs)
|
|
282
|
+
_check_invariants(predicate_tuple, instance=self, func=method)
|
|
283
|
+
try:
|
|
284
|
+
return method(self, *args, **kwargs)
|
|
285
|
+
finally:
|
|
286
|
+
_check_invariants(predicate_tuple, instance=self, func=method)
|
|
287
|
+
|
|
288
|
+
return wrapper
|
|
289
|
+
|
|
290
|
+
setattr(cls, attribute_name, make_wrapper(attribute))
|
|
291
|
+
|
|
292
|
+
return cls
|
|
293
|
+
|
|
294
|
+
return decorator
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
_SNAPSHOT_SENTINEL = object()
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _snapshot(value: object) -> object:
|
|
301
|
+
try:
|
|
302
|
+
return copy.deepcopy(value)
|
|
303
|
+
except Exception:
|
|
304
|
+
return _SNAPSHOT_SENTINEL
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _compare_snapshots(
|
|
308
|
+
*,
|
|
309
|
+
func: Callable[..., object],
|
|
310
|
+
args: tuple[object, ...],
|
|
311
|
+
kwargs: Mapping[str, object],
|
|
312
|
+
snap_args: tuple[object, ...],
|
|
313
|
+
snap_kwargs: Mapping[str, object],
|
|
314
|
+
) -> None:
|
|
315
|
+
for index, (original, snapshot) in enumerate(zip(args, snap_args, strict=False)):
|
|
316
|
+
if snapshot is _SNAPSHOT_SENTINEL:
|
|
317
|
+
continue
|
|
318
|
+
if original != snapshot:
|
|
319
|
+
msg = (
|
|
320
|
+
f"pure contract for {_qualname(func)} detected mutation of "
|
|
321
|
+
f"positional argument {index}"
|
|
322
|
+
)
|
|
323
|
+
raise AssertionError(msg)
|
|
324
|
+
|
|
325
|
+
for key, snapshot in snap_kwargs.items():
|
|
326
|
+
if snapshot is _SNAPSHOT_SENTINEL:
|
|
327
|
+
continue
|
|
328
|
+
if kwargs[key] != snapshot:
|
|
329
|
+
msg = (
|
|
330
|
+
f"pure contract for {_qualname(func)} detected mutation of "
|
|
331
|
+
f"keyword argument '{key}'"
|
|
332
|
+
)
|
|
333
|
+
raise AssertionError(msg)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def _pure_violation(func: Callable[..., object], target: str) -> Callable[..., object]:
|
|
337
|
+
def raiser(*args: object, **kwargs: object) -> object: # pragma: no cover - trivial
|
|
338
|
+
msg = f"pure contract for {_qualname(func)} forbids calling {target}"
|
|
339
|
+
raise AssertionError(msg)
|
|
340
|
+
|
|
341
|
+
return raiser
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
@contextmanager
|
|
345
|
+
def _pure_environment(func: Callable[..., object]) -> Iterator[None]:
|
|
346
|
+
with ExitStack() as stack:
|
|
347
|
+
stack.enter_context(
|
|
348
|
+
_patch(builtins, "open", _pure_violation(func, "builtins.open"))
|
|
349
|
+
)
|
|
350
|
+
stack.enter_context(
|
|
351
|
+
_patch(Path, "write_text", _pure_violation(func, "Path.write_text"))
|
|
352
|
+
)
|
|
353
|
+
stack.enter_context(
|
|
354
|
+
_patch(Path, "write_bytes", _pure_violation(func, "Path.write_bytes"))
|
|
355
|
+
)
|
|
356
|
+
stack.enter_context(
|
|
357
|
+
_patch(logging.Logger, "_log", _pure_violation(func, "logging"))
|
|
358
|
+
)
|
|
359
|
+
yield
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
@contextmanager
|
|
363
|
+
def _patch(
|
|
364
|
+
obj: object, attribute: str, replacement: Callable[..., object]
|
|
365
|
+
) -> Iterator[None]:
|
|
366
|
+
original = getattr(obj, attribute)
|
|
367
|
+
setattr(obj, attribute, replacement)
|
|
368
|
+
try:
|
|
369
|
+
yield
|
|
370
|
+
finally:
|
|
371
|
+
setattr(obj, attribute, original)
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def pure(func: Callable[P, R]) -> Callable[P, R]: # noqa: UP047
|
|
375
|
+
"""Validate that the wrapped callable behaves like a pure function."""
|
|
376
|
+
|
|
377
|
+
@wraps(func)
|
|
378
|
+
def wrapped(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
379
|
+
if not dbc_active():
|
|
380
|
+
return func(*args, **kwargs)
|
|
381
|
+
|
|
382
|
+
snapshot_args = tuple(_snapshot(arg) for arg in args)
|
|
383
|
+
snapshot_kwargs: dict[str, object] = {
|
|
384
|
+
key: _snapshot(value) for key, value in kwargs.items()
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
with _pure_environment(func):
|
|
388
|
+
result = func(*args, **kwargs)
|
|
389
|
+
|
|
390
|
+
_compare_snapshots(
|
|
391
|
+
func=func,
|
|
392
|
+
args=tuple(args),
|
|
393
|
+
kwargs=dict(kwargs),
|
|
394
|
+
snap_args=snapshot_args,
|
|
395
|
+
snap_kwargs=snapshot_kwargs,
|
|
396
|
+
)
|
|
397
|
+
return result
|
|
398
|
+
|
|
399
|
+
return wrapped
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
__all__ = [
|
|
403
|
+
"dbc_active",
|
|
404
|
+
"dbc_enabled",
|
|
405
|
+
"disable_dbc",
|
|
406
|
+
"enable_dbc",
|
|
407
|
+
"ensure",
|
|
408
|
+
"invariant",
|
|
409
|
+
"pure",
|
|
410
|
+
"require",
|
|
411
|
+
"skip_invariant",
|
|
412
|
+
]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
2
|
+
# you may not use this file except in compliance with the License.
|
|
3
|
+
# You may obtain a copy of the License at
|
|
4
|
+
#
|
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
#
|
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
10
|
+
# See the License for the specific language governing permissions and
|
|
11
|
+
# limitations under the License.
|
|
12
|
+
|
|
13
|
+
"""Deadline utilities for orchestrating prompt evaluations."""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
from datetime import UTC, datetime, timedelta
|
|
19
|
+
|
|
20
|
+
__all__ = ["Deadline"]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _utcnow() -> datetime:
|
|
24
|
+
"""Return the current UTC timestamp."""
|
|
25
|
+
|
|
26
|
+
return datetime.now(UTC)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(slots=True, frozen=True)
|
|
30
|
+
class Deadline:
|
|
31
|
+
"""Immutable value object describing a wall-clock expiration."""
|
|
32
|
+
|
|
33
|
+
expires_at: datetime
|
|
34
|
+
|
|
35
|
+
def __post_init__(self) -> None:
|
|
36
|
+
expires_at = self.expires_at
|
|
37
|
+
if expires_at.tzinfo is None or expires_at.utcoffset() is None:
|
|
38
|
+
msg = "Deadline expires_at must be timezone-aware."
|
|
39
|
+
raise ValueError(msg)
|
|
40
|
+
|
|
41
|
+
now = _utcnow()
|
|
42
|
+
if expires_at <= now:
|
|
43
|
+
msg = "Deadline expires_at must be in the future."
|
|
44
|
+
raise ValueError(msg)
|
|
45
|
+
|
|
46
|
+
if expires_at - now < timedelta(seconds=1):
|
|
47
|
+
msg = "Deadline must be at least one second in the future."
|
|
48
|
+
raise ValueError(msg)
|
|
49
|
+
|
|
50
|
+
def remaining(self, *, now: datetime | None = None) -> timedelta:
|
|
51
|
+
"""Return the remaining duration before expiration."""
|
|
52
|
+
|
|
53
|
+
current = now or _utcnow()
|
|
54
|
+
if current.tzinfo is None or current.utcoffset() is None:
|
|
55
|
+
msg = "Deadline remaining now must be timezone-aware."
|
|
56
|
+
raise ValueError(msg)
|
|
57
|
+
|
|
58
|
+
return self.expires_at - current
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
2
|
+
# you may not use this file except in compliance with the License.
|
|
3
|
+
# You may obtain a copy of the License at
|
|
4
|
+
#
|
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
#
|
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
10
|
+
# See the License for the specific language governing permissions and
|
|
11
|
+
# limitations under the License.
|
|
12
|
+
|
|
13
|
+
"""Prompt authoring primitives exposed by :mod:`weakincentives.prompt`."""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from ._types import SupportsDataclass, SupportsToolResult
|
|
18
|
+
from .chapter import Chapter, ChaptersExpansionPolicy
|
|
19
|
+
from .composition import (
|
|
20
|
+
DelegationParams,
|
|
21
|
+
DelegationPrompt,
|
|
22
|
+
DelegationSummarySection,
|
|
23
|
+
ParentPromptParams,
|
|
24
|
+
ParentPromptSection,
|
|
25
|
+
RecapParams,
|
|
26
|
+
RecapSection,
|
|
27
|
+
)
|
|
28
|
+
from .errors import (
|
|
29
|
+
PromptError,
|
|
30
|
+
PromptRenderError,
|
|
31
|
+
PromptValidationError,
|
|
32
|
+
SectionPath,
|
|
33
|
+
)
|
|
34
|
+
from .markdown import MarkdownSection
|
|
35
|
+
from .overrides import (
|
|
36
|
+
LocalPromptOverridesStore,
|
|
37
|
+
PromptDescriptor,
|
|
38
|
+
PromptLike,
|
|
39
|
+
PromptOverride,
|
|
40
|
+
PromptOverridesError,
|
|
41
|
+
PromptOverridesStore,
|
|
42
|
+
SectionDescriptor,
|
|
43
|
+
SectionOverride,
|
|
44
|
+
ToolDescriptor,
|
|
45
|
+
ToolOverride,
|
|
46
|
+
hash_json,
|
|
47
|
+
hash_text,
|
|
48
|
+
)
|
|
49
|
+
from .prompt import Prompt
|
|
50
|
+
from .protocols import (
|
|
51
|
+
PromptProtocol,
|
|
52
|
+
ProviderAdapterProtocol,
|
|
53
|
+
RenderedPromptProtocol,
|
|
54
|
+
)
|
|
55
|
+
from .section import Section
|
|
56
|
+
from .structured_output import (
|
|
57
|
+
OutputParseError,
|
|
58
|
+
StructuredOutputConfig,
|
|
59
|
+
parse_structured_output,
|
|
60
|
+
)
|
|
61
|
+
from .tool import Tool, ToolContext, ToolHandler
|
|
62
|
+
from .tool_result import ToolResult
|
|
63
|
+
|
|
64
|
+
__all__ = [
|
|
65
|
+
"Chapter",
|
|
66
|
+
"ChaptersExpansionPolicy",
|
|
67
|
+
"DelegationParams",
|
|
68
|
+
"DelegationPrompt",
|
|
69
|
+
"DelegationSummarySection",
|
|
70
|
+
"LocalPromptOverridesStore",
|
|
71
|
+
"MarkdownSection",
|
|
72
|
+
"OutputParseError",
|
|
73
|
+
"ParentPromptParams",
|
|
74
|
+
"ParentPromptSection",
|
|
75
|
+
"Prompt",
|
|
76
|
+
"PromptDescriptor",
|
|
77
|
+
"PromptError",
|
|
78
|
+
"PromptLike",
|
|
79
|
+
"PromptOverride",
|
|
80
|
+
"PromptOverridesError",
|
|
81
|
+
"PromptOverridesStore",
|
|
82
|
+
"PromptProtocol",
|
|
83
|
+
"PromptRenderError",
|
|
84
|
+
"PromptValidationError",
|
|
85
|
+
"ProviderAdapterProtocol",
|
|
86
|
+
"RecapParams",
|
|
87
|
+
"RecapSection",
|
|
88
|
+
"RenderedPromptProtocol",
|
|
89
|
+
"Section",
|
|
90
|
+
"SectionDescriptor",
|
|
91
|
+
"SectionOverride",
|
|
92
|
+
"SectionPath",
|
|
93
|
+
"StructuredOutputConfig",
|
|
94
|
+
"SupportsDataclass",
|
|
95
|
+
"SupportsToolResult",
|
|
96
|
+
"Tool",
|
|
97
|
+
"ToolContext",
|
|
98
|
+
"ToolDescriptor",
|
|
99
|
+
"ToolHandler",
|
|
100
|
+
"ToolOverride",
|
|
101
|
+
"ToolResult",
|
|
102
|
+
"hash_json",
|
|
103
|
+
"hash_text",
|
|
104
|
+
"parse_structured_output",
|
|
105
|
+
]
|