# Runtime Filters
Runtime filtering allows you to control which models are visible at load-time and during runtime without rebuilding the packaged snapshot.
## Overview
LLMDB separates build-time and load-time concerns:
- **Build-time** (Engine): Produces complete, unfiltered snapshots from sources
- **Load-time** (LLMDB.load): Applies filters and builds indexes
- **Runtime** (Runtime.apply): Updates filters dynamically without reload
The `base_models` list stored in the snapshot enables widening filters later without rebuilding from sources.
## Configuration Model
### Keys
Configure filters using the `:filter` key in your application config:
```elixir
config :llm_db,
filter: %{
allow: :all | %{provider_atom => [pattern_strings]},
deny: %{provider_atom => [pattern_strings]}
}
```
### Semantics
| Configuration | Behavior |
|--------------|----------|
| `allow: :all` | Everything passes unless denied (default) |
| `allow: %{}` | Same as `:all` (empty map) |
| `allow: %{provider: []}` | Blocks provider entirely |
| `allow: %{provider: ["pattern*"]}` | Only matching models pass |
| `deny: %{provider: ["pattern*"]}` | Always overrides allow |
**Provider Keys:**
- Accept atoms (`:openai`) or strings (`"openai"`)
- Unknown providers are warned and ignored
- Must correspond to existing provider atoms
**Pattern Types:**
- Glob: `"gpt-4*"`, `"*-preview"`, `"claude-3-haiku-*"`
- Regex: `~r/gpt-4.*/`, `~r/claude-3-\w+-20240307/`
- Globs compile to anchored regex for matching
## Common Recipes
### Allow only Haiku models across providers
```elixir
config :llm_db,
filter: %{
allow: %{
anthropic: ["claude-3-haiku-*"],
openrouter: ["anthropic/claude-3-haiku-*"]
},
deny: %{}
}
```
### Allow all except preview/beta models
```elixir
config :llm_db,
filter: %{
allow: :all,
deny: %{
openai: ["*-preview", "*-beta"],
google: ["*-experimental"]
}
}
```
### Block a provider entirely
```elixir
config :llm_db,
filter: %{
allow: %{
openai: [], # Empty list blocks provider
anthropic: ["claude-3-*"]
},
deny: %{}
}
```
Alternatively, simply omit the provider from a non-empty allow map:
```elixir
config :llm_db,
filter: %{
allow: %{
anthropic: ["claude-3-*"]
# openai implicitly blocked (not in allow map)
},
deny: %{}
}
```
### Carve out exceptions with deny
```elixir
config :llm_db,
filter: %{
allow: %{anthropic: ["claude-3-haiku-*"]},
deny: %{anthropic: ["*-legacy"]}
}
```
This allows all Haiku models except legacy versions.
### Runtime override to switch families
Start with one configuration:
```elixir
# config/runtime.exs
config :llm_db,
filter: %{
allow: %{anthropic: ["claude-3-haiku-*"]},
deny: %{}
}
```
Then override at runtime to switch to different models:
```elixir
{:ok, _snapshot} = LLMDB.load(
runtime_overrides: %{
filter: %{
allow: %{
anthropic: ["claude-3.5-sonnet-*", "claude-3-opus-*"]
},
deny: %{}
}
}
)
```
### Programmatic update without reload
For hot updates during runtime without calling `load/1`:
```elixir
# Get current snapshot
snapshot = LLMDB.Store.snapshot()
# Apply new filters
{:ok, updated_snapshot} = LLMDB.Runtime.apply(snapshot, %{
filter: %{
allow: %{openai: ["gpt-4o-*"]},
deny: %{openai: ["*-preview"]}
}
})
# Update store (atomic swap)
LLMDB.Store.put!(updated_snapshot)
```
The `Runtime.apply/2` function:
- Recompiles filters and reapplies to `base_models`
- Rebuilds indexes (models_by_key, aliases_by_key, models_by_provider)
- Enables filter widening because it uses the full `base_models` list
- Returns `{:ok, snapshot}` or `{:error, reason}`
## Runtime Overrides
### Via LLMDB.load/1
Pass `runtime_overrides` to override config at load time:
```elixir
{:ok, _snapshot} = LLMDB.load(
runtime_overrides: %{
filter: %{allow: %{...}, deny: %{...}},
prefer: [:openai, :anthropic]
}
)
```
Runtime overrides take precedence over application config.
### Via LLMDB.Runtime.apply/2
For updates without reloading:
```elixir
snapshot = LLMDB.Store.snapshot()
{:ok, updated_snapshot} = LLMDB.Runtime.apply(snapshot, %{
filter: %{...}
})
LLMDB.Store.put!(updated_snapshot)
```
## Error Handling and Troubleshooting
### "Filters eliminated all models"
**Error:**
```
{:error, "llm_db: filters eliminated all models (allow: ..., deny: ...).
Use allow: :all to widen filters or remove deny patterns."}
```
**Causes:**
- Allow patterns match no models
- All models are denied
- Unknown providers in allow map (silently ignored, leaving no valid providers)
**Solutions:**
1. Check provider names for typos
2. Verify patterns match actual model IDs
3. Use `allow: :all` to widen filters
4. Remove or adjust deny patterns
### "Unknown providers in filter"
**Warning:**
```
llm_db: unknown provider(s) in filter: [:unknwon_provider].
Known providers: [:openai, :anthropic, ...].
Check spelling or remove unknown providers from configuration.
```
**Causes:**
- Typo in provider name
- Provider doesn't exist in snapshot
- String not converted to existing atom
**Solutions:**
1. Check spelling: `:openai` not `:open_ai`
2. Verify provider exists: `LLMDB.providers()` or `LLMDB.list_providers()`
3. Remove unknown provider from config
### Nothing shows for provider X
With a non-empty allow map, providers must be explicitly listed:
```elixir
# This BLOCKS openai (not in allow map)
config :llm_db,
filter: %{
allow: %{anthropic: ["claude-3-*"]}, # Only anthropic allowed
deny: %{}
}
# To include openai, add it to allow:
config :llm_db,
filter: %{
allow: %{
anthropic: ["claude-3-*"],
openai: ["gpt-4*"]
},
deny: %{}
}
```
### Check what's allowed
```elixir
LLMDB.allowed?("openai:gpt-4o-mini")
#=> true or false
LLMDB.allowed?({:openai, "gpt-4o-preview"})
#=> false (if denied by pattern)
{:ok, model} = LLMDB.model("openai:gpt-4o-mini")
LLMDB.allowed?(model)
#=> true
```
## Safety and Performance
### Provider Key Safety
- Provider keys use `String.to_existing_atom/1` to prevent atom leaks
- Unknown string keys are ignored with warnings
- Atom keys are validated against known providers
- Safe for use in production with untrusted config sources
### Pattern Performance
- Patterns compile once at load/override time
- Runtime matching is O(patterns-for-provider) per `allowed?/1` call
- Regex patterns are compiled and cached
- Typical pattern counts (<100 per provider) have negligible overhead
**Recommendations:**
- Prefer glob patterns (`"gpt-4*"`) over complex regex for readability
- Avoid untrusted user input for Regex patterns (config should be trusted)
- For thousands of patterns, consider pre-filtering at source level
### Filter Widening
Runtime filter updates can widen or narrow because they operate on `base_models`:
```elixir
# Start narrow
LLMDB.load(runtime_overrides: %{
filter: %{allow: %{anthropic: ["claude-3-haiku-*"]}, deny: %{}}
})
#=> Only Haiku models visible
# Widen later
snapshot = LLMDB.Store.snapshot()
{:ok, snapshot} = LLMDB.Runtime.apply(snapshot, %{
filter: %{allow: %{anthropic: ["claude-3-*"]}, deny: %{}}
})
LLMDB.Store.put!(snapshot)
#=> All Claude 3 models now visible (pulled from base_models)
```
## Edge Cases
### Empty allow map
```elixir
config :llm_db,
filter: %{allow: %{}, deny: %{}}
```
This behaves like `allow: :all` (map size is 0).
### Allow with only unknown providers
```elixir
config :llm_db,
filter: %{
allow: %{nonexistent_provider: ["model-*"]},
deny: %{}
}
```
After filtering out unknown providers, allow becomes `%{}` (empty map), which acts like `:all`. You'll see a warning but load will succeed.
To actually restrict to specific providers, use known provider names:
```elixir
config :llm_db,
filter: %{
allow: %{anthropic: ["claude-3-haiku-*"]},
deny: %{}
}
```
### Regex safety
Regex from application config is trusted. Avoid accepting user-supplied Regex patterns:
```elixir
# Safe: config file
config :llm_db,
filter: %{allow: %{openai: [~r/gpt-4.*/]}, deny: %{}}
# Unsafe: user input (don't do this)
user_pattern = ~r/#{user_input}.*/ # Potential ReDoS attack
```
Stick to glob patterns for user-facing configuration.
## Migration from Old Format
If you previously used top-level `:allow` and `:deny` keys:
**Old:**
```elixir
config :llm_db,
allow: %{openai: ["gpt-4*"]},
deny: %{openai: ["*-preview"]}
```
**New:**
```elixir
config :llm_db,
filter: %{
allow: %{openai: ["gpt-4*"]},
deny: %{openai: ["*-preview"]}
}
```
The old format is no longer supported as of version 2025.11.7+. Update your configuration to use the singular `:filter` key.