mirror of
https://github.com/TauricResearch/TradingAgents.git
synced 2026-06-17 05:16:14 +03:00
feat(cli): detect missing provider API keys and persist to .env
Adds a canonical PROVIDER_API_KEY_ENV mapping (14 providers including the three dual-region pairs) and an ensure_api_key() helper. When the selected provider's key is absent from the environment, the CLI prompts via questionary.password, writes the value to .env via python-dotenv's set_key (preserves existing lines), and exports it into os.environ so the run continues without restart. Wired into cli/main.py right after the region prompts so qwen-cn, glm-cn, and minimax-cn each check their own region-specific key. openai_client refactored to consult the same mapping, eliminating its private duplicate of provider→env-var data.
This commit is contained in:
44
tradingagents/llm_clients/api_key_env.py
Normal file
44
tradingagents/llm_clients/api_key_env.py
Normal file
@@ -0,0 +1,44 @@
|
||||
"""Canonical provider -> API-key env-var mapping.
|
||||
|
||||
A single source of truth for which environment variable holds the API
|
||||
key for each supported LLM provider. Used by the CLI's interactive key
|
||||
prompt (cli/utils.ensure_api_key) and by anything else that needs to
|
||||
ask "does this provider require a key, and which env var is it?".
|
||||
|
||||
When adding a new provider, register its env var here so the CLI flow
|
||||
prompts for it automatically instead of failing on first API call.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
|
||||
PROVIDER_API_KEY_ENV: dict[str, Optional[str]] = {
|
||||
"openai": "OPENAI_API_KEY",
|
||||
"anthropic": "ANTHROPIC_API_KEY",
|
||||
"google": "GOOGLE_API_KEY",
|
||||
"azure": "AZURE_OPENAI_API_KEY",
|
||||
"xai": "XAI_API_KEY",
|
||||
"deepseek": "DEEPSEEK_API_KEY",
|
||||
# Dual-region providers each carry their own account; keys are not
|
||||
# interchangeable between the international and China endpoints.
|
||||
"qwen": "DASHSCOPE_API_KEY",
|
||||
"qwen-cn": "DASHSCOPE_CN_API_KEY",
|
||||
"glm": "ZHIPU_API_KEY",
|
||||
"glm-cn": "ZHIPU_CN_API_KEY",
|
||||
"minimax": "MINIMAX_API_KEY",
|
||||
"minimax-cn": "MINIMAX_CN_API_KEY",
|
||||
"openrouter": "OPENROUTER_API_KEY",
|
||||
# Local runtimes do not authenticate.
|
||||
"ollama": None,
|
||||
}
|
||||
|
||||
|
||||
def get_api_key_env(provider: str) -> Optional[str]:
|
||||
"""Return the env var name for `provider`'s API key, or None if not applicable.
|
||||
|
||||
Unknown providers also return None — callers should treat that as
|
||||
"no key check possible" rather than as "no key required".
|
||||
"""
|
||||
return PROVIDER_API_KEY_ENV.get(provider.lower())
|
||||
@@ -4,6 +4,7 @@ from typing import Any, Optional
|
||||
from langchain_core.messages import AIMessage
|
||||
from langchain_openai import ChatOpenAI
|
||||
|
||||
from .api_key_env import get_api_key_env
|
||||
from .base_client import BaseLLMClient, normalize_content
|
||||
from .capabilities import get_capabilities
|
||||
from .validators import validate_model
|
||||
@@ -135,26 +136,22 @@ _PASSTHROUGH_KWARGS = (
|
||||
"api_key", "callbacks", "http_client", "http_async_client",
|
||||
)
|
||||
|
||||
# Provider base URLs and API key env vars
|
||||
_PROVIDER_CONFIG = {
|
||||
"xai": ("https://api.x.ai/v1", "XAI_API_KEY"),
|
||||
"deepseek": ("https://api.deepseek.com", "DEEPSEEK_API_KEY"),
|
||||
# DashScope exposes two regional endpoints with separate accounts; an
|
||||
# international key won't authenticate against the China endpoint and
|
||||
# vice versa (fixes issue #758).
|
||||
"qwen": ("https://dashscope-intl.aliyuncs.com/compatible-mode/v1", "DASHSCOPE_API_KEY"),
|
||||
"qwen-cn": ("https://dashscope.aliyuncs.com/compatible-mode/v1", "DASHSCOPE_CN_API_KEY"),
|
||||
# Zhipu exposes the same GLM models under two brands with separate
|
||||
# accounts: Z.AI (international, api.z.ai) and BigModel
|
||||
# (open.bigmodel.cn, China). Keys aren't interchangeable across them.
|
||||
"glm": ("https://api.z.ai/api/paas/v4/", "ZHIPU_API_KEY"),
|
||||
"glm-cn": ("https://open.bigmodel.cn/api/paas/v4/", "ZHIPU_CN_API_KEY"),
|
||||
# MiniMax exposes two regional endpoints with separate keys; mainland
|
||||
# Chinese users hit .com while global users hit .io.
|
||||
"minimax": ("https://api.minimax.io/v1", "MINIMAX_API_KEY"),
|
||||
"minimax-cn": ("https://api.minimaxi.com/v1", "MINIMAX_CN_API_KEY"),
|
||||
"openrouter": ("https://openrouter.ai/api/v1", "OPENROUTER_API_KEY"),
|
||||
"ollama": ("http://localhost:11434/v1", None),
|
||||
# Provider base URLs. API-key env vars live in api_key_env.PROVIDER_API_KEY_ENV
|
||||
# (one canonical mapping consulted by both this client and the CLI's
|
||||
# interactive key-prompt). Dual-region providers (qwen/glm/minimax) keep
|
||||
# separate endpoints because international and China accounts cannot share
|
||||
# credentials (#758).
|
||||
_PROVIDER_BASE_URL = {
|
||||
"xai": "https://api.x.ai/v1",
|
||||
"deepseek": "https://api.deepseek.com",
|
||||
"qwen": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1",
|
||||
"qwen-cn": "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
||||
"glm": "https://api.z.ai/api/paas/v4/",
|
||||
"glm-cn": "https://open.bigmodel.cn/api/paas/v4/",
|
||||
"minimax": "https://api.minimax.io/v1",
|
||||
"minimax-cn": "https://api.minimaxi.com/v1",
|
||||
"openrouter": "https://openrouter.ai/api/v1",
|
||||
"ollama": "http://localhost:11434/v1",
|
||||
}
|
||||
|
||||
|
||||
@@ -185,9 +182,9 @@ class OpenAIClient(BaseLLMClient):
|
||||
# Provider-specific base URL and auth. An explicit base_url on the
|
||||
# client (e.g. a corporate proxy) takes precedence over the
|
||||
# provider default so users can route through their own gateway.
|
||||
if self.provider in _PROVIDER_CONFIG:
|
||||
default_base, api_key_env = _PROVIDER_CONFIG[self.provider]
|
||||
llm_kwargs["base_url"] = self.base_url or default_base
|
||||
if self.provider in _PROVIDER_BASE_URL:
|
||||
llm_kwargs["base_url"] = self.base_url or _PROVIDER_BASE_URL[self.provider]
|
||||
api_key_env = get_api_key_env(self.provider)
|
||||
if api_key_env:
|
||||
api_key = os.environ.get(api_key_env)
|
||||
if api_key:
|
||||
|
||||
Reference in New Issue
Block a user