context-engineering-core 0.12.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.
- context_engine/__init__.py +15 -0
- context_engine/__main__.py +3 -0
- context_engine/_sqlite.py +19 -0
- context_engine/_windows_security.py +125 -0
- context_engine/_windows_storage.py +110 -0
- context_engine/adapters.py +148 -0
- context_engine/answers.py +90 -0
- context_engine/budget.py +93 -0
- context_engine/cli.py +222 -0
- context_engine/config.py +193 -0
- context_engine/context.py +116 -0
- context_engine/demo.py +98 -0
- context_engine/errors.py +91 -0
- context_engine/evaluation/__init__.py +1 -0
- context_engine/evaluation/commands.py +90 -0
- context_engine/evaluation/corpus.py +247 -0
- context_engine/evaluation/data/freeze.json +26 -0
- context_engine/evaluation/data/ground_truth.json +287 -0
- context_engine/evaluation/data/pin_retention.json +8 -0
- context_engine/evaluation/data/protocol.json +122 -0
- context_engine/evaluation/data/scenario.json +1226 -0
- context_engine/evaluation/data/summary_fixture.json +7 -0
- context_engine/evaluation/execution.py +538 -0
- context_engine/evaluation/fixtures.py +32 -0
- context_engine/evaluation/grading.py +62 -0
- context_engine/evaluation/harness.py +165 -0
- context_engine/evaluation/heldout/__init__.py +260 -0
- context_engine/evaluation/heldout/__main__.py +40 -0
- context_engine/evaluation/heldout/data/ground_truth.json +18 -0
- context_engine/evaluation/heldout/data/manifest.json +9 -0
- context_engine/evaluation/heldout/data/protocol.json +14 -0
- context_engine/evaluation/heldout/data/scenarios.json +18 -0
- context_engine/evaluation/heldout/data/scoring_manifest.json +4 -0
- context_engine/evaluation/heldout/data/scoring_protocol.json +12 -0
- context_engine/evaluation/heldout/scoring.py +176 -0
- context_engine/evaluation/protocol.py +264 -0
- context_engine/evaluation/reporting.py +294 -0
- context_engine/evaluation/results.py +331 -0
- context_engine/evaluation/run_commands.py +146 -0
- context_engine/evaluation/strategies.py +261 -0
- context_engine/inspection.py +266 -0
- context_engine/layers/__init__.py +18 -0
- context_engine/layers/cap.py +99 -0
- context_engine/layers/common.py +162 -0
- context_engine/layers/pin.py +68 -0
- context_engine/layers/retrieve.py +233 -0
- context_engine/layers/summarize.py +202 -0
- context_engine/layers/window.py +29 -0
- context_engine/mcp_server.py +267 -0
- context_engine/memory/__init__.py +5 -0
- context_engine/memory/codec.py +91 -0
- context_engine/memory/demo.py +90 -0
- context_engine/memory/store.py +770 -0
- context_engine/models.py +384 -0
- context_engine/pipeline.py +383 -0
- context_engine/providers/__init__.py +1 -0
- context_engine/providers/client.py +301 -0
- context_engine/providers/commands.py +138 -0
- context_engine/providers/contracts.py +324 -0
- context_engine/providers/credentials.py +66 -0
- context_engine/providers/memory.py +112 -0
- context_engine/providers/store.py +408 -0
- context_engine/providers/transport.py +195 -0
- context_engine/providers/windows_credentials.py +23 -0
- context_engine/py.typed +0 -0
- context_engine/service/__init__.py +1 -0
- context_engine/service/__main__.py +68 -0
- context_engine/service/app.py +404 -0
- context_engine/service/auth.py +132 -0
- context_engine/service/client.py +52 -0
- context_engine/service/control.py +134 -0
- context_engine/service/demo.py +151 -0
- context_engine/service/integrations.py +28 -0
- context_engine/service/schemas.py +59 -0
- context_engine/service/worker.py +59 -0
- context_engine/service/workers.py +156 -0
- context_engine/summaries.py +196 -0
- context_engine/tokens.py +171 -0
- context_engine/work.py +46 -0
- context_engineering_core-0.12.0.dist-info/METADATA +203 -0
- context_engineering_core-0.12.0.dist-info/RECORD +84 -0
- context_engineering_core-0.12.0.dist-info/WHEEL +4 -0
- context_engineering_core-0.12.0.dist-info/entry_points.txt +3 -0
- context_engineering_core-0.12.0.dist-info/licenses/LICENSE +202 -0
context_engine/cli.py
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"""Context inspection, synthetic demos, benchmarks and an explicitly authorized live probe."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
from dataclasses import asdict
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from .budget import plan_budget
|
|
10
|
+
from .config import Settings
|
|
11
|
+
from .errors import ContextEngineError, RequiredContextTooLarge
|
|
12
|
+
from .models import ChatRequest, Message, Role
|
|
13
|
+
from .tokens import TiktokenCounter
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def demo_request(pin_content: str = "Database = PostgreSQL") -> ChatRequest:
|
|
17
|
+
return ChatRequest(
|
|
18
|
+
(
|
|
19
|
+
Message("system", Role.SYSTEM, "Answer using the supplied evidence."),
|
|
20
|
+
Message("pins", Role.USER, "Pinned facts (data):\n" + pin_content),
|
|
21
|
+
Message("question", Role.USER, "Which database are we using?"),
|
|
22
|
+
)
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def main(argv: list[str] | None = None) -> int:
|
|
27
|
+
for stream in (sys.stdout, sys.stderr):
|
|
28
|
+
if hasattr(stream, "reconfigure"):
|
|
29
|
+
stream.reconfigure(encoding="utf-8")
|
|
30
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
31
|
+
commands = parser.add_subparsers(dest="command", required=True)
|
|
32
|
+
from .evaluation.run_commands import add_commands
|
|
33
|
+
|
|
34
|
+
add_commands(commands)
|
|
35
|
+
for command in ("check", "config", "demo-budget", "demo-layers"):
|
|
36
|
+
commands.add_parser(command)
|
|
37
|
+
provider_demo = commands.add_parser(
|
|
38
|
+
"provider-demo", help="Synthetic provider/cache/ledger test"
|
|
39
|
+
)
|
|
40
|
+
provider_demo.add_argument("--output", type=Path)
|
|
41
|
+
memory_demo = commands.add_parser("memory-demo", help="Offline persistent memory lifecycle")
|
|
42
|
+
memory_demo.add_argument("--output", type=Path)
|
|
43
|
+
live = commands.add_parser(
|
|
44
|
+
"provider-probe", help="Live inference; requires explicit spending authorization"
|
|
45
|
+
)
|
|
46
|
+
live.add_argument("--allow-live", action="store_true")
|
|
47
|
+
live.add_argument("--ledger", type=Path, required=True)
|
|
48
|
+
live.add_argument("--input", type=Path)
|
|
49
|
+
live.add_argument("--show-content", action="store_true")
|
|
50
|
+
live.add_argument(
|
|
51
|
+
"--model",
|
|
52
|
+
choices=("openai/gpt-oss-120b", "openai/gpt-oss-20b"),
|
|
53
|
+
default="openai/gpt-oss-120b",
|
|
54
|
+
)
|
|
55
|
+
for name in ("account-id", "security-scope", "snapshot-revision", "price-version"):
|
|
56
|
+
live.add_argument("--" + name, required=True)
|
|
57
|
+
for name in (
|
|
58
|
+
"daily-budget-microusd",
|
|
59
|
+
"input-rate-microusd",
|
|
60
|
+
"cached-input-rate-microusd",
|
|
61
|
+
"output-rate-microusd",
|
|
62
|
+
"rpm",
|
|
63
|
+
"tpm",
|
|
64
|
+
"rpd",
|
|
65
|
+
"tpd",
|
|
66
|
+
):
|
|
67
|
+
live.add_argument("--" + name, type=int, required=True)
|
|
68
|
+
benchmark = commands.add_parser(
|
|
69
|
+
"benchmark-check", help="Validate the frozen synthetic benchmark offline"
|
|
70
|
+
)
|
|
71
|
+
benchmark.add_argument("--fact", default="shard")
|
|
72
|
+
benchmark.add_argument("--budget", type=int, default=900)
|
|
73
|
+
benchmark.add_argument("--model")
|
|
74
|
+
benchmark.add_argument("--show-content", action="store_true")
|
|
75
|
+
benchmark.add_argument("--data-dir", type=Path)
|
|
76
|
+
benchmark.add_argument("--output", type=Path)
|
|
77
|
+
strategies = commands.add_parser(
|
|
78
|
+
"strategy-eval", help="Compare context strategies on the frozen corpus; no inference"
|
|
79
|
+
)
|
|
80
|
+
strategies.add_argument(
|
|
81
|
+
"--budget", type=int, action="append", help="Input allowance; repeatable"
|
|
82
|
+
)
|
|
83
|
+
strategies.add_argument("--probes", action="store_true", help="Include per-fact results")
|
|
84
|
+
strategies.add_argument("--data-dir", type=Path)
|
|
85
|
+
strategies.add_argument("--output", type=Path, help="Save JSON to a new file; never overwrite")
|
|
86
|
+
planned = commands.add_parser(
|
|
87
|
+
"benchmark-plan", help="List frozen probe slots without inference"
|
|
88
|
+
)
|
|
89
|
+
planned.add_argument("--data-dir", type=Path)
|
|
90
|
+
planned.add_argument("--output", type=Path)
|
|
91
|
+
for command in ("inspect", "probe"):
|
|
92
|
+
command_parser = commands.add_parser(command, help="Inspect context locally; no inference")
|
|
93
|
+
command_parser.add_argument(
|
|
94
|
+
"--input", type=Path, help="Version-1 JSON input; omit for synthetic demo"
|
|
95
|
+
)
|
|
96
|
+
command_parser.add_argument("--budget", type=int, help="Override the input token allowance")
|
|
97
|
+
command_parser.add_argument(
|
|
98
|
+
"--show-content", action="store_true", help="Include assembled prompt bodies"
|
|
99
|
+
)
|
|
100
|
+
command_parser.add_argument(
|
|
101
|
+
"--output", type=Path, help="Save JSON to a new file; never overwrite"
|
|
102
|
+
)
|
|
103
|
+
args = parser.parse_args(argv)
|
|
104
|
+
try:
|
|
105
|
+
if args.command == "memory-demo":
|
|
106
|
+
from .inspection import save_inspection
|
|
107
|
+
from .memory.demo import memory_demo
|
|
108
|
+
|
|
109
|
+
result = memory_demo()
|
|
110
|
+
if args.output is not None:
|
|
111
|
+
save_inspection(args.output, result)
|
|
112
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
113
|
+
return 0
|
|
114
|
+
if args.command in ("benchmark-run", "model-swap", "benchmark-report"):
|
|
115
|
+
from .evaluation.run_commands import run_command
|
|
116
|
+
|
|
117
|
+
result = run_command(args)
|
|
118
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
119
|
+
return 1 if result["status"] == "INVALID" else 0
|
|
120
|
+
if args.command in ("provider-demo", "provider-probe"):
|
|
121
|
+
from .inspection import save_inspection
|
|
122
|
+
from .providers.commands import provider_demo, provider_probe
|
|
123
|
+
|
|
124
|
+
result = provider_demo() if args.command == "provider-demo" else provider_probe(args)
|
|
125
|
+
if args.command == "provider-demo" and args.output is not None:
|
|
126
|
+
save_inspection(args.output, result)
|
|
127
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
128
|
+
return 0 if result["status"] in ("PASS", "success", "replay") else 1
|
|
129
|
+
if args.command == "strategy-eval":
|
|
130
|
+
from .evaluation.corpus import check_fixture, load_bundle
|
|
131
|
+
from .evaluation.strategies import compare_strategies
|
|
132
|
+
from .inspection import save_inspection
|
|
133
|
+
|
|
134
|
+
bundle = load_bundle(args.data_dir)
|
|
135
|
+
check_fixture(bundle)
|
|
136
|
+
budgets = None if args.budget is None else tuple(args.budget)
|
|
137
|
+
if budgets is not None and any(b <= 0 for b in budgets):
|
|
138
|
+
raise ContextEngineError("Budgets must be positive integers")
|
|
139
|
+
result = compare_strategies(bundle, budgets, include_probes=args.probes)
|
|
140
|
+
if args.output is not None:
|
|
141
|
+
save_inspection(args.output, result)
|
|
142
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
143
|
+
return 0
|
|
144
|
+
if args.command in ("benchmark-check", "benchmark-plan"):
|
|
145
|
+
from .evaluation.commands import benchmark_check, benchmark_plan
|
|
146
|
+
from .inspection import save_inspection
|
|
147
|
+
|
|
148
|
+
if args.command == "benchmark-check":
|
|
149
|
+
result = benchmark_check(
|
|
150
|
+
fact=args.fact,
|
|
151
|
+
budget=args.budget,
|
|
152
|
+
model=args.model,
|
|
153
|
+
show_content=args.show_content,
|
|
154
|
+
data_dir=args.data_dir,
|
|
155
|
+
)
|
|
156
|
+
else:
|
|
157
|
+
result = benchmark_plan(data_dir=args.data_dir)
|
|
158
|
+
if args.output is not None:
|
|
159
|
+
save_inspection(args.output, result)
|
|
160
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
161
|
+
return 1 if result["status"] == "INVALID" else 0
|
|
162
|
+
settings = Settings.from_env()
|
|
163
|
+
if args.command == "config":
|
|
164
|
+
print(
|
|
165
|
+
json.dumps(
|
|
166
|
+
{
|
|
167
|
+
key: asdict(getattr(settings, key))
|
|
168
|
+
for key in ("budget", "tokenizer", "cap", "retrieval")
|
|
169
|
+
},
|
|
170
|
+
indent=2,
|
|
171
|
+
)
|
|
172
|
+
)
|
|
173
|
+
return 0
|
|
174
|
+
if args.command in ("inspect", "probe"):
|
|
175
|
+
from .inspection import demo_job, load_job, save_inspection
|
|
176
|
+
|
|
177
|
+
job = load_job(args.input, settings) if args.input is not None else demo_job(settings)
|
|
178
|
+
result = job.run(input_cap=args.budget).to_dict(include_content=args.show_content)
|
|
179
|
+
result.update(
|
|
180
|
+
mode="offline_probe" if args.command == "probe" else "offline_inspection",
|
|
181
|
+
synthetic_input=job.synthetic,
|
|
182
|
+
inference_calls=0,
|
|
183
|
+
)
|
|
184
|
+
if args.output is not None:
|
|
185
|
+
save_inspection(args.output, result)
|
|
186
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
187
|
+
return 0
|
|
188
|
+
counter = TiktokenCounter(settings.tokenizer)
|
|
189
|
+
if args.command == "check":
|
|
190
|
+
from rank_bm25 import BM25Plus
|
|
191
|
+
|
|
192
|
+
BM25Plus([["setup"]]).get_scores(["setup"])
|
|
193
|
+
counter.count_request(demo_request())
|
|
194
|
+
print("deps ok")
|
|
195
|
+
return 0
|
|
196
|
+
if args.command == "demo-layers":
|
|
197
|
+
from .demo import layer_demo
|
|
198
|
+
|
|
199
|
+
print(json.dumps(layer_demo(counter), indent=2, ensure_ascii=False))
|
|
200
|
+
return 0
|
|
201
|
+
plan = plan_budget(required_request=demo_request(), config=settings.budget, counter=counter)
|
|
202
|
+
print(json.dumps({"valid_request": plan.to_dict()}, indent=2))
|
|
203
|
+
# Synthetic, bounded fixture: enough tokens to exceed the allowance.
|
|
204
|
+
if settings.budget.input_allowance > 10000:
|
|
205
|
+
raise ContextEngineError("Demo accepts input allowances up to 10,000 tokens")
|
|
206
|
+
try:
|
|
207
|
+
plan_budget(
|
|
208
|
+
required_request=demo_request("oversized pin " * settings.budget.input_allowance),
|
|
209
|
+
config=settings.budget,
|
|
210
|
+
counter=counter,
|
|
211
|
+
)
|
|
212
|
+
except RequiredContextTooLarge as error:
|
|
213
|
+
print(json.dumps({"expected_failure": error.to_dict()}, indent=2))
|
|
214
|
+
return 0
|
|
215
|
+
raise ContextEngineError("Oversized demo fixture unexpectedly fit")
|
|
216
|
+
except ContextEngineError as error:
|
|
217
|
+
print(json.dumps(error.to_dict()), file=sys.stderr)
|
|
218
|
+
return 1
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
if __name__ == "__main__":
|
|
222
|
+
raise SystemExit(main())
|
context_engine/config.py
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""Validated scalar configuration; no models, tokenizer, provider or benchmark imports."""
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
import os
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from .errors import ConfigurationError
|
|
10
|
+
|
|
11
|
+
CONFIG_DIRECTORY = Path(__file__).resolve().parent
|
|
12
|
+
MAX_INSPECTION_BYTES = 2_000_000
|
|
13
|
+
MAX_INSPECTION_TURNS = 10_000
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def nonnegative_integer(name: str, value: int, *, minimum: int = 0) -> None:
|
|
17
|
+
if type(value) is not int or value < minimum:
|
|
18
|
+
raise ConfigurationError("Expected an integer within range", field=name, minimum=minimum)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True, slots=True)
|
|
22
|
+
class BudgetConfig:
|
|
23
|
+
input_cap: int = 3000
|
|
24
|
+
model_capacity: int = 131072
|
|
25
|
+
completion_reservation: int = 256
|
|
26
|
+
retrieval_reserve: int = 280
|
|
27
|
+
summary_reserve: int = 80
|
|
28
|
+
provider_input_cap: int | None = None
|
|
29
|
+
|
|
30
|
+
def __post_init__(self) -> None:
|
|
31
|
+
for name in ("input_cap", "model_capacity", "completion_reservation"):
|
|
32
|
+
nonnegative_integer(name, getattr(self, name), minimum=1)
|
|
33
|
+
for name in ("retrieval_reserve", "summary_reserve"):
|
|
34
|
+
nonnegative_integer(name, getattr(self, name))
|
|
35
|
+
if self.provider_input_cap is not None:
|
|
36
|
+
nonnegative_integer("provider_input_cap", self.provider_input_cap, minimum=1)
|
|
37
|
+
if self.completion_reservation >= self.model_capacity:
|
|
38
|
+
raise ConfigurationError("Completion reservation leaves no input capacity")
|
|
39
|
+
if self.retrieval_reserve + self.summary_reserve > self.input_allowance:
|
|
40
|
+
raise ConfigurationError("Layer reserves exceed the input allowance")
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def input_allowance(self) -> int:
|
|
44
|
+
ceilings = [self.input_cap, self.model_capacity - self.completion_reservation]
|
|
45
|
+
if self.provider_input_cap is not None:
|
|
46
|
+
ceilings.append(self.provider_input_cap)
|
|
47
|
+
return min(ceilings)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass(frozen=True, slots=True)
|
|
51
|
+
class TokenizerConfig:
|
|
52
|
+
encoding_name: str = "o200k_harmony"
|
|
53
|
+
calibration_factor: float = 1.07
|
|
54
|
+
adapter_overhead_tokens: int = 0
|
|
55
|
+
|
|
56
|
+
def __post_init__(self) -> None:
|
|
57
|
+
if not isinstance(self.encoding_name, str) or not self.encoding_name.strip():
|
|
58
|
+
raise ConfigurationError("Tokenizer name must be non-empty", field="encoding_name")
|
|
59
|
+
factor = self.calibration_factor
|
|
60
|
+
if (
|
|
61
|
+
type(factor) not in (int, float)
|
|
62
|
+
or factor < 1
|
|
63
|
+
or (isinstance(factor, float) and not math.isfinite(factor))
|
|
64
|
+
):
|
|
65
|
+
raise ConfigurationError("Calibration factor must be finite and at least one")
|
|
66
|
+
nonnegative_integer("adapter_overhead_tokens", self.adapter_overhead_tokens)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass(frozen=True, slots=True)
|
|
70
|
+
class CapConfig:
|
|
71
|
+
max_tokens: int = 35
|
|
72
|
+
head_tokens: int = 22
|
|
73
|
+
tail_tokens: int = 8
|
|
74
|
+
|
|
75
|
+
def __post_init__(self) -> None:
|
|
76
|
+
for name in ("max_tokens", "head_tokens", "tail_tokens"):
|
|
77
|
+
nonnegative_integer(name, getattr(self, name), minimum=1)
|
|
78
|
+
if max(self.head_tokens, self.tail_tokens) > self.max_tokens:
|
|
79
|
+
raise ConfigurationError("CAP head/tail limits exceed the message cap")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass(frozen=True, slots=True)
|
|
83
|
+
class RetrievalConfig:
|
|
84
|
+
chunk_characters: int = 360
|
|
85
|
+
overlap_characters: int = 80
|
|
86
|
+
top_k: int = 4
|
|
87
|
+
minimum_score: float = 0.0
|
|
88
|
+
k1: float = 1.5
|
|
89
|
+
b: float = 0.75
|
|
90
|
+
recover_capped_window: bool = True
|
|
91
|
+
|
|
92
|
+
def __post_init__(self) -> None:
|
|
93
|
+
if type(self.recover_capped_window) is not bool:
|
|
94
|
+
raise ConfigurationError("WINDOW recovery must be an explicit boolean")
|
|
95
|
+
nonnegative_integer("chunk_characters", self.chunk_characters, minimum=1)
|
|
96
|
+
nonnegative_integer("overlap_characters", self.overlap_characters)
|
|
97
|
+
nonnegative_integer("top_k", self.top_k, minimum=1)
|
|
98
|
+
if self.overlap_characters >= self.chunk_characters:
|
|
99
|
+
raise ConfigurationError("Chunk overlap must be smaller than chunk size")
|
|
100
|
+
for name in ("minimum_score", "k1", "b"):
|
|
101
|
+
value = getattr(self, name)
|
|
102
|
+
try:
|
|
103
|
+
valid = type(value) in (int, float) and math.isfinite(value) and value >= 0
|
|
104
|
+
except OverflowError:
|
|
105
|
+
valid = False
|
|
106
|
+
if not valid:
|
|
107
|
+
raise ConfigurationError("Invalid retrieval parameter", field=name)
|
|
108
|
+
if self.k1 <= 0 or self.b > 1:
|
|
109
|
+
raise ConfigurationError("BM25 requires positive k1 and b between zero and one")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@dataclass(frozen=True, slots=True)
|
|
113
|
+
class Settings:
|
|
114
|
+
budget: BudgetConfig = field(default_factory=BudgetConfig)
|
|
115
|
+
tokenizer: TokenizerConfig = field(default_factory=TokenizerConfig)
|
|
116
|
+
output_dir: Path = CONFIG_DIRECTORY / "output"
|
|
117
|
+
cap: CapConfig = field(default_factory=CapConfig)
|
|
118
|
+
retrieval: RetrievalConfig = field(default_factory=RetrievalConfig)
|
|
119
|
+
|
|
120
|
+
def __post_init__(self) -> None:
|
|
121
|
+
if not isinstance(self.budget, BudgetConfig) or not isinstance(
|
|
122
|
+
self.tokenizer, TokenizerConfig
|
|
123
|
+
):
|
|
124
|
+
raise ConfigurationError(
|
|
125
|
+
"Settings require validated budget and tokenizer configuration"
|
|
126
|
+
)
|
|
127
|
+
if not isinstance(self.cap, CapConfig) or not isinstance(self.retrieval, RetrievalConfig):
|
|
128
|
+
raise ConfigurationError("Settings require validated layer configuration")
|
|
129
|
+
try:
|
|
130
|
+
path = Path(self.output_dir).expanduser()
|
|
131
|
+
if not path.is_absolute():
|
|
132
|
+
path = CONFIG_DIRECTORY / path
|
|
133
|
+
object.__setattr__(self, "output_dir", path.resolve())
|
|
134
|
+
except (TypeError, ValueError, OSError, RuntimeError):
|
|
135
|
+
raise ConfigurationError("Invalid output directory", field="output_dir") from None
|
|
136
|
+
|
|
137
|
+
@classmethod
|
|
138
|
+
def from_env(cls, environ: Mapping[str, str] | None = None) -> "Settings":
|
|
139
|
+
env = os.environ if environ is None else environ
|
|
140
|
+
|
|
141
|
+
def integer(key: str, default: int) -> int:
|
|
142
|
+
try:
|
|
143
|
+
return int(env[key]) if key in env else default
|
|
144
|
+
except (ValueError, TypeError):
|
|
145
|
+
raise ConfigurationError(
|
|
146
|
+
"Environment setting must be an integer", field=key
|
|
147
|
+
) from None
|
|
148
|
+
|
|
149
|
+
def number(key: str, default: float) -> float:
|
|
150
|
+
try:
|
|
151
|
+
return float(env[key]) if key in env else default
|
|
152
|
+
except (TypeError, ValueError, OverflowError):
|
|
153
|
+
raise ConfigurationError("Environment setting must be numeric", field=key) from None
|
|
154
|
+
|
|
155
|
+
try:
|
|
156
|
+
factor = float(env.get("TOKENIZER_FUDGE", "1.07"))
|
|
157
|
+
except (ValueError, TypeError):
|
|
158
|
+
raise ConfigurationError(
|
|
159
|
+
"Environment setting must be numeric", field="TOKENIZER_FUDGE"
|
|
160
|
+
) from None
|
|
161
|
+
return cls(
|
|
162
|
+
budget=BudgetConfig(
|
|
163
|
+
input_cap=integer("CONTEXT_BUDGET_TOKENS", 3000),
|
|
164
|
+
model_capacity=integer("MODEL_CONTEXT_TOKENS", 131072),
|
|
165
|
+
completion_reservation=integer("MAX_COMPLETION_TOKENS", 256),
|
|
166
|
+
retrieval_reserve=integer("RETRIEVAL_RESERVE_TOKENS", 280),
|
|
167
|
+
summary_reserve=integer("SUMMARY_RESERVE_TOKENS", 80),
|
|
168
|
+
provider_input_cap=(
|
|
169
|
+
integer("PROVIDER_INPUT_CAP_TOKENS", 0)
|
|
170
|
+
if "PROVIDER_INPUT_CAP_TOKENS" in env
|
|
171
|
+
else None
|
|
172
|
+
),
|
|
173
|
+
),
|
|
174
|
+
tokenizer=TokenizerConfig(
|
|
175
|
+
encoding_name=env.get("TOKENIZER_NAME", "o200k_harmony"),
|
|
176
|
+
calibration_factor=factor,
|
|
177
|
+
adapter_overhead_tokens=integer("ADAPTER_OVERHEAD_TOKENS", 0),
|
|
178
|
+
),
|
|
179
|
+
output_dir=Path(env.get("CONTEXT_OUTPUT_DIR", str(CONFIG_DIRECTORY / "output"))),
|
|
180
|
+
cap=CapConfig(
|
|
181
|
+
max_tokens=integer("CAP_MAX_TOKENS", 35),
|
|
182
|
+
head_tokens=integer("CAP_HEAD_TOKENS", 22),
|
|
183
|
+
tail_tokens=integer("CAP_TAIL_TOKENS", 8),
|
|
184
|
+
),
|
|
185
|
+
retrieval=RetrievalConfig(
|
|
186
|
+
chunk_characters=integer("RETRIEVAL_CHUNK_CHARACTERS", 360),
|
|
187
|
+
overlap_characters=integer("RETRIEVAL_OVERLAP_CHARACTERS", 80),
|
|
188
|
+
top_k=integer("RETRIEVAL_TOP_K", 4),
|
|
189
|
+
minimum_score=number("RETRIEVAL_MINIMUM_SCORE", 0.0),
|
|
190
|
+
k1=number("BM25_K1", 1.5),
|
|
191
|
+
b=number("BM25_B", 0.75),
|
|
192
|
+
),
|
|
193
|
+
)
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Typed, immutable carrier snapshots and composable layer contract."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Protocol
|
|
5
|
+
|
|
6
|
+
from .budget import BudgetPlan
|
|
7
|
+
from .errors import ContractError
|
|
8
|
+
from .models import (
|
|
9
|
+
ChatRequest,
|
|
10
|
+
Chunk,
|
|
11
|
+
ContextBlock,
|
|
12
|
+
KeyedPins,
|
|
13
|
+
Message,
|
|
14
|
+
Role,
|
|
15
|
+
Scope,
|
|
16
|
+
Turn,
|
|
17
|
+
freeze_sequence,
|
|
18
|
+
integer_value,
|
|
19
|
+
text_value,
|
|
20
|
+
)
|
|
21
|
+
from .work import Cancellation, validate_cancellation
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True, slots=True)
|
|
25
|
+
class LayerDiagnostic:
|
|
26
|
+
layer: str
|
|
27
|
+
reason_code: str
|
|
28
|
+
changed_turn_ids: tuple[str, ...] = ()
|
|
29
|
+
input_text_tokens: int = 0
|
|
30
|
+
output_text_tokens: int = 0
|
|
31
|
+
|
|
32
|
+
def __post_init__(self) -> None:
|
|
33
|
+
text_value("layer", self.layer)
|
|
34
|
+
text_value("reason_code", self.reason_code)
|
|
35
|
+
ids = freeze_sequence(self, "changed_turn_ids", str)
|
|
36
|
+
if len(set(ids)) != len(ids) or any(not value.strip() for value in ids):
|
|
37
|
+
raise ContractError("Diagnostic turn IDs must be unique and nonempty")
|
|
38
|
+
integer_value("input_text_tokens", self.input_text_tokens)
|
|
39
|
+
integer_value("output_text_tokens", self.output_text_tokens)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True, slots=True)
|
|
43
|
+
class ContextCarrier:
|
|
44
|
+
scope: Scope
|
|
45
|
+
original_turns: tuple[Turn, ...]
|
|
46
|
+
working_turns: tuple[Turn, ...]
|
|
47
|
+
pins: KeyedPins
|
|
48
|
+
question: Message
|
|
49
|
+
plan: BudgetPlan | None = None
|
|
50
|
+
blocks: tuple[ContextBlock, ...] = ()
|
|
51
|
+
diagnostics: tuple[LayerDiagnostic, ...] = ()
|
|
52
|
+
required_request: ChatRequest | None = None
|
|
53
|
+
prepared_fingerprint: str | None = None
|
|
54
|
+
window_planned: bool = False
|
|
55
|
+
retrieved_chunks: tuple[Chunk, ...] = ()
|
|
56
|
+
cancellation: Cancellation | None = None
|
|
57
|
+
|
|
58
|
+
def __post_init__(self) -> None:
|
|
59
|
+
if not isinstance(self.scope, Scope):
|
|
60
|
+
raise ContractError("Carrier requires a scope")
|
|
61
|
+
originals = freeze_sequence(self, "original_turns", Turn)
|
|
62
|
+
working = freeze_sequence(self, "working_turns", Turn)
|
|
63
|
+
blocks = freeze_sequence(self, "blocks", ContextBlock)
|
|
64
|
+
diagnostics = freeze_sequence(self, "diagnostics", LayerDiagnostic)
|
|
65
|
+
chunks = freeze_sequence(self, "retrieved_chunks", Chunk)
|
|
66
|
+
if any(chunk.source.scope != self.scope for chunk in chunks):
|
|
67
|
+
raise ContractError("Retrieved chunk scope mismatch")
|
|
68
|
+
if self.required_request is not None and not isinstance(self.required_request, ChatRequest):
|
|
69
|
+
raise ContractError("Carrier requires a validated mandatory request")
|
|
70
|
+
if self.prepared_fingerprint is not None:
|
|
71
|
+
text_value("prepared_fingerprint", self.prepared_fingerprint)
|
|
72
|
+
if type(self.window_planned) is not bool:
|
|
73
|
+
raise ContractError("window_planned must be boolean")
|
|
74
|
+
if any(turn.scope != self.scope for turn in (*originals, *working)):
|
|
75
|
+
raise ContractError("Carrier history scope mismatch")
|
|
76
|
+
if not isinstance(self.pins, KeyedPins) or self.pins.scope != self.scope:
|
|
77
|
+
raise ContractError("Carrier pin scope mismatch")
|
|
78
|
+
if not isinstance(self.question, Message) or self.question.role != Role.USER:
|
|
79
|
+
raise ContractError("Current question must be a user message")
|
|
80
|
+
if self.plan is not None and not isinstance(self.plan, BudgetPlan):
|
|
81
|
+
raise ContractError("Carrier requires a validated budget plan")
|
|
82
|
+
if len({block.kind for block in blocks}) != len(blocks):
|
|
83
|
+
raise ContractError("Carrier block kinds must be unique")
|
|
84
|
+
if any(source.scope != self.scope for block in blocks for source in block.sources):
|
|
85
|
+
raise ContractError("Carrier block source scope mismatch")
|
|
86
|
+
original_ids = [turn.turn_id for turn in originals]
|
|
87
|
+
working_ids = [turn.turn_id for turn in working]
|
|
88
|
+
if len(set(original_ids)) != len(original_ids):
|
|
89
|
+
raise ContractError("Original turn IDs must be unique")
|
|
90
|
+
if working_ids != original_ids:
|
|
91
|
+
raise ContractError("Working copies must preserve original turn order and IDs")
|
|
92
|
+
original_messages = [m.message_id for turn in originals for m in turn.messages]
|
|
93
|
+
if len(set(original_messages)) != len(original_messages):
|
|
94
|
+
raise ContractError("History message IDs must be unique")
|
|
95
|
+
if self.question.message_id in original_messages:
|
|
96
|
+
raise ContractError("Current question must not already occur in history")
|
|
97
|
+
for original, copy in zip(originals, working, strict=True):
|
|
98
|
+
if original.revision != copy.revision or original.timestamp != copy.timestamp:
|
|
99
|
+
raise ContractError("Working copies must preserve source revision and timestamp")
|
|
100
|
+
if [
|
|
101
|
+
(m.message_id, m.role, m.tool_call_id, m.tool_calls) for m in original.messages
|
|
102
|
+
] != [(m.message_id, m.role, m.tool_call_id, m.tool_calls) for m in copy.messages]:
|
|
103
|
+
raise ContractError(
|
|
104
|
+
"Working copies must preserve message identity and tool metadata"
|
|
105
|
+
)
|
|
106
|
+
if self.plan is not None and not set(self.plan.window_turn_ids).issubset(original_ids):
|
|
107
|
+
raise ContractError("Planned window contains unknown turns")
|
|
108
|
+
if any(not set(item.changed_turn_ids).issubset(original_ids) for item in diagnostics):
|
|
109
|
+
raise ContractError("Layer diagnostic references unknown turns")
|
|
110
|
+
validate_cancellation(self.cancellation)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class Layer(Protocol):
|
|
114
|
+
"""Implementations return a new snapshot and never mutate source history."""
|
|
115
|
+
|
|
116
|
+
def apply(self, context: ContextCarrier) -> ContextCarrier: ...
|
context_engine/demo.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Synthetic C02 layer walkthrough. No provider or answer generation."""
|
|
2
|
+
|
|
3
|
+
from datetime import UTC, datetime
|
|
4
|
+
|
|
5
|
+
from .budget import validate_request
|
|
6
|
+
from .config import BudgetConfig, CapConfig, RetrievalConfig
|
|
7
|
+
from .context import ContextCarrier
|
|
8
|
+
from .layers import CapLayer, PinLayer, RetrieveLayer, SummaryLayer, SummarySnapshot, WindowLayer
|
|
9
|
+
from .layers.common import render_request
|
|
10
|
+
from .layers.summarize import FrozenSummaryPolicy
|
|
11
|
+
from .models import BlockKind, KeyedPins, Message, Pin, Role, Scope, Turn
|
|
12
|
+
from .tokens import TokenCounter
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def demo_context():
|
|
16
|
+
at = datetime(2026, 9, 7, tzinfo=UTC)
|
|
17
|
+
scope = Scope("demo", "incident")
|
|
18
|
+
old = Turn(
|
|
19
|
+
"t1",
|
|
20
|
+
scope,
|
|
21
|
+
(
|
|
22
|
+
Message(
|
|
23
|
+
"m1",
|
|
24
|
+
Role.TOOL,
|
|
25
|
+
"BEGIN "
|
|
26
|
+
+ "routine telemetry. " * 60
|
|
27
|
+
+ "The partition carrying the blame was shard-19. "
|
|
28
|
+
+ "routine telemetry. " * 60
|
|
29
|
+
+ " END",
|
|
30
|
+
tool_call_id="historical-call",
|
|
31
|
+
),
|
|
32
|
+
),
|
|
33
|
+
timestamp=at,
|
|
34
|
+
)
|
|
35
|
+
recent = tuple(
|
|
36
|
+
Turn(
|
|
37
|
+
f"t{i}",
|
|
38
|
+
scope,
|
|
39
|
+
(
|
|
40
|
+
Message(
|
|
41
|
+
f"m{i}", Role.USER, f"Recent update {i}: " + "routine checks continue. " * 40
|
|
42
|
+
),
|
|
43
|
+
),
|
|
44
|
+
timestamp=at,
|
|
45
|
+
)
|
|
46
|
+
for i in range(2, 13)
|
|
47
|
+
)
|
|
48
|
+
turns = (old,) + recent
|
|
49
|
+
pins = KeyedPins(
|
|
50
|
+
scope, (Pin(scope, "environment", "production", "application", effective_at=at),)
|
|
51
|
+
)
|
|
52
|
+
context = ContextCarrier(
|
|
53
|
+
scope,
|
|
54
|
+
turns,
|
|
55
|
+
turns,
|
|
56
|
+
pins,
|
|
57
|
+
Message("question", Role.USER, "Which partition carried the blame?"),
|
|
58
|
+
)
|
|
59
|
+
budget = BudgetConfig(input_cap=900, retrieval_reserve=280, summary_reserve=80)
|
|
60
|
+
return context, budget, at
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def layer_demo(counter: TokenCounter) -> dict:
|
|
64
|
+
context, budget, at = demo_context()
|
|
65
|
+
turns = context.original_turns
|
|
66
|
+
old = turns[0]
|
|
67
|
+
scope = context.scope
|
|
68
|
+
capped = CapLayer(counter, CapConfig()).apply(context)
|
|
69
|
+
prepared = PinLayer(counter, budget, "Use the supplied evidence to answer.", at).apply(capped)
|
|
70
|
+
retrieved = RetrieveLayer(counter, RetrievalConfig()).apply(prepared)
|
|
71
|
+
windowed = WindowLayer(counter).apply(retrieved)
|
|
72
|
+
omitted = tuple(t for t in turns if t.turn_id not in windowed.plan.window_turn_ids)
|
|
73
|
+
snapshot = SummarySnapshot.from_history(
|
|
74
|
+
scope=scope,
|
|
75
|
+
content="Earlier discussion concerned an incident and routine telemetry.",
|
|
76
|
+
history=omitted,
|
|
77
|
+
policy_version="demo-v1",
|
|
78
|
+
created_at=at,
|
|
79
|
+
)
|
|
80
|
+
completed = SummaryLayer(counter, FrozenSummaryPolicy(snapshot), at).apply(windowed)
|
|
81
|
+
request = render_request(completed.blocks, completed.question, completed.required_request.tools)
|
|
82
|
+
estimate = validate_request(request=request, config=budget, counter=counter)
|
|
83
|
+
evidence = next(b for b in completed.blocks if b.kind == BlockKind.RETRIEVED)
|
|
84
|
+
return {
|
|
85
|
+
"inference_calls": 0,
|
|
86
|
+
"input_allowance": budget.input_allowance,
|
|
87
|
+
"original_message_tokens": counter.count_text(old.messages[0].content),
|
|
88
|
+
"capped_message_tokens": counter.count_text(capped.working_turns[0].messages[0].content),
|
|
89
|
+
"original_preserved": completed.original_turns == context.original_turns,
|
|
90
|
+
"capped_middle_fact_present": "shard-19" in capped.working_turns[0].messages[0].content,
|
|
91
|
+
"retrieved_middle_fact_present": "shard-19" in evidence.content,
|
|
92
|
+
"retrieved_turn_ids": sorted({c.source.turn_id for c in completed.retrieved_chunks}),
|
|
93
|
+
"window_turn_ids": list(completed.plan.window_turn_ids),
|
|
94
|
+
"summary_used": any(b.kind == BlockKind.SUMMARY and b.content for b in completed.blocks),
|
|
95
|
+
"estimated_request_tokens": estimate.estimated_tokens,
|
|
96
|
+
"layer_order": [d.layer for d in completed.diagnostics],
|
|
97
|
+
"retrieved_evidence": evidence.content,
|
|
98
|
+
}
|