Skip to content

Commit

Permalink
.Net: Obsolete SKContext[...] indexer (#2188)
Browse files Browse the repository at this point in the history
### Motivation and Context
The SKContext[...] indexer is simply shorthand for
SKContext.Variables[...]. With additional routes to doing the same
thing, there's more confusion about which to use when. In this case,
there's a great deal of confusion about the mutability of these
variables and whether they can/should be changed within functions or
between functions.

### Description
This PR starts to address the above issues by simplifying the SDK
surface. The first step is to remove the "extra" route to accessing the
same data, the indexer on SKContext.

**This is a breaking change.** The mitigation is simply to update all
uses of `context["MY_VALUE"]` to `context.Variables["MY_VALUE"]`

In the PRs that follow, the mutability issue will be addressed by making
it clear which variables are input, how and where to set output, and
which can/cannot be transformed by an SKFunction.

### Contribution Checklist
- [X] The code builds clean without any errors or warnings
- [X] The PR follows the [SK Contribution
Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md)
and the [pre-submission formatting
script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts)
raises no violations
- [X] All unit tests pass, and I have added new tests where possible
- [ ] I didn't break anyone 😄
   => This is a breaking change.
  • Loading branch information
shawncal committed Jul 26, 2023
1 parent 6cee23f commit 32447c0
Show file tree
Hide file tree
Showing 16 changed files with 213 additions and 208 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ [END OF EXAMPLES]
var oracle = kernel.CreateSemanticFunction(SemanticFunction, maxTokens: 200, temperature: 0, topP: 1);

var context = kernel.CreateNewContext();
context["externalInformation"] = "";
context.Variables["externalInformation"] = "";
var answer = await oracle.InvokeAsync(questions, context);

// If the answer contains commands, execute them using the prompt renderer.
Expand All @@ -155,7 +155,7 @@ [END OF EXAMPLES]
Console.WriteLine(information);

// The rendered prompt contains the information retrieved from search engines
context["externalInformation"] = information;
context.Variables["externalInformation"] = information;

// Run the semantic function again, now including information from Bing
answer = await oracle.InvokeAsync(questions, context);
Expand Down
18 changes: 9 additions & 9 deletions dotnet/samples/KernelSyntaxExamples/Example15_MemorySkill.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ public static async Task RunAsync()
var memorySaver = kernel.CreateSemanticFunction(SaveFunctionDefinition);

var context = kernel.CreateNewContext();
context[TextMemorySkill.CollectionParam] = MemoryCollectionName;
context[TextMemorySkill.KeyParam] = "info5";
context["info"] = "My family is from New York";
context.Variables[TextMemorySkill.CollectionParam] = MemoryCollectionName;
context.Variables[TextMemorySkill.KeyParam] = "info5";
context.Variables["info"] = "My family is from New York";
await memorySaver.InvokeAsync(context);

// ========= Test memory remember =========
Expand Down Expand Up @@ -96,8 +96,8 @@ Consider only the facts below when answering questions.
var aboutMeOracle = kernel.CreateSemanticFunction(RecallFunctionDefinition, maxTokens: 100);

context = kernel.CreateNewContext();
context[TextMemorySkill.CollectionParam] = MemoryCollectionName;
context[TextMemorySkill.RelevanceParam] = "0.8";
context.Variables[TextMemorySkill.CollectionParam] = MemoryCollectionName;
context.Variables[TextMemorySkill.RelevanceParam] = "0.8";
var result = await aboutMeOracle.InvokeAsync("Do I live in the same town where I grew up?", context);

Console.WriteLine("Do I live in the same town where I grew up?\n");
Expand All @@ -114,9 +114,9 @@ Do I live in the same town where I grew up?
// ========= Remove a memory =========
Console.WriteLine("========= Example: Forgetting a Memory =========");

context["fact1"] = "What is my name?";
context["fact2"] = "What do I do for a living?";
context[TextMemorySkill.RelevanceParam] = ".75";
context.Variables["fact1"] = "What is my name?";
context.Variables["fact2"] = "What do I do for a living?";
context.Variables[TextMemorySkill.RelevanceParam] = ".75";

result = await aboutMeOracle.InvokeAsync("Tell me a bit about myself", context);

Expand All @@ -130,7 +130,7 @@ Tell me a bit about myself
My name is Andrea and my family is from New York. I work as a tourist operator.
*/

context[TextMemorySkill.KeyParam] = "info1";
context.Variables[TextMemorySkill.KeyParam] = "info1";
await memorySkill.RemoveAsync(MemoryCollectionName, "info1", logger: context.Logger);

result = await aboutMeOracle.InvokeAsync("Tell me a bit about myself", context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,13 @@ public static async Task RunAsync()
var context = kernel.CreateNewContext();

// Put the selected document into the variable used by the system prompt (see 28-system-prompt.txt).
context["selectedText"] = selectedText;
context.Variables["selectedText"] = selectedText;

// Demo another variable, e.g. when the chat started, used by the system prompt (see 28-system-prompt.txt).
context["startTime"] = DateTimeOffset.Now.ToString("hh:mm:ss tt zz", CultureInfo.CurrentCulture);
context.Variables["startTime"] = DateTimeOffset.Now.ToString("hh:mm:ss tt zz", CultureInfo.CurrentCulture);

// This is the user message, store it in the variable used by 28-user-prompt.txt
context["userMessage"] = "extract locations as a bullet point list";
context.Variables["userMessage"] = "extract locations as a bullet point list";

// Instantiate the prompt renderer, which we will use to turn prompt templates
// into strings, that we will store into a Chat history object, which is then sent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ public async Task ItSupportsVariablesAsync()

var kernel = Kernel.Builder.Build();
var context = kernel.CreateNewContext();
context["input"] = Input;
context["winner"] = Winner;
context.Variables["input"] = Input;
context.Variables["winner"] = Winner;

// Act
var result = await this._target.RenderAsync(Template, context);
Expand Down Expand Up @@ -72,7 +72,7 @@ public async Task ItAllowsToPassVariablesToFunctionsAsync()
var kernel = Kernel.Builder.Build();
kernel.ImportSkill(new MySkill(), "my");
var context = kernel.CreateNewContext();
context["call"] = "123";
context.Variables["call"] = "123";

// Act
var result = await this._target.RenderAsync(Template, context);
Expand Down
117 changes: 61 additions & 56 deletions dotnet/src/SemanticKernel.Abstractions/Orchestration/SKContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,33 +29,12 @@ public sealed class SKContext
/// <returns>Processed input, aka result</returns>
public string Result => this.Variables.ToString();

/// <summary>
/// Whether an error occurred while executing functions in the pipeline.
/// </summary>
public bool ErrorOccurred { get; private set; }

/// <summary>
/// Error details.
/// </summary>
public string LastErrorDescription { get; private set; } = string.Empty;

/// <summary>
/// When an error occurs, this is the most recent exception.
/// </summary>
public Exception? LastException { get; private set; }

/// <summary>
/// When a prompt is processed, aka the current data after any model results processing occurred.
/// (One prompt can have multiple results).
/// </summary>
public IReadOnlyCollection<ModelResult> ModelResults { get; set; } = Array.Empty<ModelResult>();

/// <summary>
/// The token to monitor for cancellation requests.
/// </summary>
[Obsolete("Add a CancellationToken param to SKFunction method signatures instead of retrieving it from SKContext.")]
public CancellationToken CancellationToken { get; } = default;

/// <summary>
/// The culture currently associated with this context.
/// </summary>
Expand All @@ -65,46 +44,11 @@ public CultureInfo Culture
set => this._culture = value ?? CultureInfo.CurrentCulture;
}

/// <summary>
/// Shortcut into user data, access variables by name
/// </summary>
/// <param name="name">Variable name</param>
public string this[string name]
{
get => this.Variables[name];
set => this.Variables[name] = value;
}

/// <summary>
/// Call this method to signal when an error occurs.
/// In the usual scenarios this is also how execution is stopped, e.g. to inform the user or take necessary steps.
/// </summary>
/// <param name="errorDescription">Error description</param>
/// <param name="exception">If available, the exception occurred</param>
/// <returns>The current instance</returns>
public SKContext Fail(string errorDescription, Exception? exception = null)
{
this.ErrorOccurred = true;
this.LastErrorDescription = errorDescription;
this.LastException = exception;
return this;
}

/// <summary>
/// User variables
/// </summary>
public ContextVariables Variables { get; }

/// <summary>
/// Semantic memory
/// </summary>
[Obsolete("Memory no longer passed through SKContext. Instead, initialize your skill class with the memory provider it needs.")]
public ISemanticTextMemory Memory
{
get => throw new InvalidOperationException(
"Memory no longer passed through SKContext. Instead, initialize your skill class with the memory provider it needs.");
}

/// <summary>
/// Read only skills collection
/// </summary>
Expand Down Expand Up @@ -202,4 +146,65 @@ private string DebuggerDisplay
return display;
}
}

#region Error handling
/// <summary>
/// Whether an error occurred while executing functions in the pipeline.
/// </summary>
public bool ErrorOccurred { get; private set; }

/// <summary>
/// Error details.
/// </summary>
public string LastErrorDescription { get; private set; } = string.Empty;

/// <summary>
/// When an error occurs, this is the most recent exception.
/// </summary>
public Exception? LastException { get; private set; }

/// <summary>
/// Call this method to signal when an error occurs.
/// In the usual scenarios this is also how execution is stopped, e.g. to inform the user or take necessary steps.
/// </summary>
/// <param name="errorDescription">Error description</param>
/// <param name="exception">If available, the exception occurred</param>
/// <returns>The current instance</returns>
public SKContext Fail(string errorDescription, Exception? exception = null)
{
this.ErrorOccurred = true;
this.LastErrorDescription = errorDescription;
this.LastException = exception;
return this;
}
#endregion

#region Obsolete
/// <summary>
/// Shortcut into user data, access variables by name
/// </summary>
/// <param name="name">Variable name</param>
[Obsolete("Use SKContext.Variables instead. The SKContext[...] indexer will be removed in a future release.")]
public string this[string name]
{
get => this.Variables[name];
set => this.Variables[name] = value;
}

/// <summary>
/// The token to monitor for cancellation requests.
/// </summary>
[Obsolete("Add a CancellationToken param to SKFunction method signatures instead of retrieving it from SKContext.")]
public CancellationToken CancellationToken { get; } = default;

/// <summary>
/// Semantic memory
/// </summary>
[Obsolete("Memory no longer passed through SKContext. Instead, initialize your skill class with the memory provider it needs.")]
public ISemanticTextMemory Memory
{
get => throw new InvalidOperationException(
"Memory no longer passed through SKContext. Instead, initialize your skill class with the memory provider it needs.");
}
#endregion
}
16 changes: 8 additions & 8 deletions dotnet/src/SemanticKernel.UnitTests/KernelTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,12 @@ public async Task ItProvidesAccessToFunctionsViaSKContextAsync()
SKContext result = await kernel.RunAsync(skill["ReadSkillCollectionAsync"]);

// Assert - 3 functions, var name is not case sensitive
Assert.Equal("Nice fun", result["jk.joker"]);
Assert.Equal("Nice fun", result["JK.JOKER"]);
Assert.Equal("Just say hello", result["mySk.sayhello"]);
Assert.Equal("Just say hello", result["mySk.SayHello"]);
Assert.Equal("Export info.", result["mySk.ReadSkillCollectionAsync"]);
Assert.Equal("Export info.", result["mysk.readskillcollectionasync"]);
Assert.Equal("Nice fun", result.Variables["jk.joker"]);
Assert.Equal("Nice fun", result.Variables["JK.JOKER"]);
Assert.Equal("Just say hello", result.Variables["mySk.sayhello"]);
Assert.Equal("Just say hello", result.Variables["mySk.SayHello"]);
Assert.Equal("Export info.", result.Variables["mySk.ReadSkillCollectionAsync"]);
Assert.Equal("Export info.", result.Variables["mysk.readskillcollectionasync"]);
}

[Fact]
Expand Down Expand Up @@ -182,15 +182,15 @@ public async Task<SKContext> ReadSkillCollectionAsync(SKContext context)
{
foreach (FunctionView f in list.Value)
{
context[$"{list.Key}.{f.Name}"] = f.Description;
context.Variables[$"{list.Key}.{f.Name}"] = f.Description;
}
}

foreach (KeyValuePair<string, List<FunctionView>> list in procMem.NativeFunctions)
{
foreach (FunctionView f in list.Value)
{
context[$"{list.Key}.{f.Name}"] = f.Description;
context.Variables[$"{list.Key}.{f.Name}"] = f.Description;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,18 @@ public void ItHasHelpersForContextVariables()
variables.Set("foo1", "bar1");

// Act
target["foo2"] = "bar2";
target["INPUT"] = Guid.NewGuid().ToString("N");
target.Variables["foo2"] = "bar2";
target.Variables["INPUT"] = Guid.NewGuid().ToString("N");

// Assert
Assert.Equal("bar1", target["foo1"]);
Assert.Equal("bar1", target.Variables["foo1"]);
Assert.Equal("bar2", target["foo2"]);
Assert.Equal("bar1", target.Variables["foo1"]);
Assert.Equal("bar2", target.Variables["foo2"]);
Assert.Equal("bar2", target.Variables["foo2"]);
Assert.Equal(target["INPUT"], target.Result);
Assert.Equal(target["INPUT"], target.ToString());
Assert.Equal(target["INPUT"], target.Variables.Input);
Assert.Equal(target["INPUT"], target.Variables.ToString());
Assert.Equal(target.Variables["INPUT"], target.Result);
Assert.Equal(target.Variables["INPUT"], target.ToString());
Assert.Equal(target.Variables["INPUT"], target.Variables.Input);
Assert.Equal(target.Variables["INPUT"], target.Variables.ToString());
}

[Fact]
Expand Down
Loading

0 comments on commit 32447c0

Please sign in to comment.