Skip to content

Instantly share code, notes, and snippets.

@dgomesbr
Created April 1, 2026 13:52
Show Gist options
  • Select an option

  • Save dgomesbr/34354c511ca3de7d6d7387c86f5d8cfe to your computer and use it in GitHub Desktop.

Select an option

Save dgomesbr/34354c511ca3de7d6d7387c86f5d8cfe to your computer and use it in GitHub Desktop.
refactor: extract shared provider_registry module — eliminates 4-file duplication
diff --git a/bot/core.py b/bot/core.py
index d842ac3..468a4d3 100644
--- a/bot/core.py
+++ b/bot/core.py
@@ -30,88 +30,14 @@ from openai import AsyncOpenAI, APIError
# ---------------------------------------------------------------------------
# LLM provider presets (Multi-Provider support)
# ---------------------------------------------------------------------------
-# Each provider maps to (base_url, default_model, api_key_env_var).
-# Users set LLM_PROVIDER=<key> for one-step configuration;
-# LLM_BASE_URL and OMICSCLAW_MODEL can still override.
-#
-# Inspired by EvoScientist's Multi-Provider architecture, adapted for
-# OmicsClaw's lightweight AsyncOpenAI-based design. All providers are
-# accessed through the OpenAI-compatible API protocol.
-
-PROVIDER_PRESETS: dict[str, tuple[str, str, str]] = {
- # --- Tier 1: Primary providers ---
- "deepseek": ("https://api.deepseek.com", "deepseek-chat", "DEEPSEEK_API_KEY"),
- "openai": ("", "gpt-4o", "OPENAI_API_KEY"),
- "anthropic": ("https://api.anthropic.com/v1/", "claude-sonnet-4-5-20250514", "ANTHROPIC_API_KEY"),
- "gemini": ("https://generativelanguage.googleapis.com/v1beta/openai/", "gemini-2.5-flash", "GOOGLE_API_KEY"),
- "nvidia": ("https://integrate.api.nvidia.com/v1", "deepseek-ai/deepseek-r1", "NVIDIA_API_KEY"),
-
- # --- Tier 2: Third-party aggregators ---
- "siliconflow": ("https://api.siliconflow.cn/v1", "deepseek-ai/DeepSeek-V3", "SILICONFLOW_API_KEY"),
- "openrouter": ("https://openrouter.ai/api/v1", "deepseek/deepseek-chat-v3-0324", "OPENROUTER_API_KEY"),
- "volcengine": ("https://ark.cn-beijing.volces.com/api/v3", "doubao-1.5-pro-256k", "VOLCENGINE_API_KEY"),
- "dashscope": ("https://dashscope.aliyuncs.com/compatible-mode/v1", "qwen-max", "DASHSCOPE_API_KEY"),
- "zhipu": ("https://open.bigmodel.cn/api/paas/v4", "glm-4-flash", "ZHIPU_API_KEY"),
-
- # --- Tier 3: Local & custom ---
- "ollama": ("http://localhost:11434/v1", "qwen2.5:7b", ""),
- "custom": ("", "", ""),
-
- # --- Legacy alias (backward compat — same as gemini) ---
-}
-
-# Ordered list for auto-detection: when LLM_PROVIDER is not set, we pick the
-# first provider whose API key env var is present in the environment.
-_PROVIDER_DETECT_ORDER = [
- "deepseek", "openai", "anthropic", "gemini", "nvidia",
- "siliconflow", "openrouter", "volcengine", "dashscope", "zhipu",
-]
-
-
-def resolve_provider(
- provider: str = "",
- base_url: str = "",
- model: str = "",
- api_key: str = "",
-) -> tuple[str | None, str, str]:
- """Return (base_url_or_None, model, resolved_api_key) after applying provider defaults.
-
- Priority: explicit env vars > provider preset > auto-detect > hardcoded fallback.
+# Centralised in omicsclaw.core.provider_registry — imported here so that
+# every consumer (bot, interactive, routing, agents) shares one definition.
- When *provider* is empty and *api_key* is empty, we scan provider-specific
- environment variables (DEEPSEEK_API_KEY, OPENAI_API_KEY, ANTHROPIC_API_KEY, …)
- to auto-detect the provider.
- """
- provider_key = provider.lower().strip() if provider else ""
-
- # Auto-detect provider from available API keys
- if not provider_key and not api_key:
- for p in _PROVIDER_DETECT_ORDER:
- env_var = PROVIDER_PRESETS[p][2]
- if env_var and os.environ.get(env_var):
- provider_key = p
- api_key = os.environ[env_var]
- break
-
- # Look up preset
- preset = PROVIDER_PRESETS.get(provider_key, ("", "", ""))
- preset_url, preset_model, preset_key_env = preset
-
- # Allow per-provider base_url override via env var (e.g. ANTHROPIC_BASE_URL)
- env_base_url = ""
- if provider_key:
- env_base_url = os.environ.get(f"{provider_key.upper()}_BASE_URL", "")
-
- resolved_url = base_url or env_base_url or preset_url or None
- resolved_model = model or preset_model or "deepseek-chat"
-
- # Resolve API key: explicit > per-provider env > LLM_API_KEY fallback
- if not api_key and preset_key_env:
- api_key = os.environ.get(preset_key_env, "")
- if not api_key:
- api_key = os.environ.get("LLM_API_KEY", os.environ.get("OPENAI_API_KEY", ""))
-
- return resolved_url, resolved_model, api_key
+from omicsclaw.core.provider_registry import ( # noqa: E402
+ PROVIDER_PRESETS,
+ PROVIDER_DETECT_ORDER as _PROVIDER_DETECT_ORDER,
+ resolve_provider,
+)
# ---------------------------------------------------------------------------
diff --git a/bot/onboard.py b/bot/onboard.py
index b33efb5..fc97d47 100644
--- a/bot/onboard.py
+++ b/bot/onboard.py
@@ -18,6 +18,12 @@ console = Console()
_PROJECT_ROOT = Path(__file__).resolve().parent.parent
_ENV_PATH = _PROJECT_ROOT / ".env"
+# Import provider list from the single source of truth
+import sys
+if str(_PROJECT_ROOT) not in sys.path:
+ sys.path.insert(0, str(_PROJECT_ROOT))
+from omicsclaw.core.provider_registry import PROVIDER_CHOICES
+
def load_env() -> dict:
env_vars = {}
if _ENV_PATH.exists():
@@ -63,7 +69,7 @@ def run_onboard():
env_vars = load_env()
# ── 1. LLM Provider ──
- providers = ["deepseek", "openai", "anthropic", "gemini", "siliconflow", "zhipu", "dashscope", "volcengine", "openrouter", "ollama", "custom"]
+ providers = list(PROVIDER_CHOICES)
current_provider = env_vars.get("LLM_PROVIDER", "deepseek")
provider = questionary.select(
diff --git a/omicsclaw/core/provider_registry.py b/omicsclaw/core/provider_registry.py
new file mode 100644
index 0000000..e8778cf
--- /dev/null
+++ b/omicsclaw/core/provider_registry.py
@@ -0,0 +1,88 @@
+"""Centralised LLM provider registry.
+
+Single source of truth for provider presets, auto-detection order, and
+credential resolution. Every module that needs provider config imports
+from here — no more copy-pasting PROVIDER_PRESETS into fallback blocks.
+
+Consumers:
+ bot/core.py → init(), resolve_provider()
+ omicsclaw/routing/ → _resolve_llm_config()
+ omicsclaw/agents/ → _get_llm()
+ bot/onboard.py → provider list for wizard
+"""
+from __future__ import annotations
+
+import os
+
+# Each provider maps to (base_url, default_model, api_key_env_var).
+PROVIDER_PRESETS: dict[str, tuple[str, str, str]] = {
+ # --- Tier 1: Primary providers ---
+ "deepseek": ("https://api.deepseek.com", "deepseek-chat", "DEEPSEEK_API_KEY"),
+ "openai": ("", "gpt-4o", "OPENAI_API_KEY"),
+ "anthropic": ("https://api.anthropic.com/v1/", "claude-sonnet-4-5-20250514", "ANTHROPIC_API_KEY"),
+ "gemini": ("https://generativelanguage.googleapis.com/v1beta/openai/", "gemini-2.5-flash", "GOOGLE_API_KEY"),
+ "nvidia": ("https://integrate.api.nvidia.com/v1", "deepseek-ai/deepseek-r1", "NVIDIA_API_KEY"),
+ # --- Tier 2: Third-party aggregators ---
+ "siliconflow": ("https://api.siliconflow.cn/v1", "deepseek-ai/DeepSeek-V3", "SILICONFLOW_API_KEY"),
+ "openrouter": ("https://openrouter.ai/api/v1", "deepseek/deepseek-chat-v3-0324", "OPENROUTER_API_KEY"),
+ "volcengine": ("https://ark.cn-beijing.volces.com/api/v3", "doubao-1.5-pro-256k", "VOLCENGINE_API_KEY"),
+ "dashscope": ("https://dashscope.aliyuncs.com/compatible-mode/v1", "qwen-max", "DASHSCOPE_API_KEY"),
+ "zhipu": ("https://open.bigmodel.cn/api/paas/v4", "glm-4-flash", "ZHIPU_API_KEY"),
+ # --- Tier 3: Local & custom ---
+ "ollama": ("http://localhost:11434/v1", "qwen2.5:7b", ""),
+ "custom": ("", "", ""),
+}
+
+# Ordered list for auto-detection: first provider whose API key env var is
+# present wins.
+PROVIDER_DETECT_ORDER: list[str] = [
+ "deepseek", "openai", "anthropic", "gemini", "nvidia",
+ "siliconflow", "openrouter", "volcengine", "dashscope", "zhipu",
+]
+
+# Provider names offered in the onboard wizard (display order).
+PROVIDER_CHOICES: list[str] = [
+ "deepseek", "openai", "anthropic", "gemini",
+ "siliconflow", "zhipu", "dashscope", "volcengine",
+ "openrouter", "ollama", "custom",
+]
+
+
+def resolve_provider(
+ provider: str = "",
+ base_url: str = "",
+ model: str = "",
+ api_key: str = "",
+) -> tuple[str | None, str, str]:
+ """Return *(base_url | None, model, api_key)* after applying provider defaults.
+
+ Priority: explicit args → provider preset → auto-detect → fallback.
+ """
+ provider_key = provider.lower().strip() if provider else ""
+
+ # Auto-detect from available API keys
+ if not provider_key and not api_key:
+ for p in PROVIDER_DETECT_ORDER:
+ env_var = PROVIDER_PRESETS[p][2]
+ if env_var and os.environ.get(env_var):
+ provider_key = p
+ api_key = os.environ[env_var]
+ break
+
+ preset = PROVIDER_PRESETS.get(provider_key, ("", "", ""))
+ preset_url, preset_model, preset_key_env = preset
+
+ # Per-provider base_url override (e.g. ANTHROPIC_BASE_URL)
+ env_base_url = ""
+ if provider_key:
+ env_base_url = os.environ.get(f"{provider_key.upper()}_BASE_URL", "")
+
+ resolved_url = base_url or env_base_url or preset_url or None
+ resolved_model = model or preset_model or "deepseek-chat"
+
+ if not api_key and preset_key_env:
+ api_key = os.environ.get(preset_key_env, "")
+ if not api_key:
+ api_key = os.environ.get("LLM_API_KEY", os.environ.get("OPENAI_API_KEY", ""))
+
+ return resolved_url, resolved_model, api_key
diff --git a/omicsclaw/routing/llm_router.py b/omicsclaw/routing/llm_router.py
index b10713f..59731e7 100644
--- a/omicsclaw/routing/llm_router.py
+++ b/omicsclaw/routing/llm_router.py
@@ -18,35 +18,12 @@ except ImportError:
logger = logging.getLogger(__name__)
# ---------------------------------------------------------------------------
-# Provider resolution — reuses bot.core when available, else falls back
+# Provider resolution — single source of truth in provider_registry
# ---------------------------------------------------------------------------
-
-# Import shared provider presets from bot.core (avoids hard-coding provider
-# URLs in multiple places). Graceful fallback for standalone usage.
-try:
- from bot.core import PROVIDER_PRESETS, _PROVIDER_DETECT_ORDER
- _HAS_CORE = True
-except ImportError:
- _HAS_CORE = False
- # Minimal fallback matching the most common providers
- PROVIDER_PRESETS = {
- "deepseek": ("https://api.deepseek.com", "deepseek-chat", "DEEPSEEK_API_KEY"),
- "openai": ("https://api.openai.com/v1", "gpt-4o", "OPENAI_API_KEY"),
- "gemini": ("https://generativelanguage.googleapis.com/v1beta/openai/", "gemini-2.5-flash", "GOOGLE_API_KEY"),
- "anthropic": ("https://api.anthropic.com/v1/", "claude-sonnet-4-5-20250514", "ANTHROPIC_API_KEY"),
- "nvidia": ("https://integrate.api.nvidia.com/v1", "deepseek-ai/deepseek-r1", "NVIDIA_API_KEY"),
- "siliconflow":("https://api.siliconflow.cn/v1", "deepseek-ai/DeepSeek-V3", "SILICONFLOW_API_KEY"),
- "openrouter": ("https://openrouter.ai/api/v1", "deepseek/deepseek-chat-v3-0324", "OPENROUTER_API_KEY"),
- "volcengine": ("https://ark.cn-beijing.volces.com/api/v3", "doubao-1.5-pro-256k", "VOLCENGINE_API_KEY"),
- "dashscope": ("https://dashscope.aliyuncs.com/compatible-mode/v1", "qwen-max", "DASHSCOPE_API_KEY"),
- "zhipu": ("https://open.bigmodel.cn/api/paas/v4", "glm-4-flash", "ZHIPU_API_KEY"),
- "ollama": ("http://localhost:11434/v1", "qwen2.5:7b", ""),
- "custom": ("", "", ""),
- }
- _PROVIDER_DETECT_ORDER = [
- "deepseek", "openai", "anthropic", "gemini", "nvidia",
- "siliconflow", "openrouter", "volcengine", "dashscope", "zhipu",
- ]
+from omicsclaw.core.provider_registry import (
+ PROVIDER_PRESETS,
+ PROVIDER_DETECT_ORDER as _PROVIDER_DETECT_ORDER,
+)
def _resolve_llm_config() -> Tuple[str, str, str]:
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment