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:

  1. Explicit CLI flags (--provider, --config-index) take highest priority
  2. CLI configuration file (ngpt-cli.conf) values apply when CLI flags are absent
  3. Environment variables override file-based settings
  4. 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 in ngpt/core/config.py enables selection by provider name (--provider) or array index (--config-index)
  • Priority resolution: process_config_selection() in ngpt/cli/handlers/client_handler.py implements 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 fresh NGPTClient instances 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →