semantic-kernel icon indicating copy to clipboard operation
semantic-kernel copied to clipboard

Planner - Allow manually specifying context variables

Open pbollom opened this issue 1 year ago • 2 comments

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.

pbollom avatar Apr 06 '23 00:04 pbollom

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 avatar Apr 06 '23 03:04 lemillermicrosoft

@lemillermicrosoft can you take a look at this and close this if it is complete

evchaki avatar May 16 '23 20:05 evchaki

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!

lemillermicrosoft avatar Jun 12 '23 21:06 lemillermicrosoft

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.

isaackhazi avatar Jun 18 '23 12:06 isaackhazi

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.

isaackhazi avatar Jun 19 '23 08:06 isaackhazi

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

bakerd13 avatar Jul 08 '23 07:07 bakerd13