Files
semantic-kernel/samples/dotnet/KernelHttpServer/SemanticKernelEndpoint.cs
T
Devis Lucato 32fbc90aa7 Fix code style and xmldocs, add Roslynator analyzer (#1030)
Add [Roslynator](https://github.com/JosefPihrt/Roslynator) and fix some
code style issues found.

`dotnet_analyzer_diagnostic.severity` is set to `none` to stop `dotnet
format` from editing a lot of files, even unrelated issues, probably
detected by other analyzers, and a lot of issues are disabled. Currently
only these are enabled:

```
dotnet_diagnostic.RCS1036.severity = warning # Remove unnecessary blank line.
dotnet_diagnostic.RCS1037.severity = warning # Remove trailing white-space.
dotnet_diagnostic.RCS1097.severity = warning # Remove redundant 'ToString' call.
dotnet_diagnostic.RCS1138.severity = warning # Add summary to documentation comment.
dotnet_diagnostic.RCS1168.severity = warning # Parameter name 'foo' differs from base name 'bar'.
dotnet_diagnostic.RCS1175.severity = warning # Unused 'this' parameter 'operation'.
dotnet_diagnostic.RCS1192.severity = warning # Unnecessary usage of verbatim string literal.
dotnet_diagnostic.RCS1139.severity = warning # Add summary element to documentation comment.
dotnet_diagnostic.RCS1211.severity = warning # Remove unnecessary else clause.
dotnet_diagnostic.RCS1214.severity = warning # Unnecessary interpolated string.
dotnet_diagnostic.RCS1225.severity = warning # Make class sealed.
dotnet_diagnostic.RCS1232.severity = warning # Order elements in documentation comment.
```

Note: 
* There are a lot more issues to fix, for now I disabled a lot of checks
and fixed only a bunch.
* Future: enable more of `dotnet_diagnostic.RCS` currently disabled,
e.g. XMLDoc checks, to get better code docs

Other fixes:
* fix incorrect namespaces in copilot chat
* use static property for regex
* typos, missing copyright
* switch from CA1032 to RCS1194 to cover more exception ctors
2023-05-17 12:16:21 -07:00

167 lines
5.9 KiB
C#

// Copyright (c) Microsoft. All rights reserved.
using System.Linq;
using System.Net;
using System.Text.Json;
using System.Threading.Tasks;
using KernelHttpServer.Model;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.AI;
using Microsoft.SemanticKernel.Memory;
using Microsoft.SemanticKernel.Orchestration;
using Microsoft.SemanticKernel.Planning;
namespace KernelHttpServer;
public class SemanticKernelEndpoint
{
private static readonly JsonSerializerOptions s_jsonOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
private readonly IMemoryStore _memoryStore;
public SemanticKernelEndpoint(IMemoryStore memoryStore)
{
this._memoryStore = memoryStore;
}
[Function("InvokeFunction")]
public async Task<HttpResponseData> InvokeFunctionAsync(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "skills/{skillName}/invoke/{functionName}")]
HttpRequestData req,
FunctionContext executionContext, string skillName, string functionName)
{
// in this sample we are using a per-request kernel that is created on each invocation
// once created, we feed the kernel the ask received via POST from the client
// and attempt to invoke the function with the given name
var ask = await JsonSerializer.DeserializeAsync<Ask>(req.Body, s_jsonOptions);
if (ask == null)
{
return await req.CreateResponseWithMessageAsync(HttpStatusCode.BadRequest, "Invalid request, unable to parse the request payload");
}
var kernel = SemanticKernelFactory.CreateForRequest(
req,
executionContext.GetLogger<SemanticKernelEndpoint>(),
ask.Skills,
this._memoryStore);
if (kernel == null)
{
return await req.CreateResponseWithMessageAsync(HttpStatusCode.BadRequest, "Missing one or more expected HTTP Headers");
}
var f = kernel.Skills.GetFunction(skillName, functionName);
var contextVariables = new ContextVariables(ask.Value);
foreach (var input in ask.Inputs)
{
contextVariables.Set(input.Key, input.Value);
}
var result = await kernel.RunAsync(contextVariables, f);
if (result.ErrorOccurred)
{
return await ResponseErrorWithMessageAsync(req, result);
}
var r = req.CreateResponse(HttpStatusCode.OK);
await r.WriteAsJsonAsync(new AskResult { Value = result.Result, State = result.Variables.Select(v => new AskInput { Key = v.Key, Value = v.Value }) });
return r;
}
[Function("CreatePlan")]
public async Task<HttpResponseData> CreatePlanAsync(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "planner/createplan")]
HttpRequestData req,
FunctionContext executionContext)
{
var ask = await JsonSerializer.DeserializeAsync<Ask>(req.Body, s_jsonOptions);
if (ask == null)
{
return await req.CreateResponseWithMessageAsync(HttpStatusCode.BadRequest, "Invalid request, unable to parse the request payload");
}
var kernel = SemanticKernelFactory.CreateForRequest(
req,
executionContext.GetLogger<SemanticKernelEndpoint>(),
ask.Skills);
if (kernel == null)
{
return await req.CreateResponseWithMessageAsync(HttpStatusCode.BadRequest, "Missing one or more expected HTTP Headers");
}
var planner = new SequentialPlanner(kernel);
var goal = ask.Value;
var plan = await planner.CreatePlanAsync(goal);
var r = req.CreateResponse(HttpStatusCode.OK);
await r.WriteAsJsonAsync(new AskResult { Value = plan.ToJson() });
return r;
}
[Function("ExecutePlan")]
public async Task<HttpResponseData> ExecutePlanAsync(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "planner/execute/{maxSteps?}")]
HttpRequestData req,
FunctionContext executionContext, int? maxSteps = 10)
{
var ask = await JsonSerializer.DeserializeAsync<Ask>(req.Body, s_jsonOptions);
if (ask == null)
{
return await req.CreateResponseWithMessageAsync(HttpStatusCode.BadRequest, "Invalid request, unable to parse the request payload");
}
var kernel = SemanticKernelFactory.CreateForRequest(
req,
executionContext.GetLogger<SemanticKernelEndpoint>(),
ask.Skills);
if (kernel == null)
{
return await req.CreateResponseWithMessageAsync(HttpStatusCode.BadRequest, "Missing one or more expected HTTP Headers");
}
var context = kernel.CreateNewContext();
var plan = Plan.FromJson(ask.Value, context);
var iterations = 1;
while (plan.HasNextStep &&
iterations < maxSteps)
{
try
{
plan = await kernel.StepAsync(context.Variables, plan);
}
catch (KernelException e)
{
context.Fail(e.Message, e);
return await ResponseErrorWithMessageAsync(req, context);
}
iterations++;
}
var r = req.CreateResponse(HttpStatusCode.OK);
await r.WriteAsJsonAsync(new AskResult { Value = plan.State.ToString() });
return r;
}
private static async Task<HttpResponseData> ResponseErrorWithMessageAsync(HttpRequestData req, SKContext result)
{
return result.LastException is AIException aiException && aiException.Detail is not null
? await req.CreateResponseWithMessageAsync(HttpStatusCode.BadRequest, string.Concat(aiException.Message, " - Detail: " + aiException.Detail))
: await req.CreateResponseWithMessageAsync(HttpStatusCode.BadRequest, result.LastErrorDescription);
}
}