How nGPT Manages and Switches Between Multiple LLM Provider Configurations Dynamically
nGPT stores multiple LLM provider configurations in a JSON file and dynamically selects between them at runtime using CLI flags, environment variables, or config indices, enabling seamless provider switching without code changes.
The open-source project nazdridoy/ngpt implements a flexible configuration system that allows users to manage and switch between multiple LLM provider configurations dynamically. Rather than hardcoding provider details, nGPT maintains a portable configuration file that supports multiple provider entries, enabling users to switch between OpenAI, local Ollama instances, or other compatible APIs through simple command-line arguments.
Storing Multiple LLM Provider Configurations
nGPT persists provider settings in a JSON configuration file located at ~/.config/ngpt/ngpt.conf (or a custom path specified via --config). The file accepts either a single configuration object or an array of provider configurations, making it possible to define multiple LLM backends simultaneously.
Each provider entry requires four key fields:
[
{
"api_key": "sk-openai-123",
"base_url": "https://api.openai.com/v1/",
"provider": "OpenAI",
"model": "gpt-4o"
},
{
"api_key": "local-key",
"base_url": "http://127.0.0.1:11434/v1/",
"provider": "Ollama",
"model": "llama3"
}
]
This structure allows nGPT to manage and switch between multiple LLM provider configurations dynamically by treating each array element as a distinct, selectable backend.
Loading and Selecting Configurations Dynamically
The configuration management logic resides in ngpt/core/config.py, which exposes two primary functions for handling provider selection at runtime.
Loading All Provider Configurations
The load_configs() function (lines 22-38) reads the JSON configuration file and normalizes the output to a list of dictionaries. This function handles both single-object and array-based configurations, ensuring backward compatibility while enabling multi-provider setups.
def load_configs(custom_path: Optional[str] = None) -> List[Dict[str, Any]]:
config_path = get_config_path(custom_path)
if config_path.exists():
with open(config_path, "r") as f:
file_configs = json.load(f)
# Accept both a single dict and a list of dicts
if isinstance(file_configs, dict):
configs = [file_configs]
else:
configs = file_configs
return configs
Selecting a Specific Provider by Name or Index
The load_config() function (lines 22-62) implements the dynamic selection logic. It accepts either a provider name or a config_index parameter to determine which configuration entry to activate.
When --provider is specified, the function performs a case-insensitive search through the configuration list. If multiple entries match the provider name, nGPT prompts the user to disambiguate. If no match exists, it falls back to the entry at the specified index (defaulting to 0).
def load_config(..., config_index: int = 0, provider: Optional[str] = None) -> Dict[str, Any]:
configs = load_configs(custom_path)
if provider:
matching = [c for c in configs if c.get("provider", "").lower() == provider.lower()]
# Handle no match / multiple matches / single match logic
...
if config_index < 0 or config_index >= len(configs):
config_index = 0
config = configs[config_index]
return config
Environment Variable Overrides
After selecting a configuration, load_config() applies environment variable overrides to enable dynamic credential management without modifying the configuration file. The function checks for OPENAI_API_KEY, OPENAI_BASE_URL, and OPENAI_MODEL, updating the selected configuration dictionary with any present values.
# Environment-variable overrides
for env_var, key in env_mapping.items():
if env_var in os.environ:
if key == "api_key" or os.environ[env_var]:
config[key] = os.environ[env_var]
CLI Priority Resolution and Client Initialization
The ngpt/cli/handlers/client_handler.py file orchestrates the final configuration resolution and client instantiation, ensuring that user preferences from various sources merge correctly.
Resolving Configuration Priority
The process_config_selection() function (lines 17-45) establishes a clear precedence hierarchy for configuration sources:
- Explicit CLI flags (
--provider,--config-index) take highest priority - CLI configuration file (
ngpt-cli.conf) values apply when CLI flags are absent - Environment variables override file-based settings
- Main configuration file (
ngpt.conf) provides defaults
This function also enforces mutual exclusivity between --provider and --config-index, exiting with an error if both are specified simultaneously.
def process_config_selection(args, cli_config):
effective_provider = args.provider
effective_config_index = args.config_index
# CLI-config is used only when the user didn't set the flag explicitly
if '--provider' not in sys.argv and 'provider' in cli_config:
effective_provider = cli_config['provider']
if '--config-index' not in sys.argv and 'config-index' in cli_config:
effective_config_index = cli_config['config-index']
# Mutually exclusive check
if effective_config_index != 0 and effective_provider:
sys.exit(2) # error reported to user
return effective_provider, effective_config_index
Runtime Overrides and Client Instantiation
The initialize_client() function (lines 49-80) completes the dynamic provider switching workflow. It loads the selected configuration, applies any runtime CLI overrides (--api-key, --base-url, --model), and instantiates the NGPTClient class from ngpt/api/client.py.
Because load_config() returns a fresh dictionary on each invocation, the CLI can switch providers dynamically between commands without restarting the process or modifying persistent files.
def initialize_client(args, cli_config):
eff_provider, eff_index = process_config_selection(args, cli_config)
active_config = load_config(args.config, eff_index, eff_provider)
# CLI overrides
if args.api_key is not None:
active_config["api_key"] = args.api_key
if args.base_url is not None:
active_config["base_url"] = args.base_url
if args.model is not None:
active_config["model"] = args.model
client = NGPTClient(
api_key=active_config.get("api_key"),
base_url=active_config.get("base_url"),
provider=active_config.get("provider"),
model=active_config.get("model")
)
return client, active_config
Practical Examples for Dynamic Provider Switching
The following examples demonstrate how to manage and switch between multiple LLM provider configurations dynamically using nGPT's CLI interface.
Define Multiple Providers in the Configuration File
Create or edit ~/.config/ngpt/ngpt.conf to include multiple provider entries:
[
{
"api_key": "sk-openai-abc123",
"base_url": "https://api.openai.com/v1/",
"provider": "OpenAI",
"model": "gpt-4o"
},
{
"api_key": "ollama-local",
"base_url": "http://localhost:11434/v1/",
"provider": "Ollama",
"model": "llama3.1"
},
{
"api_key": "azure-key",
"base_url": "https://my-resource.openai.azure.com/openai/deployments/gpt-4/",
"provider": "Azure",
"model": "gpt-4"
}
]
Switch Providers by Name
Use the --provider flag to dynamically select a specific configuration without modifying the config file:
ngpt --provider Ollama "Explain quantum computing in simple terms"
nGPT searches the configuration array for a matching provider name (case-insensitive) and instantiates the client with the corresponding base URL and API key.
Switch Providers by Index
Reference configurations directly by their position in the array using --config-index:
ngpt --config-index 2 "Generate a SQL query for user analytics"
This selects the third entry (index 2) from the configuration list, useful when provider names are ambiguous or when scripting nGPT with numeric identifiers.
Override Specific Fields at Runtime
Combine provider selection with field-level overrides to modify behavior without editing persistent files:
ngpt --provider OpenAI --model gpt-3.5-turbo "Summarize this article" < article.txt
The --api-key, --base-url, and --model flags override values from the selected configuration, enabling temporary switches to different models within the same provider.
Summary
nGPT manages and switches between multiple LLM provider configurations dynamically through a layered configuration system:
- JSON-based storage: Provider credentials and endpoints are stored as an array in
ngpt.conf, supporting multiple simultaneous configurations - Flexible selection: The
load_config()function inngpt/core/config.pyenables selection by provider name (--provider) or array index (--config-index) - Priority resolution:
process_config_selection()inngpt/cli/handlers/client_handler.pyimplements a clear precedence hierarchy: CLI flags > CLI config > environment variables > main config - Runtime overrides: Individual fields (API key, base URL, model) can be overridden via CLI arguments or environment variables without modifying configuration files
- Dynamic instantiation: The
initialize_client()function creates freshNGPTClientinstances for each invocation, enabling provider switching without process restarts
Frequently Asked Questions
How does nGPT handle multiple providers with the same name?
When multiple configurations share the same provider name and you select via --provider <name>, nGPT identifies all matching entries and prompts you to choose the specific instance you want to use. If you prefer non-interactive selection, use --config-index to specify the exact array position instead of relying on name matching.
Can I switch providers without editing the configuration file?
Yes. nGPT supports complete dynamic provider switching through command-line flags. Use --provider to select by name or --config-index to select by position. Additionally, you can override specific connection parameters using --api-key, --base-url, and --model flags to temporarily redirect to different endpoints without modifying ngpt.conf.
What happens if both --provider and --config-index are specified?
nGPT treats these flags as mutually exclusive. The process_config_selection() function in ngpt/cli/handlers/client_handler.py explicitly checks for simultaneous usage of both flags and exits with error code 2 if detected. This prevents ambiguity in configuration selection and ensures predictable behavior.
How do environment variables interact with the configuration file?
Environment variables act as overrides to the selected configuration. After load_config() selects a provider entry (by name or index), it checks for OPENAI_API_KEY, OPENAI_BASE_URL, and OPENAI_MODEL environment variables. If present, these values replace the corresponding fields in the configuration dictionary before the client is instantiated, allowing sensitive credentials to be injected securely without storing them in files.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →