semantic-kernel
semantic-kernel copied to clipboard
Planner - Allow manually specifying context variables
If you create and execute a plan as follows (essentially what is in 5-using-the-planner.ipynb):
SKContext plan = await _kernel.RunAsync(ask, _planner["CreatePlan"]);
SKContext executionResults = plan;
int step = 1;
int maxSteps = 5;
while (!executionResults.Variables.ToPlan().IsComplete && step < maxSteps)
{
executionResults.Variables.Set("foo", "bar");
SKContext results = await _kernel.RunAsync(executionResults.Variables, _planner["ExecutePlan"]);
...
executionResults = results;
step++;
}
And the function that gets executed as part of that plan looks like this:
[SKFunction("A description of Baz")]
[SKFunctionInput(Description = "Input to Baz")]
[SKFunctionContextParameter(Name = "foo", Description = "A description of what foo does")]
public async Task<string> Baz(string input, SKContext context)
The only way foo
is available in the passed context is if it was defined in the plan XML, something like
<function.Test.Baz foo="bar" />
The call executionResults.Variables.Set("foo", "bar");
does not result in foo
being available in Baz.
I believe this is because FunctionFlowRunner executes the skill using a new set of variables, built from the attributes (looking at this line: var result = await this._kernel.RunAsync(functionVariables, skillFunction);
and tracing back where functionVariables
is built)
Allowing a developer to specify variables to persist would be useful for cases where there is data the developer don't want to expose to injection risk. As an example, it could be a user session identifier.
Yes, you are right. Thank you for the detailed issue. We are in the process of changing this right now. Will update you as soon as we have improved this.
@lemillermicrosoft can you take a look at this and close this if it is complete
public static async Task RunAsync()
{
var kernel = new KernelBuilder()
.WithLogger(ConsoleLogger.Log)
.WithAzureTextCompletionService(
Env.Var("AZURE_OPENAI_DEPLOYMENT_NAME"),
Env.Var("AZURE_OPENAI_ENDPOINT"),
Env.Var("AZURE_OPENAI_KEY"))
.Build();
var skill = kernel.ImportSkill(new TestSkill());
var plan = new Plan("Foobar plan for #345");
plan.AddSteps(skill["Baz"], skill["Baz"]);
var context = kernel.CreateNewContext();
context.Variables.Update("My input");
context.Variables.Set("foo", "bar");
var result = await plan.InvokeAsync(context);
Console.WriteLine(result.Result);
}
This should work now. Please re-open if this doesn't meet expectations still. Thanks for filing the issue!
public static async Task RunAsync() { var kernel = new KernelBuilder() .WithLogger(ConsoleLogger.Log) .WithAzureTextCompletionService( Env.Var("AZURE_OPENAI_DEPLOYMENT_NAME"), Env.Var("AZURE_OPENAI_ENDPOINT"), Env.Var("AZURE_OPENAI_KEY")) .Build(); var skill = kernel.ImportSkill(new TestSkill()); var plan = new Plan("Foobar plan for #345"); plan.AddSteps(skill["Baz"], skill["Baz"]); var context = kernel.CreateNewContext(); context.Variables.Update("My input"); context.Variables.Set("foo", "bar"); var result = await plan.InvokeAsync(context); Console.WriteLine(result.Result); }
This code snippet should work now. Please reopen the issue if it doesn't meet your expectations. Thanks for filing the issue!
I have attempted to run this, but it seems like the context variable "foo"
set in the example is not persisted and is unavailable to Baz
when the plan is executed.
In my example below, user_id
is not available to ReplyTextMessage
.
var context = kernel.CreateNewContext();
context.Variables.Set("user_id", "abcd");
[SKFunction, Description("Reply to the user with a text message.")]
public async Task ReplyTextMessage(
[Description("Text message to reply")] string input,
[Description("User's Id")] string user_id)
{}
Not sure if I am missing something here.
Ok, seems like I have figured out my issue. The context
has to be passed to the plan's InvokeAsync method for the variables to be available and persisted in the skill functions.
var result = await plan.InvokeAsync(context);
However, I have noticed that the output generated by the planner is overwritten by the initial context's "input".
Any clarifications on this would be much appreciated.
This is the same issue I am finding with the ActionPlanner it runs the correct skfunction but in the function i set some extra context variables. But when it is executed with SKContext planContext = await plan.InvokeAsync(chatContext); the returned context variable are the same as the variables passed in plus the result INPUT. Currently I had to create a new plan and planner to test this out. I added just two lines of code and now any variables that are set in a function are returned and working for me as i want. I have attached my example with the two line changes line 275 & 362-3. Now not sure this has been fixed but using 0.17.230704.3-preview FunctionPlan.cs.txt