From 8cbad2fc656100082af2a1f775068240eda5e567 Mon Sep 17 00:00:00 2001 From: Andrei Ignat Date: Sun, 31 Aug 2025 06:00:30 +0300 Subject: [PATCH 1/2] skprompt --- README.md | 30 +- later.md | 2 +- v2/Generator/DocusaurusExample.txt | 8 - v2/Generator/MultiGeneratorV2.cs | 1 + v2/Generator/all.csv | 1 + v2/GeneratorData/Category.cs | 1 + v2/RSCGExamplesData/GeneratorDataRec.json | 6 + .../SKPromptGenerator/description.json | 22 + v2/rscg_examples/SKPromptGenerator/nuget.txt | 1 + v2/rscg_examples/SKPromptGenerator/readme.txt | 298 ++++++++ .../src/.tours/SKPromptGenerator.tour | 48 ++ .../SKPromptGenerator/src/DemoAI.sln | 25 + .../src/DemoAI/DemoAI.csproj | 21 + .../SKPromptGenerator/src/DemoAI/Program.cs | 3 + .../src/DemoAI/WeatherCity.cs | 13 + v2/rscg_examples/SKPromptGenerator/video.json | 39 + v2/rscg_examples_site/docs/Categories/AI.md | 6 + .../docs/Categories/_PrimitiveAI.mdx | 7 + .../docs/RSCG-Examples/EnumsEnhanced.md | 3 - .../docs/RSCG-Examples/Nino.md | 37 +- .../docs/RSCG-Examples/SKPromptGenerator.md | 673 ++++++++++++++++++ .../docs/RSCG-Examples/index.md | 18 +- v2/rscg_examples_site/docs/about.md | 2 +- v2/rscg_examples_site/docs/indexRSCG.md | 5 +- .../src/components/HomepageFeatures/index.js | 2 +- .../static/exports/RSCG.json | 8 + .../static/exports/RSCG.xlsx | Bin 11930 -> 11974 bytes 27 files changed, 1223 insertions(+), 57 deletions(-) create mode 100644 v2/rscg_examples/SKPromptGenerator/description.json create mode 100644 v2/rscg_examples/SKPromptGenerator/nuget.txt create mode 100644 v2/rscg_examples/SKPromptGenerator/readme.txt create mode 100644 v2/rscg_examples/SKPromptGenerator/src/.tours/SKPromptGenerator.tour create mode 100644 v2/rscg_examples/SKPromptGenerator/src/DemoAI.sln create mode 100644 v2/rscg_examples/SKPromptGenerator/src/DemoAI/DemoAI.csproj create mode 100644 v2/rscg_examples/SKPromptGenerator/src/DemoAI/Program.cs create mode 100644 v2/rscg_examples/SKPromptGenerator/src/DemoAI/WeatherCity.cs create mode 100644 v2/rscg_examples/SKPromptGenerator/video.json create mode 100644 v2/rscg_examples_site/docs/Categories/AI.md create mode 100644 v2/rscg_examples_site/docs/Categories/_PrimitiveAI.mdx create mode 100644 v2/rscg_examples_site/docs/RSCG-Examples/SKPromptGenerator.md diff --git a/README.md b/README.md index 4637d9d0e..80de4b0cf 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# RSCG - 218 Examples of Roslyn Source Code Generators / 14 created by Microsoft / +# RSCG - 219 Examples of Roslyn Source Code Generators / 14 created by Microsoft / -## Latest Update : 2025-08-06 => 06 August 2025 +## Latest Update : 2025-08-07 => 07 August 2025 If you want to see examples with code, please click ***[List V2](https://ignatandrei.github.io/RSCG_Examples/v2/docs/List-of-RSCG)*** @@ -8,7 +8,7 @@ If you want just those from Microsoft, please click ***[Microsoft](https://ignat If you want to see by category, please click ***[category](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples)*** or click any category below -[actor](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#actor) -[aop](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#aop) -[api](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#api) -[async](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#async) -[bitwise](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#bitwise) -[blazor](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#blazor) -[builder](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#builder) -[clone](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#clone) -[codetostring](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#codetostring) -[commandline](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#commandline) -[console](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#console) -[constructor](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#constructor) -[database](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#database) -[dependencyinjection](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#dependencyinjection) -[disposer](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#disposer) -[enhancementclass](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#enhancementclass) -[enhancementproject](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#enhancementproject) -[enum](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#enum) -[equals](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#equals) -[filestocode](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#filestocode) -[functionalprogramming](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#functionalprogramming) -[hangfire](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#hangfire) -[interface](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#interface) -[linq](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#linq) -[mapper](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#mapper) -[mediator](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#mediator) -[mvc](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#mvc) -[mvvm](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#mvvm) -[optimizer](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#optimizer) -[primitiveobsession](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#primitiveobsession) -[serializer](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#serializer) -[signalr](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#signalr) -[statemachine](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#statemachine) -[templating](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#templating) -[tests](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#tests) -[winapi](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#winapi) - +[actor](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#actor) -[ai](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#ai) -[aop](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#aop) -[api](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#api) -[async](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#async) -[bitwise](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#bitwise) -[blazor](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#blazor) -[builder](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#builder) -[clone](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#clone) -[codetostring](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#codetostring) -[commandline](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#commandline) -[console](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#console) -[constructor](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#constructor) -[database](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#database) -[dependencyinjection](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#dependencyinjection) -[disposer](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#disposer) -[enhancementclass](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#enhancementclass) -[enhancementproject](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#enhancementproject) -[enum](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#enum) -[equals](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#equals) -[filestocode](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#filestocode) -[functionalprogramming](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#functionalprogramming) -[hangfire](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#hangfire) -[interface](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#interface) -[linq](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#linq) -[mapper](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#mapper) -[mediator](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#mediator) -[mvc](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#mvc) -[mvvm](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#mvvm) -[optimizer](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#optimizer) -[primitiveobsession](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#primitiveobsession) -[serializer](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#serializer) -[signalr](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#signalr) -[statemachine](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#statemachine) -[templating](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#templating) -[tests](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#tests) -[winapi](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#winapi) - ## If you have a Roslyn Source Code Generator, please create an issue. @@ -20,8 +20,30 @@ If you want to be notified each time I add a new RSCG example , please click htt ## Content -Those are the 218 Roslyn Source Code Generators that I have tested you can see and download source code example. +Those are the 219 Roslyn Source Code Generators that I have tested you can see and download source code example. ( including 14 from Microsoft ) +### 219. [SKPromptGenerator](https://ignatandrei.github.io/RSCG_Examples/v2/docs/SKPromptGenerator) , in the [AI](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#ai) category + +Generated on : 2025-08-07 => 07 August 2025 + +
+ Expand + + + +Author: Charlie Chen + +A source generator to automatically create classes from string prompts. + +Nuget: [https://www.nuget.org/packages/SKPromptGenerator/](https://www.nuget.org/packages/SKPromptGenerator/) + + +Link: [https://ignatandrei.github.io/RSCG_Examples/v2/docs/SKPromptGenerator](https://ignatandrei.github.io/RSCG_Examples/v2/docs/SKPromptGenerator) + +Source: [https://github.com/CharlieDigital/SKPromptGenerator](https://github.com/CharlieDigital/SKPromptGenerator) + +
+ ### 218. [Nino](https://ignatandrei.github.io/RSCG_Examples/v2/docs/Nino) , in the [Serializer](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#serializer) category Generated on : 2025-08-06 => 06 August 2025 diff --git a/later.md b/later.md index 6c7c6ed7a..c03311ab3 100644 --- a/later.md +++ b/later.md @@ -1,6 +1,6 @@ # Just later -## Latest Update : 2025-08-06 => 06 August 2025 +## Latest Update : 2025-08-07 => 07 August 2025 diff --git a/v2/Generator/DocusaurusExample.txt b/v2/Generator/DocusaurusExample.txt index 4b6f7a900..fa5863c99 100644 --- a/v2/Generator/DocusaurusExample.txt +++ b/v2/Generator/DocusaurusExample.txt @@ -103,17 +103,9 @@ Those are taken from $(BaseIntermediateOutputPath)\GX {{~ for fileContent in Description.Data.outputFiles.generatedFiles ~}} - -{{~ if fileContent.content | string.contains "```" ~}} - -{{fileContent.content}} - -{{~ else ~}} - ```csharp showLineNumbers {{fileContent.content}} ``` -{{end}} {{~ end ~}} diff --git a/v2/Generator/MultiGeneratorV2.cs b/v2/Generator/MultiGeneratorV2.cs index 11f3a5cae..2503b5359 100644 --- a/v2/Generator/MultiGeneratorV2.cs +++ b/v2/Generator/MultiGeneratorV2.cs @@ -120,6 +120,7 @@ public string[] SourceNoRSCG() var text=await File.ReadAllTextAsync(nameFile); text = text.Replace("(img/", $"({d.Generator!.Source}/img/"); text=text.Replace("(README_IMAGE.png)", $"({d.Generator!.Source}/README_IMAGE.png)"); + text = text.Replace("(README", $"({d.Generator!.Source}/README"); text = text.Replace("(integ-tests/", $"({d.Generator!.Source}/integ-tests/"); text = text.Replace("(./samples", $"({d.Generator!.Source}/samples"); text = text.Replace("(./tests", $"({d.Generator!.Source}/tests"); diff --git a/v2/Generator/all.csv b/v2/Generator/all.csv index bbef09e0e..662d5e73f 100644 --- a/v2/Generator/all.csv +++ b/v2/Generator/all.csv @@ -217,3 +217,4 @@ Nr,Key,Source,Category 216,SG4MVC, https://github.com/SG4MVC/SG4MVC,MVC 217,EnumsEnhanced, https://github.com/snowberry-software/EnumsEnhanced,Enum 218,Nino, https://github.com/JasonXuDeveloper/Nino,Serializer +219,SKPromptGenerator, https://github.com/CharlieDigital/SKPromptGenerator,AI diff --git a/v2/GeneratorData/Category.cs b/v2/GeneratorData/Category.cs index d02f02f4a..3750f80f3 100644 --- a/v2/GeneratorData/Category.cs +++ b/v2/GeneratorData/Category.cs @@ -39,5 +39,6 @@ public enum Category Console=34, Async=35, MVC=36, + AI=37, } diff --git a/v2/RSCGExamplesData/GeneratorDataRec.json b/v2/RSCGExamplesData/GeneratorDataRec.json index 661c4374b..b38228316 100644 --- a/v2/RSCGExamplesData/GeneratorDataRec.json +++ b/v2/RSCGExamplesData/GeneratorDataRec.json @@ -1317,5 +1317,11 @@ "Category": 16, "dtStart": "2025-08-06T00:00:00", "show": true +}, +{ + "ID":"SKPromptGenerator", + "Category": 37, + "dtStart": "2025-08-07T00:00:00", + "show": true } ] \ No newline at end of file diff --git a/v2/rscg_examples/SKPromptGenerator/description.json b/v2/rscg_examples/SKPromptGenerator/description.json new file mode 100644 index 000000000..533e73330 --- /dev/null +++ b/v2/rscg_examples/SKPromptGenerator/description.json @@ -0,0 +1,22 @@ +{ + "generator":{ + "name":"SKPromptGenerator", + "nuget":[ + "https://www.nuget.org/packages/SKPromptGenerator/" + ], + "link":"https://github.com/CharlieDigital/SKPromptGenerator", + "author":"Charlie Chen", + "source":"https://github.com/CharlieDigital/SKPromptGenerator" + }, + "data":{ + "goodFor":["Generate typed prompts for Semantic Kernel"], + "csprojDemo":"DemoAI.csproj", + "csFiles":["Program.cs","WeatherCity.cs"], + "excludeDirectoryGenerated":[""], + "includeAdditionalFiles":[""] + }, + "links":{ + "blog":"", + "video":"" + } +} \ No newline at end of file diff --git a/v2/rscg_examples/SKPromptGenerator/nuget.txt b/v2/rscg_examples/SKPromptGenerator/nuget.txt new file mode 100644 index 000000000..30465b85e --- /dev/null +++ b/v2/rscg_examples/SKPromptGenerator/nuget.txt @@ -0,0 +1 @@ +A source generator to automatically create classes from string prompts. \ No newline at end of file diff --git a/v2/rscg_examples/SKPromptGenerator/readme.txt b/v2/rscg_examples/SKPromptGenerator/readme.txt new file mode 100644 index 000000000..5639b31fd --- /dev/null +++ b/v2/rscg_examples/SKPromptGenerator/readme.txt @@ -0,0 +1,298 @@ +# Semantic Kernel (SK) Prompt Generator + +SKPromptGenerator is a C# source generator which automatically creates strongly-typed classes for your string prompt templates. + +It is intended to be used with [Semantic Kernel](https://github.com/microsoft/semantic-kernel). + +https://github.com/user-attachments/assets/a3727ef4-6880-4939-be40-5c5c08948a3e + +> πŸ’‘ NOTE: As of version 4, the token format is switched from `{name}` to `{{$name}}` to match Semantic Kernel. + +## Motivation + +When working with prompts, you'll end up doing a lot of string templating and repetitive code. + +Wouldn't it be nice if you could just have a strongly-typed class for each prompt automatically created using the prompt? + +This library does exactly that. + +```csharp +public static class Prompts +{ + // Define a prompt + [PromptTemplate] + public const string Capitol = """ + What is the capitol of {{$state}} {{$country}}? + Respond directly in a single line + """; +} + +// Execute the prompt passing in a Semantic Kernel instance. +var capitol = await new CapitolPrompt( + state: "NJ", + country: "USA" +).ExecuteAsync(kernel); +``` + +The tokens in the prompt string become named parameters on the class constructor πŸŽ‰ + +## Limitations + +1. Your prompt must be a `const string` because the generator needs to be able to read the string in the source. +2. Your prompts must live in some class in a namespace. If you get the error `error CS1001: Identifier expected`, then you are probably missing a namespace around your prompt. +3. You must add a dependency to `Microsoft.SemanticKernel` since the `ExecuteAsync` method requires the `Kernel` instance. +4. Currently only targets .NET 8; considering `netstandard2.0`. + +## Installing + +This generator is built for .NET 8. + +To install: + +```shell +dotnet add package SKPromptGenerator +``` + +For the latest releases, see: https://www.nuget.org/packages/SKPromptGenerator + +## Using + +This repository includes a sample project under the `/app` directory. + +To use, create a new console app: + +```shell +mkdir sk-prompt-gen-test +cd sk-prompt-gen-test +dotnet new console +dotnet add package SKPromptGenerator +dotnet add package Microsoft.SemanticKernel +``` + +In the project, create a class like so (you can call your class whatever you want): + +```csharp +namespace SomeNamespace; + +public static class Prompts +{ + [PromptTemplate] + public const string Capitol = """ + What is the capitol of {{$state}} {{$country}}? + Respond directly in a single line + When writing the state, always write it as the full name + Write your output in the format: The capitol of is: . + For example: The capitol of California is: Sacramento. + """; +} +``` + +> πŸ’‘ Note the usage of a namespace for the class. The prompt need not be in a standalone class. It can also be placed in an existing `Controller` (for example) + +In the code above, we've created a prompt with two tokens: `{{$state}}` and `{{$country}}`. + +The `[PromptTemplate]` attribute instructs the generator to create a class like so: + +```csharp +using System; +using Microsoft.SemanticKernel.Connectors.OpenAI; +using SKPromptGenerator; + +namespace SomeNamespace; + +public partial class CapitolPrompt( + string state, string country +) : PromptTemplateBase +{ + public override string Text => $$""" +What is the capitol of {{state}} {{country}}? +Respond directly in a single line +When writing the state, always write it as the full name +Write your output in the format: The capitol of is: . +For example: The capitol of California is: Sacramento. +"""; + + public override OpenAIPromptExecutionSettings Settings => new OpenAIPromptExecutionSettings + { + MaxTokens = 500, + Temperature = 0.5d, + TopP = 0d, + }; +} +``` + +Note the two class parameters `state` and `country` which are extracted from the prompt template are now string literal tokens. + +Now we can use the prompt like so: + +```csharp +var capitol = await new CapitolPrompt("NJ", "USA").ExecuteAsync(kernel); + +Console.WriteLine($"{capitol}"); +// The capitol of New Jersey is: Trenton. + +capitol = await new CapitolPrompt("NY", "USA").ExecuteAsync(kernel); + +Console.WriteLine($"{capitol}"); +// The capitol of New York is: Albany. +``` + +If your prompt returns JSON, we can also deserialize it into an object: + +```csharp +// Our model that we are serializing to +public record CapitolResponse(string Country, string State, string Capitol); + +// Use ExecuteWithJsonAsync if we also want the raw JSON +var (sacramento, json) = await new CapitolJsonPrompt( + "CA", + "US" +).ExecuteWithJsonAsync(kernel); +``` + +NOTE: The underlying code will strip Markdown fences so if you are expecting your result to contain markdown, it will be stripped. + +### Typed Parameters + +If you want to use typed parameters, you can append the type to the parameter token: + +```csharp +[PromptTemplate] +public const string Cities = """ + Write a list of {{$count:int}} cities in {{$region}}, {{$country}} + Write each city on a separate line + Start you response with: Sure, here are {{$count:int}} cities in {{$region}}, {{$country}} + """; +``` + +This will generate the signature: + +```csharp +public partial class CitiesPrompt( + int count, string region, string country +) : PromptTemplateBase +``` + +Which can then be invoked with a typed, integer parameter for count: + +```csharp +var njCities = await new CitiesPrompt(4, "NJ", "USA").ExecuteAsync(kernel); +``` + +This will output: + +``` +Sure, here are 4 cities in NJ, USA: + +1. Newark +2. Jersey City +3. Paterson +4. Elizabeth +``` + +Note that if you are using your own types, those types should be added using a global `using` statement or specify the full type name since the generated class does not know about your namespaces. + +(See the example in the `/app` directory for usage) + +### Including History + +The `ExecuteAsync` method takes a `historyBuilder` parameter which will receives a `ChatHistory` instance + +The unit tests show how this can be used: + +```csharp +[Fact] +public async void History_Builder_Test() +{ + var response = await new HistoryTmplPrompt("Spencer").ExecuteAsync( + new Kernel(), + // πŸ‘‡ Here we can build the chat history up before adding the new user prompt + historyBuilder: (h) => + { + h.Add(new ChatMessageContent(AuthorRole.User, "User question")); + h.Add(new ChatMessageContent(AuthorRole.System, "System response")); + } + ); + + // Fake test where we are just going to return the history instead + Assert.Equal("User question\nSystem response", response); +} +``` + +You can do the retrieval of the actual history *before* the block and then do the history building in the block. + +## Custom Base Class + +If you want to customize how the prompt is executed, you can specify a custom base class when assigning the attribute. + +Your base class must inherit from `PromptTemplateBase`: + +```csharp +public abstract class CustomBase : PromptTemplateBase +{ + public override async Task ExecuteAsync( + Kernel kernel, + string? serviceId = null, + CancellationToken cancellation = default + ) + { + return await Task.FromResult("response"); + } +} +``` + +And then you can specify this custom base class as a generic type: + +```csharp +[PromptTemplate] +public const string CapitolCustom = """ + What is the capitol of {{$state}} {{$country}}? + Respond directly in a single line + When writing the state, always write it as the full name + Write your output in the format: The capitol of is: . + For example: The capitol of California is: Sacramento. + """; +``` + +This allows you to take full control of the execution of the prompt (e.g. add logging, telemetry, etc.). + +> πŸ’‘ Note: you should use a global `using` statement for the namespace of your custom base class. + +## Prompt Execution Settings + +The `PromptTemplate` attribute also allows specification of the prompt execution settings. + +The three parameters are: + +|Parameter|Details|Default| +|--|--|--| +|`MaxTokens`|The maximum number of tokens in the response|`500`| +|`Temperature`|The temperature|`0.5`| +|`TopP`|The TopP|`0`| + +For example: + +```csharp +public static class Prompts +{ + [PromptTemplate(10, 0.1)] + public const string SampleTmpl1 = """ + What is the capitol of {{$state}} {{$country}} + Respond directly on a single line. + """; +} +``` + +(See the `PromptTmpl` class for details) + +## Using the Sample App + +To use the sample app, you'll need to set up user secrets: + +```shell +dotnet user-secrets init +dotnet user-secrets set "AzureOpenAIKey" "YOUR_AZURE_OPEN_AI_KEY" +dotnet user-secrets set "AzureOpenAIEndpoint" "YOUR_AZURE_OPEN_AI_ENDPOINT" +``` + +If you are using OpenAI, feel free to fork this project and simply change the service type and configuration values. diff --git a/v2/rscg_examples/SKPromptGenerator/src/.tours/SKPromptGenerator.tour b/v2/rscg_examples/SKPromptGenerator/src/.tours/SKPromptGenerator.tour new file mode 100644 index 000000000..3a75c29e2 --- /dev/null +++ b/v2/rscg_examples/SKPromptGenerator/src/.tours/SKPromptGenerator.tour @@ -0,0 +1,48 @@ + +{ + "$schema": "https://aka.ms/codetour-schema", + "title": "SKPromptGenerator", + "steps": + [ + { + "file": "DemoAI/DemoAI.csproj", + "description": "First, we add Nuget [SKPromptGenerator](https://www.nuget.org/packages/SKPromptGenerator/) in csproj ", + "pattern": "SKPromptGenerator" + } + + ,{ + "file": "DemoAI/WeatherCity.cs", + "description": "File WeatherCity.cs ", + "pattern": "this is the code" + } + + ,{ + "file": "DemoAI/Program.cs", + "description": "File Program.cs \r\n>> dotnet run --project DemoAI/DemoAI.csproj ", + "pattern": "this is the code" + } + + + ,{ + "file": "DemoAI/obj/GX/SKPromptGenerator/SKPromptGenerator.PromptTemplateBaseGenerator/PromptTemplateBase.g.cs", + "description": "Generated File 3 from 3 : PromptTemplateBase.g.cs ", + "line": 1 + } + + ,{ + "file": "DemoAI/obj/GX/SKPromptGenerator/SKPromptGenerator.PromptTemplateAttributeGenerator/PromptTemplateAttribute.g.cs", + "description": "Generated File 2 from 3 : PromptTemplateAttribute.g.cs ", + "line": 1 + } + + ,{ + "file": "DemoAI/obj/GX/SKPromptGenerator/SKPromptGenerator.PromptGenerator/WeatherPrompt.g.cs", + "description": "Generated File 1 from 3 : WeatherPrompt.g.cs ", + "line": 1 + } + + ], + + "ref": "main" + +} \ No newline at end of file diff --git a/v2/rscg_examples/SKPromptGenerator/src/DemoAI.sln b/v2/rscg_examples/SKPromptGenerator/src/DemoAI.sln new file mode 100644 index 000000000..8c074fec7 --- /dev/null +++ b/v2/rscg_examples/SKPromptGenerator/src/DemoAI.sln @@ -0,0 +1,25 @@ +ο»Ώ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36414.22 d17.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DemoAI", "DemoAI\DemoAI.csproj", "{7E2B9DD1-E30E-4990-B3AB-71E1B0DD612D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7E2B9DD1-E30E-4990-B3AB-71E1B0DD612D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7E2B9DD1-E30E-4990-B3AB-71E1B0DD612D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7E2B9DD1-E30E-4990-B3AB-71E1B0DD612D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7E2B9DD1-E30E-4990-B3AB-71E1B0DD612D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {15647B26-E02C-48A2-8743-6DD3297D586A} + EndGlobalSection +EndGlobal diff --git a/v2/rscg_examples/SKPromptGenerator/src/DemoAI/DemoAI.csproj b/v2/rscg_examples/SKPromptGenerator/src/DemoAI/DemoAI.csproj new file mode 100644 index 000000000..d22bcfc8f --- /dev/null +++ b/v2/rscg_examples/SKPromptGenerator/src/DemoAI/DemoAI.csproj @@ -0,0 +1,21 @@ +ο»Ώ + + + Exe + net8.0 + enable + enable + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + true + $(BaseIntermediateOutputPath)\GX + + diff --git a/v2/rscg_examples/SKPromptGenerator/src/DemoAI/Program.cs b/v2/rscg_examples/SKPromptGenerator/src/DemoAI/Program.cs new file mode 100644 index 000000000..4397d9ad8 --- /dev/null +++ b/v2/rscg_examples/SKPromptGenerator/src/DemoAI/Program.cs @@ -0,0 +1,3 @@ +ο»Ώ// See https://aka.ms/new-console-template for more information +Console.WriteLine("Hello, World!"); +var capitol = new DemoAI.WeatherPrompt("Bucuresti"); diff --git a/v2/rscg_examples/SKPromptGenerator/src/DemoAI/WeatherCity.cs b/v2/rscg_examples/SKPromptGenerator/src/DemoAI/WeatherCity.cs new file mode 100644 index 000000000..4105add51 --- /dev/null +++ b/v2/rscg_examples/SKPromptGenerator/src/DemoAI/WeatherCity.cs @@ -0,0 +1,13 @@ +ο»Ώusing SKPromptGenerator; // <-- Add namespace here + +namespace DemoAI +{ + public static partial class MyPrompts + { + [PromptTemplate] // <-- Remove namespace here + public const string Weather = """ + What is the weather in the city {{$city}} ? + Respond directly in a single line + """; + } +} \ No newline at end of file diff --git a/v2/rscg_examples/SKPromptGenerator/video.json b/v2/rscg_examples/SKPromptGenerator/video.json new file mode 100644 index 000000000..280d7b9b9 --- /dev/null +++ b/v2/rscg_examples/SKPromptGenerator/video.json @@ -0,0 +1,39 @@ +{ + "scriptName": "SKPromptGenerator", + "steps": +[ + {"typeStep":"exec","arg":"clipchamp.exe launch"}, + {"typeStep":"text","arg": "Welcome to Roslyn Examples"}, + {"typeStep":"text","arg":"If you want to see more examples , see List Of RSCG"}, + {"typeStep":"browser","arg":"https://ignatandrei.github.io/RSCG_Examples/v2/docs/List-of-RSCG"}, + {"typeStep":"text","arg": "My name is Andrei Ignat and I am deeply fond of Roslyn Source Code Generator. "}, + +{"typeStep":"text","arg": "Today I will present SKPromptGenerator . Generate typed prompts for Semantic Kernel ."}, +{"typeStep":"browser","arg":"https://www.nuget.org/packages/SKPromptGenerator/"}, +{"typeStep":"text","arg": "The whole example is here"}, +{"typeStep":"browser","arg":"https://ignatandrei.github.io/RSCG_Examples/v2/docs/SKPromptGenerator"}, +{"typeStep":"text","arg": "You can download the code from here"}, +{"typeStep":"browser","arg":"https://ignatandrei.github.io/RSCG_Examples/v2/docs/SKPromptGenerator#download-example-net--c-"}, +{"typeStep":"text","arg":"Here is the code downloaded "}, +{"typeStep":"exec","arg":"explorer.exe /select,D:\\gth\\RSCG_Examples\\v2\\rscg_examples\\SKPromptGenerator\\src\\DemoAI.sln"}, +{"typeStep":"text","arg": "So , let's start the project with Visual Studio Code "}, +{"typeStep":"stepvscode","arg": "-n D:\\gth\\RSCG_Examples\\v2\\rscg_examples\\SKPromptGenerator\\src"}, + +{"typeStep":"text","arg": "To use it ,you will put the Nuget SKPromptGenerator into the csproj "}, + +{"typeStep":"stepvscode","arg": "-r -g D:\\gth\\RSCG_Examples\\v2\\rscg_examples\\SKPromptGenerator\\src\\DemoAI\\DemoAI.csproj"}, + +{"typeStep":"text","arg": "And now I will show you an example of using SKPromptGenerator"}, + +{"typeStep":"hide","arg": "now execute the tour in VSCode"}, +{"typeStep":"tour", "arg": "src/.tours/"}, +{"typeStep":"text","arg":" And I will execute the project"}, +{"typeStep":"showproj", "arg":"DemoAI.csproj"}, +{"typeStep":"text","arg":" This concludes the project"}, +{"typeStep":"waitseconds","arg":"30"}, +{"typeStep":"text","arg": "Remember, you can download the code from here"}, +{"typeStep":"browser","arg":"https://ignatandrei.github.io/RSCG_Examples/v2/docs/SKPromptGenerator#download-example-net--c-", +SpeakTest=" "}, +{"typeStep":"waitseconds","arg":"30"}, +] +} diff --git a/v2/rscg_examples_site/docs/Categories/AI.md b/v2/rscg_examples_site/docs/Categories/AI.md new file mode 100644 index 000000000..1be6cd59f --- /dev/null +++ b/v2/rscg_examples_site/docs/Categories/AI.md @@ -0,0 +1,6 @@ +

AI

+ +Number RSCG: 1 + + 1 [SKPromptGenerator](/docs/SKPromptGenerator) + \ No newline at end of file diff --git a/v2/rscg_examples_site/docs/Categories/_PrimitiveAI.mdx b/v2/rscg_examples_site/docs/Categories/_PrimitiveAI.mdx new file mode 100644 index 000000000..1268bac92 --- /dev/null +++ b/v2/rscg_examples_site/docs/Categories/_PrimitiveAI.mdx @@ -0,0 +1,7 @@ +### Category "AI" has the following generators: + + 1 [SKPromptGenerator](/docs/SKPromptGenerator) + +### See category + +[AI](/docs/Categories/AI) diff --git a/v2/rscg_examples_site/docs/RSCG-Examples/EnumsEnhanced.md b/v2/rscg_examples_site/docs/RSCG-Examples/EnumsEnhanced.md index 010e22638..6a79a61bf 100644 --- a/v2/rscg_examples_site/docs/RSCG-Examples/EnumsEnhanced.md +++ b/v2/rscg_examples_site/docs/RSCG-Examples/EnumsEnhanced.md @@ -614,8 +614,6 @@ Those are taken from $(BaseIntermediateOutputPath)\GX - - ```csharp showLineNumbers #nullable enable @@ -1009,7 +1007,6 @@ if(subValue.Equals("Mercedes", StringComparison.OrdinalIgnoreCase)) { #nullable restore ``` - diff --git a/v2/rscg_examples_site/docs/RSCG-Examples/Nino.md b/v2/rscg_examples_site/docs/RSCG-Examples/Nino.md index ecd02491b..8cae39f5c 100644 --- a/v2/rscg_examples_site/docs/RSCG-Examples/Nino.md +++ b/v2/rscg_examples_site/docs/RSCG-Examples/Nino.md @@ -60,7 +60,7 @@ Jason Xu [![NuGet](https://img.shields.io/nuget/v/Nino?label=NuGet&style=flat-square&logo=nuget)](https://www.nuget.org/packages/Nino) [![OpenUPM](https://img.shields.io/npm/v/com.jasonxudeveloper.nino?label=OpenUPM&style=flat-square&logo=unity®istry_uri=https://package.openupm.com)](https://openupm.com/packages/com.jasonxudeveloper.nino/) -[🌐 **Official Website**](https://nino.xgamedev.net/en/) β€’ [πŸ“š **Documentation**](https://nino.xgamedev.net/en/doc/start) β€’ [πŸš€ **Performance**](https://nino.xgamedev.net/en/perf/micro) β€’ [πŸ‡¨πŸ‡³ **δΈ­ζ–‡**](README.zh.md) +[🌐 **Official Website**](https://nino.xgamedev.net/en/) β€’ [πŸ“š **Documentation**](https://nino.xgamedev.net/en/doc/start) β€’ [πŸš€ **Performance**](https://nino.xgamedev.net/en/perf/micro) β€’ [πŸ‡¨πŸ‡³ **δΈ­ζ–‡**](https://github.com/JasonXuDeveloper/Nino/README.zh.md) *Fast, flexible, and effortless C# binary serialization* @@ -187,7 +187,7 @@ Nino consistently delivers exceptional performance across various scenarios. See **Made with ❀️ by [JasonXuDeveloper](https://github.com/JasonXuDeveloper)** -*Licensed under [MIT License](LICENSE)* +*Licensed under [MIT License](https://github.com/JasonXuDeveloper/Nino/LICENSE)* @@ -283,8 +283,6 @@ Those are taken from $(BaseIntermediateOutputPath)\GX - - ```csharp showLineNumbers // #pragma warning disable CS8669 @@ -488,13 +486,10 @@ namespace Serializer.NinoGen } } ``` - - - ```csharp showLineNumbers // using System; @@ -621,13 +616,10 @@ namespace Serializer.NinoGen } } ``` - - - ```csharp showLineNumbers // #pragma warning disable CS8669 @@ -694,13 +686,10 @@ namespace Serializer.NinoGen } } ``` - - - ```csharp showLineNumbers /* Base Types: @@ -715,13 +704,10 @@ Circular Types: */ ``` - - - ```csharp showLineNumbers // #pragma warning disable CS0109, CS8669 @@ -730,13 +716,10 @@ using System.Runtime.CompilerServices; ``` - - - ```csharp showLineNumbers // @@ -752,13 +735,10 @@ namespace Serializer.NinoGen } #endif ``` - - - ```csharp showLineNumbers // #pragma warning disable CS8669 @@ -947,13 +927,10 @@ namespace Serializer.NinoGen } } ``` - - - ```csharp showLineNumbers // @@ -1077,13 +1054,10 @@ namespace Serializer.NinoGen } } ``` - - - ```csharp showLineNumbers // #pragma warning disable CS8669 @@ -1153,13 +1127,10 @@ namespace Serializer.NinoGen } } ``` - - - ```csharp showLineNumbers // @@ -1210,13 +1181,10 @@ namespace Serializer.NinoGen } } ``` - - - ```csharp showLineNumbers /* Type: SerializerDemo.Person @@ -1227,7 +1195,6 @@ Members: */ ``` - diff --git a/v2/rscg_examples_site/docs/RSCG-Examples/SKPromptGenerator.md b/v2/rscg_examples_site/docs/RSCG-Examples/SKPromptGenerator.md new file mode 100644 index 000000000..e56f98637 --- /dev/null +++ b/v2/rscg_examples_site/docs/RSCG-Examples/SKPromptGenerator.md @@ -0,0 +1,673 @@ +--- +sidebar_position: 2190 +title: 219 - SKPromptGenerator +description: Generate typed prompts for Semantic Kernel +slug: /SKPromptGenerator +--- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import TOCInline from '@theme/TOCInline'; +import SameCategory from '../Categories/_PrimitiveAI.mdx'; + +# SKPromptGenerator by Charlie Chen + + + + +## NuGet / site data +[![Nuget](https://img.shields.io/nuget/dt/SKPromptGenerator?label=SKPromptGenerator)](https://www.nuget.org/packages/SKPromptGenerator/) +[![GitHub last commit](https://img.shields.io/github/last-commit/CharlieDigital/SKPromptGenerator?label=updated)](https://github.com/CharlieDigital/SKPromptGenerator) +![GitHub Repo stars](https://img.shields.io/github/stars/CharlieDigital/SKPromptGenerator?style=social) + +## Details + +### Info +:::info + +Name: **SKPromptGenerator** + +A source generator to automatically create classes from string prompts. + +Author: Charlie Chen + +NuGet: +*https://www.nuget.org/packages/SKPromptGenerator/* + + +You can find more details at https://github.com/CharlieDigital/SKPromptGenerator + +Source: https://github.com/CharlieDigital/SKPromptGenerator + +::: + +### Author +:::note +Charlie Chen +![Alt text](https:/github.com/CharlieDigital.png) +::: + +### Original Readme +:::note + +# Semantic Kernel (SK) Prompt Generator + +SKPromptGenerator is a C# source generator which automatically creates strongly-typed classes for your string prompt templates. + +It is intended to be used with [Semantic Kernel](https://github.com/microsoft/semantic-kernel). + +https://github.com/user-attachments/assets/a3727ef4-6880-4939-be40-5c5c08948a3e + +> πŸ’‘ NOTE: As of version 4, the token format is switched from `{name}` to `{{$name}}` to match Semantic Kernel. + +## Motivation + +When working with prompts, you'll end up doing a lot of string templating and repetitive code. + +Wouldn't it be nice if you could just have a strongly-typed class for each prompt automatically created using the prompt? + +This library does exactly that. + +```csharp +public static class Prompts +{ + // Define a prompt + [PromptTemplate] + public const string Capitol = """ + What is the capitol of {{$state}} {{$country}}? + Respond directly in a single line + """; +} + +// Execute the prompt passing in a Semantic Kernel instance. +var capitol = await new CapitolPrompt( + state: "NJ", + country: "USA" +).ExecuteAsync(kernel); +``` + +The tokens in the prompt string become named parameters on the class constructor πŸŽ‰ + +## Limitations + +1. Your prompt must be a `const string` because the generator needs to be able to read the string in the source. +2. Your prompts must live in some class in a namespace. If you get the error `error CS1001: Identifier expected`, then you are probably missing a namespace around your prompt. +3. You must add a dependency to `Microsoft.SemanticKernel` since the `ExecuteAsync` method requires the `Kernel` instance. +4. Currently only targets .NET 8; considering `netstandard2.0`. + +## Installing + +This generator is built for .NET 8. + +To install: + +```shell +dotnet add package SKPromptGenerator +``` + +For the latest releases, see: https://www.nuget.org/packages/SKPromptGenerator + +## Using + +This repository includes a sample project under the `/app` directory. + +To use, create a new console app: + +```shell +mkdir sk-prompt-gen-test +cd sk-prompt-gen-test +dotnet new console +dotnet add package SKPromptGenerator +dotnet add package Microsoft.SemanticKernel +``` + +In the project, create a class like so (you can call your class whatever you want): + +```csharp +namespace SomeNamespace; + +public static class Prompts +{ + [PromptTemplate] + public const string Capitol = """ + What is the capitol of {{$state}} {{$country}}? + Respond directly in a single line + When writing the state, always write it as the full name + Write your output in the format: The capitol of is: . + For example: The capitol of California is: Sacramento. + """; +} +``` + +> πŸ’‘ Note the usage of a namespace for the class. The prompt need not be in a standalone class. It can also be placed in an existing `Controller` (for example) + +In the code above, we've created a prompt with two tokens: `{{$state}}` and `{{$country}}`. + +The `[PromptTemplate]` attribute instructs the generator to create a class like so: + +```csharp +using System; +using Microsoft.SemanticKernel.Connectors.OpenAI; +using SKPromptGenerator; + +namespace SomeNamespace; + +public partial class CapitolPrompt( + string state, string country +) : PromptTemplateBase +{ + public override string Text => $$""" +What is the capitol of {{state}} {{country}}? +Respond directly in a single line +When writing the state, always write it as the full name +Write your output in the format: The capitol of is: . +For example: The capitol of California is: Sacramento. +"""; + + public override OpenAIPromptExecutionSettings Settings => new OpenAIPromptExecutionSettings + { + MaxTokens = 500, + Temperature = 0.5d, + TopP = 0d, + }; +} +``` + +Note the two class parameters `state` and `country` which are extracted from the prompt template are now string literal tokens. + +Now we can use the prompt like so: + +```csharp +var capitol = await new CapitolPrompt("NJ", "USA").ExecuteAsync(kernel); + +Console.WriteLine($"{capitol}"); +// The capitol of New Jersey is: Trenton. + +capitol = await new CapitolPrompt("NY", "USA").ExecuteAsync(kernel); + +Console.WriteLine($"{capitol}"); +// The capitol of New York is: Albany. +``` + +If your prompt returns JSON, we can also deserialize it into an object: + +```csharp +// Our model that we are serializing to +public record CapitolResponse(string Country, string State, string Capitol); + +// Use ExecuteWithJsonAsync if we also want the raw JSON +var (sacramento, json) = await new CapitolJsonPrompt( + "CA", + "US" +).ExecuteWithJsonAsync(kernel); +``` + +NOTE: The underlying code will strip Markdown fences so if you are expecting your result to contain markdown, it will be stripped. + +### Typed Parameters + +If you want to use typed parameters, you can append the type to the parameter token: + +```csharp +[PromptTemplate] +public const string Cities = """ + Write a list of {{$count:int}} cities in {{$region}}, {{$country}} + Write each city on a separate line + Start you response with: Sure, here are {{$count:int}} cities in {{$region}}, {{$country}} + """; +``` + +This will generate the signature: + +```csharp +public partial class CitiesPrompt( + int count, string region, string country +) : PromptTemplateBase +``` + +Which can then be invoked with a typed, integer parameter for count: + +```csharp +var njCities = await new CitiesPrompt(4, "NJ", "USA").ExecuteAsync(kernel); +``` + +This will output: + +``` +Sure, here are 4 cities in NJ, USA: + +1. Newark +2. Jersey City +3. Paterson +4. Elizabeth +``` + +Note that if you are using your own types, those types should be added using a global `using` statement or specify the full type name since the generated class does not know about your namespaces. + +(See the example in the `/app` directory for usage) + +### Including History + +The `ExecuteAsync` method takes a `historyBuilder` parameter which will receives a `ChatHistory` instance + +The unit tests show how this can be used: + +```csharp +[Fact] +public async void History_Builder_Test() +{ + var response = await new HistoryTmplPrompt("Spencer").ExecuteAsync( + new Kernel(), + // πŸ‘‡ Here we can build the chat history up before adding the new user prompt + historyBuilder: (h) => + { + h.Add(new ChatMessageContent(AuthorRole.User, "User question")); + h.Add(new ChatMessageContent(AuthorRole.System, "System response")); + } + ); + + // Fake test where we are just going to return the history instead + Assert.Equal("User question\nSystem response", response); +} +``` + +You can do the retrieval of the actual history *before* the block and then do the history building in the block. + +## Custom Base Class + +If you want to customize how the prompt is executed, you can specify a custom base class when assigning the attribute. + +Your base class must inherit from `PromptTemplateBase`: + +```csharp +public abstract class CustomBase : PromptTemplateBase +{ + public override async Task ExecuteAsync( + Kernel kernel, + string? serviceId = null, + CancellationToken cancellation = default + ) + { + return await Task.FromResult("response"); + } +} +``` + +And then you can specify this custom base class as a generic type: + +```csharp +[PromptTemplate] +public const string CapitolCustom = """ + What is the capitol of {{$state}} {{$country}}? + Respond directly in a single line + When writing the state, always write it as the full name + Write your output in the format: The capitol of is: . + For example: The capitol of California is: Sacramento. + """; +``` + +This allows you to take full control of the execution of the prompt (e.g. add logging, telemetry, etc.). + +> πŸ’‘ Note: you should use a global `using` statement for the namespace of your custom base class. + +## Prompt Execution Settings + +The `PromptTemplate` attribute also allows specification of the prompt execution settings. + +The three parameters are: + +|Parameter|Details|Default| +|--|--|--| +|`MaxTokens`|The maximum number of tokens in the response|`500`| +|`Temperature`|The temperature|`0.5`| +|`TopP`|The TopP|`0`| + +For example: + +```csharp +public static class Prompts +{ + [PromptTemplate(10, 0.1)] + public const string SampleTmpl1 = """ + What is the capitol of {{$state}} {{$country}} + Respond directly on a single line. + """; +} +``` + +(See the `PromptTmpl` class for details) + +## Using the Sample App + +To use the sample app, you'll need to set up user secrets: + +```shell +dotnet user-secrets init +dotnet user-secrets set "AzureOpenAIKey" "YOUR_AZURE_OPEN_AI_KEY" +dotnet user-secrets set "AzureOpenAIEndpoint" "YOUR_AZURE_OPEN_AI_ENDPOINT" +``` + +If you are using OpenAI, feel free to fork this project and simply change the service type and configuration values. + + +::: + +### About +:::note + +Generate typed prompts for Semantic Kernel + + +::: + +## How to use + +### Example (source csproj, source files) + + + + + +This is the CSharp Project that references **SKPromptGenerator** +```xml showLineNumbers {12} + + + + Exe + net8.0 + enable + enable + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + true + $(BaseIntermediateOutputPath)\GX + + + +``` + + + + + + This is the use of **SKPromptGenerator** in *Program.cs* + +```csharp showLineNumbers +// See https://aka.ms/new-console-template for more information +Console.WriteLine("Hello, World!"); +var capitol = new DemoAI.WeatherPrompt("Bucuresti"); + +``` + + + + + This is the use of **SKPromptGenerator** in *WeatherCity.cs* + +```csharp showLineNumbers +using SKPromptGenerator; // <-- Add namespace here + +namespace DemoAI +{ + public static partial class MyPrompts + { + [PromptTemplate] // <-- Remove namespace here + public const string Weather = """ + What is the weather in the city {{$city}} ? + Respond directly in a single line + """; + } +} +``` + + + + +### Generated Files + +Those are taken from $(BaseIntermediateOutputPath)\GX + + + + + +```csharp showLineNumbers +using System; +using Microsoft.SemanticKernel.Connectors.OpenAI; +using SKPromptGenerator; + +namespace DemoAI; + +/// +/// Generated prompt for `Weather` +/// +public partial class WeatherPrompt( + string city +) : PromptTemplateBase +{ + /// + /// The base prompt template string for `Weather` + /// + public override string Text => $$""" +What is the weather in the city {{city}} ? +Respond directly in a single line +"""; + + /// + /// Settings for the prompt `Weather`: + /// MaxTokens = 500 + /// Temperature = 0.5d + /// TopP = 0d + /// + public override OpenAIPromptExecutionSettings Settings => new OpenAIPromptExecutionSettings + { + MaxTokens = 500, + Temperature = 0.5d, + TopP = 0d, + }; +} +``` + + + + +```csharp showLineNumbers +using System; +using System.Reflection; + +namespace SKPromptGenerator; + +/// +/// Attribute applied to `const string` class fields to generate a prompt class. +/// Use this when specifying a custom base class for executing the prompt. +/// +/// The maximum number of tokens; default is 500 +/// The temperature; default is 0.5 +/// The Top P parameter; default is 0 +/// The base type for the template inheriting from `PromptTemplateBase` +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false, AllowMultiple = false)] +public class PromptTemplateAttribute( + int maxTokens = 500, + double temperature = 0.5, + double topP = 0 +) : Attribute where T : PromptTemplateBase { + public int MaxTokens => maxTokens; + public double Temperature => temperature; + public double TopP => topP; +} + +/// +/// Attribute applied to `const string` class fields to generate a prompt class. +/// +/// The maximum number of tokens; default is 500 +/// The temperature; default is 0.5 +/// The Top P parameter; default is 0 +/// The base type for the template inheriting from `PromptTemplateBase` +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false, AllowMultiple = false)] +public class PromptTemplateAttribute( + int maxTokens = 500, + double temperature = 0.5, + double topP = 0 +) : PromptTemplateAttribute(maxTokens, temperature, topP) { + +} +``` + + + + +```csharp showLineNumbers +using System.Text.Json; +using System.Text.Json.Serialization; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.ChatCompletion; +using Microsoft.SemanticKernel.Connectors.OpenAI; + +namespace SKPromptGenerator; + +/// +/// Abstract base class for executing the prompt. Override this class to +/// provide custom execution of the prompt. +/// +public abstract class PromptTemplateBase +{ + protected static readonly JsonSerializerOptions SerializerOptions = new() { + PropertyNameCaseInsensitive = true + }; + + /// + /// The execution settings for this prompt. + /// + public abstract OpenAIPromptExecutionSettings Settings \{ get; } + + /// + /// The text of this prompt. + /// + public abstract string Text \{ get; } + + /// + /// Executes the prompt using the default execution. Override this method + /// to provide custom execution logic (e.g. logging, telemetry, etc.) + /// + /// The Semantic Kernel instance. + /// An optional service ID to specify for execution. + /// An optional builder for the chat history. + /// An optional cancellation token. + /// A string with the results of execution. + public virtual async Task ExecuteAsync( + Kernel kernel, + #nullable enable + string? serviceId = null, + Action? historyBuilder = null, + #nullable disable + CancellationToken cancellation = default + ) + { + var chat = kernel.GetRequiredService(serviceId); + + var history = new ChatHistory(); + + if (historyBuilder != null) + { + historyBuilder(history); + } + + history.AddUserMessage(Text); + + var result = await chat.GetChatMessageContentAsync(history, Settings, kernel, cancellation); + + return result.ToString(); + } + + /// + /// Executes the prompt and expects a JSON response that will be deserialized + /// to the type `T`. + /// + /// The Semantic Kernel instance. + /// An optional service ID to specify for execution. + /// An optional builder for the chat history. + /// An optional cancellation token. + /// The type `T` of the response object. + /// An instance of type `T` deserialized from the JSON response. + #nullable enable + public virtual async Task ExecuteAsync( + Kernel kernel, + #nullable enable + string? serviceId = null, + Action? historyBuilder = null, + #nullable disable + CancellationToken cancellation = default + ) { + var (result, _) = await ExecuteWithJsonAsync(kernel, serviceId, historyBuilder, cancellation); + + return result; + } + #nullable disable + + /// + /// Executes the prompt and expects a JSON response that will be deserialized + /// to the type `T`. This call includes the JSON result as part of the tuple. + /// This method call will perform trimming of JSON fences if present using + /// regular string find/replace. + /// + /// The Semantic Kernel instance. + /// An optional service ID to specify for execution. + /// An optional builder for the chat history. + /// An optional cancellation token. + /// The type `T` of the response object. + /// An instance of type `T` deserialized from the JSON response in a tuple with the full JSON response as well.. + #nullable enable + public virtual async Task<(T? Result, string Json)> ExecuteWithJsonAsync( + Kernel kernel, + #nullable enable + string? serviceId = null, + Action? historyBuilder = null, + #nullable disable + CancellationToken cancellation = default + ) { + var json = await ExecuteAsync(kernel, serviceId, historyBuilder, cancellation); + + json = json.Trim().Replace("```json", "").Replace("```", ""); + + return (JsonSerializer.Deserialize(json, SerializerOptions), json); + } + #nullable disable +} +``` + + + + + +## Useful + +### Download Example (.NET C#) + +:::tip + +[Download Example project SKPromptGenerator ](/sources/SKPromptGenerator.zip) + +::: + + +### Share SKPromptGenerator + + + +https://ignatandrei.github.io/RSCG_Examples/v2/docs/SKPromptGenerator + +aaa + + diff --git a/v2/rscg_examples_site/docs/RSCG-Examples/index.md b/v2/rscg_examples_site/docs/RSCG-Examples/index.md index fc97d4954..ecf5f3574 100644 --- a/v2/rscg_examples_site/docs/RSCG-Examples/index.md +++ b/v2/rscg_examples_site/docs/RSCG-Examples/index.md @@ -1,7 +1,7 @@ --- sidebar_position: 30 -title: 218 RSCG list by category -description: 218 RSCG list by category +title: 219 RSCG list by category +description: 219 RSCG list by category slug: /rscg-examples --- @@ -22,6 +22,18 @@ import DocCardList from '@theme/DocCardList'; +## AI +
+ + Expand AI =>examples:1 + + + +[SKPromptGenerator](/docs/SKPromptGenerator) + +
+ + ## AOP
@@ -1359,6 +1371,8 @@ flowchart LR; Actor--> ActorSrcGen((ActorSrcGen)) + AI--> SKPromptGenerator((SKPromptGenerator)) + AOP--> WhatIAmDoing((WhatIAmDoing)) API--> SkinnyControllersCommon((SkinnyControllersCommon)) diff --git a/v2/rscg_examples_site/docs/about.md b/v2/rscg_examples_site/docs/about.md index 625ce6b9c..05f3aa535 100644 --- a/v2/rscg_examples_site/docs/about.md +++ b/v2/rscg_examples_site/docs/about.md @@ -6,7 +6,7 @@ title: About ## Content You will find here code examples -of 218 Roslyn Source Code Generator (RSCG) +of 219 Roslyn Source Code Generator (RSCG) that can be useful for you. That means, you will write more elegant and concise code - even if the generators code is not always nice to look. ## Are those examples ready for production? diff --git a/v2/rscg_examples_site/docs/indexRSCG.md b/v2/rscg_examples_site/docs/indexRSCG.md index a9158fb3e..13ae32d44 100644 --- a/v2/rscg_examples_site/docs/indexRSCG.md +++ b/v2/rscg_examples_site/docs/indexRSCG.md @@ -7,9 +7,9 @@ slug: /List-of-RSCG import useBaseUrl from '@docusaurus/useBaseUrl'; -## 218 RSCG with examples in descending chronological order +## 219 RSCG with examples in descending chronological order -This is the list of 218 ( 14 from Microsoft) RSCG with examples +This is the list of 219 ( 14 from Microsoft) RSCG with examples [See by category](/docs/rscg-examples) [See as json](/exports/RSCG.json) [See as Excel](/exports/RSCG.xlsx) @@ -20,6 +20,7 @@ This is the list of 218 ( 14 from Microsoft) RSCG with examples | No | Name | Date | Category | | --------- | ----- | ---- | -------- | +|219| [SKPromptGenerator by Charlie Chen ](/docs/SKPromptGenerator)|2025-08-07 => 07 August 2025 | [AI](/docs/Categories/AI) | |218| [Nino by Jason Xu ](/docs/Nino)|2025-08-06 => 06 August 2025 | [Serializer](/docs/Categories/Serializer) | |217| [EnumsEnhanced by VNCC ](/docs/EnumsEnhanced)|2025-08-05 => 05 August 2025 | [Enum](/docs/Categories/Enum) | |216| [SG4MVC by Mark Flanagan ](/docs/SG4MVC)|2025-08-04 => 04 August 2025 | [MVC](/docs/Categories/MVC) | diff --git a/v2/rscg_examples_site/src/components/HomepageFeatures/index.js b/v2/rscg_examples_site/src/components/HomepageFeatures/index.js index 15c2fb118..a78eb3890 100644 --- a/v2/rscg_examples_site/src/components/HomepageFeatures/index.js +++ b/v2/rscg_examples_site/src/components/HomepageFeatures/index.js @@ -4,7 +4,7 @@ import styles from './styles.module.css'; const FeatureList = [ { -title: '218 Examples (14 from MSFT)', +title: '219 Examples (14 from MSFT)', Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default, description: ( <> diff --git a/v2/rscg_examples_site/static/exports/RSCG.json b/v2/rscg_examples_site/static/exports/RSCG.json index 6ee352e49..2e7be95a6 100644 --- a/v2/rscg_examples_site/static/exports/RSCG.json +++ b/v2/rscg_examples_site/static/exports/RSCG.json @@ -1745,6 +1745,14 @@ "Source": "https://github.com/JasonXuDeveloper/Nino", "Category": "Serializer", "AddedOn": "2025-08-06T00:00:00" + }, + { + "Name": "SKPromptGenerator", + "Link": "https://ignatandrei.github.io/RSCG_Examples/v2/docs/SKPromptGenerator", + "NuGet": "https://www.nuget.org/packages/SKPromptGenerator/", + "Source": "https://github.com/CharlieDigital/SKPromptGenerator", + "Category": "AI", + "AddedOn": "2025-08-07T00:00:00" } ] } \ No newline at end of file diff --git a/v2/rscg_examples_site/static/exports/RSCG.xlsx b/v2/rscg_examples_site/static/exports/RSCG.xlsx index 4457a38414851c1829e5ec2ebe6ef9aaa753b779..9ccc19e07284475f3e2c9132b8ea350036c98b4a 100644 GIT binary patch delta 878 zcmbOgdn~p-z?+#xgn@yBgP}rSKDue&JJ!7n3=B$)3=I50x*|uvJijPADL+43uOc_6 zxBoO>lYxM1{nD9x#RPkW4z#KrTU+PY$S9TBUb5=b&uibW-JNMY`Q*-iR(6s3-k!x7n!T6AOY;#OS?G~ICe*Jh&a9;BH zW;6A>%Ii-BeX2O$pgLK0J>!3;FW35F*yiuCJpba7euCdiD@8`@S2vP_4(MMl4@(i- z@Z9g9WJ1e$6z?+#xgn@yBgF&WAE?VPL690Y%1_mWY1_pj0U6G?-o?n!ml%JoiSCO03 z+kd+6kO7Ztz3Qhrk0`|ztG4MlNNsG@=UC~iJZneSVITYYL)F&1A7`Jo+J5s)ort}} z&A^O>5gGy#JYV(ZMDI}h9eQwC_OoBpA3b#`5&UUaU|YZ4V2R1qpiPn&vg0naD8AQm zT^}s>IP#N7;CCAZHTzRRE2n_i3UKuWF_c#)`{LV~vb3=}srk|9je!RNgxMb4z=wn^&GGFdjI4# z*!KB$=dusG%YJU&t|dF&{(t{}c9^*g;6Qxm8Tx4R7ENhJfmDlRL$lR(kuprLe*;Cf&SJixLdCCt92yklJETp5w$O%tYlU;R9n1Q^>HM-J_ zZzj*sl~F(qcqRr3V3I?CtxS{8Gf7Q;r^^!wQV0rG4Pd$iVQz#fAgfH1fg!#qHK$l# z4@3rdGct)VAQCkwux|mC?FDL-LeY3!YcjvK%;bDM9fzQs30 From 607d98921948e9d37d7f537c26b75bf88d5cf51a Mon Sep 17 00:00:00 2001 From: Andrei Ignat Date: Sun, 31 Aug 2025 06:02:28 +0300 Subject: [PATCH 2/2] zip --- v2/book/examples/SKPromptGenerator.html | 69 ++++++++++++++++++ v2/book/list.html | 6 +- v2/book/pandocHTML.yaml | 1 + .../static/sources/SKPromptGenerator.zip | Bin 0 -> 1641 bytes 4 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 v2/book/examples/SKPromptGenerator.html create mode 100644 v2/rscg_examples_site/static/sources/SKPromptGenerator.zip diff --git a/v2/book/examples/SKPromptGenerator.html b/v2/book/examples/SKPromptGenerator.html new file mode 100644 index 000000000..17d93386b --- /dev/null +++ b/v2/book/examples/SKPromptGenerator.html @@ -0,0 +1,69 @@ + +

RSCG nr 219 : SKPromptGenerator

+ +

Info

+Nuget : https://www.nuget.org/packages/SKPromptGenerator/ + +

You can find more details at : https://github.com/CharlieDigital/SKPromptGenerator

+ +

Author :Charlie Chen

+ +

Source: https://github.com/CharlieDigital/SKPromptGenerator

+ +

About

+ +Generate typed prompts for Semantic Kernel + +

+ How to use +

+

+ Add reference to the SKPromptGenerator in the csproj +

+ + +

This was for me the starting code

+ +
+ I have coded the file Program.cs +
+ +
+ +
+ I have coded the file WeatherCity.cs +
+ +
+

And here are the generated files

+ +
+ The file generated is WeatherPrompt.g.cs +
+ + +
+ The file generated is PromptTemplateAttribute.g.cs +
+ + +
+ The file generated is PromptTemplateBase.g.cs +
+ + +

+ You can download the code and this page as pdf from + + https://ignatandrei.github.io/RSCG_Examples/v2/docs/SKPromptGenerator + +

+ + +

+ You can see the whole list at + + https://ignatandrei.github.io/RSCG_Examples/v2/docs/List-of-RSCG + +

+ diff --git a/v2/book/list.html b/v2/book/list.html index 7bdcfa523..922c935ee 100644 --- a/v2/book/list.html +++ b/v2/book/list.html @@ -17,7 +17,7 @@

-This is the list of 218 RSCG with examples => +This is the list of 219 RSCG with examples =>

@@ -898,6 +898,10 @@

+ + + +
218 Nino
219SKPromptGenerator
diff --git a/v2/book/pandocHTML.yaml b/v2/book/pandocHTML.yaml index 207f3c0b5..a1c3bd536 100644 --- a/v2/book/pandocHTML.yaml +++ b/v2/book/pandocHTML.yaml @@ -232,6 +232,7 @@ input-files: - examples/SG4MVC.html - examples/EnumsEnhanced.html - examples/Nino.html +- examples/SKPromptGenerator.html # or you may use input-file: with a single value # defaults: diff --git a/v2/rscg_examples_site/static/sources/SKPromptGenerator.zip b/v2/rscg_examples_site/static/sources/SKPromptGenerator.zip new file mode 100644 index 0000000000000000000000000000000000000000..ebbbc6deb6a10cb203d71a628dc66e74c9459caf GIT binary patch literal 1641 zcmWIWW@Zs#U|`^2SY0U_-I*0XZzm%ILpBQo0~b)#B{etS(NnKDCvWMbgMG6N1lrDb zAF)=by6KgD+ANNvUCzMlw%1#&tSj@X6c2XBSXwXzmri|l^4kUXSK4bdy?32lc2#R{ z!}I^?$K;Lw?E3$*TK>u+!?lsO9&FCq9mH;YJMLFa#Lot zpm4{8_j|3hZr$#D&wDPR=a=+z2cJZd;O{CbE9#Ehx~{!;_m{pYhmwcjLCquk%yZ9# z@7yr`;kFYji+tafL)zx!fZT-N>F5`4z$R!>eB zmhD_QJ?pAzs=QBW=JdR-T>dSYPi8z>rN3iN`D*#dTJa~B{m~#-1dHp)M{iqpcT*)W zg4LKA7(meuiC}$bJSP_y6y;~N1|Rf0e8@gWioYsTF)-OFa}k?|vx`!OO-1A_ z_H{Yi&)yCfsXyC1|B3#NwbD9APpq}hH_A`@^y}-#m;MH|jU6{k;=az27ED}qnRVym ze`ic52ydCSRxO!TmwCxqfy*}BTqhc~Tc%o`HIrodYyM&HsoO?rGSeNHpK$J3wu4LL zLhJ2sd#&-^G-YY$GdIk!~jS@bP24cY3_)2dQJD%O%}Hc=l{ z?;Kx5!!8@{LrBn2>soM>n9ZO}3H9Ozhv)iXoDEo6ykn4gB zMU@wNkGoEv-F-8+vLz-n(7^Yyb(PtaaQ{^9b!9hi?4ICu_`Q15I^T@V;<<_3#)yPq@0BLg*#do{^9eV*VezUJYdm>edXG!L4U-b{gy^V zRRJ)nqOgtaD(wD`Nb)a&PZKZinWqQ{KsM?ru|@-I0E|_{^J&8cv_W z9L>eJbQHwZdBS9mZ@Te!>o(!#7CSq3Sskd33poC0{pPHFTHWoUuX2k{&AVoDH?#l1 z?Bv@AUolv6w=XH(D*HQd!qva4^bwBd1Umk5<9oH+Ku4zl)0!y4@!_e7B^jwj&Y2~Z zKo6u&IOxS}DA4x4anIrNkG6hcT;<^?Am|viko}`U8oTrfHN}}>(^BW3Y3_fbJ!40- z*T?-l#-IQHJn^TdR({i0`5Sh2%P+X@Oh4bLvP5*6cZ|MR#CBbq(mBeK$L0xVFTGk> zbUZe~KEpJlbx}n{`85S@PtA#wB8m+9ijD=_JAO);D;&E1@}t(>FFby{Ww}4)c>kaN z`mVDE!Zp*He7>q%t7+=pS&%tv^VY2G>WybLs%y@fz5XASy!5z1%vGzt9ea85xNLuj zt@X`0^3~LJMSC$%x^4U|?g~?1x3$OO1H2iTM8G)=d%+3}5+r~qW|8$_%dZGc4}l?z zE%zdufGrszOy~iY0Z!22#cXgnxi^I4g(;01Xw9 A_5c6? literal 0 HcmV?d00001