ctxoptimizer 0.4.1__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.
- ctxoptimizer/__init__.py +5 -0
- ctxoptimizer/__main__.py +14 -0
- ctxoptimizer/admin_security.py +130 -0
- ctxoptimizer/analysis/__init__.py +1 -0
- ctxoptimizer/analysis/baseline.py +69 -0
- ctxoptimizer/analysis/break_even.py +49 -0
- ctxoptimizer/analysis/drift.py +151 -0
- ctxoptimizer/analysis/edits.py +148 -0
- ctxoptimizer/analysis/markers.py +34 -0
- ctxoptimizer/analysis/recommend.py +114 -0
- ctxoptimizer/analysis/segments.py +57 -0
- ctxoptimizer/analysis/session.py +62 -0
- ctxoptimizer/analysis/state.py +239 -0
- ctxoptimizer/analysis/tokens.py +62 -0
- ctxoptimizer/analysis/waste.py +322 -0
- ctxoptimizer/cli.py +873 -0
- ctxoptimizer/config.py +512 -0
- ctxoptimizer/dashboard/__init__.py +1 -0
- ctxoptimizer/dashboard/api.py +537 -0
- ctxoptimizer/dashboard/service.py +157 -0
- ctxoptimizer/dashboard/static/.gitkeep +1 -0
- ctxoptimizer/dashboard/templates/.gitkeep +1 -0
- ctxoptimizer/dashboard/templates/doctor.html +46 -0
- ctxoptimizer/dashboard/templates/live.html +91 -0
- ctxoptimizer/dashboard/templates/overview.html +172 -0
- ctxoptimizer/dashboard/templates/pricing.html +115 -0
- ctxoptimizer/dashboard/templates/request.html +111 -0
- ctxoptimizer/dashboard/templates/session.html +67 -0
- ctxoptimizer/dashboard/templates/settings.html +79 -0
- ctxoptimizer/dashboard/templates/table.html +56 -0
- ctxoptimizer/dashboard/templates/transforms.html +118 -0
- ctxoptimizer/dashboard/templates/waste.html +120 -0
- ctxoptimizer/dashboard/views.py +429 -0
- ctxoptimizer/data/cache_limits.json +23 -0
- ctxoptimizer/data/default_config.toml +185 -0
- ctxoptimizer/data/pricing.json +421 -0
- ctxoptimizer/doctor.py +129 -0
- ctxoptimizer/guard.py +139 -0
- ctxoptimizer/ids.py +73 -0
- ctxoptimizer/log.py +73 -0
- ctxoptimizer/mcp.py +124 -0
- ctxoptimizer/models.py +145 -0
- ctxoptimizer/pipeline.py +126 -0
- ctxoptimizer/profiles.py +152 -0
- ctxoptimizer/providers/__init__.py +37 -0
- ctxoptimizer/providers/anthropic.py +105 -0
- ctxoptimizer/providers/base.py +191 -0
- ctxoptimizer/providers/gemini.py +143 -0
- ctxoptimizer/providers/ollama.py +133 -0
- ctxoptimizer/providers/openai_chat.py +135 -0
- ctxoptimizer/providers/openai_responses.py +121 -0
- ctxoptimizer/proxy.py +665 -0
- ctxoptimizer/routing.py +259 -0
- ctxoptimizer/server.py +319 -0
- ctxoptimizer/service.py +150 -0
- ctxoptimizer/telemetry/__init__.py +1 -0
- ctxoptimizer/telemetry/blobs.py +57 -0
- ctxoptimizer/telemetry/expand.py +62 -0
- ctxoptimizer/telemetry/pricing.py +168 -0
- ctxoptimizer/telemetry/redact.py +53 -0
- ctxoptimizer/telemetry/sse.py +145 -0
- ctxoptimizer/telemetry/store.py +829 -0
- ctxoptimizer/telemetry/usage.py +435 -0
- ctxoptimizer/transforms/__init__.py +87 -0
- ctxoptimizer/transforms/ansi.py +21 -0
- ctxoptimizer/transforms/anthropic_cache.py +100 -0
- ctxoptimizer/transforms/backref.py +74 -0
- ctxoptimizer/transforms/base.py +93 -0
- ctxoptimizer/transforms/cache_serve.py +146 -0
- ctxoptimizer/transforms/dedupe_lines.py +69 -0
- ctxoptimizer/transforms/eligibility.py +24 -0
- ctxoptimizer/transforms/openai_hints.py +84 -0
- ctxoptimizer/transforms/progress.py +52 -0
- ctxoptimizer/transforms/stale_result_elision.py +109 -0
- ctxoptimizer/transforms/truncate.py +100 -0
- ctxoptimizer/transforms/volatile.py +177 -0
- ctxoptimizer/transforms/whitespace.py +25 -0
- ctxoptimizer-0.4.1.dist-info/METADATA +196 -0
- ctxoptimizer-0.4.1.dist-info/RECORD +83 -0
- ctxoptimizer-0.4.1.dist-info/WHEEL +4 -0
- ctxoptimizer-0.4.1.dist-info/entry_points.txt +3 -0
- ctxoptimizer-0.4.1.dist-info/licenses/LICENSE +21 -0
- ctxoptimizer-0.4.1.dist-info/licenses/NOTICE +13 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
"""Deterministic waste detectors W1-W6 (SPEC section 13.6). Advisory only."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from hashlib import sha256
|
|
7
|
+
|
|
8
|
+
from ctxoptimizer.analysis.drift import DriftEvent
|
|
9
|
+
from ctxoptimizer.ids import new_ulid
|
|
10
|
+
from ctxoptimizer.models import WasteFinding
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True, slots=True)
|
|
14
|
+
class HashObservation:
|
|
15
|
+
hash: bytes
|
|
16
|
+
tokens: int
|
|
17
|
+
turn: int = 0
|
|
18
|
+
tool_name: str | None = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True, slots=True)
|
|
22
|
+
class EditObservation:
|
|
23
|
+
"""A whitespace-normalized edit pair; raw text is never persisted."""
|
|
24
|
+
|
|
25
|
+
path: str
|
|
26
|
+
old: str
|
|
27
|
+
new: str
|
|
28
|
+
tool_name: str | None = None
|
|
29
|
+
tokens: int = 0
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def redundant_rereads(
|
|
33
|
+
current: list[HashObservation],
|
|
34
|
+
prior: dict[bytes, HashObservation],
|
|
35
|
+
*,
|
|
36
|
+
session_id: str,
|
|
37
|
+
project: str | None,
|
|
38
|
+
ts: float,
|
|
39
|
+
) -> list[WasteFinding]:
|
|
40
|
+
"""W1: content re-read after an earlier turn in the same session."""
|
|
41
|
+
findings: list[WasteFinding] = []
|
|
42
|
+
seen: set[bytes] = set()
|
|
43
|
+
for observation in current:
|
|
44
|
+
if observation.hash not in prior or observation.hash in seen:
|
|
45
|
+
continue
|
|
46
|
+
seen.add(observation.hash)
|
|
47
|
+
findings.append(
|
|
48
|
+
_finding(
|
|
49
|
+
"redundant_reread",
|
|
50
|
+
observation,
|
|
51
|
+
session_id,
|
|
52
|
+
project,
|
|
53
|
+
ts,
|
|
54
|
+
occurrences=2,
|
|
55
|
+
evidence={
|
|
56
|
+
"turn": observation.turn,
|
|
57
|
+
"first_seen_turn": prior[observation.hash].turn,
|
|
58
|
+
"tool": observation.tool_name,
|
|
59
|
+
},
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
return findings
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def identical_outputs(
|
|
66
|
+
observations: list[HashObservation],
|
|
67
|
+
*,
|
|
68
|
+
session_id: str,
|
|
69
|
+
project: str | None,
|
|
70
|
+
ts: float,
|
|
71
|
+
min_occurrences: int = 3,
|
|
72
|
+
) -> list[WasteFinding]:
|
|
73
|
+
"""W2: the same tool output appears at least N times in one session."""
|
|
74
|
+
grouped: dict[bytes, list[HashObservation]] = {}
|
|
75
|
+
for observation in observations:
|
|
76
|
+
grouped.setdefault(observation.hash, []).append(observation)
|
|
77
|
+
findings: list[WasteFinding] = []
|
|
78
|
+
for items in grouped.values():
|
|
79
|
+
if len(items) < min_occurrences:
|
|
80
|
+
continue
|
|
81
|
+
tokens = items[0].tokens * (len(items) - 1)
|
|
82
|
+
findings.append(
|
|
83
|
+
_finding(
|
|
84
|
+
"identical_output",
|
|
85
|
+
items[0],
|
|
86
|
+
session_id,
|
|
87
|
+
project,
|
|
88
|
+
ts,
|
|
89
|
+
occurrences=len(items),
|
|
90
|
+
evidence={"turns": [item.turn for item in items]},
|
|
91
|
+
tokens=tokens,
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
return findings
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def unbatched_fanout(
|
|
98
|
+
turns: list[list[str]],
|
|
99
|
+
*,
|
|
100
|
+
read_names: set[str],
|
|
101
|
+
session_id: str,
|
|
102
|
+
project: str | None,
|
|
103
|
+
ts: float,
|
|
104
|
+
min_calls: int = 4,
|
|
105
|
+
) -> list[WasteFinding]:
|
|
106
|
+
"""W3: many sequential read-like calls in one assistant turn."""
|
|
107
|
+
findings: list[WasteFinding] = []
|
|
108
|
+
for turn_index, names in enumerate(turns):
|
|
109
|
+
reads = [name for name in names if name.lower() in read_names]
|
|
110
|
+
if len(reads) < min_calls:
|
|
111
|
+
continue
|
|
112
|
+
findings.append(
|
|
113
|
+
WasteFinding(
|
|
114
|
+
finding_id=new_ulid(),
|
|
115
|
+
ts=ts,
|
|
116
|
+
session_id=session_id,
|
|
117
|
+
project=project,
|
|
118
|
+
detector="unbatched_fanout",
|
|
119
|
+
key_hash=sha256(f"{turn_index}:{','.join(reads)}".encode()).digest()[:16],
|
|
120
|
+
request_id=None,
|
|
121
|
+
occurrences=1,
|
|
122
|
+
first_seen_ts=ts,
|
|
123
|
+
last_seen_ts=ts,
|
|
124
|
+
evidence={"turn": turn_index, "calls": reads},
|
|
125
|
+
est_tokens_wasted=0,
|
|
126
|
+
est_cost_usd=None,
|
|
127
|
+
status="new",
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
return findings
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def runaway_session(
|
|
134
|
+
previous_ts: float | None,
|
|
135
|
+
current_ts: float,
|
|
136
|
+
turns: int,
|
|
137
|
+
*,
|
|
138
|
+
session_id: str,
|
|
139
|
+
project: str | None,
|
|
140
|
+
tokens: int,
|
|
141
|
+
idle_gap_s: float = 900.0,
|
|
142
|
+
min_turns: int = 6,
|
|
143
|
+
) -> WasteFinding | None:
|
|
144
|
+
"""W4: an active session resumes after a long idle gap."""
|
|
145
|
+
if previous_ts is None or turns < min_turns:
|
|
146
|
+
return None
|
|
147
|
+
gap = current_ts - previous_ts
|
|
148
|
+
if gap <= idle_gap_s:
|
|
149
|
+
return None
|
|
150
|
+
return WasteFinding(
|
|
151
|
+
finding_id=new_ulid(),
|
|
152
|
+
ts=current_ts,
|
|
153
|
+
session_id=session_id,
|
|
154
|
+
project=project,
|
|
155
|
+
detector="runaway_session",
|
|
156
|
+
key_hash=sha256(f"{session_id}:{int(gap)}".encode()).digest()[:16],
|
|
157
|
+
request_id=None,
|
|
158
|
+
occurrences=1,
|
|
159
|
+
first_seen_ts=current_ts,
|
|
160
|
+
last_seen_ts=current_ts,
|
|
161
|
+
evidence={"gap_s": round(gap, 3), "turns": turns},
|
|
162
|
+
est_tokens_wasted=tokens,
|
|
163
|
+
est_cost_usd=None,
|
|
164
|
+
status="new",
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def recurring_drift(
|
|
169
|
+
events: list[DriftEvent],
|
|
170
|
+
*,
|
|
171
|
+
session_id: str,
|
|
172
|
+
project: str | None,
|
|
173
|
+
ts: float,
|
|
174
|
+
min_occurrences: int = 2,
|
|
175
|
+
) -> list[WasteFinding]:
|
|
176
|
+
"""W5/W6: the same drift cause recurring in a session."""
|
|
177
|
+
grouped: dict[str, list[DriftEvent]] = {}
|
|
178
|
+
for event in events:
|
|
179
|
+
grouped.setdefault(event.cause, []).append(event)
|
|
180
|
+
findings: list[WasteFinding] = []
|
|
181
|
+
for cause, items in grouped.items():
|
|
182
|
+
if len(items) < min_occurrences:
|
|
183
|
+
continue
|
|
184
|
+
detector = (
|
|
185
|
+
"params_drift" if cause in {"params_changed", "model_switched"} else "recurring_drift"
|
|
186
|
+
)
|
|
187
|
+
findings.append(
|
|
188
|
+
WasteFinding(
|
|
189
|
+
finding_id=new_ulid(),
|
|
190
|
+
ts=ts,
|
|
191
|
+
session_id=session_id,
|
|
192
|
+
project=project,
|
|
193
|
+
detector=detector,
|
|
194
|
+
key_hash=sha256(f"{detector}:{cause}".encode()).digest()[:16],
|
|
195
|
+
request_id=None,
|
|
196
|
+
occurrences=len(items),
|
|
197
|
+
first_seen_ts=ts,
|
|
198
|
+
last_seen_ts=ts,
|
|
199
|
+
evidence={"cause": cause, "events": len(items)},
|
|
200
|
+
est_tokens_wasted=sum(item.est_tokens_lost for item in items),
|
|
201
|
+
est_cost_usd=None,
|
|
202
|
+
status="new",
|
|
203
|
+
)
|
|
204
|
+
)
|
|
205
|
+
return findings
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def schema_bloat(
|
|
209
|
+
name: str,
|
|
210
|
+
tokens: int,
|
|
211
|
+
request_count: int,
|
|
212
|
+
*,
|
|
213
|
+
session_id: str,
|
|
214
|
+
project: str | None,
|
|
215
|
+
ts: float,
|
|
216
|
+
min_requests: int = 10,
|
|
217
|
+
) -> WasteFinding | None:
|
|
218
|
+
"""W8: an unused tool schema re-billed on every subsequent request."""
|
|
219
|
+
if request_count < min_requests:
|
|
220
|
+
return None
|
|
221
|
+
return WasteFinding(
|
|
222
|
+
finding_id=new_ulid(),
|
|
223
|
+
ts=ts,
|
|
224
|
+
session_id=session_id,
|
|
225
|
+
project=project,
|
|
226
|
+
detector="schema_bloat",
|
|
227
|
+
key_hash=sha256(f"schema_bloat:{name}".encode()).digest()[:16],
|
|
228
|
+
request_id=None,
|
|
229
|
+
occurrences=request_count,
|
|
230
|
+
first_seen_ts=ts,
|
|
231
|
+
last_seen_ts=ts,
|
|
232
|
+
evidence={"tool": name, "requests": request_count},
|
|
233
|
+
est_tokens_wasted=tokens * max(request_count - 1, 0),
|
|
234
|
+
est_cost_usd=None,
|
|
235
|
+
status="new",
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def churn_edits(
|
|
240
|
+
edits: list[EditObservation],
|
|
241
|
+
last_by_path: dict[str, EditObservation],
|
|
242
|
+
*,
|
|
243
|
+
session_id: str,
|
|
244
|
+
project: str | None,
|
|
245
|
+
ts: float,
|
|
246
|
+
) -> list[WasteFinding]:
|
|
247
|
+
"""W7: whitespace-only edits and A->B->A reverts (opt-in)."""
|
|
248
|
+
findings: list[WasteFinding] = []
|
|
249
|
+
for edit in edits:
|
|
250
|
+
previous = last_by_path.get(edit.path)
|
|
251
|
+
kind: str | None = None
|
|
252
|
+
if edit.new != edit.old and edit.new.strip() == edit.old.strip():
|
|
253
|
+
kind = "whitespace_only"
|
|
254
|
+
elif previous is not None and edit.old == previous.new and edit.new == previous.old:
|
|
255
|
+
kind = "revert"
|
|
256
|
+
if kind is not None:
|
|
257
|
+
findings.append(
|
|
258
|
+
WasteFinding(
|
|
259
|
+
finding_id=new_ulid(),
|
|
260
|
+
ts=ts,
|
|
261
|
+
session_id=session_id,
|
|
262
|
+
project=project,
|
|
263
|
+
detector="churn_detector",
|
|
264
|
+
key_hash=sha256(f"churn:{kind}:{edit.path}".encode()).digest()[:16],
|
|
265
|
+
request_id=None,
|
|
266
|
+
occurrences=1,
|
|
267
|
+
first_seen_ts=ts,
|
|
268
|
+
last_seen_ts=ts,
|
|
269
|
+
evidence={
|
|
270
|
+
"kind": kind,
|
|
271
|
+
"path": edit.path,
|
|
272
|
+
"tool": edit.tool_name,
|
|
273
|
+
},
|
|
274
|
+
est_tokens_wasted=edit.tokens,
|
|
275
|
+
est_cost_usd=None,
|
|
276
|
+
status="new",
|
|
277
|
+
)
|
|
278
|
+
)
|
|
279
|
+
last_by_path[edit.path] = edit
|
|
280
|
+
return findings
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _finding(
|
|
284
|
+
detector: str,
|
|
285
|
+
observation: HashObservation,
|
|
286
|
+
session_id: str,
|
|
287
|
+
project: str | None,
|
|
288
|
+
ts: float,
|
|
289
|
+
*,
|
|
290
|
+
occurrences: int,
|
|
291
|
+
evidence: dict[str, object],
|
|
292
|
+
tokens: int | None = None,
|
|
293
|
+
) -> WasteFinding:
|
|
294
|
+
return WasteFinding(
|
|
295
|
+
finding_id=new_ulid(),
|
|
296
|
+
ts=ts,
|
|
297
|
+
session_id=session_id,
|
|
298
|
+
project=project,
|
|
299
|
+
detector=detector,
|
|
300
|
+
key_hash=sha256(detector.encode() + observation.hash).digest()[:16],
|
|
301
|
+
request_id=None,
|
|
302
|
+
occurrences=occurrences,
|
|
303
|
+
first_seen_ts=ts,
|
|
304
|
+
last_seen_ts=ts,
|
|
305
|
+
evidence=evidence,
|
|
306
|
+
est_tokens_wasted=observation.tokens if tokens is None else tokens,
|
|
307
|
+
est_cost_usd=None,
|
|
308
|
+
status="new",
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
__all__ = [
|
|
313
|
+
"EditObservation",
|
|
314
|
+
"HashObservation",
|
|
315
|
+
"churn_edits",
|
|
316
|
+
"identical_outputs",
|
|
317
|
+
"recurring_drift",
|
|
318
|
+
"redundant_rereads",
|
|
319
|
+
"runaway_session",
|
|
320
|
+
"schema_bloat",
|
|
321
|
+
"unbatched_fanout",
|
|
322
|
+
]
|