byokit 0.0.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.
- byokit/__init__.py +1299 -0
- byokit/__main__.py +3 -0
- byokit/cli.py +113 -0
- byokit/custom_model.py +122 -0
- byokit/data/NOTICE +23 -0
- byokit/data/catalog.json +1 -0
- byokit/enums.py +289 -0
- byokit/inference.py +944 -0
- byokit/py.typed +0 -0
- byokit/sdk.py +553 -0
- byokit/types.py +238 -0
- byokit-0.0.1.dist-info/METADATA +31 -0
- byokit-0.0.1.dist-info/RECORD +17 -0
- byokit-0.0.1.dist-info/WHEEL +5 -0
- byokit-0.0.1.dist-info/entry_points.txt +2 -0
- byokit-0.0.1.dist-info/licenses/LICENSE +21 -0
- byokit-0.0.1.dist-info/top_level.txt +1 -0
byokit/__init__.py
ADDED
|
@@ -0,0 +1,1299 @@
|
|
|
1
|
+
"""Offline discovery and stateless, explicitly invoked inference."""
|
|
2
|
+
|
|
3
|
+
from .enums import *
|
|
4
|
+
import copy
|
|
5
|
+
import math
|
|
6
|
+
from datetime import date
|
|
7
|
+
|
|
8
|
+
FEATURE_STATES = {"supported", "unsupported", "conditional", "unknown"}
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def finite(n):
|
|
12
|
+
return (
|
|
13
|
+
isinstance(n, (int, float))
|
|
14
|
+
and not isinstance(n, bool)
|
|
15
|
+
and math.isfinite(n)
|
|
16
|
+
and n >= 0
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def positive(n):
|
|
21
|
+
return finite(n) and n > 0
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def diagnostic(code, message, severity="warning", offering_id=None):
|
|
25
|
+
return dict(
|
|
26
|
+
code=code,
|
|
27
|
+
message=message,
|
|
28
|
+
severity=severity,
|
|
29
|
+
**({"offeringId": offering_id} if offering_id else {}),
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def validate_catalog(data):
|
|
34
|
+
errors = []
|
|
35
|
+
if not isinstance(data, dict) or data.get("schemaVersion") != 1:
|
|
36
|
+
return ["Unsupported schemaVersion"]
|
|
37
|
+
for key in ["providers", "models", "families", "offerings", "presets"]:
|
|
38
|
+
if not isinstance(data.get(key), list):
|
|
39
|
+
errors.append(f"{key} must be an array")
|
|
40
|
+
if errors:
|
|
41
|
+
return errors
|
|
42
|
+
for key in ["providers", "models", "families", "offerings", "presets"]:
|
|
43
|
+
seen = set()
|
|
44
|
+
for item in data[key]:
|
|
45
|
+
item_id = item.get("id")
|
|
46
|
+
if not isinstance(item_id, str) or not item_id or item_id in seen:
|
|
47
|
+
errors.append(f"{key}: invalid or duplicate id")
|
|
48
|
+
seen.add(item_id)
|
|
49
|
+
providers = {p["id"] for p in data["providers"]}
|
|
50
|
+
models = {m["id"] for m in data["models"]}
|
|
51
|
+
for o in data["offerings"]:
|
|
52
|
+
errors.extend(_validate_signals(o))
|
|
53
|
+
if o.get("releaseDate") is not None:
|
|
54
|
+
try:
|
|
55
|
+
if date.fromisoformat(o["releaseDate"]).isoformat() != o["releaseDate"]:
|
|
56
|
+
raise ValueError()
|
|
57
|
+
except (ValueError, TypeError):
|
|
58
|
+
errors.append(f"{o['id']}: invalid releaseDate")
|
|
59
|
+
if "connection" in o:
|
|
60
|
+
try:
|
|
61
|
+
validate_connection(o["connection"])
|
|
62
|
+
except ValueError:
|
|
63
|
+
errors.append(o["id"] + ": invalid connection")
|
|
64
|
+
if o.get("providerId") not in providers or o.get("modelId") not in models:
|
|
65
|
+
errors.append(f"{o['id']}: unresolved reference")
|
|
66
|
+
if o.get("deployment") not in ["hosted", "local", "self-hosted"]:
|
|
67
|
+
errors.append(f"{o['id']}: invalid deployment")
|
|
68
|
+
for key, value in o.get("limits", {}).items():
|
|
69
|
+
if key != "contextAccounting" and value is not None and not positive(value):
|
|
70
|
+
errors.append(f"{o['id']}: invalid {key}")
|
|
71
|
+
for feature in o.get("features", {}).values():
|
|
72
|
+
if feature.get("status") not in FEATURE_STATES:
|
|
73
|
+
errors.append(f"{o['id']}: invalid feature status")
|
|
74
|
+
for value in o.get("price", {}).values():
|
|
75
|
+
if isinstance(value, (int, float)) and not finite(value):
|
|
76
|
+
errors.append(f"{o['id']}: invalid price")
|
|
77
|
+
for p in data["presets"]:
|
|
78
|
+
if not isinstance(p.get("providerIds"), list) or any(
|
|
79
|
+
i not in providers for i in p["providerIds"]
|
|
80
|
+
):
|
|
81
|
+
errors.append(f"{p['id']}: unresolved provider")
|
|
82
|
+
errors.extend(validate_verifications(data.get("verifications", []), data))
|
|
83
|
+
return errors
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def validate_config(config):
|
|
87
|
+
errors = []
|
|
88
|
+
if "sort" in config and config["sort"] not in {v.value for v in ModelSort}:
|
|
89
|
+
errors.append("Unknown model sort")
|
|
90
|
+
if "list" in config and config["list"] not in {v.value for v in ModelList}:
|
|
91
|
+
errors.append("Unknown model list")
|
|
92
|
+
if "local" in config and config["local"] is not False:
|
|
93
|
+
if not isinstance(config["local"], list):
|
|
94
|
+
errors.append("local must be false or an array")
|
|
95
|
+
else:
|
|
96
|
+
for entry in config["local"]:
|
|
97
|
+
try:
|
|
98
|
+
_validate_local(entry)
|
|
99
|
+
except ValueError as error:
|
|
100
|
+
errors.append(str(error))
|
|
101
|
+
allowed = {
|
|
102
|
+
"root": [
|
|
103
|
+
"sort",
|
|
104
|
+
"list",
|
|
105
|
+
"local",
|
|
106
|
+
"limit",
|
|
107
|
+
"asOf",
|
|
108
|
+
"unknownPolicy",
|
|
109
|
+
"schemaVersion",
|
|
110
|
+
"catalog",
|
|
111
|
+
"preset",
|
|
112
|
+
"providerIds",
|
|
113
|
+
"filters",
|
|
114
|
+
"prefer",
|
|
115
|
+
"ui",
|
|
116
|
+
"budget",
|
|
117
|
+
"tasks",
|
|
118
|
+
],
|
|
119
|
+
"filters": [
|
|
120
|
+
"deployment",
|
|
121
|
+
"origin",
|
|
122
|
+
"openWeights",
|
|
123
|
+
"commercialUse",
|
|
124
|
+
"input",
|
|
125
|
+
"output",
|
|
126
|
+
"requiredFeatures",
|
|
127
|
+
"minContextTokens",
|
|
128
|
+
"maxInputPricePerM",
|
|
129
|
+
"maxOutputPricePerM",
|
|
130
|
+
"maxParameters",
|
|
131
|
+
"maxTimeToFirstTokenMs",
|
|
132
|
+
"minTokensPerSecond",
|
|
133
|
+
"fileFormats",
|
|
134
|
+
"includeTags",
|
|
135
|
+
"excludeTags",
|
|
136
|
+
"lifecycle",
|
|
137
|
+
"popularity",
|
|
138
|
+
"includeDeprecated",
|
|
139
|
+
"verified",
|
|
140
|
+
"verifiedBy",
|
|
141
|
+
"verifiedWithinDays",
|
|
142
|
+
],
|
|
143
|
+
"prefer": ["features", "lowerCost"],
|
|
144
|
+
"ui": ["maxProviders", "maxModelsPerProvider", "allowCustomModel"],
|
|
145
|
+
"budget": ["maxInputTokens", "maxOutputTokens", "overheadTokens"],
|
|
146
|
+
}
|
|
147
|
+
for group, keys in allowed.items():
|
|
148
|
+
for key in config if group == "root" else config.get(group, {}) or {}:
|
|
149
|
+
if key not in keys:
|
|
150
|
+
errors.append(f"Unknown {group}.{key}")
|
|
151
|
+
for key in [
|
|
152
|
+
"deployment",
|
|
153
|
+
"origin",
|
|
154
|
+
"input",
|
|
155
|
+
"output",
|
|
156
|
+
"requiredFeatures",
|
|
157
|
+
"fileFormats",
|
|
158
|
+
"includeTags",
|
|
159
|
+
"excludeTags",
|
|
160
|
+
"lifecycle",
|
|
161
|
+
"popularity",
|
|
162
|
+
]:
|
|
163
|
+
if key in config.get("filters", {}) and (
|
|
164
|
+
not isinstance(config["filters"][key], list)
|
|
165
|
+
or any(not isinstance(v, str) for v in config["filters"][key])
|
|
166
|
+
):
|
|
167
|
+
errors.append(f"filters.{key} must be a string array")
|
|
168
|
+
for key in [
|
|
169
|
+
"minContextTokens",
|
|
170
|
+
"maxInputPricePerM",
|
|
171
|
+
"maxOutputPricePerM",
|
|
172
|
+
"maxParameters",
|
|
173
|
+
"maxTimeToFirstTokenMs",
|
|
174
|
+
"minTokensPerSecond",
|
|
175
|
+
]:
|
|
176
|
+
if key in config.get("filters", {}) and not finite(config["filters"][key]):
|
|
177
|
+
errors.append(f"filters.{key} must be a nonnegative finite number")
|
|
178
|
+
for key in ["maxProviders", "maxModelsPerProvider"]:
|
|
179
|
+
value = config.get("ui", {}).get(key)
|
|
180
|
+
if value is not None and (
|
|
181
|
+
not isinstance(value, int)
|
|
182
|
+
or isinstance(value, bool)
|
|
183
|
+
or not 1 <= value <= 100
|
|
184
|
+
):
|
|
185
|
+
errors.append(f"ui.{key} must be an integer from 1 to 100")
|
|
186
|
+
import re
|
|
187
|
+
|
|
188
|
+
if "limit" in config and (
|
|
189
|
+
not isinstance(config["limit"], int)
|
|
190
|
+
or isinstance(config["limit"], bool)
|
|
191
|
+
or not 1 <= config["limit"] <= 1000
|
|
192
|
+
):
|
|
193
|
+
errors.append("limit must be an integer from 1 to 1000")
|
|
194
|
+
if "asOf" in config:
|
|
195
|
+
try:
|
|
196
|
+
if not isinstance(config["asOf"], str) or not re.fullmatch(
|
|
197
|
+
r"\d{4}-\d{2}-\d{2}T.*Z", config["asOf"]
|
|
198
|
+
):
|
|
199
|
+
raise ValueError("UTC timestamp required")
|
|
200
|
+
_timestamp(config["asOf"])
|
|
201
|
+
except (ValueError, TypeError):
|
|
202
|
+
errors.append("asOf must be an ISO timestamp")
|
|
203
|
+
f = config.get("filters", {})
|
|
204
|
+
if "verified" in f and not isinstance(f["verified"], bool):
|
|
205
|
+
errors.append("filters.verified must be boolean")
|
|
206
|
+
if "verifiedBy" in f and (
|
|
207
|
+
not isinstance(f["verifiedBy"], str)
|
|
208
|
+
or not re.fullmatch(r"github:[a-z0-9-]+", f["verifiedBy"], re.I)
|
|
209
|
+
):
|
|
210
|
+
errors.append("verifiedBy must identify a GitHub contributor")
|
|
211
|
+
if "verifiedWithinDays" in f and (
|
|
212
|
+
not finite(f["verifiedWithinDays"]) or f["verifiedWithinDays"] < 1
|
|
213
|
+
):
|
|
214
|
+
errors.append("verifiedWithinDays must be positive")
|
|
215
|
+
vocab = {
|
|
216
|
+
"input": Modality,
|
|
217
|
+
"output": Modality,
|
|
218
|
+
"requiredFeatures": FeatureName,
|
|
219
|
+
"deployment": Deployment,
|
|
220
|
+
"lifecycle": Lifecycle,
|
|
221
|
+
"popularity": Popularity,
|
|
222
|
+
}
|
|
223
|
+
for key, enum in vocab.items():
|
|
224
|
+
values = f.get(key)
|
|
225
|
+
if isinstance(values, list) and any(
|
|
226
|
+
v not in {m.value for m in enum} for v in values
|
|
227
|
+
):
|
|
228
|
+
errors.append(f"Unknown filters.{key} value")
|
|
229
|
+
preferred = config.get("prefer", {}).get("features")
|
|
230
|
+
if preferred is not None and (
|
|
231
|
+
not isinstance(preferred, list)
|
|
232
|
+
or any(v not in {m.value for m in FeatureName} for v in preferred)
|
|
233
|
+
):
|
|
234
|
+
errors.append("Unknown preferred feature")
|
|
235
|
+
if "commercialUse" in f and f["commercialUse"] not in {
|
|
236
|
+
m.value for m in CommercialUse
|
|
237
|
+
}:
|
|
238
|
+
errors.append("Unknown commercialUse value")
|
|
239
|
+
if config.get("unknownPolicy", "exclude") not in ["include", "exclude"]:
|
|
240
|
+
errors.append("Invalid unknownPolicy")
|
|
241
|
+
for key in ["maxInputTokens", "maxOutputTokens", "overheadTokens"]:
|
|
242
|
+
if key in config.get("budget", {}) and not finite(config["budget"][key]):
|
|
243
|
+
errors.append(f"budget.{key} must be a nonnegative finite number")
|
|
244
|
+
return errors
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def matches(o, m, config, provider_tags=None):
|
|
248
|
+
f = config.get("filters", {})
|
|
249
|
+
unknown = config.get("unknownPolicy") == "include"
|
|
250
|
+
if "deployment" in f and o["deployment"] not in f["deployment"]:
|
|
251
|
+
return False
|
|
252
|
+
if "origin" in f and m.get("origin") not in f["origin"]:
|
|
253
|
+
return False
|
|
254
|
+
if "openWeights" in f:
|
|
255
|
+
value = m.get("openWeights")
|
|
256
|
+
if (value is None and not unknown) or (
|
|
257
|
+
value is not None and value != f["openWeights"]
|
|
258
|
+
):
|
|
259
|
+
return False
|
|
260
|
+
if "commercialUse" in f:
|
|
261
|
+
value = m.get("commercialUse", "unknown")
|
|
262
|
+
if (value == "unknown" and not unknown) or (
|
|
263
|
+
value != "unknown" and value != f["commercialUse"]
|
|
264
|
+
):
|
|
265
|
+
return False
|
|
266
|
+
for direction in ["input", "output"]:
|
|
267
|
+
required = f.get(direction, [])
|
|
268
|
+
values = o.get("modalities", {}).get(direction)
|
|
269
|
+
if required and (
|
|
270
|
+
(values is None and not unknown)
|
|
271
|
+
or (values is not None and not all(v in values for v in required))
|
|
272
|
+
):
|
|
273
|
+
return False
|
|
274
|
+
if f.get("fileFormats") and (
|
|
275
|
+
(o.get("fileFormats") is None and not unknown)
|
|
276
|
+
or (
|
|
277
|
+
o.get("fileFormats") is not None
|
|
278
|
+
and not all(v in o["fileFormats"] for v in f["fileFormats"])
|
|
279
|
+
)
|
|
280
|
+
):
|
|
281
|
+
return False
|
|
282
|
+
for feature in f.get("requiredFeatures", []):
|
|
283
|
+
state = o.get("features", {}).get(feature, {}).get("status", "unknown")
|
|
284
|
+
if state != "supported" and not (state == "unknown" and unknown):
|
|
285
|
+
return False
|
|
286
|
+
constraints = [
|
|
287
|
+
("minContextTokens", o.get("limits", {}).get("context"), "min"),
|
|
288
|
+
("maxInputPricePerM", o.get("price", {}).get("input"), "max"),
|
|
289
|
+
("maxOutputPricePerM", o.get("price", {}).get("output"), "max"),
|
|
290
|
+
("maxParameters", m.get("size", {}).get("totalParameters"), "max"),
|
|
291
|
+
("maxTimeToFirstTokenMs", o.get("speed", {}).get("timeToFirstTokenMs"), "max"),
|
|
292
|
+
("minTokensPerSecond", o.get("speed", {}).get("tokensPerSecond"), "min"),
|
|
293
|
+
]
|
|
294
|
+
for key, value, op in constraints:
|
|
295
|
+
if key in f and (
|
|
296
|
+
(not finite(value) and not unknown)
|
|
297
|
+
or (finite(value) and (value < f[key] if op == "min" else value > f[key]))
|
|
298
|
+
):
|
|
299
|
+
return False
|
|
300
|
+
if o["lifecycle"]["status"] not in f.get("lifecycle", [Lifecycle.LIVE]):
|
|
301
|
+
return False
|
|
302
|
+
if "popularity" in f and o["popularity"]["status"] not in f["popularity"]:
|
|
303
|
+
return False
|
|
304
|
+
tags = set(
|
|
305
|
+
[
|
|
306
|
+
t
|
|
307
|
+
for t in m.get("tags", []) + (provider_tags or [])
|
|
308
|
+
if not t.startswith(("lifecycle:", "popularity:"))
|
|
309
|
+
]
|
|
310
|
+
+ o.get("tags", [])
|
|
311
|
+
)
|
|
312
|
+
if any(t not in tags for t in f.get("includeTags", [])) or any(
|
|
313
|
+
t in tags for t in f.get("excludeTags", [])
|
|
314
|
+
):
|
|
315
|
+
return False
|
|
316
|
+
return f.get("includeDeprecated", False) or o.get("status") != "deprecated"
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
class Catalog:
|
|
320
|
+
def __init__(self, data):
|
|
321
|
+
errors = validate_catalog(data)
|
|
322
|
+
if errors:
|
|
323
|
+
raise ValueError("; ".join(errors))
|
|
324
|
+
self.data = copy.deepcopy(data)
|
|
325
|
+
self.providers = {p["id"]: p for p in self.data["providers"]}
|
|
326
|
+
self.models = {m["id"]: m for m in self.data["models"]}
|
|
327
|
+
self.offerings = {o["id"]: o for o in self.data["offerings"]}
|
|
328
|
+
|
|
329
|
+
def get(self, offering_id):
|
|
330
|
+
return self.offerings.get(offering_id)
|
|
331
|
+
|
|
332
|
+
def get_models(
|
|
333
|
+
self,
|
|
334
|
+
config: "PickerConfig | ModelList | None" = None,
|
|
335
|
+
options: "PickerConfig | None" = None,
|
|
336
|
+
) -> list[dict]:
|
|
337
|
+
config = _normalize_query(config, options)
|
|
338
|
+
groups = self._select(config, True)
|
|
339
|
+
result = []
|
|
340
|
+
for g in groups:
|
|
341
|
+
for o in g["offerings"]:
|
|
342
|
+
model = self.models.get(
|
|
343
|
+
o["modelId"],
|
|
344
|
+
{
|
|
345
|
+
"id": o["modelId"],
|
|
346
|
+
"name": o["name"],
|
|
347
|
+
"openWeights": None,
|
|
348
|
+
"commercialUse": "unknown",
|
|
349
|
+
"license": None,
|
|
350
|
+
"tags": [],
|
|
351
|
+
},
|
|
352
|
+
)
|
|
353
|
+
family = next(
|
|
354
|
+
(
|
|
355
|
+
f
|
|
356
|
+
for f in self.data["families"]
|
|
357
|
+
if f["id"] == model.get("familyId")
|
|
358
|
+
),
|
|
359
|
+
None,
|
|
360
|
+
)
|
|
361
|
+
assets = self.data.get("assets", [])
|
|
362
|
+
result.append(
|
|
363
|
+
dict(
|
|
364
|
+
o,
|
|
365
|
+
connection=connection_for(o),
|
|
366
|
+
provider=g["provider"],
|
|
367
|
+
model=model,
|
|
368
|
+
family=family,
|
|
369
|
+
logos={
|
|
370
|
+
"provider": next(
|
|
371
|
+
(
|
|
372
|
+
a
|
|
373
|
+
for a in assets
|
|
374
|
+
if a["id"] == g["provider"].get("logoId")
|
|
375
|
+
),
|
|
376
|
+
None,
|
|
377
|
+
),
|
|
378
|
+
"family": next(
|
|
379
|
+
(
|
|
380
|
+
a
|
|
381
|
+
for a in assets
|
|
382
|
+
if family and a["id"] == family.get("logoId")
|
|
383
|
+
),
|
|
384
|
+
None,
|
|
385
|
+
),
|
|
386
|
+
},
|
|
387
|
+
verification=verification_summary(
|
|
388
|
+
self.data,
|
|
389
|
+
o["id"],
|
|
390
|
+
as_of=config.get("asOf"),
|
|
391
|
+
max_age_days=config.get("filters", {}).get(
|
|
392
|
+
"verifiedWithinDays", 90
|
|
393
|
+
),
|
|
394
|
+
contributor=config.get("filters", {}).get("verifiedBy"),
|
|
395
|
+
),
|
|
396
|
+
)
|
|
397
|
+
)
|
|
398
|
+
hosted = [o for o in result if o["deployment"] != "local"]
|
|
399
|
+
sort = config.get("sort", ModelSort.ALPHABETICAL_ASCEND)
|
|
400
|
+
sort = {
|
|
401
|
+
"alphabetical": ModelSort.ALPHABETICAL_ASCEND,
|
|
402
|
+
"price": ModelSort.PRICE_ASCEND,
|
|
403
|
+
"speed": ModelSort.SPEED_DESCEND,
|
|
404
|
+
}.get(sort, sort)
|
|
405
|
+
if sort != ModelSort.PROVIDER_ORDER:
|
|
406
|
+
from functools import cmp_to_key
|
|
407
|
+
|
|
408
|
+
def ordering(a, b):
|
|
409
|
+
def compare(x, y):
|
|
410
|
+
return (x > y) - (x < y)
|
|
411
|
+
|
|
412
|
+
result = 0
|
|
413
|
+
if sort in [
|
|
414
|
+
ModelSort.PRICE_ASCEND,
|
|
415
|
+
ModelSort.PRICE_DESCEND,
|
|
416
|
+
ModelSort.SPEED_ASCEND,
|
|
417
|
+
ModelSort.SPEED_DESCEND,
|
|
418
|
+
]:
|
|
419
|
+
price = sort in [ModelSort.PRICE_ASCEND, ModelSort.PRICE_DESCEND]
|
|
420
|
+
|
|
421
|
+
def value(o):
|
|
422
|
+
return (
|
|
423
|
+
o.get("price", {}).get("input")
|
|
424
|
+
if o.get("price", {}).get("currency") == "USD"
|
|
425
|
+
else None
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
x = (
|
|
429
|
+
value(a)
|
|
430
|
+
if price
|
|
431
|
+
else (a.get("speed") or {}).get("tokensPerSecond")
|
|
432
|
+
)
|
|
433
|
+
y = (
|
|
434
|
+
value(b)
|
|
435
|
+
if price
|
|
436
|
+
else (b.get("speed") or {}).get("tokensPerSecond")
|
|
437
|
+
)
|
|
438
|
+
if x is None or y is None:
|
|
439
|
+
result = -1 if x is not None else 1 if y is not None else 0
|
|
440
|
+
else:
|
|
441
|
+
result = compare(x, y) * (
|
|
442
|
+
1
|
|
443
|
+
if sort in [ModelSort.PRICE_ASCEND, ModelSort.SPEED_ASCEND]
|
|
444
|
+
else -1
|
|
445
|
+
)
|
|
446
|
+
return (
|
|
447
|
+
result
|
|
448
|
+
or compare(a["name"].lower(), b["name"].lower())
|
|
449
|
+
* (-1 if sort == ModelSort.ALPHABETICAL_DESCEND else 1)
|
|
450
|
+
or compare(a["providerId"], b["providerId"])
|
|
451
|
+
or compare(a["id"], b["id"])
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
hosted.sort(key=cmp_to_key(ordering))
|
|
455
|
+
return copy.deepcopy(
|
|
456
|
+
hosted[: config.get("limit", len(result))]
|
|
457
|
+
+ [o for o in result if o["deployment"] == "local"]
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
def select(self, config=None, options=None):
|
|
461
|
+
return self._select(_normalize_query(config, options))
|
|
462
|
+
|
|
463
|
+
def _select(self, config=None, uncapped=False):
|
|
464
|
+
config = config or {}
|
|
465
|
+
errors = validate_config(config)
|
|
466
|
+
if errors:
|
|
467
|
+
raise ValueError("; ".join(errors))
|
|
468
|
+
preset = next(
|
|
469
|
+
(
|
|
470
|
+
p
|
|
471
|
+
for p in self.data["presets"]
|
|
472
|
+
if p["id"]
|
|
473
|
+
== config.get(
|
|
474
|
+
"preset",
|
|
475
|
+
"default" if config.get("list") == ModelList.DEFAULT else "global",
|
|
476
|
+
)
|
|
477
|
+
),
|
|
478
|
+
None,
|
|
479
|
+
)
|
|
480
|
+
if not preset and config.get("list") == ModelList.DEFAULT:
|
|
481
|
+
preset = next(
|
|
482
|
+
(p for p in self.data["presets"] if p["id"] == "global"), None
|
|
483
|
+
)
|
|
484
|
+
if not preset:
|
|
485
|
+
raise ValueError("Unknown preset")
|
|
486
|
+
ids = config.get(
|
|
487
|
+
"providerIds",
|
|
488
|
+
list(self.providers)
|
|
489
|
+
if config.get("list") == ModelList.COMPLETE
|
|
490
|
+
else preset["providerIds"],
|
|
491
|
+
)
|
|
492
|
+
if any(i not in self.providers for i in ids):
|
|
493
|
+
raise ValueError("Unknown provider in providerIds")
|
|
494
|
+
groups = []
|
|
495
|
+
|
|
496
|
+
def order(o):
|
|
497
|
+
count = sum(
|
|
498
|
+
o.get("features", {}).get(f, {}).get("status") == "supported"
|
|
499
|
+
for f in config.get("prefer", {}).get("features", [])
|
|
500
|
+
)
|
|
501
|
+
price = o.get("price", {}).get("input")
|
|
502
|
+
return (
|
|
503
|
+
-count,
|
|
504
|
+
(price if price is not None else math.inf)
|
|
505
|
+
if config.get("prefer", {}).get("lowerCost")
|
|
506
|
+
else 0,
|
|
507
|
+
o.get("rank", 100),
|
|
508
|
+
o["id"],
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
for provider_id in ids:
|
|
512
|
+
if self.providers[provider_id]["deployment"] == "local":
|
|
513
|
+
continue
|
|
514
|
+
choices = sorted(
|
|
515
|
+
(
|
|
516
|
+
o
|
|
517
|
+
for o in (
|
|
518
|
+
_with_signals(item, config.get("asOf"))
|
|
519
|
+
for item in self.data["offerings"]
|
|
520
|
+
if item["providerId"] == provider_id
|
|
521
|
+
)
|
|
522
|
+
if matches(
|
|
523
|
+
o,
|
|
524
|
+
self.models[o["modelId"]],
|
|
525
|
+
config,
|
|
526
|
+
self.providers[provider_id].get("tags", []),
|
|
527
|
+
)
|
|
528
|
+
and matches_verification(self.data, o["id"], config)
|
|
529
|
+
),
|
|
530
|
+
key=order,
|
|
531
|
+
)
|
|
532
|
+
cap = (
|
|
533
|
+
(
|
|
534
|
+
len(choices)
|
|
535
|
+
if config.get("list") == ModelList.COMPLETE
|
|
536
|
+
or "providerIds" in config
|
|
537
|
+
else config.get("ui", {}).get("maxModelsPerProvider", 1)
|
|
538
|
+
)
|
|
539
|
+
if uncapped
|
|
540
|
+
else config.get("ui", {}).get("maxModelsPerProvider", 4)
|
|
541
|
+
)
|
|
542
|
+
if choices:
|
|
543
|
+
groups.append(
|
|
544
|
+
{
|
|
545
|
+
"provider": self.providers[provider_id],
|
|
546
|
+
"offerings": choices[:cap],
|
|
547
|
+
}
|
|
548
|
+
)
|
|
549
|
+
locals_ = _local_groups(config, self)
|
|
550
|
+
return (
|
|
551
|
+
groups
|
|
552
|
+
if uncapped
|
|
553
|
+
else groups[
|
|
554
|
+
: max(0, config.get("ui", {}).get("maxProviders", 8) - len(locals_))
|
|
555
|
+
]
|
|
556
|
+
) + locals_
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
def check_fit(offering, budget, options=None):
|
|
560
|
+
options = options or {}
|
|
561
|
+
if not offering:
|
|
562
|
+
return {
|
|
563
|
+
"status": "unknown",
|
|
564
|
+
"certainty": "unknown",
|
|
565
|
+
"diagnostics": [
|
|
566
|
+
diagnostic(
|
|
567
|
+
"MODEL_UNVERIFIED",
|
|
568
|
+
"Custom model limits and capabilities are unverified.",
|
|
569
|
+
)
|
|
570
|
+
],
|
|
571
|
+
}
|
|
572
|
+
for key in ["inputTokens", "outputTokens", "overheadTokens"]:
|
|
573
|
+
if key in budget and budget[key] is not None and not finite(budget[key]):
|
|
574
|
+
raise ValueError(f"Invalid {key}")
|
|
575
|
+
input_tokens = (budget.get("inputTokens") or 0) + (
|
|
576
|
+
budget.get("overheadTokens") or 0
|
|
577
|
+
)
|
|
578
|
+
output = budget.get("outputTokens") or 0
|
|
579
|
+
limits = offering.get("limits", {})
|
|
580
|
+
diagnostics = []
|
|
581
|
+
oid = offering["id"]
|
|
582
|
+
for f in options.get("requiredFeatures", []):
|
|
583
|
+
state = offering.get("features", {}).get(f, {}).get("status", "unknown")
|
|
584
|
+
if state != "supported":
|
|
585
|
+
diagnostics.append(
|
|
586
|
+
diagnostic(
|
|
587
|
+
"CAPABILITY_UNSUPPORTED"
|
|
588
|
+
if state == "unsupported"
|
|
589
|
+
else "CAPABILITY_UNVERIFIED",
|
|
590
|
+
f"{f}: {state}",
|
|
591
|
+
"error" if state == "unsupported" else "warning",
|
|
592
|
+
oid,
|
|
593
|
+
)
|
|
594
|
+
)
|
|
595
|
+
for direction in ["input", "output"]:
|
|
596
|
+
for modality in options.get("modalities", {}).get(direction, []):
|
|
597
|
+
values = offering.get("modalities", {}).get(direction)
|
|
598
|
+
if values is None or modality not in values:
|
|
599
|
+
diagnostics.append(
|
|
600
|
+
diagnostic(
|
|
601
|
+
"MODALITY_UNVERIFIED"
|
|
602
|
+
if values is None
|
|
603
|
+
else "MODALITY_UNSUPPORTED",
|
|
604
|
+
f"{direction} modality {modality} is {'unverified' if values is None else 'unsupported'}.",
|
|
605
|
+
"warning" if values is None else "error",
|
|
606
|
+
oid,
|
|
607
|
+
)
|
|
608
|
+
)
|
|
609
|
+
if positive(limits.get("input")) and input_tokens > limits["input"]:
|
|
610
|
+
diagnostics.append(
|
|
611
|
+
diagnostic(
|
|
612
|
+
"INPUT_BUDGET_EXCEEDED",
|
|
613
|
+
f"Input {input_tokens} exceeds {limits['input']}.",
|
|
614
|
+
"error",
|
|
615
|
+
oid,
|
|
616
|
+
)
|
|
617
|
+
)
|
|
618
|
+
if positive(limits.get("output")) and output > limits["output"]:
|
|
619
|
+
diagnostics.append(
|
|
620
|
+
diagnostic(
|
|
621
|
+
"OUTPUT_BUDGET_EXCEEDED",
|
|
622
|
+
f"Output {output} exceeds {limits['output']}.",
|
|
623
|
+
"error",
|
|
624
|
+
oid,
|
|
625
|
+
)
|
|
626
|
+
)
|
|
627
|
+
used = (
|
|
628
|
+
input_tokens + output
|
|
629
|
+
if limits.get("contextAccounting") == "combined"
|
|
630
|
+
else input_tokens
|
|
631
|
+
)
|
|
632
|
+
ratio = None
|
|
633
|
+
if positive(limits.get("context")) and limits.get("contextAccounting") in [
|
|
634
|
+
"combined",
|
|
635
|
+
"input-only",
|
|
636
|
+
]:
|
|
637
|
+
ratio = used / limits["context"]
|
|
638
|
+
if ratio > 1:
|
|
639
|
+
diagnostics.append(
|
|
640
|
+
diagnostic(
|
|
641
|
+
"CONTEXT_BUDGET_EXCEEDED",
|
|
642
|
+
f"Required {used} exceeds context {limits['context']}.",
|
|
643
|
+
"error",
|
|
644
|
+
oid,
|
|
645
|
+
)
|
|
646
|
+
)
|
|
647
|
+
elif ratio >= 0.8:
|
|
648
|
+
diagnostics.append(
|
|
649
|
+
diagnostic(
|
|
650
|
+
"CONTEXT_NEAR_LIMIT",
|
|
651
|
+
f"Context budget uses {math.floor(ratio * 100 + 0.5)}%.",
|
|
652
|
+
"warning",
|
|
653
|
+
oid,
|
|
654
|
+
)
|
|
655
|
+
)
|
|
656
|
+
else:
|
|
657
|
+
if positive(limits.get("context")) and input_tokens > limits["context"]:
|
|
658
|
+
diagnostics.append(
|
|
659
|
+
diagnostic(
|
|
660
|
+
"CONTEXT_BUDGET_EXCEEDED",
|
|
661
|
+
f"Input {input_tokens} exceeds context {limits['context']}.",
|
|
662
|
+
"error",
|
|
663
|
+
oid,
|
|
664
|
+
)
|
|
665
|
+
)
|
|
666
|
+
diagnostics.append(
|
|
667
|
+
diagnostic(
|
|
668
|
+
"LOCAL_CONTEXT_UNKNOWN"
|
|
669
|
+
if offering.get("deployment") == "local"
|
|
670
|
+
else "CONTEXT_UNVERIFIED",
|
|
671
|
+
"Effective context or its accounting rules are unknown.",
|
|
672
|
+
offering_id=oid,
|
|
673
|
+
)
|
|
674
|
+
)
|
|
675
|
+
if output > 0 and not positive(limits.get("output")):
|
|
676
|
+
diagnostics.append(
|
|
677
|
+
diagnostic(
|
|
678
|
+
"OUTPUT_LIMIT_UNKNOWN", "Output limit is unknown.", offering_id=oid
|
|
679
|
+
)
|
|
680
|
+
)
|
|
681
|
+
if options.get("estimate"):
|
|
682
|
+
diagnostics.append(
|
|
683
|
+
diagnostic(
|
|
684
|
+
"TOKEN_ESTIMATE",
|
|
685
|
+
"Token count is estimated; this is not an exact fit guarantee.",
|
|
686
|
+
offering_id=oid,
|
|
687
|
+
)
|
|
688
|
+
)
|
|
689
|
+
if offering.get("status") == "deprecated":
|
|
690
|
+
diagnostics.append(
|
|
691
|
+
diagnostic(
|
|
692
|
+
"MODEL_DEPRECATED", "Selected model is deprecated.", offering_id=oid
|
|
693
|
+
)
|
|
694
|
+
)
|
|
695
|
+
unknown_codes = {
|
|
696
|
+
"CONTEXT_UNVERIFIED",
|
|
697
|
+
"LOCAL_CONTEXT_UNKNOWN",
|
|
698
|
+
"OUTPUT_LIMIT_UNKNOWN",
|
|
699
|
+
"CAPABILITY_UNVERIFIED",
|
|
700
|
+
"MODALITY_UNVERIFIED",
|
|
701
|
+
"TOKEN_ESTIMATE",
|
|
702
|
+
}
|
|
703
|
+
status = (
|
|
704
|
+
"exceeds"
|
|
705
|
+
if any(d["severity"] == "error" for d in diagnostics)
|
|
706
|
+
else "unknown"
|
|
707
|
+
if any(d["code"] in unknown_codes for d in diagnostics)
|
|
708
|
+
else "fits"
|
|
709
|
+
)
|
|
710
|
+
return dict(
|
|
711
|
+
status=status,
|
|
712
|
+
certainty="estimated"
|
|
713
|
+
if options.get("estimate")
|
|
714
|
+
else options.get("certainty", "declared"),
|
|
715
|
+
inputTokens=input_tokens,
|
|
716
|
+
outputTokens=output,
|
|
717
|
+
diagnostics=diagnostics,
|
|
718
|
+
**({"ratio": ratio} if ratio is not None else {}),
|
|
719
|
+
)
|
|
720
|
+
|
|
721
|
+
|
|
722
|
+
def check_task(catalog, task):
|
|
723
|
+
if not task.get("offeringIds"):
|
|
724
|
+
return [
|
|
725
|
+
diagnostic("NO_MODELS", "Task requires at least one offering.", "error")
|
|
726
|
+
]
|
|
727
|
+
result = []
|
|
728
|
+
b = task.get("budget", {})
|
|
729
|
+
r = task.get("requires", {})
|
|
730
|
+
if b.get("maxInputTokens") is None or b.get("maxOutputTokens") is None:
|
|
731
|
+
result.append(
|
|
732
|
+
diagnostic(
|
|
733
|
+
"BUDGET_UNDECLARED",
|
|
734
|
+
"Declare input and output token budgets for build-time checks.",
|
|
735
|
+
)
|
|
736
|
+
)
|
|
737
|
+
for oid in task["offeringIds"]:
|
|
738
|
+
fit = check_fit(
|
|
739
|
+
catalog.get(oid),
|
|
740
|
+
{
|
|
741
|
+
"inputTokens": b.get("maxInputTokens"),
|
|
742
|
+
"outputTokens": b.get("maxOutputTokens"),
|
|
743
|
+
"overheadTokens": b.get("overheadTokens"),
|
|
744
|
+
},
|
|
745
|
+
{
|
|
746
|
+
"requiredFeatures": r.get("features", []),
|
|
747
|
+
"modalities": r.get("modalities", {}),
|
|
748
|
+
},
|
|
749
|
+
)
|
|
750
|
+
result.extend(dict(d, offeringId=oid) for d in fit["diagnostics"])
|
|
751
|
+
return result
|
|
752
|
+
|
|
753
|
+
|
|
754
|
+
def estimate_price(offering, budget):
|
|
755
|
+
i = budget["inputTokens"]
|
|
756
|
+
o = budget["outputTokens"]
|
|
757
|
+
if not finite(i) or not finite(o):
|
|
758
|
+
raise ValueError("Invalid token counts")
|
|
759
|
+
p = offering.get("price", {})
|
|
760
|
+
if (
|
|
761
|
+
p.get("currency") != "USD"
|
|
762
|
+
or not finite(p.get("input"))
|
|
763
|
+
or not finite(p.get("output"))
|
|
764
|
+
or p.get("tiers")
|
|
765
|
+
):
|
|
766
|
+
return {"status": "unknown"}
|
|
767
|
+
return {
|
|
768
|
+
"status": "estimated",
|
|
769
|
+
"currency": "USD",
|
|
770
|
+
"amount": (i * p["input"] + o * p["output"]) / 1e6,
|
|
771
|
+
"excludes": [
|
|
772
|
+
"cache pricing",
|
|
773
|
+
"additional reasoning charges",
|
|
774
|
+
"tax",
|
|
775
|
+
"tier changes",
|
|
776
|
+
],
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
|
|
780
|
+
def load_catalog():
|
|
781
|
+
"""Load the packaged offline snapshot. No network or Node dependency."""
|
|
782
|
+
import json
|
|
783
|
+
from importlib.resources import files
|
|
784
|
+
|
|
785
|
+
return Catalog(
|
|
786
|
+
json.loads(files("byokit").joinpath("data/catalog.json").read_text())
|
|
787
|
+
)
|
|
788
|
+
|
|
789
|
+
|
|
790
|
+
def derive_tags(offering, model=None):
|
|
791
|
+
model = model or {}
|
|
792
|
+
prefixes = (
|
|
793
|
+
"cap:",
|
|
794
|
+
"input:",
|
|
795
|
+
"output:",
|
|
796
|
+
"price:",
|
|
797
|
+
"context:",
|
|
798
|
+
"weights:",
|
|
799
|
+
"origin:",
|
|
800
|
+
"deployment:",
|
|
801
|
+
"lifecycle:",
|
|
802
|
+
"popularity:",
|
|
803
|
+
)
|
|
804
|
+
tags = [t for t in offering.get("tags", []) if not t.startswith(prefixes)]
|
|
805
|
+
tags.extend(
|
|
806
|
+
"cap:" + name
|
|
807
|
+
for name, f in offering.get("features", {}).items()
|
|
808
|
+
if f.get("status") == "supported"
|
|
809
|
+
)
|
|
810
|
+
for direction in ["input", "output"]:
|
|
811
|
+
tags.extend(
|
|
812
|
+
direction + ":" + m
|
|
813
|
+
for m in offering.get("modalities", {}).get(direction, []) or []
|
|
814
|
+
)
|
|
815
|
+
if model.get("openWeights") is True:
|
|
816
|
+
tags.append("weights:open")
|
|
817
|
+
if model.get("origin"):
|
|
818
|
+
tags.append("origin:" + model["origin"])
|
|
819
|
+
tags.append("deployment:" + offering["deployment"])
|
|
820
|
+
ctx = offering.get("limits", {}).get("context")
|
|
821
|
+
if positive(ctx):
|
|
822
|
+
tags.append(
|
|
823
|
+
"context:"
|
|
824
|
+
+ (
|
|
825
|
+
"1m+"
|
|
826
|
+
if ctx >= 1000000
|
|
827
|
+
else "128k+"
|
|
828
|
+
if ctx >= 128000
|
|
829
|
+
else "32k+"
|
|
830
|
+
if ctx >= 32000
|
|
831
|
+
else "under-32k"
|
|
832
|
+
)
|
|
833
|
+
)
|
|
834
|
+
p = offering.get("price", {})
|
|
835
|
+
if (
|
|
836
|
+
p.get("currency") == "USD"
|
|
837
|
+
and finite(p.get("input"))
|
|
838
|
+
and finite(p.get("output"))
|
|
839
|
+
and not p.get("tiers")
|
|
840
|
+
):
|
|
841
|
+
cost = (p["input"] + p["output"]) / 1000
|
|
842
|
+
tags.append(
|
|
843
|
+
"price:"
|
|
844
|
+
+ (
|
|
845
|
+
"zero-token-rate"
|
|
846
|
+
if cost == 0
|
|
847
|
+
else "budget"
|
|
848
|
+
if cost <= 0.002
|
|
849
|
+
else "mid"
|
|
850
|
+
if cost <= 0.02
|
|
851
|
+
else "premium"
|
|
852
|
+
)
|
|
853
|
+
)
|
|
854
|
+
return sorted(set(tags))
|
|
855
|
+
|
|
856
|
+
|
|
857
|
+
async def preflight(
|
|
858
|
+
offering,
|
|
859
|
+
messages,
|
|
860
|
+
*,
|
|
861
|
+
count_tokens=None,
|
|
862
|
+
output_tokens=0,
|
|
863
|
+
overhead_tokens=0,
|
|
864
|
+
options=None,
|
|
865
|
+
):
|
|
866
|
+
if count_tokens is None:
|
|
867
|
+
return {
|
|
868
|
+
"status": "unknown",
|
|
869
|
+
"certainty": "unknown",
|
|
870
|
+
"diagnostics": [
|
|
871
|
+
diagnostic(
|
|
872
|
+
"TOKEN_COUNT_UNKNOWN",
|
|
873
|
+
"Supply a provider-aware token counter for actual requests.",
|
|
874
|
+
)
|
|
875
|
+
],
|
|
876
|
+
}
|
|
877
|
+
import inspect
|
|
878
|
+
|
|
879
|
+
count = count_tokens(messages, offering)
|
|
880
|
+
if inspect.isawaitable(count):
|
|
881
|
+
count = await count
|
|
882
|
+
return check_fit(
|
|
883
|
+
offering,
|
|
884
|
+
{
|
|
885
|
+
"inputTokens": count["tokens"],
|
|
886
|
+
"outputTokens": output_tokens,
|
|
887
|
+
"overheadTokens": overhead_tokens,
|
|
888
|
+
},
|
|
889
|
+
{
|
|
890
|
+
**(options or {}),
|
|
891
|
+
"estimate": count.get("certainty") != "measured",
|
|
892
|
+
"certainty": count.get("certainty", "estimated"),
|
|
893
|
+
},
|
|
894
|
+
)
|
|
895
|
+
|
|
896
|
+
|
|
897
|
+
def _timestamp(value):
|
|
898
|
+
from datetime import datetime
|
|
899
|
+
|
|
900
|
+
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
|
|
901
|
+
if parsed.tzinfo is None:
|
|
902
|
+
raise ValueError("Timezone required")
|
|
903
|
+
return parsed.timestamp()
|
|
904
|
+
|
|
905
|
+
|
|
906
|
+
def validate_verifications(records, data):
|
|
907
|
+
import re
|
|
908
|
+
from urllib.parse import urlparse
|
|
909
|
+
|
|
910
|
+
if not isinstance(records, list):
|
|
911
|
+
return ["verifications must be an array"]
|
|
912
|
+
errors = []
|
|
913
|
+
ids = set()
|
|
914
|
+
offerings = {o["id"] for o in data["offerings"]}
|
|
915
|
+
for r in records:
|
|
916
|
+
if not isinstance(r, dict):
|
|
917
|
+
errors.append("Invalid verification record")
|
|
918
|
+
continue
|
|
919
|
+
if not isinstance(r.get("id"), str) or not r["id"] or r["id"] in ids:
|
|
920
|
+
errors.append("Invalid or duplicate verification ID")
|
|
921
|
+
ids.add(r.get("id"))
|
|
922
|
+
if r.get("offeringId") not in offerings:
|
|
923
|
+
errors.append("Verification target is missing")
|
|
924
|
+
if not isinstance(r.get("contributor"), str) or not re.fullmatch(
|
|
925
|
+
r"github:[a-z0-9-]+", r["contributor"], re.I
|
|
926
|
+
):
|
|
927
|
+
errors.append("Invalid verification contributor")
|
|
928
|
+
try:
|
|
929
|
+
if not re.fullmatch(r"\d{4}-\d{2}-\d{2}T.*Z", r.get("checkedAt", "")):
|
|
930
|
+
raise ValueError("Invalid date")
|
|
931
|
+
_timestamp(r["checkedAt"])
|
|
932
|
+
except (ValueError, TypeError):
|
|
933
|
+
errors.append("Invalid verification timestamp")
|
|
934
|
+
if r.get("result") not in ["passed", "failed"]:
|
|
935
|
+
errors.append("Invalid verification result")
|
|
936
|
+
if (
|
|
937
|
+
not isinstance(r.get("checks"), list)
|
|
938
|
+
or not r["checks"]
|
|
939
|
+
or any(c not in {m.value for m in VerificationCheck} for c in r["checks"])
|
|
940
|
+
):
|
|
941
|
+
errors.append("Verification must name its checks")
|
|
942
|
+
try:
|
|
943
|
+
u = urlparse(r.get("evidence", ""))
|
|
944
|
+
if u.scheme != "https" or not u.hostname or u.username or u.password:
|
|
945
|
+
raise ValueError("Invalid URL")
|
|
946
|
+
except (ValueError, TypeError):
|
|
947
|
+
errors.append("Verification evidence is required")
|
|
948
|
+
if not isinstance(r.get("adapter"), str) or not r["adapter"]:
|
|
949
|
+
errors.append("Verification adapter and version are required")
|
|
950
|
+
if set(r) - {
|
|
951
|
+
"id",
|
|
952
|
+
"offeringId",
|
|
953
|
+
"contributor",
|
|
954
|
+
"checkedAt",
|
|
955
|
+
"result",
|
|
956
|
+
"checks",
|
|
957
|
+
"evidence",
|
|
958
|
+
"adapter",
|
|
959
|
+
"notes",
|
|
960
|
+
}:
|
|
961
|
+
errors.append(
|
|
962
|
+
"Unknown verification field; do not record keys or request payloads"
|
|
963
|
+
)
|
|
964
|
+
return errors
|
|
965
|
+
|
|
966
|
+
|
|
967
|
+
def verification_summary(
|
|
968
|
+
data, offering_id, *, as_of=None, max_age_days=90, contributor=None
|
|
969
|
+
):
|
|
970
|
+
from datetime import datetime, timezone
|
|
971
|
+
|
|
972
|
+
now = _timestamp(as_of) if as_of else datetime.now(timezone.utc).timestamp()
|
|
973
|
+
records = [
|
|
974
|
+
r
|
|
975
|
+
for r in data.get("verifications", [])
|
|
976
|
+
if r["offeringId"] == offering_id
|
|
977
|
+
and (not contributor or r["contributor"].lower() == contributor.lower())
|
|
978
|
+
]
|
|
979
|
+
calls = sorted(
|
|
980
|
+
(
|
|
981
|
+
r
|
|
982
|
+
for r in records
|
|
983
|
+
if "textGeneration" in r["checks"] and _timestamp(r["checkedAt"]) <= now
|
|
984
|
+
),
|
|
985
|
+
key=lambda r: (
|
|
986
|
+
-_timestamp(r["checkedAt"]),
|
|
987
|
+
0 if r["result"] == "failed" else 1,
|
|
988
|
+
r["id"],
|
|
989
|
+
),
|
|
990
|
+
)
|
|
991
|
+
latest = calls[0] if calls else None
|
|
992
|
+
status = (
|
|
993
|
+
"unverified"
|
|
994
|
+
if latest is None
|
|
995
|
+
else "stale"
|
|
996
|
+
if now - _timestamp(latest["checkedAt"]) > max_age_days * 86400
|
|
997
|
+
else latest["result"]
|
|
998
|
+
)
|
|
999
|
+
return {"status": status, "latest": latest, "records": records}
|
|
1000
|
+
|
|
1001
|
+
|
|
1002
|
+
def matches_verification(data, oid, config):
|
|
1003
|
+
f = config.get("filters", {})
|
|
1004
|
+
if not any(k in f for k in ["verified", "verifiedBy", "verifiedWithinDays"]):
|
|
1005
|
+
return True
|
|
1006
|
+
status = verification_summary(
|
|
1007
|
+
data,
|
|
1008
|
+
oid,
|
|
1009
|
+
as_of=config.get("asOf"),
|
|
1010
|
+
max_age_days=f.get("verifiedWithinDays", 90),
|
|
1011
|
+
contributor=f.get("verifiedBy"),
|
|
1012
|
+
)["status"]
|
|
1013
|
+
return status != "passed" if f.get("verified") is False else status == "passed"
|
|
1014
|
+
|
|
1015
|
+
|
|
1016
|
+
def get_models(
|
|
1017
|
+
config: "PickerConfig | ModelList | None" = None,
|
|
1018
|
+
options: "PickerConfig | None" = None,
|
|
1019
|
+
) -> list[dict]:
|
|
1020
|
+
return load_catalog().get_models(config, options)
|
|
1021
|
+
|
|
1022
|
+
|
|
1023
|
+
from .types import (
|
|
1024
|
+
PickerConfig,
|
|
1025
|
+
ContributorId,
|
|
1026
|
+
CustomTag,
|
|
1027
|
+
CustomProviderId,
|
|
1028
|
+
CustomCountryCode,
|
|
1029
|
+
CustomFileFormat,
|
|
1030
|
+
)
|
|
1031
|
+
|
|
1032
|
+
|
|
1033
|
+
def github_contributor(handle: str) -> ContributorId:
|
|
1034
|
+
import re
|
|
1035
|
+
|
|
1036
|
+
if not re.fullmatch(r"[a-z0-9-]{1,39}", handle, re.I):
|
|
1037
|
+
raise ValueError("Invalid GitHub handle")
|
|
1038
|
+
return ContributorId("github:" + handle)
|
|
1039
|
+
|
|
1040
|
+
|
|
1041
|
+
def custom_tag(value: str) -> CustomTag:
|
|
1042
|
+
import re
|
|
1043
|
+
|
|
1044
|
+
if not re.fullmatch(r"[a-z][a-z0-9_-]*:[^\s]+", value):
|
|
1045
|
+
raise ValueError("Use a namespaced tag")
|
|
1046
|
+
return CustomTag(value)
|
|
1047
|
+
|
|
1048
|
+
|
|
1049
|
+
def custom_provider_id(value: str) -> CustomProviderId:
|
|
1050
|
+
import re
|
|
1051
|
+
|
|
1052
|
+
if not re.fullmatch(r"[a-z][a-z0-9_-]{0,63}", value):
|
|
1053
|
+
raise ValueError("Invalid custom provider ID")
|
|
1054
|
+
return CustomProviderId(value)
|
|
1055
|
+
|
|
1056
|
+
|
|
1057
|
+
def custom_country_code(value: str) -> CustomCountryCode:
|
|
1058
|
+
import re
|
|
1059
|
+
|
|
1060
|
+
if not re.fullmatch(r"[a-z]{2}", value):
|
|
1061
|
+
raise ValueError("Use a two-letter country code")
|
|
1062
|
+
return CustomCountryCode(value)
|
|
1063
|
+
|
|
1064
|
+
|
|
1065
|
+
def custom_file_format(value: str) -> CustomFileFormat:
|
|
1066
|
+
import re
|
|
1067
|
+
|
|
1068
|
+
if not re.fullmatch(r"[a-z0-9][a-z0-9.+-]{0,63}", value):
|
|
1069
|
+
raise ValueError("Invalid custom file format")
|
|
1070
|
+
return CustomFileFormat(value)
|
|
1071
|
+
|
|
1072
|
+
|
|
1073
|
+
def _signal_date(value):
|
|
1074
|
+
import re
|
|
1075
|
+
|
|
1076
|
+
try:
|
|
1077
|
+
return (
|
|
1078
|
+
isinstance(value, str)
|
|
1079
|
+
and bool(re.fullmatch(r"\d{4}-\d{2}-\d{2}T.*Z", value))
|
|
1080
|
+
and math.isfinite(_timestamp(value))
|
|
1081
|
+
)
|
|
1082
|
+
except (ValueError, TypeError):
|
|
1083
|
+
return False
|
|
1084
|
+
|
|
1085
|
+
|
|
1086
|
+
def _validate_signals(o):
|
|
1087
|
+
from urllib.parse import urlparse
|
|
1088
|
+
|
|
1089
|
+
errors = []
|
|
1090
|
+
|
|
1091
|
+
def source(v):
|
|
1092
|
+
try:
|
|
1093
|
+
return (
|
|
1094
|
+
isinstance(v, str)
|
|
1095
|
+
and urlparse(v).scheme == "https"
|
|
1096
|
+
and bool(urlparse(v).netloc)
|
|
1097
|
+
)
|
|
1098
|
+
except ValueError:
|
|
1099
|
+
return False
|
|
1100
|
+
|
|
1101
|
+
def record(v, keys):
|
|
1102
|
+
return isinstance(v, dict) and set(v) <= set(keys)
|
|
1103
|
+
|
|
1104
|
+
l = o.get("lifecycleEvidence")
|
|
1105
|
+
p = o.get("popularityEvidence")
|
|
1106
|
+
if "lifecycleEvidence" in o and (
|
|
1107
|
+
not record(l, ["status", "source", "checkedAt", "basis"])
|
|
1108
|
+
or l.get("status") not in {v.value for v in Lifecycle}
|
|
1109
|
+
or ("basis" in l and l["basis"] not in {v.value for v in LifecycleBasis})
|
|
1110
|
+
or not source(l.get("source"))
|
|
1111
|
+
or not _signal_date(l.get("checkedAt"))
|
|
1112
|
+
):
|
|
1113
|
+
errors.append(o["id"] + ": invalid lifecycle evidence")
|
|
1114
|
+
if "popularityEvidence" in o and (
|
|
1115
|
+
not record(
|
|
1116
|
+
p,
|
|
1117
|
+
[
|
|
1118
|
+
"metric",
|
|
1119
|
+
"rank",
|
|
1120
|
+
"population",
|
|
1121
|
+
"scope",
|
|
1122
|
+
"source",
|
|
1123
|
+
"windowStart",
|
|
1124
|
+
"windowEnd",
|
|
1125
|
+
],
|
|
1126
|
+
)
|
|
1127
|
+
or p.get("metric") not in {v.value for v in PopularityMetric}
|
|
1128
|
+
or type(p.get("rank")) is not int
|
|
1129
|
+
or p["rank"] < 1
|
|
1130
|
+
or type(p.get("population")) is not int
|
|
1131
|
+
or p["population"] < p["rank"]
|
|
1132
|
+
or not isinstance(p.get("scope"), str)
|
|
1133
|
+
or not p["scope"].strip()
|
|
1134
|
+
or not source(p.get("source"))
|
|
1135
|
+
or not _signal_date(p.get("windowStart"))
|
|
1136
|
+
or not _signal_date(p.get("windowEnd"))
|
|
1137
|
+
or _timestamp(p["windowStart"]) >= _timestamp(p["windowEnd"])
|
|
1138
|
+
):
|
|
1139
|
+
errors.append(o["id"] + ": invalid popularity evidence")
|
|
1140
|
+
return errors
|
|
1141
|
+
|
|
1142
|
+
|
|
1143
|
+
def _with_signals(o, as_of=None):
|
|
1144
|
+
import time
|
|
1145
|
+
|
|
1146
|
+
now = _timestamp(as_of) if as_of else time.time()
|
|
1147
|
+
|
|
1148
|
+
def fresh(date, days):
|
|
1149
|
+
return _signal_date(date) and 0 <= now - _timestamp(date) <= days * 86400
|
|
1150
|
+
|
|
1151
|
+
l = o.get("lifecycleEvidence")
|
|
1152
|
+
p = o.get("popularityEvidence")
|
|
1153
|
+
lifecycle = {
|
|
1154
|
+
"status": ("OUTDATED" if o.get("status") == "deprecated" else l["status"])
|
|
1155
|
+
if l and fresh(l["checkedAt"], 90)
|
|
1156
|
+
else "UNCERTAIN",
|
|
1157
|
+
"evidence": l,
|
|
1158
|
+
}
|
|
1159
|
+
popularity = {
|
|
1160
|
+
"status": (
|
|
1161
|
+
"POPULAR"
|
|
1162
|
+
if p["rank"] <= math.ceil(p["population"] * 0.1)
|
|
1163
|
+
else "NOT_POPULAR"
|
|
1164
|
+
)
|
|
1165
|
+
if p and fresh(p["windowEnd"], 30)
|
|
1166
|
+
else "UNCERTAIN",
|
|
1167
|
+
"evidence": p,
|
|
1168
|
+
}
|
|
1169
|
+
tags = [
|
|
1170
|
+
t for t in o.get("tags", []) if not t.startswith(("lifecycle:", "popularity:"))
|
|
1171
|
+
]
|
|
1172
|
+
return dict(
|
|
1173
|
+
o,
|
|
1174
|
+
lifecycle=lifecycle,
|
|
1175
|
+
popularity=popularity,
|
|
1176
|
+
tags=tags
|
|
1177
|
+
+ ["lifecycle:" + lifecycle["status"], "popularity:" + popularity["status"]],
|
|
1178
|
+
)
|
|
1179
|
+
|
|
1180
|
+
|
|
1181
|
+
def _normalize_query(input=None, options=None):
|
|
1182
|
+
if isinstance(input, str):
|
|
1183
|
+
if input not in {v.value for v in ModelList}:
|
|
1184
|
+
raise ValueError("Unknown model list")
|
|
1185
|
+
config = dict(options or {}, list=input)
|
|
1186
|
+
else:
|
|
1187
|
+
if input is not None and not isinstance(input, dict) or options is not None:
|
|
1188
|
+
raise ValueError("Use get_models(options) or get_models(list, options)")
|
|
1189
|
+
config = dict(input or {})
|
|
1190
|
+
if not any(k in config for k in ["list", "preset", "providerIds"]):
|
|
1191
|
+
config["list"] = ModelList.DEFAULT
|
|
1192
|
+
if config.get("list") == ModelList.CHINA:
|
|
1193
|
+
config["preset"] = "china"
|
|
1194
|
+
if config.get("list") == ModelList.GLOBAL:
|
|
1195
|
+
config["preset"] = "global"
|
|
1196
|
+
return config
|
|
1197
|
+
|
|
1198
|
+
|
|
1199
|
+
def _validate_local(entry):
|
|
1200
|
+
from urllib.parse import urlparse
|
|
1201
|
+
|
|
1202
|
+
if not isinstance(entry, dict) or set(entry) - {
|
|
1203
|
+
"endpoint",
|
|
1204
|
+
"api",
|
|
1205
|
+
"modelId",
|
|
1206
|
+
"name",
|
|
1207
|
+
}:
|
|
1208
|
+
raise ValueError("Invalid local entry")
|
|
1209
|
+
try:
|
|
1210
|
+
url = urlparse(entry.get("endpoint", ""))
|
|
1211
|
+
if (
|
|
1212
|
+
not isinstance(entry.get("endpoint"), str)
|
|
1213
|
+
or url.scheme not in ["http", "https"]
|
|
1214
|
+
or not url.netloc
|
|
1215
|
+
or url.username
|
|
1216
|
+
or url.password
|
|
1217
|
+
or url.query
|
|
1218
|
+
or url.fragment
|
|
1219
|
+
):
|
|
1220
|
+
raise ValueError(
|
|
1221
|
+
"Use a local HTTP(S) URL without credentials, query, or fragment"
|
|
1222
|
+
)
|
|
1223
|
+
except (ValueError, TypeError):
|
|
1224
|
+
raise ValueError("Local entry needs an HTTP(S) endpoint")
|
|
1225
|
+
if "api" in entry and entry["api"] not in {v.value for v in LocalApi}:
|
|
1226
|
+
raise ValueError("Unknown local API")
|
|
1227
|
+
for key in ["modelId", "name"]:
|
|
1228
|
+
if key in entry and (not isinstance(entry[key], str) or not entry[key].strip()):
|
|
1229
|
+
raise ValueError("Invalid local " + key)
|
|
1230
|
+
|
|
1231
|
+
|
|
1232
|
+
def _local_groups(config, catalog):
|
|
1233
|
+
if (
|
|
1234
|
+
config.get("local") is False
|
|
1235
|
+
or "deployment" in config.get("filters", {})
|
|
1236
|
+
and "local" not in config["filters"]["deployment"]
|
|
1237
|
+
):
|
|
1238
|
+
return []
|
|
1239
|
+
preset = next(
|
|
1240
|
+
(p for p in catalog.data["presets"] if p["id"] == config.get("preset")), None
|
|
1241
|
+
)
|
|
1242
|
+
ids = config.get("providerIds", preset["providerIds"] if preset else None)
|
|
1243
|
+
if (
|
|
1244
|
+
"local" not in config
|
|
1245
|
+
and ids is not None
|
|
1246
|
+
and not any(catalog.providers[id]["deployment"] == "local" for id in ids)
|
|
1247
|
+
):
|
|
1248
|
+
return []
|
|
1249
|
+
entries = config.get("local", [{}])
|
|
1250
|
+
if not entries:
|
|
1251
|
+
return []
|
|
1252
|
+
provider = dict(
|
|
1253
|
+
catalog.providers.get(
|
|
1254
|
+
"local",
|
|
1255
|
+
{"id": "local", "auth": "optional", "deployment": "local", "tags": []},
|
|
1256
|
+
),
|
|
1257
|
+
name="Local",
|
|
1258
|
+
)
|
|
1259
|
+
offerings = []
|
|
1260
|
+
for i, entry in enumerate(entries):
|
|
1261
|
+
offerings.append(
|
|
1262
|
+
{
|
|
1263
|
+
"id": "local/custom"
|
|
1264
|
+
if len(entries) == 1
|
|
1265
|
+
else "local/endpoint-" + str(i),
|
|
1266
|
+
"providerId": "local",
|
|
1267
|
+
"modelId": entry.get("modelId", "local/custom"),
|
|
1268
|
+
"apiModelId": entry.get("modelId", ""),
|
|
1269
|
+
"name": entry.get(
|
|
1270
|
+
"name", entry.get("modelId", "Local — enter your server URL")
|
|
1271
|
+
),
|
|
1272
|
+
"deployment": "local",
|
|
1273
|
+
"local": {
|
|
1274
|
+
"endpoint": entry.get("endpoint", ""),
|
|
1275
|
+
"api": entry.get("api", "openai-compatible"),
|
|
1276
|
+
"configured": bool(entry.get("endpoint")),
|
|
1277
|
+
},
|
|
1278
|
+
"modalities": {"input": None, "output": None},
|
|
1279
|
+
"limits": {
|
|
1280
|
+
"context": None,
|
|
1281
|
+
"input": None,
|
|
1282
|
+
"output": None,
|
|
1283
|
+
"contextAccounting": "unknown",
|
|
1284
|
+
},
|
|
1285
|
+
"features": {},
|
|
1286
|
+
"price": {"currency": "USD", "input": None, "output": None},
|
|
1287
|
+
"tags": ["deployment:local"],
|
|
1288
|
+
"lifecycle": {"status": "UNCERTAIN", "evidence": None},
|
|
1289
|
+
"popularity": {"status": "UNCERTAIN", "evidence": None},
|
|
1290
|
+
}
|
|
1291
|
+
)
|
|
1292
|
+
return [{"provider": provider, "offerings": offerings}]
|
|
1293
|
+
|
|
1294
|
+
|
|
1295
|
+
from .inference import call, call_claude, prepare_call, connection_for, CallError
|
|
1296
|
+
|
|
1297
|
+
from .sdk import Byokit, OpenAI, Anthropic, openAI, claude
|
|
1298
|
+
|
|
1299
|
+
from .custom_model import append_model, validate_connection
|