From 154fd88b76cc4cd9bd9405d8c521399fc452c1d2 Mon Sep 17 00:00:00 2001 From: Daniel van Strien Date: Tue, 17 Mar 2026 05:21:17 -0700 Subject: [PATCH] feat: add Hugging Face as a first-class inference provider Register Hugging Face Inference Providers (router.huggingface.co/v1) as a named provider alongside existing ones. Users can now: - hermes chat --provider huggingface - Use hf:model-name syntax (e.g. hf:Qwen/Qwen3-235B-A22B-Thinking-2507) - Set HF_TOKEN in ~/.hermes/.env - Select from 18 curated models via hermes model picker OpenAI-compatible endpoint with automatic failover across providers (Groq, Together, SambaNova, etc.), free tier included. Files changed: - hermes_cli/auth.py: ProviderConfig + aliases (hf, hugging-face, huggingface-hub) - hermes_cli/models.py: _PROVIDER_MODELS, _PROVIDER_LABELS, _PROVIDER_ALIASES, _PROVIDER_ORDER - hermes_cli/main.py: provider_labels, providers list, --provider choices, dispatch - hermes_cli/setup.py: provider_choices, setup flow with token prompt - hermes_cli/config.py: HF_TOKEN + HF_BASE_URL in OPTIONAL_ENV_VARS - agent/model_metadata.py: context window entries for all curated HF models - .env.example: HF_TOKEN documentation Based on PR #1171 by @davanstrien. Salvaged onto current main with additional completeness: setup.py flow, config.py env vars, auth.py aliases, model_metadata context windows, .env.example. --- .env.example | 9 +++++++++ agent/model_metadata.py | 19 +++++++++++++++++++ hermes_cli/auth.py | 9 +++++++++ hermes_cli/config.py | 15 +++++++++++++++ hermes_cli/main.py | 27 +++++++++++++++++++++++++-- hermes_cli/models.py | 28 +++++++++++++++++++++++++++- hermes_cli/setup.py | 30 +++++++++++++++++++++++++++++- 7 files changed, 133 insertions(+), 4 deletions(-) diff --git a/.env.example b/.env.example index d273a6966a0b..515c0016079b 100644 --- a/.env.example +++ b/.env.example @@ -59,6 +59,15 @@ OPENCODE_ZEN_API_KEY= # OpenCode Go provides access to open models (GLM-5, Kimi K2.5, MiniMax M2.5) # $10/month subscription. Get your key at: https://opencode.ai/auth OPENCODE_GO_API_KEY= + +# ============================================================================= +# LLM PROVIDER (Hugging Face Inference Providers) +# ============================================================================= +# Hugging Face routes to 20+ open models via unified OpenAI-compatible endpoint. +# Free tier included ($0.10/month), no markup on provider rates. +# Get your token at: https://huggingface.co/settings/tokens +# Required permission: "Make calls to Inference Providers" +HF_TOKEN= # OPENCODE_GO_BASE_URL=https://opencode.ai/zen/go/v1 # Override default base URL # ============================================================================= diff --git a/agent/model_metadata.py b/agent/model_metadata.py index cd847aa82c43..fcc24d0c1eb0 100644 --- a/agent/model_metadata.py +++ b/agent/model_metadata.py @@ -119,6 +119,25 @@ DEFAULT_CONTEXT_LENGTHS = { "qwen-plus-latest": 131072, "qwen3.5-flash": 131072, "qwen-vl-max": 32768, + # Hugging Face Inference Providers — model IDs use org/name format + "Qwen/Qwen3.5-397B-A17B": 131072, + "Qwen/Qwen3-235B-A22B-Thinking-2507": 131072, + "Qwen/Qwen3-Coder-480B-A35B-Instruct": 131072, + "Qwen/Qwen3-Coder-Next": 131072, + "Qwen/Qwen3-Next-80B-A3B-Instruct": 131072, + "Qwen/Qwen3-Next-80B-A3B-Thinking": 131072, + "deepseek-ai/DeepSeek-R1-0528": 65536, + "deepseek-ai/DeepSeek-V3.2": 65536, + "moonshotai/Kimi-K2-Instruct": 262144, + "moonshotai/Kimi-K2-Instruct-0905": 262144, + "moonshotai/Kimi-K2.5": 262144, + "moonshotai/Kimi-K2-Thinking": 262144, + "MiniMaxAI/MiniMax-M2.5": 204800, + "MiniMaxAI/MiniMax-M2.1": 204800, + "XiaomiMiMo/MiMo-V2-Flash": 32768, + "zai-org/GLM-5": 202752, + "zai-org/GLM-4.7": 202752, + "zai-org/GLM-4.7-Flash": 202752, } diff --git a/hermes_cli/auth.py b/hermes_cli/auth.py index 54573acf1808..c897fabf1c5c 100644 --- a/hermes_cli/auth.py +++ b/hermes_cli/auth.py @@ -195,6 +195,14 @@ PROVIDER_REGISTRY: Dict[str, ProviderConfig] = { api_key_env_vars=("KILOCODE_API_KEY",), base_url_env_var="KILOCODE_BASE_URL", ), + "huggingface": ProviderConfig( + id="huggingface", + name="Hugging Face", + auth_type="api_key", + inference_base_url="https://router.huggingface.co/v1", + api_key_env_vars=("HF_TOKEN",), + base_url_env_var="HF_BASE_URL", + ), } @@ -574,6 +582,7 @@ def resolve_provider( "claude": "anthropic", "claude-code": "anthropic", "aigateway": "ai-gateway", "vercel": "ai-gateway", "vercel-ai-gateway": "ai-gateway", "opencode": "opencode-zen", "zen": "opencode-zen", + "hf": "huggingface", "hugging-face": "huggingface", "huggingface-hub": "huggingface", "go": "opencode-go", "opencode-go-sub": "opencode-go", "kilo": "kilocode", "kilo-code": "kilocode", "kilo-gateway": "kilocode", } diff --git a/hermes_cli/config.py b/hermes_cli/config.py index 874ceb29e58a..16b283bcd63f 100644 --- a/hermes_cli/config.py +++ b/hermes_cli/config.py @@ -549,6 +549,21 @@ OPTIONAL_ENV_VARS = { "category": "provider", "advanced": True, }, + "HF_TOKEN": { + "description": "Hugging Face token for Inference Providers (20+ open models via router.huggingface.co)", + "prompt": "Hugging Face Token", + "url": "https://huggingface.co/settings/tokens", + "password": True, + "category": "provider", + }, + "HF_BASE_URL": { + "description": "Hugging Face Inference Providers base URL override", + "prompt": "HF base URL (leave empty for default)", + "url": None, + "password": False, + "category": "provider", + "advanced": True, + }, # ── Tool API keys ── "PARALLEL_API_KEY": { diff --git a/hermes_cli/main.py b/hermes_cli/main.py index d5d4885a74fe..0a80d0b92b9f 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -785,6 +785,7 @@ def cmd_model(args): "ai-gateway": "AI Gateway", "kilocode": "Kilo Code", "alibaba": "Alibaba Cloud (DashScope)", + "huggingface": "Hugging Face", "custom": "Custom endpoint", } active_label = provider_labels.get(active, active) @@ -809,6 +810,7 @@ def cmd_model(args): ("opencode-go", "OpenCode Go (open models, $10/month subscription)"), ("ai-gateway", "AI Gateway (Vercel — 200+ models, pay-per-use)"), ("alibaba", "Alibaba Cloud / DashScope (Qwen models, Anthropic-compatible)"), + ("huggingface", "Hugging Face Inference Providers (20+ open models)"), ] # Add user-defined custom providers from config.yaml @@ -877,7 +879,7 @@ def cmd_model(args): _model_flow_anthropic(config, current_model) elif selected_provider == "kimi-coding": _model_flow_kimi(config, current_model) - elif selected_provider in ("zai", "minimax", "minimax-cn", "kilocode", "opencode-zen", "opencode-go", "ai-gateway", "alibaba"): + elif selected_provider in ("zai", "minimax", "minimax-cn", "kilocode", "opencode-zen", "opencode-go", "ai-gateway", "alibaba", "huggingface"): _model_flow_api_key_provider(config, selected_provider, current_model) @@ -1444,6 +1446,27 @@ _PROVIDER_MODELS = { "google/gemini-3-pro-preview", "google/gemini-3-flash-preview", ], + # Curated model list sourced from https://models.dev (huggingface provider) + "huggingface": [ + "Qwen/Qwen3.5-397B-A17B", + "Qwen/Qwen3-235B-A22B-Thinking-2507", + "Qwen/Qwen3-Coder-480B-A35B-Instruct", + "Qwen/Qwen3-Coder-Next", + "Qwen/Qwen3-Next-80B-A3B-Instruct", + "Qwen/Qwen3-Next-80B-A3B-Thinking", + "deepseek-ai/DeepSeek-R1-0528", + "deepseek-ai/DeepSeek-V3.2", + "moonshotai/Kimi-K2-Instruct", + "moonshotai/Kimi-K2-Instruct-0905", + "moonshotai/Kimi-K2.5", + "moonshotai/Kimi-K2-Thinking", + "MiniMaxAI/MiniMax-M2.5", + "MiniMaxAI/MiniMax-M2.1", + "XiaomiMiMo/MiMo-V2-Flash", + "zai-org/GLM-5", + "zai-org/GLM-4.7", + "zai-org/GLM-4.7-Flash", + ], } @@ -2642,7 +2665,7 @@ For more help on a command: ) chat_parser.add_argument( "--provider", - choices=["auto", "openrouter", "nous", "openai-codex", "anthropic", "zai", "kimi-coding", "minimax", "minimax-cn", "kilocode"], + choices=["auto", "openrouter", "nous", "openai-codex", "anthropic", "huggingface", "zai", "kimi-coding", "minimax", "minimax-cn", "kilocode"], default=None, help="Inference provider (default: auto)" ) diff --git a/hermes_cli/models.py b/hermes_cli/models.py index 174aa9475065..5eb34ca74bb3 100644 --- a/hermes_cli/models.py +++ b/hermes_cli/models.py @@ -155,6 +155,28 @@ _PROVIDER_MODELS: dict[str, list[str]] = { "qwen3.5-flash", "qwen-vl-max", ], + # Curated model list for Hugging Face Inference Providers + # sourced from https://models.dev (huggingface provider) + "huggingface": [ + "Qwen/Qwen3.5-397B-A17B", + "Qwen/Qwen3-235B-A22B-Thinking-2507", + "Qwen/Qwen3-Coder-480B-A35B-Instruct", + "Qwen/Qwen3-Coder-Next", + "Qwen/Qwen3-Next-80B-A3B-Instruct", + "Qwen/Qwen3-Next-80B-A3B-Thinking", + "deepseek-ai/DeepSeek-R1-0528", + "deepseek-ai/DeepSeek-V3.2", + "moonshotai/Kimi-K2-Instruct", + "moonshotai/Kimi-K2-Instruct-0905", + "moonshotai/Kimi-K2.5", + "moonshotai/Kimi-K2-Thinking", + "MiniMaxAI/MiniMax-M2.5", + "MiniMaxAI/MiniMax-M2.1", + "XiaomiMiMo/MiMo-V2-Flash", + "zai-org/GLM-5", + "zai-org/GLM-4.7", + "zai-org/GLM-4.7-Flash", + ], } _PROVIDER_LABELS = { @@ -172,6 +194,7 @@ _PROVIDER_LABELS = { "ai-gateway": "AI Gateway", "kilocode": "Kilo Code", "alibaba": "Alibaba Cloud (DashScope)", + "huggingface": "Hugging Face", "custom": "Custom endpoint", } @@ -201,6 +224,9 @@ _PROVIDER_ALIASES = { "aliyun": "alibaba", "qwen": "alibaba", "alibaba-cloud": "alibaba", + "hf": "huggingface", + "hugging-face": "huggingface", + "huggingface-hub": "huggingface", } @@ -234,7 +260,7 @@ def list_available_providers() -> list[dict[str, str]]: # Canonical providers in display order _PROVIDER_ORDER = [ "openrouter", "nous", "openai-codex", - "zai", "kimi-coding", "minimax", "minimax-cn", "kilocode", "anthropic", "alibaba", + "huggingface", "zai", "kimi-coding", "minimax", "minimax-cn", "kilocode", "anthropic", "alibaba", "opencode-zen", "opencode-go", "ai-gateway", "deepseek", "custom", ] diff --git a/hermes_cli/setup.py b/hermes_cli/setup.py index c9a117c5db74..f15158e0d0eb 100644 --- a/hermes_cli/setup.py +++ b/hermes_cli/setup.py @@ -61,6 +61,11 @@ _DEFAULT_PROVIDER_MODELS = { "minimax-cn": ["MiniMax-M2.5", "MiniMax-M2.5-highspeed", "MiniMax-M2.1"], "ai-gateway": ["anthropic/claude-opus-4.6", "anthropic/claude-sonnet-4.6", "openai/gpt-5", "google/gemini-3-flash"], "kilocode": ["anthropic/claude-opus-4.6", "anthropic/claude-sonnet-4.6", "openai/gpt-5.4", "google/gemini-3-pro-preview", "google/gemini-3-flash-preview"], + "huggingface": [ + "Qwen/Qwen3.5-397B-A17B", "Qwen/Qwen3-235B-A22B-Thinking-2507", + "Qwen/Qwen3-Coder-480B-A35B-Instruct", "deepseek-ai/DeepSeek-R1-0528", + "deepseek-ai/DeepSeek-V3.2", "moonshotai/Kimi-K2.5", + ], } @@ -741,6 +746,7 @@ def setup_model_provider(config: dict): "Alibaba Cloud / DashScope (Qwen models via Anthropic-compatible API)", "OpenCode Zen (35+ curated models, pay-as-you-go)", "OpenCode Go (open models, $10/month subscription)", + "Hugging Face Inference Providers (20+ open models)", ] if keep_label: provider_choices.append(keep_label) @@ -1412,7 +1418,29 @@ def setup_model_provider(config: dict): _set_model_provider(config, "opencode-go", pconfig.inference_base_url) selected_base_url = pconfig.inference_base_url - # else: provider_idx == 14 (Keep current) — only shown when a provider already exists + elif provider_idx == 14: # Hugging Face Inference Providers + selected_provider = "huggingface" + print() + print_header("Hugging Face API Token") + pconfig = PROVIDER_REGISTRY["huggingface"] + print_info(f"Provider: {pconfig.name}") + print_info("Get your token at: https://huggingface.co/settings/tokens") + print_info("Required permission: 'Make calls to Inference Providers'") + print() + + api_key = prompt_fn( + " HF_TOKEN: ", + is_password=True, + ).strip() + if api_key: + save_env_value("HF_TOKEN", api_key) + # Clear OpenRouter env vars to prevent routing confusion + save_env_value("OPENAI_BASE_URL", "") + save_env_value("OPENAI_API_KEY", "") + _set_model_provider(config, "huggingface", pconfig.inference_base_url) + selected_base_url = pconfig.inference_base_url + + # else: provider_idx == 15 (Keep current) — only shown when a provider already exists # Normalize "keep current" to an explicit provider so downstream logic # doesn't fall back to the generic OpenRouter/static-model path. if selected_provider is None: