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
|
||||
|
||||
import questionary
|
||||
from rich.console import Console
|
||||
|
||||
from cli.models import AnalystType
|
||||
|
||||
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"
|
||||
|
||||
ANALYST_ORDER = [
|
||||
|
|
@ -136,43 +152,6 @@ def select_research_depth() -> int:
|
|||
def select_shallow_thinking_agent(provider) -> str:
|
||||
"""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(
|
||||
"Select Your [Quick-Thinking LLM Engine]:",
|
||||
choices=[
|
||||
|
|
@ -201,45 +180,6 @@ def select_shallow_thinking_agent(provider) -> str:
|
|||
def select_deep_thinking_agent(provider) -> str:
|
||||
"""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(
|
||||
"Select Your [Deep-Thinking LLM Engine]:",
|
||||
choices=[
|
||||
|
|
@ -264,16 +204,6 @@ def select_deep_thinking_agent(provider) -> str:
|
|||
|
||||
def select_llm_provider() -> tuple[str, str]:
|
||||
"""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(
|
||||
"Select your LLM Provider:",
|
||||
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
|
||||
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 = {
|
||||
"project_dir": os.path.abspath(os.path.join(os.path.dirname(__file__), ".")),
|
||||
|
|
@ -8,10 +21,10 @@ DEFAULT_CONFIG = {
|
|||
"dataflows/data_cache",
|
||||
),
|
||||
# LLM settings
|
||||
"llm_provider": "openai",
|
||||
"deep_think_llm": "gpt-5.2",
|
||||
"quick_think_llm": "gpt-5-mini",
|
||||
"backend_url": "https://api.openai.com/v1",
|
||||
"llm_provider": DEFAULT_PROVIDER,
|
||||
"deep_think_llm": DEFAULT_LLM_SETTINGS.get("deep_think_llm", "gpt-5.2"),
|
||||
"quick_think_llm": DEFAULT_LLM_SETTINGS.get("quick_think_llm", "gpt-5-mini"),
|
||||
"backend_url": DEFAULT_BACKEND_URL,
|
||||
# Provider-specific thinking configuration
|
||||
"google_thinking_level": None, # "high", "minimal", etc.
|
||||
"openai_reasoning_effort": None, # "medium", "high", "low"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from langchain_openai import ChatOpenAI
|
||||
|
||||
from .base_client import BaseLLMClient, normalize_content
|
||||
|
|
@ -24,11 +27,23 @@ _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"),
|
||||
"openrouter": ("https://openrouter.ai/api/v1", "OPENROUTER_API_KEY"),
|
||||
"ollama": ("http://localhost:11434/v1", None),
|
||||
CONFIG_PATH = Path(__file__).resolve().parents[2] / "config.json"
|
||||
with CONFIG_PATH.open("r", encoding="utf-8") as config_file:
|
||||
CONFIG = json.load(config_file)
|
||||
|
||||
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}
|
||||
|
||||
# Provider-specific base URL and auth
|
||||
if self.provider in _PROVIDER_CONFIG:
|
||||
base_url, api_key_env = _PROVIDER_CONFIG[self.provider]
|
||||
if self.provider in _PROVIDER_BASE_URLS:
|
||||
base_url = _PROVIDER_BASE_URLS[self.provider]
|
||||
llm_kwargs["base_url"] = base_url
|
||||
api_key_env = _PROVIDER_API_KEY_ENV.get(self.provider)
|
||||
if api_key_env:
|
||||
api_key = os.environ.get(api_key_env)
|
||||
if api_key:
|
||||
llm_kwargs["api_key"] = api_key
|
||||
else:
|
||||
elif self.provider == "ollama":
|
||||
llm_kwargs["api_key"] = "ollama"
|
||||
elif self.base_url:
|
||||
llm_kwargs["base_url"] = self.base_url
|
||||
|
|
|
|||
Loading…
Reference in New Issue