roadmodel 0.2.76__tar.gz → 0.2.77__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.
Files changed (57) hide show
  1. {roadmodel-0.2.76 → roadmodel-0.2.77}/PKG-INFO +1 -1
  2. {roadmodel-0.2.76 → roadmodel-0.2.77}/pyproject.toml +1 -1
  3. {roadmodel-0.2.76 → roadmodel-0.2.77}/src/roadmodel/__init__.py +1 -1
  4. {roadmodel-0.2.76 → roadmodel-0.2.77}/src/roadmodel/cost.py +5 -2
  5. {roadmodel-0.2.76 → roadmodel-0.2.77}/src/roadmodel/recommend.py +24 -1
  6. {roadmodel-0.2.76 → roadmodel-0.2.77}/src/roadmodel/scoring.py +1 -1
  7. {roadmodel-0.2.76 → roadmodel-0.2.77}/.gitignore +0 -0
  8. {roadmodel-0.2.76 → roadmodel-0.2.77}/LICENSE +0 -0
  9. {roadmodel-0.2.76 → roadmodel-0.2.77}/NOTICE +0 -0
  10. {roadmodel-0.2.76 → roadmodel-0.2.77}/README.md +0 -0
  11. {roadmodel-0.2.76 → roadmodel-0.2.77}/docs/benchmarks.json +0 -0
  12. {roadmodel-0.2.76 → roadmodel-0.2.77}/docs/catalog.json +0 -0
  13. {roadmodel-0.2.76 → roadmodel-0.2.77}/docs/context-windows.json +0 -0
  14. {roadmodel-0.2.76 → roadmodel-0.2.77}/docs/model-selector.txt +0 -0
  15. {roadmodel-0.2.76 → roadmodel-0.2.77}/docs/model-tier-cost-scale.md +0 -0
  16. {roadmodel-0.2.76 → roadmodel-0.2.77}/docs/settings-display.md +0 -0
  17. {roadmodel-0.2.76 → roadmodel-0.2.77}/docs/templates/phase-roadmap-template.md +0 -0
  18. {roadmodel-0.2.76 → roadmodel-0.2.77}/docs/templates/planning-kit-how-to-use.md +0 -0
  19. {roadmodel-0.2.76 → roadmodel-0.2.77}/docs/templates/project-roadmap-template.md +0 -0
  20. {roadmodel-0.2.76 → roadmodel-0.2.77}/docs/templates/prompt-phase-roadmap.md +0 -0
  21. {roadmodel-0.2.76 → roadmodel-0.2.77}/docs/templates/prompt-project-roadmap.md +0 -0
  22. {roadmodel-0.2.76 → roadmodel-0.2.77}/docs/user-context.example.md +0 -0
  23. {roadmodel-0.2.76 → roadmodel-0.2.77}/hatch_build.py +0 -0
  24. {roadmodel-0.2.76 → roadmodel-0.2.77}/infra/README.md +0 -0
  25. {roadmodel-0.2.76 → roadmodel-0.2.77}/infra/gcp-killswitch/README.md +0 -0
  26. {roadmodel-0.2.76 → roadmodel-0.2.77}/infra/supabase/README.md +0 -0
  27. {roadmodel-0.2.76 → roadmodel-0.2.77}/roadmodel/data/benchmarks.json +0 -0
  28. {roadmodel-0.2.76 → roadmodel-0.2.77}/roadmodel/data/catalog.json +0 -0
  29. {roadmodel-0.2.76 → roadmodel-0.2.77}/roadmodel/data/context-windows.json +0 -0
  30. {roadmodel-0.2.76 → roadmodel-0.2.77}/roadmodel/data/model-selector.txt +0 -0
  31. {roadmodel-0.2.76 → roadmodel-0.2.77}/roadmodel/data/model-tier-cost-scale.md +0 -0
  32. {roadmodel-0.2.76 → roadmodel-0.2.77}/roadmodel/data/phase-roadmap-template.md +0 -0
  33. {roadmodel-0.2.76 → roadmodel-0.2.77}/roadmodel/data/planning-kit-how-to-use.md +0 -0
  34. {roadmodel-0.2.76 → roadmodel-0.2.77}/roadmodel/data/project-roadmap-template.md +0 -0
  35. {roadmodel-0.2.76 → roadmodel-0.2.77}/roadmodel/data/prompt-phase-roadmap.md +0 -0
  36. {roadmodel-0.2.76 → roadmodel-0.2.77}/roadmodel/data/prompt-project-roadmap.md +0 -0
  37. {roadmodel-0.2.76 → roadmodel-0.2.77}/roadmodel/data/settings-display.md +0 -0
  38. {roadmodel-0.2.76 → roadmodel-0.2.77}/roadmodel/data/user-context.example.md +0 -0
  39. {roadmodel-0.2.76 → roadmodel-0.2.77}/scripts/agent_context.py +0 -0
  40. {roadmodel-0.2.76 → roadmodel-0.2.77}/scripts/agent_rules.py +0 -0
  41. {roadmodel-0.2.76 → roadmodel-0.2.77}/scripts/update_projects.py +0 -0
  42. {roadmodel-0.2.76 → roadmodel-0.2.77}/scripts/updater_lock.py +0 -0
  43. {roadmodel-0.2.76 → roadmodel-0.2.77}/scripts/upgrade_fleet.py +0 -0
  44. {roadmodel-0.2.76 → roadmodel-0.2.77}/scripts/worktree_memory.py +0 -0
  45. {roadmodel-0.2.76 → roadmodel-0.2.77}/src/roadmodel/__main__.py +0 -0
  46. {roadmodel-0.2.76 → roadmodel-0.2.77}/src/roadmodel/cli.py +0 -0
  47. {roadmodel-0.2.76 → roadmodel-0.2.77}/src/roadmodel/config.py +0 -0
  48. {roadmodel-0.2.76 → roadmodel-0.2.77}/src/roadmodel/errors.py +0 -0
  49. {roadmodel-0.2.76 → roadmodel-0.2.77}/src/roadmodel/mcp_server.py +0 -0
  50. {roadmodel-0.2.76 → roadmodel-0.2.77}/src/roadmodel/providers/__init__.py +0 -0
  51. {roadmodel-0.2.76 → roadmodel-0.2.77}/src/roadmodel/providers/anthropic.py +0 -0
  52. {roadmodel-0.2.76 → roadmodel-0.2.77}/src/roadmodel/providers/google.py +0 -0
  53. {roadmodel-0.2.76 → roadmodel-0.2.77}/src/roadmodel/providers/openai.py +0 -0
  54. {roadmodel-0.2.76 → roadmodel-0.2.77}/src/roadmodel/providers/openai_compatible.py +0 -0
  55. {roadmodel-0.2.76 → roadmodel-0.2.77}/src/roadmodel/providers/registry.py +0 -0
  56. {roadmodel-0.2.76 → roadmodel-0.2.77}/src/roadmodel/usage.py +0 -0
  57. {roadmodel-0.2.76 → roadmodel-0.2.77}/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.76
3
+ Version: 0.2.77
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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "roadmodel"
7
- version = "0.2.76"
7
+ version = "0.2.77"
8
8
  description = "BYO-key CLI that recommends the right AI model, platform, and settings for a prompt."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1,2 +1,2 @@
1
1
  # src/roadmodel/__init__.py
2
- __version__ = "0.2.76"
2
+ __version__ = "0.2.77"
@@ -404,7 +404,7 @@ _HOST_METHODS: frozenset[str] = frozenset({"antigravity"})
404
404
  _LOCAL_PROVIDERS: frozenset[str] = frozenset({"ollama"})
405
405
 
406
406
 
407
- def model_provider(model_ref: str) -> str | None:
407
+ def model_provider(model_ref: str, catalog: dict[str, Any] | None = None) -> str | None:
408
408
  """Resolve a model id-or-name to its MAKER (the company that produces it):
409
409
  ``anthropic`` / ``openai`` / ``google`` / ``xai`` / ``deepseek`` / ``mistral``
410
410
  / ``zai`` / ``groq`` / ``cursor`` …
@@ -419,7 +419,10 @@ def model_provider(model_ref: str) -> str | None:
419
419
  rather than dropping a valid backup.
420
420
  """
421
421
  try:
422
- catalog = _load_catalog()
422
+ # A caller that already holds the catalog passes it: the scorer asks
423
+ # once per candidate, and re-parsing the JSON each time was most of a
424
+ # ladder table's cost (Phase 4.5, 2026-10-08).
425
+ catalog = catalog if catalog is not None else _load_catalog()
423
426
  model_id = str(_resolve_model(model_ref, catalog)["id"])
424
427
  except (ValueError, BundledDocNotFoundError, KeyError, TypeError):
425
428
  return None
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
 
4
4
  import contextvars
5
5
  import json
6
+ import os
6
7
  import re
7
8
  from collections import Counter
8
9
  from concurrent.futures import ThreadPoolExecutor
@@ -1553,15 +1554,37 @@ def _enforce_table_effort(ladder: dict[str, dict[str, str]], row: scoring.Ladder
1553
1554
  return adjusted
1554
1555
 
1555
1556
 
1557
+ # Ladder tables already computed in this process, by user-context text, the
1558
+ # unavailable models and any catalog override. The catalog and benchmarks are
1559
+ # bundled, so one context always yields the same table; rebuilding it (28 rows,
1560
+ # each ranking the whole pool) cost the hosted service about a second a request
1561
+ # for a well-funded user (Phase 4.5 soak, 2026-10-08: B9 latency). Rows are
1562
+ # read, never mutated. Oldest entry out first past _TABLE_CACHE_MAX.
1563
+ _TABLE_CACHE: dict[tuple[str, tuple[str, ...], str], dict[str, scoring.Ladder]] = {}
1564
+ _TABLE_CACHE_MAX: Final = 64
1565
+
1566
+
1556
1567
  def _ladder_table_for(
1557
1568
  user_context_text: str, unavailable_models: list[str] | None
1558
1569
  ) -> dict[str, scoring.Ladder]:
1559
1570
  """The scorer's ladder table for this user-context, or an empty one if it
1560
1571
  cannot be computed: a scoring failure costs the table, never the call."""
1572
+ key = (
1573
+ user_context_text,
1574
+ tuple(sorted(unavailable_models or [])),
1575
+ os.environ.get("ROADMODEL_CATALOG_PATH", ""),
1576
+ )
1577
+ cached = _TABLE_CACHE.get(key)
1578
+ if cached is not None:
1579
+ return cached
1561
1580
  try:
1562
- return scoring.ladder_table(user_context_text, unavailable_models=unavailable_models)
1581
+ table = scoring.ladder_table(user_context_text, unavailable_models=unavailable_models)
1563
1582
  except Exception: # noqa: BLE001 - fail open to the free-choice ladder
1564
1583
  return {}
1584
+ if len(_TABLE_CACHE) >= _TABLE_CACHE_MAX:
1585
+ _TABLE_CACHE.pop(next(iter(_TABLE_CACHE)), None)
1586
+ _TABLE_CACHE[key] = table
1587
+ return table
1565
1588
 
1566
1589
 
1567
1590
  def _fit_row_to_input(
@@ -1034,7 +1034,7 @@ def rank(
1034
1034
  if str(model.get("jurisdiction", "")).lower() not in juris:
1035
1035
  excluded.append({"model": model_id, "reason": "jurisdiction"})
1036
1036
  continue
1037
- provider = _cost.model_provider(model_id) or str(model.get("provider", "unknown"))
1037
+ provider = _cost.model_provider(model_id, cat) or str(model.get("provider", "unknown"))
1038
1038
  quality, source, letter = _quality(model, task, bench, scale)
1039
1039
  points = _evidence_points(model_id, task, bench, scale)
1040
1040
  shortfall = max(0.0, requirement - quality)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes