Skip to content

Commit

Permalink
Merge branch 'main' into pinecone-get-batch-using-single-request
Browse files Browse the repository at this point in the history
  • Loading branch information
RogerBarreto authored Aug 21, 2024
2 parents 8a4f838 + 9264b3e commit 2712559
Show file tree
Hide file tree
Showing 26 changed files with 430 additions and 219 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ from either OpenAI or Azure OpenAI and to run one of the C#, Python, and Java co
### For Python:

1. Go to the Quick start page [here](https://learn.microsoft.com/en-us/semantic-kernel/get-started/quick-start-guide?pivots=programming-language-python) and follow the steps to dive in.
2. You'll need to ensure that you toggle to C# in the the Choose a programming language table at the top of the page.
2. You'll need to ensure that you toggle to Python in the the Choose a programming language table at the top of the page.
![pythonmap](https://learn.microsoft.com/en-us/semantic-kernel/media/pythonmap.png)

### For Java:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ State only the name of the participant to take the next turn.
- {{{CopyWriterName}}}

Always follow these rules when selecting the next participant:
- After user input, it is {{{CopyWriterName}}}'a turn.
- After {{{CopyWriterName}}} replies, it is {{{ReviewerName}}}'s turn.
- After {{{ReviewerName}}} provides feedback, it is {{{CopyWriterName}}}'s turn.

Expand Down Expand Up @@ -105,6 +104,8 @@ State only the name of the participant to take the next turn.
SelectionStrategy =
new KernelFunctionSelectionStrategy(selectionFunction, CreateKernelWithChatCompletion())
{
// Always start with the writer agent.
InitialAgent = agentWriter,
// Returns the entire result value as a string.
ResultParser = (result) => result.GetValue<string>() ?? CopyWriterName,
// The prompt variable name for the agents argument.
Expand Down
75 changes: 27 additions & 48 deletions dotnet/src/Agents/Core/AgentGroupChat.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.SemanticKernel.Agents.Chat;
using Microsoft.SemanticKernel.ChatCompletion;

namespace Microsoft.SemanticKernel.Agents;

Expand Down Expand Up @@ -53,7 +52,7 @@ public void AddAgent(Agent agent)
/// The interactions will proceed according to the <see cref="SelectionStrategy"/> and the <see cref="TerminationStrategy"/>
/// defined via <see cref="AgentGroupChat.ExecutionSettings"/>.
/// In the absence of an <see cref="AgentGroupChatSettings.SelectionStrategy"/>, this method will not invoke any agents.
/// Any agent may be explicitly selected by calling <see cref="AgentGroupChat.InvokeAsync(Agent, bool, CancellationToken)"/>.
/// Any agent may be explicitly selected by calling <see cref="AgentGroupChat.InvokeAsync(Agent, CancellationToken)"/>.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>Asynchronous enumeration of messages.</returns>
Expand All @@ -77,30 +76,11 @@ public override async IAsyncEnumerable<ChatMessageContent> InvokeAsync([Enumerat
for (int index = 0; index < this.ExecutionSettings.TerminationStrategy.MaximumIterations; index++)
{
// Identify next agent using strategy
this.Logger.LogAgentGroupChatSelectingAgent(nameof(InvokeAsync), this.ExecutionSettings.SelectionStrategy.GetType());

Agent agent;
try
{
agent = await this.ExecutionSettings.SelectionStrategy.NextAsync(this.Agents, this.History, cancellationToken).ConfigureAwait(false);
}
catch (Exception exception)
{
this.Logger.LogAgentGroupChatNoAgentSelected(nameof(InvokeAsync), exception);
throw;
}

this.Logger.LogAgentGroupChatSelectedAgent(nameof(InvokeAsync), agent.GetType(), agent.Id, this.ExecutionSettings.SelectionStrategy.GetType());
Agent agent = await this.SelectAgentAsync(cancellationToken).ConfigureAwait(false);

// Invoke agent and process messages along with termination
await foreach (var message in base.InvokeAgentAsync(agent, cancellationToken).ConfigureAwait(false))
await foreach (var message in this.InvokeAsync(agent, cancellationToken).ConfigureAwait(false))
{
if (message.Role == AuthorRole.Assistant)
{
var task = this.ExecutionSettings.TerminationStrategy.ShouldTerminateAsync(agent, this.History, cancellationToken);
this.IsComplete = await task.ConfigureAwait(false);
}

yield return message;
}

Expand All @@ -122,45 +102,23 @@ public override async IAsyncEnumerable<ChatMessageContent> InvokeAsync([Enumerat
/// <remark>
/// Specified agent joins the chat.
/// </remark>>
public IAsyncEnumerable<ChatMessageContent> InvokeAsync(
Agent agent,
CancellationToken cancellationToken = default) =>
this.InvokeAsync(agent, isJoining: true, cancellationToken);

/// <summary>
/// Process a single interaction between a given <see cref="KernelAgent"/> an a <see cref="AgentGroupChat"/> irregardless of
/// the <see cref="SelectionStrategy"/> defined via <see cref="AgentGroupChat.ExecutionSettings"/>. Likewise, this does
/// not regard <see cref="TerminationStrategy.MaximumIterations"/> as it only takes a single turn for the specified agent.
/// </summary>
/// <param name="agent">The agent actively interacting with the chat.</param>
/// <param name="isJoining">Optional flag to control if agent is joining the chat.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>Asynchronous enumeration of messages.</returns>
public async IAsyncEnumerable<ChatMessageContent> InvokeAsync(
Agent agent,
bool isJoining,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
this.EnsureStrategyLoggerAssignment();

this.Logger.LogAgentGroupChatInvokingAgent(nameof(InvokeAsync), agent.GetType(), agent.Id);

if (isJoining)
{
this.AddAgent(agent);
}
this.AddAgent(agent);

await foreach (var message in base.InvokeAgentAsync(agent, cancellationToken).ConfigureAwait(false))
{
if (message.Role == AuthorRole.Assistant)
{
var task = this.ExecutionSettings.TerminationStrategy.ShouldTerminateAsync(agent, this.History, cancellationToken);
this.IsComplete = await task.ConfigureAwait(false);
}

yield return message;
}

this.IsComplete = await this.ExecutionSettings.TerminationStrategy.ShouldTerminateAsync(agent, this.History, cancellationToken).ConfigureAwait(false);

this.Logger.LogAgentGroupChatYield(nameof(InvokeAsync), this.IsComplete);
}

Expand All @@ -187,4 +145,25 @@ private void EnsureStrategyLoggerAssignment()
this.ExecutionSettings.TerminationStrategy.Logger = this.LoggerFactory.CreateLogger(this.ExecutionSettings.TerminationStrategy.GetType());
}
}

private async Task<Agent> SelectAgentAsync(CancellationToken cancellationToken)
{
this.Logger.LogAgentGroupChatSelectingAgent(nameof(InvokeAsync), this.ExecutionSettings.SelectionStrategy.GetType());

Agent agent;

try
{
agent = await this.ExecutionSettings.SelectionStrategy.NextAsync(this.Agents, this.History, cancellationToken).ConfigureAwait(false);
}
catch (Exception exception)
{
this.Logger.LogAgentGroupChatNoAgentSelected(nameof(InvokeAsync), exception);
throw;
}

this.Logger.LogAgentGroupChatSelectedAgent(nameof(InvokeAsync), agent.GetType(), agent.Id, this.ExecutionSettings.SelectionStrategy.GetType());

return agent;
}
}
19 changes: 14 additions & 5 deletions dotnet/src/Agents/Core/Chat/KernelFunctionSelectionStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ public class KernelFunctionSelectionStrategy(KernelFunction function, Kernel ker
/// </summary>
public KernelFunction Function { get; } = function;

/// <summary>
/// When set, will use <see cref="SelectionStrategy.InitialAgent"/> in the event of a failure to select an agent.
/// </summary>
public bool UseInitialAgentAsFallback { get; init; }

/// <summary>
/// The <see cref="Microsoft.SemanticKernel.Kernel"/> used when invoking <see cref="KernelFunctionSelectionStrategy.Function"/>.
/// </summary>
Expand All @@ -59,7 +64,7 @@ public class KernelFunctionSelectionStrategy(KernelFunction function, Kernel ker
public Func<FunctionResult, string> ResultParser { get; init; } = (result) => result.GetValue<string>() ?? string.Empty;

/// <inheritdoc/>
public sealed override async Task<Agent> NextAsync(IReadOnlyList<Agent> agents, IReadOnlyList<ChatMessageContent> history, CancellationToken cancellationToken = default)
protected sealed override async Task<Agent> SelectAgentAsync(IReadOnlyList<Agent> agents, IReadOnlyList<ChatMessageContent> history, CancellationToken cancellationToken = default)
{
KernelArguments originalArguments = this.Arguments ?? [];
KernelArguments arguments =
Expand All @@ -76,13 +81,17 @@ public sealed override async Task<Agent> NextAsync(IReadOnlyList<Agent> agents,
this.Logger.LogKernelFunctionSelectionStrategyInvokedFunction(nameof(NextAsync), this.Function.PluginName, this.Function.Name, result.ValueType);

string? agentName = this.ResultParser.Invoke(result);
if (string.IsNullOrEmpty(agentName))
if (string.IsNullOrEmpty(agentName) && (!this.UseInitialAgentAsFallback || this.InitialAgent == null))
{
throw new KernelException("Agent Failure - Strategy unable to determine next agent.");
}

return
agents.FirstOrDefault(a => (a.Name ?? a.Id) == agentName) ??
throw new KernelException($"Agent Failure - Strategy unable to select next agent: {agentName}");
Agent? agent = agents.FirstOrDefault(a => (a.Name ?? a.Id) == agentName);
if (agent == null && this.UseInitialAgentAsFallback)
{
agent = this.InitialAgent;
}

return agent ?? throw new KernelException($"Agent Failure - Strategy unable to select next agent: {agentName}");
}
}
39 changes: 38 additions & 1 deletion dotnet/src/Agents/Core/Chat/SelectionStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,19 @@ namespace Microsoft.SemanticKernel.Agents.Chat;
/// </summary>
public abstract class SelectionStrategy
{
/// <summary>
/// Flag indicating if an agent has been selected (first time).
/// </summary>
protected bool HasSelected { get; private set; }

/// <summary>
/// An optional agent for initial selection.
/// </summary>
/// <remarks>
/// Useful to avoid latency in initial agent selection.
/// </remarks>
public Agent? InitialAgent { get; set; }

/// <summary>
/// The <see cref="ILogger"/> associated with the <see cref="SelectionStrategy"/>.
/// </summary>
Expand All @@ -24,5 +37,29 @@ public abstract class SelectionStrategy
/// <param name="history">The chat history.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>The agent who shall take the next turn.</returns>
public abstract Task<Agent> NextAsync(IReadOnlyList<Agent> agents, IReadOnlyList<ChatMessageContent> history, CancellationToken cancellationToken = default);
public async Task<Agent> NextAsync(IReadOnlyList<Agent> agents, IReadOnlyList<ChatMessageContent> history, CancellationToken cancellationToken = default)
{
if (agents.Count == 0 && this.InitialAgent == null)
{
throw new KernelException("Agent Failure - No agents present to select.");
}

Agent agent =
(!this.HasSelected && this.InitialAgent != null) ?
this.InitialAgent :
await this.SelectAgentAsync(agents, history, cancellationToken).ConfigureAwait(false);

this.HasSelected = true;

return agent;
}

/// <summary>
/// Determine which agent goes next.
/// </summary>
/// <param name="agents">The agents participating in chat.</param>
/// <param name="history">The chat history.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>The agent who shall take the next turn.</returns>
protected abstract Task<Agent> SelectAgentAsync(IReadOnlyList<Agent> agents, IReadOnlyList<ChatMessageContent> history, CancellationToken cancellationToken = default);
}
19 changes: 14 additions & 5 deletions dotnet/src/Agents/Core/Chat/SequentialSelectionStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,15 @@ public sealed class SequentialSelectionStrategy : SelectionStrategy
public void Reset() => this._index = 0;

/// <inheritdoc/>
public override Task<Agent> NextAsync(IReadOnlyList<Agent> agents, IReadOnlyList<ChatMessageContent> history, CancellationToken cancellationToken = default)
protected override Task<Agent> SelectAgentAsync(IReadOnlyList<Agent> agents, IReadOnlyList<ChatMessageContent> history, CancellationToken cancellationToken = default)
{
if (agents.Count == 0)
if (this.HasSelected &&
this.InitialAgent != null &&
agents.Count > 0 &&
agents[0] == this.InitialAgent)
{
throw new KernelException("Agent Failure - No agents present to select.");
// Avoid selecting first agent twice
IncrementIndex();
}

// Set of agents array may not align with previous execution, constrain index to valid range.
Expand All @@ -33,12 +37,17 @@ public override Task<Agent> NextAsync(IReadOnlyList<Agent> agents, IReadOnlyList
this._index = 0;
}

var agent = agents[this._index];
Agent agent = agents[this._index];

this.Logger.LogSequentialSelectionStrategySelectedAgent(nameof(NextAsync), this._index, agents.Count, agent.Id);

this._index = (this._index + 1) % agents.Count;
IncrementIndex();

return Task.FromResult(agent);

void IncrementIndex()
{
this._index = (this._index + 1) % agents.Count;
}
}
}
7 changes: 2 additions & 5 deletions dotnet/src/Agents/UnitTests/Core/AgentGroupChatTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,7 @@ public async Task VerifyGroupAgentChatAgentMembershipAsync()
chat.AddAgent(agent3);
Assert.Equal(3, chat.Agents.Count);

var messages = await chat.InvokeAsync(agent4, isJoining: false).ToArrayAsync();
Assert.Equal(3, chat.Agents.Count);

messages = await chat.InvokeAsync(agent4).ToArrayAsync();
ChatMessageContent[] messages = await chat.InvokeAsync(agent4).ToArrayAsync();
Assert.Equal(4, chat.Agents.Count);
}

Expand Down Expand Up @@ -204,7 +201,7 @@ protected override Task<bool> ShouldAgentTerminateAsync(Agent agent, IReadOnlyLi

private sealed class FailedSelectionStrategy : SelectionStrategy
{
public override Task<Agent> NextAsync(IReadOnlyList<Agent> agents, IReadOnlyList<ChatMessageContent> history, CancellationToken cancellationToken = default)
protected override Task<Agent> SelectAgentAsync(IReadOnlyList<Agent> agents, IReadOnlyList<ChatMessageContent> history, CancellationToken cancellationToken = default)
{
throw new InvalidOperationException();
}
Expand Down
Loading

0 comments on commit 2712559

Please sign in to comment.