PromptProvider is a .NET SDK for prompt fetching and prompt version management with:
- Langfuse integration for remote prompt storage/retrieval
- Local defaults as fallback for reliability
- Logical prompt keys mapped to actual Langfuse keys
- Optional label/version defaults per prompt
It is designed so your application code can ask for prompts by logical names while PromptProvider resolves mapping, label/version precedence, and fallback behavior.
dotnet add package PromptProviderRegister PromptProvider in DI:
using PromptProvider;
builder.Services.AddPromptProvider(
configureLangfuse: options => builder.Configuration.GetSection("Langfuse").Bind(options),
configurePrompts: options => builder.Configuration.GetSection("Prompts").Bind(options)
// configurePromptKeys is still supported for legacy split configuration
);Inject and use IPromptService:
using PromptProvider.Interfaces;
public class MyService
{
private readonly IPromptService _promptService;
public MyService(IPromptService promptService)
{
_promptService = promptService;
}
public async Task<string> GetWelcomeAsync(CancellationToken cancellationToken)
{
var prompt = await _promptService.GetPromptAsync("WelcomePrompt", cancellationToken: cancellationToken);
return prompt?.Content ?? "Fallback text";
}
}This is a great question — here is the exact meaning:
- Logical name (aka app key): the friendly key used in your code, for example
"WelcomePrompt". - Actual name (aka Langfuse key): the real prompt identifier stored in Langfuse, for example
"prompts.welcome".
So your app can call:
await _promptService.GetPromptAsync("WelcomePrompt", cancellationToken: ct);And PromptProvider internally resolves it to the actual Langfuse key:
WelcomePrompt -> prompts.welcome
This mapping comes from your Prompts.Entries configuration (Name = logical name, Key = actual Langfuse name).
PromptProvider supports both:
- Unified configuration (recommended)
- Legacy split configuration (
Prompts.Defaults,Prompts.ChatDefaults,PromptKeys)
{
"Langfuse": {
"BaseUrl": "https://api.langfuse.com",
"PublicKey": "your-public-key",
"SecretKey": "your-secret-key",
"HttpClient": {
"MaxConnectionsPerServer": 50,
"PooledConnectionLifetimeMinutes": 10,
"RequestTimeoutSeconds": 30
},
"Resilience": {
"Enabled": true,
"MaxRetries": 2,
"BaseDelayMs": 200
}
},
"Prompts": {
"Entries": [
{
"Name": "WelcomePrompt",
"Key": "prompts.welcome",
"Label": "production",
"Default": "Welcome to our system!"
},
{
"Name": "SupportChat",
"Key": "chat.support",
"ChatDefault": [
{ "Role": "system", "Content": "You are a helpful assistant." }
]
}
]
}
}Each prompt entry can include:
Name: logical/app key used in your code (GetPromptAsync("Name"))Key: actual Langfuse prompt key stored remotelyLabel: default label when caller does not pass oneVersion: default version when caller does not pass oneDefault: local text fallback promptChatDefault: local chat fallback prompt
All fields are optional except whatever your scenario needs. Example combinations:
- Key + label only
- Local default only
- Key + default + label
- Key + version + fallback
You can continue using:
Prompts.DefaultsPrompts.ChatDefaultsPromptKeys
PromptProvider merges these with unified entries when building resolved prompt configuration.
For GetPromptAsync and GetChatPromptAsync, precedence is:
- Explicit method args (
version,label) - Configured defaults from
Prompts.Entries/Prompts.PromptEntries(or legacyPromptKeys) - Langfuse default behavior
If an effective version exists, label is ignored.
- PromptProvider first tries Langfuse (when configured).
- If fetch fails or prompt is unavailable, PromptProvider returns local defaults (if configured).
- If no local fallback exists, result is
null.
These are optional and configurable by SDK consumers.
MaxConnectionsPerServerPooledConnectionLifetimeMinutesRequestTimeoutSeconds
Use these to tune throughput and connection reuse based on your traffic profile.
Enabled(defaultfalse)MaxRetries(default2)BaseDelayMs(default200)
When enabled, transient HTTP errors are retried with exponential backoff.
var prompt = await _promptService.GetPromptAsync("WelcomePrompt", label: "production", cancellationToken: ct);
if (prompt is not null)
{
Console.WriteLine(prompt.Content);
Console.WriteLine(prompt.Source); // PromptSource.Langfuse or PromptSource.Local
}var chatPrompt = await _promptService.GetChatPromptAsync("SupportChat", cancellationToken: ct);
var messages = chatPrompt?.ChatMessages;var prompts = await _promptService.GetPromptsAsync(
new[] { "WelcomePrompt", "SystemPrompt", "UnknownPrompt" },
cancellationToken: ct);
// Successful prompts are returned even if some keys fail.await _promptService.CreatePromptAsync(new CreatePromptRequest
{
PromptKey = "WelcomePrompt",
Content = "Welcome to our system!",
CommitMessage = "Update welcome text"
}, ct);await _promptService.CreateChatPromptAsync(new CreateChatPromptRequest
{
PromptKey = "SupportChat",
ChatMessages =
[
new ChatMessage { Role = "system", Content = "You are a helpful assistant." },
new ChatMessage { Role = "user", Content = "How can I reset my password?" }
],
CommitMessage = "Improve support flow"
}, ct);await _promptService.UpdatePromptLabelsAsync(
promptKey: "WelcomePrompt",
version: 3,
request: new UpdatePromptLabelsRequest { NewLabels = ["production"] },
cancellationToken: ct);GetPromptsAsyncexecutes prompt fetches concurrently.- Individual key failures do not fail the full batch.
- Strong typing is used in responses:
PromptSource(Local,Langfuse)PromptKind(Text,Chat,Unknown)
- Langfuse options are validated at startup (fail-fast for partial/invalid configuration).
MIT