mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-28 06:51:16 +08:00
feat(models): remote model catalog manifest for OpenRouter + Nous Portal (#16033)
OpenRouter and Nous Portal curated picker lists now resolve via a JSON manifest served by the docs site, falling back to the in-repo snapshot when unreachable. Lets us update model lists without shipping a release. Live URL: https://hermes-agent.nousresearch.com/docs/api/model-catalog.json (source at website/static/api/model-catalog.json; auto-deploys via the existing deploy-site.yml GitHub Pages pipeline on every merge to main). Schema (v1) carries id + optional description + free-form metadata at manifest, provider, and model levels. Pricing and context length stay live-fetched via existing machinery (/v1/models endpoints, models.dev). Config (new model_catalog section, default enabled): model_catalog.url master manifest URL model_catalog.ttl_hours disk cache TTL (default 24h) model_catalog.providers.<name>.url optional per-provider override Fetch pipeline: in-process cache -> disk cache (fresh < TTL) -> HTTP fetch -> disk-cache-on-failure fallback -> in-repo snapshot as last resort. Never raises to callers; at worst returns the bundled list. Changes: - website/static/api/model-catalog.json initial manifest (35 OR + 31 Nous) - scripts/build_model_catalog.py regenerator from in-repo lists - hermes_cli/model_catalog.py fetch + validate + cache module - hermes_cli/models.py fetch_openrouter_models() + new get_curated_nous_model_ids() - hermes_cli/main.py, hermes_cli/auth.py Nous flows use the helper - hermes_cli/config.py model_catalog defaults - website/docs/reference/model-catalog.md + sidebars.ts - tests/hermes_cli/test_model_catalog.py 21 tests (validation, fetch success/failure, accessors, disabled, overrides, integration)
This commit is contained in:
103
website/docs/reference/model-catalog.md
Normal file
103
website/docs/reference/model-catalog.md
Normal file
@@ -0,0 +1,103 @@
|
||||
---
|
||||
sidebar_position: 11
|
||||
title: Model Catalog
|
||||
description: Remotely-hosted manifest driving curated model picker lists for OpenRouter and Nous Portal.
|
||||
---
|
||||
|
||||
# Model Catalog
|
||||
|
||||
Hermes fetches curated model lists for **OpenRouter** and **Nous Portal** from a JSON manifest hosted alongside the docs site. This lets maintainers update picker lists without shipping a new `hermes-agent` release.
|
||||
|
||||
When the manifest is unreachable (offline, network blocked, hosting failure), Hermes silently falls back to the in-repo snapshot that ships with the CLI. The manifest never breaks the picker — worst case you see whatever list was bundled with your installed version.
|
||||
|
||||
## Live manifest URL
|
||||
|
||||
```
|
||||
https://hermes-agent.nousresearch.com/docs/api/model-catalog.json
|
||||
```
|
||||
|
||||
Published on every merge to `main` via the existing `deploy-site.yml` GitHub Pages pipeline. The source of truth lives in the repo at `website/static/api/model-catalog.json`.
|
||||
|
||||
## Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"updated_at": "2026-04-25T22:00:00Z",
|
||||
"metadata": {},
|
||||
"providers": {
|
||||
"openrouter": {
|
||||
"metadata": {},
|
||||
"models": [
|
||||
{"id": "moonshotai/kimi-k2.6", "description": "recommended", "metadata": {}},
|
||||
{"id": "openai/gpt-5.4", "description": ""}
|
||||
]
|
||||
},
|
||||
"nous": {
|
||||
"metadata": {},
|
||||
"models": [
|
||||
{"id": "anthropic/claude-opus-4.7"},
|
||||
{"id": "moonshotai/kimi-k2.6"}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Field notes:
|
||||
|
||||
- **`version`** — integer schema version. Future schemas bump this; Hermes refuses manifests with versions it doesn't understand and falls back to the hardcoded snapshot.
|
||||
- **`metadata`** — free-form dict at the manifest, provider, and model level. Any keys. Hermes ignores unknown fields, so you can annotate entries (`"tier": "paid"`, `"tags": [...]`, etc.) without coordinating a schema change.
|
||||
- **`description`** — OpenRouter-only. Drives picker badge text (`"recommended"`, `"free"`, or empty). Nous Portal doesn't use this — free-tier gating is determined live from the Portal's pricing endpoint.
|
||||
- **Pricing and context length** are NOT in the manifest. Those come from live provider APIs (`/v1/models` endpoints, models.dev) at fetch time.
|
||||
|
||||
## Fetch behavior
|
||||
|
||||
| When | What happens |
|
||||
|---|---|
|
||||
| `/model` or `hermes model` | Fetches if disk cache is stale, else uses cache |
|
||||
| Disk cache fresh (< TTL) | No network hit |
|
||||
| Network failure with cache | Silent fallback to cache, one log line |
|
||||
| Network failure, no cache | Silent fallback to in-repo snapshot |
|
||||
| Manifest fails schema validation | Treated as unreachable |
|
||||
|
||||
Cache location: `~/.hermes/cache/model_catalog.json`.
|
||||
|
||||
## Config
|
||||
|
||||
```yaml
|
||||
model_catalog:
|
||||
enabled: true
|
||||
url: https://hermes-agent.nousresearch.com/docs/api/model-catalog.json
|
||||
ttl_hours: 24
|
||||
providers: {}
|
||||
```
|
||||
|
||||
Set `enabled: false` to disable remote fetch entirely and always use the in-repo snapshot.
|
||||
|
||||
### Per-provider override URLs
|
||||
|
||||
Third parties can self-host their own curation list using the same schema. Point a provider at a custom URL:
|
||||
|
||||
```yaml
|
||||
model_catalog:
|
||||
providers:
|
||||
openrouter:
|
||||
url: https://example.com/my-openrouter-curation.json
|
||||
```
|
||||
|
||||
The overriding manifest only needs to populate the provider block(s) it cares about. Other providers continue to resolve against the master URL.
|
||||
|
||||
## Updating the manifest
|
||||
|
||||
Maintainers:
|
||||
|
||||
```bash
|
||||
# Re-generate from the in-repo hardcoded lists (keeps manifest in sync after
|
||||
# editing OPENROUTER_MODELS or _PROVIDER_MODELS["nous"] in hermes_cli/models.py).
|
||||
python scripts/build_model_catalog.py
|
||||
```
|
||||
|
||||
Then PR the resulting change to `website/static/api/model-catalog.json` to `main`. The docs site auto-deploys on merge and the new manifest is live within a few minutes.
|
||||
|
||||
You can also hand-edit the JSON directly for fine-grained metadata changes that don't belong in the in-repo snapshot — the generator script is a convenience, not the single source of truth.
|
||||
@@ -613,6 +613,7 @@ const sidebars: SidebarsConfig = {
|
||||
'reference/tools-reference',
|
||||
'reference/toolsets-reference',
|
||||
'reference/mcp-config-reference',
|
||||
'reference/model-catalog',
|
||||
'reference/skills-catalog',
|
||||
'reference/optional-skills-catalog',
|
||||
'reference/faq',
|
||||
|
||||
259
website/static/api/model-catalog.json
Normal file
259
website/static/api/model-catalog.json
Normal file
@@ -0,0 +1,259 @@
|
||||
{
|
||||
"version": 1,
|
||||
"updated_at": "2026-04-26T12:34:42Z",
|
||||
"metadata": {
|
||||
"source": "hermes-agent repo",
|
||||
"docs": "https://hermes-agent.nousresearch.com/docs/reference/model-catalog"
|
||||
},
|
||||
"providers": {
|
||||
"openrouter": {
|
||||
"metadata": {
|
||||
"display_name": "OpenRouter",
|
||||
"note": "Descriptions drive picker badges. Live /api/v1/models filters curated ids by tool-calling support and free pricing."
|
||||
},
|
||||
"models": [
|
||||
{
|
||||
"id": "moonshotai/kimi-k2.6",
|
||||
"description": "recommended"
|
||||
},
|
||||
{
|
||||
"id": "deepseek/deepseek-v4-pro",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"id": "deepseek/deepseek-v4-flash",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"id": "anthropic/claude-opus-4.7",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"id": "anthropic/claude-opus-4.6",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"id": "anthropic/claude-sonnet-4.6",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"id": "qwen/qwen3.6-plus",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"id": "anthropic/claude-sonnet-4.5",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"id": "anthropic/claude-haiku-4.5",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"id": "openrouter/elephant-alpha",
|
||||
"description": "free"
|
||||
},
|
||||
{
|
||||
"id": "openai/gpt-5.5",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"id": "openai/gpt-5.4-mini",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"id": "xiaomi/mimo-v2.5-pro",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"id": "xiaomi/mimo-v2.5",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"id": "openai/gpt-5.3-codex",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"id": "google/gemini-3-pro-image-preview",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"id": "google/gemini-3-flash-preview",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"id": "google/gemini-3.1-pro-preview",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"id": "google/gemini-3.1-flash-lite-preview",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"id": "qwen/qwen3.5-plus-02-15",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"id": "qwen/qwen3.5-35b-a3b",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"id": "stepfun/step-3.5-flash",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"id": "minimax/minimax-m2.7",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"id": "minimax/minimax-m2.5",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"id": "minimax/minimax-m2.5:free",
|
||||
"description": "free"
|
||||
},
|
||||
{
|
||||
"id": "z-ai/glm-5.1",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"id": "z-ai/glm-5v-turbo",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"id": "z-ai/glm-5-turbo",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"id": "x-ai/grok-4.20",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"id": "nvidia/nemotron-3-super-120b-a12b",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"id": "nvidia/nemotron-3-super-120b-a12b:free",
|
||||
"description": "free"
|
||||
},
|
||||
{
|
||||
"id": "arcee-ai/trinity-large-preview:free",
|
||||
"description": "free"
|
||||
},
|
||||
{
|
||||
"id": "arcee-ai/trinity-large-thinking",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"id": "openai/gpt-5.5-pro",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"id": "openai/gpt-5.4-nano",
|
||||
"description": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
"nous": {
|
||||
"metadata": {
|
||||
"display_name": "Nous Portal",
|
||||
"note": "Free-tier gating is determined live via Portal pricing (partition_nous_models_by_tier), not this manifest."
|
||||
},
|
||||
"models": [
|
||||
{
|
||||
"id": "moonshotai/kimi-k2.6"
|
||||
},
|
||||
{
|
||||
"id": "deepseek/deepseek-v4-pro"
|
||||
},
|
||||
{
|
||||
"id": "deepseek/deepseek-v4-flash"
|
||||
},
|
||||
{
|
||||
"id": "xiaomi/mimo-v2.5-pro"
|
||||
},
|
||||
{
|
||||
"id": "xiaomi/mimo-v2.5"
|
||||
},
|
||||
{
|
||||
"id": "anthropic/claude-opus-4.7"
|
||||
},
|
||||
{
|
||||
"id": "anthropic/claude-opus-4.6"
|
||||
},
|
||||
{
|
||||
"id": "anthropic/claude-sonnet-4.6"
|
||||
},
|
||||
{
|
||||
"id": "anthropic/claude-sonnet-4.5"
|
||||
},
|
||||
{
|
||||
"id": "anthropic/claude-haiku-4.5"
|
||||
},
|
||||
{
|
||||
"id": "openai/gpt-5.5"
|
||||
},
|
||||
{
|
||||
"id": "openai/gpt-5.4-mini"
|
||||
},
|
||||
{
|
||||
"id": "openai/gpt-5.3-codex"
|
||||
},
|
||||
{
|
||||
"id": "google/gemini-3-pro-preview"
|
||||
},
|
||||
{
|
||||
"id": "google/gemini-3-flash-preview"
|
||||
},
|
||||
{
|
||||
"id": "google/gemini-3.1-pro-preview"
|
||||
},
|
||||
{
|
||||
"id": "google/gemini-3.1-flash-lite-preview"
|
||||
},
|
||||
{
|
||||
"id": "qwen/qwen3.5-plus-02-15"
|
||||
},
|
||||
{
|
||||
"id": "qwen/qwen3.5-35b-a3b"
|
||||
},
|
||||
{
|
||||
"id": "stepfun/step-3.5-flash"
|
||||
},
|
||||
{
|
||||
"id": "minimax/minimax-m2.7"
|
||||
},
|
||||
{
|
||||
"id": "minimax/minimax-m2.5"
|
||||
},
|
||||
{
|
||||
"id": "minimax/minimax-m2.5:free"
|
||||
},
|
||||
{
|
||||
"id": "z-ai/glm-5.1"
|
||||
},
|
||||
{
|
||||
"id": "z-ai/glm-5v-turbo"
|
||||
},
|
||||
{
|
||||
"id": "z-ai/glm-5-turbo"
|
||||
},
|
||||
{
|
||||
"id": "x-ai/grok-4.20-beta"
|
||||
},
|
||||
{
|
||||
"id": "nvidia/nemotron-3-super-120b-a12b"
|
||||
},
|
||||
{
|
||||
"id": "arcee-ai/trinity-large-thinking"
|
||||
},
|
||||
{
|
||||
"id": "openai/gpt-5.5-pro"
|
||||
},
|
||||
{
|
||||
"id": "openai/gpt-5.4-nano"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user