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
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Explicit live probe and an entirely synthetic accounting demonstration."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import os
|
|
5
|
+
from dataclasses import replace
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from tempfile import TemporaryDirectory
|
|
8
|
+
|
|
9
|
+
from ..config import BudgetConfig, Settings
|
|
10
|
+
from ..errors import ContractError
|
|
11
|
+
from ..inspection import demo_job, load_job
|
|
12
|
+
from ..models import ChatRequest, Message, Role, Scope
|
|
13
|
+
from ..tokens import TiktokenCounter
|
|
14
|
+
from .client import ProviderClient
|
|
15
|
+
from .contracts import Completion, GenerationConfig, PriceCard, QuotaPolicy, ReplayPolicy, Usage
|
|
16
|
+
from .store import RuntimeStore
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SyntheticTransport:
|
|
20
|
+
namespace = "c05-offline-demo-v1"
|
|
21
|
+
|
|
22
|
+
def __init__(self):
|
|
23
|
+
self.calls = 0
|
|
24
|
+
|
|
25
|
+
async def send(self, payload, api_key, timeout):
|
|
26
|
+
self.calls += 1
|
|
27
|
+
return Completion(
|
|
28
|
+
payload["model"],
|
|
29
|
+
f"synthetic-{self.calls}",
|
|
30
|
+
"synthetic-answer",
|
|
31
|
+
"stop",
|
|
32
|
+
Usage(40, 5, cached_input_tokens=4, reasoning_tokens=2),
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
async def _demo(directory: Path) -> dict:
|
|
37
|
+
store = RuntimeStore(directory / "runtime.sqlite")
|
|
38
|
+
quota = QuotaPolicy("synthetic-demo", 10000, rpd=2)
|
|
39
|
+
transport = SyntheticTransport()
|
|
40
|
+
client = ProviderClient(
|
|
41
|
+
store=store,
|
|
42
|
+
quota=quota,
|
|
43
|
+
prices=PriceCard("synthetic-v1", 1000000, 500000, 2000000),
|
|
44
|
+
transport=transport,
|
|
45
|
+
api_key="synthetic-not-a-credential",
|
|
46
|
+
replay=ReplayPolicy(enabled=True),
|
|
47
|
+
)
|
|
48
|
+
request = ChatRequest((Message("q", Role.USER, "Synthetic question one?"),))
|
|
49
|
+
options = dict(
|
|
50
|
+
scope=Scope("demo", "demo"), security_scope="synthetic-v1", snapshot_revision="synthetic-v1"
|
|
51
|
+
)
|
|
52
|
+
budget = BudgetConfig(input_cap=900)
|
|
53
|
+
first = await client.complete(request, budget, **options)
|
|
54
|
+
replay = await client.complete(request, budget, **options)
|
|
55
|
+
changed = ChatRequest((Message("q", Role.USER, "Synthetic question two?"),))
|
|
56
|
+
miss = await client.complete(changed, budget, **options)
|
|
57
|
+
client.generation = replace(client.generation, temperature=0.5)
|
|
58
|
+
blocked = await client.complete(changed, budget, **options)
|
|
59
|
+
results = dict(
|
|
60
|
+
first=first.to_dict(),
|
|
61
|
+
identical_replay=replay.to_dict(),
|
|
62
|
+
changed_question=miss.to_dict(),
|
|
63
|
+
changed_generation=blocked.to_dict(),
|
|
64
|
+
)
|
|
65
|
+
valid = (
|
|
66
|
+
first.status == miss.status == "success"
|
|
67
|
+
and replay.status == "replay"
|
|
68
|
+
and replay.new_cost_microusd == 0
|
|
69
|
+
and blocked.error_code == "quota_exhausted"
|
|
70
|
+
and blocked.request_key != miss.request_key
|
|
71
|
+
and transport.calls == 2
|
|
72
|
+
)
|
|
73
|
+
return dict(
|
|
74
|
+
mode="offline_provider_demo",
|
|
75
|
+
status="PASS" if valid else "FAIL",
|
|
76
|
+
real_inference_calls=0,
|
|
77
|
+
simulated_transport_calls=transport.calls,
|
|
78
|
+
pricing="synthetic; not provider pricing or billing evidence",
|
|
79
|
+
results=results,
|
|
80
|
+
ledger=store.snapshot(quota),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def provider_demo() -> dict:
|
|
85
|
+
with TemporaryDirectory(prefix="context-provider-demo-") as directory:
|
|
86
|
+
# Let the store create its private directory, not tempfile's Windows ACL.
|
|
87
|
+
return asyncio.run(_demo(Path(directory) / "storage"))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def provider_probe(args) -> dict:
|
|
91
|
+
# All authorization/config checks precede the first file mutation or network operation.
|
|
92
|
+
if not args.allow_live or args.daily_budget_microusd <= 0:
|
|
93
|
+
raise ContractError("Live probe requires --allow-live and a positive explicit spending cap")
|
|
94
|
+
api_key = os.environ.get("GROQ_API_KEY")
|
|
95
|
+
if not api_key or any(ord(c) < 33 or ord(c) > 126 for c in api_key):
|
|
96
|
+
raise ContractError("Live probe requires a valid GROQ_API_KEY environment variable")
|
|
97
|
+
quota = QuotaPolicy(
|
|
98
|
+
args.account_id,
|
|
99
|
+
args.daily_budget_microusd,
|
|
100
|
+
rpm=args.rpm,
|
|
101
|
+
tpm=args.tpm,
|
|
102
|
+
rpd=args.rpd,
|
|
103
|
+
tpd=args.tpd,
|
|
104
|
+
)
|
|
105
|
+
prices = PriceCard(
|
|
106
|
+
args.price_version,
|
|
107
|
+
args.input_rate_microusd,
|
|
108
|
+
args.cached_input_rate_microusd,
|
|
109
|
+
args.output_rate_microusd,
|
|
110
|
+
)
|
|
111
|
+
settings = Settings.from_env()
|
|
112
|
+
job = load_job(args.input, settings) if args.input is not None else demo_job(settings)
|
|
113
|
+
generation = GenerationConfig(
|
|
114
|
+
model=args.model, max_completion_tokens=job.arguments["budget"].completion_reservation
|
|
115
|
+
)
|
|
116
|
+
assembled = job.run()
|
|
117
|
+
client = ProviderClient(
|
|
118
|
+
store=RuntimeStore(args.ledger),
|
|
119
|
+
quota=quota,
|
|
120
|
+
prices=prices,
|
|
121
|
+
api_key=api_key,
|
|
122
|
+
generation=generation,
|
|
123
|
+
counter=TiktokenCounter(job.tokenizer),
|
|
124
|
+
)
|
|
125
|
+
result = asyncio.run(
|
|
126
|
+
client.complete(
|
|
127
|
+
assembled.request,
|
|
128
|
+
job.arguments["budget"],
|
|
129
|
+
scope=job.arguments["pinned_facts"].scope,
|
|
130
|
+
security_scope=args.security_scope,
|
|
131
|
+
snapshot_revision=args.snapshot_revision,
|
|
132
|
+
)
|
|
133
|
+
)
|
|
134
|
+
return dict(
|
|
135
|
+
mode="live_provider_probe",
|
|
136
|
+
synthetic_input=job.synthetic,
|
|
137
|
+
**result.to_dict(include_content=args.show_content),
|
|
138
|
+
)
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
"""Provider/accounting contracts; monetary amounts are integer micro-US dollars."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import math
|
|
5
|
+
import re
|
|
6
|
+
from dataclasses import asdict, dataclass
|
|
7
|
+
|
|
8
|
+
from ..errors import ContractError
|
|
9
|
+
from ..models import ToolCall, canonical_json, freeze_sequence, integer_value, text_value
|
|
10
|
+
|
|
11
|
+
ENDPOINT = "https://api.groq.com/openai/v1/chat/completions"
|
|
12
|
+
ADAPTER_VERSION = "groq-chat-v1"
|
|
13
|
+
MODELS = ("openai/gpt-oss-120b", "openai/gpt-oss-20b")
|
|
14
|
+
# Inception Mercury (OpenAI-compatible); paid per token, no documented free tier.
|
|
15
|
+
INCEPTION_ENDPOINT = "https://api.inceptionlabs.ai/v1/chat/completions"
|
|
16
|
+
INCEPTION_ADAPTER_VERSION = "inception-chat-v1"
|
|
17
|
+
INCEPTION_MODELS = ("mercury-3", "mercury-2.5", "mercury-2")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def fingerprint(value) -> str:
|
|
21
|
+
return hashlib.sha256(canonical_json(value).encode()).hexdigest()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def positive_number(name, value, maximum=300):
|
|
25
|
+
if type(value) not in (int, float) or not math.isfinite(value) or not 0 < value <= maximum:
|
|
26
|
+
raise ContractError("Expected a positive finite bounded duration", field=name)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def identifier(value: str) -> str:
|
|
30
|
+
if not isinstance(value, str) or not re.fullmatch(r"[A-Za-z0-9._:/-]{1,200}", value):
|
|
31
|
+
raise ContractError("Invalid opaque provider identifier")
|
|
32
|
+
return value
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True)
|
|
36
|
+
class GenerationConfig:
|
|
37
|
+
model: str = MODELS[0]
|
|
38
|
+
max_completion_tokens: int = 256
|
|
39
|
+
temperature: float = 0.0
|
|
40
|
+
reasoning_effort: str = "low"
|
|
41
|
+
tool_choice: str = "none"
|
|
42
|
+
|
|
43
|
+
def __post_init__(self):
|
|
44
|
+
if self.model not in MODELS or self.reasoning_effort not in ("low", "medium", "high"):
|
|
45
|
+
raise ContractError("Unsupported C05 model or reasoning setting")
|
|
46
|
+
integer_value("max_completion_tokens", self.max_completion_tokens, minimum=1)
|
|
47
|
+
if self.max_completion_tokens > 65536:
|
|
48
|
+
raise ContractError("Completion request exceeds model profile")
|
|
49
|
+
if (
|
|
50
|
+
type(self.temperature) not in (int, float)
|
|
51
|
+
or not math.isfinite(self.temperature)
|
|
52
|
+
or not 0 <= self.temperature <= 2
|
|
53
|
+
):
|
|
54
|
+
raise ContractError("Invalid generation temperature")
|
|
55
|
+
if self.tool_choice not in ("none", "auto"):
|
|
56
|
+
raise ContractError("Unsupported tool choice")
|
|
57
|
+
|
|
58
|
+
def to_wire(self, request):
|
|
59
|
+
payload = {
|
|
60
|
+
**request.to_wire(),
|
|
61
|
+
**asdict(self),
|
|
62
|
+
"stream": False,
|
|
63
|
+
"n": 1,
|
|
64
|
+
"include_reasoning": False,
|
|
65
|
+
"service_tier": "on_demand",
|
|
66
|
+
}
|
|
67
|
+
if not request.tools:
|
|
68
|
+
payload.pop("tool_choice")
|
|
69
|
+
return payload
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass(frozen=True)
|
|
73
|
+
class InceptionGenerationConfig:
|
|
74
|
+
"""Mercury request settings; the API accepts temperature 0.5-1 only."""
|
|
75
|
+
|
|
76
|
+
model: str = "mercury-3"
|
|
77
|
+
max_completion_tokens: int = 256
|
|
78
|
+
temperature: float = 0.5
|
|
79
|
+
reasoning_effort: str = "low"
|
|
80
|
+
tool_choice: str = "none"
|
|
81
|
+
|
|
82
|
+
def __post_init__(self):
|
|
83
|
+
if self.model not in INCEPTION_MODELS or self.reasoning_effort not in (
|
|
84
|
+
"instant",
|
|
85
|
+
"low",
|
|
86
|
+
"medium",
|
|
87
|
+
"high",
|
|
88
|
+
):
|
|
89
|
+
raise ContractError("Unsupported Inception model or reasoning setting")
|
|
90
|
+
integer_value("max_completion_tokens", self.max_completion_tokens, minimum=1)
|
|
91
|
+
if self.max_completion_tokens > 50000:
|
|
92
|
+
raise ContractError("Completion request exceeds the smallest Mercury limit")
|
|
93
|
+
if (
|
|
94
|
+
type(self.temperature) not in (int, float)
|
|
95
|
+
or not math.isfinite(self.temperature)
|
|
96
|
+
or not 0.5 <= self.temperature <= 1
|
|
97
|
+
):
|
|
98
|
+
raise ContractError("Inception temperature must be between 0.5 and 1")
|
|
99
|
+
if self.tool_choice not in ("none", "auto"):
|
|
100
|
+
raise ContractError("Unsupported tool choice")
|
|
101
|
+
|
|
102
|
+
def to_wire(self, request):
|
|
103
|
+
payload = {**request.to_wire(), **asdict(self), "stream": False}
|
|
104
|
+
if not request.tools:
|
|
105
|
+
payload.pop("tool_choice")
|
|
106
|
+
return payload
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@dataclass(frozen=True)
|
|
110
|
+
class RetryConfig:
|
|
111
|
+
max_attempts: int = 3
|
|
112
|
+
attempt_timeout: float = 20.0
|
|
113
|
+
total_timeout: float = 45.0
|
|
114
|
+
max_backoff: float = 10.0
|
|
115
|
+
|
|
116
|
+
def __post_init__(self):
|
|
117
|
+
integer_value("max_attempts", self.max_attempts, minimum=1)
|
|
118
|
+
if self.max_attempts > 5:
|
|
119
|
+
raise ContractError("Retry attempt limit is five")
|
|
120
|
+
for name in ("attempt_timeout", "total_timeout", "max_backoff"):
|
|
121
|
+
positive_number(name, getattr(self, name))
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@dataclass(frozen=True)
|
|
125
|
+
class QuotaPolicy:
|
|
126
|
+
account_id: str
|
|
127
|
+
daily_budget_microusd: int = 0
|
|
128
|
+
rpm: int = 30
|
|
129
|
+
tpm: int = 8000
|
|
130
|
+
rpd: int = 1000
|
|
131
|
+
tpd: int = 200000
|
|
132
|
+
billing_mode: str = "metered"
|
|
133
|
+
|
|
134
|
+
def __post_init__(self):
|
|
135
|
+
identifier(self.account_id)
|
|
136
|
+
if self.billing_mode not in ("metered", "free_tier"):
|
|
137
|
+
raise ContractError("Billing mode must be metered or explicitly confirmed free_tier")
|
|
138
|
+
if self.billing_mode == "free_tier" and self.daily_budget_microusd != 0:
|
|
139
|
+
raise ContractError("Free-tier mode requires a zero daily paid-spending cap")
|
|
140
|
+
for name in ("daily_budget_microusd", "rpm", "tpm", "rpd", "tpd"):
|
|
141
|
+
integer_value(name, getattr(self, name))
|
|
142
|
+
if getattr(self, name) > 10**12:
|
|
143
|
+
raise ContractError("Quota exceeds local integer accounting range")
|
|
144
|
+
|
|
145
|
+
def validate_prices(self, prices):
|
|
146
|
+
if not isinstance(prices, PriceCard):
|
|
147
|
+
raise ContractError("A validated price card is required")
|
|
148
|
+
if self.billing_mode == "free_tier" and prices.reserve_cost(1_000_000, 1_000_000) != 0:
|
|
149
|
+
raise ContractError("Free-tier mode requires zero configured token prices")
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@dataclass(frozen=True)
|
|
153
|
+
class PriceCard:
|
|
154
|
+
version: str
|
|
155
|
+
input_per_million_microusd: int
|
|
156
|
+
cached_input_per_million_microusd: int
|
|
157
|
+
output_per_million_microusd: int
|
|
158
|
+
|
|
159
|
+
def __post_init__(self):
|
|
160
|
+
identifier(self.version)
|
|
161
|
+
for name in (
|
|
162
|
+
"input_per_million_microusd",
|
|
163
|
+
"cached_input_per_million_microusd",
|
|
164
|
+
"output_per_million_microusd",
|
|
165
|
+
):
|
|
166
|
+
integer_value(name, getattr(self, name))
|
|
167
|
+
if getattr(self, name) > 10**12:
|
|
168
|
+
raise ContractError("Price exceeds local integer accounting range")
|
|
169
|
+
|
|
170
|
+
def cost(self, input_tokens: int, output_tokens: int, cached_tokens: int = 0) -> int:
|
|
171
|
+
for value in (input_tokens, output_tokens, cached_tokens):
|
|
172
|
+
integer_value("tokens", value)
|
|
173
|
+
if cached_tokens > input_tokens:
|
|
174
|
+
raise ContractError("Cached input exceeds total input")
|
|
175
|
+
numerator = (
|
|
176
|
+
(input_tokens - cached_tokens) * self.input_per_million_microusd
|
|
177
|
+
+ cached_tokens * self.cached_input_per_million_microusd
|
|
178
|
+
+ output_tokens * self.output_per_million_microusd
|
|
179
|
+
)
|
|
180
|
+
return (numerator + 999999) // 1000000
|
|
181
|
+
|
|
182
|
+
def reserve_cost(self, input_tokens: int, output_tokens: int) -> int:
|
|
183
|
+
for value in (input_tokens, output_tokens):
|
|
184
|
+
integer_value("tokens", value)
|
|
185
|
+
# Never assume that caching is discounted in an arbitrary caller-supplied price card.
|
|
186
|
+
numerator = input_tokens * max(
|
|
187
|
+
self.input_per_million_microusd, self.cached_input_per_million_microusd
|
|
188
|
+
)
|
|
189
|
+
return (numerator + output_tokens * self.output_per_million_microusd + 999999) // 1000000
|
|
190
|
+
|
|
191
|
+
def observed_cost(self, usage) -> int:
|
|
192
|
+
if usage.cached_input_tokens is None:
|
|
193
|
+
return self.reserve_cost(usage.input_tokens, usage.output_tokens)
|
|
194
|
+
return self.cost(usage.input_tokens, usage.output_tokens, usage.cached_input_tokens)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@dataclass(frozen=True)
|
|
198
|
+
class ReplayPolicy:
|
|
199
|
+
enabled: bool = False
|
|
200
|
+
ttl_seconds: int = 3600
|
|
201
|
+
|
|
202
|
+
def __post_init__(self):
|
|
203
|
+
if type(self.enabled) is not bool:
|
|
204
|
+
raise ContractError("Replay must be explicitly enabled with a boolean")
|
|
205
|
+
integer_value("ttl_seconds", self.ttl_seconds, minimum=1)
|
|
206
|
+
if self.ttl_seconds > 86400:
|
|
207
|
+
raise ContractError("C05 replay TTL is limited to one day")
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@dataclass(frozen=True)
|
|
211
|
+
class Usage:
|
|
212
|
+
input_tokens: int
|
|
213
|
+
output_tokens: int
|
|
214
|
+
cached_input_tokens: int | None = None
|
|
215
|
+
reasoning_tokens: int | None = None
|
|
216
|
+
|
|
217
|
+
def __post_init__(self):
|
|
218
|
+
for name in ("input_tokens", "output_tokens"):
|
|
219
|
+
integer_value(name, getattr(self, name))
|
|
220
|
+
for name, maximum in (
|
|
221
|
+
("cached_input_tokens", self.input_tokens),
|
|
222
|
+
("reasoning_tokens", self.output_tokens),
|
|
223
|
+
):
|
|
224
|
+
value = getattr(self, name)
|
|
225
|
+
if value is not None:
|
|
226
|
+
integer_value(name, value)
|
|
227
|
+
if value > maximum:
|
|
228
|
+
raise ContractError("Usage detail exceeds its containing total")
|
|
229
|
+
if self.input_tokens + self.output_tokens > 10**9:
|
|
230
|
+
raise ContractError("Usage exceeds supported accounting range")
|
|
231
|
+
|
|
232
|
+
@property
|
|
233
|
+
def total_tokens(self):
|
|
234
|
+
return self.input_tokens + self.output_tokens
|
|
235
|
+
|
|
236
|
+
@property
|
|
237
|
+
def visible_output_tokens(self):
|
|
238
|
+
return None if self.reasoning_tokens is None else self.output_tokens - self.reasoning_tokens
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
@dataclass(frozen=True)
|
|
242
|
+
class Completion:
|
|
243
|
+
model: str
|
|
244
|
+
response_id: str
|
|
245
|
+
content: str
|
|
246
|
+
finish_reason: str
|
|
247
|
+
usage: Usage
|
|
248
|
+
tool_calls: tuple[ToolCall, ...] = ()
|
|
249
|
+
system_fingerprint: str | None = None
|
|
250
|
+
|
|
251
|
+
def __post_init__(self):
|
|
252
|
+
identifier(self.response_id)
|
|
253
|
+
if self.model not in MODELS + INCEPTION_MODELS or self.finish_reason not in (
|
|
254
|
+
"stop",
|
|
255
|
+
"length",
|
|
256
|
+
"tool_calls",
|
|
257
|
+
"content_filter",
|
|
258
|
+
):
|
|
259
|
+
raise ContractError("Unsupported provider completion")
|
|
260
|
+
text_value("content", self.content, nonempty=False)
|
|
261
|
+
if not isinstance(self.usage, Usage):
|
|
262
|
+
raise ContractError("Completion requires validated usage")
|
|
263
|
+
calls = freeze_sequence(self, "tool_calls", ToolCall)
|
|
264
|
+
if bool(calls) != (self.finish_reason == "tool_calls") or len(
|
|
265
|
+
{c.call_id for c in calls}
|
|
266
|
+
) != len(calls):
|
|
267
|
+
raise ContractError("Tool-call finish reason disagrees with response")
|
|
268
|
+
if self.system_fingerprint is not None:
|
|
269
|
+
identifier(self.system_fingerprint)
|
|
270
|
+
|
|
271
|
+
def to_dict(self):
|
|
272
|
+
return asdict(self)
|
|
273
|
+
|
|
274
|
+
@classmethod
|
|
275
|
+
def from_dict(cls, value):
|
|
276
|
+
data = dict(value)
|
|
277
|
+
data["usage"] = Usage(**data["usage"])
|
|
278
|
+
data["tool_calls"] = tuple(ToolCall(**c) for c in data["tool_calls"])
|
|
279
|
+
return cls(**data)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
class TransportFailure(Exception):
|
|
283
|
+
"""Only controlled metadata crosses the network/ledger boundary."""
|
|
284
|
+
|
|
285
|
+
def __init__(
|
|
286
|
+
self, code: str, *, uncertain: bool, retryable=False, retry_after=None, usage=None
|
|
287
|
+
):
|
|
288
|
+
identifier(code)
|
|
289
|
+
if type(uncertain) is not bool or type(retryable) is not bool:
|
|
290
|
+
raise ContractError("Failure flags must be booleans")
|
|
291
|
+
if retry_after is not None and (
|
|
292
|
+
type(retry_after) not in (int, float)
|
|
293
|
+
or not math.isfinite(retry_after)
|
|
294
|
+
or retry_after < 0
|
|
295
|
+
):
|
|
296
|
+
raise ContractError("Invalid retry delay")
|
|
297
|
+
if usage is not None and not isinstance(usage, Usage):
|
|
298
|
+
raise ContractError("Failure usage must be validated")
|
|
299
|
+
super().__init__(code)
|
|
300
|
+
self.code, self.uncertain, self.retryable = code, uncertain, retryable
|
|
301
|
+
self.retry_after, self.usage = retry_after, usage
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
@dataclass(frozen=True)
|
|
305
|
+
class ModelResult:
|
|
306
|
+
status: str
|
|
307
|
+
request_key: str
|
|
308
|
+
attempt_ids: tuple[str, ...] = ()
|
|
309
|
+
completion: Completion | None = None
|
|
310
|
+
error_code: str | None = None
|
|
311
|
+
replay_of: str | None = None
|
|
312
|
+
new_cost_microusd: int | None = 0
|
|
313
|
+
original_cost_microusd: int | None = None
|
|
314
|
+
warning: str | None = None
|
|
315
|
+
|
|
316
|
+
def to_dict(self, *, include_content=False):
|
|
317
|
+
if type(include_content) is not bool:
|
|
318
|
+
raise ContractError("Content export requires an explicit boolean")
|
|
319
|
+
result = asdict(self)
|
|
320
|
+
if self.completion is not None and not include_content:
|
|
321
|
+
result["completion"].pop("content")
|
|
322
|
+
result["completion"].pop("tool_calls")
|
|
323
|
+
result["content_included"] = include_content
|
|
324
|
+
return result
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Explicit, bounded credential loading; no shell evaluation or secret-bearing errors."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import stat
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from ..errors import ContractError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def private_bytes(path, maximum):
|
|
11
|
+
if os.name == "nt":
|
|
12
|
+
from .windows_credentials import private_bytes as windows_private_bytes
|
|
13
|
+
|
|
14
|
+
return windows_private_bytes(path, maximum)
|
|
15
|
+
descriptor = None
|
|
16
|
+
try:
|
|
17
|
+
descriptor = os.open(
|
|
18
|
+
Path(path), os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0) | getattr(os, "O_NONBLOCK", 0)
|
|
19
|
+
)
|
|
20
|
+
info = os.fstat(descriptor)
|
|
21
|
+
if not stat.S_ISREG(info.st_mode) or (os.name == "posix" and info.st_mode & 0o077):
|
|
22
|
+
raise ContractError("Configuration must be a private regular file")
|
|
23
|
+
with os.fdopen(descriptor, "rb") as handle:
|
|
24
|
+
descriptor = None
|
|
25
|
+
raw = handle.read(maximum + 1)
|
|
26
|
+
if len(raw) > maximum:
|
|
27
|
+
raise ContractError("Private configuration exceeds its size limit")
|
|
28
|
+
return raw
|
|
29
|
+
except (OSError, TypeError, ValueError):
|
|
30
|
+
raise ContractError("Cannot read private configuration") from None
|
|
31
|
+
finally:
|
|
32
|
+
if descriptor is not None:
|
|
33
|
+
os.close(descriptor)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def load_api_key(env_file=None):
|
|
37
|
+
key = os.environ.get("GROQ_API_KEY")
|
|
38
|
+
if env_file is not None:
|
|
39
|
+
try:
|
|
40
|
+
lines = private_bytes(env_file, 8192).decode("ascii").splitlines()
|
|
41
|
+
except UnicodeError:
|
|
42
|
+
raise ContractError("Credential file must contain a plain ASCII assignment") from None
|
|
43
|
+
values = []
|
|
44
|
+
for line in lines:
|
|
45
|
+
line = line.strip()
|
|
46
|
+
if not line or line.startswith("#"):
|
|
47
|
+
continue
|
|
48
|
+
name, sep, value = line.partition("=")
|
|
49
|
+
if sep != "=" or name.strip() != "GROQ_API_KEY":
|
|
50
|
+
raise ContractError("Credential file permits only GROQ_API_KEY and comments")
|
|
51
|
+
value = value.strip()
|
|
52
|
+
if len(value) >= 2 and value[0] in "\"'" and value[-1] == value[0]:
|
|
53
|
+
value = value[1:-1]
|
|
54
|
+
values.append(value)
|
|
55
|
+
if len(values) != 1:
|
|
56
|
+
raise ContractError("Credential file requires exactly one GROQ_API_KEY assignment")
|
|
57
|
+
if key and key != values[0]:
|
|
58
|
+
raise ContractError("Environment and explicit credential file disagree")
|
|
59
|
+
key = values[0]
|
|
60
|
+
if (
|
|
61
|
+
not key
|
|
62
|
+
or len(key) > 4096
|
|
63
|
+
or any(not (c.isascii() and (c.isalnum() or c in "_-")) for c in key)
|
|
64
|
+
):
|
|
65
|
+
raise ContractError("Missing or invalid credential; value is never displayed")
|
|
66
|
+
return key
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Optional memory-aware inference; memory owns replay lifecycle, not the provider cache."""
|
|
2
|
+
|
|
3
|
+
import uuid
|
|
4
|
+
from dataclasses import asdict
|
|
5
|
+
|
|
6
|
+
from ..errors import ContractError, MemoryConflict, MemoryIntegrityError
|
|
7
|
+
from ..memory import MemoryStore
|
|
8
|
+
from .client import ProviderClient
|
|
9
|
+
from .contracts import Completion, ModelResult, fingerprint
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MemoryProvider:
|
|
13
|
+
def __init__(self, memory: MemoryStore, client: ProviderClient, *, replay=False):
|
|
14
|
+
if not isinstance(memory, MemoryStore) or not isinstance(client, ProviderClient):
|
|
15
|
+
raise ContractError("Memory integration requires validated stores/client")
|
|
16
|
+
if type(replay) is not bool or client.replay.enabled:
|
|
17
|
+
raise ContractError(
|
|
18
|
+
"Use memory-managed replay only; provider body cache must be disabled"
|
|
19
|
+
)
|
|
20
|
+
self.memory, self.client, self.replay = memory, client, replay
|
|
21
|
+
|
|
22
|
+
async def complete(self, scope, question, budget, *, system, security_scope, **assembly):
|
|
23
|
+
if not isinstance(security_scope, str) or not security_scope.strip():
|
|
24
|
+
raise ContractError("An authorized security scope is required")
|
|
25
|
+
snapshot, context = self.memory.assemble(scope, question, budget, system=system, **assembly)
|
|
26
|
+
client = self.client
|
|
27
|
+
key = fingerprint(
|
|
28
|
+
{
|
|
29
|
+
"snapshot": snapshot.snapshot_id,
|
|
30
|
+
"request": context.request.to_wire(),
|
|
31
|
+
"security": security_scope,
|
|
32
|
+
"budget": asdict(budget),
|
|
33
|
+
"generation": asdict(client.generation),
|
|
34
|
+
"prices": asdict(client.prices),
|
|
35
|
+
"account": client.quota.account_id,
|
|
36
|
+
"counting": context.diagnostics.estimate.to_dict(),
|
|
37
|
+
"transport": getattr(
|
|
38
|
+
client.transport, "namespace", type(client.transport).__qualname__
|
|
39
|
+
),
|
|
40
|
+
"policy": "memory-provider-v1",
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
if self.replay and (cached := self.memory.cache_get(snapshot, key)) is not None:
|
|
44
|
+
try:
|
|
45
|
+
completion = Completion.from_dict(cached["completion"])
|
|
46
|
+
with client.store.transaction() as db:
|
|
47
|
+
source = db.execute(
|
|
48
|
+
"SELECT * FROM attempts WHERE id=?", (cached["attempt_id"],)
|
|
49
|
+
).fetchone()
|
|
50
|
+
if (
|
|
51
|
+
source is None
|
|
52
|
+
or source["account"] != fingerprint(client.quota.account_id)
|
|
53
|
+
or source["state"] != "completed"
|
|
54
|
+
or source["request_key"] != cached["request_key"]
|
|
55
|
+
or source["completion_hash"] != fingerprint(completion.to_dict())
|
|
56
|
+
or source["cost"] != cached["cost"]
|
|
57
|
+
):
|
|
58
|
+
raise MemoryIntegrityError("Memory replay ancestry is invalid")
|
|
59
|
+
db.execute(
|
|
60
|
+
"INSERT INTO events(id,account,request_key,created,kind,source_attempt,"
|
|
61
|
+
"new_cost) VALUES(?,?,?,?,?,?,0)",
|
|
62
|
+
(
|
|
63
|
+
uuid.uuid4().hex,
|
|
64
|
+
source["account"],
|
|
65
|
+
source["request_key"],
|
|
66
|
+
client.clock(),
|
|
67
|
+
"memory_replay",
|
|
68
|
+
source["id"],
|
|
69
|
+
),
|
|
70
|
+
)
|
|
71
|
+
self.memory.assert_current(snapshot)
|
|
72
|
+
return ModelResult(
|
|
73
|
+
"replay",
|
|
74
|
+
cached["request_key"],
|
|
75
|
+
completion=completion,
|
|
76
|
+
replay_of=cached["attempt_id"],
|
|
77
|
+
new_cost_microusd=0,
|
|
78
|
+
original_cost_microusd=cached["cost"],
|
|
79
|
+
)
|
|
80
|
+
except (KeyError, TypeError, ValueError):
|
|
81
|
+
raise MemoryIntegrityError("Malformed memory replay") from None
|
|
82
|
+
result = await client.complete(
|
|
83
|
+
context.request,
|
|
84
|
+
budget,
|
|
85
|
+
scope=scope,
|
|
86
|
+
security_scope=security_scope,
|
|
87
|
+
snapshot_revision=snapshot.snapshot_id,
|
|
88
|
+
policy_version="memory-provider-v1",
|
|
89
|
+
)
|
|
90
|
+
try:
|
|
91
|
+
self.memory.assert_current(snapshot)
|
|
92
|
+
if self.replay and result.status == "success":
|
|
93
|
+
self.memory.cache_put(
|
|
94
|
+
snapshot,
|
|
95
|
+
key,
|
|
96
|
+
{
|
|
97
|
+
"completion": result.completion.to_dict(),
|
|
98
|
+
"attempt_id": result.attempt_ids[-1],
|
|
99
|
+
"request_key": result.request_key,
|
|
100
|
+
"cost": result.original_cost_microusd,
|
|
101
|
+
},
|
|
102
|
+
)
|
|
103
|
+
except MemoryConflict:
|
|
104
|
+
return ModelResult(
|
|
105
|
+
"error",
|
|
106
|
+
result.request_key,
|
|
107
|
+
result.attempt_ids,
|
|
108
|
+
error_code="memory_revision_conflict",
|
|
109
|
+
new_cost_microusd=result.new_cost_microusd,
|
|
110
|
+
warning="response_discarded_memory_changed",
|
|
111
|
+
)
|
|
112
|
+
return result
|