ADR 013 · Accepted

Three-Level Configuration Architecture (Provider-Model-Configuration)

Context

The nr_llm extension needs to manage LLM configurations for various use cases (chat, translation, embeddings, etc.). Initially, configurations were stored in a single table mixing connection settings, model parameters, and use-case-specific prompts.

Problem statement

A single-table approach creates several issues:

  1. API Key Duplication: Same API key repeated across multiple configurations.

  2. Model Redundancy: Model capabilities and pricing duplicated.

  3. Inflexible Connections: Cannot have multiple API keys for same provider (prod/dev).

  4. Mixed Concerns: Connection details, model specs, and prompts intermingled.

  5. Maintenance Burden: Changing an API key requires updating multiple records.

Real-world scenarios not supported

Scenario

Single-Table Problem

Separate prod/dev OpenAI accounts

Must duplicate all configurations

Self-hosted Ollama + cloud fallback

Cannot model multiple endpoints

Cost tracking per API key

No clear key-to-usage mapping

Model catalog with shared pricing

Model specs repeated everywhere

Team-specific API keys

No multi-tenancy support

Decision

Implement a three-level hierarchical architecture separating concerns:

┌─────────────────────────────────────────────────────────────────────────┐
│ CONFIGURATION (Use-Case Specific)                                        │
│ "blog-summarizer", "product-description", "support-translator"          │
│                                                                          │
│ Fields: system_prompt, temperature, max_tokens, top_p, use_case_type    │
│ References: model_uid → Model                                            │
└──────────────────────────────────┬──────────────────────────────────────┘
                                   │ N:1
┌──────────────────────────────────▼──────────────────────────────────────┐
│ MODEL (Available Models)                                                 │
│ "gpt-5", "claude-sonnet-4-5", "llama-70b", "text-embedding-3-large"     │
│                                                                          │
│ Fields: model_id, context_length, capabilities, cost_input, cost_output │
│ References: provider_uid → Provider                                      │
└──────────────────────────────────┬──────────────────────────────────────┘
                                   │ N:1
┌──────────────────────────────────▼──────────────────────────────────────┐
│ PROVIDER (API Connections)                                               │
│ "openai-prod", "openai-dev", "local-ollama", "azure-openai-eu"          │
│                                                                          │
│ Fields: endpoint_url, api_key (encrypted), adapter_type, timeout        │
└─────────────────────────────────────────────────────────────────────────┘

Level 1: Provider (Connection Layer)

Represents a specific API connection with credentials.

tx_nrllm_provider
├── identifier        -- Unique slug: "openai-prod", "ollama-local"
├── name              -- Display name: "OpenAI Production"
├── adapter_type      -- Protocol: openai, anthropic, gemini, ollama...
├── endpoint_url      -- Custom endpoint (empty = default)
├── api_key           -- Encrypted API key
├── organization_id   -- Optional org ID (OpenAI)
├── timeout           -- Request timeout in seconds
├── max_retries       -- Retry count on failure
└── options           -- JSON: additional adapter options

Key Design Points:

  • One provider = one API key = one billing relationship.

  • Same adapter type can have multiple providers (prod/dev accounts).

  • Adapter type determines the protocol/client class used.

Level 2: Model (Capability Layer)

Represents a specific model available through a provider.

tx_nrllm_model
├── identifier        -- Unique slug: "gpt-5", "claude-sonnet"
├── name              -- Display name: "GPT-5 (128K)"
├── provider_uid      -- FK → Provider
├── model_id          -- API model identifier: "gpt-5"
├── context_length    -- Token limit: 128000
├── max_output_tokens -- Output limit: 16384
├── capabilities      -- CSV: chat,vision,streaming,tools
├── cost_input        -- Cents per 1M input tokens
├── cost_output       -- Cents per 1M output tokens
└── is_default        -- Default model for this provider

Key Design Points:

  • Models belong to exactly one provider.

  • Capabilities define what the model can do.

  • Pricing stored as integers (cents/1M tokens) to avoid float issues.

  • Same logical model can exist multiple times (different providers).

Level 3: Configuration (Use-Case Layer)

Represents a specific use case with model and prompt settings.

tx_nrllm_configuration
├── identifier        -- Unique slug: "blog-summarizer"
├── name              -- Display name: "Blog Post Summarizer"
├── model_uid         -- FK → Model
├── system_prompt     -- System message for the model
├── temperature       -- Creativity: 0.0 - 2.0
├── max_tokens        -- Response length limit
├── top_p             -- Nucleus sampling
├── presence_penalty  -- Topic diversity
├── frequency_penalty -- Word repetition penalty
└── use_case_type     -- chat, completion, embedding, translation

Key Design Points:

  • Configurations reference models, not providers directly.

  • All LLM parameters are tunable per use case.

  • Same model can be used by multiple configurations.

Relationships

┌────────────┐       ┌─────────┐       ┌───────────────┐
│ Provider   │ 1───N │ Model   │ 1───N │ Configuration │
└────────────┘       └─────────┘       └───────────────┘
     │                    │                    │
     │ api_key            │ model_id           │ system_prompt
     │ endpoint           │ capabilities       │ temperature
     │ adapter_type       │ pricing            │ max_tokens
     └────────────────────┴────────────────────┘
Entity Responsibilities

Entity

Responsibility

Changes When

Provider

API authentication & connection

API key rotates, endpoint changes

Model

Capabilities & pricing

New model version, pricing update

Configuration

Use-case behavior

Prompt tuning, parameter adjustment

Implementation

Database tables

Domain models

Service layer access

Backend module structure

Admin Tools → LLM
├── Dashboard      (overview, stats)
├── Providers      (CRUD, connection test)
├── Models         (CRUD, fetch from API)
└── Configurations (CRUD, prompt testing)

Consequences

Positive

●● Single Source of Truth: API key stored once per provider.

●● Flexible Connections: Multiple providers of same type (prod/dev/backup).

Model Catalog: Centralized model specs and pricing.

Clear Separation: Connection vs capability vs use-case concerns.

Easy Key Rotation: Update one provider, all configs inherit.

Cost Tracking: Usage attributable to specific providers.

Multi-Tenancy Ready: Different API keys per team/project.

Negative

Increased Complexity: Three tables instead of one.

More Joins: Queries must traverse relationships.

Migration Required: Existing data needs transformation.

Learning Curve: Users must understand hierarchy.

Net Score: +5 (Strong positive)

Trade-offs

Single Table

Three-Level

Simple queries

Normalized data

Data duplication

Referential integrity

Faster reads

Smaller storage

Harder maintenance

Easier updates

Alternatives considered

1. Two-Level (Provider → Configuration)

Rejected: Models would be embedded in configurations, duplicating capabilities/pricing.

2. Four-Level (Provider → Model → Preset → Configuration)

Rejected: Preset layer adds complexity without clear benefit. Temperature/token settings belong with use-case.

3. Single Table with JSON Columns

Rejected: Loses referential integrity, harder to query, no normalization.

4. Configuration Inheritance

Rejected: Complex to implement, confusing precedence rules.

Future considerations

  1. Model Auto-Discovery: Fetch available models from provider APIs.

  2. Cost Aggregation: Track usage and costs per provider/model.

  3. Fallback Chains: Configuration → fallback model if primary fails.

  4. Rate Limiting: Per-provider rate limit tracking.

  5. Health Monitoring: Provider availability status.

References

← All ADRs