consequa-x 0.1.0a2__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.
- consequa_x/__init__.py +22 -0
- consequa_x/__main__.py +3 -0
- consequa_x/adapters/__init__.py +5 -0
- consequa_x/adapters/ollama.py +350 -0
- consequa_x/attribution.py +407 -0
- consequa_x/benchmark/__init__.py +1 -0
- consequa_x/benchmark/assessment_metrics.py +54 -0
- consequa_x/benchmark/calibration.py +787 -0
- consequa_x/benchmark/calibration_export.py +139 -0
- consequa_x/benchmark/calibration_mechanisms.py +266 -0
- consequa_x/benchmark/decision_coalition.py +254 -0
- consequa_x/benchmark/decision_contrastive.py +448 -0
- consequa_x/benchmark/decision_replay.py +329 -0
- consequa_x/benchmark/decision_shapley.py +628 -0
- consequa_x/benchmark/evaluation.py +187 -0
- consequa_x/benchmark/interaction_export.py +220 -0
- consequa_x/benchmark/interaction_mechanisms.py +192 -0
- consequa_x/benchmark/interval_comparison.py +276 -0
- consequa_x/benchmark/intervals.py +164 -0
- consequa_x/benchmark/payments.py +426 -0
- consequa_x/benchmark/policy_experiment.py +298 -0
- consequa_x/benchmark/policy_runner.py +234 -0
- consequa_x/benchmark/runner.py +241 -0
- consequa_x/benchmark/study.py +183 -0
- consequa_x/benchmark/targets.py +149 -0
- consequa_x/benchmark/templates.py +102 -0
- consequa_x/cli.py +1191 -0
- consequa_x/explanation.py +130 -0
- consequa_x/graph.py +142 -0
- consequa_x/models.py +532 -0
- consequa_x/policies.py +248 -0
- consequa_x/py.typed +0 -0
- consequa_x/schema.py +159 -0
- consequa_x/schemas/trace-v0.1.json +543 -0
- consequa_x/verification.py +192 -0
- consequa_x-0.1.0a2.dist-info/METADATA +303 -0
- consequa_x-0.1.0a2.dist-info/RECORD +41 -0
- consequa_x-0.1.0a2.dist-info/WHEEL +5 -0
- consequa_x-0.1.0a2.dist-info/entry_points.txt +2 -0
- consequa_x-0.1.0a2.dist-info/licenses/LICENSE +21 -0
- consequa_x-0.1.0a2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
"""Replay a captured decision prefix, then sample the same policy again.
|
|
2
|
+
|
|
3
|
+
This is an isolated simulator primitive for studying continuation distributions.
|
|
4
|
+
It is not a causal attribution estimator or a reproduction of CAR. Held decisions
|
|
5
|
+
are reused only at their exact public request boundary. Fresh decisions receive
|
|
6
|
+
new phase seeds while the payment-world seed and outcome contract stay fixed.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from copy import deepcopy
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
import json
|
|
14
|
+
from typing import Literal
|
|
15
|
+
|
|
16
|
+
from ..models import AssessmentStatus, EventType, ResponseStatus, ToolResponse, Trace, to_dict
|
|
17
|
+
from ..policies import PolicyDecision, PolicyError, PolicyReply, PolicyRequest, validate_reply
|
|
18
|
+
from .evaluation import BudgetedPolicy, CallBudgetExceeded
|
|
19
|
+
from .policy_runner import MAX_TRIAL_SEED, run_policy_scenario
|
|
20
|
+
from .runner import Episode
|
|
21
|
+
from .templates import DEFAULT_REFUND_TEMPLATE, RefundTaskTemplate
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class DecisionPrefixMismatch(PolicyError):
|
|
25
|
+
"""A held decision was reached with a different public policy request."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True)
|
|
29
|
+
class DecisionReplayReceipt:
|
|
30
|
+
"""One reached decision, including refused or failed fresh decisions."""
|
|
31
|
+
|
|
32
|
+
index: int
|
|
33
|
+
source: Literal["held_factual", "resampled"]
|
|
34
|
+
stage: str
|
|
35
|
+
continuation_model_seed: int
|
|
36
|
+
factual_event_id: str | None
|
|
37
|
+
factual_model_seed: int | None
|
|
38
|
+
policy_calls: int
|
|
39
|
+
request: dict
|
|
40
|
+
reply: dict | None = None
|
|
41
|
+
error_type: str | None = None
|
|
42
|
+
error: str | None = None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass(frozen=True)
|
|
46
|
+
class DecisionPrefixReplay:
|
|
47
|
+
"""One branch receipt; failure has no fabricated episode or outcome."""
|
|
48
|
+
|
|
49
|
+
hold_count: int
|
|
50
|
+
continuation_seed: int
|
|
51
|
+
factual_seed: int
|
|
52
|
+
factual_decision_count: int
|
|
53
|
+
policy_metadata: dict
|
|
54
|
+
decisions: tuple[DecisionReplayReceipt, ...]
|
|
55
|
+
policy_calls: int
|
|
56
|
+
episode: Episode | None = None
|
|
57
|
+
error_type: str | None = None
|
|
58
|
+
error: str | None = None
|
|
59
|
+
budget_exhausted: bool = False
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def unreached_factual_indices(self) -> tuple[int, ...]:
|
|
63
|
+
"""Captured positions not reached, including after a failed decision."""
|
|
64
|
+
return tuple(range(min(len(self.decisions), self.factual_decision_count),
|
|
65
|
+
self.factual_decision_count))
|
|
66
|
+
|
|
67
|
+
def summary(self) -> dict:
|
|
68
|
+
return {
|
|
69
|
+
"status": "completed" if self.episode is not None else "failed",
|
|
70
|
+
"hold_count": self.hold_count,
|
|
71
|
+
"continuation_seed": self.continuation_seed,
|
|
72
|
+
"factual_seed": self.factual_seed,
|
|
73
|
+
"factual_decision_count": self.factual_decision_count,
|
|
74
|
+
"reached_decisions": len(self.decisions),
|
|
75
|
+
"held_decisions": sum(item.source == "held_factual" and item.reply is not None
|
|
76
|
+
for item in self.decisions),
|
|
77
|
+
"resampled_decisions": sum(item.source == "resampled" and item.reply is not None
|
|
78
|
+
for item in self.decisions),
|
|
79
|
+
"held_requests": sum(item.source == "held_factual" for item in self.decisions),
|
|
80
|
+
"resampled_requests": sum(item.source == "resampled" for item in self.decisions),
|
|
81
|
+
"failed_decisions": sum(item.error_type is not None for item in self.decisions),
|
|
82
|
+
"unreached_factual_indices": list(self.unreached_factual_indices),
|
|
83
|
+
"policy_calls": self.policy_calls,
|
|
84
|
+
"budget_exhausted": self.budget_exhausted,
|
|
85
|
+
"error_type": self.error_type,
|
|
86
|
+
"error": self.error,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass(frozen=True)
|
|
91
|
+
class _Capture:
|
|
92
|
+
event_id: str
|
|
93
|
+
model_seed: int
|
|
94
|
+
request: PolicyRequest
|
|
95
|
+
reply: PolicyReply
|
|
96
|
+
policy_metadata: dict
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _json_key(value) -> str:
|
|
100
|
+
# JSON comparison also distinguishes booleans from integers and integers
|
|
101
|
+
# from floats; Python container equality alone would accept these changes.
|
|
102
|
+
return json.dumps(to_dict(value), sort_keys=True, separators=(",", ":"), allow_nan=False)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _object_fields(value, names: set[str], label: str) -> dict:
|
|
106
|
+
if type(value) is not dict or set(value) != names:
|
|
107
|
+
raise ValueError(f"{label} must contain exactly {sorted(names)}")
|
|
108
|
+
return value
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _request_from_capture(value) -> PolicyRequest:
|
|
112
|
+
value = _object_fields(value, {"stage", "task", "context", "responses",
|
|
113
|
+
"attempts_remaining"}, "captured policy_request")
|
|
114
|
+
if type(value["responses"]) is not list:
|
|
115
|
+
raise ValueError("captured responses must be a JSON array")
|
|
116
|
+
responses = []
|
|
117
|
+
for item in value["responses"]:
|
|
118
|
+
item = _object_fields(item, {"attempt_id", "status", "time_ms",
|
|
119
|
+
"provider_request_id", "result"}, "captured response")
|
|
120
|
+
responses.append(ToolResponse(
|
|
121
|
+
item["attempt_id"], ResponseStatus(item["status"]), item["time_ms"],
|
|
122
|
+
item["provider_request_id"], item["result"],
|
|
123
|
+
))
|
|
124
|
+
return PolicyRequest(value["stage"], value["task"], value["context"], tuple(responses),
|
|
125
|
+
value["attempts_remaining"])
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _reply_from_capture(value) -> PolicyReply:
|
|
129
|
+
value = _object_fields(value, {"decision", "metadata"}, "captured policy_reply")
|
|
130
|
+
decision = _object_fields(value["decision"], {"operation", "assessment", "rationale"},
|
|
131
|
+
"captured decision")
|
|
132
|
+
return PolicyReply(PolicyDecision(decision["operation"], AssessmentStatus(decision["assessment"]),
|
|
133
|
+
decision["rationale"]), value["metadata"])
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _metadata(policy) -> dict:
|
|
137
|
+
try:
|
|
138
|
+
declared = policy.metadata
|
|
139
|
+
if type(declared) is not dict:
|
|
140
|
+
raise PolicyError("policy.metadata must be a JSON object")
|
|
141
|
+
# PolicyReply validates strict JSON rather than silently coercing tuples.
|
|
142
|
+
return PolicyReply(PolicyDecision("stop"), declared).metadata
|
|
143
|
+
except (TypeError, ValueError) as error:
|
|
144
|
+
raise PolicyError("policy.metadata must be a strict finite JSON object") from error
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class _CapturedPolicy:
|
|
148
|
+
"""Validate recorded boundaries in a fresh world without calling a provider."""
|
|
149
|
+
|
|
150
|
+
def __init__(self, captures: tuple[_Capture, ...], metadata: dict):
|
|
151
|
+
self.captures = captures
|
|
152
|
+
self.index = 0
|
|
153
|
+
self._metadata = metadata
|
|
154
|
+
|
|
155
|
+
@property
|
|
156
|
+
def metadata(self):
|
|
157
|
+
return deepcopy(self._metadata)
|
|
158
|
+
|
|
159
|
+
def decide(self, request: PolicyRequest, *, seed: int) -> PolicyReply:
|
|
160
|
+
if self.index >= len(self.captures):
|
|
161
|
+
raise ValueError("factual trace is missing a reached decision capture")
|
|
162
|
+
capture = self.captures[self.index]
|
|
163
|
+
if seed != capture.model_seed or _json_key(request) != _json_key(capture.request):
|
|
164
|
+
raise ValueError("factual captures do not match the declared world and template")
|
|
165
|
+
self.index += 1
|
|
166
|
+
return deepcopy(capture.reply)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _validate_factual(factual: Episode, template: RefundTaskTemplate) -> tuple[_Capture, ...]:
|
|
170
|
+
# Re-decode to check frozen records' mutable JSON dictionaries at the boundary.
|
|
171
|
+
if not isinstance(factual.trace, Trace):
|
|
172
|
+
raise TypeError("factual.trace must be a Trace")
|
|
173
|
+
trace = Trace.from_dict(factual.trace.to_dict())
|
|
174
|
+
events = tuple(event for event in trace.events if event.type is EventType.AGENT_DECISION)
|
|
175
|
+
if not events:
|
|
176
|
+
raise ValueError("factual trace must contain captured policy decisions")
|
|
177
|
+
captures = []
|
|
178
|
+
for index, event in enumerate(events):
|
|
179
|
+
required = {"policy_request", "policy_reply", "policy", "model_seed", "selected"}
|
|
180
|
+
if not required <= event.data.keys():
|
|
181
|
+
raise ValueError("factual decision lacks a complete policy capture")
|
|
182
|
+
model_seed = event.data["model_seed"]
|
|
183
|
+
if type(model_seed) is not int or not 0 <= model_seed <= 2**31 - 1:
|
|
184
|
+
raise ValueError("captured model_seed must be an integer in the model seed range")
|
|
185
|
+
if index == 0:
|
|
186
|
+
if model_seed % 6 or model_seed // 6 > MAX_TRIAL_SEED:
|
|
187
|
+
raise ValueError("factual selection seed must start a valid phase schedule")
|
|
188
|
+
first_seed = model_seed
|
|
189
|
+
elif model_seed != first_seed + index:
|
|
190
|
+
raise ValueError("factual model seeds must follow the recorded phase schedule")
|
|
191
|
+
request = _request_from_capture(event.data["policy_request"])
|
|
192
|
+
reply = _reply_from_capture(event.data["policy_reply"])
|
|
193
|
+
validate_reply(request, reply)
|
|
194
|
+
metadata = PolicyReply(PolicyDecision("stop"), event.data["policy"]).metadata
|
|
195
|
+
if captures and _json_key(metadata) != _json_key(captures[0].policy_metadata):
|
|
196
|
+
raise ValueError("factual policy metadata changed between decisions")
|
|
197
|
+
if type(event.data["selected"]) is not bool or event.data["selected"] != (
|
|
198
|
+
reply.decision.operation in {"refund", "retry"}
|
|
199
|
+
):
|
|
200
|
+
raise ValueError("captured selected flag does not match its decision")
|
|
201
|
+
captures.append(_Capture(event.id, model_seed, request, reply, metadata))
|
|
202
|
+
captured = tuple(captures)
|
|
203
|
+
attempts = captured[0].request.attempts_remaining
|
|
204
|
+
if not 1 <= attempts <= 5:
|
|
205
|
+
raise ValueError("factual initial attempt budget must be in 1..5")
|
|
206
|
+
checker = _CapturedPolicy(captured, captured[0].policy_metadata)
|
|
207
|
+
regenerated = run_policy_scenario(
|
|
208
|
+
factual.config, checker, seed=factual.seed, policy_seed=captured[0].model_seed // 6,
|
|
209
|
+
max_attempts=attempts, template=template,
|
|
210
|
+
)
|
|
211
|
+
if checker.index != len(captured):
|
|
212
|
+
raise ValueError("factual trace contains unreachable decision captures")
|
|
213
|
+
if (_json_key(regenerated.trace) != _json_key(trace)
|
|
214
|
+
or type(factual.action_selected) is not bool
|
|
215
|
+
or factual.action_selected != regenerated.action_selected):
|
|
216
|
+
raise ValueError("factual trace is inconsistent with its captured policy and world")
|
|
217
|
+
return captured
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
class _PrefixPolicy:
|
|
221
|
+
def __init__(self, captures: tuple[_Capture, ...], policy: BudgetedPolicy, hold_count: int):
|
|
222
|
+
self.captures = captures
|
|
223
|
+
self.policy = policy
|
|
224
|
+
self.hold_count = hold_count
|
|
225
|
+
self.receipts: list[DecisionReplayReceipt] = []
|
|
226
|
+
self._metadata = deepcopy(captures[0].policy_metadata)
|
|
227
|
+
|
|
228
|
+
@property
|
|
229
|
+
def metadata(self):
|
|
230
|
+
return deepcopy(self._metadata)
|
|
231
|
+
|
|
232
|
+
def decide(self, request: PolicyRequest, *, seed: int) -> PolicyReply:
|
|
233
|
+
index = len(self.receipts)
|
|
234
|
+
capture = self.captures[index] if index < len(self.captures) else None
|
|
235
|
+
source = "held_factual" if index < self.hold_count else "resampled"
|
|
236
|
+
before = self.policy.calls
|
|
237
|
+
request_data = to_dict(request)
|
|
238
|
+
reply = None
|
|
239
|
+
error = None
|
|
240
|
+
try:
|
|
241
|
+
if source == "held_factual":
|
|
242
|
+
if _json_key(request) != _json_key(capture.request):
|
|
243
|
+
raise DecisionPrefixMismatch("Held decision reached a different public request")
|
|
244
|
+
reply = PolicyReply(deepcopy(capture.reply.decision), {
|
|
245
|
+
"decision_prefix_replay": {
|
|
246
|
+
"source": "held_factual", "provider_call": False,
|
|
247
|
+
"factual_event_id": capture.event_id,
|
|
248
|
+
"factual_model_seed": capture.model_seed,
|
|
249
|
+
"requested_continuation_model_seed": seed,
|
|
250
|
+
"factual_reply_metadata": deepcopy(capture.reply.metadata),
|
|
251
|
+
},
|
|
252
|
+
})
|
|
253
|
+
else:
|
|
254
|
+
if _json_key(_metadata(self.policy)) != _json_key(self._metadata):
|
|
255
|
+
raise PolicyError("Continuation policy metadata differs from factual policy")
|
|
256
|
+
reply = self.policy.decide(deepcopy(request), seed=seed)
|
|
257
|
+
if _json_key(_metadata(self.policy)) != _json_key(self._metadata):
|
|
258
|
+
raise PolicyError("Continuation policy metadata changed during its decision")
|
|
259
|
+
validate_reply(request, reply)
|
|
260
|
+
reply = deepcopy(reply)
|
|
261
|
+
return reply
|
|
262
|
+
except PolicyError as caught:
|
|
263
|
+
error = caught
|
|
264
|
+
raise
|
|
265
|
+
finally:
|
|
266
|
+
self.receipts.append(DecisionReplayReceipt(
|
|
267
|
+
index, source, request.stage, seed,
|
|
268
|
+
capture.event_id if capture else None, capture.model_seed if capture else None,
|
|
269
|
+
self.policy.calls - before, request_data,
|
|
270
|
+
to_dict(reply) if reply is not None and error is None else None,
|
|
271
|
+
type(error).__name__ if error else None, str(error) if error else None,
|
|
272
|
+
))
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def replay_decision_prefix(
|
|
276
|
+
factual: Episode,
|
|
277
|
+
continuation_policy: BudgetedPolicy,
|
|
278
|
+
*,
|
|
279
|
+
hold_count: int,
|
|
280
|
+
continuation_seed: int,
|
|
281
|
+
template: RefundTaskTemplate = DEFAULT_REFUND_TEMPLATE,
|
|
282
|
+
) -> DecisionPrefixReplay:
|
|
283
|
+
"""Reset the factual world, hold N exact decisions, and resample the suffix.
|
|
284
|
+
|
|
285
|
+
All captures, the template, and declared policy identity are checked before
|
|
286
|
+
any supplied policy call. ``continuation_seed`` chooses model phase keys;
|
|
287
|
+
``factual.seed`` continues to choose the simulator world. A budgeted policy
|
|
288
|
+
may be shared across branches, and only calls through that object count.
|
|
289
|
+
Branches can end earlier or later than the factual trace. Failed branches
|
|
290
|
+
retain their reached receipts and have no semantic outcome label.
|
|
291
|
+
|
|
292
|
+
Metadata equality checks declared identity/configuration, not hidden model
|
|
293
|
+
state. Sampling under seeds does not guarantee deterministic model output.
|
|
294
|
+
"""
|
|
295
|
+
if not isinstance(factual, Episode):
|
|
296
|
+
raise TypeError("factual must be an Episode")
|
|
297
|
+
if not isinstance(continuation_policy, BudgetedPolicy):
|
|
298
|
+
raise TypeError("continuation_policy must be a BudgetedPolicy")
|
|
299
|
+
if type(hold_count) is not int or hold_count < 0:
|
|
300
|
+
raise ValueError("hold_count must be a nonnegative integer")
|
|
301
|
+
if type(continuation_seed) is not int or not 0 <= continuation_seed <= MAX_TRIAL_SEED:
|
|
302
|
+
raise ValueError(f"continuation_seed must be an integer in 0..{MAX_TRIAL_SEED}")
|
|
303
|
+
if type(factual.seed) is not int or not 0 <= factual.seed <= MAX_TRIAL_SEED:
|
|
304
|
+
raise ValueError(f"factual seed must be an integer in 0..{MAX_TRIAL_SEED}")
|
|
305
|
+
if not isinstance(template, RefundTaskTemplate):
|
|
306
|
+
raise TypeError("template must be a RefundTaskTemplate")
|
|
307
|
+
captures = _validate_factual(factual, template)
|
|
308
|
+
if hold_count > len(captures):
|
|
309
|
+
raise ValueError("hold_count exceeds the factual captured decision count")
|
|
310
|
+
metadata = _metadata(continuation_policy)
|
|
311
|
+
if _json_key(metadata) != _json_key(captures[0].policy_metadata):
|
|
312
|
+
raise ValueError("Continuation policy metadata differs from factual policy")
|
|
313
|
+
prefix_policy = _PrefixPolicy(captures, continuation_policy, hold_count)
|
|
314
|
+
before = continuation_policy.calls
|
|
315
|
+
episode = None
|
|
316
|
+
error = None
|
|
317
|
+
try:
|
|
318
|
+
episode = run_policy_scenario(
|
|
319
|
+
factual.config, prefix_policy, seed=factual.seed, policy_seed=continuation_seed,
|
|
320
|
+
max_attempts=captures[0].request.attempts_remaining, template=template,
|
|
321
|
+
)
|
|
322
|
+
except PolicyError as caught:
|
|
323
|
+
error = caught
|
|
324
|
+
return DecisionPrefixReplay(
|
|
325
|
+
hold_count, continuation_seed, factual.seed, len(captures), deepcopy(metadata),
|
|
326
|
+
tuple(prefix_policy.receipts), continuation_policy.calls - before, episode,
|
|
327
|
+
type(error).__name__ if error else None, str(error) if error else None,
|
|
328
|
+
isinstance(error, CallBudgetExceeded),
|
|
329
|
+
)
|