Should Your Prompt Store Pick Your Model A developer argues that Langfuse's prompt configuration, which bundles model selection and parameters with prompt content, creates an untyped blob that conflates low-risk content changes with high-risk infrastructure decisions. Using Microsoft.Extensions.AI, they propose moving model selection to environment-specific configuration, binding the model at client construction time rather than in the prompt config. Langfuse https://langfuse.com with Microsoft.Extensions.AI has an appealing story: update prompts without redeploying. A prompt fetches its config blob—model, tokens, temperature—which the code passes straight to the LLM. It works. But it puts a boundary in what I'd suggest might be better placed elsewhere — and moving it is a small enough change to be worth exploring. This post is about where to move that line in a .NET codebase using Microsoft.Extensions.AI against OpenAI or Azure OpenAI, with Langfuse as the source of prompts. Let me be fair to it first, because the coupling is a deliberate design, not an accident. Langfuse's prompt config is an optional JSON object versioned alongside the prompt. That means someone can open the Langfuse UI, change the model or a parameter, and ship it — no code change, no redeploy. Combined with labels pointers to specific versions that your code references , a rollback is just moving the production label back to an earlier version. For prompt content iteration, that story is genuinely good, and there is a real audience of people who want model config coupled to prompt versions more tightly so each version is fully self-describing and reproducible. So this is a trade-off, not a bug. The question is whether the thing you are optimizing for — non-engineers tuning prompts without a deploy — is worth what the coupling costs. Three points stand out. It is an untyped blob feeding provider selection. The Langfuse config is arbitrary JSON without schema enforcement. On the other end, whatever LLM plumbing you use will treat that model string as authoritative. A missing key, a stray max tokens , or a gpt4o typo might not fail at build time or deploy time — it could fail on a live request, or silently do something unintended. You have a loosely-typed value driving an infrastructure decision, and the mistake may not surface until traffic hits it. It conflates two change lifecycles with different risk profiles. Prompt wording is a content decision: low risk, iterate freely. Which model runs and what the token ceiling is are infrastructure, cost, and reliability decisions with different review considerations. When both live in the same editable object, whoever edits prompts effectively has influence over production model selection and spend. That's considerable authority to place in a text field. Environment parameterization gets awkward. The config travels with the prompt version. So "cheap model in dev, strong model in prod" tends to push you toward label gymnastics or separate projects, rather than a straightforward per-environment mapping that lives with your other infra config. Here is the detail that makes this pleasant in Microsoft.Extensions.AI : for OpenAI and Azure OpenAI, the model identity is bound when you construct the client, not when you call it. IChatClient client = new AzureOpenAIClient new Uri endpoint , new DefaultAzureCredential .GetChatClient "my-deployment" // <-- model bound HERE .AsIChatClient ; The provider comes from which client type you build and its endpoint. The deployment comes from GetChatClient . By the time you have an IChatClient , it already knows what model it is. You barely need to touch ChatOptions.ModelId at all. That means model selection reduces to: pick the right pre-built client. And "pick a thing by name" is exactly what keyed dependency injection is for. So the plan is: extractor-strong .The prompt says what it wants done. Your infra config says which deployment and token budget executes it. Model wiring goes in appsettings.{Environment}.json , so the same aliases map to different deployments per environment: // appsettings.Production.json { "ModelRegistry": { "Models": { "extractor-strong": { "Provider": "AzureOpenAI", "Endpoint": "https://my-res.openai.azure.com/", "Deployment": "gpt-4o-prod", "MaxOutputTokensCeiling": 4096, "DefaultTemperature": 0 }, "summarizer-cheap": { "Provider": "AzureOpenAI", "Endpoint": "https://my-res.openai.azure.com/", "Deployment": "gpt-4o-mini", "MaxOutputTokensCeiling": 1024 } } } } js public sealed class ModelRegistryOptions { public const string Section = "ModelRegistry"; public Dictionary