chore: import upstream snapshot with attribution
This commit is contained in:
+106
@@ -0,0 +1,106 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
using ProcessWithCloudEvents.Processes.Steps;
|
||||
|
||||
namespace ProcessWithCloudEvents.Processes;
|
||||
|
||||
/// <summary>
|
||||
/// Components related to the SK Process for generating documentation
|
||||
/// </summary>
|
||||
public static class DocumentGenerationProcess
|
||||
{
|
||||
/// <summary>
|
||||
/// SK Process events emitted by <see cref="DocumentGenerationProcess"/>
|
||||
/// </summary>
|
||||
public static class DocGenerationEvents
|
||||
{
|
||||
/// <summary>
|
||||
/// Event to start the document generation process
|
||||
/// </summary>
|
||||
public const string StartDocumentGeneration = nameof(StartDocumentGeneration);
|
||||
/// <summary>
|
||||
/// Event emitted when the user rejects the document
|
||||
/// </summary>
|
||||
public const string UserRejectedDocument = nameof(UserRejectedDocument);
|
||||
/// <summary>
|
||||
/// Event emitted when the user approves the document
|
||||
/// </summary>
|
||||
public const string UserApprovedDocument = nameof(UserApprovedDocument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SK Process topics emitted by <see cref="DocumentGenerationProcess"/>
|
||||
/// Topics are used to emit events to external systems
|
||||
/// </summary>
|
||||
public static class DocGenerationTopics
|
||||
{
|
||||
/// <summary>
|
||||
/// Request user review document generation topic
|
||||
/// </summary>
|
||||
public const string RequestUserReview = nameof(RequestUserReview);
|
||||
/// <summary>
|
||||
/// Publish documentat generated topic
|
||||
/// </summary>
|
||||
public const string PublishDocumentation = nameof(PublishDocumentation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a process builder for the Document Generation SK Process
|
||||
/// </summary>
|
||||
/// <param name="processName">name of the SK Process</param>
|
||||
/// <returns>instance of <see cref="ProcessBuilder"/></returns>
|
||||
public static ProcessBuilder CreateProcessBuilder(string processName = "DocumentationGeneration")
|
||||
{
|
||||
// Create the process builder
|
||||
ProcessBuilder processBuilder = new(processName);
|
||||
|
||||
// Add the steps
|
||||
var infoGatheringStep = processBuilder.AddStepFromType<GatherProductInfoStep>();
|
||||
var docsGenerationStep = processBuilder.AddStepFromType<GenerateDocumentationStep>();
|
||||
var docsProofreadStep = processBuilder.AddStepFromType<ProofReadDocumentationStep>();
|
||||
var docsPublishStep = processBuilder.AddStepFromType<PublishDocumentationStep>();
|
||||
|
||||
var proxyStep = processBuilder.AddProxyStep(id: processName, [DocGenerationTopics.RequestUserReview, DocGenerationTopics.PublishDocumentation]);
|
||||
|
||||
// Orchestrate the external input events
|
||||
processBuilder
|
||||
.OnInputEvent(DocGenerationEvents.StartDocumentGeneration)
|
||||
.SendEventTo(new(infoGatheringStep));
|
||||
|
||||
processBuilder
|
||||
.OnInputEvent(DocGenerationEvents.UserRejectedDocument)
|
||||
.SendEventTo(new(docsGenerationStep, functionName: GenerateDocumentationStep.ProcessFunctions.ApplySuggestions));
|
||||
|
||||
processBuilder
|
||||
.OnInputEvent(DocGenerationEvents.UserApprovedDocument)
|
||||
.SendEventTo(new(docsPublishStep, parameterName: "userApproval"));
|
||||
|
||||
// Hooking up the rest of the process steps
|
||||
infoGatheringStep
|
||||
.OnFunctionResult()
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(docsGenerationStep, functionName: GenerateDocumentationStep.ProcessFunctions.GenerateDocs));
|
||||
|
||||
docsGenerationStep
|
||||
.OnEvent(GenerateDocumentationStep.OutputEvents.DocumentationGenerated)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(docsProofreadStep));
|
||||
|
||||
docsProofreadStep
|
||||
.OnEvent(ProofReadDocumentationStep.OutputEvents.DocumentationRejected)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(docsGenerationStep, functionName: GenerateDocumentationStep.ProcessFunctions.ApplySuggestions));
|
||||
|
||||
// When the proofreader approves the documentation, send it to the 'docs' parameter of the docsPublishStep
|
||||
// Additionally, the generated document is emitted externally for user approval using the pre-configured proxyStep
|
||||
docsProofreadStep
|
||||
.OnEvent(ProofReadDocumentationStep.OutputEvents.DocumentationApproved)
|
||||
.EmitExternalEvent(proxyStep, DocGenerationTopics.RequestUserReview)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(docsPublishStep, parameterName: "document"));
|
||||
|
||||
// When event is approved by user, it gets published externally too
|
||||
docsPublishStep
|
||||
.OnFunctionResult()
|
||||
.EmitExternalEvent(proxyStep, DocGenerationTopics.PublishDocumentation);
|
||||
|
||||
return processBuilder;
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace ProcessWithCloudEvents.Processes.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Object used to store generated document data
|
||||
/// Since this object is used as parameter and state type by multiple steps,
|
||||
/// Its members must be public and serializable
|
||||
/// </summary>
|
||||
//[DataContract]
|
||||
public class DocumentInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Id of the document
|
||||
/// </summary>
|
||||
public string Id { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// Title of the document
|
||||
/// </summary>
|
||||
public string Title { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// Content of the document
|
||||
/// </summary>
|
||||
public string Content { get; set; } = string.Empty;
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using ProcessWithCloudEvents.Processes.Steps;
|
||||
|
||||
namespace ProcessWithCloudEvents.Processes.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Object used in the <see cref="GatherProductInfoStep"/>
|
||||
/// </summary>
|
||||
public class ProductInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Title of the product
|
||||
/// </summary>
|
||||
public string Title { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// Content of the product
|
||||
/// </summary>
|
||||
public string Content { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// User comments
|
||||
/// </summary>
|
||||
public string UserInput { get; set; } = string.Empty;
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<NoWarn>
|
||||
$(NoWarn);CA2007,CA1861,VSTHRD111,SKEXP0001,SKEXP0010,SKEXP0040,SKEXP0050,SKEXP0060,SKEXP0080,SKEXP0110
|
||||
</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Connectors\Connectors.OpenAI\Connectors.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Experimental\Process.Abstractions\Process.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Experimental\Process.Core\Process.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
using ProcessWithCloudEvents.Processes.Models;
|
||||
|
||||
namespace ProcessWithCloudEvents.Processes.Steps;
|
||||
|
||||
/// <summary>
|
||||
/// Step that receives product information
|
||||
/// </summary>
|
||||
public class GatherProductInfoStep : KernelProcessStep
|
||||
{
|
||||
/// <summary>
|
||||
/// Only step function that process the information passed
|
||||
/// When there is only one function, there is no need to specify functionNames in the KernelFunction annotator
|
||||
/// </summary>
|
||||
/// <param name="productInfo">product information</param>
|
||||
/// <returns></returns>
|
||||
[KernelFunction]
|
||||
public ProductInfo OnReceiveUserRequest(ProductInfo productInfo)
|
||||
{
|
||||
Console.WriteLine($"[{nameof(GatherProductInfoStep)}]:\tGathering product information for product named {productInfo.Title}");
|
||||
|
||||
// For example purposes we just return some fictional information.
|
||||
productInfo.Content = """
|
||||
Product Description:
|
||||
GlowBrew is a revolutionary AI driven coffee machine with industry leading number of LEDs and programmable light shows. The machine is also capable of brewing coffee and has a built in grinder.
|
||||
|
||||
Product Features:
|
||||
1. **Luminous Brew Technology**: Customize your morning ambiance with programmable LED lights that sync with your brewing process.
|
||||
2. **AI Taste Assistant**: Learns your taste preferences over time and suggests new brew combinations to explore.
|
||||
3. **Gourmet Aroma Diffusion**: Built-in aroma diffusers enhance your coffee's scent profile, energizing your senses before the first sip.
|
||||
|
||||
Troubleshooting:
|
||||
- **Issue**: LED Lights Malfunctioning
|
||||
- **Solution**: Reset the lighting settings via the app. Ensure the LED connections inside the GlowBrew are secure. Perform a factory reset if necessary.
|
||||
""";
|
||||
|
||||
return productInfo;
|
||||
}
|
||||
}
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using ProcessWithCloudEvents.Processes.Models;
|
||||
|
||||
namespace ProcessWithCloudEvents.Processes.Steps;
|
||||
|
||||
/// <summary>
|
||||
/// Step that generates document content
|
||||
/// </summary>
|
||||
public class GenerateDocumentationStep : KernelProcessStep<GenerateDocumentationState>
|
||||
{
|
||||
/// <summary>
|
||||
/// Function names of the steps, to be refereced when hooking up the step in a SK process
|
||||
/// </summary>
|
||||
public static class ProcessFunctions
|
||||
{
|
||||
/// <summary>
|
||||
/// Genereta Doc function name
|
||||
/// </summary>
|
||||
public const string GenerateDocs = nameof(GenerateDocs);
|
||||
/// <summary>
|
||||
/// Apply Suggestions function name
|
||||
/// </summary>
|
||||
public const string ApplySuggestions = nameof(ApplySuggestions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Output events of the step, using this since 2 steps emit the same output event
|
||||
/// </summary>
|
||||
public static class OutputEvents
|
||||
{
|
||||
/// <summary>
|
||||
/// Document Generated output event
|
||||
/// </summary>
|
||||
public const string DocumentationGenerated = nameof(DocumentationGenerated);
|
||||
}
|
||||
|
||||
internal GenerateDocumentationState _state = new();
|
||||
|
||||
private readonly string _systemPrompt =
|
||||
"""
|
||||
Your job is to write high quality and engaging customer facing documentation for a new product from Contoso. You will be provide with information
|
||||
about the product in the form of internal documentation, specs, and troubleshooting guides and you must use this information and
|
||||
nothing else to generate the documentation. If suggestions are provided on the documentation you create, take the suggestions into account and
|
||||
rewrite the documentation. Make sure the product sounds amazing.
|
||||
""";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ValueTask ActivateAsync(KernelProcessStepState<GenerateDocumentationState> state)
|
||||
{
|
||||
this._state = state.State!;
|
||||
this._state.ChatHistory ??= new ChatHistory(this._systemPrompt);
|
||||
|
||||
return base.ActivateAsync(state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Function that generates documentation from the <see cref="ProductInfo"/> provided
|
||||
/// </summary>
|
||||
/// <param name="kernel">instance of <see cref="Kernel"/></param>
|
||||
/// <param name="context">instance of <see cref="KernelProcessStepContext"/></param>
|
||||
/// <param name="productInfo">content to be used for document generation</param>
|
||||
/// <returns></returns>
|
||||
[KernelFunction(ProcessFunctions.GenerateDocs)]
|
||||
public async Task GenerateDocumentationAsync(Kernel kernel, KernelProcessStepContext context, ProductInfo productInfo)
|
||||
{
|
||||
Console.WriteLine($"[{nameof(GenerateDocumentationStep)}]:\tGenerating documentation for provided productInfo...");
|
||||
|
||||
// Add the new product info to the chat history
|
||||
this._state.ChatHistory!.AddUserMessage($"Product Info:\n{productInfo.Title} - {productInfo.Content}");
|
||||
|
||||
// Get a response from the LLM
|
||||
IChatCompletionService chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
|
||||
var generatedDocumentationResponse = await chatCompletionService.GetChatMessageContentAsync(this._state.ChatHistory!);
|
||||
|
||||
DocumentInfo generatedContent = new()
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
Title = $"Generated document - {productInfo.Title}",
|
||||
Content = generatedDocumentationResponse.Content!,
|
||||
};
|
||||
|
||||
this._state!.LastGeneratedDocument = generatedContent;
|
||||
|
||||
await context.EmitEventAsync(OutputEvents.DocumentationGenerated, generatedContent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Function that integrates suggestion into document content
|
||||
/// </summary>
|
||||
/// <param name="kernel">instance of <see cref="Kernel"/></param>
|
||||
/// <param name="context">instance of <see cref="KernelProcessStepContext"/></param>
|
||||
/// <param name="suggestions">suggestions to be integrated into the document content</param>
|
||||
/// <returns></returns>
|
||||
[KernelFunction(ProcessFunctions.ApplySuggestions)]
|
||||
public async Task ApplySuggestionsAsync(Kernel kernel, KernelProcessStepContext context, string suggestions)
|
||||
{
|
||||
Console.WriteLine($"[{nameof(GenerateDocumentationStep)}]:\tRewriting documentation with provided suggestions...");
|
||||
|
||||
// Add the new product info to the chat history
|
||||
this._state.ChatHistory!.AddUserMessage($"Rewrite the documentation with the following suggestions:\n\n{suggestions}");
|
||||
|
||||
// Get a response from the LLM
|
||||
IChatCompletionService chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
|
||||
var generatedDocumentationResponse = await chatCompletionService.GetChatMessageContentAsync(this._state.ChatHistory!);
|
||||
|
||||
DocumentInfo updatedContent = new()
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
Title = $"Revised - {this._state?.LastGeneratedDocument.Title}",
|
||||
Content = generatedDocumentationResponse.Content!,
|
||||
};
|
||||
|
||||
this._state!.LastGeneratedDocument = updatedContent;
|
||||
|
||||
await context.EmitEventAsync(OutputEvents.DocumentationGenerated, updatedContent);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// State of <see cref="GenerateDocumentationStep"/>
|
||||
/// State must be saved since data is shared across functions
|
||||
/// </summary>
|
||||
public sealed class GenerateDocumentationState
|
||||
{
|
||||
/// <summary>
|
||||
/// Last Document generated data
|
||||
/// </summary>
|
||||
public DocumentInfo LastGeneratedDocument { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Chat history
|
||||
/// </summary>
|
||||
public ChatHistory? ChatHistory { get; set; }
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Text.Json;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Connectors.OpenAI;
|
||||
using ProcessWithCloudEvents.Processes.Models;
|
||||
|
||||
namespace ProcessWithCloudEvents.Processes.Steps;
|
||||
|
||||
/// <summary>
|
||||
/// Step that determines generated document readiness
|
||||
/// </summary>
|
||||
public class ProofReadDocumentationStep : KernelProcessStep
|
||||
{
|
||||
/// <summary>
|
||||
/// SK Process Events emitted by <see cref="ProofReadDocumentationStep"/>
|
||||
/// </summary>
|
||||
public static class OutputEvents
|
||||
{
|
||||
/// <summary>
|
||||
/// Document has errors and needs to be revised event
|
||||
/// </summary>
|
||||
public const string DocumentationRejected = nameof(DocumentationRejected);
|
||||
/// <summary>
|
||||
/// Document looks ok and can be processed by the next step
|
||||
/// </summary>
|
||||
public const string DocumentationApproved = nameof(DocumentationApproved);
|
||||
}
|
||||
|
||||
private readonly string _systemPrompt = """"
|
||||
Your job is to proofread customer facing documentation for a new product from Contoso. You will be provide with proposed documentation
|
||||
for a product and you must do the following things:
|
||||
|
||||
1. Determine if the documentation is passes the following criteria:
|
||||
1. Documentation must use a professional tone.
|
||||
1. Documentation should be free of spelling or grammar mistakes.
|
||||
1. Documentation should be free of any offensive or inappropriate language.
|
||||
1. Documentation should be technically accurate.
|
||||
2. If the documentation does not pass 1, you must write detailed feedback of the changes that are needed to improve the documentation.
|
||||
"""";
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the document is needs a revision or is ready to be processed by the next step
|
||||
/// </summary>
|
||||
/// <param name="kernel">instance of <see cref="Kernel"/></param>
|
||||
/// <param name="context">instance of <see cref="KernelProcessStepContext"/></param>
|
||||
/// <param name="document">document content that is verified</param>
|
||||
/// <returns></returns>
|
||||
[KernelFunction]
|
||||
public async Task ProofreadDocumentationAsync(Kernel kernel, KernelProcessStepContext context, DocumentInfo document)
|
||||
{
|
||||
var chatHistory = new ChatHistory(this._systemPrompt);
|
||||
chatHistory.AddUserMessage(document.Content);
|
||||
|
||||
// Use structured output to ensure the response format is easily parsable
|
||||
var settings = new OpenAIPromptExecutionSettings()
|
||||
{
|
||||
ResponseFormat = typeof(ProofreadingResponse)
|
||||
};
|
||||
|
||||
IChatCompletionService chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
|
||||
var proofreadResponse = await chatCompletionService.GetChatMessageContentAsync(chatHistory, executionSettings: settings);
|
||||
var formattedResponse = JsonSerializer.Deserialize<ProofreadingResponse>(proofreadResponse.Content!);
|
||||
|
||||
Console.WriteLine($"[{nameof(ProofReadDocumentationStep)}]:\n\tGrade = {(formattedResponse!.MeetsExpectations ? "Pass" : "Fail")}\n\tExplanation = {formattedResponse.Explanation}\n\tSuggestions = {string.Join("\n\t\t", formattedResponse.Suggestions)}");
|
||||
|
||||
if (formattedResponse.MeetsExpectations)
|
||||
{
|
||||
// Events that are getting piped to steps that will be resumed, like PublishDocumentationStep.OnPublishDocumentation
|
||||
// require events to be marked as public so they are persisted and restored correctly
|
||||
await context.EmitEventAsync(OutputEvents.DocumentationApproved, data: document, visibility: KernelProcessEventVisibility.Public);
|
||||
}
|
||||
else
|
||||
{
|
||||
await context.EmitEventAsync(new()
|
||||
{
|
||||
Id = OutputEvents.DocumentationRejected,
|
||||
// This event is getting piped to the GenerateDocumentationStep.ApplySuggestionsAsync step which expects a string with suggestions for the document
|
||||
Data = $"Explanation = {formattedResponse.Explanation}, Suggestions = {string.Join(",", formattedResponse.Suggestions)} ",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ProofreadingResponse
|
||||
{
|
||||
[Description("Specifies if the proposed documentation meets the expected standards for publishing.")]
|
||||
public bool MeetsExpectations { get; set; }
|
||||
|
||||
[Description("An explanation of why the documentation does or does not meet expectations.")]
|
||||
public string Explanation { get; set; } = "";
|
||||
|
||||
[Description("A lis of suggestions, may be empty if there no suggestions for improvement.")]
|
||||
public List<string> Suggestions { get; set; } = [];
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
using ProcessWithCloudEvents.Processes.Models;
|
||||
|
||||
namespace ProcessWithCloudEvents.Processes.Steps;
|
||||
|
||||
/// <summary>
|
||||
/// Step that publishes the generated documentation
|
||||
/// </summary>
|
||||
public class PublishDocumentationStep : KernelProcessStep
|
||||
{
|
||||
/// <summary>
|
||||
/// Function that publishes the generated documentation
|
||||
/// </summary>
|
||||
/// <param name="document">document to be published</param>
|
||||
/// <param name="userApproval">approval from the user</param>
|
||||
/// <returns><see cref="DocumentInfo"/></returns>
|
||||
[KernelFunction]
|
||||
public DocumentInfo OnPublishDocumentation(DocumentInfo document, bool userApproval)
|
||||
{
|
||||
if (userApproval)
|
||||
{
|
||||
// For example purposes we just write the generated docs to the console
|
||||
Console.WriteLine($"[{nameof(PublishDocumentationStep)}]:\tPublishing product documentation approved by user: \n{document.Title}\n{document.Content}");
|
||||
}
|
||||
return document;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user