mirror of
https://github.com/TauricResearch/TradingAgents.git
synced 2026-06-17 05:16:14 +03:00
Merge remote-tracking branch 'upstream/main' into crypto-analysis-mvp
# Conflicts: # cli/utils.py # tradingagents/agents/analysts/social_media_analyst.py # tradingagents/agents/researchers/bear_researcher.py
This commit is contained in:
138
cli/utils.py
138
cli/utils.py
@@ -1,9 +1,13 @@
|
||||
import questionary
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import List, Optional, Tuple, Dict
|
||||
|
||||
import questionary
|
||||
from dotenv import find_dotenv, set_key
|
||||
from rich.console import Console
|
||||
|
||||
from cli.models import AnalystType, AssetType
|
||||
from tradingagents.llm_clients.api_key_env import get_api_key_env
|
||||
from tradingagents.llm_clients.model_catalog import get_model_options
|
||||
|
||||
console = Console()
|
||||
@@ -12,7 +16,7 @@ TICKER_INPUT_EXAMPLES = "Examples: SPY, CNC.TO, 7203.T, 0700.HK"
|
||||
|
||||
ANALYST_ORDER = [
|
||||
("Market Analyst", AnalystType.MARKET),
|
||||
("Social Media Analyst", AnalystType.SOCIAL),
|
||||
("Sentiment Analyst", AnalystType.SOCIAL),
|
||||
("News Analyst", AnalystType.NEWS),
|
||||
("Fundamentals Analyst", AnalystType.FUNDAMENTALS),
|
||||
]
|
||||
@@ -264,8 +268,9 @@ def select_llm_provider() -> tuple[str, str | None]:
|
||||
("Anthropic", "anthropic", "https://api.anthropic.com/"),
|
||||
("xAI", "xai", "https://api.x.ai/v1"),
|
||||
("DeepSeek", "deepseek", "https://api.deepseek.com"),
|
||||
("Qwen", "qwen", "https://dashscope.aliyuncs.com/compatible-mode/v1"),
|
||||
("Qwen", "qwen", "https://dashscope-intl.aliyuncs.com/compatible-mode/v1"),
|
||||
("GLM", "glm", "https://open.bigmodel.cn/api/paas/v4/"),
|
||||
("MiniMax", "minimax", "https://api.minimax.io/v1"),
|
||||
("OpenRouter", "openrouter", "https://openrouter.ai/api/v1"),
|
||||
("Azure OpenAI", "azure", None),
|
||||
("Ollama", "ollama", "http://localhost:11434/v1"),
|
||||
@@ -316,7 +321,9 @@ def ask_openai_reasoning_effort() -> str:
|
||||
def ask_anthropic_effort() -> str | None:
|
||||
"""Ask for Anthropic effort level.
|
||||
|
||||
Controls token usage and response thoroughness on Claude 4.5+ and 4.6 models.
|
||||
Controls token usage and response thoroughness on Claude 4.5 / 4.6 / 4.7
|
||||
models. The API also accepts "max"; we expose low/medium/high as the
|
||||
common selection range.
|
||||
"""
|
||||
return questionary.select(
|
||||
"Select Effort Level:",
|
||||
@@ -353,6 +360,129 @@ def ask_gemini_thinking_config() -> str | None:
|
||||
).ask()
|
||||
|
||||
|
||||
def ask_glm_region() -> tuple[str, str]:
|
||||
"""Ask which GLM platform (Z.AI international vs BigModel China) to use.
|
||||
|
||||
Zhipu serves the same GLM models under two brands with separate
|
||||
accounts; keys aren't interchangeable. Returns (provider_key, backend_url).
|
||||
"""
|
||||
return questionary.select(
|
||||
"Select GLM platform:",
|
||||
choices=[
|
||||
questionary.Choice(
|
||||
"Z.AI — api.z.ai (international, uses ZHIPU_API_KEY)",
|
||||
value=("glm", "https://api.z.ai/api/paas/v4/"),
|
||||
),
|
||||
questionary.Choice(
|
||||
"BigModel — open.bigmodel.cn (China, uses ZHIPU_CN_API_KEY)",
|
||||
value=("glm-cn", "https://open.bigmodel.cn/api/paas/v4/"),
|
||||
),
|
||||
],
|
||||
style=questionary.Style([
|
||||
("selected", "fg:cyan noinherit"),
|
||||
("highlighted", "fg:cyan noinherit"),
|
||||
("pointer", "fg:cyan noinherit"),
|
||||
]),
|
||||
).ask()
|
||||
|
||||
|
||||
def ask_qwen_region() -> tuple[str, str]:
|
||||
"""Ask which Qwen region (international vs China) to use.
|
||||
|
||||
Alibaba DashScope exposes two endpoints with separate accounts —
|
||||
a key from one region does NOT authenticate against the other
|
||||
(fixes #758). Returns (provider_key, backend_url).
|
||||
"""
|
||||
return questionary.select(
|
||||
"Select Qwen region:",
|
||||
choices=[
|
||||
questionary.Choice(
|
||||
"International — dashscope-intl.aliyuncs.com (uses DASHSCOPE_API_KEY)",
|
||||
value=("qwen", "https://dashscope-intl.aliyuncs.com/compatible-mode/v1"),
|
||||
),
|
||||
questionary.Choice(
|
||||
"China — dashscope.aliyuncs.com (uses DASHSCOPE_CN_API_KEY)",
|
||||
value=("qwen-cn", "https://dashscope.aliyuncs.com/compatible-mode/v1"),
|
||||
),
|
||||
],
|
||||
style=questionary.Style([
|
||||
("selected", "fg:cyan noinherit"),
|
||||
("highlighted", "fg:cyan noinherit"),
|
||||
("pointer", "fg:cyan noinherit"),
|
||||
]),
|
||||
).ask()
|
||||
|
||||
|
||||
def ask_minimax_region() -> tuple[str, str]:
|
||||
"""Ask which MiniMax region (global vs China) to use.
|
||||
|
||||
MiniMax exposes two endpoints with separate accounts — a key from
|
||||
one region does NOT authenticate against the other. Returns
|
||||
(provider_key, backend_url).
|
||||
"""
|
||||
return questionary.select(
|
||||
"Select MiniMax region:",
|
||||
choices=[
|
||||
questionary.Choice(
|
||||
"Global — api.minimax.io (uses MINIMAX_API_KEY)",
|
||||
value=("minimax", "https://api.minimax.io/v1"),
|
||||
),
|
||||
questionary.Choice(
|
||||
"China — api.minimaxi.com (uses MINIMAX_CN_API_KEY)",
|
||||
value=("minimax-cn", "https://api.minimaxi.com/v1"),
|
||||
),
|
||||
],
|
||||
style=questionary.Style([
|
||||
("selected", "fg:cyan noinherit"),
|
||||
("highlighted", "fg:cyan noinherit"),
|
||||
("pointer", "fg:cyan noinherit"),
|
||||
]),
|
||||
).ask()
|
||||
|
||||
|
||||
def ensure_api_key(provider: str) -> Optional[str]:
|
||||
"""Make sure the API key for `provider` is available in the environment.
|
||||
|
||||
If the env var is already set, returns its value untouched. Otherwise
|
||||
interactively prompts the user, persists the value to the project's
|
||||
.env file via python-dotenv's set_key (creating .env if needed), and
|
||||
exports it into os.environ so the current process picks it up.
|
||||
|
||||
Returns None for providers that do not require a key (e.g. ollama)
|
||||
and for providers not found in the canonical mapping.
|
||||
"""
|
||||
env_var = get_api_key_env(provider)
|
||||
if env_var is None:
|
||||
return None # ollama / unknown — no key check possible
|
||||
|
||||
existing = os.environ.get(env_var)
|
||||
if existing:
|
||||
return existing
|
||||
|
||||
console.print(
|
||||
f"\n[yellow]{env_var} is not set in your environment.[/yellow]"
|
||||
)
|
||||
key = questionary.password(
|
||||
f"Paste your {env_var} (will be saved to .env):",
|
||||
style=questionary.Style([
|
||||
("text", "fg:cyan"),
|
||||
("highlighted", "noinherit"),
|
||||
]),
|
||||
).ask()
|
||||
if not key:
|
||||
console.print(
|
||||
f"[red]Skipped. API calls will fail until {env_var} is set.[/red]"
|
||||
)
|
||||
return None
|
||||
|
||||
env_path = find_dotenv(usecwd=True) or str(Path.cwd() / ".env")
|
||||
Path(env_path).touch(exist_ok=True)
|
||||
set_key(env_path, env_var, key)
|
||||
os.environ[env_var] = key
|
||||
console.print(f"[green]Saved {env_var} to {env_path}[/green]")
|
||||
return key
|
||||
|
||||
|
||||
def ask_output_language() -> str:
|
||||
"""Ask for report output language."""
|
||||
choice = questionary.select(
|
||||
|
||||
Reference in New Issue
Block a user