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.
Files changed (41) hide show
  1. consequa_x/__init__.py +22 -0
  2. consequa_x/__main__.py +3 -0
  3. consequa_x/adapters/__init__.py +5 -0
  4. consequa_x/adapters/ollama.py +350 -0
  5. consequa_x/attribution.py +407 -0
  6. consequa_x/benchmark/__init__.py +1 -0
  7. consequa_x/benchmark/assessment_metrics.py +54 -0
  8. consequa_x/benchmark/calibration.py +787 -0
  9. consequa_x/benchmark/calibration_export.py +139 -0
  10. consequa_x/benchmark/calibration_mechanisms.py +266 -0
  11. consequa_x/benchmark/decision_coalition.py +254 -0
  12. consequa_x/benchmark/decision_contrastive.py +448 -0
  13. consequa_x/benchmark/decision_replay.py +329 -0
  14. consequa_x/benchmark/decision_shapley.py +628 -0
  15. consequa_x/benchmark/evaluation.py +187 -0
  16. consequa_x/benchmark/interaction_export.py +220 -0
  17. consequa_x/benchmark/interaction_mechanisms.py +192 -0
  18. consequa_x/benchmark/interval_comparison.py +276 -0
  19. consequa_x/benchmark/intervals.py +164 -0
  20. consequa_x/benchmark/payments.py +426 -0
  21. consequa_x/benchmark/policy_experiment.py +298 -0
  22. consequa_x/benchmark/policy_runner.py +234 -0
  23. consequa_x/benchmark/runner.py +241 -0
  24. consequa_x/benchmark/study.py +183 -0
  25. consequa_x/benchmark/targets.py +149 -0
  26. consequa_x/benchmark/templates.py +102 -0
  27. consequa_x/cli.py +1191 -0
  28. consequa_x/explanation.py +130 -0
  29. consequa_x/graph.py +142 -0
  30. consequa_x/models.py +532 -0
  31. consequa_x/policies.py +248 -0
  32. consequa_x/py.typed +0 -0
  33. consequa_x/schema.py +159 -0
  34. consequa_x/schemas/trace-v0.1.json +543 -0
  35. consequa_x/verification.py +192 -0
  36. consequa_x-0.1.0a2.dist-info/METADATA +303 -0
  37. consequa_x-0.1.0a2.dist-info/RECORD +41 -0
  38. consequa_x-0.1.0a2.dist-info/WHEEL +5 -0
  39. consequa_x-0.1.0a2.dist-info/entry_points.txt +2 -0
  40. consequa_x-0.1.0a2.dist-info/licenses/LICENSE +21 -0
  41. 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
+ )