lorebase-keeper 0.2.3__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.
- lorebase_keeper/__init__.py +1 -0
- lorebase_keeper/__main__.py +456 -0
- lorebase_keeper/agents/__init__.py +1 -0
- lorebase_keeper/agents/budget.py +247 -0
- lorebase_keeper/agents/context.py +529 -0
- lorebase_keeper/agents/explain.py +582 -0
- lorebase_keeper/agents/feed.py +259 -0
- lorebase_keeper/agents/markdown.py +219 -0
- lorebase_keeper/agents/pack.py +403 -0
- lorebase_keeper/agents/settings.py +111 -0
- lorebase_keeper/app.py +373 -0
- lorebase_keeper/auth/__init__.py +4 -0
- lorebase_keeper/auth/api_keys.py +127 -0
- lorebase_keeper/auth/audit.py +356 -0
- lorebase_keeper/auth/cache.py +53 -0
- lorebase_keeper/auth/config.py +618 -0
- lorebase_keeper/auth/deps.py +183 -0
- lorebase_keeper/auth/keyring.py +14 -0
- lorebase_keeper/auth/oidc.py +242 -0
- lorebase_keeper/auth/permissions.py +142 -0
- lorebase_keeper/auth/ports.py +212 -0
- lorebase_keeper/auth/principals.py +377 -0
- lorebase_keeper/auth/provider.py +250 -0
- lorebase_keeper/auth/rbac.py +247 -0
- lorebase_keeper/auth/read_audit.py +357 -0
- lorebase_keeper/auth/runtime.py +113 -0
- lorebase_keeper/auth/sessions.py +452 -0
- lorebase_keeper/bootstrap.py +151 -0
- lorebase_keeper/client_address.py +129 -0
- lorebase_keeper/context.py +226 -0
- lorebase_keeper/db.py +93 -0
- lorebase_keeper/deps.py +47 -0
- lorebase_keeper/errors.py +257 -0
- lorebase_keeper/health.py +61 -0
- lorebase_keeper/kindhealth/__init__.py +6 -0
- lorebase_keeper/kindhealth/config.py +51 -0
- lorebase_keeper/kindhealth/markdown.py +163 -0
- lorebase_keeper/kindhealth/models.py +179 -0
- lorebase_keeper/kindhealth/report.py +154 -0
- lorebase_keeper/kindhealth/scan.py +512 -0
- lorebase_keeper/middleware.py +285 -0
- lorebase_keeper/pagination.py +87 -0
- lorebase_keeper/ratelimit/__init__.py +10 -0
- lorebase_keeper/ratelimit/bucket.py +118 -0
- lorebase_keeper/ratelimit/config.py +283 -0
- lorebase_keeper/ratelimit/enforce.py +393 -0
- lorebase_keeper/ratelimit/limiter.py +149 -0
- lorebase_keeper/ratelimit/quotas.py +204 -0
- lorebase_keeper/ratelimit/shared.py +242 -0
- lorebase_keeper/readiness.py +257 -0
- lorebase_keeper/reading/__init__.py +1 -0
- lorebase_keeper/reading/caching.py +251 -0
- lorebase_keeper/reading/compiler.py +678 -0
- lorebase_keeper/reading/cursors.py +56 -0
- lorebase_keeper/reading/diff.py +408 -0
- lorebase_keeper/reading/graph.py +268 -0
- lorebase_keeper/reading/history.py +152 -0
- lorebase_keeper/reading/points.py +410 -0
- lorebase_keeper/reading/problems.py +74 -0
- lorebase_keeper/reading/record_history.py +722 -0
- lorebase_keeper/reading/records.py +780 -0
- lorebase_keeper/reading/requests.py +107 -0
- lorebase_keeper/reading/runtime.py +145 -0
- lorebase_keeper/reading/search.py +708 -0
- lorebase_keeper/reading/sensitivity.py +882 -0
- lorebase_keeper/reading/settings.py +339 -0
- lorebase_keeper/reading/snapshots.py +148 -0
- lorebase_keeper/reading/tree.py +98 -0
- lorebase_keeper/reading/views.py +264 -0
- lorebase_keeper/reading/visibility.py +93 -0
- lorebase_keeper/routes/__init__.py +1 -0
- lorebase_keeper/routes/admin.py +597 -0
- lorebase_keeper/routes/agents.py +160 -0
- lorebase_keeper/routes/branches.py +438 -0
- lorebase_keeper/routes/derivations.py +215 -0
- lorebase_keeper/routes/documents.py +497 -0
- lorebase_keeper/routes/domain_api.py +59 -0
- lorebase_keeper/routes/domains.py +220 -0
- lorebase_keeper/routes/events.py +187 -0
- lorebase_keeper/routes/kind_health.py +166 -0
- lorebase_keeper/routes/operations.py +392 -0
- lorebase_keeper/routes/proposal_items.py +563 -0
- lorebase_keeper/routes/proposals.py +1107 -0
- lorebase_keeper/routes/read.py +493 -0
- lorebase_keeper/routes/revisions.py +523 -0
- lorebase_keeper/routes/schema.py +682 -0
- lorebase_keeper/routes/schema_inference.py +437 -0
- lorebase_keeper/routes/schema_suggestions.py +328 -0
- lorebase_keeper/routes/sessions.py +286 -0
- lorebase_keeper/routes/source_preview.py +232 -0
- lorebase_keeper/routes/sources.py +200 -0
- lorebase_keeper/routes/trace.py +506 -0
- lorebase_keeper/routes/vault.py +332 -0
- lorebase_keeper/routes/webhooks.py +298 -0
- lorebase_keeper/routes/whoami.py +88 -0
- lorebase_keeper/services/__init__.py +1 -0
- lorebase_keeper/services/agent_writes.py +1035 -0
- lorebase_keeper/services/branches.py +337 -0
- lorebase_keeper/services/config.py +431 -0
- lorebase_keeper/services/derivation_preview.py +279 -0
- lorebase_keeper/services/document_urls.py +114 -0
- lorebase_keeper/services/domains.py +118 -0
- lorebase_keeper/services/errors.py +196 -0
- lorebase_keeper/services/operation_catalog.py +806 -0
- lorebase_keeper/services/operation_events.py +199 -0
- lorebase_keeper/services/operations.py +1229 -0
- lorebase_keeper/services/pii_import.py +222 -0
- lorebase_keeper/services/proposal_reads.py +158 -0
- lorebase_keeper/services/proposals.py +850 -0
- lorebase_keeper/services/reads.py +286 -0
- lorebase_keeper/services/schema.py +601 -0
- lorebase_keeper/services/schema_inference.py +390 -0
- lorebase_keeper/services/schema_suggestions.py +418 -0
- lorebase_keeper/services/source_preview.py +127 -0
- lorebase_keeper/settings.py +313 -0
- lorebase_keeper/tracing/__init__.py +1 -0
- lorebase_keeper/tracing/capture.py +350 -0
- lorebase_keeper/tracing/config.py +157 -0
- lorebase_keeper/tracing/explain.py +97 -0
- lorebase_keeper/tracing/middleware.py +359 -0
- lorebase_keeper/tracing/notes.py +80 -0
- lorebase_keeper/tracing/recorder.py +180 -0
- lorebase_keeper-0.2.3.dist-info/METADATA +21 -0
- lorebase_keeper-0.2.3.dist-info/RECORD +126 -0
- lorebase_keeper-0.2.3.dist-info/WHEEL +4 -0
- lorebase_keeper-0.2.3.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"""Token budget of context packs: a cheap deterministic estimate and the trimming order.
|
|
2
|
+
|
|
3
|
+
A pack is estimated section by section: `tokens = ceil(chars / chars_per_token)` over the
|
|
4
|
+
exact text each section renders to in the requested format (compact JSON or markdown). No
|
|
5
|
+
tokenizer and no model are involved, so the same request at the same Revision always yields
|
|
6
|
+
the same pack.
|
|
7
|
+
|
|
8
|
+
When the sections exceed the budget, trim steps are applied in the configured order
|
|
9
|
+
(`agents.context.trim_order`; default `DEFAULT_TRIM_ORDER`) until the pack fits:
|
|
10
|
+
|
|
11
|
+
1. `lineage`: lineage entries, last first;
|
|
12
|
+
2. `recent_changes`: change entries, oldest first;
|
|
13
|
+
3. `far_neighbor_values`: values of neighbors beyond depth 1 (their reference and title stay);
|
|
14
|
+
4. `far_neighbors`: neighbors beyond depth 1 with their Links, deepest and last first;
|
|
15
|
+
5. `schema_descriptions`: descriptions in the schema fragment (names and types stay);
|
|
16
|
+
6. `near_neighbor_values`: values of depth 1 neighbors;
|
|
17
|
+
7. `near_neighbors`: depth 1 neighbors with their Links, last first;
|
|
18
|
+
8. `schema`: the schema fragment.
|
|
19
|
+
|
|
20
|
+
Each step removes as few units as needed (found by bisection: removing units never grows a
|
|
21
|
+
section). Seed Records are never trimmed; a pack whose seeds alone exceed the budget is
|
|
22
|
+
returned whole and flagged `over_budget`.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import math
|
|
28
|
+
from collections.abc import Callable, Mapping, Sequence
|
|
29
|
+
from dataclasses import dataclass, field, replace
|
|
30
|
+
from enum import StrEnum
|
|
31
|
+
from typing import Any
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TrimStep(StrEnum):
|
|
35
|
+
LINEAGE = "lineage"
|
|
36
|
+
RECENT_CHANGES = "recent_changes"
|
|
37
|
+
FAR_NEIGHBOR_VALUES = "far_neighbor_values"
|
|
38
|
+
FAR_NEIGHBORS = "far_neighbors"
|
|
39
|
+
SCHEMA_DESCRIPTIONS = "schema_descriptions"
|
|
40
|
+
NEAR_NEIGHBOR_VALUES = "near_neighbor_values"
|
|
41
|
+
NEAR_NEIGHBORS = "near_neighbors"
|
|
42
|
+
SCHEMA = "schema"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
DEFAULT_TRIM_ORDER: tuple[TrimStep, ...] = tuple(TrimStep)
|
|
46
|
+
NEAR_DEPTH = 1
|
|
47
|
+
"""Neighbors at this depth or closer are `near`; deeper ones are `far`."""
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def estimate_tokens(chars: int, chars_per_token: float) -> int:
|
|
51
|
+
"""Estimated tokens of `chars` characters."""
|
|
52
|
+
return math.ceil(chars / chars_per_token)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass(frozen=True, slots=True)
|
|
56
|
+
class SeedItem:
|
|
57
|
+
ref: str
|
|
58
|
+
kind: str
|
|
59
|
+
title: str | None
|
|
60
|
+
values: Mapping[str, Any]
|
|
61
|
+
updated_revision: str | None = None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass(frozen=True, slots=True)
|
|
65
|
+
class NeighborItem:
|
|
66
|
+
ref: str
|
|
67
|
+
kind: str
|
|
68
|
+
title: str | None
|
|
69
|
+
depth: int
|
|
70
|
+
values: Mapping[str, Any] | None
|
|
71
|
+
"""`None` once trimmed: only the reference and title remain."""
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass(frozen=True, slots=True)
|
|
75
|
+
class LinkItem:
|
|
76
|
+
relation: str
|
|
77
|
+
"""Forward api name of the Relation."""
|
|
78
|
+
source: str
|
|
79
|
+
target: str
|
|
80
|
+
values: Mapping[str, Any] = field(default_factory=dict)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass(frozen=True, slots=True)
|
|
84
|
+
class ChangeItem:
|
|
85
|
+
revision: str
|
|
86
|
+
at: str
|
|
87
|
+
ref: str
|
|
88
|
+
change: str
|
|
89
|
+
attributes: tuple[str, ...]
|
|
90
|
+
actor: str
|
|
91
|
+
origin: str
|
|
92
|
+
message: str
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@dataclass(frozen=True, slots=True)
|
|
96
|
+
class LineageItem:
|
|
97
|
+
ref: str
|
|
98
|
+
origin: str | None
|
|
99
|
+
actor: str | None
|
|
100
|
+
at: str | None
|
|
101
|
+
attributes: tuple[str, ...]
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@dataclass(frozen=True, slots=True)
|
|
105
|
+
class Draft:
|
|
106
|
+
"""The content of a pack, in output order. `None` marks a section left out."""
|
|
107
|
+
|
|
108
|
+
seeds: tuple[SeedItem, ...]
|
|
109
|
+
neighbors: tuple[NeighborItem, ...] = ()
|
|
110
|
+
links: tuple[LinkItem, ...] = ()
|
|
111
|
+
schema: bool = False
|
|
112
|
+
schema_descriptions: bool = True
|
|
113
|
+
changes: tuple[ChangeItem, ...] | None = None
|
|
114
|
+
lineage: tuple[LineageItem, ...] | None = None
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@dataclass(frozen=True, slots=True)
|
|
118
|
+
class Trim:
|
|
119
|
+
step: TrimStep
|
|
120
|
+
removed: int
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@dataclass(frozen=True, slots=True)
|
|
124
|
+
class BudgetResult:
|
|
125
|
+
draft: Draft
|
|
126
|
+
trims: tuple[Trim, ...]
|
|
127
|
+
sections: Mapping[str, int]
|
|
128
|
+
"""Characters per section of the final draft."""
|
|
129
|
+
limit_chars: float
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def chars(self) -> int:
|
|
133
|
+
return sum(self.sections.values())
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def over_budget(self) -> bool:
|
|
137
|
+
return self.chars > self.limit_chars
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
Measure = Callable[[Draft], Mapping[str, int]]
|
|
141
|
+
"""Characters of each section of a draft, as rendered in the requested format."""
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _is_near(item: NeighborItem) -> bool:
|
|
145
|
+
return item.depth <= NEAR_DEPTH
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _candidates(draft: Draft, near: bool, *, with_values: bool) -> list[int]:
|
|
149
|
+
"""Neighbor positions in removal order (deepest, then last in output order, first)."""
|
|
150
|
+
positions = [
|
|
151
|
+
i
|
|
152
|
+
for i, item in enumerate(draft.neighbors)
|
|
153
|
+
if _is_near(item) is near and (not with_values or item.values is not None)
|
|
154
|
+
]
|
|
155
|
+
return sorted(positions, key=lambda i: (-draft.neighbors[i].depth, -i))
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def units(draft: Draft, step: TrimStep) -> int:
|
|
159
|
+
"""How many units `step` can remove from `draft`."""
|
|
160
|
+
match step:
|
|
161
|
+
case TrimStep.LINEAGE:
|
|
162
|
+
return len(draft.lineage or ())
|
|
163
|
+
case TrimStep.RECENT_CHANGES:
|
|
164
|
+
return len(draft.changes or ())
|
|
165
|
+
case TrimStep.FAR_NEIGHBOR_VALUES | TrimStep.NEAR_NEIGHBOR_VALUES:
|
|
166
|
+
near = step is TrimStep.NEAR_NEIGHBOR_VALUES
|
|
167
|
+
return len(_candidates(draft, near, with_values=True))
|
|
168
|
+
case TrimStep.FAR_NEIGHBORS | TrimStep.NEAR_NEIGHBORS:
|
|
169
|
+
return len(_candidates(draft, step is TrimStep.NEAR_NEIGHBORS, with_values=False))
|
|
170
|
+
case TrimStep.SCHEMA_DESCRIPTIONS:
|
|
171
|
+
return 1 if draft.schema and draft.schema_descriptions else 0
|
|
172
|
+
case TrimStep.SCHEMA:
|
|
173
|
+
return 1 if draft.schema else 0
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def apply(draft: Draft, step: TrimStep, count: int) -> Draft:
|
|
177
|
+
"""`draft` with the first `count` units of `step` removed."""
|
|
178
|
+
if count <= 0:
|
|
179
|
+
return draft
|
|
180
|
+
match step:
|
|
181
|
+
case TrimStep.LINEAGE:
|
|
182
|
+
lineage = draft.lineage or ()
|
|
183
|
+
return replace(draft, lineage=lineage[: max(len(lineage) - count, 0)])
|
|
184
|
+
case TrimStep.RECENT_CHANGES:
|
|
185
|
+
return replace(draft, changes=(draft.changes or ())[count:])
|
|
186
|
+
case TrimStep.FAR_NEIGHBOR_VALUES | TrimStep.NEAR_NEIGHBOR_VALUES:
|
|
187
|
+
near = step is TrimStep.NEAR_NEIGHBOR_VALUES
|
|
188
|
+
stripped = set(_candidates(draft, near, with_values=True)[:count])
|
|
189
|
+
neighbors = tuple(
|
|
190
|
+
replace(item, values=None) if i in stripped else item
|
|
191
|
+
for i, item in enumerate(draft.neighbors)
|
|
192
|
+
)
|
|
193
|
+
return replace(draft, neighbors=neighbors)
|
|
194
|
+
case TrimStep.FAR_NEIGHBORS | TrimStep.NEAR_NEIGHBORS:
|
|
195
|
+
near = step is TrimStep.NEAR_NEIGHBORS
|
|
196
|
+
dropped = set(_candidates(draft, near, with_values=False)[:count])
|
|
197
|
+
gone = {draft.neighbors[i].ref for i in dropped}
|
|
198
|
+
return replace(
|
|
199
|
+
draft,
|
|
200
|
+
neighbors=tuple(n for i, n in enumerate(draft.neighbors) if i not in dropped),
|
|
201
|
+
links=tuple(
|
|
202
|
+
link
|
|
203
|
+
for link in draft.links
|
|
204
|
+
if link.source not in gone and link.target not in gone
|
|
205
|
+
),
|
|
206
|
+
)
|
|
207
|
+
case TrimStep.SCHEMA_DESCRIPTIONS:
|
|
208
|
+
return replace(draft, schema_descriptions=False)
|
|
209
|
+
case TrimStep.SCHEMA:
|
|
210
|
+
return replace(draft, schema=False)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def fit(
|
|
214
|
+
draft: Draft,
|
|
215
|
+
*,
|
|
216
|
+
budget_tokens: int,
|
|
217
|
+
chars_per_token: float,
|
|
218
|
+
measure: Measure,
|
|
219
|
+
order: Sequence[TrimStep] = DEFAULT_TRIM_ORDER,
|
|
220
|
+
) -> BudgetResult:
|
|
221
|
+
"""Trim `draft` by `order` until its sections fit `budget_tokens` (see module doc)."""
|
|
222
|
+
limit = budget_tokens * chars_per_token
|
|
223
|
+
|
|
224
|
+
def size(candidate: Draft) -> int:
|
|
225
|
+
return sum(measure(candidate).values())
|
|
226
|
+
|
|
227
|
+
current = draft
|
|
228
|
+
trims: list[Trim] = []
|
|
229
|
+
for step in order:
|
|
230
|
+
if size(current) <= limit:
|
|
231
|
+
break
|
|
232
|
+
available = units(current, step)
|
|
233
|
+
if available == 0:
|
|
234
|
+
continue
|
|
235
|
+
count = available
|
|
236
|
+
if size(apply(current, step, available)) <= limit:
|
|
237
|
+
low, high = 1, available
|
|
238
|
+
while low < high:
|
|
239
|
+
middle = (low + high) // 2
|
|
240
|
+
if size(apply(current, step, middle)) <= limit:
|
|
241
|
+
high = middle
|
|
242
|
+
else:
|
|
243
|
+
low = middle + 1
|
|
244
|
+
count = low
|
|
245
|
+
current = apply(current, step, count)
|
|
246
|
+
trims.append(Trim(step, count))
|
|
247
|
+
return BudgetResult(current, tuple(trims), dict(measure(current)), limit)
|