roadmodel 0.2.72__tar.gz → 0.2.74__tar.gz
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.
- {roadmodel-0.2.72 → roadmodel-0.2.74}/PKG-INFO +1 -1
- {roadmodel-0.2.72/roadmodel/data → roadmodel-0.2.74/docs}/catalog.json +1 -1
- {roadmodel-0.2.72 → roadmodel-0.2.74}/pyproject.toml +1 -1
- {roadmodel-0.2.72/docs → roadmodel-0.2.74/roadmodel/data}/catalog.json +1 -1
- {roadmodel-0.2.72 → roadmodel-0.2.74}/src/roadmodel/__init__.py +1 -1
- {roadmodel-0.2.72 → roadmodel-0.2.74}/src/roadmodel/recommend.py +13 -3
- {roadmodel-0.2.72 → roadmodel-0.2.74}/src/roadmodel/scoring.py +58 -7
- {roadmodel-0.2.72 → roadmodel-0.2.74}/.gitignore +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/LICENSE +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/NOTICE +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/README.md +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/docs/benchmarks.json +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/docs/context-windows.json +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/docs/model-selector.txt +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/docs/model-tier-cost-scale.md +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/docs/settings-display.md +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/docs/templates/phase-roadmap-template.md +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/docs/templates/planning-kit-how-to-use.md +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/docs/templates/project-roadmap-template.md +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/docs/templates/prompt-phase-roadmap.md +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/docs/templates/prompt-project-roadmap.md +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/docs/user-context.example.md +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/hatch_build.py +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/infra/README.md +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/infra/gcp-killswitch/README.md +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/infra/supabase/README.md +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/roadmodel/data/benchmarks.json +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/roadmodel/data/context-windows.json +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/roadmodel/data/model-selector.txt +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/roadmodel/data/model-tier-cost-scale.md +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/roadmodel/data/phase-roadmap-template.md +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/roadmodel/data/planning-kit-how-to-use.md +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/roadmodel/data/project-roadmap-template.md +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/roadmodel/data/prompt-phase-roadmap.md +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/roadmodel/data/prompt-project-roadmap.md +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/roadmodel/data/settings-display.md +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/roadmodel/data/user-context.example.md +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/scripts/agent_context.py +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/scripts/agent_rules.py +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/scripts/update_projects.py +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/scripts/updater_lock.py +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/scripts/upgrade_fleet.py +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/scripts/worktree_memory.py +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/src/roadmodel/__main__.py +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/src/roadmodel/cli.py +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/src/roadmodel/config.py +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/src/roadmodel/cost.py +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/src/roadmodel/errors.py +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/src/roadmodel/mcp_server.py +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/src/roadmodel/providers/__init__.py +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/src/roadmodel/providers/anthropic.py +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/src/roadmodel/providers/google.py +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/src/roadmodel/providers/openai.py +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/src/roadmodel/providers/openai_compatible.py +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/src/roadmodel/providers/registry.py +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/src/roadmodel/usage.py +0 -0
- {roadmodel-0.2.72 → roadmodel-0.2.74}/src/roadmodel/user_context.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: roadmodel
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.74
|
|
4
4
|
Summary: BYO-key CLI that recommends the right AI model, platform, and settings for a prompt.
|
|
5
5
|
Project-URL: Homepage, https://roadmodel.ai
|
|
6
6
|
Project-URL: Repository, https://github.com/nathanramoscfa/roadmodel
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": "4",
|
|
3
3
|
"output_contract_version": 2,
|
|
4
|
-
"generated_at_utc": "2026-10-
|
|
4
|
+
"generated_at_utc": "2026-10-07T20:14:28Z",
|
|
5
5
|
"source_doc_sha256": {
|
|
6
6
|
"model-selector.txt": "6a75e2265459fa7302cb80d0faf5ec2aad575034fc29866238cd1579c001cc2d",
|
|
7
7
|
"model-tier-cost-scale.md": "2b3b0eb82237eb5cb5bf468a3fe971bcaf0160ad7d0bc700698d2c16640bc41d"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": "4",
|
|
3
3
|
"output_contract_version": 2,
|
|
4
|
-
"generated_at_utc": "2026-10-
|
|
4
|
+
"generated_at_utc": "2026-10-07T20:14:28Z",
|
|
5
5
|
"source_doc_sha256": {
|
|
6
6
|
"model-selector.txt": "6a75e2265459fa7302cb80d0faf5ec2aad575034fc29866238cd1579c001cc2d",
|
|
7
7
|
"model-tier-cost-scale.md": "2b3b0eb82237eb5cb5bf468a3fe971bcaf0160ad7d0bc700698d2c16640bc41d"
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# src/roadmodel/__init__.py
|
|
2
|
-
__version__ = "0.2.
|
|
2
|
+
__version__ = "0.2.74"
|
|
@@ -479,7 +479,11 @@ _SAAS_LADDER_TABLE_HEADER: Final = (
|
|
|
479
479
|
"1. One line `CLASSIFICATION: <category> / <complexity> / <novel|routine>`. "
|
|
480
480
|
"<category> is the task's PRIMARY category (coding, planning, agentic, multimodal, "
|
|
481
481
|
"long-context, knowledge or speed) per Step 1 of the selection algorithm; "
|
|
482
|
-
"<complexity> is low, medium or high per its Step 2.
|
|
482
|
+
"<complexity> is low, medium or high per its Step 2. High-volume routine work "
|
|
483
|
+
"(classifying, tagging, extracting or summarizing many similar items) is `speed` "
|
|
484
|
+
"whatever accuracy it needs, and its complexity is how hard ONE item is. Building an "
|
|
485
|
+
"agent or an app (writing its code) is `coding`; running one, where the model itself "
|
|
486
|
+
"acts through tools in a loop, is `agentic`. `novel` marks "
|
|
483
487
|
"research-grade work only: an open problem, a new algorithm, or a proof of a "
|
|
484
488
|
"result with no known proof. Writing out a known result (a textbook proof, a "
|
|
485
489
|
"standard derivation) is `routine` however many steps it takes, and its "
|
|
@@ -1487,12 +1491,18 @@ def _fit_row_to_input(
|
|
|
1487
1491
|
"""The row with every model whose context window cannot hold the task's
|
|
1488
1492
|
material left out (scoring.too_small_for), and what the check found. The
|
|
1489
1493
|
table is computed before the engine reads the task, so the row is
|
|
1490
|
-
recomputed here when a model its frontier draws on is too small
|
|
1491
|
-
|
|
1494
|
+
recomputed here when a model its frontier draws on is too small. When the
|
|
1495
|
+
material is larger than every known window, or none left fits, the row
|
|
1496
|
+
stands and ``fits`` says so."""
|
|
1492
1497
|
small = set(scoring.too_small_for(input_tokens))
|
|
1493
1498
|
drawn = {p.candidate.model_id for p in [*row.frontier, *row.points]}
|
|
1494
1499
|
excluded = sorted(small & drawn)
|
|
1495
1500
|
report: dict[str, Any] = {"input_tokens": input_tokens, "excluded": excluded, "fits": True}
|
|
1501
|
+
if scoring.nothing_fits(input_tokens):
|
|
1502
|
+
# Larger than every known window: no pick holds it, and one whose
|
|
1503
|
+
# window is unknown would stand only by default.
|
|
1504
|
+
report["fits"] = False
|
|
1505
|
+
return row, report
|
|
1496
1506
|
if not excluded:
|
|
1497
1507
|
return row, report
|
|
1498
1508
|
refit = scoring.ladder(
|
|
@@ -330,6 +330,14 @@ def context_windows() -> dict[str, int]:
|
|
|
330
330
|
return {str(k): v for k, v in models.items() if isinstance(v, int) and v > 0}
|
|
331
331
|
|
|
332
332
|
|
|
333
|
+
def nothing_fits(input_tokens: int, windows: dict[str, int] | None = None) -> bool:
|
|
334
|
+
"""True when no model with a known context window can hold
|
|
335
|
+
``input_tokens`` of material: the material is larger than any model
|
|
336
|
+
takes, and a model whose window is unknown is no answer."""
|
|
337
|
+
known = windows if windows is not None else context_windows()
|
|
338
|
+
return bool(known) and len(too_small_for(input_tokens, known)) == len(known)
|
|
339
|
+
|
|
340
|
+
|
|
333
341
|
def too_small_for(input_tokens: int, windows: dict[str, int] | None = None) -> list[str]:
|
|
334
342
|
"""The catalog ids whose context window cannot hold ``input_tokens`` of
|
|
335
343
|
material with CONTEXT_HEADROOM to spare. A model with no known window is
|
|
@@ -953,10 +961,20 @@ def rank(
|
|
|
953
961
|
catalog: dict[str, Any] | None = None,
|
|
954
962
|
benchmarks: dict[str, Any] | None = None,
|
|
955
963
|
top: int | None = None,
|
|
964
|
+
effort_aware: bool = True,
|
|
956
965
|
) -> Ranking:
|
|
957
|
-
"""Rank every reachable (model, platform) pair for ``task``.
|
|
966
|
+
"""Rank every reachable (model, platform) pair for ``task``.
|
|
967
|
+
|
|
968
|
+
With ``effort_aware`` (the default), a model AA has measured at several
|
|
969
|
+
efforts stands at the one that scores best for the task's posture, among
|
|
970
|
+
those it may run (:func:`effort_settings`), its quality, requirement
|
|
971
|
+
penalty (in the category and in general capability) and cost read there;
|
|
972
|
+
a model measured only at efforts the task does not run is left out. Off,
|
|
973
|
+
every model stands on its headline row at its posture's effort: the pool
|
|
974
|
+
:func:`ladder` expands itself."""
|
|
958
975
|
cat = catalog if catalog is not None else _cost._load_catalog()
|
|
959
|
-
|
|
976
|
+
raw_bench = benchmarks if benchmarks is not None else _load_benchmarks()
|
|
977
|
+
bench = with_composites(raw_bench)
|
|
960
978
|
text = user_context_text or ""
|
|
961
979
|
headroom = consumption_headroom(text)
|
|
962
980
|
pools = pool_states(text)
|
|
@@ -1092,6 +1110,28 @@ def rank(
|
|
|
1092
1110
|
continue
|
|
1093
1111
|
candidates.append(best_for_model)
|
|
1094
1112
|
|
|
1113
|
+
if effort_aware and candidates:
|
|
1114
|
+
settings = effort_settings(
|
|
1115
|
+
candidates, task, headroom, lam=lam, k=k, catalog=cat, benchmarks=raw_bench
|
|
1116
|
+
)
|
|
1117
|
+
best: dict[str, Candidate] = {}
|
|
1118
|
+
for c in settings:
|
|
1119
|
+
held = best.get(c.model_id)
|
|
1120
|
+
if held is None or (c.score, -c.effective_cost_usd) > (
|
|
1121
|
+
held.score,
|
|
1122
|
+
-held.effective_cost_usd,
|
|
1123
|
+
):
|
|
1124
|
+
best[c.model_id] = c
|
|
1125
|
+
for c in candidates:
|
|
1126
|
+
if c.model_id not in best:
|
|
1127
|
+
excluded.append(
|
|
1128
|
+
{
|
|
1129
|
+
"model": c.model_id,
|
|
1130
|
+
"reason": "measured only at efforts this task does not run",
|
|
1131
|
+
}
|
|
1132
|
+
)
|
|
1133
|
+
candidates = [best[c.model_id] for c in candidates if c.model_id in best]
|
|
1134
|
+
|
|
1095
1135
|
# Funded first, then score, then cheaper, then id — deterministic.
|
|
1096
1136
|
candidates.sort(
|
|
1097
1137
|
key=lambda c: (c.funding == "unfunded", -c.score, c.effective_cost_usd, c.model_id)
|
|
@@ -1396,7 +1436,8 @@ def _at_level(
|
|
|
1396
1436
|
task: Task,
|
|
1397
1437
|
bench: dict[str, Any],
|
|
1398
1438
|
scales: tuple[EvidenceScale | None, EvidenceScale | None],
|
|
1399
|
-
|
|
1439
|
+
lam: float,
|
|
1440
|
+
k: float,
|
|
1400
1441
|
) -> Candidate:
|
|
1401
1442
|
"""``c`` run at ``level`` (None: its headline row, at the posture's
|
|
1402
1443
|
effort): quality from AA's row at that effort, and the requirement
|
|
@@ -1426,7 +1467,7 @@ def _at_level(
|
|
|
1426
1467
|
mult = EFFORT_TOKEN_MULTIPLIER[level]
|
|
1427
1468
|
eff_cost = c.blended_price_usd * c.scarcity * mult
|
|
1428
1469
|
decades = math.log10(max(eff_cost, COST_FLOOR_USD) / COST_FLOOR_USD)
|
|
1429
|
-
cost_pen =
|
|
1470
|
+
cost_pen = lam * k * decades
|
|
1430
1471
|
nudge = 0.5 if task.category in AGENT_CATEGORIES and c.platform_id in AGENT_SURFACES else 0.0
|
|
1431
1472
|
return replace(
|
|
1432
1473
|
c,
|
|
@@ -1449,8 +1490,9 @@ def effort_settings(
|
|
|
1449
1490
|
pool: list[Candidate],
|
|
1450
1491
|
task: Task,
|
|
1451
1492
|
headroom: str,
|
|
1452
|
-
ranking: Ranking,
|
|
1453
1493
|
*,
|
|
1494
|
+
lam: float,
|
|
1495
|
+
k: float,
|
|
1454
1496
|
catalog: dict[str, Any],
|
|
1455
1497
|
benchmarks: dict[str, Any],
|
|
1456
1498
|
) -> list[Candidate]:
|
|
@@ -1485,7 +1527,7 @@ def effort_settings(
|
|
|
1485
1527
|
if headroom == "uncapped" and c.scarcity == 0:
|
|
1486
1528
|
levels = levels[-1:] # a free path runs at its top effort
|
|
1487
1529
|
out.extend(
|
|
1488
|
-
_at_level(c, lv, model, task, bench, scales,
|
|
1530
|
+
_at_level(c, lv, model, task, bench, scales, lam, k) for lv in (levels or [None])
|
|
1489
1531
|
)
|
|
1490
1532
|
return out
|
|
1491
1533
|
|
|
@@ -1550,6 +1592,7 @@ def ladder(
|
|
|
1550
1592
|
unavailable_models=unavailable_models,
|
|
1551
1593
|
catalog=cat,
|
|
1552
1594
|
benchmarks=bench,
|
|
1595
|
+
effort_aware=False,
|
|
1553
1596
|
)
|
|
1554
1597
|
funded = [c for c in ranking.candidates if c.funding != "unfunded"]
|
|
1555
1598
|
pool = funded or ranking.candidates
|
|
@@ -1559,7 +1602,15 @@ def ladder(
|
|
|
1559
1602
|
headroom = consumption_headroom(user_context_text or "")
|
|
1560
1603
|
# The picks are read off the same frontier drawn over each model at every
|
|
1561
1604
|
# effort AA measured it at, so an effort is part of the pick it prices.
|
|
1562
|
-
settings = effort_settings(
|
|
1605
|
+
settings = effort_settings(
|
|
1606
|
+
pool,
|
|
1607
|
+
base,
|
|
1608
|
+
headroom,
|
|
1609
|
+
lam=ranking.lam,
|
|
1610
|
+
k=ranking.k_points_per_decade,
|
|
1611
|
+
catalog=cat,
|
|
1612
|
+
benchmarks=bench,
|
|
1613
|
+
)
|
|
1563
1614
|
# Every model AA measured only at efforts this task does not run leaves
|
|
1564
1615
|
# none: the picks then fall back to the models' frontier.
|
|
1565
1616
|
points = frontier(settings, bench) or front
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|