| name | lockfile |
| description | Use when creating llmring.lock file for new project (REQUIRED for all applications), configuring model aliases with semantic task-based names, managing environment-specific profiles (dev/staging/prod), or setting up fallback models - lockfile creation is mandatory first step, bundled lockfile is only for llmring tools |
Aliases, Profiles, and Lockfile Configuration
Installation
uv add llmring
pip install llmring
When to Create Your Own Lockfile
You MUST create your own llmring.lock for:
- ✅ Any real application
- ✅ Any library you're building
- ✅ Any code you're committing to git
The bundled lockfile that ships with llmring is ONLY for running llmring lock chat. It provides the "advisor" alias so the configuration assistant works. It is NOT for your application.
API Overview
This skill covers:
- Lockfile (
llmring.lock) structure and resolution
- Semantic alias naming (use task names, not performance descriptors)
- Profiles for environment-specific configuration
- Fallback models for automatic failover
- CLI commands for lockfile management
- Python API for alias operations
Quick Start
llmring lock init
llmring list --provider openai
llmring list --provider anthropic
llmring list --provider google
llmring bind summarizer "anthropic:claude-3-5-haiku-20241022"
llmring bind analyzer "openai:gpt-4o"
llmring lock chat
⚠️ Important: Always check llmring list --provider <name> for current model names before binding. Model names change frequently (e.g., claude-sonnet-4-5-20250929 → claude-sonnet-4-5-20250929).
Using aliases in code:
from llmring import LLMRing, LLMRequest, Message
async with LLMRing() as service:
request = LLMRequest(
model="summarizer",
messages=[Message(role="user", content="Hello")]
)
response = await service.chat(request)
Choosing Alias Names
Use domain-specific semantic names:
- ✅
"summarizer" - Clear what it does
- ✅
"code-reviewer" - Describes purpose
- ✅
"extractor" - Self-documenting
- ✅
"sql-generator" - Intent is obvious
Avoid generic performance descriptors:
- ❌
"fast", "balanced", "deep" - Don't describe the task
Generic names like "fast" appear in examples for illustration only. Real applications should use names that describe the task, not model characteristics.
Lockfile Resolution Order
LLMRing searches for lockfiles in this order:
- Explicit path via
lockfile_path parameter (must exist)
- Environment variable
LLMRING_LOCKFILE_PATH (must exist)
- Current directory
./llmring.lock (if exists)
- LLMRing's internal lockfile (only for
llmring lock chat - NOT for your app)
Example:
from llmring import LLMRing
async with LLMRing(lockfile_path="./my-llmring.lock") as service:
pass
Lockfile Structure
Lockfiles use TOML format:
version = "1.0"
default_profile = "default"
[profiles.default]
name = "default"
[[profiles.default.bindings]]
alias = "summarizer"
models = ["openai:gpt-4o-mini"]
[[profiles.default.bindings]]
alias = "analyzer"
models = [
"anthropic:claude-sonnet-4-5-20250929",
"openai:gpt-4o",
"google:gemini-2.5-pro"
]
[[profiles.default.bindings]]
alias = "code-reviewer"
models = ["anthropic:claude-sonnet-4-5-20250929"]
[profiles.dev]
name = "dev"
[[profiles.dev.bindings]]
alias = "assistant"
models = ["openai:gpt-4o-mini"]
[profiles.prod]
name = "prod"
[[profiles.prod.bindings]]
alias = "assistant"
models = ["anthropic:claude-sonnet-4-5-20250929"]
CLI Commands
llmring lock init
Create a new lockfile with registry-based defaults.
llmring lock init
llmring lock init --force
llmring lock init --file path/to/llmring.lock
What it does:
- Fetches recommended models from registry
- Creates default profile with common aliases
- Places lockfile in appropriate location
llmring bind
Bind an alias to one or more models.
llmring bind fast "openai:gpt-4o-mini"
llmring bind balanced "anthropic:claude-sonnet-4-5-20250929,openai:gpt-4o"
llmring bind assistant "openai:gpt-4o-mini" --profile dev
llmring bind assistant "anthropic:claude-opus-4" --profile prod
Format:
- Model references:
provider:model
- Multiple models: comma-separated for fallbacks
- Profile:
--profile name (defaults to "default")
llmring aliases
List all configured aliases.
llmring aliases
llmring aliases --profile dev
llmring aliases --verbose
Output:
fast → openai:gpt-4o-mini
balanced → anthropic:claude-sonnet-4-5-20250929 (+ 2 fallbacks)
deep → anthropic:claude-opus-4
llmring lock chat
Conversational lockfile management with AI advisor.
llmring lock chat
What it does:
- Natural language interface for configuration
- AI-powered recommendations based on registry
- Explains cost implications and tradeoffs
- Configures aliases with fallback models
- Sets up environment-specific profiles
Example session:
You: I need a fast, cheap model for development
Advisor: I recommend gpt-4o-mini - it's $0.15/$0.60 per million tokens...
You: Set that as my 'dev' alias
Advisor: Done! Added binding dev → openai:gpt-4o-mini
llmring lock validate
Validate lockfile structure and bindings.
llmring lock validate
llmring lock validate --file path/to/llmring.lock
Python API
LLMRing with Lockfile
from llmring import LLMRing
async with LLMRing() as service:
pass
async with LLMRing(lockfile_path="./custom.lock") as service:
pass
Resolving Aliases
from llmring import LLMRing
async with LLMRing() as service:
model_ref = service.resolve_alias("fast")
print(model_ref)
model_ref = service.resolve_alias("assistant", profile="dev")
print(model_ref)
Binding Aliases Programmatically
from llmring import LLMRing
async with LLMRing() as service:
service.bind_alias("myalias", "openai:gpt-4o")
service.bind_alias("assistant", "openai:gpt-4o-mini", profile="dev")
Listing Aliases
from llmring import LLMRing
async with LLMRing() as service:
aliases = service.list_aliases()
for alias, model in aliases.items():
print(f"{alias} → {model}")
aliases = service.list_aliases(profile="dev")
Unbinding Aliases
from llmring import LLMRing
async with LLMRing() as service:
service.unbind_alias("myalias")
service.unbind_alias("assistant", profile="dev")
Initializing Lockfile Programmatically
from llmring import LLMRing
async with LLMRing() as service:
service.init_lockfile()
service.init_lockfile(force=True)
Clearing Alias Cache
Aliases are cached for performance. Clear when updating lockfile:
from llmring import LLMRing
async with LLMRing() as service:
service.clear_alias_cache()
model = service.resolve_alias("fast")
Profiles: Environment-Specific Configuration
Profiles let you use different models in different environments.
Profile Setup
[profiles.dev]
name = "dev"
[[profiles.dev.bindings]]
alias = "assistant"
models = ["openai:gpt-4o-mini"]
[profiles.staging]
name = "staging"
[[profiles.staging.bindings]]
alias = "assistant"
models = ["anthropic:claude-sonnet-4-5-20250929"]
[profiles.prod]
name = "prod"
[[profiles.prod.bindings]]
alias = "assistant"
models = [
"anthropic:claude-opus-4",
"anthropic:claude-sonnet-4-5-20250929"
]
Using Profiles
Via environment variable:
export LLMRING_PROFILE=dev
python my_app.py
Via CLI:
llmring chat "Hello" --profile dev
llmring aliases --profile prod
In code:
from llmring import LLMRing, LLMRequest, Message
async with LLMRing() as service:
request = LLMRequest(
model="assistant",
messages=[Message(role="user", content="Hello")]
)
response = await service.chat(request, profile="dev")
response = await service.chat(request, profile="prod")
Profile Selection Priority
- Explicit parameter:
profile="dev" or --profile dev (highest)
- Environment variable:
LLMRING_PROFILE=dev
- Default:
default profile (lowest)
Common Profile Use Cases
- dev: Cheap, fast models for development
- test: Local models (Ollama) or mocks
- staging: Production-like but with cost savings
- prod: Highest quality models
- a-b-testing: Different models for the same alias
Fallback Models
Aliases can specify multiple models for automatic failover.
Lockfile:
[[profiles.default.bindings]]
alias = "balanced"
models = [
"anthropic:claude-sonnet-4-5-20250929",
"openai:gpt-4o",
"google:gemini-2.5-pro"
]
What happens:
async with LLMRing() as service:
request = LLMRequest(
model="assistant",
messages=[Message(role="user", content="Hello")]
)
response = await service.chat(request)
Use cases:
- High availability (failover on rate limits)
- Cost optimization (try cheaper first)
- Provider diversity (avoid single vendor lock-in)
Packaging Lockfiles with Your Application
To ship lockfiles with your Python package:
Add to pyproject.toml:
[tool.hatch.build]
include = [
"src/yourpackage/**/*.py",
"src/yourpackage/**/*.lock",
]
Or with setuptools, add to MANIFEST.in:
include src/yourpackage/*.lock
In your package:
mypackage/
├── src/
│ └── mypackage/
│ ├── __init__.py
│ └── llmring.lock # Ship with package
├── pyproject.toml
└── README.md
Users can then override:
from llmring import LLMRing
async with LLMRing() as service:
pass
async with LLMRing(lockfile_path="./my-llmring.lock") as service:
pass
Using LLMRing in Libraries
If building a library that uses LLMRing, follow this pattern:
Pattern:
- Ship with bundled
llmring.lock
- Accept
lockfile_path parameter for user override
- Validate required aliases in
__init__
- Document required aliases in README
Simple Library Example:
from pathlib import Path
from llmring import LLMRing
DEFAULT_LOCKFILE = Path(__file__).parent / "llmring.lock"
REQUIRED_ALIASES = ["summarizer"]
class MyLibrary:
def __init__(self, lockfile_path=None):
"""Initialize with optional custom lockfile.
Args:
lockfile_path: Optional path to custom lockfile.
If None, uses library's bundled lockfile.
Raises:
ValueError: If lockfile missing required aliases
"""
lockfile = lockfile_path or DEFAULT_LOCKFILE
self.ring = LLMRing(lockfile_path=lockfile)
self.ring.require_aliases(REQUIRED_ALIASES, context="my-library")
def summarize(self, text: str) -> str:
return self.ring.chat("summarizer", messages=[...]).content
Validation Helpers:
from llmring import LLMRing
ring = LLMRing(lockfile_path="./my.lock")
if ring.has_alias("summarizer"):
response = ring.chat("summarizer", messages=[...])
ring.require_aliases(
["summarizer", "analyzer"],
context="my-library"
)
Library Composition:
When Library B uses Library A, pass lockfile to both:
class LibraryB:
def __init__(self, lockfile_path=None):
lockfile = lockfile_path or DEFAULT_LOCKFILE
self.lib_a = LibraryA(lockfile_path=lockfile)
self.ring = LLMRing(lockfile_path=lockfile)
self.ring.require_aliases(REQUIRED_ALIASES, context="library-b")
User Override:
from my_library import MyLibrary
lib = MyLibrary()
lib = MyLibrary(lockfile_path="./my-models.lock")
Best Practices:
- Validate with
require_aliases() in __init__
- Document required aliases clearly
- Pass lockfile down when using other llmring libraries
Lockfile Composability
Libraries can ship lockfiles that consumers extend and override. This allows libraries to define their required aliases while giving consumers control over which models are used.
Extending Library Lockfiles
Consumer lockfiles can extend library lockfiles using the [extends] section. The [extends] section is optional - you can always define your own aliases without extending any libraries.
version = "1.0"
default_profile = "default"
[extends]
packages = ["libA", "libB"]
[profiles.default]
name = "default"
[[profiles.default.bindings]]
alias = "myalias"
models = ["openai:gpt-4o"]
[[profiles.default.bindings]]
alias = "summarizer"
models = ["anthropic:claude-3-5-haiku-20241022"]
Key points:
[extends] is optional - skip it if you don't need library lockfiles
- You can define your own aliases in
bindings regardless of extends
- Unqualified aliases (like
summarizer) only resolve from your local lockfile
- Namespaced aliases (like
libA:summarizer) resolve from extended libraries
- Extends are NOT recursive: If libA's lockfile has its own
[extends], those are ignored when resolving libA:alias. Resolution only goes one level deep.
When you extend a library, its aliases become available as namespaced aliases.
Namespaced Aliases
Use package:alias format to reference aliases from extended libraries:
from llmring import LLMRing, LLMRequest, Message
async with LLMRing() as service:
request = LLMRequest(
model="libA:summarizer",
messages=[Message(role="user", content="Summarize this...")]
)
response = await service.chat(request)
Resolution rules:
libA:summarizer → Checks consumer lockfile for override, then libA's lockfile
summarizer (unqualified) → Consumer lockfile only
Overriding Library Aliases
Consumers can override library aliases by defining them in their lockfile:
version = "1.0"
default_profile = "default"
[extends]
packages = ["libA"]
[profiles.default]
name = "default"
[[profiles.default.bindings]]
alias = "libA:summarizer"
models = ["openai:gpt-4o-mini"]
When the consumer's code (or libA's code) resolves libA:summarizer, it uses the consumer's override.
Validating Extended Lockfiles
Use llmring lock check to validate your extends configuration:
llmring lock check
Output:
Checking lockfile: ./llmring.lock
[extends]
✓ libA: found llmring.lock
Aliases: summarizer, analyzer
✓ libB: found llmring.lock
Aliases: translator
✗ libC: package not installed or has no llmring.lock
Warnings:
⚠ 'summarizer' defined in both libA and libB
Use libA:summarizer or libB:summarizer to disambiguate
Local aliases:
myalias → openai:gpt-4o
Summary: 2/3 packages OK, 3 extended aliases, 1 local aliases
Exit codes:
- 0: All OK
- 1: Missing packages or lockfiles
- 2: Lockfile parse error
Library Authors: Supporting Composability
When building a library that uses LLMRing:
1. Ship a bundled lockfile with your package:
mylib/
├── __init__.py
└── llmring.lock # Bundled with package
2. Include lockfile in package distribution:
[tool.hatch.build]
include = ["src/mylib/**/*.lock"]
3. Use require_aliases() to validate at startup:
from llmring import LLMRing
class MyLibrary:
def __init__(self):
self.ring = LLMRing()
self.ring.require_aliases(["mylib:summarizer"], context="mylib")
4. Document which namespaced aliases your library needs:
## Required Aliases
This library requires:
- `mylib:summarizer` - Used for text summarization
Extend our lockfile in yours:
\`\`\`toml
[extends]
packages = ["mylib"]
\`\`\`
Alias Naming for Libraries
Consider using prefixed names to avoid conflicts:
[[profiles.default.bindings]]
alias = "summarizer"
models = ["anthropic:claude-3-5-haiku-20241022"]
The namespace (mylib:) is added automatically when consumers extend your library.
Common Patterns
Development vs Production
export LLMRING_PROFILE=dev
llmring bind assistant "openai:gpt-4o-mini" --profile dev
llmring bind assistant "anthropic:claude-opus-4" --profile prod
Semantic Aliases
llmring bind summarizer "openai:gpt-4o-mini"
llmring bind analyst "anthropic:claude-sonnet-4-5-20250929"
llmring bind coder "openai:gpt-4o"
Use in code:
summarizer_request = LLMRequest(model="summarizer", ...)
analyst_request = LLMRequest(model="analyst", ...)
coder_request = LLMRequest(model="coder", ...)
Multi-Region Deployments
[profiles.us-west]
[[profiles.us-west.bindings]]
alias = "assistant"
models = ["openai:gpt-4o"]
[profiles.eu-central]
[[profiles.eu-central.bindings]]
alias = "assistant"
models = ["anthropic:claude-sonnet-4-5-20250929"]
Common Mistakes
Wrong: Hardcoding Model IDs
request = LLMRequest(
model="openai:gpt-4o-mini",
messages=[...]
)
Right: Use Semantic Aliases
request = LLMRequest(
model="summarizer",
messages=[...]
)
Wrong: No Fallback Models
[[profiles.default.bindings]]
alias = "assistant"
models = ["anthropic:claude-sonnet-4-5-20250929"]
Right: Include Fallbacks
[[profiles.default.bindings]]
alias = "assistant"
models = [
"anthropic:claude-sonnet-4-5-20250929",
"openai:gpt-4o",
"google:gemini-2.5-pro"
]
Wrong: Not Using Profiles
if os.getenv("ENV") == "dev":
model = "openai:gpt-4o-mini"
else:
model = "anthropic:claude-opus-4"
request = LLMRequest(model=model, ...)
Right: Use Profiles
request = LLMRequest(model="assistant", ...)
Wrong: Invalid Model References
llmring bind fast "gpt-4o-mini"
Right: Provider:Model Format
llmring bind fast "openai:gpt-4o-mini"
Best Practices
- Use semantic aliases: Names like "fast", "balanced", "analyst" are clearer than model IDs
- Configure fallbacks: Always have backup models for high availability
- Use profiles for environments: Different models for dev/staging/prod
- Ship lockfiles with packages: Include in your package distribution
- Use conversational config:
llmring lock chat for easy setup
- Document aliases: In your README, explain what each alias is for
- Clear cache after updates: Call
clear_alias_cache() after lockfile changes
Error Handling
from llmring import LLMRing
from llmring.exceptions import ModelNotFoundError
async with LLMRing() as service:
try:
model_ref = service.resolve_alias("myalias")
except ModelNotFoundError:
print("Alias not found in lockfile")
try:
request = LLMRequest(model="myalias", messages=[...])
response = await service.chat(request)
except ModelNotFoundError:
print("Could not resolve alias to available model")
Related Skills
llmring-chat - Basic chat using aliases
llmring-streaming - Streaming with aliases
llmring-tools - Tools with aliased models
llmring-structured - Structured output with aliases
llmring-providers - Direct provider access (bypassing aliases)
Summary
Lockfiles provide:
- Semantic aliases (readable, maintainable)
- Automatic failover (high availability)
- Environment-specific configs (dev/staging/prod)
- Centralized model management
- Easy model updates without code changes
Recommendation: Always use aliases instead of direct model references for flexibility and maintainability.