refactor(config): centralize LLM configuration in config.json
Remove .env.example and move LLM provider settings, base URLs, and model options to a centralized config.json file. Update default_config.py, openai_client.py, and cli/utils.py to load configuration from this file, improving maintainability and reducing hardcoded values across the codebase.
This commit is contained in:
parent
589b351f2a
commit
065d033faf
|
|
@ -1,6 +0,0 @@
|
||||||
# LLM Providers (set the one you use)
|
|
||||||
OPENAI_API_KEY=
|
|
||||||
GOOGLE_API_KEY=
|
|
||||||
ANTHROPIC_API_KEY=
|
|
||||||
XAI_API_KEY=
|
|
||||||
OPENROUTER_API_KEY=
|
|
||||||
104
cli/utils.py
104
cli/utils.py
|
|
@ -1,12 +1,28 @@
|
||||||
import questionary
|
import json
|
||||||
|
from pathlib import Path
|
||||||
from typing import List, Optional, Tuple, Dict
|
from typing import List, Optional, Tuple, Dict
|
||||||
|
|
||||||
|
import questionary
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
|
|
||||||
from cli.models import AnalystType
|
from cli.models import AnalystType
|
||||||
|
|
||||||
console = Console()
|
console = Console()
|
||||||
|
|
||||||
|
CONFIG_PATH = Path(__file__).resolve().parents[1] / "config.json"
|
||||||
|
with CONFIG_PATH.open("r", encoding="utf-8") as config_file:
|
||||||
|
CONFIG = json.load(config_file)
|
||||||
|
|
||||||
|
BASE_URLS = [(display, url) for display, url in CONFIG["BASE_URLS"]]
|
||||||
|
DEEP_AGENT_OPTIONS = {
|
||||||
|
provider: [(display, value) for display, value in options]
|
||||||
|
for provider, options in CONFIG["DEEP_AGENT_OPTIONS"].items()
|
||||||
|
}
|
||||||
|
SHALLOW_AGENT_OPTIONS = {
|
||||||
|
provider: [(display, value) for display, value in options]
|
||||||
|
for provider, options in CONFIG["SHALLOW_AGENT_OPTIONS"].items()
|
||||||
|
}
|
||||||
|
|
||||||
TICKER_INPUT_EXAMPLES = "Examples: SPY, CNC.TO, 7203.T, 0700.HK"
|
TICKER_INPUT_EXAMPLES = "Examples: SPY, CNC.TO, 7203.T, 0700.HK"
|
||||||
|
|
||||||
ANALYST_ORDER = [
|
ANALYST_ORDER = [
|
||||||
|
|
@ -136,43 +152,6 @@ def select_research_depth() -> int:
|
||||||
def select_shallow_thinking_agent(provider) -> str:
|
def select_shallow_thinking_agent(provider) -> str:
|
||||||
"""Select shallow thinking llm engine using an interactive selection."""
|
"""Select shallow thinking llm engine using an interactive selection."""
|
||||||
|
|
||||||
# Define shallow thinking llm engine options with their corresponding model names
|
|
||||||
# Ordering: medium → light → heavy (balanced first for quick tasks)
|
|
||||||
# Within same tier, newer models first
|
|
||||||
SHALLOW_AGENT_OPTIONS = {
|
|
||||||
"openai": [
|
|
||||||
("GPT-5 Mini - Balanced speed, cost, and capability", "gpt-5-mini"),
|
|
||||||
("GPT-5 Nano - High-throughput, simple tasks", "gpt-5-nano"),
|
|
||||||
("GPT-5.4 - Latest frontier, 1M context", "gpt-5.4"),
|
|
||||||
("GPT-4.1 - Smartest non-reasoning model", "gpt-4.1"),
|
|
||||||
],
|
|
||||||
"anthropic": [
|
|
||||||
("Claude Sonnet 4.6 - Best speed and intelligence balance", "claude-sonnet-4-6"),
|
|
||||||
("Claude Haiku 4.5 - Fast, near-instant responses", "claude-haiku-4-5"),
|
|
||||||
("Claude Sonnet 4.5 - Agents and coding", "claude-sonnet-4-5"),
|
|
||||||
],
|
|
||||||
"google": [
|
|
||||||
("Gemini 3 Flash - Next-gen fast", "gemini-3-flash-preview"),
|
|
||||||
("Gemini 2.5 Flash - Balanced, stable", "gemini-2.5-flash"),
|
|
||||||
("Gemini 3.1 Flash Lite - Most cost-efficient", "gemini-3.1-flash-lite-preview"),
|
|
||||||
("Gemini 2.5 Flash Lite - Fast, low-cost", "gemini-2.5-flash-lite"),
|
|
||||||
],
|
|
||||||
"xai": [
|
|
||||||
("Grok 4.1 Fast (Non-Reasoning) - Speed optimized, 2M ctx", "grok-4-1-fast-non-reasoning"),
|
|
||||||
("Grok 4 Fast (Non-Reasoning) - Speed optimized", "grok-4-fast-non-reasoning"),
|
|
||||||
("Grok 4.1 Fast (Reasoning) - High-performance, 2M ctx", "grok-4-1-fast-reasoning"),
|
|
||||||
],
|
|
||||||
"openrouter": [
|
|
||||||
("NVIDIA Nemotron 3 Nano 30B (free)", "nvidia/nemotron-3-nano-30b-a3b:free"),
|
|
||||||
("Z.AI GLM 4.5 Air (free)", "z-ai/glm-4.5-air:free"),
|
|
||||||
],
|
|
||||||
"ollama": [
|
|
||||||
("Qwen3:latest (8B, local)", "qwen3:latest"),
|
|
||||||
("GPT-OSS:latest (20B, local)", "gpt-oss:latest"),
|
|
||||||
("GLM-4.7-Flash:latest (30B, local)", "glm-4.7-flash:latest"),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
choice = questionary.select(
|
choice = questionary.select(
|
||||||
"Select Your [Quick-Thinking LLM Engine]:",
|
"Select Your [Quick-Thinking LLM Engine]:",
|
||||||
choices=[
|
choices=[
|
||||||
|
|
@ -201,45 +180,6 @@ def select_shallow_thinking_agent(provider) -> str:
|
||||||
def select_deep_thinking_agent(provider) -> str:
|
def select_deep_thinking_agent(provider) -> str:
|
||||||
"""Select deep thinking llm engine using an interactive selection."""
|
"""Select deep thinking llm engine using an interactive selection."""
|
||||||
|
|
||||||
# Define deep thinking llm engine options with their corresponding model names
|
|
||||||
# Ordering: heavy → medium → light (most capable first for deep tasks)
|
|
||||||
# Within same tier, newer models first
|
|
||||||
DEEP_AGENT_OPTIONS = {
|
|
||||||
"openai": [
|
|
||||||
("GPT-5.4 - Latest frontier, 1M context", "gpt-5.4"),
|
|
||||||
("GPT-5.2 - Strong reasoning, cost-effective", "gpt-5.2"),
|
|
||||||
("GPT-5 Mini - Balanced speed, cost, and capability", "gpt-5-mini"),
|
|
||||||
("GPT-5.4 Pro - Most capable, expensive ($30/$180 per 1M tokens)", "gpt-5.4-pro"),
|
|
||||||
],
|
|
||||||
"anthropic": [
|
|
||||||
("Claude Opus 4.6 - Most intelligent, agents and coding", "claude-opus-4-6"),
|
|
||||||
("Claude Opus 4.5 - Premium, max intelligence", "claude-opus-4-5"),
|
|
||||||
("Claude Sonnet 4.6 - Best speed and intelligence balance", "claude-sonnet-4-6"),
|
|
||||||
("Claude Sonnet 4.5 - Agents and coding", "claude-sonnet-4-5"),
|
|
||||||
],
|
|
||||||
"google": [
|
|
||||||
("Gemini 3.1 Pro - Reasoning-first, complex workflows", "gemini-3.1-pro-preview"),
|
|
||||||
("Gemini 3 Flash - Next-gen fast", "gemini-3-flash-preview"),
|
|
||||||
("Gemini 2.5 Pro - Stable pro model", "gemini-2.5-pro"),
|
|
||||||
("Gemini 2.5 Flash - Balanced, stable", "gemini-2.5-flash"),
|
|
||||||
],
|
|
||||||
"xai": [
|
|
||||||
("Grok 4 - Flagship model", "grok-4-0709"),
|
|
||||||
("Grok 4.1 Fast (Reasoning) - High-performance, 2M ctx", "grok-4-1-fast-reasoning"),
|
|
||||||
("Grok 4 Fast (Reasoning) - High-performance", "grok-4-fast-reasoning"),
|
|
||||||
("Grok 4.1 Fast (Non-Reasoning) - Speed optimized, 2M ctx", "grok-4-1-fast-non-reasoning"),
|
|
||||||
],
|
|
||||||
"openrouter": [
|
|
||||||
("Z.AI GLM 4.5 Air (free)", "z-ai/glm-4.5-air:free"),
|
|
||||||
("NVIDIA Nemotron 3 Nano 30B (free)", "nvidia/nemotron-3-nano-30b-a3b:free"),
|
|
||||||
],
|
|
||||||
"ollama": [
|
|
||||||
("GLM-4.7-Flash:latest (30B, local)", "glm-4.7-flash:latest"),
|
|
||||||
("GPT-OSS:latest (20B, local)", "gpt-oss:latest"),
|
|
||||||
("Qwen3:latest (8B, local)", "qwen3:latest"),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
choice = questionary.select(
|
choice = questionary.select(
|
||||||
"Select Your [Deep-Thinking LLM Engine]:",
|
"Select Your [Deep-Thinking LLM Engine]:",
|
||||||
choices=[
|
choices=[
|
||||||
|
|
@ -264,16 +204,6 @@ def select_deep_thinking_agent(provider) -> str:
|
||||||
|
|
||||||
def select_llm_provider() -> tuple[str, str]:
|
def select_llm_provider() -> tuple[str, str]:
|
||||||
"""Select the OpenAI api url using interactive selection."""
|
"""Select the OpenAI api url using interactive selection."""
|
||||||
# Define OpenAI api options with their corresponding endpoints
|
|
||||||
BASE_URLS = [
|
|
||||||
("OpenAI", "https://api.openai.com/v1"),
|
|
||||||
("Google", "https://generativelanguage.googleapis.com/v1"),
|
|
||||||
("Anthropic", "https://api.anthropic.com/"),
|
|
||||||
("xAI", "https://api.x.ai/v1"),
|
|
||||||
("Openrouter", "https://openrouter.ai/api/v1"),
|
|
||||||
("Ollama", "http://localhost:11434/v1"),
|
|
||||||
]
|
|
||||||
|
|
||||||
choice = questionary.select(
|
choice = questionary.select(
|
||||||
"Select your LLM Provider:",
|
"Select your LLM Provider:",
|
||||||
choices=[
|
choices=[
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
{
|
||||||
|
"BASE_URLS": [
|
||||||
|
["OpenAI", "https://api.openai.com/v1"],
|
||||||
|
["Google", "https://generativelanguage.googleapis.com/v1"],
|
||||||
|
["Anthropic", "https://api.anthropic.com/"],
|
||||||
|
["xAI", "https://api.x.ai/v1"],
|
||||||
|
["Openrouter", "https://openrouter.ai/api/v1"],
|
||||||
|
["Ollama", "http://localhost:11434/v1"],
|
||||||
|
["LMStudio", "http://localhost:1234/v1"]
|
||||||
|
],
|
||||||
|
"SHALLOW_AGENT_OPTIONS": {
|
||||||
|
"openai": [
|
||||||
|
["GPT-5 Mini - Balanced speed, cost, and capability", "gpt-5-mini"],
|
||||||
|
["GPT-5 Nano - High-throughput, simple tasks", "gpt-5-nano"],
|
||||||
|
["GPT-5.4 - Latest frontier, 1M context", "gpt-5.4"],
|
||||||
|
["GPT-4.1 - Smartest non-reasoning model", "gpt-4.1"]
|
||||||
|
],
|
||||||
|
"anthropic": [
|
||||||
|
["Claude Sonnet 4.6 - Best speed and intelligence balance", "claude-sonnet-4-6"],
|
||||||
|
["Claude Haiku 4.5 - Fast, near-instant responses", "claude-haiku-4-5"],
|
||||||
|
["Claude Sonnet 4.5 - Agents and coding", "claude-sonnet-4-5"]
|
||||||
|
],
|
||||||
|
"google": [
|
||||||
|
["Gemini 3 Flash - Next-gen fast", "gemini-3-flash-preview"],
|
||||||
|
["Gemini 2.5 Flash - Balanced, stable", "gemini-2.5-flash"],
|
||||||
|
["Gemini 3.1 Flash Lite - Most cost-efficient", "gemini-3.1-flash-lite-preview"],
|
||||||
|
["Gemini 2.5 Flash Lite - Fast, low-cost", "gemini-2.5-flash-lite"]
|
||||||
|
],
|
||||||
|
"xai": [
|
||||||
|
["Grok 4.1 Fast (Non-Reasoning) - Speed optimized, 2M ctx", "grok-4-1-fast-non-reasoning"],
|
||||||
|
["Grok 4 Fast (Non-Reasoning) - Speed optimized", "grok-4-fast-non-reasoning"],
|
||||||
|
["Grok 4.1 Fast (Reasoning) - High-performance, 2M ctx", "grok-4-1-fast-reasoning"]
|
||||||
|
],
|
||||||
|
"openrouter": [
|
||||||
|
["NVIDIA Nemotron 3 Nano 30B (free)", "nvidia/nemotron-3-nano-30b-a3b:free"],
|
||||||
|
["Z.AI GLM 4.5 Air (free)", "z-ai/glm-4.5-air:free"]
|
||||||
|
],
|
||||||
|
"ollama": [
|
||||||
|
["Qwen3:latest (8B, local)", "qwen3:latest"],
|
||||||
|
["GPT-OSS:latest (20B, local)", "gpt-oss:latest"],
|
||||||
|
["GLM-4.7-Flash:latest (30B, local)", "glm-4.7-flash:latest"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"DEEP_AGENT_OPTIONS": {
|
||||||
|
"openai": [
|
||||||
|
["GPT-5.4 - Latest frontier, 1M context", "gpt-5.4"],
|
||||||
|
["GPT-5.2 - Strong reasoning, cost-effective", "gpt-5.2"],
|
||||||
|
["GPT-5 Mini - Balanced speed, cost, and capability", "gpt-5-mini"],
|
||||||
|
["GPT-5.4 Pro - Most capable, expensive ($30/$180 per 1M tokens)", "gpt-5.4-pro"]
|
||||||
|
],
|
||||||
|
"anthropic": [
|
||||||
|
["Claude Opus 4.6 - Most intelligent, agents and coding", "claude-opus-4-6"],
|
||||||
|
["Claude Opus 4.5 - Premium, max intelligence", "claude-opus-4-5"],
|
||||||
|
["Claude Sonnet 4.6 - Best speed and intelligence balance", "claude-sonnet-4-6"],
|
||||||
|
["Claude Sonnet 4.5 - Agents and coding", "claude-sonnet-4-5"]
|
||||||
|
],
|
||||||
|
"google": [
|
||||||
|
["Gemini 3.1 Pro - Reasoning-first, complex workflows", "gemini-3.1-pro-preview"],
|
||||||
|
["Gemini 3 Flash - Next-gen fast", "gemini-3-flash-preview"],
|
||||||
|
["Gemini 2.5 Pro - Stable pro model", "gemini-2.5-pro"],
|
||||||
|
["Gemini 2.5 Flash - Balanced, stable", "gemini-2.5-flash"]
|
||||||
|
],
|
||||||
|
"xai": [
|
||||||
|
["Grok 4 - Flagship model", "grok-4-0709"],
|
||||||
|
["Grok 4.1 Fast (Reasoning) - High-performance, 2M ctx", "grok-4-1-fast-reasoning"],
|
||||||
|
["Grok 4 Fast (Reasoning) - High-performance", "grok-4-fast-reasoning"],
|
||||||
|
["Grok 4.1 Fast (Non-Reasoning) - Speed optimized, 2M ctx", "grok-4-1-fast-non-reasoning"]
|
||||||
|
],
|
||||||
|
"openrouter": [
|
||||||
|
["Z.AI GLM 4.5 Air (free)", "z-ai/glm-4.5-air:free"],
|
||||||
|
["NVIDIA Nemotron 3 Nano 30B (free)", "nvidia/nemotron-3-nano-30b-a3b:free"]
|
||||||
|
],
|
||||||
|
"ollama": [
|
||||||
|
["GLM-4.7-Flash:latest (30B, local)", "glm-4.7-flash:latest"],
|
||||||
|
["GPT-OSS:latest (20B, local)", "gpt-oss:latest"],
|
||||||
|
["Qwen3:latest (8B, local)", "qwen3:latest"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"DEFAULT_LLM_SETTINGS": {
|
||||||
|
"llm_provider": "openai",
|
||||||
|
"deep_think_llm": "gpt-5.2",
|
||||||
|
"quick_think_llm": "gpt-5-mini"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,17 @@
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
CONFIG_PATH = Path(__file__).resolve().parents[1] / "config.json"
|
||||||
|
with CONFIG_PATH.open("r", encoding="utf-8") as config_file:
|
||||||
|
CONFIG = json.load(config_file)
|
||||||
|
|
||||||
|
DEFAULT_LLM_SETTINGS = CONFIG.get("DEFAULT_LLM_SETTINGS", {})
|
||||||
|
BASE_URLS = {display.lower(): url for display, url in CONFIG.get("BASE_URLS", [])}
|
||||||
|
DEFAULT_PROVIDER = DEFAULT_LLM_SETTINGS.get("llm_provider", "openai").lower()
|
||||||
|
DEFAULT_BACKEND_URL = BASE_URLS.get(
|
||||||
|
DEFAULT_PROVIDER, BASE_URLS.get("openai", "https://api.openai.com/v1")
|
||||||
|
)
|
||||||
|
|
||||||
DEFAULT_CONFIG = {
|
DEFAULT_CONFIG = {
|
||||||
"project_dir": os.path.abspath(os.path.join(os.path.dirname(__file__), ".")),
|
"project_dir": os.path.abspath(os.path.join(os.path.dirname(__file__), ".")),
|
||||||
|
|
@ -8,10 +21,10 @@ DEFAULT_CONFIG = {
|
||||||
"dataflows/data_cache",
|
"dataflows/data_cache",
|
||||||
),
|
),
|
||||||
# LLM settings
|
# LLM settings
|
||||||
"llm_provider": "openai",
|
"llm_provider": DEFAULT_PROVIDER,
|
||||||
"deep_think_llm": "gpt-5.2",
|
"deep_think_llm": DEFAULT_LLM_SETTINGS.get("deep_think_llm", "gpt-5.2"),
|
||||||
"quick_think_llm": "gpt-5-mini",
|
"quick_think_llm": DEFAULT_LLM_SETTINGS.get("quick_think_llm", "gpt-5-mini"),
|
||||||
"backend_url": "https://api.openai.com/v1",
|
"backend_url": DEFAULT_BACKEND_URL,
|
||||||
# Provider-specific thinking configuration
|
# Provider-specific thinking configuration
|
||||||
"google_thinking_level": None, # "high", "minimal", etc.
|
"google_thinking_level": None, # "high", "minimal", etc.
|
||||||
"openai_reasoning_effort": None, # "medium", "high", "low"
|
"openai_reasoning_effort": None, # "medium", "high", "low"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
from langchain_openai import ChatOpenAI
|
from langchain_openai import ChatOpenAI
|
||||||
|
|
||||||
from .base_client import BaseLLMClient, normalize_content
|
from .base_client import BaseLLMClient, normalize_content
|
||||||
|
|
@ -24,11 +27,23 @@ _PASSTHROUGH_KWARGS = (
|
||||||
"api_key", "callbacks", "http_client", "http_async_client",
|
"api_key", "callbacks", "http_client", "http_async_client",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Provider base URLs and API key env vars
|
CONFIG_PATH = Path(__file__).resolve().parents[2] / "config.json"
|
||||||
_PROVIDER_CONFIG = {
|
with CONFIG_PATH.open("r", encoding="utf-8") as config_file:
|
||||||
"xai": ("https://api.x.ai/v1", "XAI_API_KEY"),
|
CONFIG = json.load(config_file)
|
||||||
"openrouter": ("https://openrouter.ai/api/v1", "OPENROUTER_API_KEY"),
|
|
||||||
"ollama": ("http://localhost:11434/v1", None),
|
load_dotenv()
|
||||||
|
|
||||||
|
_BASE_URLS = {
|
||||||
|
display.lower(): url for display, url in CONFIG.get("BASE_URLS", [])
|
||||||
|
}
|
||||||
|
_PROVIDER_BASE_URLS = {
|
||||||
|
"xai": _BASE_URLS.get("xai", "https://api.x.ai/v1"),
|
||||||
|
"openrouter": _BASE_URLS.get("openrouter", "https://openrouter.ai/api/v1"),
|
||||||
|
"ollama": _BASE_URLS.get("ollama", "http://localhost:11434/v1"),
|
||||||
|
}
|
||||||
|
_PROVIDER_API_KEY_ENV = {
|
||||||
|
"xai": "XAI_API_KEY",
|
||||||
|
"openrouter": "OPENROUTER_API_KEY",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -56,14 +71,15 @@ class OpenAIClient(BaseLLMClient):
|
||||||
llm_kwargs = {"model": self.model}
|
llm_kwargs = {"model": self.model}
|
||||||
|
|
||||||
# Provider-specific base URL and auth
|
# Provider-specific base URL and auth
|
||||||
if self.provider in _PROVIDER_CONFIG:
|
if self.provider in _PROVIDER_BASE_URLS:
|
||||||
base_url, api_key_env = _PROVIDER_CONFIG[self.provider]
|
base_url = _PROVIDER_BASE_URLS[self.provider]
|
||||||
llm_kwargs["base_url"] = base_url
|
llm_kwargs["base_url"] = base_url
|
||||||
|
api_key_env = _PROVIDER_API_KEY_ENV.get(self.provider)
|
||||||
if api_key_env:
|
if api_key_env:
|
||||||
api_key = os.environ.get(api_key_env)
|
api_key = os.environ.get(api_key_env)
|
||||||
if api_key:
|
if api_key:
|
||||||
llm_kwargs["api_key"] = api_key
|
llm_kwargs["api_key"] = api_key
|
||||||
else:
|
elif self.provider == "ollama":
|
||||||
llm_kwargs["api_key"] = "ollama"
|
llm_kwargs["api_key"] = "ollama"
|
||||||
elif self.base_url:
|
elif self.base_url:
|
||||||
llm_kwargs["base_url"] = self.base_url
|
llm_kwargs["base_url"] = self.base_url
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue