weakincentives 0.3.0__py3-none-any.whl → 0.4.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.
Potentially problematic release.
This version of weakincentives might be problematic. Click here for more details.
- weakincentives/__init__.py +1 -1
- weakincentives/adapters/__init__.py +3 -2
- weakincentives/examples/code_review_prompt.py +12 -3
- weakincentives/examples/code_review_session.py +7 -3
- weakincentives/prompt/markdown.py +1 -1
- weakincentives/prompt/prompt.py +7 -7
- weakincentives/prompt/structured_output.py +2 -2
- weakincentives/prompt/tool.py +2 -2
- weakincentives/serde/dataclass_serde.py +16 -14
- weakincentives/session/session.py +5 -0
- weakincentives/tools/__init__.py +12 -0
- weakincentives/tools/asteval.py +698 -0
- weakincentives/tools/vfs.py +59 -56
- weakincentives-0.4.0.dist-info/METADATA +490 -0
- {weakincentives-0.3.0.dist-info → weakincentives-0.4.0.dist-info}/RECORD +17 -16
- weakincentives-0.3.0.dist-info/METADATA +0 -231
- {weakincentives-0.3.0.dist-info → weakincentives-0.4.0.dist-info}/WHEEL +0 -0
- {weakincentives-0.3.0.dist-info → weakincentives-0.4.0.dist-info}/licenses/LICENSE +0 -0
weakincentives/__init__.py
CHANGED
|
@@ -27,5 +27,6 @@ __all__ = [
|
|
|
27
27
|
]
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
def __dir__() -> list[str]:
|
|
31
|
-
|
|
30
|
+
def __dir__() -> list[str]:
|
|
31
|
+
extra_exports = {"LiteLLMAdapter", "OpenAIAdapter"}
|
|
32
|
+
return sorted({*globals().keys(), *(__all__), *extra_exports})
|
|
@@ -19,7 +19,7 @@ from dataclasses import dataclass, field
|
|
|
19
19
|
|
|
20
20
|
from ..prompt import MarkdownSection, Prompt
|
|
21
21
|
from ..session import Session
|
|
22
|
-
from ..tools import PlanningToolsSection, VfsToolsSection
|
|
22
|
+
from ..tools import AstevalSection, PlanningToolsSection, VfsToolsSection
|
|
23
23
|
from .code_review_tools import build_tools
|
|
24
24
|
|
|
25
25
|
|
|
@@ -72,10 +72,12 @@ def build_code_review_prompt(session: Session) -> Prompt[ReviewResponse]:
|
|
|
72
72
|
- `vfs_list_directory` lists directories and files staged in the virtual
|
|
73
73
|
filesystem snapshot.
|
|
74
74
|
- `vfs_read_file` reads staged file contents.
|
|
75
|
-
- `vfs_write_file` stages
|
|
75
|
+
- `vfs_write_file` stages UTF-8 edits before applying them to the host
|
|
76
76
|
workspace.
|
|
77
77
|
- `vfs_delete_entry` removes staged files or directories that are no
|
|
78
78
|
longer needed.
|
|
79
|
+
- `evaluate_python` runs Python snippets inside a sandbox with
|
|
80
|
+
optional staged file reads and writes.
|
|
79
81
|
If the task requires information beyond these capabilities, ask the
|
|
80
82
|
user for clarification rather than guessing.
|
|
81
83
|
|
|
@@ -97,6 +99,7 @@ def build_code_review_prompt(session: Session) -> Prompt[ReviewResponse]:
|
|
|
97
99
|
)
|
|
98
100
|
planning_section = PlanningToolsSection(session=session)
|
|
99
101
|
vfs_section = VfsToolsSection(session=session)
|
|
102
|
+
asteval_section = AstevalSection(session=session)
|
|
100
103
|
user_turn_section = MarkdownSection[ReviewTurnParams](
|
|
101
104
|
title="Review Request",
|
|
102
105
|
template="${request}",
|
|
@@ -106,7 +109,13 @@ def build_code_review_prompt(session: Session) -> Prompt[ReviewResponse]:
|
|
|
106
109
|
ns="examples/code-review",
|
|
107
110
|
key="code-review-session",
|
|
108
111
|
name="code_review_agent",
|
|
109
|
-
sections=[
|
|
112
|
+
sections=[
|
|
113
|
+
guidance_section,
|
|
114
|
+
planning_section,
|
|
115
|
+
vfs_section,
|
|
116
|
+
asteval_section,
|
|
117
|
+
user_turn_section,
|
|
118
|
+
],
|
|
110
119
|
)
|
|
111
120
|
|
|
112
121
|
|
|
@@ -95,7 +95,7 @@ class CodeReviewSession:
|
|
|
95
95
|
indent=2,
|
|
96
96
|
)
|
|
97
97
|
if response.text:
|
|
98
|
-
return response.text
|
|
98
|
+
return response.text # pragma: no cover - convenience path for plain text
|
|
99
99
|
return "(no response from assistant)"
|
|
100
100
|
|
|
101
101
|
def render_tool_history(self) -> str:
|
|
@@ -126,11 +126,15 @@ class CodeReviewSession:
|
|
|
126
126
|
f" → {event.result.message}"
|
|
127
127
|
)
|
|
128
128
|
if payload:
|
|
129
|
-
print(
|
|
129
|
+
print(
|
|
130
|
+
f" payload: {payload}"
|
|
131
|
+
) # pragma: no cover - console output only
|
|
130
132
|
latest = select_latest(self._session, ToolCallLog)
|
|
131
133
|
if latest is not None:
|
|
132
134
|
count = len(select_all(self._session, ToolCallLog))
|
|
133
|
-
print(
|
|
135
|
+
print( # pragma: no cover - console output only
|
|
136
|
+
f" (session recorded this call as #{count})"
|
|
137
|
+
)
|
|
134
138
|
|
|
135
139
|
def _register_tool_history(self) -> None:
|
|
136
140
|
for result_type in (
|
|
@@ -59,7 +59,7 @@ class MarkdownSection[ParamsT: SupportsDataclass](Section[ParamsT]):
|
|
|
59
59
|
try:
|
|
60
60
|
normalized_params = self._normalize_params(params)
|
|
61
61
|
rendered_body = template.substitute(normalized_params)
|
|
62
|
-
except KeyError as error:
|
|
62
|
+
except KeyError as error:
|
|
63
63
|
missing = error.args[0]
|
|
64
64
|
raise PromptRenderError(
|
|
65
65
|
"Missing placeholder during render.",
|
weakincentives/prompt/prompt.py
CHANGED
|
@@ -46,7 +46,7 @@ class RenderedPrompt[OutputT]:
|
|
|
46
46
|
default=_EMPTY_TOOL_PARAM_DESCRIPTIONS
|
|
47
47
|
)
|
|
48
48
|
|
|
49
|
-
def __str__(self) -> str:
|
|
49
|
+
def __str__(self) -> str:
|
|
50
50
|
return self.text
|
|
51
51
|
|
|
52
52
|
@property
|
|
@@ -71,11 +71,11 @@ def _clone_dataclass(instance: SupportsDataclass) -> SupportsDataclass:
|
|
|
71
71
|
|
|
72
72
|
|
|
73
73
|
def _format_specialization_argument(argument: object | None) -> str:
|
|
74
|
-
if argument is None:
|
|
74
|
+
if argument is None:
|
|
75
75
|
return "?"
|
|
76
76
|
if isinstance(argument, type):
|
|
77
77
|
return argument.__name__
|
|
78
|
-
return repr(argument)
|
|
78
|
+
return repr(argument)
|
|
79
79
|
|
|
80
80
|
|
|
81
81
|
@dataclass(frozen=True, slots=True)
|
|
@@ -314,7 +314,7 @@ class Prompt[OutputT]:
|
|
|
314
314
|
if candidate is None or container is None:
|
|
315
315
|
return None, None, None
|
|
316
316
|
|
|
317
|
-
if not isinstance(candidate, type):
|
|
317
|
+
if not isinstance(candidate, type):
|
|
318
318
|
candidate_type = cast(type[Any], type(candidate))
|
|
319
319
|
raise PromptValidationError(
|
|
320
320
|
"Prompt output type must be a dataclass.",
|
|
@@ -333,7 +333,7 @@ class Prompt[OutputT]:
|
|
|
333
333
|
|
|
334
334
|
def _build_response_format_params(self) -> ResponseFormatParams:
|
|
335
335
|
container = self._output_container
|
|
336
|
-
if container is None:
|
|
336
|
+
if container is None:
|
|
337
337
|
raise RuntimeError(
|
|
338
338
|
"Output container missing during response format construction."
|
|
339
339
|
)
|
|
@@ -458,7 +458,7 @@ class Prompt[OutputT]:
|
|
|
458
458
|
dataclass_type=params_type,
|
|
459
459
|
placeholder=error.placeholder,
|
|
460
460
|
) from error
|
|
461
|
-
except Exception as error:
|
|
461
|
+
except Exception as error:
|
|
462
462
|
raise PromptRenderError(
|
|
463
463
|
"Section rendering failed.",
|
|
464
464
|
section_path=node.path,
|
|
@@ -519,7 +519,7 @@ class Prompt[OutputT]:
|
|
|
519
519
|
else:
|
|
520
520
|
try:
|
|
521
521
|
enabled = node.section.is_enabled(section_params)
|
|
522
|
-
except Exception as error:
|
|
522
|
+
except Exception as error:
|
|
523
523
|
raise PromptRenderError(
|
|
524
524
|
"Section enabled predicate failed.",
|
|
525
525
|
section_path=node.path,
|
|
@@ -110,7 +110,7 @@ def parse_structured_output[PayloadT](
|
|
|
110
110
|
parsed_items.append(parsed_item)
|
|
111
111
|
return cast(PayloadT, parsed_items)
|
|
112
112
|
|
|
113
|
-
raise OutputParseError(
|
|
113
|
+
raise OutputParseError(
|
|
114
114
|
"Unknown output container declared.",
|
|
115
115
|
dataclass_type=dataclass_type,
|
|
116
116
|
)
|
|
@@ -141,7 +141,7 @@ def _extract_json_payload(text: str, dataclass_type: type[Any]) -> object:
|
|
|
141
141
|
continue
|
|
142
142
|
try:
|
|
143
143
|
payload, _ = decoder.raw_decode(text, index)
|
|
144
|
-
except json.JSONDecodeError:
|
|
144
|
+
except json.JSONDecodeError:
|
|
145
145
|
continue
|
|
146
146
|
return payload
|
|
147
147
|
|
weakincentives/prompt/tool.py
CHANGED
|
@@ -51,7 +51,7 @@ class Tool[ParamsT: SupportsDataclass, ResultT: SupportsDataclass]:
|
|
|
51
51
|
)
|
|
52
52
|
if params_type is None or result_type is None:
|
|
53
53
|
origin = getattr(self, "__orig_class__", None)
|
|
54
|
-
if origin is not None: # pragma: no cover -
|
|
54
|
+
if origin is not None: # pragma: no cover - interpreter-specific path
|
|
55
55
|
args = get_args(origin)
|
|
56
56
|
if len(args) == 2 and all(isinstance(arg, type) for arg in args):
|
|
57
57
|
params_type = cast(type[SupportsDataclass], args[0])
|
|
@@ -161,7 +161,7 @@ class Tool[ParamsT: SupportsDataclass, ResultT: SupportsDataclass]:
|
|
|
161
161
|
|
|
162
162
|
try:
|
|
163
163
|
hints = get_type_hints(handler, include_extras=True)
|
|
164
|
-
except Exception:
|
|
164
|
+
except Exception:
|
|
165
165
|
hints = {}
|
|
166
166
|
|
|
167
167
|
annotation = hints.get(parameter.name, parameter.annotation)
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
from __future__ import annotations
|
|
14
14
|
|
|
15
|
+
# pyright: reportUnknownArgumentType=false, reportUnknownVariableType=false, reportUnknownMemberType=false, reportUnknownParameterType=false, reportUnnecessaryIsInstance=false, reportCallIssue=false, reportArgumentType=false, reportPossiblyUnboundVariable=false
|
|
15
16
|
import dataclasses
|
|
16
17
|
import re
|
|
17
18
|
from collections.abc import Callable, Iterable, Mapping, Sequence, Sized
|
|
@@ -57,10 +58,7 @@ def _ordered_values(values: Iterable[object]) -> list[object]:
|
|
|
57
58
|
|
|
58
59
|
items = list(values)
|
|
59
60
|
if isinstance(values, (set, frozenset)):
|
|
60
|
-
|
|
61
|
-
return sorted(items)
|
|
62
|
-
except TypeError:
|
|
63
|
-
return sorted(items, key=repr)
|
|
61
|
+
return sorted(items, key=repr)
|
|
64
62
|
return items
|
|
65
63
|
|
|
66
64
|
|
|
@@ -101,7 +99,7 @@ def _merge_annotated_meta(
|
|
|
101
99
|
base = args[0]
|
|
102
100
|
for extra in args[1:]:
|
|
103
101
|
if isinstance(extra, Mapping):
|
|
104
|
-
merged.update(extra)
|
|
102
|
+
merged.update(cast(Mapping[str, object], extra))
|
|
105
103
|
return base, merged
|
|
106
104
|
|
|
107
105
|
|
|
@@ -191,14 +189,16 @@ def _apply_constraints(value: object, meta: Mapping[str, object], path: str) ->
|
|
|
191
189
|
|
|
192
190
|
members = meta.get("in") or meta.get("enum")
|
|
193
191
|
if isinstance(members, Iterable) and not isinstance(members, (str, bytes)):
|
|
194
|
-
|
|
192
|
+
options_iter = cast(Iterable[object], members)
|
|
193
|
+
options = _ordered_values(options_iter)
|
|
195
194
|
normalized_options = [_normalize_option(option) for option in options]
|
|
196
195
|
if result not in normalized_options:
|
|
197
196
|
_fail(f"must be one of {normalized_options}")
|
|
198
197
|
|
|
199
198
|
not_members = meta.get("not_in")
|
|
200
199
|
if isinstance(not_members, Iterable) and not isinstance(not_members, (str, bytes)):
|
|
201
|
-
|
|
200
|
+
forbidden_iter = cast(Iterable[object], not_members)
|
|
201
|
+
forbidden = _ordered_values(forbidden_iter)
|
|
202
202
|
normalized_forbidden = [_normalize_option(option) for option in forbidden]
|
|
203
203
|
if result in normalized_forbidden:
|
|
204
204
|
_fail(f"may not be one of {normalized_forbidden}")
|
|
@@ -222,8 +222,9 @@ def _apply_constraints(value: object, meta: Mapping[str, object], path: str) ->
|
|
|
222
222
|
|
|
223
223
|
converter = meta.get("convert", meta.get("transform"))
|
|
224
224
|
if converter:
|
|
225
|
+
converter_fn = cast(Callable[[object], object], converter)
|
|
225
226
|
try:
|
|
226
|
-
result =
|
|
227
|
+
result = converter_fn(result)
|
|
227
228
|
except (TypeError, ValueError) as error:
|
|
228
229
|
raise type(error)(f"{path}: {error}") from error
|
|
229
230
|
except Exception as error: # pragma: no cover - defensive
|
|
@@ -243,7 +244,7 @@ def _coerce_to_type(
|
|
|
243
244
|
origin = get_origin(base_type)
|
|
244
245
|
type_name = getattr(base_type, "__name__", type(base_type).__name__)
|
|
245
246
|
|
|
246
|
-
if base_type
|
|
247
|
+
if base_type is object or base_type is _AnyType:
|
|
247
248
|
return _apply_constraints(value, merged_meta, path)
|
|
248
249
|
|
|
249
250
|
if origin is Union:
|
|
@@ -290,7 +291,7 @@ def _coerce_to_type(
|
|
|
290
291
|
if value == literal:
|
|
291
292
|
return _apply_constraints(literal, merged_meta, path)
|
|
292
293
|
if config.coerce:
|
|
293
|
-
literal_type = type(literal)
|
|
294
|
+
literal_type = cast(type[object], type(literal))
|
|
294
295
|
try:
|
|
295
296
|
if isinstance(literal, bool) and isinstance(value, str):
|
|
296
297
|
coerced_literal = _bool_from_str(value)
|
|
@@ -313,7 +314,7 @@ def _coerce_to_type(
|
|
|
313
314
|
return _apply_constraints(value, merged_meta, path)
|
|
314
315
|
if not isinstance(value, Mapping):
|
|
315
316
|
type_name = getattr(
|
|
316
|
-
dataclass_type, "__name__", dataclass_type.
|
|
317
|
+
dataclass_type, "__name__", type(dataclass_type).__name__
|
|
317
318
|
)
|
|
318
319
|
raise TypeError(f"{path}: expected mapping for dataclass {type_name}")
|
|
319
320
|
try:
|
|
@@ -353,7 +354,7 @@ def _coerce_to_type(
|
|
|
353
354
|
if isinstance(value, str):
|
|
354
355
|
value = [value]
|
|
355
356
|
elif isinstance(value, Iterable):
|
|
356
|
-
value = list(value)
|
|
357
|
+
value = list(cast(Iterable[object], value))
|
|
357
358
|
else:
|
|
358
359
|
raise TypeError(f"{path}: expected set")
|
|
359
360
|
else:
|
|
@@ -367,7 +368,7 @@ def _coerce_to_type(
|
|
|
367
368
|
if isinstance(value, str): # pragma: no cover - handled by earlier coercion
|
|
368
369
|
items = [value]
|
|
369
370
|
elif isinstance(value, Iterable):
|
|
370
|
-
items = list(value)
|
|
371
|
+
items = list(cast(Iterable[object], value))
|
|
371
372
|
else: # pragma: no cover - defensive guard
|
|
372
373
|
raise TypeError(f"{path}: expected iterable")
|
|
373
374
|
args = get_args(base_type)
|
|
@@ -402,8 +403,9 @@ def _coerce_to_type(
|
|
|
402
403
|
key_type, value_type = (
|
|
403
404
|
get_args(base_type) if get_args(base_type) else (object, object)
|
|
404
405
|
)
|
|
406
|
+
mapping_value = cast(Mapping[object, object], value)
|
|
405
407
|
result_dict: dict[object, object] = {}
|
|
406
|
-
for key, item in
|
|
408
|
+
for key, item in mapping_value.items():
|
|
407
409
|
coerced_key = _coerce_to_type(key, key_type, None, f"{path} keys", config)
|
|
408
410
|
coerced_value = _coerce_to_type(
|
|
409
411
|
item, value_type, None, f"{path}[{coerced_key}]", config
|
|
@@ -95,6 +95,11 @@ class Session:
|
|
|
95
95
|
|
|
96
96
|
return cast(tuple[S, ...], self._state.get(slice_type, ()))
|
|
97
97
|
|
|
98
|
+
def seed_slice[S](self, slice_type: type[S], values: Iterable[S]) -> None:
|
|
99
|
+
"""Initialize or replace the stored tuple for the provided type."""
|
|
100
|
+
|
|
101
|
+
self._state[slice_type] = tuple(values)
|
|
102
|
+
|
|
98
103
|
def _on_tool_invoked(self, event: object) -> None:
|
|
99
104
|
if isinstance(event, ToolInvoked):
|
|
100
105
|
self._handle_tool_invoked(event)
|
weakincentives/tools/__init__.py
CHANGED
|
@@ -14,6 +14,13 @@
|
|
|
14
14
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
|
+
from .asteval import (
|
|
18
|
+
AstevalSection,
|
|
19
|
+
EvalFileRead,
|
|
20
|
+
EvalFileWrite,
|
|
21
|
+
EvalParams,
|
|
22
|
+
EvalResult,
|
|
23
|
+
)
|
|
17
24
|
from .errors import ToolValidationError
|
|
18
25
|
from .planning import (
|
|
19
26
|
AddStep,
|
|
@@ -44,6 +51,11 @@ from .vfs import (
|
|
|
44
51
|
|
|
45
52
|
__all__ = [
|
|
46
53
|
"ToolValidationError",
|
|
54
|
+
"AstevalSection",
|
|
55
|
+
"EvalParams",
|
|
56
|
+
"EvalResult",
|
|
57
|
+
"EvalFileRead",
|
|
58
|
+
"EvalFileWrite",
|
|
47
59
|
"Plan",
|
|
48
60
|
"PlanStep",
|
|
49
61
|
"PlanStatus",
|