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/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/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 4457a3841..9ccc19e07 100644 Binary files a/v2/rscg_examples_site/static/exports/RSCG.xlsx and b/v2/rscg_examples_site/static/exports/RSCG.xlsx differ 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 000000000..ebbbc6deb Binary files /dev/null and b/v2/rscg_examples_site/static/sources/SKPromptGenerator.zip differ