Intermediate14 min
Configurable Models
init_chat_model() lets you define a chain once and swap the underlying model at runtime via config — no redeploy needed. This article covers when that's worth the complexity, how config resolution actually works, what configurable_fields='any' silently exposes to attackers, and how to gate cost before your bill 50× overnight.
Quick Reference
- →init_chat_model() with no model = fully configurable; pass model name in config at invoke time
- →configurable_fields=("model", "temperature") exposes only those fields as runtime overrides
- →configurable_fields="any" also exposes api_key and base_url — never use in untrusted contexts
- →config_prefix="llm" scopes keys to llm_model, llm_temperature — required when chaining two configurable models
- →bind_tools() and with_structured_output() work on configurable models, but tool schemas vary by provider
- →Cost gate: validate the incoming model name against an allowlist before it reaches init_chat_model
- →with_fallbacks() wraps a configurable model to handle unavailable providers gracefully
When NOT to Use Configurable Models
Configurable models solve a real problem, but they add a resolution layer that makes debugging harder. Use them when the use case genuinely requires runtime model switching. Skip them when it doesn't.
| Scenario | Use configurable model? | Why |
|---|---|---|
| A/B testing models in production | Yes | Swap without redeploy; track per-model metrics |
| User-controlled model selection | Yes | Config comes from request context, not code |
| Multi-tenant with different model tiers | Yes | Model resolved per tenant at invoke time |
| Single internal service, model never changes | No | Hardcode it — adds zero value, adds indirection |
| Debug: try a different model locally | No | Just change the constant — no need for runtime config |
| Different models for different tasks in one chain | Yes, with config_prefix | Classifier cheap, generator expensive |
Configurable models obscure what model actually ran
When something goes wrong, you need to know which model was invoked. Always log config["configurable"] at the request layer — it won't appear in LangChain's default trace unless you explicitly capture it.