{"id":28550521,"url":"https://github.com/elastacloud/dotprompt","last_synced_at":"2025-07-03T20:32:24.971Z","repository":{"id":258504465,"uuid":"871540278","full_name":"elastacloud/DotPrompt","owner":"elastacloud","description":"Library allowing you to use GenAI prompts saved as .prompt files, keeping your prompts organised and controlled","archived":false,"fork":false,"pushed_at":"2025-04-26T14:01:21.000Z","size":116,"stargazers_count":8,"open_issues_count":1,"forks_count":1,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-06-10T03:09:31.760Z","etag":null,"topics":["azure-openai","dotnet","genai","openai","prompt-files","prompts"],"latest_commit_sha":null,"homepage":"https://elastacloud.com","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/elastacloud.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-10-12T08:44:39.000Z","updated_at":"2025-06-06T17:50:13.000Z","dependencies_parsed_at":"2024-11-04T12:24:22.841Z","dependency_job_id":"3b2e76f2-bbf8-4305-8a96-182160be929e","html_url":"https://github.com/elastacloud/DotPrompt","commit_stats":null,"previous_names":["elastacloud/dotprompt"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/elastacloud/DotPrompt","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elastacloud%2FDotPrompt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elastacloud%2FDotPrompt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elastacloud%2FDotPrompt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elastacloud%2FDotPrompt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/elastacloud","download_url":"https://codeload.github.com/elastacloud/DotPrompt/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elastacloud%2FDotPrompt/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":263399556,"owners_count":23460727,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["azure-openai","dotnet","genai","openai","prompt-files","prompts"],"created_at":"2025-06-10T03:09:31.407Z","updated_at":"2025-07-03T20:32:24.958Z","avatar_url":"https://github.com/elastacloud.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# DotPrompt\n\n[![NuGet version](https://badge.fury.io/nu/DotPrompt.svg)](https://badge.fury.io/nu/DotPrompt)\n[![codecov](https://codecov.io/github/elastacloud/DotPrompt/graph/badge.svg?token=hTfjzLIEsM)](https://codecov.io/github/elastacloud/DotPrompt)\n\nDotPrompt is a simple library which allows you to build prompts using a configuration-based syntax, without needing to embed them into your application. It supports templating for prompts through the [Fluid](https://github.com/sebastienros/fluid) templating language, allowing you to re-use the same prompt and pass in different values at runtime.\n\nA prompt file is simply any file ending with a `.prompt` extension. The actual file itself is a YAML configuration file, and the extension allows the library to quickly identify the file for its intended purpose.\n\n## Note for JetBrains IDE users\n\nThere is a [known issue](https://youtrack.jetbrains.com/issue/IJPL-162378) with `.prompt` files causing unusual behaviour in tools such as Rider and IntelliJ. You can work around this by either disabling the Terminal plug-in or by using a different editor to modify the files.\n\n## The prompt file\n\nA prompt file's contents contain some top-level identification properties, followed by configuration information and then finally the prompts.\n\nA complete prompt file would look like this.\n\n```yaml\nname: Example\nmodel: gpt-4o\nconfig:\n  outputFormat: text\n  temperature: 0.9\n  maxTokens: 500\n  input:\n    parameters:\n      topic: string\n      style?: string\n    default:\n      topic: social media\nprompts:\n  system: |\n    You are a helpful research assistant who will provide descriptive responses for a given topic and how it impacts society\n  user: |\n    Explain the impact of {{ topic }} on how we engage with technology as a society\n    {% if style -%}\n    Can you answer in the style of a {{ style }}\n    {% endif -%}\nfewShots:\n  - user: What is Bluetooth\n    response: Bluetooth is a short-range wireless technology standard that is used for exchanging data between fixed and mobile devices over short distances and building personal area networks.\n  - user: How does machine learning differ from traditional programming?\n    response: Machine learning allows algorithms to learn from data and improve over time without being explicitly programmed.\n  - user: Can you provide an example of AI in everyday life?\n    response: AI is used in virtual assistants like Siri and Alexa, which understand and respond to voice commands.\n```\n\n### Name\n\nThe `name` is optional in the configuration, if it's not provided then the name is taken from the file name minus the extension. So a file called `gen-lookup-code.prompt` would get the name `gen-lookup-code`. This doesn't play a role in the generation of the prompts themselves (though future updates might), but allows you to identify the prompt source when logging, and to select the prompt from the prompt manager.\n\nIf you use this property then when the file is loaded the name is converted to lowercase and spaces are replaced with hyphens. So a name of `My cool Prompt` would become `my-cool-prompt`. This is done to make sure the name is easily accessible from the code.\n\n### Model\n\nThis is another optional item in the configuration, but it provides information to the user of the prompt file which model (or deployment for Azure Open AI) it should use. As this can be null if not specified this the consumer should make sure to check before usage. For example:\n\n```csharp\nvar model = promptFile.Model ?? \"my-default\";\n```\n\nUsing this option though allows the prompt engineer to be very explicit about which model they intended to be used to provide the best results.\n\n### Config\n\nThe `config` section has some top level items which are provided for the client to use in their LLM calls to set options on each call. The `outputFormat` property takes a value of either `text` or `json` depending on how the LLM is intended to respond to the request. If specifying `json` then some LLMs require either the system or user prompt to state that the expected output is JSON as well. If the library does not detect the term `JSON` in the prompt then it will append a small statement to the system prompt requesting for the response to be in JSON format.\n\n### Input\n\nThe `input` section contains details about the parameters being provided to the prompts. These aren't required and you can create prompts which don't have any values being passed in at all. But if you do then these are what you need.\n\n#### Parameters\n\nUnder `input` is the `parameters` section which contains a list of key-value pairs where the key is the name of the parameter, and the value is it's type. If you suffix the parameter name with a question mark (e.g. `style?`) then it is considered to be an optional parameter and will not error if you do not provide a value for it.\n\nThe supported types are:\n\n| Parameter Type | Dotnet Type                                                                                                                                                                                  | C# Equivalent                                                                                            |\n|----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------|\n| string         | System.String                                                                                                                                                                                | string                                                                                                   |\n| bool           | System.Boolean                                                                                                                                                                               | bool                                                                                                     |\n| datetime       | System.DateTimeOffset                                                                                                                                                                        | System.DateTimeOffset                                                                                    |\n| number         | System.Byte\u003cbr/\u003eSystem.SByte\u003cbr/\u003eSystem.UInt16\u003cbr/\u003eSystem.Int16\u003cbr/\u003eSystem.UInt32\u003cbr/\u003eSystem.Int32\u003cbr/\u003eSystem.UInt64\u003cbr/\u003eSystem.Int64\u003cbr/\u003eSystem.Single\u003cbr/\u003eSystem.Double\u003cbr/\u003eSystem.Decimal | byte\u003cbr/\u003esbyte\u003cbr/\u003eushort\u003cbr/\u003eshort\u003cbr/\u003euint\u003cbr/\u003eint\u003cbr/\u003eulong\u003cbr/\u003elong\u003cbr/\u003efloat\u003cbr/\u003edouble\u003cbr/\u003edecimal |\n| object         | System.Object                                                                                                                                                                                | object                                                                                                   |\n\nThe first 4 are used as provided. Objects which are passed to the prompt will have their `ToString` method called to be used in the prompt.\n\nThe `datetime` type can either be displayed with it's default `ToString` representation, or you can use Fluid's filters to specify it's [format](https://deanebarker.net/tech/fluid/filters/misc/#h19), change timezone, and more.\n\nIf you provide a value for a parameter which does not conform to the type specified then an error would be thrown.\n\n#### Defaults\n\nAlso in `input` is the `default` section. This section allows you to specify default values for any of the parameters. So if the parameter is not provided in your application then the default value will be used instead.\n\n### Prompts\n\nThe `prompts` section contains the templates for the system and user prompts. Whilst the user prompt is required, you do not need to specify a system prompt.\n\nBoth the `system` and `user` prompts are string values and can be defined in any way which YAML supports. The example above is using a multiline string where the carriage returns are preserved.\n\nYAML has great support for multiline string values through [Block Scalars](https://yaml-multiline.info). With these it supports both _literal_ and _folded_ strings. With literal strings the new line characters in the input string are maintained and the string remains exactly as written. With folded the new line characters are collapsed and replaced by a space character, allowing you to write very long strings over multiple lines. Using folded, if you use two new line characters, then a newline is added to the string.\n\n```yaml\n# Folded\nexample: \u003e\n  The ships hung\n  in the sky in much the same\n  way as bricks don't\n\n# Produces:\n# The ships hung in the sky in much the same way as bricks don't\n```\n\n```yaml\n# Literal\nexample: |\n  The ships hung\n  in the sky in much the same\n  way as bricks don't\n\n# Produces:\n# The ships hung\n# in the sky in much the same\n# way as bricks don't\n```\n\n#### Prompt templates\n\nThe syntax of the prompts uses the [Fluid](https://github.com/sebastienros/fluid) templating language, which is itself based on [Liquid](https://shopify.github.io/liquid/) created by Shopify. This templating language allows us to define user prompts which can change depending on values being passed into the template parser.\n\nIn the example above you can see `{{ topic }}` which is a placeholder for the value being passed in, and will be substituted straight into the template. There is also the `{% if style -%} ... {% endif -%}` section which tells the parser to only include this section if the `style` parameter has a value. The `-%}` at the end of marker contains the hyphen symbol which tells the parser that it should collapse the blank lines.\n\nThere is a great tutorial on writing templates with Fluid available [online](https://deanebarker.net/tech/fluid/).\n\nWhen you generate the prompt it does not replace the template, only giving you the generated output. This means you can generate the prompt as many times as you want with different input values.\n\n### Few-shot prompting\n\n`fewShots` is a section to allow the prompt writer to provide [few-shot prompting](https://www.promptingguide.ai/techniques/fewshot) techniques to the solution. When constructing a prompt you would include these, along with your system prompt, and then the user prompt, this provides examples on how the LLM should respond to the user prompt. If you're using OpenAI or Azure OpenAI then you can use the extension methods (see later) which will create all the messages for you.\n\n## Examples\n\n### Loading a prompt file directly\n\nPrompt files can be accessed directly. If you have only a couple of files or want to quickly test them out then this is a fairly simple way of doing so.\n\n```csharp\nusing DotPrompt;\n\nvar promptFile = PromptFile.FromFile(\"path/to/prompt-file.prompt\");\n\nvar systemPrompt = promptFile.GetSystemPrompt(null);\nvar userPrompt = promptFile.GetUserPrompt(new Dictionary\u003cstring, object\u003e\n{\n    { \"topic\", \"bluetooth\" },\n    { \"style\", \"used car salesman\" }\n});\n```\n\nIf the prompt file contained the example above, then it would produce the following.\n\n```text\nSystem Prompt:\nYou are a helpful research assistant who will provide descriptive responses for a given topic and how it impacts society\n\nUser Prompt:\nExplain the impact of bluetooth on how we engage with technology as a society\nCan you answer in the style of a used car salesman\n```\n\nThis might result in a response from the LLM which looks like this (sorry)\n\n\u003e Ladies and gentlemen, gather 'round and let me tell you about the miracle of modern technology that's revolutionized the way we connect with our gadgets—I'm talking about Bluetooth! Bluetooth is the unsung hero, the secret sauce that's been making our lives more convenient, more connected, and definitely more high-tech. Picture this: seamless, wire-free communication between your favorite devices. No more tangled cords, no more mess. It's like having a VIP pass to the front row of the future!\n\u003e \n\u003e ...\n\n### Using the Prompt Manager\n\nThe prompt manager is the preferred method for handling your prompt files. It allows you to load them from a location, access then by name, and then use them in your application.\n\nThe default for the prompt manager is to access files in the local `prompts` folder, though you can specify a different path if you want to.\n\n```csharp\n// Load from the default location of the `prompts` directory\nvar promptManager = new PromptManager();\nvar promptFile = promptManager.GetPromptFile(\"example\");\n\n// Use a different folder\nvar promptManager = new PromptManager(\"another-location\");\n\nvar promptFile = promptManager.GetPromptFile(\"example\");\n\n// List all of the prompts loaded\nvar promptNames = promptManager.ListPromptFileNames();\n```\n\nThe prompt manager implements an `IPromptManager` interface, and so if you want to use this through a DI container, or IoC pattern, then you can easily provide a mocked version for testing.\n\nThe prompt manager can also take an `IPromptStore` instance which allows you to build a custom store which might not be file-based (see [Creating a custom prompt store](#creating-a-custom-prompt-store)). This also allows for providing a mocked interface so you can write unit tests which are not dependent on the storage mechanism.\n\n### Full examples\n\nUsing the prompt manager to read a prompt and then use it in a call to an Azure OpenAI endpoint.\n\n_N.B._ This example assumes that there is a `prompts` directory with the prompt file available.\n\n```csharp\nusing System.ClientModel;\nusing Azure.AI.OpenAI;\nusing DotPrompt;\n\nvar openAiClient = new(new Uri(\"https://endpoint\"), new ApiKeyCredential(\"abc123\"));\n\nvar promptManager = new PromptManager();\nvar promptFile = promptManager.GetPromptFile(\"example\");\n\n// The system prompt and user prompt methods take dictionaries containing the values needed for the\n// template. If none are needed you can simply pass in null.\nvar systemPrompt = promptFile.GetSystemPrompt(null);\nvar userPrompt = promptFile.GetUserPrompt(new Dictionary\u003cstring, object\u003e\n{\n    { \"topic\", \"bluetooth\" },\n    { \"style\", \"used car salesman\" }\n});\n\nvar client = openAiClient.GetChatClient(promptFile.Model ?? \"default-model\");\n\nvar completion = await client.CompleteChatAsync(\n    [\n        new SystemChatMessage(systemPrompt),\n        new UserChatMessage(userPrompt)\n    ],\n    new ChatCompletionOptions\n    (\n        ResponseFormat = promptFile.OutputFormat == OutputFormat.Json ? ChatResponseFormat.JsonObject : ChatResponseFormat.Text,\n        Temperature = promptFile.Config.Temperature,\n        MaxTokens = promptFile.Config.MaxTokens\n    )\n);\n```\n\nOr, using the OpenAI provided extension methods.\n\n```csharp\nusing System.ClientModel;\nusing Azure.AI.OpenAI;\nusing DotPrompt;\nusing DotPrompt.Extensions.OpenAi;\n\nvar openAiClient = new(new Uri(\"https://endpoint\"), new ApiKeyCredential(\"abc123\"));\n\nvar promptManager = new PromptManager();\nvar promptFile = promptManager.GetPromptFile(\"example\");\n\nvar promptValues = new Dictionary\u003cstring, object\u003e\n{\n    { \"topic\", \"bluetooth\" },\n    { \"style\", \"used car salesman\" }\n};\n\nvar client = openAiClient.GetChatClient(promptFile.Model ?? \"default-model\");\n\nvar completion = await client.CompleteChatAsync(\n    promptFile.ToOpenAiChatMessages(promptValues),\n    promptFile.ToOpenAiChatCompletionOptions()\n);\n\nvar response = completion.Value;\nConsole.WriteLine(response.Content[0].Text);\n```\n\nAnd now, if we need to modify our prompt, we can simply change the prompt file and leave our code alone (assuming the parameters don't change).\n\n## Creating a custom prompt store\n\nThe above shows how you can use DotPrompt to read prompt files from disk. But what if you have a situation where you want your prompts somewhere more central, like a cloud storage service, or a database? Well The prompt manager can take an `IPromptStore` instance as an argument. In all the examples above it's using the `FilePromptStore` which is included, but you can also build your own. It just needs to implement the interface and you're done.\n\nTo give you an example, here's a simple implementation which uses an Azure Storage Table Store to hold the prompt details.\n\n```csharp\n/// \u003csummary\u003e\n/// Implementation of the IPromptStore for Azure Storage Tables\n/// \u003c/summary\u003e\npublic class AzureTablePromptStore : IPromptStore\n{\n    /// \u003csummary\u003e\n    /// Loads the prompts from the table store\n    /// \u003c/summary\u003e\n    public IEnumerable\u003cPromptFile\u003e Load()\n    {\n        var tableClient = GetTableClient();\n        var promptEntities = tableClient.Query\u003cPromptEntity\u003e(e =\u003e e.PartitionKey == \"DotPromptTest\");\n\n        var promptFiles = promptEntities\n            .Select(pe =\u003e pe.ToPromptFile())\n            .ToList();\n\n        return promptFiles;\n    }\n\n    /// \u003csummary\u003e\n    /// Gets a table client\n    /// \u003c/summary\u003e\n    private static TableClient GetTableClient()\n    {\n        // Replace the configuration items here with your value or switch to using\n        // Entra based authentication\n        var client = new TableServiceClient(\n            new Uri($\"https://{Configuration.StorageAccountName}.table.core.windows.net/\"),\n            new TableSharedKeyCredential(Configuration.StorageAccountName, Configuration.StorageAccountKey)\n        );\n\n        var tableClient = client.GetTableClient(\"prompts\");\n        tableClient.CreateIfNotExists();\n\n        return tableClient;\n    }\n}\n\n/// \u003csummary\u003e\n/// Represents a record held in the storage table\n/// \u003c/summary\u003e\npublic class PromptEntity : ITableEntity\n{\n    /// \u003csummary\u003e\n    /// Gets, sets the partition key for the record\n    /// \u003c/summary\u003e\n    public string PartitionKey { get; set; } = string.Empty;\n    \n    /// \u003csummary\u003e\n    /// Gets, sets the row key for the record\n    /// \u003c/summary\u003e\n    public string RowKey { get; set; } = string.Empty;\n    \n    /// \u003csummary\u003e\n    /// Gets, sets the timestamp of the entry\n    /// \u003c/summary\u003e\n    public DateTimeOffset? Timestamp { get; set; }\n    \n    /// \u003csummary\u003e\n    /// Gets, sets the records ETag value\n    /// \u003c/summary\u003e\n    public ETag ETag { get; set; }\n    \n    /// \u003csummary\u003e\n    /// Gets, sets the model to use\n    /// \u003c/summary\u003e\n    public string? Model { get; set; }\n    \n    /// \u003csummary\u003e\n    /// Gets, sets the output format\n    /// \u003c/summary\u003e\n    public string OutputFormat { get; set; } = string.Empty;\n    \n    /// \u003csummary\u003e\n    /// Gets, sets the maximum number of tokens\n    /// \u003c/summary\u003e\n    public int MaxTokens { get; set; }\n    \n    /// \u003csummary\u003e\n    /// Gets, sets the parameter information which is held as a JSON string value\n    /// \u003c/summary\u003e\n    public string Parameters { get; set; } = string.Empty;\n    \n    /// \u003csummary\u003e\n    /// Gets, sets the default values which are held as a JSON string value\n    /// \u003c/summary\u003e\n    public string Default { get; set; } = string.Empty;\n    \n    /// \u003csummary\u003e\n    /// Gets, sets the system prompt template\n    /// \u003c/summary\u003e\n    public string SystemPrompt { get; set; } = string.Empty;\n    \n    /// \u003csummary\u003e\n    /// Gets, sets the user prompt template\n    /// \u003c/summary\u003e\n    public string UserPrompt { get; set; } = string.Empty;\n\n    /// \u003csummary\u003e\n    /// Returns the prompt entity record into a \u003csee cref=\"PromptFile\"/\u003e instance\n    /// \u003c/summary\u003e\n    /// \u003creturns\u003e\u003c/returns\u003e\n    public PromptFile ToPromptFile()\n    {\n        var parameters = new Dictionary\u003cstring, string\u003e();\n        var defaults = new Dictionary\u003cstring, object\u003e();\n        \n        // If there are parameter values then convert them into a dictionary\n        if (!string.IsNullOrEmpty(Parameters))\n        {\n            var entityParameters = (JsonObject)JsonNode.Parse(Parameters)!;\n            foreach (var (prop, propType) in entityParameters)\n            {\n                parameters.Add(prop, propType?.AsValue().ToString() ?? string.Empty);\n            }\n        }\n\n        // If there are default values then convert them into a dictionary\n        if (!string.IsNullOrEmpty(Default))\n        {\n            var entityDefaults = (JsonObject)JsonNode.Parse(Default)!;\n            foreach (var (prop, defaultValue) in entityDefaults)\n            {\n                defaults.Add(prop, defaultValue?.AsValue().GetValue\u003cobject\u003e() ?? string.Empty);\n            }\n        }\n        \n        // Generate the new prompt file\n        var promptFile = new PromptFile\n        {\n            Name = RowKey,\n            Model = Model,\n            Config = new PromptConfig\n            {\n                OutputFormat = Enum.Parse\u003cOutputFormat\u003e(OutputFormat, true),\n                MaxTokens = MaxTokens,\n                Input = new InputSchema\n                {\n                    Parameters = parameters,\n                    Default = defaults\n                }\n            },\n            Prompts = new Prompts\n            {\n                System = SystemPrompt,\n                User = UserPrompt\n            }\n        };\n\n        return promptFile;\n    }\n}\n```\n\nAnd then to use this we would do the following\n\n```csharp\nvar promptManager = new PromptManager(new AzureTablePromptStore());\nvar promptFile = promptManager.GetPromptFile(\"example\");\n```\n\n## Upcoming features\n\nThere's still scope for work to do here and some of the items we're looking at include\n\n* Additional configuration options\n* Additional prompting techniques\n* Open to feedback. Is there anything you'd like to see? Let us know\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felastacloud%2Fdotprompt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Felastacloud%2Fdotprompt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felastacloud%2Fdotprompt/lists"}