mirror of
https://github.com/microsoft/semantic-kernel.git
synced 2026-10-12 02:19:06 +00:00
### Motivation and Context Need for a simple planner showing how to leverage semantic functions to implement Intent Detection pattern and turn it into a planner, selecting a function given a goal. The planner also allows to provide a Planning capability using less powerful models, like GPT3 (text-davinci-002) and GPT3.5 (text-davinci-003, GPT turbo 3.5), where often the more advanced planner can hallucinate. Fix one bug in "WriterSkill/ShortPoem/skprompt.txt" leading to incorrect results when the goal was missing a termination (period or new line) ### Description * New package `Microsoft.SemanticKernel.Planning.ActionPlanner`extending the Planning namespace. The package is automatically installed, ie it's part of SK metapackage. * Move SequentialPlanner into the new package `Microsoft.SemanticKernel.Planning.SequentialPlanner` lib, part of the metapackage * Add new Action Planner. * Store planner prompts to skprompt.txt, using embedded resources to avoid files overwriting each other.
167 lines
5.9 KiB
C#
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 JsonSerializerOptions { 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);
|
|
}
|
|
}
|