forked from microsoft/semantic-kernel
-
Notifications
You must be signed in to change notification settings - Fork 0
/
SemanticKernelEndpoint.cs
179 lines (147 loc) · 6.4 KB
/
SemanticKernelEndpoint.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
// 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");
}
// TODO: Support SequentialPlanner
var planner = new ActionPlanner(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();
foreach (var input in ask.Inputs)
{
context.Variables.Set(input.Key, input.Value);
}
// Reload the plan with full context to be executed
var plan = Plan.FromJson(ask.Value, context);
var r = req.CreateResponse(HttpStatusCode.OK);
try
{
if (plan.Steps.Count < maxSteps)
{
var planResult = await plan.InvokeAsync(context);
await r.WriteAsJsonAsync(new AskResult { Value = planResult.Result });
}
else
{
var iterations = 1;
while (plan.HasNextStep &&
iterations < maxSteps)
{
plan = await kernel.StepAsync(context.Variables, plan);
iterations++;
}
await r.WriteAsJsonAsync(new AskResult { Value = plan.State.ToString() });
}
}
catch (KernelException e)
{
context.Fail(e.Message, e);
return await ResponseErrorWithMessageAsync(req, context);
}
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);
}
}