feat: add support for custom API key and base URL in configuration

This commit is contained in:
X-iZhang
2026-02-13 00:12:47 +00:00
parent 8210192b03
commit a7f1e166d7
3 changed files with 61 additions and 10 deletions
+8
View File
@@ -65,6 +65,8 @@ class EvoScientistConfig:
google_api_key: str = ""
siliconflow_api_key: str = ""
openrouter_api_key: str = ""
custom_api_key: str = ""
custom_base_url: str = ""
tavily_api_key: str = ""
# LLM Settings
@@ -217,6 +219,8 @@ _ENV_MAPPINGS = {
"google_api_key": "GOOGLE_API_KEY",
"siliconflow_api_key": "SILICONFLOW_API_KEY",
"openrouter_api_key": "OPENROUTER_API_KEY",
"custom_api_key": "CUSTOM_API_KEY",
"custom_base_url": "CUSTOM_BASE_URL",
"tavily_api_key": "TAVILY_API_KEY",
"default_mode": "EVOSCIENTIST_DEFAULT_MODE",
"default_workdir": "EVOSCIENTIST_WORKSPACE_DIR",
@@ -289,5 +293,9 @@ def apply_config_to_env(config: EvoScientistConfig) -> None:
os.environ["SILICONFLOW_API_KEY"] = config.siliconflow_api_key
if config.openrouter_api_key and not os.environ.get("OPENROUTER_API_KEY"):
os.environ["OPENROUTER_API_KEY"] = config.openrouter_api_key
if config.custom_api_key and not os.environ.get("CUSTOM_API_KEY"):
os.environ["CUSTOM_API_KEY"] = config.custom_api_key
if config.custom_base_url and not os.environ.get("CUSTOM_BASE_URL"):
os.environ["CUSTOM_BASE_URL"] = config.custom_base_url
if config.tavily_api_key and not os.environ.get("TAVILY_API_KEY"):
os.environ["TAVILY_API_KEY"] = config.tavily_api_key
+11 -3
View File
@@ -125,9 +125,17 @@ def get_chat_model(
else:
provider = "anthropic" # Default fallback
# SiliconFlow / OpenRouter → route through OpenAI provider with base_url
_is_third_party = provider in ("siliconflow", "openrouter")
if provider == "siliconflow":
# SiliconFlow / OpenRouter / Custom → route through OpenAI provider with base_url
_is_third_party = provider in ("siliconflow", "openrouter", "custom")
if provider == "custom":
base_url = os.environ.get("CUSTOM_BASE_URL", "")
if base_url:
kwargs["base_url"] = base_url
api_key = os.environ.get("CUSTOM_API_KEY", "")
if api_key:
kwargs["api_key"] = api_key
provider = "openai"
elif provider == "siliconflow":
kwargs["base_url"] = _SILICONFLOW_BASE_URL
api_key = os.environ.get("SILICONFLOW_API_KEY", "")
if api_key:
+42 -7
View File
@@ -367,10 +367,11 @@ def _step_provider(config: EvoScientistConfig) -> str:
Choice(title="NVIDIA (DeepSeek, Kimi, GLM, MiniMax, Step, etc.)", value="nvidia"),
Choice(title="SiliconFlow (third party)", value="siliconflow"),
Choice(title="OpenRouter (third party)", value="openrouter"),
Choice(title="Other (OpenAI-compatible)", value="custom"),
]
# Set default based on current config
default = config.provider if config.provider in ["anthropic", "openai", "google-genai", "nvidia", "siliconflow", "openrouter"] else "anthropic"
default = config.provider if config.provider in ["anthropic", "openai", "google-genai", "nvidia", "siliconflow", "openrouter", "custom"] else "anthropic"
provider = questionary.select(
"Select your LLM provider:",
@@ -395,6 +396,7 @@ def _provider_key_info(config: EvoScientistConfig, provider: str):
"google-genai": ("Google", config.google_api_key or os.environ.get("GOOGLE_API_KEY", ""), validate_google_key),
"siliconflow": ("SiliconFlow", config.siliconflow_api_key or os.environ.get("SILICONFLOW_API_KEY", ""), validate_siliconflow_key),
"openrouter": ("OpenRouter", config.openrouter_api_key or os.environ.get("OPENROUTER_API_KEY", ""), validate_openrouter_key),
"custom": ("Custom", config.custom_api_key or os.environ.get("CUSTOM_API_KEY", ""), None),
}
return mapping.get(provider, ("OpenAI", config.openai_api_key or os.environ.get("OPENAI_API_KEY", ""), validate_openai_key))
@@ -432,7 +434,7 @@ def _prompt_and_validate_api_key(
if not key_to_validate:
return None
if not skip_validation:
if not skip_validation and validate_fn is not None:
console.print(" [dim]Validating...[/dim]", end="")
valid, msg = validate_fn(key_to_validate)
if valid:
@@ -492,6 +494,31 @@ _THIRD_PARTY_EXAMPLES: dict[str, list[tuple[str, str]]] = {
}
def _step_base_url(config: EvoScientistConfig) -> str:
"""Prompt for custom provider base URL.
Args:
config: Current configuration.
Returns:
Base URL string.
"""
current = config.custom_base_url
hint = f"Current: {current}" if current else ""
default = current if current else ""
url = questionary.text(
f"Base URL{' (' + hint + ', Enter to keep)' if hint else ''}:",
default=default,
style=WIZARD_STYLE,
qmark=QMARK,
placeholder=FormattedText([("fg:#858585", " e.g. https://api.example.com/v1")]) if not default else None,
).ask()
if url is None:
raise KeyboardInterrupt()
return url.strip()
def _step_model(config: EvoScientistConfig, provider: str) -> str:
"""Step 3: Select model for the provider.
@@ -544,13 +571,12 @@ def _step_model(config: EvoScientistConfig, provider: str) -> str:
entries = get_models_for_provider(provider)
if not entries:
# Fallback if no models for provider
console.print(f" [yellow]No registered models for {provider}[/yellow]")
# Custom / unknown provider: direct text input
model = questionary.text(
"Enter model name:",
default=config.model,
"Model name:",
style=WIZARD_STYLE,
qmark=QMARK,
placeholder=FormattedText([("fg:#858585", " e.g. owner/model-name")]),
).ask()
if model is None:
raise KeyboardInterrupt()
@@ -1422,7 +1448,12 @@ def run_onboard(skip_validation: bool = False) -> bool:
provider = _step_provider(config)
config.provider = provider
# Step 2: Provider API Key
# Step 2a: Base URL (custom provider only)
if provider == "custom":
base_url = _step_base_url(config)
config.custom_base_url = base_url
# Step 2b: Provider API Key
new_key = _step_provider_api_key(config, provider, skip_validation)
if new_key is not None:
if provider == "anthropic":
@@ -1435,6 +1466,8 @@ def run_onboard(skip_validation: bool = False) -> bool:
config.siliconflow_api_key = new_key
elif provider == "openrouter":
config.openrouter_api_key = new_key
elif provider == "custom":
config.custom_api_key = new_key
else:
config.openai_api_key = new_key
else:
@@ -1448,6 +1481,8 @@ def run_onboard(skip_validation: bool = False) -> bool:
current = config.siliconflow_api_key
elif provider == "openrouter":
current = config.openrouter_api_key
elif provider == "custom":
current = config.custom_api_key
else:
current = config.openai_api_key
if not current: