# How to Structure Configurations for Large Language Models (LLMs) with Hypster

> Structure LLM configurations with Hypster using Python functions for typed parameters validation nesting and dot notation overrides with the instantiate API.

- Repository: [Gilad Rubin/hypster](https://github.com/gilad-rubin/hypster)
- Tags: how-to-guide
- Published: 2026-03-02

---

**Hypster structures LLM configurations as Python functions using an `HP` instance for typed parameters, enabling validation, nesting, and dot-notation overrides through the `instantiate()` API.**

Hypster is a lightweight, open-source configuration library designed to make LLM pipeline management declarative and type-safe. Instead of juggling YAML files or dictionaries, you define configurations as standard Python functions using the `HP` domain-specific language (DSL). This approach allows you to structure configurations for Large Language Models (LLMs) with Hypster using native Python syntax while gaining runtime validation and modular composition.

## Core Concepts for LLM Configuration

### The HP DSL for Typed Parameters

The `HP` class in [`src/hypster/hp.py`](https://github.com/gilad-rubin/hypster/blob/main/src/hypster/hp.py) provides a tiny DSL for declaring typed configuration parameters. Inside your configuration function, you access methods like `hp.select()`, `hp.float()`, `hp.int()`, and `hp.bool()` to define constraints such as allowed options, min/max bounds, and default values.

When `instantiate()` executes your configuration function, the `HP` object resolves raw input values against these declarations, falling back to defaults when overrides are not provided.

### Validation and Instantiation

Hypster validates configuration functions through `validate_config_func_signature` in [`src/hypster/utils.py`](https://github.com/gilad-rubin/hypster/blob/main/src/hypster/utils.py), which ensures the function accepts `hp: HP` as its first parameter. During instantiation, the `instantiate()` function in [`src/hypster/core.py`](https://github.com/gilad-rubin/hypster/blob/main/src/hypster/core.py) creates an `HP` instance, executes the configuration function to collect resolved values, and then invokes `_handle_unknown_parameters` to warn or raise errors if the caller supplied keys that were never consumed by any `hp.*` call.

## Building a Simple LLM Configuration

Start by defining a basic LLM configuration function:

```python
from hypster import HP, instantiate

def llm_cfg(hp: HP):
    # Choose a model name from a small whitelist

    model_name = hp.select(
        ["gpt-4o-mini", "gpt-4o"],
        name="model_name"
    )
    # Temperature must be between 0.0 and 1.0

    temperature = hp.float(0.0, name="temperature", min=0.0, max=1.0)
    # Maximum tokens allowed by the provider

    max_tokens = hp.int(256, name="max_tokens", max=2048)

    return {
        "model_name": model_name,
        "temperature": temperature,
        "max_tokens": max_tokens,
    }

```

Instantiate with defaults or overrides:

```python

# Instantiate the default configuration

default_cfg = instantiate(llm_cfg)

# Override a couple of values for a "creative" run

creative_cfg = instantiate(
    llm_cfg,
    values={"model_name": "gpt-4o", "temperature": 1.0}
)

print(default_cfg)

# {'model_name': 'gpt-4o-mini', 'temperature': 0.0, 'max_tokens': 256}

print(creative_cfg)

# {'model_name': 'gpt-4o', 'temperature': 1.0, 'max_tokens': 256}

```

## Hierarchical Configurations with Nesting

For complex LLM pipelines, use `hp.nest()` to compose configurations:

```python
from hypster import HP, instantiate

# Re-use the LLM config defined above

def qa_pipeline_cfg(hp: HP):
    llm = hp.nest(llm_cfg, name="llm")   # nested config

    # QA-specific parameters

    max_context = hp.int(1500, name="max_context", min=500, max=3000)

    return {
        "llm": llm,                     # expose the nested dict

        "max_context": max_context,
    }

```

Override nested values using dot notation:

```python
pipeline_cfg = instantiate(
    qa_pipeline_cfg,
    values={
        "llm.model_name": "gpt-4o",
        "llm.temperature": 0.7,
        "max_context": 2000,
    },
)

print(pipeline_cfg["llm"]["model_name"])   # gpt-4o

print(pipeline_cfg["max_context"])         # 2000

```

The `nest` implementation in [`src/hypster/hp.py`](https://github.com/gilad-rubin/hypster/blob/main/src/hypster/hp.py) handles prefix reservation, value extraction, and propagation of called-parameter tracking to prevent duplicate or conflicting names across the hierarchy.

## Loading External Configurations

Save and load configurations from disk for modularity:

```python

# Save the basic LLM config to a file

llm_cfg.save("configs/llm.py")

def rag_cfg(hp: HP):
    # Load and nest the saved LLM config

    llm = hp.nest("configs/llm.py", name="llm")
    # Retrieval-specific parameters

    top_k = hp.int(10, name="top_k", min=1, max=100)

    return {"llm": llm, "top_k": top_k}

```

```python
rag = instantiate(
    rag_cfg,
    values={"llm.model_name": "gpt-4o-mini", "top_k": 5}
)

```

## Summary

- Structure LLM configurations as Python functions accepting `hp: HP` as the first argument, validated by `validate_config_func_signature` in [`src/hypster/utils.py`](https://github.com/gilad-rubin/hypster/blob/main/src/hypster/utils.py).
- Use the `HP` DSL (`select`, `float`, `int`, `nest`) to declare typed parameters with defaults and constraints in [`src/hypster/hp.py`](https://github.com/gilad-rubin/hypster/blob/main/src/hypster/hp.py).
- Instantiate configurations via `instantiate()` from [`src/hypster/core.py`](https://github.com/gilad-rubin/hypster/blob/main/src/hypster/core.py), which validates signatures, resolves values, and detects unknown parameters via `_handle_unknown_parameters`.
- Compose hierarchical LLM pipelines using `hp.nest()` to embed sub-configurations while maintaining a single flat namespace addressable via dot notation (e.g., `"llm.temperature"`).
- Save and load configuration modules to disk for reusable, version-controlled LLM components.

## Frequently Asked Questions

### What is the HP class in Hypster?

The `HP` class is the core domain-specific language (DSL) for declaring typed configuration parameters. Defined in [`src/hypster/hp.py`](https://github.com/gilad-rubin/hypster/blob/main/src/hypster/hp.py), it provides methods like `select()`, `float()`, `int()`, and `nest()` that allow you to specify defaults, validation bounds, and allowed options. When `instantiate()` executes your configuration function, the `HP` object resolves raw input values against these declarations, falling back to defaults when overrides are not provided.

### How does Hypster validate LLM configuration functions?

Hypster validates configuration functions through `validate_config_func_signature` in [`src/hypster/utils.py`](https://github.com/gilad-rubin/hypster/blob/main/src/hypster/utils.py), which ensures the function accepts `hp: HP` as its first parameter. During instantiation, the `instantiate()` function in [`src/hypster/core.py`](https://github.com/gilad-rubin/hypster/blob/main/src/hypster/core.py) creates an `HP` instance, executes the configuration function to collect resolved values, and then invokes `_handle_unknown_parameters` to warn or raise errors if the caller supplied keys that were never consumed by any `hp.*` call. This guarantees that LLM configurations are strictly typed and free of orphaned parameters.

### Can I nest multiple LLM configurations within a single pipeline?

Yes, Hypster supports hierarchical composition through the `hp.nest()` method. You can embed one configuration function (or a saved module path) inside another, allowing you to build complex LLM pipelines—such as combining a retriever, LLM, and tokenizer—while maintaining a single flat namespace. The nested call receives a filtered view of the parent’s `values` dictionary, enabling dot-notation overrides like `"llm.temperature"` or `"retriever.top_k"`. The implementation in [`src/hypster/hp.py`](https://github.com/gilad-rubin/hypster/blob/main/src/hypster/hp.py) ensures that parameter names remain unique across the hierarchy by sharing a `called_params` set between parent and child configurations.

### How do I save and reuse LLM configurations across projects?

You can persist any configuration function to disk using the `.save()` method, which writes the function to a Python file. Later, you can reference this file path directly in `hp.nest()` to load and embed the saved configuration into a new pipeline. This approach enables version-controlled, reusable LLM components that can be shared across projects without copying code. For example, saving a standard `llm_cfg` to `"configs/llm.py"` allows multiple downstream configurations (RAG, chatbots, agents) to import and override it consistently using dot-notation keys like `"llm.model_name"`.