mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Compare commits
11
Commits
@@ -1,15 +1,11 @@
|
||||
{
|
||||
"name": "C# (.NET)",
|
||||
//"image": "mcr.microsoft.com/devcontainers/dotnet",
|
||||
// Workaround for https://github.com/devcontainers/images/issues/1752
|
||||
"build": {
|
||||
"dockerfile": "dotnet.Dockerfile"
|
||||
},
|
||||
"image": "mcr.microsoft.com/devcontainers/dotnet:10.0",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/dotnet:2.4.2": {},
|
||||
"ghcr.io/devcontainers/features/dotnet:2.4.0": {},
|
||||
"ghcr.io/devcontainers/features/powershell:1.5.1": {},
|
||||
"ghcr.io/devcontainers/features/azure-cli:1.2.9": {},
|
||||
"ghcr.io/devcontainers/features/docker-in-docker:2.14.0": {}
|
||||
"ghcr.io/devcontainers/features/azure-cli:1.2.8": {},
|
||||
"ghcr.io/devcontainers/features/docker-in-docker:2.12.4": {}
|
||||
},
|
||||
"workspaceFolder": "/workspaces/agent-framework/dotnet/",
|
||||
"customizations": {
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
FROM mcr.microsoft.com/devcontainers/dotnet
|
||||
|
||||
# Remove Yarn repository with expired GPG key to prevent apt-get update failures
|
||||
# Tracking issue: https://github.com/devcontainers/images/issues/1752
|
||||
RUN rm -f /etc/apt/sources.list.d/yarn.list
|
||||
@@ -83,7 +83,7 @@ jobs:
|
||||
dotnet
|
||||
python
|
||||
workflow-samples
|
||||
|
||||
|
||||
# Start Cosmos DB Emulator for all integration tests and only for unit tests when CosmosDB changes happened)
|
||||
- name: Start Azure Cosmos DB Emulator
|
||||
if: ${{ runner.os == 'Windows' && (needs.paths-filter.outputs.cosmosDbChanges == 'true' || (github.event_name != 'pull_request' && matrix.integration-tests)) }}
|
||||
@@ -139,7 +139,7 @@ jobs:
|
||||
popd
|
||||
popd
|
||||
rm -rf "$TEMP_DIR"
|
||||
|
||||
|
||||
- name: Run Unit Tests
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -147,7 +147,7 @@ jobs:
|
||||
for project in $UT_PROJECTS; do
|
||||
# Query the project's target frameworks using MSBuild with the current configuration
|
||||
target_frameworks=$(dotnet msbuild $project -getProperty:TargetFrameworks -p:Configuration=${{ matrix.configuration }} -nologo 2>/dev/null | tr -d '\r')
|
||||
|
||||
|
||||
# Check if the project supports the target framework
|
||||
if [[ "$target_frameworks" == *"${{ matrix.targetFramework }}"* ]]; then
|
||||
if [[ "${{ matrix.targetFramework }}" == "${{ env.COVERAGE_FRAMEWORK }}" ]]; then
|
||||
@@ -165,8 +165,8 @@ jobs:
|
||||
COSMOSDB_KEY: C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==
|
||||
|
||||
- name: Log event name and matrix integration-tests
|
||||
shell: bash
|
||||
run: echo "github.event_name:${{ github.event_name }} matrix.integration-tests:${{ matrix.integration-tests }} github.event.action:${{ github.event.action }} github.event.pull_request.merged:${{ github.event.pull_request.merged }}"
|
||||
shell: bash
|
||||
run: echo "github.event_name:${{ github.event_name }} matrix.integration-tests:${{ matrix.integration-tests }} github.event.action:${{ github.event.action }} github.event.pull_request.merged:${{ github.event.pull_request.merged }}"
|
||||
|
||||
- name: Azure CLI Login
|
||||
if: github.event_name != 'pull_request' && matrix.integration-tests
|
||||
@@ -192,10 +192,10 @@ jobs:
|
||||
for project in $INTEGRATION_TEST_PROJECTS; do
|
||||
# Query the project's target frameworks using MSBuild with the current configuration
|
||||
target_frameworks=$(dotnet msbuild $project -getProperty:TargetFrameworks -p:Configuration=${{ matrix.configuration }} -nologo 2>/dev/null | tr -d '\r')
|
||||
|
||||
|
||||
# Check if the project supports the target framework
|
||||
if [[ "$target_frameworks" == *"${{ matrix.targetFramework }}"* ]]; then
|
||||
dotnet test -f ${{ matrix.targetFramework }} -c ${{ matrix.configuration }} $project --no-build -v Normal --logger trx --filter "Category!=IntegrationDisabled"
|
||||
dotnet test -f ${{ matrix.targetFramework }} -c ${{ matrix.configuration }} $project --no-build -v Normal --logger trx
|
||||
else
|
||||
echo "Skipping $project - does not support target framework ${{ matrix.targetFramework }} (supports: $target_frameworks)"
|
||||
fi
|
||||
|
||||
@@ -89,7 +89,6 @@
|
||||
<PackageVersion Include="Microsoft.SemanticKernel.Agents.AzureAI" Version="1.67.0-preview" />
|
||||
<PackageVersion Include="Microsoft.SemanticKernel.Plugins.OpenApi" Version="1.67.0" />
|
||||
<!-- Agent SDKs -->
|
||||
<PackageVersion Include="GitHub.Copilot.SDK" Version="0.1.18" />
|
||||
<PackageVersion Include="Microsoft.Agents.CopilotStudio.Client" Version="1.3.171-beta" />
|
||||
<!-- M365 Agents SDK -->
|
||||
<PackageVersion Include="AdaptiveCards" Version="3.1.0" />
|
||||
|
||||
@@ -65,7 +65,6 @@
|
||||
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureOpenAIChatCompletion/Agent_With_AzureOpenAIChatCompletion.csproj" />
|
||||
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureOpenAIResponses/Agent_With_AzureOpenAIResponses.csproj" />
|
||||
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/Agent_With_CustomImplementation.csproj" />
|
||||
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_GithubCopilot/Agent_With_GithubCopilot.csproj" />
|
||||
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_GoogleGemini/Agent_With_GoogleGemini.csproj" />
|
||||
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_Ollama/Agent_With_Ollama.csproj" />
|
||||
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_ONNX/Agent_With_ONNX.csproj" />
|
||||
@@ -230,7 +229,6 @@
|
||||
<Folder Name="/Samples/GettingStarted/Workflows/Agents/">
|
||||
<Project Path="samples/GettingStarted/Workflows/Agents/CustomAgentExecutors/CustomAgentExecutors.csproj" />
|
||||
<Project Path="samples/GettingStarted/Workflows/Agents/FoundryAgent/FoundryAgent.csproj" />
|
||||
<Project Path="samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/GroupChatToolApproval.csproj" />
|
||||
<Project Path="samples/GettingStarted/Workflows/Agents/WorkflowAsAnAgent/WorkflowAsAnAgent.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/GettingStarted/Workflows/Checkpoint/">
|
||||
@@ -397,7 +395,6 @@
|
||||
<Project Path="src/Microsoft.Agents.AI.Abstractions/Microsoft.Agents.AI.Abstractions.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.AGUI/Microsoft.Agents.AI.AGUI.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Anthropic/Microsoft.Agents.AI.Anthropic.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.GithubCopilot/Microsoft.Agents.AI.GithubCopilot.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.AzureAI.Persistent/Microsoft.Agents.AI.AzureAI.Persistent.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.AzureAI/Microsoft.Agents.AI.AzureAI.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.CopilotStudio/Microsoft.Agents.AI.CopilotStudio.csproj" />
|
||||
@@ -427,7 +424,6 @@
|
||||
<Project Path="tests/AzureAI.IntegrationTests/AzureAI.IntegrationTests.csproj" />
|
||||
<Project Path="tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistent.IntegrationTests.csproj" />
|
||||
<Project Path="tests/CopilotStudio.IntegrationTests/CopilotStudio.IntegrationTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.GithubCopilot.IntegrationTests/Microsoft.Agents.AI.GithubCopilot.IntegrationTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.DurableTask.IntegrationTests/Microsoft.Agents.AI.DurableTask.IntegrationTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests.csproj" />
|
||||
@@ -442,7 +438,6 @@
|
||||
<Project Path="tests/Microsoft.Agents.AI.Abstractions.UnitTests/Microsoft.Agents.AI.Abstractions.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.AGUI.UnitTests/Microsoft.Agents.AI.AGUI.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.Anthropic.UnitTests/Microsoft.Agents.AI.Anthropic.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.GithubCopilot.UnitTests/Microsoft.Agents.AI.GithubCopilot.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.AzureAI.Persistent.UnitTests/Microsoft.Agents.AI.AzureAI.Persistent.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.AzureAI.UnitTests/Microsoft.Agents.AI.AzureAI.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.CosmosNoSql.UnitTests/Microsoft.Agents.AI.CosmosNoSql.UnitTests.csproj" />
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
"src\\Microsoft.Agents.AI.Abstractions\\Microsoft.Agents.AI.Abstractions.csproj",
|
||||
"src\\Microsoft.Agents.AI.AGUI\\Microsoft.Agents.AI.AGUI.csproj",
|
||||
"src\\Microsoft.Agents.AI.Anthropic\\Microsoft.Agents.AI.Anthropic.csproj",
|
||||
"src\\Microsoft.Agents.AI.GithubCopilot\\Microsoft.Agents.AI.GithubCopilot.csproj",
|
||||
"src\\Microsoft.Agents.AI.AzureAI.Persistent\\Microsoft.Agents.AI.AzureAI.Persistent.csproj",
|
||||
"src\\Microsoft.Agents.AI.AzureAI\\Microsoft.Agents.AI.AzureAI.csproj",
|
||||
"src\\Microsoft.Agents.AI.CopilotStudio\\Microsoft.Agents.AI.CopilotStudio.csproj",
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
<PropertyGroup>
|
||||
<!-- Central version prefix - applies to all nuget packages. -->
|
||||
<VersionPrefix>1.0.0</VersionPrefix>
|
||||
<PackageVersion Condition="'$(VersionSuffix)' != ''">$(VersionPrefix)-$(VersionSuffix).260127.1</PackageVersion>
|
||||
<PackageVersion Condition="'$(VersionSuffix)' == ''">$(VersionPrefix)-preview.260127.1</PackageVersion>
|
||||
<GitTag>1.0.0-preview.260127.1</GitTag>
|
||||
<PackageVersion Condition="'$(VersionSuffix)' != ''">$(VersionPrefix)-$(VersionSuffix).260121.1</PackageVersion>
|
||||
<PackageVersion Condition="'$(VersionSuffix)' == ''">$(VersionPrefix)-preview.260121.1</PackageVersion>
|
||||
<GitTag>1.0.0-preview.260121.1</GitTag>
|
||||
|
||||
<Configurations>Debug;Release;Publish</Configurations>
|
||||
<IsPackable>true</IsPackable>
|
||||
|
||||
@@ -42,7 +42,7 @@ public static class Program
|
||||
// Create the Host agent
|
||||
var hostAgent = new HostClientAgent(loggerFactory);
|
||||
await hostAgent.InitializeAgentAsync(modelId, apiKey, agentUrls!.Split(";"));
|
||||
AgentSession session = await hostAgent.Agent!.GetNewSessionAsync(cancellationToken);
|
||||
AgentThread thread = await hostAgent.Agent!.GetNewThreadAsync(cancellationToken);
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
@@ -61,7 +61,7 @@ public static class Program
|
||||
break;
|
||||
}
|
||||
|
||||
var agentResponse = await hostAgent.Agent!.RunAsync(message, session, cancellationToken: cancellationToken);
|
||||
var agentResponse = await hostAgent.Agent!.RunAsync(message, thread, cancellationToken: cancellationToken);
|
||||
foreach (var chatMessage in agentResponse.Messages)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Cyan;
|
||||
|
||||
@@ -88,7 +88,7 @@ public static class Program
|
||||
description: "AG-UI Client Agent",
|
||||
tools: [changeBackground, readClientClimateSensors]);
|
||||
|
||||
AgentSession session = await agent.GetNewSessionAsync(cancellationToken);
|
||||
AgentThread thread = await agent.GetNewThreadAsync(cancellationToken);
|
||||
List<ChatMessage> messages = [new(ChatRole.System, "You are a helpful assistant.")];
|
||||
try
|
||||
{
|
||||
@@ -112,23 +112,23 @@ public static class Program
|
||||
|
||||
// Call RunStreamingAsync to get streaming updates
|
||||
bool isFirstUpdate = true;
|
||||
string? sessionId = null;
|
||||
string? threadId = null;
|
||||
var updates = new List<ChatResponseUpdate>();
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(messages, session, cancellationToken: cancellationToken))
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(messages, thread, cancellationToken: cancellationToken))
|
||||
{
|
||||
// Use AsChatResponseUpdate to access ChatResponseUpdate properties
|
||||
ChatResponseUpdate chatUpdate = update.AsChatResponseUpdate();
|
||||
updates.Add(chatUpdate);
|
||||
if (chatUpdate.ConversationId != null)
|
||||
{
|
||||
sessionId = chatUpdate.ConversationId;
|
||||
threadId = chatUpdate.ConversationId;
|
||||
}
|
||||
|
||||
// Display run started information from the first update
|
||||
if (isFirstUpdate && sessionId != null && update.ResponseId != null)
|
||||
if (isFirstUpdate && threadId != null && update.ResponseId != null)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine($"\n[Run Started - Session: {sessionId}, Run: {update.ResponseId}]");
|
||||
Console.WriteLine($"\n[Run Started - Thread: {threadId}, Run: {update.ResponseId}]");
|
||||
Console.ResetColor();
|
||||
isFirstUpdate = false;
|
||||
}
|
||||
@@ -177,7 +177,7 @@ public static class Program
|
||||
var lastUpdate = updates[^1];
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($"[Run Ended - Session: {sessionId}, Run: {lastUpdate.ResponseId}]");
|
||||
Console.WriteLine($"[Run Ended - Thread: {threadId}, Run: {lastUpdate.ResponseId}]");
|
||||
Console.ResetColor();
|
||||
}
|
||||
messages.Clear();
|
||||
|
||||
@@ -19,21 +19,21 @@ internal sealed class AgenticUIAgent : DelegatingAIAgent
|
||||
this._jsonSerializerOptions = jsonSerializerOptions;
|
||||
}
|
||||
|
||||
protected override Task<AgentResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
||||
protected override Task<AgentResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.RunCoreStreamingAsync(messages, session, options, cancellationToken).ToAgentResponseAsync(cancellationToken);
|
||||
return this.RunCoreStreamingAsync(messages, thread, options, cancellationToken).ToAgentResponseAsync(cancellationToken);
|
||||
}
|
||||
|
||||
protected override async IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentSession? session = null,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Track function calls that should trigger state events
|
||||
var trackedFunctionCalls = new Dictionary<string, FunctionCallContent>();
|
||||
|
||||
await foreach (var update in this.InnerAgent.RunStreamingAsync(messages, session, options, cancellationToken).ConfigureAwait(false))
|
||||
await foreach (var update in this.InnerAgent.RunStreamingAsync(messages, thread, options, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
// Process contents: track function calls and emit state events for results
|
||||
List<AIContent> stateEventsToEmit = new();
|
||||
|
||||
+4
-4
@@ -20,21 +20,21 @@ internal sealed class PredictiveStateUpdatesAgent : DelegatingAIAgent
|
||||
this._jsonSerializerOptions = jsonSerializerOptions;
|
||||
}
|
||||
|
||||
protected override Task<AgentResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
||||
protected override Task<AgentResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.RunCoreStreamingAsync(messages, session, options, cancellationToken).ToAgentResponseAsync(cancellationToken);
|
||||
return this.RunCoreStreamingAsync(messages, thread, options, cancellationToken).ToAgentResponseAsync(cancellationToken);
|
||||
}
|
||||
|
||||
protected override async IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentSession? session = null,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Track the last emitted document state to avoid duplicates
|
||||
string? lastEmittedDocument = null;
|
||||
|
||||
await foreach (var update in this.InnerAgent.RunStreamingAsync(messages, session, options, cancellationToken).ConfigureAwait(false))
|
||||
await foreach (var update in this.InnerAgent.RunStreamingAsync(messages, thread, options, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
// Check if we're seeing a write_document tool call and emit predictive state
|
||||
bool hasToolCall = false;
|
||||
|
||||
@@ -19,21 +19,21 @@ internal sealed class SharedStateAgent : DelegatingAIAgent
|
||||
this._jsonSerializerOptions = jsonSerializerOptions;
|
||||
}
|
||||
|
||||
protected override Task<AgentResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
||||
protected override Task<AgentResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.RunCoreStreamingAsync(messages, session, options, cancellationToken).ToAgentResponseAsync(cancellationToken);
|
||||
return this.RunCoreStreamingAsync(messages, thread, options, cancellationToken).ToAgentResponseAsync(cancellationToken);
|
||||
}
|
||||
|
||||
protected override async IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentSession? session = null,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (options is not ChatClientAgentRunOptions { ChatOptions.AdditionalProperties: { } properties } chatRunOptions ||
|
||||
!properties.TryGetValue("ag_ui_state", out JsonElement state))
|
||||
{
|
||||
await foreach (var update in this.InnerAgent.RunStreamingAsync(messages, session, options, cancellationToken).ConfigureAwait(false))
|
||||
await foreach (var update in this.InnerAgent.RunStreamingAsync(messages, thread, options, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
yield return update;
|
||||
}
|
||||
@@ -64,7 +64,7 @@ internal sealed class SharedStateAgent : DelegatingAIAgent
|
||||
var firstRunMessages = messages.Append(stateUpdateMessage);
|
||||
|
||||
var allUpdates = new List<AgentResponseUpdate>();
|
||||
await foreach (var update in this.InnerAgent.RunStreamingAsync(firstRunMessages, session, firstRunOptions, cancellationToken).ConfigureAwait(false))
|
||||
await foreach (var update in this.InnerAgent.RunStreamingAsync(firstRunMessages, thread, firstRunOptions, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
allUpdates.Add(update);
|
||||
|
||||
@@ -98,7 +98,7 @@ internal sealed class SharedStateAgent : DelegatingAIAgent
|
||||
ChatRole.System,
|
||||
[new TextContent("Please provide a concise summary of the state changes in at most two sentences.")]));
|
||||
|
||||
await foreach (var update in this.InnerAgent.RunStreamingAsync(secondRunMessages, session, options, cancellationToken).ConfigureAwait(false))
|
||||
await foreach (var update in this.InnerAgent.RunStreamingAsync(secondRunMessages, thread, options, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
yield return update;
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ var pirateAgentBuilder = builder.AddAIAgent(
|
||||
chatClientServiceKey: "chat-model")
|
||||
.WithAITool(new CustomAITool())
|
||||
.WithAITool(new CustomFunctionTool())
|
||||
.WithInMemorySessionStore();
|
||||
.WithInMemoryThreadStore();
|
||||
|
||||
var knightsKnavesAgentBuilder = builder.AddAIAgent("knights-and-knaves", (sp, key) =>
|
||||
{
|
||||
|
||||
@@ -28,13 +28,13 @@ internal sealed class A2AAgentClient : AgentClientBase
|
||||
public override async IAsyncEnumerable<AgentResponseUpdate> RunStreamingAsync(
|
||||
string agentName,
|
||||
IList<ChatMessage> messages,
|
||||
string? sessionId = null,
|
||||
string? threadId = null,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
this._logger.LogInformation("Running agent {AgentName} with {MessageCount} messages via A2A", agentName, messages.Count);
|
||||
|
||||
var (a2aClient, _) = this.ResolveClient(agentName);
|
||||
var contextId = sessionId ?? Guid.NewGuid().ToString("N");
|
||||
var contextId = threadId ?? Guid.NewGuid().ToString("N");
|
||||
|
||||
// Convert and send messages via A2A without try-catch in yield method
|
||||
var results = new List<AgentResponseUpdate>();
|
||||
|
||||
@@ -16,13 +16,13 @@ internal abstract class AgentClientBase
|
||||
/// </summary>
|
||||
/// <param name="agentName">The name of the agent to run.</param>
|
||||
/// <param name="messages">The messages to send to the agent.</param>
|
||||
/// <param name="sessionId">Optional session identifier for conversation continuity.</param>
|
||||
/// <param name="threadId">Optional thread identifier for conversation continuity.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>An asynchronous enumerable of agent response updates.</returns>
|
||||
public abstract IAsyncEnumerable<AgentResponseUpdate> RunStreamingAsync(
|
||||
string agentName,
|
||||
IList<ChatMessage> messages,
|
||||
string? sessionId = null,
|
||||
string? threadId = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -34,3 +34,16 @@ internal abstract class AgentClientBase
|
||||
public virtual Task<AgentCard?> GetAgentCardAsync(string agentName, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult<AgentCard?>(null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper class to create a thread-like wrapper for agent clients.
|
||||
/// </summary>
|
||||
public class AgentClientThread
|
||||
{
|
||||
public string ThreadId { get; }
|
||||
|
||||
public AgentClientThread(string? threadId = null)
|
||||
{
|
||||
this.ThreadId = threadId ?? Guid.NewGuid().ToString("N");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ internal sealed class OpenAIChatCompletionsAgentClient(HttpClient httpClient) :
|
||||
public override async IAsyncEnumerable<AgentResponseUpdate> RunStreamingAsync(
|
||||
string agentName,
|
||||
IList<ChatMessage> messages,
|
||||
string? sessionId = null,
|
||||
string? threadId = null,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
OpenAIClientOptions options = new()
|
||||
|
||||
@@ -18,7 +18,7 @@ internal sealed class OpenAIResponsesAgentClient(HttpClient httpClient) : AgentC
|
||||
public override async IAsyncEnumerable<AgentResponseUpdate> RunStreamingAsync(
|
||||
string agentName,
|
||||
IList<ChatMessage> messages,
|
||||
string? sessionId = null,
|
||||
string? threadId = null,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
OpenAIClientOptions options = new()
|
||||
@@ -30,7 +30,7 @@ internal sealed class OpenAIResponsesAgentClient(HttpClient httpClient) : AgentC
|
||||
var openAiClient = new ResponsesClient(model: agentName, credential: new ApiKeyCredential("dummy-key"), options: options).AsIChatClient();
|
||||
var chatOptions = new ChatOptions()
|
||||
{
|
||||
ConversationId = sessionId
|
||||
ConversationId = threadId
|
||||
};
|
||||
|
||||
await foreach (var update in openAiClient.GetStreamingResponseAsync(messages, chatOptions, cancellationToken: cancellationToken))
|
||||
|
||||
@@ -19,15 +19,15 @@ public static class FunctionTriggers
|
||||
public static async Task<string> RunOrchestrationAsync([OrchestrationTrigger] TaskOrchestrationContext context)
|
||||
{
|
||||
DurableAIAgent writer = context.GetAgent("WriterAgent");
|
||||
AgentSession writerSession = await writer.GetNewSessionAsync();
|
||||
AgentThread writerThread = await writer.GetNewThreadAsync();
|
||||
|
||||
AgentResponse<TextResponse> initial = await writer.RunAsync<TextResponse>(
|
||||
message: "Write a concise inspirational sentence about learning.",
|
||||
session: writerSession);
|
||||
thread: writerThread);
|
||||
|
||||
AgentResponse<TextResponse> refined = await writer.RunAsync<TextResponse>(
|
||||
message: $"Improve this further while keeping it under 25 words: {initial.Result.Text}",
|
||||
session: writerSession);
|
||||
thread: writerThread);
|
||||
|
||||
return refined.Result.Text;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ AzureOpenAIClient client = !string.IsNullOrEmpty(azureOpenAiKey)
|
||||
? new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(azureOpenAiKey))
|
||||
: new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential());
|
||||
|
||||
// Single agent used by the orchestration to demonstrate sequential calls on the same session.
|
||||
// Single agent used by the orchestration to demonstrate sequential calls on the same thread.
|
||||
const string WriterName = "WriterAgent";
|
||||
const string WriterInstructions =
|
||||
"""
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# Single Agent Orchestration Sample
|
||||
|
||||
This sample demonstrates how to use the Durable Agent Framework (DAFx) to create a simple Azure Functions app that orchestrates sequential calls to a single AI agent using the same session for context continuity.
|
||||
This sample demonstrates how to use the Durable Agent Framework (DAFx) to create a simple Azure Functions app that orchestrates sequential calls to a single AI agent using the same conversation thread for context continuity.
|
||||
|
||||
## Key Concepts Demonstrated
|
||||
|
||||
- Orchestrating multiple interactions with the same agent in a deterministic order
|
||||
- Using the same `AgentSession` across multiple calls to maintain conversational context
|
||||
- Using the same `AgentThread` across multiple calls to maintain conversational context
|
||||
- Durable orchestration with automatic checkpointing and resumption from failures
|
||||
- HTTP API integration for starting and monitoring orchestrations
|
||||
|
||||
|
||||
+4
-4
@@ -21,7 +21,7 @@ public static class FunctionTriggers
|
||||
|
||||
// Get the spam detection agent
|
||||
DurableAIAgent spamDetectionAgent = context.GetAgent("SpamDetectionAgent");
|
||||
AgentSession spamSession = await spamDetectionAgent.GetNewSessionAsync();
|
||||
AgentThread spamThread = await spamDetectionAgent.GetNewThreadAsync();
|
||||
|
||||
// Step 1: Check if the email is spam
|
||||
AgentResponse<DetectionResult> spamDetectionResponse = await spamDetectionAgent.RunAsync<DetectionResult>(
|
||||
@@ -31,7 +31,7 @@ public static class FunctionTriggers
|
||||
Email ID: {email.EmailId}
|
||||
Content: {email.EmailContent}
|
||||
""",
|
||||
session: spamSession);
|
||||
thread: spamThread);
|
||||
DetectionResult result = spamDetectionResponse.Result;
|
||||
|
||||
// Step 2: Conditional logic based on spam detection result
|
||||
@@ -43,7 +43,7 @@ public static class FunctionTriggers
|
||||
|
||||
// Generate and send response for legitimate email
|
||||
DurableAIAgent emailAssistantAgent = context.GetAgent("EmailAssistantAgent");
|
||||
AgentSession emailSession = await emailAssistantAgent.GetNewSessionAsync();
|
||||
AgentThread emailThread = await emailAssistantAgent.GetNewThreadAsync();
|
||||
|
||||
AgentResponse<EmailResponse> emailAssistantResponse = await emailAssistantAgent.RunAsync<EmailResponse>(
|
||||
message:
|
||||
@@ -53,7 +53,7 @@ public static class FunctionTriggers
|
||||
Email ID: {email.EmailId}
|
||||
Content: {email.EmailContent}
|
||||
""",
|
||||
session: emailSession);
|
||||
thread: emailThread);
|
||||
|
||||
EmailResponse emailResponse = emailAssistantResponse.Result;
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ public static class FunctionTriggers
|
||||
|
||||
// Get the writer agent
|
||||
DurableAIAgent writerAgent = context.GetAgent("WriterAgent");
|
||||
AgentSession writerSession = await writerAgent.GetNewSessionAsync();
|
||||
AgentThread writerThread = await writerAgent.GetNewThreadAsync();
|
||||
|
||||
// Set initial status
|
||||
context.SetCustomStatus($"Starting content generation for topic: {input.Topic}");
|
||||
@@ -32,7 +32,7 @@ public static class FunctionTriggers
|
||||
// Step 1: Generate initial content
|
||||
AgentResponse<GeneratedContent> writerResponse = await writerAgent.RunAsync<GeneratedContent>(
|
||||
message: $"Write a short article about '{input.Topic}'.",
|
||||
session: writerSession);
|
||||
thread: writerThread);
|
||||
GeneratedContent content = writerResponse.Result;
|
||||
|
||||
// Human-in-the-loop iteration - we set a maximum number of attempts to avoid infinite loops
|
||||
@@ -81,7 +81,7 @@ public static class FunctionTriggers
|
||||
|
||||
Human Feedback: {humanResponse.Feedback}
|
||||
""",
|
||||
session: writerSession);
|
||||
thread: writerThread);
|
||||
|
||||
content = writerResponse.Result;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ public static class FunctionTriggers
|
||||
|
||||
// Get the writer agent
|
||||
DurableAIAgent writerAgent = context.GetAgent("Writer");
|
||||
AgentSession writerSession = await writerAgent.GetNewSessionAsync();
|
||||
AgentThread writerThread = await writerAgent.GetNewThreadAsync();
|
||||
|
||||
// Set initial status
|
||||
context.SetCustomStatus($"Starting content generation for topic: {input.Topic}");
|
||||
@@ -28,7 +28,7 @@ public static class FunctionTriggers
|
||||
// Step 1: Generate initial content
|
||||
AgentResponse<GeneratedContent> writerResponse = await writerAgent.RunAsync<GeneratedContent>(
|
||||
message: $"Write a short article about '{input.Topic}'.",
|
||||
session: writerSession);
|
||||
thread: writerThread);
|
||||
GeneratedContent content = writerResponse.Result;
|
||||
|
||||
// Human-in-the-loop iteration - we set a maximum number of attempts to avoid infinite loops
|
||||
@@ -102,7 +102,7 @@ public static class FunctionTriggers
|
||||
|
||||
Human Feedback: {humanResponse.Feedback}
|
||||
""",
|
||||
session: writerSession);
|
||||
thread: writerThread);
|
||||
|
||||
content = writerResponse.Result;
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ internal sealed class Tools(ILogger<Tools> logger)
|
||||
{
|
||||
this._logger.LogInformation("Getting status for workflow instance: {InstanceId}", instanceId);
|
||||
|
||||
// Get the current agent context using the session-static property
|
||||
// Get the current agent context using the thread-static property
|
||||
OrchestrationMetadata? status = await DurableAgentContext.Current.GetOrchestrationStatusAsync(
|
||||
instanceId,
|
||||
includeDetails);
|
||||
|
||||
@@ -94,15 +94,15 @@ public sealed class FunctionTriggers
|
||||
|
||||
AIAgent agentProxy = durableClient.AsDurableAgentProxy(context, "TravelPlanner");
|
||||
|
||||
// Create a new agent session
|
||||
AgentSession session = await agentProxy.GetNewSessionAsync(cancellationToken);
|
||||
string agentSessionId = session.GetService<AgentSessionId>().ToString();
|
||||
// Create a new agent thread
|
||||
AgentThread thread = await agentProxy.GetNewThreadAsync(cancellationToken);
|
||||
string agentSessionId = thread.GetService<AgentSessionId>().ToString();
|
||||
|
||||
this._logger.LogInformation("Creating new agent session: {AgentSessionId}", agentSessionId);
|
||||
|
||||
// Run the agent in the background (fire-and-forget)
|
||||
DurableAgentRunOptions options = new() { IsFireAndForget = true };
|
||||
await agentProxy.RunAsync(prompt, session, options, cancellationToken);
|
||||
await agentProxy.RunAsync(prompt, thread, options, cancellationToken);
|
||||
|
||||
this._logger.LogInformation("Agent run started for session: {AgentSessionId}", agentSessionId);
|
||||
|
||||
|
||||
@@ -65,9 +65,9 @@ public sealed class RedisStreamResponseHandler : IAgentResponseHandler
|
||||
"DurableAgentContext.Current is not set. This handler must be used within a durable agent context.");
|
||||
}
|
||||
|
||||
// Get session ID from the current session context, which is only available in the context of
|
||||
// Get session ID from the current thread context, which is only available in the context of
|
||||
// a durable agent execution.
|
||||
string agentSessionId = context.CurrentSession.GetService<AgentSessionId>().ToString();
|
||||
string agentSessionId = context.CurrentThread.GetService<AgentSessionId>().ToString();
|
||||
string streamKey = GetStreamKey(agentSessionId);
|
||||
|
||||
IDatabase db = this._redis.GetDatabase();
|
||||
|
||||
@@ -60,8 +60,8 @@ Console.ResetColor();
|
||||
Console.WriteLine("Enter a message for the Joker agent (or 'exit' to quit):");
|
||||
Console.WriteLine();
|
||||
|
||||
// Create a session for the conversation
|
||||
AgentSession session = await agentProxy.GetNewSessionAsync();
|
||||
// Create a thread for the conversation
|
||||
AgentThread thread = await agentProxy.GetNewThreadAsync();
|
||||
|
||||
while (true)
|
||||
{
|
||||
@@ -85,7 +85,7 @@ while (true)
|
||||
{
|
||||
AgentResponse agentResponse = await agentProxy.RunAsync(
|
||||
message: input,
|
||||
session: session,
|
||||
thread: thread,
|
||||
cancellationToken: CancellationToken.None);
|
||||
|
||||
Console.WriteLine(agentResponse.Text);
|
||||
|
||||
@@ -33,7 +33,7 @@ AzureOpenAIClient client = !string.IsNullOrEmpty(azureOpenAiKey)
|
||||
? new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(azureOpenAiKey))
|
||||
: new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential());
|
||||
|
||||
// Single agent used by the orchestration to demonstrate sequential calls on the same session.
|
||||
// Single agent used by the orchestration to demonstrate sequential calls on the same thread.
|
||||
const string WriterName = "WriterAgent";
|
||||
const string WriterInstructions =
|
||||
"""
|
||||
@@ -47,15 +47,15 @@ AIAgent writerAgent = client.GetChatClient(deploymentName).AsAIAgent(WriterInstr
|
||||
static async Task<string> RunOrchestratorAsync(TaskOrchestrationContext context)
|
||||
{
|
||||
DurableAIAgent writer = context.GetAgent("WriterAgent");
|
||||
AgentSession writerSession = await writer.GetNewSessionAsync();
|
||||
AgentThread writerThread = await writer.GetNewThreadAsync();
|
||||
|
||||
AgentResponse<TextResponse> initial = await writer.RunAsync<TextResponse>(
|
||||
message: "Write a concise inspirational sentence about learning.",
|
||||
session: writerSession);
|
||||
thread: writerThread);
|
||||
|
||||
AgentResponse<TextResponse> refined = await writer.RunAsync<TextResponse>(
|
||||
message: $"Improve this further while keeping it under 25 words: {initial.Result.Text}",
|
||||
session: writerSession);
|
||||
thread: writerThread);
|
||||
|
||||
return refined.Result.Text;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# Single Agent Orchestration Sample
|
||||
|
||||
This sample demonstrates how to use the durable agents extension to create a simple console app that orchestrates sequential calls to a single AI agent using the same session for context continuity.
|
||||
This sample demonstrates how to use the durable agents extension to create a simple console app that orchestrates sequential calls to a single AI agent using the same conversation thread for context continuity.
|
||||
|
||||
## Key Concepts Demonstrated
|
||||
|
||||
- Orchestrating multiple interactions with the same agent in a deterministic order
|
||||
- Using the same `AgentSession` across multiple calls to maintain conversational context
|
||||
- Using the same `AgentThread` across multiple calls to maintain conversational context
|
||||
- Durable orchestration with automatic checkpointing and resumption from failures
|
||||
- Waiting for orchestration completion using `WaitForInstanceCompletionAsync`
|
||||
|
||||
|
||||
+4
-4
@@ -56,7 +56,7 @@ static async Task<string> RunOrchestratorAsync(TaskOrchestrationContext context,
|
||||
{
|
||||
// Get the spam detection agent
|
||||
DurableAIAgent spamDetectionAgent = context.GetAgent(SpamDetectionAgentName);
|
||||
AgentSession spamSession = await spamDetectionAgent.GetNewSessionAsync();
|
||||
AgentThread spamThread = await spamDetectionAgent.GetNewThreadAsync();
|
||||
|
||||
// Step 1: Check if the email is spam
|
||||
AgentResponse<DetectionResult> spamDetectionResponse = await spamDetectionAgent.RunAsync<DetectionResult>(
|
||||
@@ -66,7 +66,7 @@ static async Task<string> RunOrchestratorAsync(TaskOrchestrationContext context,
|
||||
Email ID: {email.EmailId}
|
||||
Content: {email.EmailContent}
|
||||
""",
|
||||
session: spamSession);
|
||||
thread: spamThread);
|
||||
DetectionResult result = spamDetectionResponse.Result;
|
||||
|
||||
// Step 2: Conditional logic based on spam detection result
|
||||
@@ -78,7 +78,7 @@ static async Task<string> RunOrchestratorAsync(TaskOrchestrationContext context,
|
||||
|
||||
// Generate and send response for legitimate email
|
||||
DurableAIAgent emailAssistantAgent = context.GetAgent(EmailAssistantAgentName);
|
||||
AgentSession emailSession = await emailAssistantAgent.GetNewSessionAsync();
|
||||
AgentThread emailThread = await emailAssistantAgent.GetNewThreadAsync();
|
||||
|
||||
AgentResponse<EmailResponse> emailAssistantResponse = await emailAssistantAgent.RunAsync<EmailResponse>(
|
||||
message:
|
||||
@@ -88,7 +88,7 @@ static async Task<string> RunOrchestratorAsync(TaskOrchestrationContext context,
|
||||
Email ID: {email.EmailId}
|
||||
Content: {email.EmailContent}
|
||||
""",
|
||||
session: emailSession);
|
||||
thread: emailThread);
|
||||
|
||||
EmailResponse emailResponse = emailAssistantResponse.Result;
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ static async Task<object> RunOrchestratorAsync(TaskOrchestrationContext context,
|
||||
{
|
||||
// Get the writer agent
|
||||
DurableAIAgent writerAgent = context.GetAgent("WriterAgent");
|
||||
AgentSession writerSession = await writerAgent.GetNewSessionAsync();
|
||||
AgentThread writerThread = await writerAgent.GetNewThreadAsync();
|
||||
|
||||
// Set initial status
|
||||
context.SetCustomStatus($"Starting content generation for topic: {input.Topic}");
|
||||
@@ -56,7 +56,7 @@ static async Task<object> RunOrchestratorAsync(TaskOrchestrationContext context,
|
||||
// Step 1: Generate initial content
|
||||
AgentResponse<GeneratedContent> writerResponse = await writerAgent.RunAsync<GeneratedContent>(
|
||||
message: $"Write a short article about '{input.Topic}' in less than 300 words.",
|
||||
session: writerSession);
|
||||
thread: writerThread);
|
||||
GeneratedContent content = writerResponse.Result;
|
||||
|
||||
// Human-in-the-loop iteration - we set a maximum number of attempts to avoid infinite loops
|
||||
@@ -105,7 +105,7 @@ static async Task<object> RunOrchestratorAsync(TaskOrchestrationContext context,
|
||||
|
||||
Human Feedback: {humanResponse.Feedback}
|
||||
""",
|
||||
session: writerSession);
|
||||
thread: writerThread);
|
||||
|
||||
content = writerResponse.Result;
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ static async Task<object> RunOrchestratorAsync(TaskOrchestrationContext context,
|
||||
{
|
||||
// Get the writer agent
|
||||
DurableAIAgent writerAgent = context.GetAgent(WriterAgentName);
|
||||
AgentSession writerSession = await writerAgent.GetNewSessionAsync();
|
||||
AgentThread writerThread = await writerAgent.GetNewThreadAsync();
|
||||
|
||||
// Set initial status
|
||||
context.SetCustomStatus($"Starting content generation for topic: {input.Topic}");
|
||||
@@ -67,7 +67,7 @@ static async Task<object> RunOrchestratorAsync(TaskOrchestrationContext context,
|
||||
// Step 1: Generate initial content
|
||||
AgentResponse<GeneratedContent> writerResponse = await writerAgent.RunAsync<GeneratedContent>(
|
||||
message: $"Write a short article about '{input.Topic}'.",
|
||||
session: writerSession);
|
||||
thread: writerThread);
|
||||
GeneratedContent content = writerResponse.Result;
|
||||
|
||||
// Human-in-the-loop iteration - we set a maximum number of attempts to avoid infinite loops
|
||||
@@ -141,7 +141,7 @@ static async Task<object> RunOrchestratorAsync(TaskOrchestrationContext context,
|
||||
|
||||
Human Feedback: {humanResponse.Feedback}
|
||||
""",
|
||||
session: writerSession);
|
||||
thread: writerThread);
|
||||
|
||||
content = writerResponse.Result;
|
||||
}
|
||||
@@ -203,7 +203,7 @@ static async Task<object> GetWorkflowStatusAsync(
|
||||
[Description("The instance ID of the workflow to check")] string instanceId,
|
||||
[Description("Whether to include detailed information")] bool includeDetails = true)
|
||||
{
|
||||
// Get the current agent context using the session-static property
|
||||
// Get the current agent context using the thread-static property
|
||||
OrchestrationMetadata? status = await DurableAgentContext.Current.GetOrchestrationStatusAsync(
|
||||
instanceId,
|
||||
includeDetails);
|
||||
@@ -298,8 +298,8 @@ Console.ResetColor();
|
||||
Console.WriteLine("Enter a topic for the Publisher agent to write about (or 'exit' to quit):");
|
||||
Console.WriteLine();
|
||||
|
||||
// Create a session for the conversation
|
||||
AgentSession session = await agentProxy.GetNewSessionAsync();
|
||||
// Create a thread for the conversation
|
||||
AgentThread thread = await agentProxy.GetNewThreadAsync();
|
||||
|
||||
using CancellationTokenSource cts = new();
|
||||
Console.CancelKeyPress += (sender, e) =>
|
||||
@@ -330,7 +330,7 @@ while (!cts.Token.IsCancellationRequested)
|
||||
{
|
||||
AgentResponse agentResponse = await agentProxy.RunAsync(
|
||||
message: input,
|
||||
session: session,
|
||||
thread: thread,
|
||||
cancellationToken: cts.Token);
|
||||
|
||||
Console.WriteLine(agentResponse.Text);
|
||||
|
||||
@@ -304,9 +304,9 @@ if (string.IsNullOrWhiteSpace(prompt) || prompt.Equals("exit", StringComparison.
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a new agent session
|
||||
AgentSession session = await agentProxy.GetNewSessionAsync();
|
||||
AgentSessionId sessionId = session.GetService<AgentSessionId>();
|
||||
// Create a new agent thread
|
||||
AgentThread thread = await agentProxy.GetNewThreadAsync();
|
||||
AgentSessionId sessionId = thread.GetService<AgentSessionId>();
|
||||
string conversationId = sessionId.ToString();
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
@@ -316,7 +316,7 @@ Console.ResetColor();
|
||||
|
||||
// Run the agent in the background
|
||||
DurableAgentRunOptions options = new() { IsFireAndForget = true };
|
||||
await agentProxy.RunAsync(prompt, session, options, CancellationToken.None);
|
||||
await agentProxy.RunAsync(prompt, thread, options, CancellationToken.None);
|
||||
|
||||
bool streamCompleted = false;
|
||||
while (!streamCompleted)
|
||||
|
||||
+3
-3
@@ -61,12 +61,12 @@ public sealed class RedisStreamResponseHandler : IAgentResponseHandler
|
||||
DurableAgentContext context = DurableAgentContext.Current
|
||||
?? throw new InvalidOperationException("DurableAgentContext.Current is not set. This handler must be used within a durable agent context.");
|
||||
|
||||
// Get conversation ID from the current session context, which is only available in the context of
|
||||
// Get conversation ID from the current thread context, which is only available in the context of
|
||||
// a durable agent execution.
|
||||
string conversationId = context.CurrentSession.GetService<AgentSessionId>().ToString();
|
||||
string conversationId = context.CurrentThread.GetService<AgentSessionId>().ToString();
|
||||
if (string.IsNullOrEmpty(conversationId))
|
||||
{
|
||||
throw new InvalidOperationException("Unable to determine conversation ID from the current session.");
|
||||
throw new InvalidOperationException("Unable to determine conversation ID from the current thread.");
|
||||
}
|
||||
|
||||
string streamKey = GetStreamKey(conversationId);
|
||||
|
||||
@@ -16,10 +16,10 @@ AgentCard agentCard = await agentCardResolver.GetAgentCardAsync();
|
||||
// Create an instance of the AIAgent for an existing A2A agent specified by the agent card.
|
||||
AIAgent agent = agentCard.AsAIAgent();
|
||||
|
||||
AgentSession session = await agent.GetNewSessionAsync();
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
|
||||
// Start the initial run with a long-running task.
|
||||
AgentResponse response = await agent.RunAsync("Conduct a comprehensive analysis of quantum computing applications in cryptography, including recent breakthroughs, implementation challenges, and future roadmap. Please include diagrams and visual representations to illustrate complex concepts.", session);
|
||||
AgentResponse response = await agent.RunAsync("Conduct a comprehensive analysis of quantum computing applications in cryptography, including recent breakthroughs, implementation challenges, and future roadmap. Please include diagrams and visual representations to illustrate complex concepts.", thread);
|
||||
|
||||
// Poll until the response is complete.
|
||||
while (response.ContinuationToken is { } token)
|
||||
@@ -28,7 +28,7 @@ while (response.ContinuationToken is { } token)
|
||||
await Task.Delay(TimeSpan.FromSeconds(2));
|
||||
|
||||
// Continue with the token.
|
||||
response = await agent.RunAsync(session, options: new AgentRunOptions { ContinuationToken = token });
|
||||
response = await agent.RunAsync(thread, options: new AgentRunOptions { ContinuationToken = token });
|
||||
}
|
||||
|
||||
// Display the result
|
||||
|
||||
@@ -20,7 +20,7 @@ AIAgent agent = chatClient.AsAIAgent(
|
||||
name: "agui-client",
|
||||
description: "AG-UI Client Agent");
|
||||
|
||||
AgentSession session = await agent.GetNewSessionAsync();
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
List<ChatMessage> messages =
|
||||
[
|
||||
new(ChatRole.System, "You are a helpful assistant.")
|
||||
@@ -49,18 +49,18 @@ try
|
||||
|
||||
// Stream the response
|
||||
bool isFirstUpdate = true;
|
||||
string? sessionId = null;
|
||||
string? threadId = null;
|
||||
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(messages, session))
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(messages, thread))
|
||||
{
|
||||
ChatResponseUpdate chatUpdate = update.AsChatResponseUpdate();
|
||||
|
||||
// First update indicates run started
|
||||
if (isFirstUpdate)
|
||||
{
|
||||
sessionId = chatUpdate.ConversationId;
|
||||
threadId = chatUpdate.ConversationId;
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine($"\n[Run Started - Session: {chatUpdate.ConversationId}, Run: {chatUpdate.ResponseId}]");
|
||||
Console.WriteLine($"\n[Run Started - Thread: {chatUpdate.ConversationId}, Run: {chatUpdate.ResponseId}]");
|
||||
Console.ResetColor();
|
||||
isFirstUpdate = false;
|
||||
}
|
||||
@@ -84,7 +84,7 @@ try
|
||||
}
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine($"\n[Run Finished - Session: {sessionId}]");
|
||||
Console.WriteLine($"\n[Run Finished - Thread: {threadId}]");
|
||||
Console.ResetColor();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ AIAgent agent = chatClient.AsAIAgent(
|
||||
name: "agui-client",
|
||||
description: "AG-UI Client Agent");
|
||||
|
||||
AgentSession session = await agent.GetNewSessionAsync();
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
List<ChatMessage> messages =
|
||||
[
|
||||
new(ChatRole.System, "You are a helpful assistant.")
|
||||
@@ -49,18 +49,18 @@ try
|
||||
|
||||
// Stream the response
|
||||
bool isFirstUpdate = true;
|
||||
string? sessionId = null;
|
||||
string? threadId = null;
|
||||
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(messages, session))
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(messages, thread))
|
||||
{
|
||||
ChatResponseUpdate chatUpdate = update.AsChatResponseUpdate();
|
||||
|
||||
// First update indicates run started
|
||||
if (isFirstUpdate)
|
||||
{
|
||||
sessionId = chatUpdate.ConversationId;
|
||||
threadId = chatUpdate.ConversationId;
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine($"\n[Run Started - Session: {chatUpdate.ConversationId}, Run: {chatUpdate.ResponseId}]");
|
||||
Console.WriteLine($"\n[Run Started - Thread: {chatUpdate.ConversationId}, Run: {chatUpdate.ResponseId}]");
|
||||
Console.ResetColor();
|
||||
isFirstUpdate = false;
|
||||
}
|
||||
@@ -116,7 +116,7 @@ try
|
||||
}
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine($"\n[Run Finished - Session: {sessionId}]");
|
||||
Console.WriteLine($"\n[Run Finished - Thread: {threadId}]");
|
||||
Console.ResetColor();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ AIAgent agent = chatClient.AsAIAgent(
|
||||
description: "AG-UI Client Agent",
|
||||
tools: frontendTools);
|
||||
|
||||
AgentSession session = await agent.GetNewSessionAsync();
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
List<ChatMessage> messages =
|
||||
[
|
||||
new(ChatRole.System, "You are a helpful assistant.")
|
||||
@@ -62,18 +62,18 @@ try
|
||||
|
||||
// Stream the response
|
||||
bool isFirstUpdate = true;
|
||||
string? sessionId = null;
|
||||
string? threadId = null;
|
||||
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(messages, session))
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(messages, thread))
|
||||
{
|
||||
ChatResponseUpdate chatUpdate = update.AsChatResponseUpdate();
|
||||
|
||||
// First update indicates run started
|
||||
if (isFirstUpdate)
|
||||
{
|
||||
sessionId = chatUpdate.ConversationId;
|
||||
threadId = chatUpdate.ConversationId;
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine($"\n[Run Started - Session: {chatUpdate.ConversationId}, Run: {chatUpdate.ResponseId}]");
|
||||
Console.WriteLine($"\n[Run Started - Thread: {chatUpdate.ConversationId}, Run: {chatUpdate.ResponseId}]");
|
||||
Console.ResetColor();
|
||||
isFirstUpdate = false;
|
||||
}
|
||||
@@ -109,7 +109,7 @@ try
|
||||
}
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine($"\n[Run Finished - Session: {sessionId}]");
|
||||
Console.WriteLine($"\n[Run Finished - Thread: {threadId}]");
|
||||
Console.ResetColor();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ JsonSerializerOptions jsonSerializerOptions = JsonSerializerOptions.Default;
|
||||
ServerFunctionApprovalClientAgent agent = new(baseAgent, jsonSerializerOptions);
|
||||
|
||||
List<ChatMessage> messages = [];
|
||||
AgentSession? session = null;
|
||||
AgentThread? thread = null;
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
Console.WriteLine("Ask a question (or type 'exit' to quit):");
|
||||
@@ -52,7 +52,7 @@ while ((input = Console.ReadLine()) != null && !input.Equals("exit", StringCompa
|
||||
approvalResponses.Clear();
|
||||
|
||||
List<AgentResponseUpdate> chatResponseUpdates = [];
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(messages, session, cancellationToken: default))
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(messages, thread, cancellationToken: default))
|
||||
{
|
||||
chatResponseUpdates.Add(update);
|
||||
foreach (AIContent content in update.Contents)
|
||||
|
||||
+4
-4
@@ -24,17 +24,17 @@ internal sealed class ServerFunctionApprovalClientAgent : DelegatingAIAgent
|
||||
|
||||
protected override Task<AgentResponse> RunCoreAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentSession? session = null,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.RunCoreStreamingAsync(messages, session, options, cancellationToken)
|
||||
return this.RunCoreStreamingAsync(messages, thread, options, cancellationToken)
|
||||
.ToAgentResponseAsync(cancellationToken);
|
||||
}
|
||||
|
||||
protected override async IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentSession? session = null,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -43,7 +43,7 @@ internal sealed class ServerFunctionApprovalClientAgent : DelegatingAIAgent
|
||||
|
||||
// Run the inner agent and intercept any approval requests
|
||||
await foreach (var update in this.InnerAgent.RunStreamingAsync(
|
||||
processedMessages, session, options, cancellationToken).ConfigureAwait(false))
|
||||
processedMessages, thread, options, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
yield return ProcessIncomingServerApprovalRequests(update, this._jsonSerializerOptions);
|
||||
}
|
||||
|
||||
+4
-4
@@ -24,17 +24,17 @@ internal sealed class ServerFunctionApprovalAgent : DelegatingAIAgent
|
||||
|
||||
protected override Task<AgentResponse> RunCoreAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentSession? session = null,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.RunCoreStreamingAsync(messages, session, options, cancellationToken)
|
||||
return this.RunCoreStreamingAsync(messages, thread, options, cancellationToken)
|
||||
.ToAgentResponseAsync(cancellationToken);
|
||||
}
|
||||
|
||||
protected override async IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentSession? session = null,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -43,7 +43,7 @@ internal sealed class ServerFunctionApprovalAgent : DelegatingAIAgent
|
||||
|
||||
// Run the inner agent and intercept any approval requests
|
||||
await foreach (var update in this.InnerAgent.RunStreamingAsync(
|
||||
processedMessages, session, options, cancellationToken).ConfigureAwait(false))
|
||||
processedMessages, thread, options, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
yield return ProcessOutgoingApprovalRequests(update, this._jsonSerializerOptions);
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ JsonSerializerOptions jsonOptions = new(JsonSerializerDefaults.Web)
|
||||
};
|
||||
StatefulAgent<AgentState> agent = new(baseAgent, jsonOptions, new AgentState());
|
||||
|
||||
AgentSession session = await agent.GetNewSessionAsync();
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
List<ChatMessage> messages =
|
||||
[
|
||||
new(ChatRole.System, "You are a helpful recipe assistant.")
|
||||
@@ -65,21 +65,21 @@ try
|
||||
|
||||
// Stream the response
|
||||
bool isFirstUpdate = true;
|
||||
string? sessionId = null;
|
||||
string? threadId = null;
|
||||
bool stateReceived = false;
|
||||
|
||||
Console.WriteLine();
|
||||
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(messages, session))
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(messages, thread))
|
||||
{
|
||||
ChatResponseUpdate chatUpdate = update.AsChatResponseUpdate();
|
||||
|
||||
// First update indicates run started
|
||||
if (isFirstUpdate)
|
||||
{
|
||||
sessionId = chatUpdate.ConversationId;
|
||||
threadId = chatUpdate.ConversationId;
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine($"[Run Started - Session: {chatUpdate.ConversationId}, Run: {chatUpdate.ResponseId}]");
|
||||
Console.WriteLine($"[Run Started - Thread: {chatUpdate.ConversationId}, Run: {chatUpdate.ResponseId}]");
|
||||
Console.ResetColor();
|
||||
isFirstUpdate = false;
|
||||
}
|
||||
@@ -113,7 +113,7 @@ try
|
||||
}
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine($"\n[Run Finished - Session: {sessionId}]");
|
||||
Console.WriteLine($"\n[Run Finished - Thread: {threadId}]");
|
||||
Console.ResetColor();
|
||||
|
||||
// Display final state if received
|
||||
|
||||
@@ -37,18 +37,18 @@ internal sealed class StatefulAgent<TState> : DelegatingAIAgent
|
||||
/// <inheritdoc />
|
||||
protected override Task<AgentResponse> RunCoreAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentSession? session = null,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.RunCoreStreamingAsync(messages, session, options, cancellationToken)
|
||||
return this.RunCoreStreamingAsync(messages, thread, options, cancellationToken)
|
||||
.ToAgentResponseAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentSession? session = null,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -64,7 +64,7 @@ internal sealed class StatefulAgent<TState> : DelegatingAIAgent
|
||||
messagesWithState.Add(stateMessage);
|
||||
|
||||
// Stream the response and update state when received
|
||||
await foreach (AgentResponseUpdate update in this.InnerAgent.RunStreamingAsync(messagesWithState, session, options, cancellationToken))
|
||||
await foreach (AgentResponseUpdate update in this.InnerAgent.RunStreamingAsync(messagesWithState, thread, options, cancellationToken))
|
||||
{
|
||||
// Check if this update contains a state snapshot
|
||||
foreach (AIContent content in update.Contents)
|
||||
|
||||
+7
-7
@@ -19,17 +19,17 @@ internal sealed class SharedStateAgent : DelegatingAIAgent
|
||||
|
||||
protected override Task<AgentResponse> RunCoreAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentSession? session = null,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.RunCoreStreamingAsync(messages, session, options, cancellationToken)
|
||||
return this.RunCoreStreamingAsync(messages, thread, options, cancellationToken)
|
||||
.ToAgentResponseAsync(cancellationToken);
|
||||
}
|
||||
|
||||
protected override async IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentSession? session = null,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -40,7 +40,7 @@ internal sealed class SharedStateAgent : DelegatingAIAgent
|
||||
state.ValueKind != JsonValueKind.Object)
|
||||
{
|
||||
// No state management requested, pass through to inner agent
|
||||
await foreach (var update in this.InnerAgent.RunStreamingAsync(messages, session, options, cancellationToken).ConfigureAwait(false))
|
||||
await foreach (var update in this.InnerAgent.RunStreamingAsync(messages, thread, options, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
yield return update;
|
||||
}
|
||||
@@ -58,7 +58,7 @@ internal sealed class SharedStateAgent : DelegatingAIAgent
|
||||
if (!hasProperties)
|
||||
{
|
||||
// Empty state - treat as no state
|
||||
await foreach (var update in this.InnerAgent.RunStreamingAsync(messages, session, options, cancellationToken).ConfigureAwait(false))
|
||||
await foreach (var update in this.InnerAgent.RunStreamingAsync(messages, thread, options, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
yield return update;
|
||||
}
|
||||
@@ -92,7 +92,7 @@ internal sealed class SharedStateAgent : DelegatingAIAgent
|
||||
|
||||
// Collect all updates from first run
|
||||
var allUpdates = new List<AgentResponseUpdate>();
|
||||
await foreach (var update in this.InnerAgent.RunStreamingAsync(firstRunMessages, session, firstRunOptions, cancellationToken).ConfigureAwait(false))
|
||||
await foreach (var update in this.InnerAgent.RunStreamingAsync(firstRunMessages, thread, firstRunOptions, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
allUpdates.Add(update);
|
||||
|
||||
@@ -129,7 +129,7 @@ internal sealed class SharedStateAgent : DelegatingAIAgent
|
||||
ChatRole.System,
|
||||
[new TextContent("Please provide a concise summary of the state changes in at most two sentences.")]));
|
||||
|
||||
await foreach (var update in this.InnerAgent.RunStreamingAsync(secondRunMessages, session, options, cancellationToken).ConfigureAwait(false))
|
||||
await foreach (var update in this.InnerAgent.RunStreamingAsync(secondRunMessages, thread, options, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
yield return update;
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ var agent = new ChatClientAgent(instrumentedChatClient,
|
||||
.UseOpenTelemetry(SourceName, configure: (cfg) => cfg.EnableSensitiveData = true) // enable telemetry at the agent level
|
||||
.Build();
|
||||
|
||||
var session = await agent.GetNewSessionAsync();
|
||||
var thread = await agent.GetNewThreadAsync();
|
||||
|
||||
appLogger.LogInformation("Agent created successfully with ID: {AgentId}", agent.Id);
|
||||
|
||||
@@ -176,7 +176,7 @@ using (appLogger.BeginScope(new Dictionary<string, object> { ["SessionId"] = ses
|
||||
Console.Write("Agent: ");
|
||||
|
||||
// Run the agent (this will create its own internal telemetry spans)
|
||||
await foreach (var update in agent.RunStreamingAsync(userInput, session))
|
||||
await foreach (var update in agent.RunStreamingAsync(userInput, thread))
|
||||
{
|
||||
Console.Write(update.Text);
|
||||
}
|
||||
|
||||
+2
-2
@@ -31,8 +31,8 @@ AIAgent agent2 = await persistentAgentsClient.CreateAIAgentAsync(
|
||||
instructions: JokerInstructions);
|
||||
|
||||
// You can then invoke the agent like any other AIAgent.
|
||||
AgentSession session = await agent1.GetNewSessionAsync();
|
||||
Console.WriteLine(await agent1.RunAsync("Tell me a joke about a pirate.", session));
|
||||
AgentThread thread = await agent1.GetNewThreadAsync();
|
||||
Console.WriteLine(await agent1.RunAsync("Tell me a joke about a pirate.", thread));
|
||||
|
||||
// Cleanup for sample purposes.
|
||||
await persistentAgentsClient.Administration.DeleteAgentAsync(agent1.Id);
|
||||
|
||||
@@ -40,11 +40,11 @@ var latestAgentVersion = jokerAgentLatest.GetService<AgentVersion>()!;
|
||||
Console.WriteLine($"Latest agent version id: {latestAgentVersion.Id}");
|
||||
|
||||
// Once you have the AIAgent, you can invoke it like any other AIAgent.
|
||||
AgentSession session = await jokerAgentLatest.GetNewSessionAsync();
|
||||
Console.WriteLine(await jokerAgentLatest.RunAsync("Tell me a joke about a pirate.", session));
|
||||
AgentThread thread = await jokerAgentLatest.GetNewThreadAsync();
|
||||
Console.WriteLine(await jokerAgentLatest.RunAsync("Tell me a joke about a pirate.", thread));
|
||||
|
||||
// This will use the same session to continue the conversation.
|
||||
Console.WriteLine(await jokerAgentLatest.RunAsync("Now tell me a joke about a cat and a dog using last joke as the anchor.", session));
|
||||
// This will use the same thread to continue the conversation.
|
||||
Console.WriteLine(await jokerAgentLatest.RunAsync("Now tell me a joke about a cat and a dog using last joke as the anchor.", thread));
|
||||
|
||||
// Cleanup by agent name removes both agent versions created.
|
||||
aiProjectClient.Agents.DeleteAgent(existingJokerAgent.Name);
|
||||
|
||||
+25
-25
@@ -28,35 +28,35 @@ namespace SampleApp
|
||||
{
|
||||
public override string? Name => "UpperCaseParrotAgent";
|
||||
|
||||
public override ValueTask<AgentSession> GetNewSessionAsync(CancellationToken cancellationToken = default)
|
||||
=> new(new CustomAgentSession());
|
||||
public override ValueTask<AgentThread> GetNewThreadAsync(CancellationToken cancellationToken = default)
|
||||
=> new(new CustomAgentThread());
|
||||
|
||||
public override ValueTask<AgentSession> DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default)
|
||||
=> new(new CustomAgentSession(serializedSession, jsonSerializerOptions));
|
||||
public override ValueTask<AgentThread> DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default)
|
||||
=> new(new CustomAgentThread(serializedThread, jsonSerializerOptions));
|
||||
|
||||
protected override async Task<AgentResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
||||
protected override async Task<AgentResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Create a session if the user didn't supply one.
|
||||
session ??= await this.GetNewSessionAsync(cancellationToken);
|
||||
// Create a thread if the user didn't supply one.
|
||||
thread ??= await this.GetNewThreadAsync(cancellationToken);
|
||||
|
||||
if (session is not CustomAgentSession typedSession)
|
||||
if (thread is not CustomAgentThread typedThread)
|
||||
{
|
||||
throw new ArgumentException($"The provided session is not of type {nameof(CustomAgentSession)}.", nameof(session));
|
||||
throw new ArgumentException($"The provided thread is not of type {nameof(CustomAgentThread)}.", nameof(thread));
|
||||
}
|
||||
|
||||
// Get existing messages from the store
|
||||
var invokingContext = new ChatHistoryProvider.InvokingContext(messages);
|
||||
var storeMessages = await typedSession.ChatHistoryProvider.InvokingAsync(invokingContext, cancellationToken);
|
||||
var storeMessages = await typedThread.ChatHistoryProvider.InvokingAsync(invokingContext, cancellationToken);
|
||||
|
||||
// Clone the input messages and turn them into response messages with upper case text.
|
||||
List<ChatMessage> responseMessages = CloneAndToUpperCase(messages, this.Name).ToList();
|
||||
|
||||
// Notify the session of the input and output messages.
|
||||
// Notify the thread of the input and output messages.
|
||||
var invokedContext = new ChatHistoryProvider.InvokedContext(messages, storeMessages)
|
||||
{
|
||||
ResponseMessages = responseMessages
|
||||
};
|
||||
await typedSession.ChatHistoryProvider.InvokedAsync(invokedContext, cancellationToken);
|
||||
await typedThread.ChatHistoryProvider.InvokedAsync(invokedContext, cancellationToken);
|
||||
|
||||
return new AgentResponse
|
||||
{
|
||||
@@ -66,29 +66,29 @@ namespace SampleApp
|
||||
};
|
||||
}
|
||||
|
||||
protected override async IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(IEnumerable<ChatMessage> messages, AgentSession? session = null, AgentRunOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
protected override async IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Create a session if the user didn't supply one.
|
||||
session ??= await this.GetNewSessionAsync(cancellationToken);
|
||||
// Create a thread if the user didn't supply one.
|
||||
thread ??= await this.GetNewThreadAsync(cancellationToken);
|
||||
|
||||
if (session is not CustomAgentSession typedSession)
|
||||
if (thread is not CustomAgentThread typedThread)
|
||||
{
|
||||
throw new ArgumentException($"The provided session is not of type {nameof(CustomAgentSession)}.", nameof(session));
|
||||
throw new ArgumentException($"The provided thread is not of type {nameof(CustomAgentThread)}.", nameof(thread));
|
||||
}
|
||||
|
||||
// Get existing messages from the store
|
||||
var invokingContext = new ChatHistoryProvider.InvokingContext(messages);
|
||||
var storeMessages = await typedSession.ChatHistoryProvider.InvokingAsync(invokingContext, cancellationToken);
|
||||
var storeMessages = await typedThread.ChatHistoryProvider.InvokingAsync(invokingContext, cancellationToken);
|
||||
|
||||
// Clone the input messages and turn them into response messages with upper case text.
|
||||
List<ChatMessage> responseMessages = CloneAndToUpperCase(messages, this.Name).ToList();
|
||||
|
||||
// Notify the session of the input and output messages.
|
||||
// Notify the thread of the input and output messages.
|
||||
var invokedContext = new ChatHistoryProvider.InvokedContext(messages, storeMessages)
|
||||
{
|
||||
ResponseMessages = responseMessages
|
||||
};
|
||||
await typedSession.ChatHistoryProvider.InvokedAsync(invokedContext, cancellationToken);
|
||||
await typedThread.ChatHistoryProvider.InvokedAsync(invokedContext, cancellationToken);
|
||||
|
||||
foreach (var message in responseMessages)
|
||||
{
|
||||
@@ -128,14 +128,14 @@ namespace SampleApp
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// A session type for our custom agent that only supports in memory storage of messages.
|
||||
/// A thread type for our custom agent that only supports in memory storage of messages.
|
||||
/// </summary>
|
||||
internal sealed class CustomAgentSession : InMemoryAgentSession
|
||||
internal sealed class CustomAgentThread : InMemoryAgentThread
|
||||
{
|
||||
internal CustomAgentSession() { }
|
||||
internal CustomAgentThread() { }
|
||||
|
||||
internal CustomAgentSession(JsonElement serializedSessionState, JsonSerializerOptions? jsonSerializerOptions = null)
|
||||
: base(serializedSessionState, jsonSerializerOptions) { }
|
||||
internal CustomAgentThread(JsonElement serializedThreadState, JsonSerializerOptions? jsonSerializerOptions = null)
|
||||
: base(serializedThreadState, jsonSerializerOptions) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="GitHub.Copilot.SDK" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.GithubCopilot\Microsoft.Agents.AI.GithubCopilot.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,51 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This sample shows how to create a GitHub Copilot agent with shell command permissions.
|
||||
|
||||
using GitHub.Copilot.SDK;
|
||||
using Microsoft.Agents.AI;
|
||||
|
||||
// Permission handler that prompts the user for approval
|
||||
static Task<PermissionRequestResult> PromptPermission(PermissionRequest request, PermissionInvocation invocation)
|
||||
{
|
||||
Console.WriteLine($"\n[Permission Request: {request.Kind}]");
|
||||
Console.Write("Approve? (y/n): ");
|
||||
|
||||
string? input = Console.ReadLine()?.Trim().ToUpperInvariant();
|
||||
string kind = input is "Y" or "YES" ? "approved" : "denied-interactively-by-user";
|
||||
|
||||
return Task.FromResult(new PermissionRequestResult { Kind = kind });
|
||||
}
|
||||
|
||||
// Create and start a Copilot client
|
||||
await using CopilotClient copilotClient = new();
|
||||
await copilotClient.StartAsync();
|
||||
|
||||
// Create an agent with a session config that enables permission handling
|
||||
SessionConfig sessionConfig = new()
|
||||
{
|
||||
OnPermissionRequest = PromptPermission,
|
||||
};
|
||||
|
||||
AIAgent agent = copilotClient.AsAIAgent(sessionConfig, ownsClient: true);
|
||||
|
||||
// Toggle between streaming and non-streaming modes
|
||||
bool useStreaming = true;
|
||||
|
||||
string prompt = "List all files in the current directory";
|
||||
Console.WriteLine($"User: {prompt}\n");
|
||||
|
||||
if (useStreaming)
|
||||
{
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(prompt))
|
||||
{
|
||||
Console.Write(update);
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
}
|
||||
else
|
||||
{
|
||||
AgentResponse response = await agent.RunAsync(prompt);
|
||||
Console.WriteLine(response);
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
# Prerequisites
|
||||
|
||||
> **⚠️ WARNING: Container Recommendation**
|
||||
>
|
||||
> GitHub Copilot can execute tools and commands that may interact with your system. For safety, it is strongly recommended to run this sample in a containerized environment (e.g., Docker, Dev Container) to avoid unintended consequences to your machine.
|
||||
|
||||
Before you begin, ensure you have the following prerequisites:
|
||||
|
||||
- .NET 10 SDK or later
|
||||
- GitHub Copilot CLI installed and available in your PATH (or provide a custom path)
|
||||
|
||||
## Setting up GitHub Copilot CLI
|
||||
|
||||
To use this sample, you need to have the GitHub Copilot CLI installed. You can install it by following the instructions at:
|
||||
https://github.com/github/copilot-sdk
|
||||
|
||||
Once installed, ensure the `copilot` command is available in your PATH, or configure a custom path using `CopilotClientOptions`.
|
||||
|
||||
## Running the Sample
|
||||
|
||||
No additional environment variables are required if using default configuration. The sample will:
|
||||
|
||||
1. Create a GitHub Copilot client with default options
|
||||
2. Create an AI agent using the Copilot SDK
|
||||
3. Send a message to the agent
|
||||
4. Display the response
|
||||
|
||||
Run the sample:
|
||||
|
||||
```powershell
|
||||
dotnet run
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
You can customize the agent by providing additional configuration:
|
||||
|
||||
```csharp
|
||||
using GitHub.Copilot.SDK;
|
||||
using Microsoft.Agents.AI;
|
||||
|
||||
// Create and start a Copilot client
|
||||
await using CopilotClient copilotClient = new();
|
||||
await copilotClient.StartAsync();
|
||||
|
||||
// Create session configuration with specific model
|
||||
SessionConfig sessionConfig = new()
|
||||
{
|
||||
Model = "claude-opus-4.5",
|
||||
Streaming = false
|
||||
};
|
||||
|
||||
// Create an agent with custom configuration using the extension method
|
||||
AIAgent agent = copilotClient.AsAIAgent(
|
||||
sessionConfig,
|
||||
ownsClient: true,
|
||||
id: "my-copilot-agent",
|
||||
name: "My Copilot Assistant",
|
||||
description: "A helpful AI assistant powered by GitHub Copilot"
|
||||
);
|
||||
|
||||
// Use the agent - ask it to write code for us
|
||||
AgentResponse response = await agent.RunAsync("Write a small .NET 10 C# hello world single file application");
|
||||
Console.WriteLine(response);
|
||||
```
|
||||
|
||||
## Streaming Responses
|
||||
|
||||
To get streaming responses:
|
||||
|
||||
```csharp
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync("Write a C# function to calculate Fibonacci numbers"))
|
||||
{
|
||||
Console.Write(update.Text);
|
||||
}
|
||||
```
|
||||
@@ -33,8 +33,8 @@ AIAgent agent2 = await assistantClient.CreateAIAgentAsync(
|
||||
instructions: JokerInstructions);
|
||||
|
||||
// You can invoke the agent like any other AIAgent.
|
||||
AgentSession session = await agent1.GetNewSessionAsync();
|
||||
Console.WriteLine(await agent1.RunAsync("Tell me a joke about a pirate.", session));
|
||||
AgentThread thread = await agent1.GetNewThreadAsync();
|
||||
Console.WriteLine(await agent1.RunAsync("Tell me a joke about a pirate.", thread));
|
||||
|
||||
// Cleanup for sample purposes.
|
||||
await assistantClient.DeleteAssistantAsync(agent1.Id);
|
||||
|
||||
@@ -22,7 +22,6 @@ See the README.md for each sample for the prerequisites for that sample.
|
||||
|[Creating an AIAgent with Azure OpenAI ChatCompletion](./Agent_With_AzureOpenAIChatCompletion/)|This sample demonstrates how to create an AIAgent using Azure OpenAI ChatCompletion as the underlying inference service|
|
||||
|[Creating an AIAgent with Azure OpenAI Responses](./Agent_With_AzureOpenAIResponses/)|This sample demonstrates how to create an AIAgent using Azure OpenAI Responses as the underlying inference service|
|
||||
|[Creating an AIAgent with a custom implementation](./Agent_With_CustomImplementation/)|This sample demonstrates how to create an AIAgent with a custom implementation|
|
||||
|[Creating an AIAgent with GitHub Copilot](./Agent_With_GithubCopilot/)|This sample demonstrates how to create an AIAgent using GitHub Copilot SDK as the underlying inference service|
|
||||
|[Creating an AIAgent with Ollama](./Agent_With_Ollama/)|This sample demonstrates how to create an AIAgent using Ollama as the underlying inference service|
|
||||
|[Creating an AIAgent with ONNX](./Agent_With_ONNX/)|This sample demonstrates how to create an AIAgent using ONNX as the underlying inference service|
|
||||
|[Creating an AIAgent with OpenAI Assistants](./Agent_With_OpenAIAssistants/)|This sample demonstrates how to create an AIAgent using OpenAI Assistants as the underlying inference service.</br>WARNING: The Assistants API is deprecated and will be shut down. For more information see the OpenAI documentation: https://platform.openai.com/docs/assistants/migration|
|
||||
|
||||
+4
-4
@@ -26,12 +26,12 @@ AIAgent agent = new AnthropicClient { APIKey = apiKey }
|
||||
.AsAIAgent(model: model, instructions: AssistantInstructions, name: AssistantName, tools: [tool]);
|
||||
|
||||
// Non-streaming agent interaction with function tools.
|
||||
AgentSession session = await agent.GetNewSessionAsync();
|
||||
Console.WriteLine(await agent.RunAsync("What is the weather like in Amsterdam?", session));
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
Console.WriteLine(await agent.RunAsync("What is the weather like in Amsterdam?", thread));
|
||||
|
||||
// Streaming agent interaction with function tools.
|
||||
session = await agent.GetNewSessionAsync();
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync("What is the weather like in Amsterdam?", session))
|
||||
thread = await agent.GetNewThreadAsync();
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync("What is the weather like in Amsterdam?", thread))
|
||||
{
|
||||
Console.WriteLine(update);
|
||||
}
|
||||
|
||||
+11
-11
@@ -39,22 +39,22 @@ AIAgent agent = new AzureOpenAIClient(
|
||||
collectionName: "chathistory",
|
||||
vectorDimensions: 3072,
|
||||
// Configure the scope values under which chat messages will be stored.
|
||||
// In this case, we are using a fixed user ID and a unique session ID for each new session.
|
||||
storageScope: new() { UserId = "UID1", SessionId = Guid.NewGuid().ToString() },
|
||||
// In this case, we are using a fixed user ID and a unique thread ID for each new thread.
|
||||
storageScope: new() { UserId = "UID1", ThreadId = new Guid().ToString() },
|
||||
// Configure the scope which would be used to search for relevant prior messages.
|
||||
// In this case, we are searching for any messages for the user across all sessions.
|
||||
// In this case, we are searching for any messages for the user across all threads.
|
||||
searchScope: new() { UserId = "UID1" }))
|
||||
});
|
||||
|
||||
// Start a new session for the agent conversation.
|
||||
AgentSession session = await agent.GetNewSessionAsync();
|
||||
// Start a new thread for the agent conversation.
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
|
||||
// Run the agent with the session that stores conversation history in the vector store.
|
||||
Console.WriteLine(await agent.RunAsync("I like jokes about Pirates. Tell me a joke about a pirate.", session));
|
||||
// Run the agent with the thread that stores conversation history in the vector store.
|
||||
Console.WriteLine(await agent.RunAsync("I like jokes about Pirates. Tell me a joke about a pirate.", thread));
|
||||
|
||||
// Start a second session. Since we configured the search scope to be across all sessions for the user,
|
||||
// Start a second thread. Since we configured the search scope to be across all threads for the user,
|
||||
// the agent should remember that the user likes pirate jokes.
|
||||
AgentSession? session2 = await agent.GetNewSessionAsync();
|
||||
AgentThread thread2 = await agent.GetNewThreadAsync();
|
||||
|
||||
// Run the agent with the second session.
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke that I might like.", session2));
|
||||
// Run the agent with the second thread.
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke that I might like.", thread2));
|
||||
|
||||
+14
-14
@@ -2,7 +2,7 @@
|
||||
|
||||
// This sample shows how to use the Mem0Provider to persist and recall memories for an agent.
|
||||
// The sample stores conversation messages in a Mem0 service and retrieves relevant memories
|
||||
// for subsequent invocations, even across new sessions.
|
||||
// for subsequent invocations, even across new threads.
|
||||
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text.Json;
|
||||
@@ -32,7 +32,7 @@ AIAgent agent = new AzureOpenAIClient(
|
||||
{
|
||||
ChatOptions = new() { Instructions = "You are a friendly travel assistant. Use known memories about the user when responding, and do not invent details." },
|
||||
AIContextProviderFactory = (ctx, ct) => new ValueTask<AIContextProvider>(ctx.SerializedState.ValueKind is not JsonValueKind.Null and not JsonValueKind.Undefined
|
||||
// If each session should have its own Mem0 scope, you can create a new id per session here:
|
||||
// If each thread should have its own Mem0 scope, you can create a new id per thread here:
|
||||
// ? new Mem0Provider(mem0HttpClient, new Mem0ProviderScope() { ThreadId = Guid.NewGuid().ToString() })
|
||||
// In this case we are storing memories scoped by application and user instead so that memories are retained across threads.
|
||||
? new Mem0Provider(mem0HttpClient, new Mem0ProviderScope() { ApplicationId = "getting-started-agents", UserId = "sample-user" })
|
||||
@@ -40,25 +40,25 @@ AIAgent agent = new AzureOpenAIClient(
|
||||
: new Mem0Provider(mem0HttpClient, ctx.SerializedState, ctx.JsonSerializerOptions))
|
||||
});
|
||||
|
||||
AgentSession session = await agent.GetNewSessionAsync();
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
|
||||
// Clear any existing memories for this scope to demonstrate fresh behavior.
|
||||
Mem0Provider mem0Provider = session.GetService<Mem0Provider>()!;
|
||||
Mem0Provider mem0Provider = thread.GetService<Mem0Provider>()!;
|
||||
await mem0Provider.ClearStoredMemoriesAsync();
|
||||
|
||||
Console.WriteLine(await agent.RunAsync("Hi there! My name is Taylor and I'm planning a hiking trip to Patagonia in November.", session));
|
||||
Console.WriteLine(await agent.RunAsync("I'm travelling with my sister and we love finding scenic viewpoints.", session));
|
||||
Console.WriteLine(await agent.RunAsync("Hi there! My name is Taylor and I'm planning a hiking trip to Patagonia in November.", thread));
|
||||
Console.WriteLine(await agent.RunAsync("I'm travelling with my sister and we love finding scenic viewpoints.", thread));
|
||||
|
||||
Console.WriteLine("\nWaiting briefly for Mem0 to index the new memories...\n");
|
||||
await Task.Delay(TimeSpan.FromSeconds(2));
|
||||
|
||||
Console.WriteLine(await agent.RunAsync("What do you already know about my upcoming trip?", session));
|
||||
Console.WriteLine(await agent.RunAsync("What do you already know about my upcoming trip?", thread));
|
||||
|
||||
Console.WriteLine("\n>> Serialize and deserialize the session to demonstrate persisted state\n");
|
||||
JsonElement serializedSession = session.Serialize();
|
||||
AgentSession restoredSession = await agent.DeserializeSessionAsync(serializedSession);
|
||||
Console.WriteLine(await agent.RunAsync("Can you recap the personal details you remember?", restoredSession));
|
||||
Console.WriteLine("\n>> Serialize and deserialize the thread to demonstrate persisted state\n");
|
||||
JsonElement serializedThread = thread.Serialize();
|
||||
AgentThread restoredThread = await agent.DeserializeThreadAsync(serializedThread);
|
||||
Console.WriteLine(await agent.RunAsync("Can you recap the personal details you remember?", restoredThread));
|
||||
|
||||
Console.WriteLine("\n>> Start a new session that shares the same Mem0 scope\n");
|
||||
AgentSession newSession = await agent.GetNewSessionAsync();
|
||||
Console.WriteLine(await agent.RunAsync("Summarize what you already know about me.", newSession));
|
||||
Console.WriteLine("\n>> Start a new thread that shares the same Mem0 scope\n");
|
||||
AgentThread newThread = await agent.GetNewThreadAsync();
|
||||
Console.WriteLine(await agent.RunAsync("Summarize what you already know about me.", newThread));
|
||||
|
||||
+24
-24
@@ -24,10 +24,10 @@ ChatClient chatClient = new AzureOpenAIClient(
|
||||
.GetChatClient(deploymentName);
|
||||
|
||||
// Create the agent and provide a factory to add our custom memory component to
|
||||
// all sessions created by the agent. Here each new memory component will have its own
|
||||
// user info object, so each session will have its own memory.
|
||||
// all threads created by the agent. Here each new memory component will have its own
|
||||
// user info object, so each thread will have its own memory.
|
||||
// In real world applications/services, where the user info would be persisted in a database,
|
||||
// and preferably shared between multiple sessions used by the same user, ensure that the
|
||||
// and preferably shared between multiple threads used by the same user, ensure that the
|
||||
// factory reads the user id from the current context and scopes the memory component
|
||||
// and its storage to that user id.
|
||||
AIAgent agent = chatClient.AsAIAgent(new ChatClientAgentOptions()
|
||||
@@ -36,47 +36,47 @@ AIAgent agent = chatClient.AsAIAgent(new ChatClientAgentOptions()
|
||||
AIContextProviderFactory = (ctx, ct) => new ValueTask<AIContextProvider>(new UserInfoMemory(chatClient.AsIChatClient(), ctx.SerializedState, ctx.JsonSerializerOptions))
|
||||
});
|
||||
|
||||
// Create a new session for the conversation.
|
||||
AgentSession session = await agent.GetNewSessionAsync();
|
||||
// Create a new thread for the conversation.
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
|
||||
Console.WriteLine(">> Use session with blank memory\n");
|
||||
Console.WriteLine(">> Use thread with blank memory\n");
|
||||
|
||||
// Invoke the agent and output the text result.
|
||||
Console.WriteLine(await agent.RunAsync("Hello, what is the square root of 9?", session));
|
||||
Console.WriteLine(await agent.RunAsync("My name is Ruaidhrí", session));
|
||||
Console.WriteLine(await agent.RunAsync("I am 20 years old", session));
|
||||
Console.WriteLine(await agent.RunAsync("Hello, what is the square root of 9?", thread));
|
||||
Console.WriteLine(await agent.RunAsync("My name is Ruaidhrí", thread));
|
||||
Console.WriteLine(await agent.RunAsync("I am 20 years old", thread));
|
||||
|
||||
// We can serialize the session. The serialized state will include the state of the memory component.
|
||||
var sesionElement = session.Serialize();
|
||||
// We can serialize the thread. The serialized state will include the state of the memory component.
|
||||
var threadElement = thread.Serialize();
|
||||
|
||||
Console.WriteLine("\n>> Use deserialized session with previously created memories\n");
|
||||
Console.WriteLine("\n>> Use deserialized thread with previously created memories\n");
|
||||
|
||||
// Later we can deserialize the session and continue the conversation with the previous memory component state.
|
||||
var deserializedSession = await agent.DeserializeSessionAsync(sesionElement);
|
||||
Console.WriteLine(await agent.RunAsync("What is my name and age?", deserializedSession));
|
||||
// Later we can deserialize the thread and continue the conversation with the previous memory component state.
|
||||
var deserializedThread = await agent.DeserializeThreadAsync(threadElement);
|
||||
Console.WriteLine(await agent.RunAsync("What is my name and age?", deserializedThread));
|
||||
|
||||
Console.WriteLine("\n>> Read memories from memory component\n");
|
||||
|
||||
// It's possible to access the memory component via the session's GetService method.
|
||||
var userInfo = deserializedSession.GetService<UserInfoMemory>()?.UserInfo;
|
||||
// It's possible to access the memory component via the thread's GetService method.
|
||||
var userInfo = deserializedThread.GetService<UserInfoMemory>()?.UserInfo;
|
||||
|
||||
// Output the user info that was captured by the memory component.
|
||||
Console.WriteLine($"MEMORY - User Name: {userInfo?.UserName}");
|
||||
Console.WriteLine($"MEMORY - User Age: {userInfo?.UserAge}");
|
||||
|
||||
Console.WriteLine("\n>> Use new session with previously created memories\n");
|
||||
Console.WriteLine("\n>> Use new thread with previously created memories\n");
|
||||
|
||||
// It is also possible to set the memories in a memory component on an individual session.
|
||||
// This is useful if we want to start a new session, but have it share the same memories as a previous session.
|
||||
var newSession = await agent.GetNewSessionAsync();
|
||||
if (userInfo is not null && newSession.GetService<UserInfoMemory>() is UserInfoMemory newSessionMemory)
|
||||
// It is also possible to set the memories in a memory component on an individual thread.
|
||||
// This is useful if we want to start a new thread, but have it share the same memories as a previous thread.
|
||||
var newThread = await agent.GetNewThreadAsync();
|
||||
if (userInfo is not null && newThread.GetService<UserInfoMemory>() is UserInfoMemory newThreadMemory)
|
||||
{
|
||||
newSessionMemory.UserInfo = userInfo;
|
||||
newThreadMemory.UserInfo = userInfo;
|
||||
}
|
||||
|
||||
// Invoke the agent and output the text result.
|
||||
// This time the agent should remember the user's name and use it in the response.
|
||||
Console.WriteLine(await agent.RunAsync("What is my name and age?", newSession));
|
||||
Console.WriteLine(await agent.RunAsync("What is my name and age?", newThread));
|
||||
|
||||
namespace SampleApp
|
||||
{
|
||||
|
||||
+10
-10
@@ -52,17 +52,17 @@ public class OpenAIChatClientAgent : DelegatingAIAgent
|
||||
/// Run the agent with the provided message and arguments.
|
||||
/// </summary>
|
||||
/// <param name="messages">The messages to pass to the agent.</param>
|
||||
/// <param name="session">The conversation session to continue with this invocation. If not provided, creates a new session. The session will be mutated with the provided messages and agent response.</param>
|
||||
/// <param name="thread">The conversation thread to continue with this invocation. If not provided, creates a new thread. The thread will be mutated with the provided messages and agent response.</param>
|
||||
/// <param name="options">Optional parameters for agent invocation.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A <see cref="ChatCompletion"/> containing the list of <see cref="ChatMessage"/> items.</returns>
|
||||
public virtual async Task<ChatCompletion> RunAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentSession? session = null,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var response = await this.RunAsync(messages.AsChatMessages(), session, options, cancellationToken).ConfigureAwait(false);
|
||||
var response = await this.RunAsync(messages.AsChatMessages(), thread, options, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return response.AsOpenAIChatCompletion();
|
||||
}
|
||||
@@ -71,26 +71,26 @@ public class OpenAIChatClientAgent : DelegatingAIAgent
|
||||
/// Run the agent streaming with the provided message and arguments.
|
||||
/// </summary>
|
||||
/// <param name="messages">The messages to pass to the agent.</param>
|
||||
/// <param name="session">The conversation session to continue with this invocation. If not provided, creates a new session. The session will be mutated with the provided messages and agent response.</param>
|
||||
/// <param name="thread">The conversation thread to continue with this invocation. If not provided, creates a new thread. The thread will be mutated with the provided messages and agent response.</param>
|
||||
/// <param name="options">Optional parameters for agent invocation.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A <see cref="ChatCompletion"/> containing the list of <see cref="ChatMessage"/> items.</returns>
|
||||
public virtual IAsyncEnumerable<StreamingChatCompletionUpdate> RunStreamingAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentSession? session = null,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var response = this.RunStreamingAsync(messages.AsChatMessages(), session, options, cancellationToken);
|
||||
var response = this.RunStreamingAsync(messages.AsChatMessages(), thread, options, cancellationToken);
|
||||
|
||||
return response.AsChatResponseUpdatesAsync().AsOpenAIStreamingChatCompletionUpdatesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected sealed override Task<AgentResponse> RunCoreAsync(IEnumerable<Microsoft.Extensions.AI.ChatMessage> messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) =>
|
||||
base.RunCoreAsync(messages, session, options, cancellationToken);
|
||||
protected sealed override Task<AgentResponse> RunCoreAsync(IEnumerable<Microsoft.Extensions.AI.ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) =>
|
||||
base.RunCoreAsync(messages, thread, options, cancellationToken);
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(IEnumerable<Microsoft.Extensions.AI.ChatMessage> messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) =>
|
||||
base.RunCoreStreamingAsync(messages, session, options, cancellationToken);
|
||||
protected override IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(IEnumerable<Microsoft.Extensions.AI.ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) =>
|
||||
base.RunCoreStreamingAsync(messages, thread, options, cancellationToken);
|
||||
}
|
||||
|
||||
+10
-10
@@ -52,17 +52,17 @@ public class OpenAIResponseClientAgent : DelegatingAIAgent
|
||||
/// Run the agent with the provided message and arguments.
|
||||
/// </summary>
|
||||
/// <param name="messages">The messages to pass to the agent.</param>
|
||||
/// <param name="session">The conversation session to continue with this invocation. If not provided, creates a new session. The session will be mutated with the provided messages and agent response.</param>
|
||||
/// <param name="thread">The conversation thread to continue with this invocation. If not provided, creates a new thread. The thread will be mutated with the provided messages and agent response.</param>
|
||||
/// <param name="options">Optional parameters for agent invocation.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A <see cref="ResponseResult"/> containing the list of <see cref="ChatMessage"/> items.</returns>
|
||||
public virtual async Task<ResponseResult> RunAsync(
|
||||
IEnumerable<ResponseItem> messages,
|
||||
AgentSession? session = null,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var response = await this.RunAsync(messages.AsChatMessages(), session, options, cancellationToken).ConfigureAwait(false);
|
||||
var response = await this.RunAsync(messages.AsChatMessages(), thread, options, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return response.AsOpenAIResponse();
|
||||
}
|
||||
@@ -71,17 +71,17 @@ public class OpenAIResponseClientAgent : DelegatingAIAgent
|
||||
/// Run the agent streaming with the provided message and arguments.
|
||||
/// </summary>
|
||||
/// <param name="messages">The messages to pass to the agent.</param>
|
||||
/// <param name="session">The conversation session to continue with this invocation. If not provided, creates a new session. The session will be mutated with the provided messages and agent response.</param>
|
||||
/// <param name="thread">The conversation thread to continue with this invocation. If not provided, creates a new thread. The thread will be mutated with the provided messages and agent response.</param>
|
||||
/// <param name="options">Optional parameters for agent invocation.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A <see cref="ResponseResult"/> containing the list of <see cref="ChatMessage"/> items.</returns>
|
||||
public virtual async IAsyncEnumerable<StreamingResponseUpdate> RunStreamingAsync(
|
||||
IEnumerable<ResponseItem> messages,
|
||||
AgentSession? session = null,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
var response = this.RunStreamingAsync(messages.AsChatMessages(), session, options, cancellationToken);
|
||||
var response = this.RunStreamingAsync(messages.AsChatMessages(), thread, options, cancellationToken);
|
||||
|
||||
await foreach (var update in response.ConfigureAwait(false))
|
||||
{
|
||||
@@ -105,10 +105,10 @@ public class OpenAIResponseClientAgent : DelegatingAIAgent
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected sealed override Task<AgentResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) =>
|
||||
base.RunCoreAsync(messages, session, options, cancellationToken);
|
||||
protected sealed override Task<AgentResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) =>
|
||||
base.RunCoreAsync(messages, thread, options, cancellationToken);
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected sealed override IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(IEnumerable<ChatMessage> messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) =>
|
||||
base.RunCoreStreamingAsync(messages, session, options, cancellationToken);
|
||||
protected sealed override IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) =>
|
||||
base.RunCoreStreamingAsync(messages, thread, options, cancellationToken);
|
||||
}
|
||||
|
||||
+7
-7
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This sample demonstrates how to maintain conversation state using the OpenAIResponseClientAgent
|
||||
// and AgentSession. By passing the same session to multiple agent invocations, the agent
|
||||
// and AgentThread. By passing the same thread to multiple agent invocations, the agent
|
||||
// automatically maintains the conversation history, allowing the AI model to understand
|
||||
// context from previous exchanges.
|
||||
|
||||
@@ -29,8 +29,8 @@ ClientResult createConversationResult = await conversationClient.CreateConversat
|
||||
using JsonDocument createConversationResultAsJson = JsonDocument.Parse(createConversationResult.GetRawResponse().Content.ToString());
|
||||
string conversationId = createConversationResultAsJson.RootElement.GetProperty("id"u8)!.GetString()!;
|
||||
|
||||
// Create a session for the conversation - this enables conversation state management for subsequent turns
|
||||
AgentSession session = await agent.GetNewSessionAsync(conversationId);
|
||||
// Create a thread for the conversation - this enables conversation state management for subsequent turns
|
||||
AgentThread thread = await agent.GetNewThreadAsync(conversationId);
|
||||
|
||||
Console.WriteLine("=== Multi-turn Conversation Demo ===\n");
|
||||
|
||||
@@ -38,22 +38,22 @@ Console.WriteLine("=== Multi-turn Conversation Demo ===\n");
|
||||
Console.WriteLine("User: What is the capital of France?");
|
||||
UserChatMessage firstMessage = new("What is the capital of France?");
|
||||
|
||||
// After this call, the conversation state associated in the options is stored in 'session' and used in subsequent calls
|
||||
ChatCompletion firstResponse = await agent.RunAsync([firstMessage], session);
|
||||
// After this call, the conversation state associated in the options is stored in 'thread' and used in subsequent calls
|
||||
ChatCompletion firstResponse = await agent.RunAsync([firstMessage], thread);
|
||||
Console.WriteLine($"Assistant: {firstResponse.Content.Last().Text}\n");
|
||||
|
||||
// Second turn: Follow-up question that relies on conversation context
|
||||
Console.WriteLine("User: What famous landmarks are located there?");
|
||||
UserChatMessage secondMessage = new("What famous landmarks are located there?");
|
||||
|
||||
ChatCompletion secondResponse = await agent.RunAsync([secondMessage], session);
|
||||
ChatCompletion secondResponse = await agent.RunAsync([secondMessage], thread);
|
||||
Console.WriteLine($"Assistant: {secondResponse.Content.Last().Text}\n");
|
||||
|
||||
// Third turn: Another follow-up that demonstrates context continuity
|
||||
Console.WriteLine("User: How tall is the most famous one?");
|
||||
UserChatMessage thirdMessage = new("How tall is the most famous one?");
|
||||
|
||||
ChatCompletion thirdResponse = await agent.RunAsync([thirdMessage], session);
|
||||
ChatCompletion thirdResponse = await agent.RunAsync([thirdMessage], thread);
|
||||
Console.WriteLine($"Assistant: {thirdResponse.Content.Last().Text}\n");
|
||||
|
||||
Console.WriteLine("=== End of Conversation ===");
|
||||
|
||||
+12
-12
@@ -4,7 +4,7 @@ This sample demonstrates how to maintain conversation state across multiple turn
|
||||
|
||||
## What This Sample Shows
|
||||
|
||||
- **Conversation State Management**: Shows how to use `ConversationClient` and `AgentSession` to maintain conversation context across multiple agent invocations
|
||||
- **Conversation State Management**: Shows how to use `ConversationClient` and `AgentThread` to maintain conversation context across multiple agent invocations
|
||||
- **Multi-turn Conversations**: Demonstrates follow-up questions that rely on context from previous messages in the conversation
|
||||
- **Server-Side Storage**: Uses OpenAI's Conversation API to manage conversation history server-side, allowing the model to access previous messages without resending them
|
||||
- **Conversation Lifecycle**: Demonstrates creating, retrieving, and deleting conversations
|
||||
@@ -24,22 +24,22 @@ ConversationClient conversationClient = openAIClient.GetConversationClient();
|
||||
ClientResult createConversationResult = await conversationClient.CreateConversationAsync(BinaryContent.Create(BinaryData.FromString("{}")));
|
||||
```
|
||||
|
||||
### AgentSession for Conversation State
|
||||
### AgentThread for Conversation State
|
||||
|
||||
The `AgentSession` works with `ChatClientAgentRunOptions` to link the agent to a server-side conversation:
|
||||
The `AgentThread` works with `ChatClientAgentRunOptions` to link the agent to a server-side conversation:
|
||||
|
||||
```csharp
|
||||
// Set up agent run options with the conversation ID
|
||||
ChatClientAgentRunOptions agentRunOptions = new() { ChatOptions = new ChatOptions() { ConversationId = conversationId } };
|
||||
|
||||
// Create a session for the conversation
|
||||
AgentSession session = await agent.GetNewSessionAsync();
|
||||
// Create a thread for the conversation
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
|
||||
// First call links the session to the conversation
|
||||
ChatCompletion firstResponse = await agent.RunAsync([firstMessage], session, agentRunOptions);
|
||||
// First call links the thread to the conversation
|
||||
ChatCompletion firstResponse = await agent.RunAsync([firstMessage], thread, agentRunOptions);
|
||||
|
||||
// Subsequent calls use the session without needing to pass options again
|
||||
ChatCompletion secondResponse = await agent.RunAsync([secondMessage], session);
|
||||
// Subsequent calls use the thread without needing to pass options again
|
||||
ChatCompletion secondResponse = await agent.RunAsync([secondMessage], thread);
|
||||
```
|
||||
|
||||
### Retrieving Conversation History
|
||||
@@ -59,9 +59,9 @@ foreach (ClientResult result in getConversationItemsResults.GetRawPages())
|
||||
1. **Create an OpenAI Client**: Initialize an `OpenAIClient` with your API key
|
||||
2. **Create a Conversation**: Use `ConversationClient` to create a server-side conversation
|
||||
3. **Create an Agent**: Initialize an `OpenAIResponseClientAgent` with the desired model and instructions
|
||||
4. **Create a Session**: Call `agent.GetNewSessionAsync()` to create a new conversation session
|
||||
5. **Link Session to Conversation**: Pass `ChatClientAgentRunOptions` with the `ConversationId` on the first call
|
||||
6. **Send Messages**: Subsequent calls to `agent.RunAsync()` only need the session - context is maintained
|
||||
4. **Create a Thread**: Call `agent.GetNewThreadAsync()` to create a new conversation thread
|
||||
5. **Link Thread to Conversation**: Pass `ChatClientAgentRunOptions` with the `ConversationId` on the first call
|
||||
6. **Send Messages**: Subsequent calls to `agent.RunAsync()` only need the thread - context is maintained
|
||||
7. **Cleanup**: Delete the conversation when done using `conversationClient.DeleteConversation()`
|
||||
|
||||
## Running the Sample
|
||||
|
||||
@@ -14,4 +14,4 @@ Agent Framework provides additional support to allow OpenAI developers to use th
|
||||
|[Using Reasoning Capabilities](./Agent_OpenAI_Step02_Reasoning/)|This sample demonstrates how to create an AI agent with reasoning capabilities using OpenAI's reasoning models and response types.|
|
||||
|[Creating an Agent from a ChatClient](./Agent_OpenAI_Step03_CreateFromChatClient/)|This sample demonstrates how to create an AI agent directly from an OpenAI.Chat.ChatClient instance using OpenAIChatClientAgent.|
|
||||
|[Creating an Agent from an OpenAIResponseClient](./Agent_OpenAI_Step04_CreateFromOpenAIResponseClient/)|This sample demonstrates how to create an AI agent directly from an OpenAI.Responses.OpenAIResponseClient instance using OpenAIResponseClientAgent.|
|
||||
|[Managing Conversation State](./Agent_OpenAI_Step05_Conversation/)|This sample demonstrates how to maintain conversation state across multiple turns using the AgentSession for context continuity.|
|
||||
|[Managing Conversation State](./Agent_OpenAI_Step05_Conversation/)|This sample demonstrates how to maintain conversation state across multiple turns using the AgentThread for context continuity.|
|
||||
+4
-4
@@ -70,16 +70,16 @@ AIAgent agent = azureOpenAIClient
|
||||
.WithAIContextProviderMessageRemoval()),
|
||||
});
|
||||
|
||||
AgentSession session = await agent.GetNewSessionAsync();
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
|
||||
Console.WriteLine(">> Asking about returns\n");
|
||||
Console.WriteLine(await agent.RunAsync("Hi! I need help understanding the return policy.", session));
|
||||
Console.WriteLine(await agent.RunAsync("Hi! I need help understanding the return policy.", thread));
|
||||
|
||||
Console.WriteLine("\n>> Asking about shipping\n");
|
||||
Console.WriteLine(await agent.RunAsync("How long does standard shipping usually take?", session));
|
||||
Console.WriteLine(await agent.RunAsync("How long does standard shipping usually take?", thread));
|
||||
|
||||
Console.WriteLine("\n>> Asking about product care\n");
|
||||
Console.WriteLine(await agent.RunAsync("What is the best way to maintain the TrailRunner tent fabric?", session));
|
||||
Console.WriteLine(await agent.RunAsync("What is the best way to maintain the TrailRunner tent fabric?", thread));
|
||||
|
||||
// Produces some sample search documents.
|
||||
// Each one contains a source name and link, which the agent can use to cite sources in its responses.
|
||||
|
||||
+7
-7
@@ -74,22 +74,22 @@ AIAgent agent = azureOpenAIClient
|
||||
AIContextProviderFactory = (ctx, ct) => new ValueTask<AIContextProvider>(new TextSearchProvider(SearchAdapter, ctx.SerializedState, ctx.JsonSerializerOptions, textSearchOptions))
|
||||
});
|
||||
|
||||
AgentSession session = await agent.GetNewSessionAsync();
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
|
||||
Console.WriteLine(">> Asking about SK sessions\n");
|
||||
Console.WriteLine(await agent.RunAsync("Hi! How do I create a thread/session in Semantic Kernel?", session));
|
||||
Console.WriteLine(">> Asking about SK threads\n");
|
||||
Console.WriteLine(await agent.RunAsync("Hi! How do I create a thread in Semantic Kernel?", thread));
|
||||
|
||||
// Here we are asking a very vague question when taken out of context,
|
||||
// but since we are including previous messages in our search using RecentMessageMemoryLimit
|
||||
// the RAG search should still produce useful results.
|
||||
Console.WriteLine("\n>> Asking about AF sessions\n");
|
||||
Console.WriteLine(await agent.RunAsync("and in Agent Framework?", session));
|
||||
Console.WriteLine("\n>> Asking about AF threads\n");
|
||||
Console.WriteLine(await agent.RunAsync("and in Agent Framework?", thread));
|
||||
|
||||
Console.WriteLine("\n>> Contrasting Approaches\n");
|
||||
Console.WriteLine(await agent.RunAsync("Please contrast the two approaches", session));
|
||||
Console.WriteLine(await agent.RunAsync("Please contrast the two approaches", thread));
|
||||
|
||||
Console.WriteLine("\n>> Asking about ancestry\n");
|
||||
Console.WriteLine(await agent.RunAsync("What are the predecessors to the Agent Framework?", session));
|
||||
Console.WriteLine(await agent.RunAsync("What are the predecessors to the Agent Framework?", thread));
|
||||
|
||||
static async Task UploadDataFromMarkdown(string markdownUrl, string sourceName, VectorStoreCollection<Guid, DocumentationChunk> vectorStoreCollection, int chunkSize, int overlap)
|
||||
{
|
||||
|
||||
+4
-4
@@ -32,16 +32,16 @@ AIAgent agent = new AzureOpenAIClient(
|
||||
AIContextProviderFactory = (ctx, ct) => new ValueTask<AIContextProvider>(new TextSearchProvider(MockSearchAsync, ctx.SerializedState, ctx.JsonSerializerOptions, textSearchOptions))
|
||||
});
|
||||
|
||||
AgentSession session = await agent.GetNewSessionAsync();
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
|
||||
Console.WriteLine(">> Asking about returns\n");
|
||||
Console.WriteLine(await agent.RunAsync("Hi! I need help understanding the return policy.", session));
|
||||
Console.WriteLine(await agent.RunAsync("Hi! I need help understanding the return policy.", thread));
|
||||
|
||||
Console.WriteLine("\n>> Asking about shipping\n");
|
||||
Console.WriteLine(await agent.RunAsync("How long does standard shipping usually take?", session));
|
||||
Console.WriteLine(await agent.RunAsync("How long does standard shipping usually take?", thread));
|
||||
|
||||
Console.WriteLine("\n>> Asking about product care\n");
|
||||
Console.WriteLine(await agent.RunAsync("What is the best way to maintain the TrailRunner tent fabric?", session));
|
||||
Console.WriteLine(await agent.RunAsync("What is the best way to maintain the TrailRunner tent fabric?", thread));
|
||||
|
||||
static Task<IEnumerable<TextSearchProvider.TextSearchResult>> MockSearchAsync(string query, CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
+4
-4
@@ -43,16 +43,16 @@ AIAgent agent = await aiProjectClient
|
||||
instructions: "You are a helpful support specialist for Contoso Outdoors. Answer questions using the provided context and cite the source document when available.",
|
||||
tools: [fileSearchTool]);
|
||||
|
||||
AgentSession session = await agent.GetNewSessionAsync();
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
|
||||
Console.WriteLine(">> Asking about returns\n");
|
||||
Console.WriteLine(await agent.RunAsync("Hi! I need help understanding the return policy.", session));
|
||||
Console.WriteLine(await agent.RunAsync("Hi! I need help understanding the return policy.", thread));
|
||||
|
||||
Console.WriteLine("\n>> Asking about shipping\n");
|
||||
Console.WriteLine(await agent.RunAsync("How long does standard shipping usually take?", session));
|
||||
Console.WriteLine(await agent.RunAsync("How long does standard shipping usually take?", thread));
|
||||
|
||||
Console.WriteLine("\n>> Asking about product care\n");
|
||||
Console.WriteLine(await agent.RunAsync("What is the best way to maintain the TrailRunner tent fabric?", session));
|
||||
Console.WriteLine(await agent.RunAsync("What is the best way to maintain the TrailRunner tent fabric?", thread));
|
||||
|
||||
// Cleanup
|
||||
await fileClient.DeleteFileAsync(uploadResult.Value.Id);
|
||||
|
||||
@@ -16,18 +16,18 @@ AIAgent agent = new AzureOpenAIClient(
|
||||
.GetChatClient(deploymentName)
|
||||
.AsAIAgent(instructions: "You are good at telling jokes.", name: "Joker");
|
||||
|
||||
// Invoke the agent with a multi-turn conversation, where the context is preserved in the session object.
|
||||
AgentSession session = await agent.GetNewSessionAsync();
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", session));
|
||||
Console.WriteLine(await agent.RunAsync("Now add some emojis to the joke and tell it in the voice of a pirate's parrot.", session));
|
||||
// Invoke the agent with a multi-turn conversation, where the context is preserved in the thread object.
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", thread));
|
||||
Console.WriteLine(await agent.RunAsync("Now add some emojis to the joke and tell it in the voice of a pirate's parrot.", thread));
|
||||
|
||||
// Invoke the agent with a multi-turn conversation and streaming, where the context is preserved in the session object.
|
||||
session = await agent.GetNewSessionAsync();
|
||||
await foreach (var update in agent.RunStreamingAsync("Tell me a joke about a pirate.", session))
|
||||
// Invoke the agent with a multi-turn conversation and streaming, where the context is preserved in the thread object.
|
||||
thread = await agent.GetNewThreadAsync();
|
||||
await foreach (var update in agent.RunStreamingAsync("Tell me a joke about a pirate.", thread))
|
||||
{
|
||||
Console.WriteLine(update);
|
||||
}
|
||||
await foreach (var update in agent.RunStreamingAsync("Now add some emojis to the joke and tell it in the voice of a pirate's parrot.", session))
|
||||
await foreach (var update in agent.RunStreamingAsync("Now add some emojis to the joke and tell it in the voice of a pirate's parrot.", thread))
|
||||
{
|
||||
Console.WriteLine(update);
|
||||
}
|
||||
|
||||
+5
-5
@@ -30,12 +30,12 @@ AIAgent agent = new AzureOpenAIClient(
|
||||
.AsAIAgent(instructions: "You are a helpful assistant", tools: [new ApprovalRequiredAIFunction(AIFunctionFactory.Create(GetWeather))]);
|
||||
|
||||
// Call the agent and check if there are any user input requests to handle.
|
||||
AgentSession session = await agent.GetNewSessionAsync();
|
||||
var response = await agent.RunAsync("What is the weather like in Amsterdam?", session);
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
var response = await agent.RunAsync("What is the weather like in Amsterdam?", thread);
|
||||
var userInputRequests = response.UserInputRequests.ToList();
|
||||
|
||||
// For streaming use:
|
||||
// var updates = await agent.RunStreamingAsync("What is the weather like in Amsterdam?", session).ToListAsync();
|
||||
// var updates = await agent.RunStreamingAsync("What is the weather like in Amsterdam?", thread).ToListAsync();
|
||||
// userInputRequests = updates.SelectMany(x => x.UserInputRequests).ToList();
|
||||
|
||||
while (userInputRequests.Count > 0)
|
||||
@@ -52,12 +52,12 @@ while (userInputRequests.Count > 0)
|
||||
.ToList();
|
||||
|
||||
// Pass the user input responses back to the agent for further processing.
|
||||
response = await agent.RunAsync(userInputResponses, session);
|
||||
response = await agent.RunAsync(userInputResponses, thread);
|
||||
|
||||
userInputRequests = response.UserInputRequests.ToList();
|
||||
|
||||
// For streaming use:
|
||||
// updates = await agent.RunStreamingAsync(userInputResponses, session).ToListAsync();
|
||||
// updates = await agent.RunStreamingAsync(userInputResponses, thread).ToListAsync();
|
||||
// userInputRequests = updates.SelectMany(x => x.UserInputRequests).ToList();
|
||||
}
|
||||
|
||||
|
||||
+14
-14
@@ -18,24 +18,24 @@ AIAgent agent = new AzureOpenAIClient(
|
||||
.GetChatClient(deploymentName)
|
||||
.AsAIAgent(instructions: "You are good at telling jokes.", name: "Joker");
|
||||
|
||||
// Start a new session for the agent conversation.
|
||||
AgentSession session = await agent.GetNewSessionAsync();
|
||||
// Start a new thread for the agent conversation.
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
|
||||
// Run the agent with a new session.
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", session));
|
||||
// Run the agent with a new thread.
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", thread));
|
||||
|
||||
// Serialize the session state to a JsonElement, so it can be stored for later use.
|
||||
JsonElement serializedSession = session.Serialize();
|
||||
// Serialize the thread state to a JsonElement, so it can be stored for later use.
|
||||
JsonElement serializedThread = thread.Serialize();
|
||||
|
||||
// Save the serialized session to a temporary file (for demonstration purposes).
|
||||
// Save the serialized thread to a temporary file (for demonstration purposes).
|
||||
string tempFilePath = Path.GetTempFileName();
|
||||
await File.WriteAllTextAsync(tempFilePath, JsonSerializer.Serialize(serializedSession));
|
||||
await File.WriteAllTextAsync(tempFilePath, JsonSerializer.Serialize(serializedThread));
|
||||
|
||||
// Load the serialized session from the temporary file (for demonstration purposes).
|
||||
JsonElement reloadedSerializedSession = JsonElement.Parse(await File.ReadAllTextAsync(tempFilePath));
|
||||
// Load the serialized thread from the temporary file (for demonstration purposes).
|
||||
JsonElement reloadedSerializedThread = JsonElement.Parse(await File.ReadAllTextAsync(tempFilePath));
|
||||
|
||||
// Deserialize the session state after loading from storage.
|
||||
AgentSession resumedSession = await agent.DeserializeSessionAsync(reloadedSerializedSession);
|
||||
// Deserialize the thread state after loading from storage.
|
||||
AgentThread resumedThread = await agent.DeserializeThreadAsync(reloadedSerializedThread);
|
||||
|
||||
// Run the agent again with the resumed session.
|
||||
Console.WriteLine(await agent.RunAsync("Now tell the same joke in the voice of a pirate, and add some emojis to the joke.", resumedSession));
|
||||
// Run the agent again with the resumed thread.
|
||||
Console.WriteLine(await agent.RunAsync("Now tell the same joke in the voice of a pirate, and add some emojis to the joke.", resumedThread));
|
||||
|
||||
+28
-28
@@ -33,37 +33,37 @@ AIAgent agent = new AzureOpenAIClient(
|
||||
Name = "Joker",
|
||||
ChatHistoryProviderFactory = (ctx, ct) => new ValueTask<ChatHistoryProvider>(
|
||||
// Create a new ChatHistoryProvider for this agent that stores chat history in a vector store.
|
||||
// Each session must get its own copy of the VectorChatHistoryProvider, since the provider
|
||||
// Each thread must get its own copy of the VectorChatHistoryProvider, since the provider
|
||||
// also contains the id that the chat history is stored under.
|
||||
new VectorChatHistoryProvider(vectorStore, ctx.SerializedState, ctx.JsonSerializerOptions))
|
||||
});
|
||||
|
||||
// Start a new session for the agent conversation.
|
||||
AgentSession session = await agent.GetNewSessionAsync();
|
||||
// Start a new thread for the agent conversation.
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
|
||||
// Run the agent with the session that stores chat history in the vector store.
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", session));
|
||||
// Run the agent with the thread that stores chat history in the vector store.
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", thread));
|
||||
|
||||
// Serialize the session state, so it can be stored for later use.
|
||||
// Since the chat history is stored in the vector store, the serialized session
|
||||
// Serialize the thread state, so it can be stored for later use.
|
||||
// Since the chat history is stored in the vector store, the serialized thread
|
||||
// only contains the guid that the messages are stored under in the vector store.
|
||||
JsonElement serializedSession = session.Serialize();
|
||||
JsonElement serializedThread = thread.Serialize();
|
||||
|
||||
Console.WriteLine("\n--- Serialized session ---\n");
|
||||
Console.WriteLine(JsonSerializer.Serialize(serializedSession, new JsonSerializerOptions { WriteIndented = true }));
|
||||
Console.WriteLine("\n--- Serialized thread ---\n");
|
||||
Console.WriteLine(JsonSerializer.Serialize(serializedThread, new JsonSerializerOptions { WriteIndented = true }));
|
||||
|
||||
// The serialized session can now be saved to a database, file, or any other storage mechanism
|
||||
// The serialized thread can now be saved to a database, file, or any other storage mechanism
|
||||
// and loaded again later.
|
||||
|
||||
// Deserialize the session state after loading from storage.
|
||||
AgentSession resumedSession = await agent.DeserializeSessionAsync(serializedSession);
|
||||
// Deserialize the thread state after loading from storage.
|
||||
AgentThread resumedThread = await agent.DeserializeThreadAsync(serializedThread);
|
||||
|
||||
// Run the agent with the session that stores chat history in the vector store a second time.
|
||||
Console.WriteLine(await agent.RunAsync("Now tell the same joke in the voice of a pirate, and add some emojis to the joke.", resumedSession));
|
||||
// Run the agent with the thread that stores chat history in the vector store a second time.
|
||||
Console.WriteLine(await agent.RunAsync("Now tell the same joke in the voice of a pirate, and add some emojis to the joke.", resumedThread));
|
||||
|
||||
// We can access the VectorChatHistoryProvider via the session's GetService method if we need to read the key under which chat history is stored.
|
||||
var chatHistoryProvider = resumedSession.GetService<VectorChatHistoryProvider>()!;
|
||||
Console.WriteLine($"\nSession is stored in vector store under key: {chatHistoryProvider.SessionDbKey}");
|
||||
// We can access the VectorChatHistoryProvider via the thread's GetService method if we need to read the key under which chat history is stored.
|
||||
var chatHistoryProvider = resumedThread.GetService<VectorChatHistoryProvider>()!;
|
||||
Console.WriteLine($"\nThread is stored in vector store under key: {chatHistoryProvider.ThreadDbKey}");
|
||||
|
||||
namespace SampleApp
|
||||
{
|
||||
@@ -80,12 +80,12 @@ namespace SampleApp
|
||||
|
||||
if (serializedState.ValueKind is JsonValueKind.String)
|
||||
{
|
||||
// Here we can deserialize the session id so that we can access the same messages as before the suspension.
|
||||
this.SessionDbKey = serializedState.Deserialize<string>();
|
||||
// Here we can deserialize the thread id so that we can access the same messages as before the suspension.
|
||||
this.ThreadDbKey = serializedState.Deserialize<string>();
|
||||
}
|
||||
}
|
||||
|
||||
public string? SessionDbKey { get; private set; }
|
||||
public string? ThreadDbKey { get; private set; }
|
||||
|
||||
public override async ValueTask<IEnumerable<ChatMessage>> InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -94,7 +94,7 @@ namespace SampleApp
|
||||
|
||||
var records = await collection
|
||||
.GetAsync(
|
||||
x => x.SessionId == this.SessionDbKey, 10,
|
||||
x => x.ThreadId == this.ThreadDbKey, 10,
|
||||
new() { OrderBy = x => x.Descending(y => y.Timestamp) },
|
||||
cancellationToken)
|
||||
.ToListAsync(cancellationToken);
|
||||
@@ -113,7 +113,7 @@ namespace SampleApp
|
||||
return;
|
||||
}
|
||||
|
||||
this.SessionDbKey ??= Guid.NewGuid().ToString("N");
|
||||
this.ThreadDbKey ??= Guid.NewGuid().ToString("N");
|
||||
|
||||
var collection = this._vectorStore.GetCollection<string, ChatHistoryItem>("ChatHistory");
|
||||
await collection.EnsureCollectionExistsAsync(cancellationToken);
|
||||
@@ -124,17 +124,17 @@ namespace SampleApp
|
||||
|
||||
await collection.UpsertAsync(allNewMessages.Select(x => new ChatHistoryItem()
|
||||
{
|
||||
Key = this.SessionDbKey + x.MessageId,
|
||||
Key = this.ThreadDbKey + x.MessageId,
|
||||
Timestamp = DateTimeOffset.UtcNow,
|
||||
SessionId = this.SessionDbKey,
|
||||
ThreadId = this.ThreadDbKey,
|
||||
SerializedMessage = JsonSerializer.Serialize(x),
|
||||
MessageText = x.Text
|
||||
}), cancellationToken);
|
||||
}
|
||||
|
||||
public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null) =>
|
||||
// We have to serialize the session id, so that on deserialization we can retrieve the messages using the same session id.
|
||||
JsonSerializer.SerializeToElement(this.SessionDbKey);
|
||||
// We have to serialize the thread id, so that on deserialization we can retrieve the messages using the same thread id.
|
||||
JsonSerializer.SerializeToElement(this.ThreadDbKey);
|
||||
|
||||
/// <summary>
|
||||
/// The data structure used to store chat history items in the vector store.
|
||||
@@ -145,7 +145,7 @@ namespace SampleApp
|
||||
public string? Key { get; set; }
|
||||
|
||||
[VectorStoreData]
|
||||
public string? SessionId { get; set; }
|
||||
public string? ThreadId { get; set; }
|
||||
|
||||
[VectorStoreData]
|
||||
public DateTimeOffset? Timestamp { get; set; }
|
||||
|
||||
@@ -44,12 +44,12 @@ await host.RunAsync().ConfigureAwait(false);
|
||||
/// </summary>
|
||||
internal sealed class SampleService(AIAgent agent, IHostApplicationLifetime appLifetime) : IHostedService
|
||||
{
|
||||
private AgentSession? _session;
|
||||
private AgentThread? _thread;
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
// Create a session that will be used for the entirety of the service lifetime so that the user can ask follow up questions.
|
||||
this._session = await agent.GetNewSessionAsync(cancellationToken);
|
||||
// Create a thread that will be used for the entirety of the service lifetime so that the user can ask follow up questions.
|
||||
this._thread = await agent.GetNewThreadAsync(cancellationToken);
|
||||
_ = this.RunAsync(appLifetime.ApplicationStopping);
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ internal sealed class SampleService(AIAgent agent, IHostApplicationLifetime appL
|
||||
}
|
||||
|
||||
// Stream the output to the console as it is generated.
|
||||
await foreach (var update in agent.RunStreamingAsync(input, this._session, cancellationToken: cancellationToken))
|
||||
await foreach (var update in agent.RunStreamingAsync(input, this._thread, cancellationToken: cancellationToken))
|
||||
{
|
||||
Console.Write(update);
|
||||
}
|
||||
|
||||
@@ -22,9 +22,9 @@ ChatMessage message = new(ChatRole.User, [
|
||||
new UriContent("https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg", "image/jpeg")
|
||||
]);
|
||||
|
||||
var session = await agent.GetNewSessionAsync();
|
||||
var thread = await agent.GetNewThreadAsync();
|
||||
|
||||
await foreach (var update in agent.RunStreamingAsync(message, session))
|
||||
await foreach (var update in agent.RunStreamingAsync(message, thread))
|
||||
{
|
||||
Console.WriteLine(update);
|
||||
}
|
||||
|
||||
+11
-11
@@ -32,41 +32,41 @@ AIAgent agent = new AzureOpenAIClient(
|
||||
// Enable background responses (only supported by {Azure}OpenAI Responses at this time).
|
||||
AgentRunOptions options = new() { AllowBackgroundResponses = true };
|
||||
|
||||
AgentSession session = await agent.GetNewSessionAsync();
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
|
||||
// Start the initial run.
|
||||
AgentResponse response = await agent.RunAsync("Write a very long novel about a team of astronauts exploring an uncharted galaxy.", session, options);
|
||||
AgentResponse response = await agent.RunAsync("Write a very long novel about a team of astronauts exploring an uncharted galaxy.", thread, options);
|
||||
|
||||
// Poll for background responses until complete.
|
||||
while (response.ContinuationToken is not null)
|
||||
{
|
||||
PersistAgentState(session, response.ContinuationToken);
|
||||
PersistAgentState(thread, response.ContinuationToken);
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(10));
|
||||
|
||||
var (restoredSession, continuationToken) = await RestoreAgentState(agent);
|
||||
var (restoredThread, continuationToken) = await RestoreAgentState(agent);
|
||||
|
||||
options.ContinuationToken = continuationToken;
|
||||
response = await agent.RunAsync(restoredSession, options);
|
||||
response = await agent.RunAsync(restoredThread, options);
|
||||
}
|
||||
|
||||
Console.WriteLine(response.Text);
|
||||
|
||||
void PersistAgentState(AgentSession? session, ResponseContinuationToken? continuationToken)
|
||||
void PersistAgentState(AgentThread thread, ResponseContinuationToken? continuationToken)
|
||||
{
|
||||
stateStore["session"] = session!.Serialize();
|
||||
stateStore["thread"] = thread.Serialize();
|
||||
stateStore["continuationToken"] = JsonSerializer.SerializeToElement(continuationToken, AgentAbstractionsJsonUtilities.DefaultOptions.GetTypeInfo(typeof(ResponseContinuationToken)));
|
||||
}
|
||||
|
||||
async Task<(AgentSession Session, ResponseContinuationToken? ContinuationToken)> RestoreAgentState(AIAgent agent)
|
||||
async Task<(AgentThread Thread, ResponseContinuationToken? ContinuationToken)> RestoreAgentState(AIAgent agent)
|
||||
{
|
||||
JsonElement serializedSession = stateStore["session"] ?? throw new InvalidOperationException("No serialized session found in state store.");
|
||||
JsonElement serializedThread = stateStore["thread"] ?? throw new InvalidOperationException("No serialized thread found in state store.");
|
||||
JsonElement? serializedToken = stateStore["continuationToken"];
|
||||
|
||||
AgentSession session = await agent.DeserializeSessionAsync(serializedSession);
|
||||
AgentThread thread = await agent.DeserializeThreadAsync(serializedThread);
|
||||
ResponseContinuationToken? continuationToken = (ResponseContinuationToken?)serializedToken?.Deserialize(AgentAbstractionsJsonUtilities.DefaultOptions.GetTypeInfo(typeof(ResponseContinuationToken)));
|
||||
|
||||
return (session, continuationToken);
|
||||
return (thread, continuationToken);
|
||||
}
|
||||
|
||||
[Description("Researches relevant space facts and scientific information for writing a science fiction novel")]
|
||||
|
||||
@@ -45,7 +45,7 @@ var middlewareEnabledAgent = originalAgent
|
||||
.Use(GuardrailMiddleware, null)
|
||||
.Build();
|
||||
|
||||
var session = await middlewareEnabledAgent.GetNewSessionAsync();
|
||||
var thread = await middlewareEnabledAgent.GetNewThreadAsync();
|
||||
|
||||
Console.WriteLine("\n\n=== Example 1: Wording Guardrail ===");
|
||||
var guardRailedResponse = await middlewareEnabledAgent.RunAsync("Tell me something harmful.");
|
||||
@@ -65,7 +65,7 @@ var options = new ChatClientAgentRunOptions(new()
|
||||
Tools = [AIFunctionFactory.Create(GetWeather, name: nameof(GetWeather))]
|
||||
});
|
||||
|
||||
var functionCallResponse = await middlewareEnabledAgent.RunAsync("What's the current time and the weather in Seattle?", session, options);
|
||||
var functionCallResponse = await middlewareEnabledAgent.RunAsync("What's the current time and the weather in Seattle?", thread, options);
|
||||
Console.WriteLine($"Function calling response: {functionCallResponse}");
|
||||
|
||||
// Special per-request middleware agent.
|
||||
@@ -89,7 +89,7 @@ var response = await originalAgent // Using per-request middleware pipeline with
|
||||
.Use(PerRequestFunctionCallingMiddleware)
|
||||
.Use(ConsolePromptingApprovalMiddleware, null)
|
||||
.Build()
|
||||
.RunAsync("What's the current time and the weather in Seattle?", session, optionsWithApproval);
|
||||
.RunAsync("What's the current time and the weather in Seattle?", thread, optionsWithApproval);
|
||||
|
||||
Console.WriteLine($"Per-request middleware response: {response}");
|
||||
|
||||
@@ -131,13 +131,13 @@ async ValueTask<object?> PerRequestFunctionCallingMiddleware(AIAgent agent, Func
|
||||
}
|
||||
|
||||
// This middleware redacts PII information from input and output messages.
|
||||
async Task<AgentResponse> PIIMiddleware(IEnumerable<ChatMessage> messages, AgentSession? session, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken)
|
||||
async Task<AgentResponse> PIIMiddleware(IEnumerable<ChatMessage> messages, AgentThread? thread, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken)
|
||||
{
|
||||
// Redact PII information from input messages
|
||||
var filteredMessages = FilterMessages(messages);
|
||||
Console.WriteLine("Pii Middleware - Filtered Messages Pre-Run");
|
||||
|
||||
var response = await innerAgent.RunAsync(filteredMessages, session, options, cancellationToken).ConfigureAwait(false);
|
||||
var response = await innerAgent.RunAsync(filteredMessages, thread, options, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Redact PII information from output messages
|
||||
response.Messages = FilterMessages(response.Messages);
|
||||
@@ -171,7 +171,7 @@ async Task<AgentResponse> PIIMiddleware(IEnumerable<ChatMessage> messages, Agent
|
||||
}
|
||||
|
||||
// This middleware enforces guardrails by redacting certain keywords from input and output messages.
|
||||
async Task<AgentResponse> GuardrailMiddleware(IEnumerable<ChatMessage> messages, AgentSession? session, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken)
|
||||
async Task<AgentResponse> GuardrailMiddleware(IEnumerable<ChatMessage> messages, AgentThread? thread, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken)
|
||||
{
|
||||
// Redact keywords from input messages
|
||||
var filteredMessages = FilterMessages(messages);
|
||||
@@ -179,7 +179,7 @@ async Task<AgentResponse> GuardrailMiddleware(IEnumerable<ChatMessage> messages,
|
||||
Console.WriteLine("Guardrail Middleware - Filtered messages Pre-Run");
|
||||
|
||||
// Proceed with the agent run
|
||||
var response = await innerAgent.RunAsync(filteredMessages, session, options, cancellationToken);
|
||||
var response = await innerAgent.RunAsync(filteredMessages, thread, options, cancellationToken);
|
||||
|
||||
// Redact keywords from output messages
|
||||
response.Messages = FilterMessages(response.Messages);
|
||||
@@ -208,9 +208,9 @@ async Task<AgentResponse> GuardrailMiddleware(IEnumerable<ChatMessage> messages,
|
||||
}
|
||||
|
||||
// This middleware handles Human in the loop console interaction for any user approval required during function calling.
|
||||
async Task<AgentResponse> ConsolePromptingApprovalMiddleware(IEnumerable<ChatMessage> messages, AgentSession? session, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken)
|
||||
async Task<AgentResponse> ConsolePromptingApprovalMiddleware(IEnumerable<ChatMessage> messages, AgentThread? thread, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken)
|
||||
{
|
||||
var response = await innerAgent.RunAsync(messages, session, options, cancellationToken);
|
||||
var response = await innerAgent.RunAsync(messages, thread, options, cancellationToken);
|
||||
|
||||
var userInputRequests = response.UserInputRequests.ToList();
|
||||
|
||||
@@ -229,7 +229,7 @@ async Task<AgentResponse> ConsolePromptingApprovalMiddleware(IEnumerable<ChatMes
|
||||
})
|
||||
.ToList();
|
||||
|
||||
response = await innerAgent.RunAsync(response.Messages, session, options, cancellationToken);
|
||||
response = await innerAgent.RunAsync(response.Messages, thread, options, cancellationToken);
|
||||
|
||||
userInputRequests = response.UserInputRequests.ToList();
|
||||
}
|
||||
|
||||
@@ -27,23 +27,23 @@ AIAgent agent = new AzureOpenAIClient(
|
||||
ChatHistoryProviderFactory = (ctx, ct) => new ValueTask<ChatHistoryProvider>(new InMemoryChatHistoryProvider(new MessageCountingChatReducer(2), ctx.SerializedState, ctx.JsonSerializerOptions))
|
||||
});
|
||||
|
||||
AgentSession session = await agent.GetNewSessionAsync();
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
|
||||
// Invoke the agent and output the text result.
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", session));
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", thread));
|
||||
|
||||
// Get the chat history to see how many messages are stored.
|
||||
IList<ChatMessage>? chatHistory = session.GetService<IList<ChatMessage>>();
|
||||
IList<ChatMessage>? chatHistory = thread.GetService<IList<ChatMessage>>();
|
||||
Console.WriteLine($"\nChat history has {chatHistory?.Count} messages.\n");
|
||||
|
||||
// Invoke the agent a few more times.
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a robot.", session));
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a robot.", thread));
|
||||
Console.WriteLine($"\nChat history has {chatHistory?.Count} messages.\n");
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a lemur.", session));
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a lemur.", thread));
|
||||
Console.WriteLine($"\nChat history has {chatHistory?.Count} messages.\n");
|
||||
|
||||
// At this point, the chat history has exceeded the limit and the original message will not exist anymore,
|
||||
// so asking a follow up question about it will not work as expected.
|
||||
Console.WriteLine(await agent.RunAsync("Tell me the joke about the pirate again, but add emojis and use the voice of a parrot.", session));
|
||||
Console.WriteLine(await agent.RunAsync("Tell me the joke about the pirate again, but add emojis and use the voice of a parrot.", thread));
|
||||
|
||||
Console.WriteLine($"\nChat history has {chatHistory?.Count} messages.\n");
|
||||
|
||||
@@ -19,10 +19,10 @@ AIAgent agent = new AzureOpenAIClient(
|
||||
// Enable background responses (only supported by OpenAI Responses at this time).
|
||||
AgentRunOptions options = new() { AllowBackgroundResponses = true };
|
||||
|
||||
AgentSession session = await agent.GetNewSessionAsync();
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
|
||||
// Start the initial run.
|
||||
AgentResponse response = await agent.RunAsync("Write a very long novel about otters in space.", session, options);
|
||||
AgentResponse response = await agent.RunAsync("Write a very long novel about otters in space.", thread, options);
|
||||
|
||||
// Poll until the response is complete.
|
||||
while (response.ContinuationToken is { } token)
|
||||
@@ -33,19 +33,19 @@ while (response.ContinuationToken is { } token)
|
||||
// Continue with the token.
|
||||
options.ContinuationToken = token;
|
||||
|
||||
response = await agent.RunAsync(session, options);
|
||||
response = await agent.RunAsync(thread, options);
|
||||
}
|
||||
|
||||
// Display the result.
|
||||
Console.WriteLine(response.Text);
|
||||
|
||||
// Reset options and session for streaming.
|
||||
// Reset options and thread for streaming.
|
||||
options = new() { AllowBackgroundResponses = true };
|
||||
session = await agent.GetNewSessionAsync();
|
||||
thread = await agent.GetNewThreadAsync();
|
||||
|
||||
AgentResponseUpdate? lastReceivedUpdate = null;
|
||||
// Start streaming.
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync("Write a very long novel about otters in space.", session, options))
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync("Write a very long novel about otters in space.", thread, options))
|
||||
{
|
||||
// Output each update.
|
||||
Console.Write(update.Text);
|
||||
@@ -63,7 +63,7 @@ await foreach (AgentResponseUpdate update in agent.RunStreamingAsync("Write a ve
|
||||
// Resume from interruption point.
|
||||
options.ContinuationToken = lastReceivedUpdate?.ContinuationToken;
|
||||
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(session, options))
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(thread, options))
|
||||
{
|
||||
// Output each update.
|
||||
Console.Write(update.Text);
|
||||
|
||||
@@ -39,9 +39,9 @@ Console.WriteLine();
|
||||
|
||||
try
|
||||
{
|
||||
AgentSession session = await agent.GetNewSessionAsync();
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
|
||||
await foreach (var response in agent.RunStreamingAsync(Task, session))
|
||||
await foreach (var response in agent.RunStreamingAsync(Task, thread))
|
||||
{
|
||||
Console.Write(response.Text);
|
||||
}
|
||||
|
||||
@@ -58,20 +58,20 @@ AIAgent agent = new AzureOpenAIClient(
|
||||
});
|
||||
|
||||
// Invoke the agent and output the text result.
|
||||
AgentSession session = await agent.GetNewSessionAsync();
|
||||
Console.WriteLine(await agent.RunAsync("I need to pick up milk from the supermarket.", session) + "\n");
|
||||
Console.WriteLine(await agent.RunAsync("I need to take Sally for soccer practice.", session) + "\n");
|
||||
Console.WriteLine(await agent.RunAsync("I need to make a dentist appointment for Jimmy.", session) + "\n");
|
||||
Console.WriteLine(await agent.RunAsync("I've taken Sally to soccer practice.", session) + "\n");
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
Console.WriteLine(await agent.RunAsync("I need to pick up milk from the supermarket.", thread) + "\n");
|
||||
Console.WriteLine(await agent.RunAsync("I need to take Sally for soccer practice.", thread) + "\n");
|
||||
Console.WriteLine(await agent.RunAsync("I need to make a dentist appointment for Jimmy.", thread) + "\n");
|
||||
Console.WriteLine(await agent.RunAsync("I've taken Sally to soccer practice.", thread) + "\n");
|
||||
|
||||
// We can serialize the session, and it will contain both the chat history and the data that each AI context provider serialized.
|
||||
JsonElement serializedSession = session.Serialize();
|
||||
// We can serialize the thread, and it will contain both the chat history and the data that each AI context provider serialized.
|
||||
JsonElement serializedThread = thread.Serialize();
|
||||
// Let's print it to console to show the contents.
|
||||
Console.WriteLine(JsonSerializer.Serialize(serializedSession, options: new JsonSerializerOptions() { WriteIndented = true, IndentSize = 2 }) + "\n");
|
||||
// The serialized session can be stored long term in a persistent store, but in this case we will just deserialize again and continue the conversation.
|
||||
session = await agent.DeserializeSessionAsync(serializedSession);
|
||||
Console.WriteLine(JsonSerializer.Serialize(serializedThread, options: new JsonSerializerOptions() { WriteIndented = true, IndentSize = 2 }) + "\n");
|
||||
// The serialized thread can be stored long term in a persistent store, but in this case we will just deserialize again and continue the conversation.
|
||||
thread = await agent.DeserializeThreadAsync(serializedThread);
|
||||
|
||||
Console.WriteLine(await agent.RunAsync("Considering my appointments, can you create a plan for my day that plans out when I should complete the items on my todo list?", session) + "\n");
|
||||
Console.WriteLine(await agent.RunAsync("Considering my appointments, can you create a plan for my day that plans out when I should complete the items on my todo list?", thread) + "\n");
|
||||
|
||||
namespace SampleApp
|
||||
{
|
||||
|
||||
+9
-9
@@ -22,25 +22,25 @@ AgentVersionCreationOptions options = new(new PromptAgentDefinition(model: deplo
|
||||
// Retrieve an AIAgent for the created server side agent version.
|
||||
ChatClientAgent jokerAgent = await aiProjectClient.CreateAIAgentAsync(name: JokerName, options);
|
||||
|
||||
// Invoke the agent with a multi-turn conversation, where the context is preserved in the session object.
|
||||
// Invoke the agent with a multi-turn conversation, where the context is preserved in the thread object.
|
||||
// Create a conversation in the server
|
||||
ProjectConversationsClient conversationsClient = aiProjectClient.GetProjectOpenAIClient().GetProjectConversationsClient();
|
||||
ProjectConversation conversation = await conversationsClient.CreateProjectConversationAsync();
|
||||
|
||||
// Providing the conversation Id is not strictly necessary, but by not providing it no information will show up in the Foundry Project UI as conversations.
|
||||
// Sessions that don't have a conversation Id will work based on the `PreviousResponseId`.
|
||||
AgentSession session = await jokerAgent.GetNewSessionAsync(conversation.Id);
|
||||
// Threads that doesn't have a conversation Id will work based on the `PreviousResponseId`.
|
||||
AgentThread thread = await jokerAgent.GetNewThreadAsync(conversation.Id);
|
||||
|
||||
Console.WriteLine(await jokerAgent.RunAsync("Tell me a joke about a pirate.", session));
|
||||
Console.WriteLine(await jokerAgent.RunAsync("Now add some emojis to the joke and tell it in the voice of a pirate's parrot.", session));
|
||||
Console.WriteLine(await jokerAgent.RunAsync("Tell me a joke about a pirate.", thread));
|
||||
Console.WriteLine(await jokerAgent.RunAsync("Now add some emojis to the joke and tell it in the voice of a pirate's parrot.", thread));
|
||||
|
||||
// Invoke the agent with a multi-turn conversation and streaming, where the context is preserved in the session object.
|
||||
session = await jokerAgent.GetNewSessionAsync(conversation.Id);
|
||||
await foreach (AgentResponseUpdate update in jokerAgent.RunStreamingAsync("Tell me a joke about a pirate.", session))
|
||||
// Invoke the agent with a multi-turn conversation and streaming, where the context is preserved in the thread object.
|
||||
thread = await jokerAgent.GetNewThreadAsync(conversation.Id);
|
||||
await foreach (AgentResponseUpdate update in jokerAgent.RunStreamingAsync("Tell me a joke about a pirate.", thread))
|
||||
{
|
||||
Console.WriteLine(update);
|
||||
}
|
||||
await foreach (AgentResponseUpdate update in jokerAgent.RunStreamingAsync("Now add some emojis to the joke and tell it in the voice of a pirate's parrot.", session))
|
||||
await foreach (AgentResponseUpdate update in jokerAgent.RunStreamingAsync("Now add some emojis to the joke and tell it in the voice of a pirate's parrot.", thread))
|
||||
{
|
||||
Console.WriteLine(update);
|
||||
}
|
||||
|
||||
+2
-2
@@ -54,6 +54,6 @@ The sample will:
|
||||
|
||||
When working with multi-turn conversations, there are two approaches:
|
||||
|
||||
- **With Conversation ID**: By passing a `conversation.Id` to `GetNewSessionAsync()`, the conversation will be visible in the Azure Foundry Project UI. This is useful for tracking and debugging conversations.
|
||||
- **Without Conversation ID**: Sessions created without a conversation ID still work correctly, maintaining context via `PreviousResponseId`. However, these conversations may not appear in the Foundry UI.
|
||||
- **With Conversation ID**: By passing a `conversation.Id` to `GetNewThreadAsync()`, the conversation will be visible in the Azure Foundry Project UI. This is useful for tracking and debugging conversations.
|
||||
- **Without Conversation ID**: Threads created without a conversation ID still work correctly, maintaining context via `PreviousResponseId`. However, these conversations may not appear in the Foundry UI.
|
||||
|
||||
|
||||
+4
-4
@@ -37,12 +37,12 @@ var newAgent = await aiProjectClient.CreateAIAgentAsync(name: AssistantName, mod
|
||||
var existingAgent = await aiProjectClient.GetAIAgentAsync(name: AssistantName, tools: [tool]);
|
||||
|
||||
// Non-streaming agent interaction with function tools.
|
||||
AgentSession session = await existingAgent.GetNewSessionAsync();
|
||||
Console.WriteLine(await existingAgent.RunAsync("What is the weather like in Amsterdam?", session));
|
||||
AgentThread thread = await existingAgent.GetNewThreadAsync();
|
||||
Console.WriteLine(await existingAgent.RunAsync("What is the weather like in Amsterdam?", thread));
|
||||
|
||||
// Streaming agent interaction with function tools.
|
||||
session = await existingAgent.GetNewSessionAsync();
|
||||
await foreach (AgentResponseUpdate update in existingAgent.RunStreamingAsync("What is the weather like in Amsterdam?", session))
|
||||
thread = await existingAgent.GetNewThreadAsync();
|
||||
await foreach (AgentResponseUpdate update in existingAgent.RunStreamingAsync("What is the weather like in Amsterdam?", thread))
|
||||
{
|
||||
Console.WriteLine(update);
|
||||
}
|
||||
|
||||
+3
-3
@@ -32,8 +32,8 @@ AIAgent agent = await aiProjectClient.CreateAIAgentAsync(name: AssistantName, mo
|
||||
|
||||
// Call the agent with approval-required function tools.
|
||||
// The agent will request approval before invoking the function.
|
||||
AgentSession session = await agent.GetNewSessionAsync();
|
||||
AgentResponse response = await agent.RunAsync("What is the weather like in Amsterdam?", session);
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
AgentResponse response = await agent.RunAsync("What is the weather like in Amsterdam?", thread);
|
||||
|
||||
// Check if there are any user input requests (approvals needed).
|
||||
List<UserInputRequestContent> userInputRequests = response.UserInputRequests.ToList();
|
||||
@@ -53,7 +53,7 @@ while (userInputRequests.Count > 0)
|
||||
.ToList();
|
||||
|
||||
// Pass the user input responses back to the agent for further processing.
|
||||
response = await agent.RunAsync(userInputMessages, session);
|
||||
response = await agent.RunAsync(userInputMessages, thread);
|
||||
|
||||
userInputRequests = response.UserInputRequests.ToList();
|
||||
}
|
||||
|
||||
+14
-14
@@ -18,27 +18,27 @@ AIProjectClient aiProjectClient = new(new Uri(endpoint), new AzureCliCredential(
|
||||
|
||||
AIAgent agent = await aiProjectClient.CreateAIAgentAsync(name: JokerName, model: deploymentName, instructions: JokerInstructions);
|
||||
|
||||
// Start a new session for the agent conversation.
|
||||
AgentSession session = await agent.GetNewSessionAsync();
|
||||
// Start a new thread for the agent conversation.
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
|
||||
// Run the agent with a new session.
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", session));
|
||||
// Run the agent with a new thread.
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", thread));
|
||||
|
||||
// Serialize the session state to a JsonElement, so it can be stored for later use.
|
||||
JsonElement serializedSession = session.Serialize();
|
||||
// Serialize the thread state to a JsonElement, so it can be stored for later use.
|
||||
JsonElement serializedThread = thread.Serialize();
|
||||
|
||||
// Save the serialized session to a temporary file (for demonstration purposes).
|
||||
// Save the serialized thread to a temporary file (for demonstration purposes).
|
||||
string tempFilePath = Path.GetTempFileName();
|
||||
await File.WriteAllTextAsync(tempFilePath, JsonSerializer.Serialize(serializedSession));
|
||||
await File.WriteAllTextAsync(tempFilePath, JsonSerializer.Serialize(serializedThread));
|
||||
|
||||
// Load the serialized session from the temporary file (for demonstration purposes).
|
||||
JsonElement reloadedSerializedSession = JsonElement.Parse(await File.ReadAllTextAsync(tempFilePath))!;
|
||||
// Load the serialized thread from the temporary file (for demonstration purposes).
|
||||
JsonElement reloadedSerializedThread = JsonElement.Parse(await File.ReadAllTextAsync(tempFilePath))!;
|
||||
|
||||
// Deserialize the session state after loading from storage.
|
||||
AgentSession resumedSession = await agent.DeserializeSessionAsync(reloadedSerializedSession);
|
||||
// Deserialize the thread state after loading from storage.
|
||||
AgentThread resumedThread = await agent.DeserializeThreadAsync(reloadedSerializedThread);
|
||||
|
||||
// Run the agent again with the resumed session.
|
||||
Console.WriteLine(await agent.RunAsync("Now tell the same joke in the voice of a pirate, and add some emojis to the joke.", resumedSession));
|
||||
// Run the agent again with the resumed thread.
|
||||
Console.WriteLine(await agent.RunAsync("Now tell the same joke in the voice of a pirate, and add some emojis to the joke.", resumedThread));
|
||||
|
||||
// Cleanup by agent name removes the agent version created.
|
||||
await aiProjectClient.Agents.DeleteAgentAsync(agent.Name);
|
||||
|
||||
+4
-4
@@ -38,12 +38,12 @@ AIAgent agent = (await aiProjectClient.CreateAIAgentAsync(name: JokerName, model
|
||||
.Build();
|
||||
|
||||
// Invoke the agent and output the text result.
|
||||
AgentSession session = await agent.GetNewSessionAsync();
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", session));
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", thread));
|
||||
|
||||
// Invoke the agent with streaming support.
|
||||
session = await agent.GetNewSessionAsync();
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync("Tell me a joke about a pirate.", session))
|
||||
thread = await agent.GetNewThreadAsync();
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync("Tell me a joke about a pirate.", thread))
|
||||
{
|
||||
Console.WriteLine(update);
|
||||
}
|
||||
|
||||
+4
-4
@@ -49,12 +49,12 @@ await host.RunAsync().ConfigureAwait(false);
|
||||
/// </summary>
|
||||
internal sealed class SampleService(AIProjectClient client, AIAgent agent, IHostApplicationLifetime appLifetime) : IHostedService
|
||||
{
|
||||
private AgentSession? _session;
|
||||
private AgentThread? _thread;
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
// Create a session that will be used for the entirety of the service lifetime so that the user can ask follow up questions.
|
||||
this._session = await agent.GetNewSessionAsync(cancellationToken);
|
||||
// Create a thread that will be used for the entirety of the service lifetime so that the user can ask follow up questions.
|
||||
this._thread = await agent.GetNewThreadAsync(cancellationToken);
|
||||
_ = this.RunAsync(appLifetime.ApplicationStopping);
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ internal sealed class SampleService(AIProjectClient client, AIAgent agent, IHost
|
||||
}
|
||||
|
||||
// Stream the output to the console as it is generated.
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(input, this._session, cancellationToken: cancellationToken))
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(input, this._thread, cancellationToken: cancellationToken))
|
||||
{
|
||||
Console.Write(update);
|
||||
}
|
||||
|
||||
+2
-2
@@ -24,9 +24,9 @@ ChatMessage message = new(ChatRole.User, [
|
||||
new DataContent(File.ReadAllBytes("assets/walkway.jpg"), "image/jpeg")
|
||||
]);
|
||||
|
||||
AgentSession session = await agent.GetNewSessionAsync();
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(message, session))
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(message, thread))
|
||||
{
|
||||
Console.WriteLine(update);
|
||||
}
|
||||
|
||||
+2
-2
@@ -39,8 +39,8 @@ AIAgent agent = await aiProjectClient.CreateAIAgentAsync(
|
||||
tools: [weatherAgent.AsAIFunction()]);
|
||||
|
||||
// Invoke the agent and output the text result.
|
||||
AgentSession session = await agent.GetNewSessionAsync();
|
||||
Console.WriteLine(await agent.RunAsync("What is the weather like in Amsterdam?", session));
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
Console.WriteLine(await agent.RunAsync("What is the weather like in Amsterdam?", thread));
|
||||
|
||||
// Cleanup by agent name removes the agent versions created.
|
||||
await aiProjectClient.Agents.DeleteAgentAsync(agent.Name);
|
||||
|
||||
+9
-9
@@ -49,7 +49,7 @@ AIAgent middlewareEnabledAgent = originalAgent
|
||||
.Use(GuardrailMiddleware, null)
|
||||
.Build();
|
||||
|
||||
AgentSession session = await middlewareEnabledAgent.GetNewSessionAsync();
|
||||
AgentThread thread = await middlewareEnabledAgent.GetNewThreadAsync();
|
||||
|
||||
Console.WriteLine("\n\n=== Example 1: Wording Guardrail ===");
|
||||
AgentResponse guardRailedResponse = await middlewareEnabledAgent.RunAsync("Tell me something harmful.");
|
||||
@@ -63,7 +63,7 @@ Console.WriteLine("\n\n=== Example 3: Agent function middleware ===");
|
||||
|
||||
// Agent function middleware support is limited to agents that wraps a upstream ChatClientAgent or derived from it.
|
||||
|
||||
AgentResponse functionCallResponse = await middlewareEnabledAgent.RunAsync("What's the current time and the weather in Seattle?", session);
|
||||
AgentResponse functionCallResponse = await middlewareEnabledAgent.RunAsync("What's the current time and the weather in Seattle?", thread);
|
||||
Console.WriteLine($"Function calling response: {functionCallResponse}");
|
||||
|
||||
// Special per-request middleware agent.
|
||||
@@ -113,13 +113,13 @@ async ValueTask<object?> FunctionCallOverrideWeather(AIAgent agent, FunctionInvo
|
||||
}
|
||||
|
||||
// This middleware redacts PII information from input and output messages.
|
||||
async Task<AgentResponse> PIIMiddleware(IEnumerable<ChatMessage> messages, AgentSession? session, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken)
|
||||
async Task<AgentResponse> PIIMiddleware(IEnumerable<ChatMessage> messages, AgentThread? thread, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken)
|
||||
{
|
||||
// Redact PII information from input messages
|
||||
var filteredMessages = FilterMessages(messages);
|
||||
Console.WriteLine("Pii Middleware - Filtered Messages Pre-Run");
|
||||
|
||||
var response = await innerAgent.RunAsync(filteredMessages, session, options, cancellationToken).ConfigureAwait(false);
|
||||
var response = await innerAgent.RunAsync(filteredMessages, thread, options, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Redact PII information from output messages
|
||||
response.Messages = FilterMessages(response.Messages);
|
||||
@@ -152,7 +152,7 @@ async Task<AgentResponse> PIIMiddleware(IEnumerable<ChatMessage> messages, Agent
|
||||
}
|
||||
|
||||
// This middleware enforces guardrails by redacting certain keywords from input and output messages.
|
||||
async Task<AgentResponse> GuardrailMiddleware(IEnumerable<ChatMessage> messages, AgentSession? session, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken)
|
||||
async Task<AgentResponse> GuardrailMiddleware(IEnumerable<ChatMessage> messages, AgentThread? thread, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken)
|
||||
{
|
||||
// Redact keywords from input messages
|
||||
var filteredMessages = FilterMessages(messages);
|
||||
@@ -160,7 +160,7 @@ async Task<AgentResponse> GuardrailMiddleware(IEnumerable<ChatMessage> messages,
|
||||
Console.WriteLine("Guardrail Middleware - Filtered messages Pre-Run");
|
||||
|
||||
// Proceed with the agent run
|
||||
var response = await innerAgent.RunAsync(filteredMessages, session, options, cancellationToken);
|
||||
var response = await innerAgent.RunAsync(filteredMessages, thread, options, cancellationToken);
|
||||
|
||||
// Redact keywords from output messages
|
||||
response.Messages = FilterMessages(response.Messages);
|
||||
@@ -189,9 +189,9 @@ async Task<AgentResponse> GuardrailMiddleware(IEnumerable<ChatMessage> messages,
|
||||
}
|
||||
|
||||
// This middleware handles Human in the loop console interaction for any user approval required during function calling.
|
||||
async Task<AgentResponse> ConsolePromptingApprovalMiddleware(IEnumerable<ChatMessage> messages, AgentSession? session, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken)
|
||||
async Task<AgentResponse> ConsolePromptingApprovalMiddleware(IEnumerable<ChatMessage> messages, AgentThread? thread, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken)
|
||||
{
|
||||
AgentResponse response = await innerAgent.RunAsync(messages, session, options, cancellationToken);
|
||||
AgentResponse response = await innerAgent.RunAsync(messages, thread, options, cancellationToken);
|
||||
|
||||
List<UserInputRequestContent> userInputRequests = response.UserInputRequests.ToList();
|
||||
|
||||
@@ -211,7 +211,7 @@ async Task<AgentResponse> ConsolePromptingApprovalMiddleware(IEnumerable<ChatMes
|
||||
})
|
||||
.ToList();
|
||||
|
||||
response = await innerAgent.RunAsync(response.Messages, session, options, cancellationToken);
|
||||
response = await innerAgent.RunAsync(response.Messages, thread, options, cancellationToken);
|
||||
|
||||
userInputRequests = response.UserInputRequests.ToList();
|
||||
}
|
||||
|
||||
@@ -42,8 +42,8 @@ AIAgent agent = await aiProjectClient.CreateAIAgentAsync(
|
||||
services: serviceProvider);
|
||||
|
||||
// Invoke the agent and output the text result.
|
||||
AgentSession session = await agent.GetNewSessionAsync();
|
||||
Console.WriteLine(await agent.RunAsync("Tell me current time and weather in Seattle.", session));
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
Console.WriteLine(await agent.RunAsync("Tell me current time and weather in Seattle.", thread));
|
||||
|
||||
// Cleanup by agent name removes the agent version created.
|
||||
await aiProjectClient.Agents.DeleteAgentAsync(agent.Name);
|
||||
|
||||
+4
-4
@@ -83,7 +83,7 @@ internal sealed class Program
|
||||
AllowBackgroundResponses = true,
|
||||
};
|
||||
|
||||
AgentSession session = await agent.GetNewSessionAsync();
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
|
||||
ChatMessage message = new(ChatRole.User, [
|
||||
new TextContent("I need you to help me search for 'OpenAI news'. Please type 'OpenAI news' and submit the search. Once you see search results, the task is complete."),
|
||||
@@ -93,7 +93,7 @@ internal sealed class Program
|
||||
// Initial request with screenshot - start with Bing search page
|
||||
Console.WriteLine("Starting computer automation session (initial screenshot: cua_browser_search.png)...");
|
||||
|
||||
AgentResponse response = await agent.RunAsync(message, session: session, options: runOptions);
|
||||
AgentResponse response = await agent.RunAsync(message, thread: thread, options: runOptions);
|
||||
|
||||
// Main interaction loop
|
||||
const int MaxIterations = 10;
|
||||
@@ -113,7 +113,7 @@ internal sealed class Program
|
||||
// Continue with the token.
|
||||
runOptions.ContinuationToken = token;
|
||||
|
||||
response = await agent.RunAsync(session, runOptions);
|
||||
response = await agent.RunAsync(thread, runOptions);
|
||||
}
|
||||
|
||||
Console.WriteLine($"Agent response received (ID: {response.ResponseId})");
|
||||
@@ -168,7 +168,7 @@ internal sealed class Program
|
||||
|
||||
// Follow-up message with action result and new screenshot
|
||||
message = new(ChatRole.User, [content]);
|
||||
response = await agent.RunAsync(message, session: session, options: runOptions);
|
||||
response = await agent.RunAsync(message, thread: thread, options: runOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-5
@@ -42,8 +42,8 @@ AIAgent agent = await persistentAgentsClient.CreateAIAgentAsync(
|
||||
});
|
||||
|
||||
// You can then invoke the agent like any other AIAgent.
|
||||
AgentSession session = await agent.GetNewSessionAsync();
|
||||
Console.WriteLine(await agent.RunAsync("Please summarize the Azure AI Agent documentation related to MCP Tool calling?", session));
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
Console.WriteLine(await agent.RunAsync("Please summarize the Azure AI Agent documentation related to MCP Tool calling?", thread));
|
||||
|
||||
// Cleanup for sample purposes.
|
||||
await persistentAgentsClient.Administration.DeleteAgentAsync(agent.Id);
|
||||
@@ -75,8 +75,8 @@ AIAgent agentWithRequiredApproval = await persistentAgentsClient.CreateAIAgentAs
|
||||
});
|
||||
|
||||
// You can then invoke the agent like any other AIAgent.
|
||||
var sessionWithRequiredApproval = await agentWithRequiredApproval.GetNewSessionAsync();
|
||||
var response = await agentWithRequiredApproval.RunAsync("Please summarize the Azure AI Agent documentation related to MCP Tool calling?", sessionWithRequiredApproval);
|
||||
var threadWithRequiredApproval = await agentWithRequiredApproval.GetNewThreadAsync();
|
||||
var response = await agentWithRequiredApproval.RunAsync("Please summarize the Azure AI Agent documentation related to MCP Tool calling?", threadWithRequiredApproval);
|
||||
var userInputRequests = response.UserInputRequests.ToList();
|
||||
|
||||
while (userInputRequests.Count > 0)
|
||||
@@ -98,7 +98,7 @@ while (userInputRequests.Count > 0)
|
||||
.ToList();
|
||||
|
||||
// Pass the user input responses back to the agent for further processing.
|
||||
response = await agentWithRequiredApproval.RunAsync(userInputResponses, sessionWithRequiredApproval);
|
||||
response = await agentWithRequiredApproval.RunAsync(userInputResponses, threadWithRequiredApproval);
|
||||
|
||||
userInputRequests = response.UserInputRequests.ToList();
|
||||
}
|
||||
|
||||
+5
-5
@@ -37,8 +37,8 @@ AIAgent agent = new AzureOpenAIClient(
|
||||
tools: [mcpTool]);
|
||||
|
||||
// You can then invoke the agent like any other AIAgent.
|
||||
AgentSession session = await agent.GetNewSessionAsync();
|
||||
Console.WriteLine(await agent.RunAsync("Please summarize the Azure AI Agent documentation related to MCP Tool calling?", session));
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
Console.WriteLine(await agent.RunAsync("Please summarize the Azure AI Agent documentation related to MCP Tool calling?", thread));
|
||||
|
||||
// **** MCP Tool with Approval Required ****
|
||||
// *****************************************
|
||||
@@ -64,8 +64,8 @@ AIAgent agentWithRequiredApproval = new AzureOpenAIClient(
|
||||
tools: [mcpToolWithApproval]);
|
||||
|
||||
// You can then invoke the agent like any other AIAgent.
|
||||
var sessionWithRequiredApproval = await agentWithRequiredApproval.GetNewSessionAsync();
|
||||
var response = await agentWithRequiredApproval.RunAsync("Please summarize the Azure AI Agent documentation related to MCP Tool calling?", sessionWithRequiredApproval);
|
||||
var threadWithRequiredApproval = await agentWithRequiredApproval.GetNewThreadAsync();
|
||||
var response = await agentWithRequiredApproval.RunAsync("Please summarize the Azure AI Agent documentation related to MCP Tool calling?", threadWithRequiredApproval);
|
||||
var userInputRequests = response.UserInputRequests.ToList();
|
||||
|
||||
while (userInputRequests.Count > 0)
|
||||
@@ -87,7 +87,7 @@ while (userInputRequests.Count > 0)
|
||||
.ToList();
|
||||
|
||||
// Pass the user input responses back to the agent for further processing.
|
||||
response = await agentWithRequiredApproval.RunAsync(userInputResponses, sessionWithRequiredApproval);
|
||||
response = await agentWithRequiredApproval.RunAsync(userInputResponses, threadWithRequiredApproval);
|
||||
|
||||
userInputRequests = response.UserInputRequests.ToList();
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ internal sealed class SloganGeneratedEvent(SloganResult sloganResult) : Workflow
|
||||
internal sealed class SloganWriterExecutor : Executor
|
||||
{
|
||||
private readonly AIAgent _agent;
|
||||
private AgentSession? _session;
|
||||
private AgentThread? _thread;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SloganWriterExecutor"/> class.
|
||||
@@ -136,9 +136,9 @@ internal sealed class SloganWriterExecutor : Executor
|
||||
|
||||
public async ValueTask<SloganResult> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
this._session ??= await this._agent.GetNewSessionAsync(cancellationToken);
|
||||
this._thread ??= await this._agent.GetNewThreadAsync(cancellationToken);
|
||||
|
||||
var result = await this._agent.RunAsync(message, this._session, cancellationToken: cancellationToken);
|
||||
var result = await this._agent.RunAsync(message, this._thread, cancellationToken: cancellationToken);
|
||||
|
||||
var sloganResult = JsonSerializer.Deserialize<SloganResult>(result.Text) ?? throw new InvalidOperationException("Failed to deserialize slogan result.");
|
||||
|
||||
@@ -157,7 +157,7 @@ internal sealed class SloganWriterExecutor : Executor
|
||||
Please use this feedback to improve your slogan.
|
||||
""";
|
||||
|
||||
var result = await this._agent.RunAsync(feedbackMessage, this._session, cancellationToken: cancellationToken);
|
||||
var result = await this._agent.RunAsync(feedbackMessage, this._thread, cancellationToken: cancellationToken);
|
||||
var sloganResult = JsonSerializer.Deserialize<SloganResult>(result.Text) ?? throw new InvalidOperationException("Failed to deserialize slogan result.");
|
||||
|
||||
await context.AddEventAsync(new SloganGeneratedEvent(sloganResult), cancellationToken);
|
||||
@@ -180,7 +180,7 @@ internal sealed class FeedbackEvent(FeedbackResult feedbackResult) : WorkflowEve
|
||||
internal sealed class FeedbackExecutor : Executor<SloganResult>
|
||||
{
|
||||
private readonly AIAgent _agent;
|
||||
private AgentSession? _session;
|
||||
private AgentThread? _thread;
|
||||
|
||||
public int MinimumRating { get; init; } = 8;
|
||||
|
||||
@@ -209,7 +209,7 @@ internal sealed class FeedbackExecutor : Executor<SloganResult>
|
||||
|
||||
public override async ValueTask HandleAsync(SloganResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
this._session ??= await this._agent.GetNewSessionAsync(cancellationToken);
|
||||
this._thread ??= await this._agent.GetNewThreadAsync(cancellationToken);
|
||||
|
||||
var sloganMessage = $"""
|
||||
Here is a slogan for the task '{message.Task}':
|
||||
@@ -217,7 +217,7 @@ internal sealed class FeedbackExecutor : Executor<SloganResult>
|
||||
Please provide feedback on this slogan, including comments, a rating from 1 to 10, and suggested actions for improvement.
|
||||
""";
|
||||
|
||||
var response = await this._agent.RunAsync(sloganMessage, this._session, cancellationToken: cancellationToken);
|
||||
var response = await this._agent.RunAsync(sloganMessage, this._thread, cancellationToken: cancellationToken);
|
||||
var feedback = JsonSerializer.Deserialize<FeedbackResult>(response.Text) ?? throw new InvalidOperationException("Failed to deserialize feedback.");
|
||||
|
||||
await context.AddEventAsync(new FeedbackEvent(feedback), cancellationToken);
|
||||
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.Workflows;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace WorkflowGroupChatToolApprovalSample;
|
||||
|
||||
/// <summary>
|
||||
/// Custom GroupChatManager that selects the next speaker based on the conversation flow.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This simple selector follows a predefined flow:
|
||||
/// 1. QA Engineer runs tests
|
||||
/// 2. DevOps Engineer checks staging and creates rollback plan
|
||||
/// 3. DevOps Engineer deploys to production (triggers approval)
|
||||
/// </remarks>
|
||||
internal sealed class DeploymentGroupChatManager : GroupChatManager
|
||||
{
|
||||
private readonly IReadOnlyList<AIAgent> _agents;
|
||||
|
||||
public DeploymentGroupChatManager(IReadOnlyList<AIAgent> agents)
|
||||
{
|
||||
this._agents = agents;
|
||||
}
|
||||
|
||||
protected override ValueTask<AIAgent> SelectNextAgentAsync(
|
||||
IReadOnlyList<ChatMessage> history,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (history.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("Conversation is empty; cannot select next speaker.");
|
||||
}
|
||||
|
||||
// First speaker after initial user message
|
||||
if (this.IterationCount == 0)
|
||||
{
|
||||
AIAgent qaAgent = this._agents.First(a => a.Name == "QAEngineer");
|
||||
return new ValueTask<AIAgent>(qaAgent);
|
||||
}
|
||||
|
||||
// Subsequent speakers are DevOps Engineer
|
||||
AIAgent devopsAgent = this._agents.First(a => a.Name == "DevOpsEngineer");
|
||||
return new ValueTask<AIAgent>(devopsAgent);
|
||||
}
|
||||
}
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Workflows\Microsoft.Agents.AI.Workflows.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,161 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This sample demonstrates how to use GroupChatBuilder with tools that require human
|
||||
// approval before execution. A group of specialized agents collaborate on a task, and
|
||||
// sensitive tool calls trigger human-in-the-loop approval.
|
||||
//
|
||||
// This sample works as follows:
|
||||
// 1. A GroupChatBuilder workflow is created with multiple specialized agents.
|
||||
// 2. A custom manager determines which agent speaks next based on conversation state.
|
||||
// 3. Agents collaborate on a software deployment task.
|
||||
// 4. When the deployment agent tries to deploy to production, it triggers an approval request.
|
||||
// 5. The sample simulates human approval and the workflow completes.
|
||||
//
|
||||
// Purpose:
|
||||
// Show how tool call approvals integrate with multi-agent group chat workflows where
|
||||
// different agents have different levels of tool access.
|
||||
//
|
||||
// Demonstrate:
|
||||
// - Using custom GroupChatManager with agents that have approval-required tools.
|
||||
// - Handling FunctionApprovalRequestContent in group chat scenarios.
|
||||
// - Multi-round group chat with tool approval interruption and resumption.
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Text.Json;
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.Workflows;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace WorkflowGroupChatToolApprovalSample;
|
||||
|
||||
/// <summary>
|
||||
/// This sample demonstrates how to use GroupChatBuilder with tools that require human
|
||||
/// approval before execution.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Pre-requisites:
|
||||
/// - An Azure OpenAI chat completion deployment must be configured.
|
||||
/// </remarks>
|
||||
public static class Program
|
||||
{
|
||||
private static async Task Main()
|
||||
{
|
||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
|
||||
|
||||
// 1. Create AI client
|
||||
IChatClient client = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.AsIChatClient();
|
||||
|
||||
// 2. Create specialized agents with their tools
|
||||
ChatClientAgent qaEngineer = new(
|
||||
client,
|
||||
"You are a QA engineer responsible for running tests before deployment. Run the appropriate test suites and report results clearly.",
|
||||
"QAEngineer",
|
||||
"QA engineer who runs tests",
|
||||
[AIFunctionFactory.Create(RunTests)]);
|
||||
|
||||
ChatClientAgent devopsEngineer = new(
|
||||
client,
|
||||
"You are a DevOps engineer responsible for deployments. First check staging status and create a rollback plan, then proceed with production deployment. Always ensure safety measures are in place before deploying.",
|
||||
"DevOpsEngineer",
|
||||
"DevOps engineer who handles deployments",
|
||||
[
|
||||
AIFunctionFactory.Create(CheckStagingStatus),
|
||||
AIFunctionFactory.Create(CreateRollbackPlan),
|
||||
new ApprovalRequiredAIFunction(AIFunctionFactory.Create(DeployToProduction))
|
||||
]);
|
||||
|
||||
// 3. Create custom GroupChatManager with speaker selection logic
|
||||
DeploymentGroupChatManager manager = new([qaEngineer, devopsEngineer])
|
||||
{
|
||||
MaximumIterationCount = 4 // Limit to 4 rounds
|
||||
};
|
||||
|
||||
// 4. Build a group chat workflow with the custom manager
|
||||
Workflow workflow = AgentWorkflowBuilder
|
||||
.CreateGroupChatBuilderWith(_ => manager)
|
||||
.AddParticipants(qaEngineer, devopsEngineer)
|
||||
.Build();
|
||||
|
||||
// 5. Start the workflow
|
||||
Console.WriteLine("Starting group chat workflow for software deployment...");
|
||||
Console.WriteLine($"Agents: [{qaEngineer.Name}, {devopsEngineer.Name}]");
|
||||
Console.WriteLine(new string('-', 60));
|
||||
|
||||
List<ChatMessage> messages = [new(ChatRole.User, "We need to deploy version 2.4.0 to production. Please coordinate the deployment.")];
|
||||
|
||||
await using StreamingRun run = await InProcessExecution.Lockstep.StreamAsync(workflow, messages);
|
||||
await run.TrySendMessageAsync(new TurnToken(emitEvents: true));
|
||||
|
||||
string? lastExecutorId = null;
|
||||
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
|
||||
{
|
||||
switch (evt)
|
||||
{
|
||||
case RequestInfoEvent e:
|
||||
{
|
||||
if (e.Request.DataIs(out FunctionApprovalRequestContent? approvalRequestContent))
|
||||
{
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($"[APPROVAL REQUIRED] From agent: {e.Request.PortInfo.PortId}");
|
||||
Console.WriteLine($" Tool: {approvalRequestContent.FunctionCall.Name}");
|
||||
Console.WriteLine($" Arguments: {JsonSerializer.Serialize(approvalRequestContent.FunctionCall.Arguments)}");
|
||||
Console.WriteLine();
|
||||
|
||||
// Approve the tool call request
|
||||
Console.WriteLine($"Tool: {approvalRequestContent.FunctionCall.Name} approved");
|
||||
await run.SendResponseAsync(e.Request.CreateResponse(approvalRequestContent.CreateResponse(approved: true)));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case AgentResponseUpdateEvent e:
|
||||
{
|
||||
if (e.ExecutorId != lastExecutorId)
|
||||
{
|
||||
if (lastExecutorId is not null)
|
||||
{
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
Console.WriteLine($"- {e.ExecutorId}: ");
|
||||
lastExecutorId = e.ExecutorId;
|
||||
}
|
||||
|
||||
Console.Write(e.Update.Text);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine(new string('-', 60));
|
||||
Console.WriteLine("Deployment workflow completed successfully!");
|
||||
Console.WriteLine("All agents have finished their tasks.");
|
||||
}
|
||||
|
||||
// Tool definitions - These are called by the agents during workflow execution
|
||||
[Description("Run automated tests for the application.")]
|
||||
private static string RunTests([Description("Name of the test suite to run")] string testSuite)
|
||||
=> $"Test suite '{testSuite}' completed: 47 passed, 0 failed, 0 skipped";
|
||||
|
||||
[Description("Check the current status of the staging environment.")]
|
||||
private static string CheckStagingStatus()
|
||||
=> "Staging environment: Healthy, Version 2.3.0 deployed, All services running";
|
||||
|
||||
[Description("Deploy specified components to production. Requires human approval.")]
|
||||
private static string DeployToProduction(
|
||||
[Description("The version to deploy")] string version,
|
||||
[Description("Comma-separated list of components to deploy")] string components)
|
||||
=> $"Production deployment complete: Version {version}, Components: {components}";
|
||||
|
||||
[Description("Create a rollback plan for the deployment.")]
|
||||
private static string CreateRollbackPlan([Description("The version being deployed")] string version)
|
||||
=> $"Rollback plan created for version {version}: Automated rollback to v2.2.0 if health checks fail within 5 minutes";
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
# Group Chat with Tool Approval Sample
|
||||
|
||||
This sample demonstrates how to use `GroupChatBuilder` with tools that require human approval before execution. A group of specialized agents collaborate on a task, and sensitive tool calls trigger human-in-the-loop approval.
|
||||
|
||||
## What This Sample Demonstrates
|
||||
|
||||
- Using a custom `GroupChatManager` with agents that have approval-required tools
|
||||
- Handling `FunctionApprovalRequestContent` in group chat scenarios
|
||||
- Multi-round group chat with tool approval interruption and resumption
|
||||
- Integrating tool call approvals with multi-agent workflows where different agents have different levels of tool access
|
||||
|
||||
## How It Works
|
||||
|
||||
1. A `GroupChatBuilder` workflow is created with multiple specialized agents
|
||||
2. A custom `DeploymentGroupChatManager` determines which agent speaks next based on conversation state
|
||||
3. Agents collaborate on a software deployment task:
|
||||
- **QA Engineer**: Runs automated tests
|
||||
- **DevOps Engineer**: Checks staging status, creates rollback plan, and deploys to production
|
||||
4. When the deployment agent tries to deploy to production, it triggers an approval request
|
||||
5. The sample simulates human approval and the workflow completes
|
||||
|
||||
## Key Components
|
||||
|
||||
### Approval-Required Tools
|
||||
|
||||
The `DeployToProduction` function is wrapped with `ApprovalRequiredAIFunction` to require human approval:
|
||||
|
||||
```csharp
|
||||
new ApprovalRequiredAIFunction(AIFunctionFactory.Create(DeployToProduction))
|
||||
```
|
||||
|
||||
### Custom Group Chat Manager
|
||||
|
||||
The `DeploymentGroupChatManager` implements custom speaker selection logic:
|
||||
- First iteration: QA Engineer runs tests
|
||||
- Subsequent iterations: DevOps Engineer handles deployment tasks
|
||||
|
||||
### Approval Handling
|
||||
|
||||
The sample demonstrates continuous event-driven execution with inline approval handling:
|
||||
- The workflow runs in a single event loop.
|
||||
- When an approval-required tool is invoked, the loop surfaces an approval request, processes the (simulated) human response, and then continues execution without starting a separate phase.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Azure OpenAI or OpenAI configured with the required environment variables
|
||||
- `AZURE_OPENAI_ENDPOINT` environment variable set
|
||||
- `AZURE_OPENAI_DEPLOYMENT_NAME` environment variable (defaults to "gpt-4o-mini")
|
||||
|
||||
## Running the Sample
|
||||
|
||||
```bash
|
||||
dotnet run
|
||||
```
|
||||
|
||||
## Expected Output
|
||||
|
||||
The sample will show:
|
||||
1. QA Engineer running tests
|
||||
2. DevOps Engineer checking staging and creating rollback plan
|
||||
3. An approval request for production deployment
|
||||
4. Simulated approval response
|
||||
5. DevOps Engineer completing the deployment
|
||||
6. Workflow completion message
|
||||
|
||||
## Related Samples
|
||||
|
||||
- [Agent Function Tools with Approvals](../../../Agents/Agent_Step04_UsingFunctionToolsWithApprovals) - Basic function approval pattern
|
||||
- [Agent Workflow Patterns](../../_Foundational/04_AgentWorkflowPatterns) - Group chat without approvals
|
||||
- [Human-in-the-Loop Basic](../../HumanInTheLoop/HumanInTheLoopBasic) - Workflow-level human interaction
|
||||
@@ -37,7 +37,7 @@ public static class Program
|
||||
// Create the workflow and turn it into an agent
|
||||
var workflow = WorkflowFactory.BuildWorkflow(chatClient);
|
||||
var agent = workflow.AsAgent("workflow-agent", "Workflow Agent");
|
||||
var session = await agent.GetNewSessionAsync();
|
||||
var thread = await agent.GetNewThreadAsync();
|
||||
|
||||
// Start an interactive loop to interact with the workflow as if it were an agent
|
||||
while (true)
|
||||
@@ -50,16 +50,16 @@ public static class Program
|
||||
break;
|
||||
}
|
||||
|
||||
await ProcessInputAsync(agent, session, input);
|
||||
await ProcessInputAsync(agent, thread, input);
|
||||
}
|
||||
|
||||
// Helper method to process user input and display streaming responses. To display
|
||||
// multiple interleaved responses correctly, we buffer updates by message ID and
|
||||
// re-render all messages on each update.
|
||||
static async Task ProcessInputAsync(AIAgent agent, AgentSession? session, string input)
|
||||
static async Task ProcessInputAsync(AIAgent agent, AgentThread thread, string input)
|
||||
{
|
||||
Dictionary<string, List<AgentResponseUpdate>> buffer = [];
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(input, session))
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(input, thread))
|
||||
{
|
||||
if (update.MessageId is null || string.IsNullOrEmpty(update.Text))
|
||||
{
|
||||
|
||||
@@ -47,7 +47,7 @@ internal sealed class Program
|
||||
|
||||
AIAgent agent = aiProjectClient.AsAIAgent(agentVersion);
|
||||
|
||||
AgentSession session = await agent.GetNewSessionAsync();
|
||||
AgentThread thread = await agent.GetNewThreadAsync();
|
||||
|
||||
ProjectConversation conversation =
|
||||
await aiProjectClient
|
||||
@@ -65,7 +65,7 @@ internal sealed class Program
|
||||
};
|
||||
ChatClientAgentRunOptions runOptions = new(chatOptions);
|
||||
|
||||
IAsyncEnumerable<AgentResponseUpdate> agentResponseUpdates = agent.RunStreamingAsync(workflowInput, session, runOptions);
|
||||
IAsyncEnumerable<AgentResponseUpdate> agentResponseUpdates = agent.RunStreamingAsync(workflowInput, thread, runOptions);
|
||||
|
||||
string? lastMessageId = null;
|
||||
await foreach (AgentResponseUpdate responseUpdate in agentResponseUpdates)
|
||||
|
||||
@@ -90,7 +90,7 @@ public static class Program
|
||||
{
|
||||
EnableSensitiveData = true // enable sensitive data at the agent level such as prompts and responses
|
||||
};
|
||||
var session = await agent.GetNewSessionAsync();
|
||||
var thread = await agent.GetNewThreadAsync();
|
||||
|
||||
// Start an interactive loop to interact with the workflow as if it were an agent
|
||||
while (true)
|
||||
@@ -103,16 +103,16 @@ public static class Program
|
||||
break;
|
||||
}
|
||||
|
||||
await ProcessInputAsync(agent, session, input);
|
||||
await ProcessInputAsync(agent, thread, input);
|
||||
}
|
||||
|
||||
// Helper method to process user input and display streaming responses. To display
|
||||
// multiple interleaved responses correctly, we buffer updates by message ID and
|
||||
// re-render all messages on each update.
|
||||
static async Task ProcessInputAsync(AIAgent agent, AgentSession? session, string input)
|
||||
static async Task ProcessInputAsync(AIAgent agent, AgentThread thread, string input)
|
||||
{
|
||||
Dictionary<string, List<AgentResponseUpdate>> buffer = [];
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(input, session))
|
||||
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(input, thread))
|
||||
{
|
||||
if (update.MessageId is null || string.IsNullOrEmpty(update.Text))
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user