roadmodel 0.2.3__tar.gz → 0.2.4__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.3 → roadmodel-0.2.4}/PKG-INFO +1 -1
- {roadmodel-0.2.3 → roadmodel-0.2.4}/pyproject.toml +1 -1
- {roadmodel-0.2.3 → roadmodel-0.2.4}/src/roadmodel/__init__.py +1 -1
- {roadmodel-0.2.3 → roadmodel-0.2.4}/src/roadmodel/cost.py +25 -0
- {roadmodel-0.2.3 → roadmodel-0.2.4}/src/roadmodel/providers/__init__.py +1 -0
- {roadmodel-0.2.3 → roadmodel-0.2.4}/src/roadmodel/providers/anthropic.py +9 -8
- {roadmodel-0.2.3 → roadmodel-0.2.4}/src/roadmodel/providers/google.py +7 -0
- {roadmodel-0.2.3 → roadmodel-0.2.4}/src/roadmodel/providers/openai.py +8 -6
- {roadmodel-0.2.3 → roadmodel-0.2.4}/src/roadmodel/recommend.py +20 -1
- {roadmodel-0.2.3 → roadmodel-0.2.4}/.gitignore +0 -0
- {roadmodel-0.2.3 → roadmodel-0.2.4}/LICENSE +0 -0
- {roadmodel-0.2.3 → roadmodel-0.2.4}/NOTICE +0 -0
- {roadmodel-0.2.3 → roadmodel-0.2.4}/README.md +0 -0
- {roadmodel-0.2.3 → roadmodel-0.2.4}/docs/catalog.json +0 -0
- {roadmodel-0.2.3 → roadmodel-0.2.4}/docs/model-selector.txt +0 -0
- {roadmodel-0.2.3 → roadmodel-0.2.4}/docs/model-tier-cost-scale.md +0 -0
- {roadmodel-0.2.3 → roadmodel-0.2.4}/docs/templates/phase-roadmap-template.md +0 -0
- {roadmodel-0.2.3 → roadmodel-0.2.4}/docs/user-context.example.md +0 -0
- {roadmodel-0.2.3 → roadmodel-0.2.4}/hatch_build.py +0 -0
- {roadmodel-0.2.3 → roadmodel-0.2.4}/infra/README.md +0 -0
- {roadmodel-0.2.3 → roadmodel-0.2.4}/infra/supabase/README.md +0 -0
- {roadmodel-0.2.3 → roadmodel-0.2.4}/roadmodel/data/catalog.json +0 -0
- {roadmodel-0.2.3 → roadmodel-0.2.4}/roadmodel/data/model-selector.txt +0 -0
- {roadmodel-0.2.3 → roadmodel-0.2.4}/roadmodel/data/model-tier-cost-scale.md +0 -0
- {roadmodel-0.2.3 → roadmodel-0.2.4}/roadmodel/data/phase-roadmap-template.md +0 -0
- {roadmodel-0.2.3 → roadmodel-0.2.4}/roadmodel/data/user-context.example.md +0 -0
- {roadmodel-0.2.3 → roadmodel-0.2.4}/src/roadmodel/__main__.py +0 -0
- {roadmodel-0.2.3 → roadmodel-0.2.4}/src/roadmodel/cli.py +0 -0
- {roadmodel-0.2.3 → roadmodel-0.2.4}/src/roadmodel/config.py +0 -0
- {roadmodel-0.2.3 → roadmodel-0.2.4}/src/roadmodel/errors.py +0 -0
- {roadmodel-0.2.3 → roadmodel-0.2.4}/src/roadmodel/mcp_server.py +0 -0
- {roadmodel-0.2.3 → roadmodel-0.2.4}/src/roadmodel/user_context.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: roadmodel
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
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,2 +1,2 @@
|
|
|
1
1
|
# src/roadmodel/__init__.py
|
|
2
|
-
__version__ = "0.2.
|
|
2
|
+
__version__ = "0.2.4"
|
|
@@ -258,6 +258,31 @@ def _resolve_method(platform_id: str, catalog: dict[str, Any]) -> dict[str, Any]
|
|
|
258
258
|
)
|
|
259
259
|
|
|
260
260
|
|
|
261
|
+
def canonical_model_name(model_ref: str) -> str:
|
|
262
|
+
"""Resolve a model id-or-name to its catalog display ``name``; return the
|
|
263
|
+
input unchanged on any catalog miss (never raises).
|
|
264
|
+
|
|
265
|
+
The recommender LLM emits the model freely as either the catalog id/slug
|
|
266
|
+
or the display name, which made the response header (raw) disagree with the
|
|
267
|
+
cost/comparison table (catalog name) and risked silently dropping the cost
|
|
268
|
+
panel on an unrecognized label (#174). Callers canonicalize once so every
|
|
269
|
+
downstream consumer references one consistent name.
|
|
270
|
+
"""
|
|
271
|
+
try:
|
|
272
|
+
return str(_resolve_model(model_ref, _load_catalog())["name"])
|
|
273
|
+
except (ValueError, BundledDocNotFoundError):
|
|
274
|
+
return model_ref
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def canonical_platform_name(platform_ref: str) -> str:
|
|
278
|
+
"""Resolve an access-method id-or-name to its catalog display ``name``;
|
|
279
|
+
return the input unchanged on any catalog miss (never raises) (#174)."""
|
|
280
|
+
try:
|
|
281
|
+
return str(_resolve_method(platform_ref, _load_catalog())["name"])
|
|
282
|
+
except (ValueError, BundledDocNotFoundError):
|
|
283
|
+
return platform_ref
|
|
284
|
+
|
|
285
|
+
|
|
261
286
|
def _as_dict(value: object) -> dict[str, Any]:
|
|
262
287
|
if not isinstance(value, dict):
|
|
263
288
|
raise BundledDocNotFoundError("catalog.json")
|
|
@@ -14,15 +14,16 @@ def recommend(
|
|
|
14
14
|
api_key: str,
|
|
15
15
|
max_output_tokens: int | None = None,
|
|
16
16
|
thinking_budget: int | None = None,
|
|
17
|
+
temperature: float | None = None,
|
|
17
18
|
) -> str:
|
|
18
|
-
# thinking_budget
|
|
19
|
-
# intentionally NOT forwarded:
|
|
20
|
-
# recommender latency work (
|
|
21
|
-
# different semantics (a `thinking` block with its own
|
|
22
|
-
# minimums) and the recommender response shape does not
|
|
23
|
-
# caps on Anthropic at all (PR #128). Anthropic reasoning
|
|
24
|
-
# Phase 5 paid-frontier scope.
|
|
25
|
-
_ = thinking_budget
|
|
19
|
+
# thinking_budget and temperature are accepted for ProviderAdapter Protocol
|
|
20
|
+
# parity but intentionally NOT forwarded: both are Gemini-specific knobs for
|
|
21
|
+
# the recommender latency/determinism work (issues #132, #176). Anthropic
|
|
22
|
+
# extended-thinking has different semantics (a `thinking` block with its own
|
|
23
|
+
# budget_tokens and minimums) and the recommender response shape does not
|
|
24
|
+
# tolerate small caps on Anthropic at all (PR #128). Anthropic reasoning
|
|
25
|
+
# control is Phase 5 paid-frontier scope.
|
|
26
|
+
_ = (thinking_budget, temperature)
|
|
26
27
|
try:
|
|
27
28
|
from anthropic import Anthropic, APIError
|
|
28
29
|
except Exception as exc: # pragma: no cover - dependency/runtime guard
|
|
@@ -16,6 +16,7 @@ def recommend(
|
|
|
16
16
|
api_key: str,
|
|
17
17
|
max_output_tokens: int | None = None,
|
|
18
18
|
thinking_budget: int | None = None,
|
|
19
|
+
temperature: float | None = None,
|
|
19
20
|
) -> str:
|
|
20
21
|
try:
|
|
21
22
|
from google import genai
|
|
@@ -40,6 +41,12 @@ def recommend(
|
|
|
40
41
|
# thinking entirely, a small value bounds it. `is not None` —
|
|
41
42
|
# not truthiness — because 0 is a meaningful value (thinking off).
|
|
42
43
|
config["thinking_config"] = {"thinking_budget": thinking_budget}
|
|
44
|
+
if temperature is not None:
|
|
45
|
+
# Recommender determinism (#176): without this Gemini samples at
|
|
46
|
+
# its default temperature (~1.0), so identical input yields
|
|
47
|
+
# different model picks run-to-run. `is not None` — not truthiness
|
|
48
|
+
# — because 0.0 (greedy/deterministic) is the intended value.
|
|
49
|
+
config["temperature"] = temperature
|
|
43
50
|
response = client.models.generate_content(
|
|
44
51
|
model=model or DEFAULT_MODEL,
|
|
45
52
|
contents=prompt,
|
|
@@ -32,13 +32,15 @@ def recommend(
|
|
|
32
32
|
api_key: str,
|
|
33
33
|
max_output_tokens: int | None = None,
|
|
34
34
|
thinking_budget: int | None = None,
|
|
35
|
+
temperature: float | None = None,
|
|
35
36
|
) -> str:
|
|
36
|
-
# thinking_budget
|
|
37
|
-
# intentionally NOT forwarded:
|
|
38
|
-
# recommender latency work (
|
|
39
|
-
# different mechanism (`reasoning.effort` on
|
|
40
|
-
# scope for the free-tier recommender, which runs
|
|
41
|
-
|
|
37
|
+
# thinking_budget and temperature are accepted for ProviderAdapter Protocol
|
|
38
|
+
# parity but intentionally NOT forwarded: both are Gemini-specific knobs for
|
|
39
|
+
# the recommender latency/determinism work (issues #132, #176). OpenAI
|
|
40
|
+
# reasoning control uses a different mechanism (`reasoning.effort` on
|
|
41
|
+
# reasoning models), out of scope for the free-tier recommender, which runs
|
|
42
|
+
# on Gemini Flash.
|
|
43
|
+
_ = (thinking_budget, temperature)
|
|
42
44
|
try:
|
|
43
45
|
from openai import APIError, OpenAI
|
|
44
46
|
except Exception as exc: # pragma: no cover - dependency/runtime guard
|
|
@@ -125,6 +125,7 @@ def recommend(
|
|
|
125
125
|
*,
|
|
126
126
|
max_output_tokens: int | None = None,
|
|
127
127
|
thinking_budget: int | None = None,
|
|
128
|
+
temperature: float | None = None,
|
|
128
129
|
) -> dict[str, str]:
|
|
129
130
|
user_context_text = user_context.read(config.user_context_path)
|
|
130
131
|
system_prompt, user_prompt = build_prompt(prompt, user_context_text=user_context_text)
|
|
@@ -136,6 +137,7 @@ def recommend(
|
|
|
136
137
|
api_key=config.api_key,
|
|
137
138
|
max_output_tokens=max_output_tokens,
|
|
138
139
|
thinking_budget=thinking_budget,
|
|
140
|
+
temperature=temperature,
|
|
139
141
|
)
|
|
140
142
|
return parse_response(raw_response)
|
|
141
143
|
|
|
@@ -172,11 +174,28 @@ def recommend_structured(
|
|
|
172
174
|
max_mode: bool = False,
|
|
173
175
|
max_output_tokens: int | None = None,
|
|
174
176
|
thinking_budget: int | None = None,
|
|
177
|
+
temperature: float | None = None,
|
|
175
178
|
) -> dict[str, Any]:
|
|
176
179
|
"""Return roadmap-style structured output plus optional cost estimates."""
|
|
177
180
|
base = recommend(
|
|
178
|
-
prompt,
|
|
181
|
+
prompt,
|
|
182
|
+
config,
|
|
183
|
+
max_output_tokens=max_output_tokens,
|
|
184
|
+
thinking_budget=thinking_budget,
|
|
185
|
+
temperature=temperature,
|
|
179
186
|
)
|
|
187
|
+
# Canonicalize the model + platform to their catalog display names (#174):
|
|
188
|
+
# the LLM emits either the id/slug or the display name freely, which made
|
|
189
|
+
# the response header (raw) disagree with the cost/comparison table
|
|
190
|
+
# (catalog name) and risked a silent cost-panel drop on an unrecognized
|
|
191
|
+
# label. Resolve once here so the payload, per-surface settings, and the
|
|
192
|
+
# cost calls below all agree; falls back to the raw value on a catalog
|
|
193
|
+
# miss (canonical_* never raises).
|
|
194
|
+
base = {
|
|
195
|
+
**base,
|
|
196
|
+
"model": cost.canonical_model_name(base["model"]),
|
|
197
|
+
"platform": cost.canonical_platform_name(base["platform"]),
|
|
198
|
+
}
|
|
180
199
|
payload: dict[str, Any] = {
|
|
181
200
|
"model": base["model"],
|
|
182
201
|
"platform": base["platform"],
|
|
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
|