-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
.Net - Agents KernelFunction Strategies (#5895)
### Motivation and Context <!-- Thank you for your contribution to the semantic-kernel repo! Please help reviewers and future users, providing the following information: 1. Why is this change required? 2. What problem does it solve? 3. What scenario does it contribute to? 4. If it fixes an open issue, please link to the issue here. --> Users of `AgentGroupChat` will likely require strategies that rely on AI processing. Text processing alone is insufficient, but developers need other options than calling an AI model. With this update, our strategy story supports either. ### Description <!-- Describe your changes, the overall approach, the underlying design. These notes will help understanding how your code works. Thanks! --> Introducing support `SelectionStrategy` and `TerminationStrategy` that utilize LLM processing with support for result processing. - `KernelFunctionSelectionStrategy` - `KernelFunctionTerminationStrategy` ### Contribution Checklist <!-- Before submitting this PR, please make sure: --> - [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 - [x] I didn't break anyone 😄
- Loading branch information
Showing
13 changed files
with
603 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
127 changes: 127 additions & 0 deletions
127
dotnet/samples/Concepts/AgentSyntax/Getting_Started/Step4_KernelFunctionStrategies.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
using System; | ||
using System.Threading.Tasks; | ||
using Examples; | ||
using Microsoft.SemanticKernel; | ||
using Microsoft.SemanticKernel.Agents; | ||
using Microsoft.SemanticKernel.Agents.Chat; | ||
using Microsoft.SemanticKernel.ChatCompletion; | ||
using Xunit; | ||
using Xunit.Abstractions; | ||
|
||
namespace GettingStarted; | ||
|
||
/// <summary> | ||
/// Demonstrate usage of <see cref="KernelFunctionTerminationStrategy"/> and <see cref="KernelFunctionSelectionStrategy"/> | ||
/// to manage <see cref="AgentGroupChat"/> execution. | ||
/// </summary> | ||
public class Step4_KernelFunctionStrategies(ITestOutputHelper output) : BaseTest(output) | ||
{ | ||
private const string ReviewerName = "ArtDirector"; | ||
private const string ReviewerInstructions = | ||
""" | ||
You are an art director who has opinions about copywriting born of a love for David Ogilvy. | ||
The goal is to determine if the given copy is acceptable to print. | ||
If so, state that it is approved. | ||
If not, provide insight on how to refine suggested copy without examples. | ||
"""; | ||
|
||
private const string CopyWriterName = "Writer"; | ||
private const string CopyWriterInstructions = | ||
""" | ||
You are a copywriter with ten years of experience and are known for brevity and a dry humor. | ||
You're laser focused on the goal at hand. Don't waste time with chit chat. | ||
The goal is to refine and decide on the single best copy as an expert in the field. | ||
Consider suggestions when refining an idea. | ||
"""; | ||
|
||
[Fact] | ||
public async Task RunAsync() | ||
{ | ||
// Define the agents | ||
ChatCompletionAgent agentReviewer = | ||
new() | ||
{ | ||
Instructions = ReviewerInstructions, | ||
Name = ReviewerName, | ||
Kernel = this.CreateKernelWithChatCompletion(), | ||
}; | ||
|
||
ChatCompletionAgent agentWriter = | ||
new() | ||
{ | ||
Instructions = CopyWriterInstructions, | ||
Name = CopyWriterName, | ||
Kernel = this.CreateKernelWithChatCompletion(), | ||
}; | ||
|
||
KernelFunction terminationFunction = | ||
KernelFunctionFactory.CreateFromPrompt( | ||
""" | ||
Determine if the copy has been approved. If so, respond with a single word: yes | ||
History: | ||
{{$history}} | ||
"""); | ||
|
||
KernelFunction selectionFunction = | ||
KernelFunctionFactory.CreateFromPrompt( | ||
""" | ||
You are in a role playing game. | ||
Carefully read the conversation history and carry on the conversation by specifying only the name of player to take the next turn. | ||
The available names are: | ||
{{$agents}} | ||
History: | ||
{{$history}} | ||
"""); | ||
|
||
// Create a chat for agent interaction. | ||
AgentGroupChat chat = | ||
new(agentWriter, agentReviewer) | ||
{ | ||
ExecutionSettings = | ||
new() | ||
{ | ||
// Here KernelFunctionTerminationStrategy will terminate | ||
// when the art-director has given their approval. | ||
TerminationStrategy = | ||
new KernelFunctionTerminationStrategy(terminationFunction, CreateKernelWithChatCompletion()) | ||
{ | ||
// Only the art-director may approve. | ||
Agents = [agentReviewer], | ||
// Customer result parser to determine if the response is "yes" | ||
ResultParser = (result) => result.GetValue<string>()?.Contains("yes", StringComparison.OrdinalIgnoreCase) ?? false, | ||
// The prompt variable name for the history argument. | ||
HistoryVariableName = "history", | ||
// Limit total number of turns | ||
MaximumIterations = 10, | ||
}, | ||
// Here a KernelFunctionSelectionStrategy selects agents based on a prompt function. | ||
SelectionStrategy = | ||
new KernelFunctionSelectionStrategy(selectionFunction, CreateKernelWithChatCompletion()) | ||
{ | ||
// Returns the entire result value as a string. | ||
ResultParser = (result) => result.GetValue<string>() ?? string.Empty, | ||
// The prompt variable name for the agents argument. | ||
AgentsVariableName = "agents", | ||
// The prompt variable name for the history argument. | ||
HistoryVariableName = "history", | ||
}, | ||
} | ||
}; | ||
|
||
// Invoke chat and display messages. | ||
string input = "concept: maps made out of egg cartons."; | ||
chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, input)); | ||
this.WriteLine($"# {AuthorRole.User}: '{input}'"); | ||
|
||
await foreach (var content in chat.InvokeAsync()) | ||
{ | ||
this.WriteLine($"# {content.Role} - {content.AuthorName ?? "*"}: '{content.Content}'"); | ||
} | ||
|
||
this.WriteLine($"# IS COMPLETE: {chat.IsComplete}"); | ||
} | ||
} |
92 changes: 92 additions & 0 deletions
92
dotnet/samples/Concepts/AgentSyntax/Getting_Started/Step5_JsonResult.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
using System.Collections.Generic; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Examples; | ||
using Microsoft.SemanticKernel; | ||
using Microsoft.SemanticKernel.Agents; | ||
using Microsoft.SemanticKernel.Agents.Chat; | ||
using Microsoft.SemanticKernel.ChatCompletion; | ||
using Resources; | ||
using Xunit; | ||
using Xunit.Abstractions; | ||
|
||
namespace GettingStarted; | ||
|
||
/// <summary> | ||
/// Demonstrate parsing JSON response. | ||
/// </summary> | ||
public class Step5_JsonResult(ITestOutputHelper output) : BaseTest(output) | ||
{ | ||
private const string TutorName = "Tutor"; | ||
private const string TutorInstructions = | ||
""" | ||
Think step-by-step and rate the user input on creativity and expressivness from 1-100. | ||
Respond in JSON format with the following JSON schema: | ||
{ | ||
"score": "integer (1-100)", | ||
"notes": "the reason for your score" | ||
} | ||
"""; | ||
|
||
[Fact] | ||
public async Task RunAsync() | ||
{ | ||
// Define the agents | ||
ChatCompletionAgent agent = | ||
new() | ||
{ | ||
Instructions = TutorInstructions, | ||
Name = TutorName, | ||
Kernel = this.CreateKernelWithChatCompletion(), | ||
}; | ||
|
||
// Create a chat for agent interaction. | ||
AgentGroupChat chat = | ||
new() | ||
{ | ||
ExecutionSettings = | ||
new() | ||
{ | ||
// Here a TerminationStrategy subclass is used that will terminate when | ||
// the response includes a score that is greater than or equal to 70. | ||
TerminationStrategy = new ThresholdTerminationStrategy() | ||
} | ||
}; | ||
|
||
// Respond to user input | ||
await InvokeAgentAsync("The sunset is very colorful."); | ||
await InvokeAgentAsync("The sunset is setting over the mountains."); | ||
await InvokeAgentAsync("The sunset is setting over the mountains and filled the sky with a deep red flame, setting the clouds ablaze."); | ||
|
||
// Local function to invoke agent and display the conversation messages. | ||
async Task InvokeAgentAsync(string input) | ||
{ | ||
chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, input)); | ||
|
||
this.WriteLine($"# {AuthorRole.User}: '{input}'"); | ||
|
||
await foreach (var content in chat.InvokeAsync(agent)) | ||
{ | ||
this.WriteLine($"# {content.Role} - {content.AuthorName ?? "*"}: '{content.Content}'"); | ||
this.WriteLine($"# IS COMPLETE: {chat.IsComplete}"); | ||
} | ||
} | ||
} | ||
|
||
private record struct InputScore(int score, string notes); | ||
|
||
private sealed class ThresholdTerminationStrategy : TerminationStrategy | ||
{ | ||
protected override Task<bool> ShouldAgentTerminateAsync(Agent agent, IReadOnlyList<ChatMessageContent> history, CancellationToken cancellationToken) | ||
{ | ||
string lastMessageContent = history[history.Count - 1].Content ?? string.Empty; | ||
|
||
InputScore? result = JsonResultTranslator.Translate<InputScore>(lastMessageContent); | ||
|
||
return Task.FromResult((result?.score ?? 0) >= 70); | ||
} | ||
} | ||
} |
79 changes: 79 additions & 0 deletions
79
dotnet/samples/Concepts/AgentSyntax/RepoUtils/JsonResultTranslator.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
using System.Text.Json; | ||
using Microsoft.SemanticKernel; | ||
|
||
namespace Resources; | ||
/// <summary> | ||
/// Supports parsing json from a text block that may contain literals delimiters: | ||
/// <list type="table"> | ||
/// <item> | ||
/// <code> | ||
/// [json] | ||
/// </code> | ||
/// </item> | ||
/// <item> | ||
/// <code> | ||
/// ``` | ||
/// [json] | ||
/// ``` | ||
/// </code> | ||
/// </item> | ||
/// <item> | ||
/// <code> | ||
/// ```json | ||
/// [json] | ||
/// ``` | ||
/// </code> | ||
/// </item> | ||
/// </list> | ||
/// </summary> | ||
/// <remarks> | ||
/// Encountering json with this form of delimiters is not uncommon for agent scenarios. | ||
/// </remarks> | ||
public static class JsonResultTranslator | ||
{ | ||
private const string LiteralDelimiter = "```"; | ||
private const string JsonPrefix = "json"; | ||
|
||
/// <summary> | ||
/// Utility method for extracting a JSON result from an agent response. | ||
/// </summary> | ||
/// <param name="result">A text result</param> | ||
/// <typeparam name="TResult">The target type of the <see cref="FunctionResult"/>.</typeparam> | ||
/// <returns>The JSON translated to the requested type.</returns> | ||
public static TResult? Translate<TResult>(string result) | ||
{ | ||
string rawJson = ExtractJson(result); | ||
|
||
return JsonSerializer.Deserialize<TResult>(rawJson); | ||
} | ||
|
||
private static string ExtractJson(string result) | ||
{ | ||
// Search for initial literal delimiter: ``` | ||
int startIndex = result.IndexOf(LiteralDelimiter, System.StringComparison.Ordinal); | ||
if (startIndex < 0) | ||
{ | ||
// No initial delimiter, return entire expression. | ||
return result; | ||
} | ||
|
||
startIndex += LiteralDelimiter.Length; | ||
|
||
// Accommodate "json" prefix, if present. | ||
if (JsonPrefix.Equals(result.Substring(startIndex, JsonPrefix.Length), System.StringComparison.OrdinalIgnoreCase)) | ||
{ | ||
startIndex += JsonPrefix.Length; | ||
} | ||
|
||
// Locate final literal delimiter | ||
int endIndex = result.IndexOf(LiteralDelimiter, startIndex, System.StringComparison.OrdinalIgnoreCase); | ||
if (endIndex < 0) | ||
{ | ||
endIndex = result.Length; | ||
} | ||
|
||
// Extract JSON | ||
return result.Substring(startIndex, endIndex - startIndex); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.