Skip to content

Commit 8f177da

Browse files
committed
Recommend Novita's current flagship models
The models listed for Novita were older ids that no longer reflect what the platform leads with. Point the recommendations at the three current flagships instead, each verified against api.novita.ai: moonshotai/kimi-k3 1M context, native vision zai-org/glm-5.2 1M context, long-horizon agentic work deepseek/deepseek-v4-flash-0731 1M context, cheapest of the three Context windows, output limits, input modalities and pricing were taken from the live /openai/v1/models response rather than carried over.
1 parent 7f98eaf commit 8f177da

6 files changed

Lines changed: 26 additions & 26 deletions

File tree

docs/cookbook/02-models.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ for spec in (
2424
"claude-cli/claude-sonnet-5",
2525
"openrouter/anthropic/claude-haiku-4.5",
2626
"openai/gpt-4o-mini",
27-
"novita/deepseek/deepseek-r1",
27+
"novita/moonshotai/kimi-k3",
2828
"ollama/llama3.1",
2929
"mock/anything",
3030
):
@@ -39,7 +39,7 @@ print(model.invoke("say hi").content)
3939
{'spec': 'claude-cli/claude-sonnet-5', 'backend': 'claude-cli', 'model': 'claude-sonnet-5'}
4040
{'spec': 'openrouter/anthropic/claude-haiku-4.5', 'backend': 'openrouter', 'model': 'anthropic/claude-haiku-4.5'}
4141
{'spec': 'openai/gpt-4o-mini', 'backend': 'openai', 'model': 'gpt-4o-mini'}
42-
{'spec': 'novita/deepseek/deepseek-r1', 'backend': 'novita', 'model': 'deepseek/deepseek-r1'}
42+
{'spec': 'novita/moonshotai/kimi-k3', 'backend': 'novita', 'model': 'moonshotai/kimi-k3'}
4343
{'spec': 'ollama/llama3.1', 'backend': 'ollama', 'model': 'llama3.1'}
4444
{'spec': 'mock/anything', 'backend': 'mock', 'model': 'anything'}
4545
hello from a scripted model
@@ -136,7 +136,7 @@ examples:
136136
openrouter/anthropic/claude-haiku-4.5 many providers, one key
137137
openrouter/openai/gpt-4o-mini:floor cheapest provider for that model
138138
openai/gpt-4o-mini the OpenAI API directly, your key
139-
novita/deepseek/deepseek-r1 Novita's own endpoint, your key
139+
novita/moonshotai/kimi-k3 Novita's own endpoint, your key
140140
ollama/llama3.1 a local server, no key and no bill
141141

142142
grapharc models --check probes which of these this machine can use

grapharc/cli/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,7 @@ def _cmd_models(args: argparse.Namespace) -> int:
498498
"openrouter/anthropic/claude-haiku-4.5": "many providers, one key",
499499
"openrouter/openai/gpt-4o-mini:floor": "cheapest provider for that model",
500500
"openai/gpt-4o-mini": "the OpenAI API directly, your key",
501-
"novita/deepseek/deepseek-r1": "Novita's own endpoint, your key",
501+
"novita/moonshotai/kimi-k3": "Novita's own endpoint, your key",
502502
"ollama/llama3.1": "a local server, no key and no bill",
503503
}
504504
payload = {

grapharc/gateway/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
get_model("claude-cli/claude-sonnet-5") # subscription, no API key
77
get_model("openrouter/anthropic/claude-sonnet-4.5") # many providers, one key
88
get_model("openai/gpt-4o-mini") # OPENAI_API_KEY
9-
get_model("novita/deepseek/deepseek-r1") # NOVITA_API_KEY
9+
get_model("novita/moonshotai/kimi-k3") # NOVITA_API_KEY
1010
get_model("ollama/llama3.1") # local server, no key
1111
get_model("mock/x", responses=[...]) # deterministic tests
1212

grapharc/gateway/novita.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
"""Novita backend — a GPU cloud hosting open-weight models, one key.
22
3-
`novita/deepseek/deepseek-r1` reaches Novita's own OpenAI-compatible endpoint
3+
`novita/moonshotai/kimi-k3` reaches Novita's own OpenAI-compatible endpoint
44
(`https://api.novita.ai/openai`), not api.openai.com, so this builds on
55
`OpenAICompatChatModel` the same way `openrouter.py` and `ollama.py` do rather
66
than on `openai.py`: the endpoint is fixed, not an override of OpenAI's own.
77
8-
Model ids on Novita are themselves `author/slug` — `deepseek/deepseek-r1`,
9-
`qwen/qwen3-235b-a22b` — the same shape OpenRouter uses, so `vendor()` in
8+
Model ids on Novita are themselves `author/slug` — `moonshotai/kimi-k3`,
9+
`zai-org/glm-5.2` — the same shape OpenRouter uses, so `vendor()` in
1010
`registry.py` already reads the right author off a Novita spec with no
1111
backend-specific handling: `BACKEND_VENDOR` stays absent for `novita`, exactly
1212
as it is absent for `openrouter`.

grapharc/gateway/registry.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
openrouter/anthropic/claude-sonnet-4.5 -> OpenRouter
88
openrouter/openai/gpt-4o:floor -> OpenRouter, cheapest provider
99
openai/gpt-4o-mini -> OpenAI directly (OPENAI_API_KEY)
10-
novita/deepseek/deepseek-r1 -> Novita (NOVITA_API_KEY)
10+
novita/moonshotai/kimi-k3 -> Novita (NOVITA_API_KEY)
1111
ollama/llama3.1 -> a local Ollama server, no key
1212
mock/anything -> scripted test double
1313
@@ -72,7 +72,7 @@ class UnknownBackendError(Exception):
7272
_BARE_BACKEND_EXAMPLE = {
7373
"openrouter": "openrouter/anthropic/claude-sonnet-4.5",
7474
"openai": "openai/gpt-4o-mini",
75-
"novita": "novita/deepseek/deepseek-r1",
75+
"novita": "novita/moonshotai/kimi-k3",
7676
"ollama": "ollama/llama3.1",
7777
}
7878

tests/test_gateway_novita.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -56,20 +56,20 @@ def test_novita_process_env_beats_the_file(tmp_path, monkeypatch):
5656

5757
def test_constructing_novita_without_a_key_explains_how_to_fix_it(no_credentials):
5858
with pytest.raises(NovitaError, match="NOVITA_API_KEY"):
59-
NovitaChatModel("deepseek/deepseek-r1")
59+
NovitaChatModel("moonshotai/kimi-k3")
6060

6161

6262
def test_novita_key_never_appears_in_a_description_or_a_redaction(monkeypatch):
6363
secret = "sk-novita-0123456789abcdef0123456789abcdef"
6464
monkeypatch.setenv("NOVITA_API_KEY", secret)
65-
assert secret not in str(describe("novita/deepseek/deepseek-r1"))
65+
assert secret not in str(describe("novita/moonshotai/kimi-k3"))
6666
assert secret not in config.redact(secret)
6767

6868

6969
def test_novita_always_points_at_its_own_endpoint(monkeypatch):
7070
"""Unlike OpenAI, there is no override — the endpoint is fixed."""
7171
monkeypatch.setenv("NOVITA_API_KEY", "sk-test")
72-
model = NovitaChatModel("deepseek/deepseek-r1")
72+
model = NovitaChatModel("moonshotai/kimi-k3")
7373
assert str(model.openai_api_base) == NOVITA_BASE_URL
7474
assert NOVITA_BASE_URL == "https://api.novita.ai/openai"
7575

@@ -80,8 +80,8 @@ def test_novita_always_points_at_its_own_endpoint(monkeypatch):
8080
@pytest.mark.parametrize(
8181
("spec", "backend", "model"),
8282
[
83-
("novita/deepseek/deepseek-r1", "novita", "deepseek/deepseek-r1"),
84-
("novita/qwen/qwen3-235b-a22b", "novita", "qwen/qwen3-235b-a22b"),
83+
("novita/moonshotai/kimi-k3", "novita", "moonshotai/kimi-k3"),
84+
("novita/zai-org/glm-5.2", "novita", "zai-org/glm-5.2"),
8585
],
8686
)
8787
def test_novita_specs_split_the_way_the_docs_say(spec, backend, model):
@@ -91,24 +91,24 @@ def test_novita_specs_split_the_way_the_docs_say(spec, backend, model):
9191

9292
def test_the_registry_builds_the_novita_backend(monkeypatch):
9393
monkeypatch.setenv("NOVITA_API_KEY", "sk-test")
94-
assert get_model("novita/deepseek/deepseek-r1")._llm_type == "grapharc-novita"
94+
assert get_model("novita/moonshotai/kimi-k3")._llm_type == "grapharc-novita"
9595

9696

9797
def test_a_missing_novita_key_surfaces_through_the_registry(no_credentials):
9898
with pytest.raises(NovitaError):
99-
get_model("novita/deepseek/deepseek-r1")
99+
get_model("novita/moonshotai/kimi-k3")
100100

101101

102102
def test_an_unknown_backend_still_names_novita():
103103
with pytest.raises(UnknownBackendError, match="novita"):
104-
get_model("nvita/deepseek/deepseek-r1")
104+
get_model("nvita/moonshotai/kimi-k3")
105105

106106

107107
def test_vendor_reads_the_model_authors_novita_ids_carry():
108108
"""Novita ids are themselves `author/slug`, the same shape OpenRouter uses,
109109
so `vendor()` needs no Novita-specific entry in `BACKEND_VENDOR`."""
110-
assert vendor("novita/deepseek/deepseek-r1") == "deepseek"
111-
assert vendor("novita/qwen/qwen3-235b-a22b") == "qwen"
110+
assert vendor("novita/moonshotai/kimi-k3") == "moonshotai"
111+
assert vendor("novita/zai-org/glm-5.2") == "zai-org"
112112

113113

114114
# ------------------------------------------------- capabilities and accounting
@@ -128,14 +128,14 @@ class Verdict(BaseModel):
128128
supported: bool
129129

130130
monkeypatch.setenv("NOVITA_API_KEY", "sk-test")
131-
model = NovitaChatModel("deepseek/deepseek-r1")
131+
model = NovitaChatModel("moonshotai/kimi-k3")
132132
model.bind_tools([get_weather])
133133
model.with_structured_output(Verdict)
134134

135135

136136
def test_the_usage_envelope_matches_every_other_backend(monkeypatch):
137137
monkeypatch.setenv("NOVITA_API_KEY", "sk-test")
138-
model = NovitaChatModel("deepseek/deepseek-r1")
138+
model = NovitaChatModel("moonshotai/kimi-k3")
139139
model._record_usage(
140140
_result(
141141
prompt_tokens=1000,
@@ -155,7 +155,7 @@ def test_novita_reports_no_cost_and_says_so_rather_than_guessing(monkeypatch):
155155
invented number would be worse than an admitted gap, so the call is
156156
counted as unpriced."""
157157
monkeypatch.setenv("NOVITA_API_KEY", "sk-test")
158-
model = NovitaChatModel("deepseek/deepseek-r1")
158+
model = NovitaChatModel("moonshotai/kimi-k3")
159159
model._settle(_result(prompt_tokens=1000, completion_tokens=50))
160160
assert model.last_usage["cost_usd"] is None
161161
assert model.spend.unpriced_calls == 1
@@ -165,7 +165,7 @@ def test_novita_reports_no_cost_and_says_so_rather_than_guessing(monkeypatch):
165165
def test_a_rate_card_prices_novita_the_way_it_prices_openai(monkeypatch):
166166
monkeypatch.setenv("NOVITA_API_KEY", "sk-test")
167167
model = NovitaChatModel(
168-
"deepseek/deepseek-r1",
168+
"moonshotai/kimi-k3",
169169
price_per_million={"input": 0.15, "cached_input": 0.075, "output": 0.60},
170170
)
171171
model._settle(
@@ -184,7 +184,7 @@ def test_novita_sets_no_max_tokens_ceiling_of_its_own(monkeypatch):
184184
"""OpenRouter defaults it to dodge a credit-reservation 402. Novita has no
185185
such reservation, so a default here would only truncate replies."""
186186
monkeypatch.setenv("NOVITA_API_KEY", "sk-test")
187-
assert NovitaChatModel("deepseek/deepseek-r1").max_tokens is None
187+
assert NovitaChatModel("moonshotai/kimi-k3").max_tokens is None
188188

189189

190190
# ------------------------------------------------------------------ live ----
@@ -201,7 +201,7 @@ def get_weather(city: str) -> str:
201201
"""Get the current weather for a city."""
202202
return f"sunny in {city}"
203203

204-
model = get_model("novita/deepseek/deepseek-r1", temperature=0, max_tokens=512)
204+
model = get_model("novita/moonshotai/kimi-k3", temperature=0, max_tokens=512)
205205
reply = model.bind_tools([get_weather]).invoke(
206206
[HumanMessage(content="What is the weather in Paris? Use the tool.")]
207207
)

0 commit comments

Comments
 (0)