Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Auto-detect text files, ensure they use LF.
* text=auto eol=lf working-tree-encoding=UTF-8

# Bash scripts
*.sh text eol=lf
*.cmd text eol=crlf
126 changes: 99 additions & 27 deletions dotnet/samples/GettingStarted/AgentSample.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// Copyright (c) Microsoft. All rights reserved.

using System.ClientModel;
using Azure.AI.Agents.Persistent;
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents;
using Microsoft.Extensions.AI;
using Microsoft.Shared.Samples;
using OpenAI;
Expand All @@ -20,45 +22,115 @@ public enum ChatClientProviders
OpenAI,
AzureOpenAI,
OpenAIResponses,
OpenAIResponses_InMemoryMessage,
OpenAIResponses_ConversationId
OpenAIResponses_InMemoryMessageThread,
OpenAIResponses_ConversationIdThread,
AzureAIAgentsPersistent
}

protected IChatClient GetChatClient(ChatClientProviders provider)
protected Task<IChatClient> GetChatClientAsync(ChatClientProviders provider, ChatClientAgentOptions options, CancellationToken cancellationToken = default)
=> provider switch
{
ChatClientProviders.OpenAI => GetOpenAIChatClient(),
ChatClientProviders.AzureOpenAI => GetAzureOpenAIChatClient(),
ChatClientProviders.OpenAI => GetOpenAIChatClientAsync(),
ChatClientProviders.AzureOpenAI => GetAzureOpenAIChatClientAsync(),
ChatClientProviders.AzureAIAgentsPersistent => GetAzureAIAgentPersistentClientAsync(options, cancellationToken),
ChatClientProviders.OpenAIResponses or
ChatClientProviders.OpenAIResponses_InMemoryMessage or
ChatClientProviders.OpenAIResponses_ConversationId
=> GetOpenAIResponsesClient(),
ChatClientProviders.OpenAIResponses_InMemoryMessageThread or
ChatClientProviders.OpenAIResponses_ConversationIdThread
=> GetOpenAIResponsesClientAsync(),
_ => throw new NotSupportedException($"Provider {provider} is not supported.")
};

protected ChatOptions? GetChatOptions(ChatClientProviders? provider)
=> provider switch
{
ChatClientProviders.OpenAIResponses_InMemoryMessage => new() { RawRepresentationFactory = static (_) => new ResponseCreationOptions() { StoredOutputEnabled = false } },
ChatClientProviders.OpenAIResponses_ConversationId => new() { RawRepresentationFactory = static (_) => new ResponseCreationOptions() { StoredOutputEnabled = true } },
ChatClientProviders.OpenAIResponses_InMemoryMessageThread => new() { RawRepresentationFactory = static (_) => new ResponseCreationOptions() { StoredOutputEnabled = false } },
ChatClientProviders.OpenAIResponses_ConversationIdThread => new() { RawRepresentationFactory = static (_) => new ResponseCreationOptions() { StoredOutputEnabled = true } },
_ => null
};

private IChatClient GetOpenAIChatClient()
=> new OpenAIClient(TestConfiguration.OpenAI.ApiKey)
.GetChatClient(TestConfiguration.OpenAI.ChatModelId)
.AsIChatClient();

private IChatClient GetAzureOpenAIChatClient()
=> ((TestConfiguration.AzureOpenAI.ApiKey is null)
// Use Azure CLI credentials if API key is not provided.
? new AzureOpenAIClient(TestConfiguration.AzureOpenAI.Endpoint, new AzureCliCredential())
: new AzureOpenAIClient(TestConfiguration.AzureOpenAI.Endpoint, new ApiKeyCredential(TestConfiguration.AzureOpenAI.ApiKey)))
.GetChatClient(TestConfiguration.AzureOpenAI.DeploymentName)
.AsIChatClient();

private IChatClient GetOpenAIResponsesClient()
=> new OpenAIClient(TestConfiguration.OpenAI.ApiKey)
.GetOpenAIResponseClient(TestConfiguration.OpenAI.ChatModelId)
.AsIChatClient();
/// <summary>
/// For providers that store the agent and the thread on the server side, this will clean and delete
/// any sample agent and thread that was created during this execution.
/// </summary>
/// <param name="provider">The chat client provider type that determines the cleanup process.</param>
/// <param name="agent">The agent instance to be cleaned up.</param>
/// <param name="thread">Optional thread associated with the agent that may also need to be cleaned up.</param>
/// <param name="cancellationToken">Cancellation token to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <remarks>
/// Ideally for faster execution and potential cost savings, server-side agents should be reused.
/// </remarks>
protected Task AgentCleanUpAsync(ChatClientProviders provider, ChatClientAgent agent, AgentThread? thread = null, CancellationToken cancellationToken = default)
{
return provider switch
{
ChatClientProviders.AzureAIAgentsPersistent => AzureAIAgentsPersistentAgentCleanUpAsync(agent, thread, cancellationToken),

// For other remaining provider sample types, no cleanup is needed as they don't offer a server-side agent/thread clean-up API.
_ => Task.CompletedTask
};
}

#region Private GetChatClient

private Task<IChatClient> GetOpenAIChatClientAsync()
=> Task.FromResult(
new OpenAIClient(TestConfiguration.OpenAI.ApiKey)
.GetChatClient(TestConfiguration.OpenAI.ChatModelId)
.AsIChatClient());

private Task<IChatClient> GetAzureOpenAIChatClientAsync()
=> Task.FromResult(
((TestConfiguration.AzureOpenAI.ApiKey is null)
// Use Azure CLI credentials if API key is not provided.
? new AzureOpenAIClient(TestConfiguration.AzureOpenAI.Endpoint, new AzureCliCredential())
: new AzureOpenAIClient(TestConfiguration.AzureOpenAI.Endpoint, new ApiKeyCredential(TestConfiguration.AzureOpenAI.ApiKey)))
.GetChatClient(TestConfiguration.AzureOpenAI.DeploymentName)
.AsIChatClient());

private Task<IChatClient> GetOpenAIResponsesClientAsync()
=> Task.FromResult(
new OpenAIClient(TestConfiguration.OpenAI.ApiKey)
.GetOpenAIResponseClient(TestConfiguration.OpenAI.ChatModelId)
.AsIChatClient());

private async Task<IChatClient> GetAzureAIAgentPersistentClientAsync(ChatClientAgentOptions options, CancellationToken cancellationToken)
{
// Get a client to create server side agents with.
var persistentAgentsClient = new PersistentAgentsClient(TestConfiguration.AzureAI.Endpoint, new AzureCliCredential());

// Create a server side agent to work with.
var persistentAgentResponse = await persistentAgentsClient.Administration.CreateAgentAsync(
model: TestConfiguration.AzureAI.DeploymentName,
name: options.Name,
instructions: options.Instructions,
cancellationToken: cancellationToken);

var persistentAgent = persistentAgentResponse.Value;

// Get the chat client to use for the agent.
return persistentAgentsClient.AsIChatClient(persistentAgent.Id);
}

#endregion

#region Private AgentCleanUp

private async Task AzureAIAgentsPersistentAgentCleanUpAsync(ChatClientAgent agent, AgentThread? thread, CancellationToken cancellationToken)
{
var persistentAgentsClient = agent.ChatClient.GetService<PersistentAgentsClient>();
if (persistentAgentsClient is null)
{
throw new InvalidOperationException("The provided chat client is not a Persistent Agents Chat Client");
}

await persistentAgentsClient.Administration.DeleteAgentAsync(agent.Id, cancellationToken);

// If a thread is provided, delete it as well.
if (thread is not null)
{
await persistentAgentsClient.Threads.DeleteThreadAsync(thread.Id, cancellationToken);
}
}

#endregion
}
2 changes: 1 addition & 1 deletion dotnet/samples/GettingStarted/GettingStarted.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,4 @@
<Using Include="GettingStarted" />
<Using Include="Microsoft.Shared.SampleUtilities" />
</ItemGroup>
</Project>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using Azure.Identity;
using Microsoft.Agents;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.AI.AzureAIAgentsPersistent;
using Microsoft.Shared.Samples;

namespace Providers;
Expand Down Expand Up @@ -37,7 +36,7 @@ public async Task RunWithAzureAIAgentsPersistent()
// Get the chat client to use for the agent.
using var chatClient = persistentAgentsClient.AsIChatClient(persistentAgent.Id);

// Define the agent
// Define the agent.
ChatClientAgent agent = new(chatClient);

// Start a new thread for the agent conversation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public async Task RunWithOpenAIAssistant()
// Get the chat client to use for the agent.
using var chatClient = assistantClient.AsIChatClient(assistantId);

// Define the agent
// Define the agent.
ChatClientAgent agent = new(chatClient);

// Start a new thread for the agent conversation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public async Task RunWithChatCompletion()
// Start a new thread for the agent conversation.
AgentThread thread = agent.GetNewThread();

// Respond to user input
// Respond to user input.
await RunAgentAsync("Tell me a joke about a pirate.");
await RunAgentAsync("Now add some emojis to the joke.");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public async Task RunWithChatCompletion(bool useConversationIdThread)
// Start a new thread for the agent conversation based on the type.
AgentThread thread = agent.GetNewThread();

// Respond to user input
// Respond to user input.
await RunAgentAsync("Tell me a joke about a pirate.");
await RunAgentAsync("Now add some emojis to the joke.");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,11 @@ namespace Steps;
/// <remarks>This class contains examples of using <see cref="ChatClientAgent"/> to showcase scenarios with and without conversation history.
/// Each test method demonstrates how to configure and interact with the agents, including handling user input and displaying responses.
/// </remarks>
public sealed class Step01_Running(ITestOutputHelper output) : AgentSample(output)
public sealed class Step01_ChatClientAgent_Running(ITestOutputHelper output) : AgentSample(output)
{
private const string ParrotName = "Parrot";
private const string ParrotInstructions = "Repeat the user message in the voice of a pirate and then end with a parrot sound.";

private const string JokerName = "Joker";
private const string JokerInstructions = "You are good at telling jokes.";

/// <summary>
/// Demonstrate the usage of <see cref="ChatClientAgent"/> where each invocation is
/// a unique interaction with no conversation history between them.
Expand All @@ -26,18 +23,21 @@ public sealed class Step01_Running(ITestOutputHelper output) : AgentSample(outpu
[InlineData(ChatClientProviders.OpenAI)]
[InlineData(ChatClientProviders.AzureOpenAI)]
[InlineData(ChatClientProviders.OpenAIResponses)]
[InlineData(ChatClientProviders.AzureAIAgentsPersistent)]
public async Task RunWithoutThread(ChatClientProviders provider)
{
// Define the options for the chat client agent.
var agentOptions = new ChatClientAgentOptions
{
Name = ParrotName,
Instructions = ParrotInstructions,
};

// Get the chat client to use for the agent.
using var chatClient = base.GetChatClient(provider);
using var chatClient = await base.GetChatClientAsync(provider, agentOptions);

// Define the agent
ChatClientAgent agent =
new(chatClient, new()
{
Name = ParrotName,
Instructions = ParrotInstructions,
});
var agent = new ChatClientAgent(chatClient, agentOptions);

// Respond to user input
await RunAgentAsync("Fortune favors the bold.");
Expand All @@ -52,6 +52,9 @@ async Task RunAgentAsync(string input)
var response = await agent.RunAsync(input);
this.WriteResponseOutput(response);
}

// Clean up the agent after use when applicable.
await base.AgentCleanUpAsync(provider, agent);
}

/// <summary>
Expand All @@ -60,24 +63,26 @@ async Task RunAgentAsync(string input)
[Theory]
[InlineData(ChatClientProviders.OpenAI)]
[InlineData(ChatClientProviders.AzureOpenAI)]
[InlineData(ChatClientProviders.OpenAIResponses_InMemoryMessage)]
[InlineData(ChatClientProviders.OpenAIResponses_ConversationId)]
[InlineData(ChatClientProviders.AzureAIAgentsPersistent)]
[InlineData(ChatClientProviders.OpenAIResponses_InMemoryMessageThread)]
[InlineData(ChatClientProviders.OpenAIResponses_ConversationIdThread)]
public async Task RunWithThread(ChatClientProviders provider)
{
// Get the chat client to use for the agent.
using var chatClient = base.GetChatClient(provider);
// Define the options for the chat client agent.
var agentOptions = new ChatClientAgentOptions
{
Name = ParrotName,
Instructions = ParrotInstructions,

// Get chat options based on the store type, if needed.
var chatOptions = base.GetChatOptions(provider);
// Get chat options based on the store type, if needed.
ChatOptions = base.GetChatOptions(provider),
};

// Get the chat client to use for the agent.
using var chatClient = await base.GetChatClientAsync(provider, agentOptions);

// Define the agent
ChatClientAgent agent =
new(chatClient, new()
{
Name = JokerName,
Instructions = JokerInstructions,
ChatOptions = chatOptions,
});
var agent = new ChatClientAgent(chatClient, agentOptions);

// Start a new thread for the agent conversation.
AgentThread thread = agent.GetNewThread();
Expand All @@ -95,6 +100,9 @@ async Task RunAgentAsync(string input)

this.WriteResponseOutput(response);
}

// Clean up the agent and thread after use when applicable.
await base.AgentCleanUpAsync(provider, agent, thread);
}

/// <summary>
Expand All @@ -104,24 +112,26 @@ async Task RunAgentAsync(string input)
[Theory]
[InlineData(ChatClientProviders.OpenAI)]
[InlineData(ChatClientProviders.AzureOpenAI)]
[InlineData(ChatClientProviders.OpenAIResponses_InMemoryMessage)]
[InlineData(ChatClientProviders.OpenAIResponses_ConversationId)]
[InlineData(ChatClientProviders.AzureAIAgentsPersistent)]
[InlineData(ChatClientProviders.OpenAIResponses_InMemoryMessageThread)]
[InlineData(ChatClientProviders.OpenAIResponses_ConversationIdThread)]
public async Task RunStreamingWithThread(ChatClientProviders provider)
{
// Get the chat client to use for the agent.
using var chatClient = base.GetChatClient(provider);
// Define the options for the chat client agent.
var agentOptions = new ChatClientAgentOptions
{
Name = ParrotName,
Instructions = ParrotInstructions,

// Get chat options based on the store type, if needed.
var chatOptions = base.GetChatOptions(provider);
// Get chat options based on the store type, if needed.
ChatOptions = base.GetChatOptions(provider),
};

// Get the chat client to use for the agent.
using var chatClient = await base.GetChatClientAsync(provider, agentOptions);

// Define the agent
ChatClientAgent agent =
new(chatClient, new()
{
Name = ParrotName,
Instructions = ParrotInstructions,
ChatOptions = chatOptions,
});
var agent = new ChatClientAgent(chatClient, agentOptions);

// Start a new thread for the agent conversation.
AgentThread thread = agent.GetNewThread();
Expand All @@ -140,5 +150,8 @@ async Task RunAgentAsync(string input)
this.WriteAgentOutput(update);
}
}

// Clean up the agent and thread after use when applicable.
await base.AgentCleanUpAsync(provider, agent, thread);
}
}
Loading
Loading