mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Compare commits
73
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c24a72107 | ||
|
|
59da578902 | ||
|
|
76ae0a62ac | ||
|
|
85921eda68 | ||
|
|
7fb8bcf660 | ||
|
|
3e59ac06b4 | ||
|
|
021e3bab72 | ||
|
|
98726a9288 | ||
|
|
54c3eb726a | ||
|
|
3b9193c15e | ||
|
|
f9a9fc7605 | ||
|
|
9677899600 | ||
|
|
db7f767180 | ||
|
|
b64358df7e | ||
|
|
331c750515 | ||
|
|
69e1ab0409 | ||
|
|
a4ba6eed57 | ||
|
|
49455ad1b9 | ||
|
|
5333cce63c | ||
|
|
f593bb535a | ||
|
|
d7094d5432 | ||
|
|
ab450078ab | ||
|
|
8e8dfb3948 | ||
|
|
a6b6937b94 | ||
|
|
a4039134de | ||
|
|
38e10eab81 | ||
|
|
140ec35cf2 | ||
|
|
0331331dbc | ||
|
|
f42a3ee6b9 | ||
|
|
ea8f751108 | ||
|
|
1f0faff623 | ||
|
|
9148392d00 | ||
|
|
fc12ab9fed | ||
|
|
baf59ca1ed | ||
|
|
166aa8fd54 | ||
|
|
64b57639b8 | ||
|
|
d1ac2d9331 | ||
|
|
7e8a3fa0e5 | ||
|
|
5fa153642e | ||
|
|
29cb87b805 | ||
|
|
350cdfc1cd | ||
|
|
e032fe3993 | ||
|
|
d6fdb91480 | ||
|
|
3730db3e94 | ||
|
|
00a124dae6 | ||
|
|
7e1fd67e76 | ||
|
|
2c9cf6f59d | ||
|
|
7238cde5af | ||
|
|
4cd81fe8e7 | ||
|
|
2f53ce4abd | ||
|
|
9fe4a61dd0 | ||
|
|
a02b82f022 | ||
|
|
8967269d3e | ||
|
|
d16d56b555 | ||
|
|
f17bf0a502 | ||
|
|
1b5e20b5b0 | ||
|
|
2397795c1d | ||
|
|
15afc966ce | ||
|
|
988623e7b8 | ||
|
|
74864f353d | ||
|
|
3d31a4a204 | ||
|
|
7e891fab39 | ||
|
|
c341ee7ed2 | ||
|
|
f5abbc67ae | ||
|
|
a36e183600 | ||
|
|
c2c8ec3d4e | ||
|
|
1c5e607a1f | ||
|
|
334d52f300 | ||
|
|
523127fbf4 | ||
|
|
5902bcb10a | ||
|
|
eb049c43a6 | ||
|
|
362652b966 | ||
|
|
127bf68748 |
+1
-2
@@ -1,6 +1,5 @@
|
||||
# Auto-detect text files, ensure they use LF.
|
||||
* text=auto eol=lf working-tree-encoding=UTF-8
|
||||
|
||||
# Bash scripts
|
||||
*.sh text eol=lf
|
||||
*.cmd text eol=crlf
|
||||
*.cmd text eol=crlf
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
# GitHub Copilot Instructions
|
||||
|
||||
This repository contains both Python and C# code.
|
||||
All python code resides under the `python/` directory.
|
||||
All C# code resides under the `dotnet/` directory.
|
||||
|
||||
The purpose of the code is to provide a framework for building AI agents.
|
||||
|
||||
When contributing to this repository, please follow these guidelines:
|
||||
|
||||
## C# Code Guidelines
|
||||
|
||||
Here are some general guidelines that apply to all code.
|
||||
|
||||
- The top of all *.cs files should have a copyright notice: `// Copyright (c) Microsoft. All rights reserved.`
|
||||
- All public methods and classes should have XML documentation comments.
|
||||
|
||||
### C# Sample Code Guidelines
|
||||
|
||||
Sample code is located in the `dotnet/samples` directory.
|
||||
|
||||
When adding a new sample, follow these steps:
|
||||
|
||||
- The sample should be a standalone .net project in one of the subdirectories of the samples directory.
|
||||
- The directory name should be the same as the project name.
|
||||
- The directory should contain a README.md file that explains what the sample does and how to run it.
|
||||
- The README.md file should follow the same format as other samples.
|
||||
- The csproj file should match the directory name.
|
||||
- The csproj file should be configured in the same way as other samples.
|
||||
- The project should preferably contain a single Program.cs file that contains all the sample code.
|
||||
- The sample should be added to the solution file in the samples directory.
|
||||
- The sample should be tested to ensure it works as expected.
|
||||
- A reference to the new samples should be added to the README.md file in the parent directory of the new sample.
|
||||
|
||||
The sample code should follow these guidelines:
|
||||
|
||||
- Configuration settings should be read from environment variables, e.g. `var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");`.
|
||||
- Environment variables should use upper snake_case naming convention.
|
||||
- Secrets should not be hardcoded in the code or committed to the repository.
|
||||
- The code should be well-documented with comments explaining the purpose of each step.
|
||||
- The code should be simple and to the point, avoiding unnecessary complexity.
|
||||
- Prefer inline literals over constants for values that are not reused. For example, use `new ChatClientAgent(chatClient, instructions: "You are a helpful assistant.")` instead of defining a constant for "instructions".
|
||||
- Ensure that all private classes are sealed
|
||||
- Use the Async suffix on the name of all async methods that return a Task or ValueTask.
|
||||
- Prefer defining variables using types rather than var, to help users understand the types involved.
|
||||
- Follow the patterns in the samples in the same directories where new samples are being added.
|
||||
- The structure of the sample should be as follows:
|
||||
- The top of the Program.cs should have a copyright notice: `// Copyright (c) Microsoft. All rights reserved.`
|
||||
- Then add a comment describing what the sample is demonstrating.
|
||||
- Then add the necessary using statements.
|
||||
- Then add the main code logic.
|
||||
- Finally, add any helper methods or classes at the bottom of the file.
|
||||
|
||||
### C# Unit Test Guidelines
|
||||
|
||||
Unit tests are located in the `dotnet/tests` directory in projects with a `.UnitTests.csproj` suffix.
|
||||
|
||||
Unit tests should follow these guidelines:
|
||||
|
||||
- Use `this.` for accessing class members
|
||||
- Add Arrange, Act and Assert comments for each test
|
||||
- Ensure that all private classes, that are not subclassed, are sealed
|
||||
- Use the Async suffix on the name of all async methods
|
||||
- Use the Moq library for mocking objects where possible
|
||||
- Validate that each test actually tests the target behavior, e.g. we should not have tests that creates a mock, calls the mock and then verifies that the mock was called, without the target code being involved. We also shouldn't have tests that test language features, e.g. something that the compiler would catch anyway.
|
||||
- Avoid adding excessive comments to tests. Instead favour clear easy to understand code.
|
||||
- Follow the patterns in the unit tests in the same project or classes to which new tests are being added
|
||||
@@ -160,6 +160,7 @@ jobs:
|
||||
AzureAI__DeploymentName: ${{ vars.AZUREAI__DEPLOYMENTNAME }}
|
||||
AzureAI__BingConnectionId: ${{ vars.AZUREAI__BINGCONECTIONID }}
|
||||
FOUNDRY_PROJECT_ENDPOINT: ${{ vars.FOUNDRY_PROJECT_ENDPOINT }}
|
||||
FOUNDRY_MEDIA_DEPLOYMENT_NAME: ${{ vars.FOUNDRY_MEDIA_DEPLOYMENT_NAME }}
|
||||
FOUNDRY_MODEL_DEPLOYMENT_NAME: ${{ vars.FOUNDRY_MODEL_DEPLOYMENT_NAME }}
|
||||
FOUNDRY_CONNECTION_GROUNDING_TOOL: ${{ vars.FOUNDRY_CONNECTION_GROUNDING_TOOL }}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_ENV
|
||||
- name: Pytest coverage comment
|
||||
id: coverageComment
|
||||
uses: MishaKav/pytest-coverage-comment@v1.1.56
|
||||
uses: MishaKav/pytest-coverage-comment@v1.1.57
|
||||
with:
|
||||
github-token: ${{ secrets.GH_ACTIONS_PR_WRITE }}
|
||||
issue-number: ${{ env.PR_NUMBER }}
|
||||
|
||||
@@ -119,22 +119,35 @@ if __name__ == "__main__":
|
||||
|
||||
### Basic Agent - .NET
|
||||
|
||||
Create a simple Agent, using OpenAI Responses, that writes a haiku about the Microsoft Agent Framework
|
||||
|
||||
```c#
|
||||
// dotnet add package Microsoft.Agents.AI.OpenAI --prerelease
|
||||
using System;
|
||||
using OpenAI;
|
||||
|
||||
// Replace the <apikey> with your OpenAI API key.
|
||||
var agent = new OpenAIClient("<apikey>")
|
||||
.GetOpenAIResponseClient("gpt-4o-mini")
|
||||
.CreateAIAgent(name: "HaikuBot", instructions: "You are an upbeat assistant that writes beautifully.");
|
||||
|
||||
Console.WriteLine(await agent.RunAsync("Write a haiku about Microsoft Agent Framework."));
|
||||
```
|
||||
|
||||
Create a simple Agent, using Azure OpenAI Responses with token based auth, that writes a haiku about the Microsoft Agent Framework
|
||||
|
||||
```c#
|
||||
// dotnet add package Microsoft.Agents.AI.OpenAI --prerelease
|
||||
// dotnet add package Azure.AI.OpenAI
|
||||
// dotnet add package Azure.Identity
|
||||
// Use `az login` to authenticate with Azure CLI
|
||||
using System;
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using OpenAI;
|
||||
|
||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")!;
|
||||
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME")!;
|
||||
|
||||
var agent = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
|
||||
.GetOpenAIResponseClient(deploymentName)
|
||||
// Replace <resource> and gpt-4o-mini with your Azure OpenAI resource name and deployment name.
|
||||
var agent = new OpenAIClient(
|
||||
new BearerTokenPolicy(new AzureCliCredential(), "https://ai.azure.com/.default"),
|
||||
new OpenAIClientOptions() { Endpoint = new Uri("https://<resource>.openai.azure.com/openai/v1") })
|
||||
.GetOpenAIResponseClient("gpt-4o-mini")
|
||||
.CreateAIAgent(name: "HaikuBot", instructions: "You are an upbeat assistant that writes beautifully.");
|
||||
|
||||
Console.WriteLine(await agent.RunAsync("Write a haiku about Microsoft Agent Framework."));
|
||||
|
||||
@@ -8,6 +8,10 @@ feature request as a new Issue.
|
||||
|
||||
For help and questions about using this project, please create a GitHub issue.
|
||||
|
||||
AI Support team will support Microsoft Agent Framework issues for customers under a **Unified support agreement when the issue arises from usage of Azure AI services** (Foundry Models, Foundry Agents etc.) in conjunction with the SDK. Conversely, if customer has any other / non unified support agreement and/or Agent Framework SDK is used in a way **not involving an Azure service**, it is treated as a purely open-source tool – Microsoft’s support organization will not handle it, and users should use GitHub or forums for assistance
|
||||
|
||||
For Copilot Studio SDK implementation issues, customers should use GitHub Issues for assistance, as outlined above. Conversely, for prerequisites managed within the Copilot Studio portal, customers can rely on the standard Microsoft Copilot Studio support channels.
|
||||
|
||||
## Microsoft Support Policy
|
||||
|
||||
Support for this **PROJECT or PRODUCT** is limited to the resources listed above.
|
||||
|
||||
@@ -499,7 +499,7 @@ We need to decide what AIContent types, each agent response type will be mapped
|
||||
| AWS (Strands) | **Approach 2** Supports a special invocation method called [structured_output](https://strandsagents.com/latest/api-reference/agent/#strands.agent.agent.Agent.structured_output) |
|
||||
| LangGraph | **Approach 1** Supports [configuring an agent](https://langchain-ai.github.io/langgraph/agents/agents/?h=structured#6-configure-structured-output) at agent construction time, and a [structured response](https://langchain-ai.github.io/langgraph/agents/run_agents/#output-format) can be retrieved as a special property on the agent response |
|
||||
| Agno | **Approach 1** Supports [configuring an agent](https://docs.agno.com/examples/getting-started/structured-output) at agent construction time |
|
||||
| A2A | **Informal Approach 2** Doesn't formally support schema negotiation, but [hints can be provided via metadata](https://a2aproject.github.io/A2A/v0.2.5/specification/#97-structured-data-exchange-requesting-and-providing-json) at invocation time |
|
||||
| A2A | **Informal Approach 2** Doesn't formally support schema negotiation, but [hints can be provided via metadata](https://a2a-protocol.org/latest/specification/#97-structured-data-exchange-requesting-and-providing-json) at invocation time |
|
||||
| Protocol Activity | Supports returning [Complex types](https://github.com/microsoft/Agents/blob/main/specs/activity/protocol-activity.md#complex-types) but no support for requesting a type |
|
||||
|
||||
### Response Reason Support
|
||||
@@ -511,5 +511,5 @@ We need to decide what AIContent types, each agent response type will be mapped
|
||||
| AWS (Strands) | Exposes a [stop_reason](https://strandsagents.com/latest/api-reference/types/#strands.types.event_loop.StopReason) property on the [AgentResult](https://strandsagents.com/latest/api-reference/agent/#strands.agent.agent_result.AgentResult) class with options that are tied closely to LLM operations. |
|
||||
| LangGraph | No equivalent present, output contains only [messages](https://langchain-ai.github.io/langgraph/agents/run_agents/#output-format) |
|
||||
| Agno | [No equivalent present](https://docs.agno.com/reference/agents/run-response) |
|
||||
| A2A | No equivalent present, response only contains a [message](https://a2aproject.github.io/A2A/v0.2.5/specification/#64-message-object) or [task](https://a2aproject.github.io/A2A/v0.2.5/specification/#61-task-object). |
|
||||
| A2A | No equivalent present, response only contains a [message](https://a2a-protocol.org/latest/specification/#64-message-object) or [task](https://a2a-protocol.org/latest/specification/#61-task-object). |
|
||||
| Protocol Activity | [No equivalent present.](https://github.com/microsoft/Agents/blob/main/specs/activity/protocol-activity.md) |
|
||||
|
||||
Vendored
+2
-1
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"dotnet.defaultSolution": "agent-framework-dotnet.slnx",
|
||||
"git.openRepositoryInParentFolders": "always"
|
||||
"git.openRepositoryInParentFolders": "always",
|
||||
"chat.agent.enabled": true
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<NoWarn>$(NoWarn);NU5128</NoWarn>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<ProjectsCoreTargetFrameworks>net9.0</ProjectsCoreTargetFrameworks>
|
||||
<ProjectsCoreTargetFrameworks>net9.0;net8.0</ProjectsCoreTargetFrameworks>
|
||||
<ProjectsDebugCoreTargetFrameworks>net9.0</ProjectsDebugCoreTargetFrameworks>
|
||||
<ProjectsTargetFrameworks>net9.0;net8.0;netstandard2.0;net472</ProjectsTargetFrameworks>
|
||||
<ProjectsDebugTargetFrameworks>net9.0;net472</ProjectsDebugTargetFrameworks>
|
||||
<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net7.0'))">true</IsAotCompatible>
|
||||
|
||||
@@ -10,88 +10,78 @@
|
||||
<AspireAppHostSdkVersion>9.5.1</AspireAppHostSdkVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<!-- Azure.* -->
|
||||
<PackageVersion Include="Aspire.Azure.AI.OpenAI" Version="9.5.0-preview.1.25474.7" />
|
||||
<!-- Aspire.* -->
|
||||
<PackageVersion Include="Aspire.Azure.AI.OpenAI" Version="9.5.1-preview.1.25502.11" />
|
||||
<PackageVersion Include="Aspire.Hosting.AppHost" Version="$(AspireAppHostSdkVersion)" />
|
||||
<PackageVersion Include="Aspire.Hosting.Azure.CognitiveServices" Version="$(AspireAppHostSdkVersion)" />
|
||||
<PackageVersion Include="Aspire.Microsoft.Azure.Cosmos" Version="$(AspireAppHostSdkVersion)" />
|
||||
<PackageVersion Include="Aspire.Hosting.Testing" Version="$(AspireAppHostSdkVersion)" />
|
||||
<PackageVersion Include="Azure.AI.Agents.Persistent" Version="1.2.0-beta.5" />
|
||||
<PackageVersion Include="Azure.AI.OpenAI" Version="2.3.0-beta.2" />
|
||||
<PackageVersion Include="Azure.Identity" Version="1.16.0" />
|
||||
<PackageVersion Include="Azure.Monitor.OpenTelemetry.Exporter" Version="1.4.0" />
|
||||
<PackageVersion Include="CommunityToolkit.Aspire.OllamaSharp" Version="9.8.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="9.0.9" />
|
||||
<PackageVersion Include="Microsoft.Extensions.AI.AzureAIInference" Version="9.9.1-preview.1.25474.6" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="9.9.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.ServiceDiscovery" Version="$(AspireAppHostSdkVersion)" />
|
||||
<!-- Azure.* -->
|
||||
<PackageVersion Include="Azure.AI.Agents.Persistent" Version="1.2.0-beta.6" />
|
||||
<PackageVersion Include="Azure.AI.OpenAI" Version="2.5.0-beta.1" />
|
||||
<PackageVersion Include="Azure.Identity" Version="1.17.0" />
|
||||
<PackageVersion Include="Azure.Monitor.OpenTelemetry.Exporter" Version="1.4.0" />
|
||||
<!-- System.* -->
|
||||
<PackageVersion Include="System.CodeDom" Version="9.0.10" />
|
||||
<PackageVersion Include="System.Collections.Immutable" Version="9.0.10" />
|
||||
<PackageVersion Include="System.CommandLine" Version="2.0.0-rc.2.25502.107" />
|
||||
<PackageVersion Include="System.Diagnostics.DiagnosticSource" Version="9.0.10" />
|
||||
<PackageVersion Include="System.Net.ServerSentEvents" Version="9.0.10" />
|
||||
<PackageVersion Include="System.Text.Json" Version="9.0.10" />
|
||||
<PackageVersion Include="System.Threading.Channels" Version="9.0.10" />
|
||||
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.6.3" />
|
||||
<PackageVersion Include="Microsoft.Bcl.HashCode" Version="6.0.0" />
|
||||
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.10" />
|
||||
<!-- OpenTelemetry -->
|
||||
<PackageVersion Include="OpenTelemetry" Version="1.12.0" />
|
||||
<PackageVersion Include="OpenTelemetry.Api" Version="1.12.0" />
|
||||
<PackageVersion Include="OpenTelemetry.Exporter.Console" Version="1.12.0" />
|
||||
<PackageVersion Include="OpenTelemetry.Exporter.InMemory" Version="1.12.0" />
|
||||
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.12.0" />
|
||||
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.12.0" />
|
||||
<PackageVersion Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.12.0" />
|
||||
<PackageVersion Include="OpenTelemetry.Instrumentation.Http" Version="1.12.0" />
|
||||
<PackageVersion Include="OpenTelemetry.Instrumentation.Runtime" Version="1.12.0" />
|
||||
<PackageVersion Include="Microsoft.Azure.Cosmos" Version="3.53.1" />
|
||||
<!-- Newtonsoft (Required by CosmosClient) -->
|
||||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
|
||||
<!-- Microsoft.AspNetCore.* -->
|
||||
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="9.0.10" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore.SwaggerUI" Version="9.0.4" />
|
||||
<!-- System.* -->
|
||||
<PackageVersion Include="System.Linq.Async" Version="6.0.3" />
|
||||
<PackageVersion Include="System.Net.ServerSentEvents" Version="9.0.9" />
|
||||
<PackageVersion Include="System.Text.Json" Version="9.0.9" />
|
||||
<PackageVersion Include="System.CodeDom" Version="9.0.9" />
|
||||
<PackageVersion Include="System.Collections.Immutable" Version="9.0.9" />
|
||||
<PackageVersion Include="System.CommandLine" Version="2.0.0-rc.1.25451.107" />
|
||||
<PackageVersion Include="System.Diagnostics.DiagnosticSource" Version="9.0.9" />
|
||||
<PackageVersion Include="System.Threading.Channels" Version="9.0.9" />
|
||||
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.6.3" />
|
||||
<!-- OpenTelemetry -->
|
||||
<PackageVersion Include="OpenTelemetry" Version="1.12.0" />
|
||||
<PackageVersion Include="OpenTelemetry.Exporter.Console" Version="1.12.0" />
|
||||
<PackageVersion Include="OpenTelemetry.Exporter.InMemory" Version="1.12.0" />
|
||||
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.12.0" />
|
||||
<!-- Microsoft.Extensions.* -->
|
||||
<PackageVersion Include="Microsoft.Bcl.HashCode" Version="6.0.0" />
|
||||
<PackageVersion Include="Microsoft.Agents.AI" Version="1.0.0-preview.251001.3" />
|
||||
<PackageVersion Include="Microsoft.Agents.AI.Abstractions" Version="1.0.0-preview.251001.3" />
|
||||
<PackageVersion Include="Microsoft.Agents.AI.AzureAI" Version="1.0.0-preview.251001.3" />
|
||||
<PackageVersion Include="Microsoft.Agents.AI.OpenAI" Version="1.0.0-preview.251001.3" />
|
||||
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.9" />
|
||||
<PackageVersion Include="OpenAI" Version="2.4.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.AI" Version="9.9.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="9.9.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="9.9.0-preview.1.25458.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="9.0.9" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.9" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.9" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.9" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="9.0.9" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="9.0.9" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.9" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.9" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.9" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.9" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.9" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="9.0.9" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Testing" Version="9.0.9" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.9" />
|
||||
<PackageVersion Include="Microsoft.Extensions.AI" Version="9.10.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="9.10.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.AI.AzureAIInference" Version="9.10.0-preview.1.25513.3" />
|
||||
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="9.10.0-preview.1.25513.3" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="9.10.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.Extensions.ServiceDiscovery" Version="$(AspireAppHostSdkVersion)" />
|
||||
<!-- Vector Stores -->
|
||||
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.InMemory" Version="1.65.0-preview" />
|
||||
<PackageVersion Include="Microsoft.SemanticKernel" Version="1.65.0" />
|
||||
<PackageVersion Include="Microsoft.SemanticKernel.Agents.Core" Version="1.65.0" />
|
||||
<PackageVersion Include="Microsoft.SemanticKernel.Agents.OpenAI" Version="1.65.0-preview" />
|
||||
<PackageVersion Include="Microsoft.SemanticKernel.Agents.AzureAI" Version="1.65.0-preview" />
|
||||
<PackageVersion Include="Microsoft.SemanticKernel" Version="1.66.0" />
|
||||
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.InMemory" Version="1.66.0-preview" />
|
||||
<PackageVersion Include="Microsoft.SemanticKernel.Agents.Core" Version="1.66.0" />
|
||||
<PackageVersion Include="Microsoft.SemanticKernel.Agents.OpenAI" Version="1.66.0-preview" />
|
||||
<PackageVersion Include="Microsoft.SemanticKernel.Agents.AzureAI" Version="1.66.0-preview" />
|
||||
<!-- Agent SDKs -->
|
||||
<PackageVersion Include="Microsoft.Agents.CopilotStudio.Client" Version="1.2.41" />
|
||||
<!-- A2A -->
|
||||
<PackageVersion Include="A2A" Version="0.3.1-preview" />
|
||||
<PackageVersion Include="A2A.AspNetCore" Version="0.3.1-preview" />
|
||||
<!-- MCP -->
|
||||
<PackageVersion Include="ModelContextProtocol" Version="0.4.0-preview.1" />
|
||||
<PackageVersion Include="ModelContextProtocol" Version="0.4.0-preview.2" />
|
||||
<!-- Inference SDKs -->
|
||||
<PackageVersion Include="Anthropic.SDK" Version="5.5.3" />
|
||||
<PackageVersion Include="AWSSDK.Extensions.Bedrock.MEAI" Version="4.0.3.5" />
|
||||
<PackageVersion Include="Anthropic.SDK" Version="5.6.0" />
|
||||
<PackageVersion Include="AWSSDK.Extensions.Bedrock.MEAI" Version="4.0.4" />
|
||||
<PackageVersion Include="Microsoft.ML.OnnxRuntimeGenAI" Version="0.9.2" />
|
||||
<PackageVersion Include="OllamaSharp" Version="5.4.7" />
|
||||
<PackageVersion Include="OpenAI" Version="2.5.0" />
|
||||
<!-- Identity -->
|
||||
<PackageVersion Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.77.1" />
|
||||
<!-- Workflows -->
|
||||
@@ -99,12 +89,14 @@
|
||||
<PackageVersion Include="Microsoft.Bot.ObjectModel.Json" Version="1.2025.1003.2" />
|
||||
<PackageVersion Include="Microsoft.Bot.ObjectModel.PowerFx" Version="1.2025.1003.2" />
|
||||
<PackageVersion Include="Microsoft.PowerFx.Interpreter" Version="1.4.0" />
|
||||
<!-- Community -->
|
||||
<PackageVersion Include="System.Linq.Async" Version="6.0.3" />
|
||||
<!-- Test -->
|
||||
<PackageVersion Include="FluentAssertions" Version="8.7.0" />
|
||||
<PackageVersion Include="FluentAssertions" Version="8.7.1" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
|
||||
<PackageVersion Include="Moq" Version="[4.18.4]" />
|
||||
<PackageVersion Include="Microsoft.SemanticKernel.Agents.Abstractions" Version="1.65.0" />
|
||||
<PackageVersion Include="Microsoft.SemanticKernel.Agents.Yaml" Version="1.65.0-beta" />
|
||||
<PackageVersion Include="Microsoft.SemanticKernel.Agents.Abstractions" Version="1.66.0" />
|
||||
<PackageVersion Include="Microsoft.SemanticKernel.Agents.Yaml" Version="1.66.0-beta" />
|
||||
<PackageVersion Include="xunit" Version="2.9.3" />
|
||||
<PackageVersion Include="xunit.abstractions" Version="2.0.3" />
|
||||
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.3" />
|
||||
@@ -113,7 +105,6 @@
|
||||
<!-- Symbols -->
|
||||
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
|
||||
<!-- Toolset -->
|
||||
<PackageVersion Include="Microsoft.Net.Compilers.Toolset" Version="4.14.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers">
|
||||
@@ -130,7 +121,7 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageVersion Include="Moq.Analyzers" Version="0.3.0" />
|
||||
<PackageVersion Include="Moq.Analyzers" Version="0.3.1" />
|
||||
<PackageReference Include="Moq.Analyzers">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
@@ -21,6 +21,10 @@
|
||||
<Folder Name="/Samples/GettingStarted/">
|
||||
<File Path="samples/GettingStarted/README.md" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/GettingStarted/A2A/">
|
||||
<File Path="samples/GettingStarted/A2A/README.md" />
|
||||
<Project Path="samples/GettingStarted/A2A/A2AAgent_AsFunctionTools/A2AAgent_AsFunctionTools.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/GettingStarted/AgentProviders/">
|
||||
<File Path="samples/GettingStarted/AgentProviders/README.md" />
|
||||
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_A2A/Agent_With_A2A.csproj" />
|
||||
@@ -113,6 +117,7 @@
|
||||
<Project Path="samples/GettingStarted/Workflows/HumanInTheLoop/HumanInTheLoopBasic/HumanInTheLoopBasic.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/GettingStarted/Workflows/Observability/">
|
||||
<Project Path="samples/GettingStarted/Workflows/Observability/ApplicationInsights/ApplicationInsights.csproj" />
|
||||
<Project Path="samples/GettingStarted/Workflows/Observability/AspireDashboard/AspireDashboard.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/GettingStarted/Workflows/Visualization/">
|
||||
@@ -124,59 +129,11 @@
|
||||
<Project Path="samples/GettingStarted/Workflows/_Foundational/03_AgentsInWorkflows/03_AgentsInWorkflows.csproj" />
|
||||
<Project Path="samples/GettingStarted/Workflows/_Foundational/04_AgentWorkflowPatterns/04_AgentWorkflowPatterns.csproj" />
|
||||
<Project Path="samples/GettingStarted/Workflows/_Foundational/05_MultiModelService/05_MultiModelService.csproj" />
|
||||
<Project Path="samples/GettingStarted/Workflows/_Foundational/06_SubWorkflows/06_SubWorkflows.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/SemanticKernelMigration/">
|
||||
<File Path="samples/SemanticKernelMigration/README.md" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/SemanticKernelMigration/AzureAIFoundry/">
|
||||
<Project Path="samples/SemanticKernelMigration/AzureAIFoundry/Step01_Basics/AzureAIFoundry_Step01_Basics.csproj" />
|
||||
<Project Path="samples/SemanticKernelMigration/AzureAIFoundry/Step02_ToolCall/AzureAIFoundry_Step02_ToolCall.csproj" />
|
||||
<Project Path="samples/SemanticKernelMigration/AzureAIFoundry/Step03_DependencyInjection/AzureAIFoundry_Step03_DependencyInjection.csproj" />
|
||||
<Project Path="samples/SemanticKernelMigration/AzureAIFoundry/Step04_CodeInterpreter/AzureAIFoundry_Step04_CodeInterpreter.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/SemanticKernelMigration/Playground/">
|
||||
<File Path="samples/SemanticKernelMigration/Playground/README.md" />
|
||||
<Project Path="samples/SemanticKernelMigration/Playground/SemanticKernelBasic/SemanticKernelBasic.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/SemanticKernelMigration/OpenAI/">
|
||||
<Project Path="samples/SemanticKernelMigration/OpenAI/Step01_Basics/OpenAI_Step01_Basics.csproj" />
|
||||
<Project Path="samples/SemanticKernelMigration/OpenAI/Step02_ToolCall/OpenAI_Step02_ToolCall.csproj" />
|
||||
<Project Path="samples/SemanticKernelMigration/OpenAI/Step03_DependencyInjection/OpenAI_Step03_DependencyInjection.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/SemanticKernelMigration/OpenAIAssistants/">
|
||||
<Project Path="samples/SemanticKernelMigration/OpenAIAssistants/Step01_Basics/OpenAIAssistants_Step01_Basics.csproj" />
|
||||
<Project Path="samples/SemanticKernelMigration/OpenAIAssistants/Step02_ToolCall/OpenAIAssistants_Step02_ToolCall.csproj" />
|
||||
<Project Path="samples/SemanticKernelMigration/OpenAIAssistants/Step03_DependencyInjection/OpenAIAssistants_Step03_DependencyInjection.csproj" />
|
||||
<Project Path="samples/SemanticKernelMigration/OpenAIAssistants/Step04_CodeInterpreter/OpenAIAssistants_Step04_CodeInterpreter.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/SemanticKernelMigration/OpenAIResponses/">
|
||||
<Project Path="samples/SemanticKernelMigration/OpenAIResponses/Step01_Basics/OpenAIResponses_Step01_Basics.csproj" />
|
||||
<Project Path="samples/SemanticKernelMigration/OpenAIResponses/Step02_ReasoningModel/OpenAIResponses_Step02_ReasoningModel.csproj" />
|
||||
<Project Path="samples/SemanticKernelMigration/OpenAIResponses/Step03_ToolCall/OpenAIResponses_Step03_ToolCall.csproj" />
|
||||
<Project Path="samples/SemanticKernelMigration/OpenAIResponses/Step04_DependencyInjection/OpenAIResponses_Step04_DependencyInjection.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/SemanticKernelMigration/AzureOpenAI/">
|
||||
<Project Path="samples/SemanticKernelMigration/AzureOpenAI/Step01_Basics/AzureOpenAI_Step01_Basics.csproj" />
|
||||
<Project Path="samples/SemanticKernelMigration/AzureOpenAI/Step02_ToolCall/AzureOpenAI_Step02_ToolCall.csproj" />
|
||||
<Project Path="samples/SemanticKernelMigration/AzureOpenAI/Step03_DependencyInjection/AzureOpenAI_Step03_DependencyInjection.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/SemanticKernelMigration/AzureOpenAIAssistants/">
|
||||
<Project Path="samples/SemanticKernelMigration/AzureOpenAIAssistants/Step01_Basics/AzureOpenAIAssistants_Step01_Basics.csproj" />
|
||||
<Project Path="samples/SemanticKernelMigration/AzureOpenAIAssistants/Step02_ToolCall/AzureOpenAIAssistants_Step02_ToolCall.csproj" />
|
||||
<Project Path="samples/SemanticKernelMigration/AzureOpenAIAssistants/Step03_DependencyInjection/AzureOpenAIAssistants_Step03_DependencyInjection.csproj" />
|
||||
<Project Path="samples/SemanticKernelMigration/AzureOpenAIAssistants/Step04_CodeInterpreter/AzureOpenAIAssistants_Step04_CodeInterpreter.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/SemanticKernelMigration/AzureOpenAIResponses/">
|
||||
<Project Path="samples/SemanticKernelMigration/AzureOpenAIResponses/Step01_Basics/AzureOpenAIResponses_Step01_Basics.csproj" />
|
||||
<Project Path="samples/SemanticKernelMigration/AzureOpenAIResponses/Step02_ReasoningModel/AzureOpenAIResponses_Step02_ReasoningModel.csproj" />
|
||||
<Project Path="samples/SemanticKernelMigration/AzureOpenAIResponses/Step03_ToolCall/AzureOpenAIResponses_Step03_ToolCall.csproj" />
|
||||
<Project Path="samples/SemanticKernelMigration/AzureOpenAIResponses/Step04_DependencyInjection/AzureOpenAIResponses_Step04_DependencyInjection.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/SemanticKernelMigration/AgentOrchestrations/">
|
||||
<Project Path="samples/SemanticKernelMigration/AgentOrchestrations/Step01_Concurrent/AgentOrchestrations_Step01_Concurrent.csproj" />
|
||||
<Project Path="samples/SemanticKernelMigration/AgentOrchestrations/Step02_Sequential/AgentOrchestrations_Step02_Sequential.csproj" />
|
||||
<Project Path="samples/SemanticKernelMigration/AgentOrchestrations/Step03_Handoff/AgentOrchestrations_Step03_Handoff.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Solution Items/">
|
||||
<File Path=".editorconfig" />
|
||||
<File Path=".gitignore" />
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
<PropertyGroup>
|
||||
<!-- Central version prefix - applies to all nuget packages. -->
|
||||
<VersionPrefix>1.0.0</VersionPrefix>
|
||||
<PackageVersion Condition="'$(VersionSuffix)' != ''">$(VersionPrefix)-$(VersionSuffix).251007.1</PackageVersion>
|
||||
<PackageVersion Condition="'$(VersionSuffix)' == ''">$(VersionPrefix)-preview.251007.1</PackageVersion>
|
||||
<PackageVersion Condition="'$(VersionSuffix)' != ''">$(VersionPrefix)-$(VersionSuffix).251016.1</PackageVersion>
|
||||
<PackageVersion Condition="'$(VersionSuffix)' == ''">$(VersionPrefix)-preview.251016.1</PackageVersion>
|
||||
<GitTag>1.0.0-preview.251016.1</GitTag>
|
||||
|
||||
<Configurations>Debug;Release;Publish</Configurations>
|
||||
<IsPackable>true</IsPackable>
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
<PackageReference Include="A2A" />
|
||||
<PackageReference Include="System.CommandLine" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" />
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" VersionOverride="10.0.0-preview.5.25277.114" />
|
||||
<PackageReference Include="System.Net.ServerSentEvents" VersionOverride="10.0.0-preview.5.25277.114" />
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" VersionOverride="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="System.Net.ServerSentEvents" VersionOverride="10.0.0-rc.2.25502.107" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
<PackageReference Include="A2A.AspNetCore" />
|
||||
<PackageReference Include="Azure.AI.Agents.Persistent" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" VersionOverride="10.0.0-preview.5.25277.114" />
|
||||
<PackageReference Include="System.Net.ServerSentEvents" VersionOverride="10.0.0-preview.5.25277.114" />
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" VersionOverride="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="System.Net.ServerSentEvents" VersionOverride="10.0.0-rc.2.25502.107" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
|
||||
<!-- A2A dependency -->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Net.ServerSentEvents" VersionOverride="10.0.0-preview.5.25277.114" />
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" VersionOverride="10.0.0-preview.5.25277.114" />
|
||||
<PackageReference Include="System.Net.ServerSentEvents" VersionOverride="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" VersionOverride="10.0.0-rc.2.25502.107" />
|
||||
</ItemGroup>
|
||||
<!-- A2A dependency -->
|
||||
|
||||
|
||||
@@ -66,6 +66,25 @@ builder.AddAIAgent("knights-and-knaves", (sp, key) =>
|
||||
#pragma warning restore VSTHRD002
|
||||
});
|
||||
|
||||
// Workflow consisting of multiple specialized agents
|
||||
var chemistryAgent = builder.AddAIAgent("chemist",
|
||||
instructions: "You are a chemistry expert. Answer thinking from the chemistry perspective",
|
||||
description: "An agent that helps with chemistry.",
|
||||
chatClientServiceKey: "chat-model");
|
||||
|
||||
var mathsAgent = builder.AddAIAgent("mathematician",
|
||||
instructions: "You are a mathematics expert. Answer thinking from the maths perspective",
|
||||
description: "An agent that helps with mathematics.",
|
||||
chatClientServiceKey: "chat-model");
|
||||
|
||||
var literatureAgent = builder.AddAIAgent("literator",
|
||||
instructions: "You are a literature expert. Answer thinking from the literature perspective",
|
||||
description: "An agent that helps with literature.",
|
||||
chatClientServiceKey: "chat-model");
|
||||
|
||||
builder.AddSequentialWorkflow("science-sequential-workflow", [chemistryAgent, mathsAgent, literatureAgent]).AddAsAIAgent();
|
||||
builder.AddConcurrentWorkflow("science-concurrent-workflow", [chemistryAgent, mathsAgent, literatureAgent]).AddAsAIAgent();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
app.MapOpenApi();
|
||||
@@ -89,6 +108,13 @@ app.MapA2A(agentName: "knights-and-knaves", path: "/a2a/knights-and-knaves", age
|
||||
app.MapOpenAIResponses("pirate");
|
||||
app.MapOpenAIResponses("knights-and-knaves");
|
||||
|
||||
app.MapOpenAIChatCompletions("pirate");
|
||||
app.MapOpenAIChatCompletions("knights-and-knaves");
|
||||
|
||||
// workflow-agents
|
||||
app.MapOpenAIResponses("science-sequential-workflow");
|
||||
app.MapOpenAIResponses("science-concurrent-workflow");
|
||||
|
||||
// Map the agents HTTP endpoints
|
||||
app.MapAgentDiscovery("/agents");
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ using Microsoft.Extensions.AI;
|
||||
|
||||
namespace AgentWebChat.Web;
|
||||
|
||||
internal sealed class A2AAgentClient : IAgentClient
|
||||
internal sealed class A2AAgentClient : AgentClientBase
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly Uri _uri;
|
||||
@@ -25,7 +25,7 @@ internal sealed class A2AAgentClient : IAgentClient
|
||||
this._uri = baseUri;
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(
|
||||
public async override IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(
|
||||
string agentName,
|
||||
IList<ChatMessage> messages,
|
||||
string? threadId = null,
|
||||
@@ -126,7 +126,7 @@ internal sealed class A2AAgentClient : IAgentClient
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<AgentCard?> GetAgentCardAsync(string agentName, CancellationToken cancellationToken = default)
|
||||
public async override Task<AgentCard?> GetAgentCardAsync(string agentName, CancellationToken cancellationToken = default)
|
||||
{
|
||||
this._logger.LogInformation("Retrieving agent card for {Agent}", agentName);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<NoWarn>$(NoWarn);CA1812</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -16,8 +17,8 @@
|
||||
|
||||
<!-- A2A dependency -->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Net.ServerSentEvents" VersionOverride="10.0.0-preview.5.25277.114" />
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" VersionOverride="10.0.0-preview.5.25277.114" />
|
||||
<PackageReference Include="System.Net.ServerSentEvents" VersionOverride="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" VersionOverride="10.0.0-rc.2.25502.107" />
|
||||
</ItemGroup>
|
||||
<!-- A2A dependency -->
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
@inject ILogger<Home> Logger
|
||||
@inject A2AAgentClient A2AActorClient
|
||||
@inject OpenAIResponsesAgentClient OpenAIResponsesAgentClient
|
||||
@inject OpenAIChatCompletionsAgentClient OpenAIChatCompletionsAgentClient
|
||||
@rendermode InteractiveServer
|
||||
@using System.Text
|
||||
@using System.Text.Json
|
||||
@@ -52,14 +53,18 @@
|
||||
<label for="protocol-select" class="protocol-select-label">Choose communication protocol:</label>
|
||||
<div class="protocol-select-wrapper">
|
||||
<select id="protocol-select" class="protocol-select" @bind="selectedProtocol" disabled="@(isStreaming)">
|
||||
<option value="OpenAIResponses">OpenAI Responses</option>
|
||||
<option value="A2A">A2A (Agent-to-Agent)</option>
|
||||
<option value="OpenAIResponses">OpenAI Responses</option>
|
||||
<option value="OpenAIChatCompletions">OpenAI ChatCompletions</option>
|
||||
<option value="A2A">A2A (Agent-to-Agent)</option>
|
||||
</select>
|
||||
<div class="protocol-info">
|
||||
@switch (selectedProtocol)
|
||||
{
|
||||
case Protocol.OpenAIResponses:
|
||||
<span class="protocol-description">ÖŽ OpenAI Responses</span>
|
||||
break;
|
||||
case Protocol.OpenAIChatCompletions:
|
||||
<span class="protocol-description">ÖŽ OpenAI ChatCompletions</span>
|
||||
break;
|
||||
case Protocol.A2A:
|
||||
default:
|
||||
@@ -903,7 +908,8 @@
|
||||
private enum Protocol
|
||||
{
|
||||
A2A, // Agent-to-Agent protocol
|
||||
OpenAIResponses
|
||||
OpenAIResponses,
|
||||
OpenAIChatCompletions
|
||||
}
|
||||
|
||||
private sealed class Conversation
|
||||
@@ -1080,11 +1086,11 @@
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
// Select the appropriate client based on protocol
|
||||
IAgentClient agentClient = selectedProtocol switch
|
||||
AgentClientBase agentClient = selectedProtocol switch
|
||||
{
|
||||
Protocol.OpenAIResponses => OpenAIResponsesAgentClient,
|
||||
Protocol.OpenAIChatCompletions => OpenAIChatCompletionsAgentClient,
|
||||
Protocol.A2A or _ => A2AActorClient
|
||||
};
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace AgentWebChat.Web;
|
||||
/// <summary>
|
||||
/// Interface for clients that can interact with agents and provide streaming responses.
|
||||
/// </summary>
|
||||
public interface IAgentClient
|
||||
internal abstract class AgentClientBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Runs an agent with the specified messages and returns a streaming response.
|
||||
@@ -19,7 +19,7 @@ public interface IAgentClient
|
||||
/// <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>
|
||||
IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(
|
||||
public abstract IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(
|
||||
string agentName,
|
||||
IList<ChatMessage> messages,
|
||||
string? threadId = null,
|
||||
@@ -31,7 +31,8 @@ public interface IAgentClient
|
||||
/// <param name="agentName">The name of the agent.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The agent card if supported, null otherwise.</returns>
|
||||
Task<AgentCard?> GetAgentCardAsync(string agentName, CancellationToken cancellationToken = default);
|
||||
public virtual Task<AgentCard?> GetAgentCardAsync(string agentName, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult<AgentCard?>(null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.ClientModel;
|
||||
using System.ClientModel.Primitives;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
using OpenAI;
|
||||
using OpenAI.Chat;
|
||||
using ChatMessage = Microsoft.Extensions.AI.ChatMessage;
|
||||
|
||||
namespace AgentWebChat.Web;
|
||||
|
||||
/// <summary>
|
||||
/// Is a simple frontend client which exercises the ability of exposed agent to communicate via OpenAI ChatCompletions protocol.
|
||||
/// </summary>
|
||||
internal sealed class OpenAIChatCompletionsAgentClient(HttpClient httpClient) : AgentClientBase
|
||||
{
|
||||
public async override IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(
|
||||
string agentName,
|
||||
IList<ChatMessage> messages,
|
||||
string? threadId = null,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
OpenAIClientOptions options = new()
|
||||
{
|
||||
Endpoint = new Uri(httpClient.BaseAddress!, $"/{agentName}/v1/"),
|
||||
Transport = new HttpClientPipelineTransport(httpClient)
|
||||
};
|
||||
|
||||
var openAiClient = new ChatClient(model: "myModel!", credential: new ApiKeyCredential("dummy-key"), options: options).AsIChatClient();
|
||||
await foreach (var update in openAiClient.GetStreamingResponseAsync(messages, cancellationToken: cancellationToken))
|
||||
{
|
||||
yield return new AgentRunResponseUpdate(update);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.ClientModel;
|
||||
using System.ClientModel.Primitives;
|
||||
using System.Runtime.CompilerServices;
|
||||
using A2A;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
using OpenAI;
|
||||
@@ -13,16 +13,9 @@ namespace AgentWebChat.Web;
|
||||
/// <summary>
|
||||
/// Is a simple frontend client which exercises the ability of exposed agent to communicate via OpenAI Responses protocol.
|
||||
/// </summary>
|
||||
internal sealed class OpenAIResponsesAgentClient : IAgentClient
|
||||
internal sealed class OpenAIResponsesAgentClient(HttpClient httpClient) : AgentClientBase
|
||||
{
|
||||
private readonly Uri _baseUri;
|
||||
|
||||
public OpenAIResponsesAgentClient(string baseUri)
|
||||
{
|
||||
this._baseUri = new Uri(baseUri.TrimEnd('/'));
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(
|
||||
public async override IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(
|
||||
string agentName,
|
||||
IList<ChatMessage> messages,
|
||||
string? threadId = null,
|
||||
@@ -30,7 +23,8 @@ internal sealed class OpenAIResponsesAgentClient : IAgentClient
|
||||
{
|
||||
OpenAIClientOptions options = new()
|
||||
{
|
||||
Endpoint = new Uri(this._baseUri, $"/{agentName}/v1/")
|
||||
Endpoint = new Uri(httpClient.BaseAddress!, $"/{agentName}/v1/"),
|
||||
Transport = new HttpClientPipelineTransport(httpClient)
|
||||
};
|
||||
|
||||
var openAiClient = new OpenAIResponseClient(model: "myModel!", credential: new ApiKeyCredential("dummy-key"), options: options).AsIChatClient();
|
||||
@@ -44,7 +38,4 @@ internal sealed class OpenAIResponsesAgentClient : IAgentClient
|
||||
yield return new AgentRunResponseUpdate(update);
|
||||
}
|
||||
}
|
||||
|
||||
public Task<AgentCard?> GetAgentCardAsync(string agentName, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult<AgentCard?>(null!);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,9 @@ Uri a2aAddress = new("http://localhost:5390/a2a");
|
||||
|
||||
builder.Services.AddHttpClient<AgentDiscoveryClient>(client => client.BaseAddress = baseAddress);
|
||||
builder.Services.AddSingleton(sp => new A2AAgentClient(sp.GetRequiredService<ILogger<A2AAgentClient>>(), a2aAddress));
|
||||
builder.Services.AddSingleton(sp => new OpenAIResponsesAgentClient("http://localhost:5390"));
|
||||
|
||||
builder.Services.AddHttpClient<OpenAIResponsesAgentClient>(client => client.BaseAddress = baseAddress);
|
||||
builder.Services.AddHttpClient<OpenAIChatCompletionsAgentClient>(client => client.BaseAddress = baseAddress);
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="A2A" />
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" />
|
||||
<PackageReference Include="System.Net.ServerSentEvents" VersionOverride="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" VersionOverride="10.0.0-rc.2.25502.107" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.A2A\Microsoft.Agents.AI.A2A.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This sample shows how to represent an A2A agent as a set of function tools, where each function tool
|
||||
// corresponds to a skill of the A2A agent, and register these function tools with another AI agent so
|
||||
// it can leverage the A2A agent's skills.
|
||||
|
||||
using System.Text.RegularExpressions;
|
||||
using A2A;
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
using OpenAI;
|
||||
|
||||
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";
|
||||
var a2aAgentHost = Environment.GetEnvironmentVariable("A2A_AGENT_HOST") ?? throw new InvalidOperationException("A2A_AGENT_HOST is not set.");
|
||||
|
||||
// Initialize an A2ACardResolver to get an A2A agent card.
|
||||
A2ACardResolver agentCardResolver = new(new Uri(a2aAgentHost));
|
||||
|
||||
// Get the agent card
|
||||
AgentCard agentCard = await agentCardResolver.GetAgentCardAsync();
|
||||
|
||||
// Create an instance of the AIAgent for an existing A2A agent specified by the agent card.
|
||||
AIAgent a2aAgent = await agentCard.GetAIAgentAsync();
|
||||
|
||||
// Create the main agent, and provide the a2a agent skills as a function tools.
|
||||
AIAgent agent = new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new AzureCliCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(
|
||||
instructions: "You are a helpful assistant that helps people with travel planning.",
|
||||
tools: [.. CreateFunctionTools(a2aAgent, agentCard)]
|
||||
);
|
||||
|
||||
// Invoke the agent and output the text result.
|
||||
Console.WriteLine(await agent.RunAsync("Plan a route from '1600 Amphitheatre Parkway, Mountain View, CA' to 'San Francisco International Airport' avoiding tolls"));
|
||||
|
||||
static IEnumerable<AIFunction> CreateFunctionTools(AIAgent a2aAgent, AgentCard agentCard)
|
||||
{
|
||||
foreach (var skill in agentCard.Skills)
|
||||
{
|
||||
// A2A agent skills don't have schemas describing the expected shape of their inputs and outputs.
|
||||
// Schemas can be beneficial for AI models to better understand the skill's contract, generate
|
||||
// the skill's input accordingly and to know what to expect in the skill's output.
|
||||
// However, the A2A specification defines properties such as name, description, tags, examples,
|
||||
// inputModes, and outputModes to provide context about the skill's purpose, capabilities, usage,
|
||||
// and supported MIME types. These properties are added to the function tool description to help
|
||||
// the model determine the appropriate shape of the skill's input and output.
|
||||
AIFunctionFactoryOptions options = new()
|
||||
{
|
||||
Name = FunctionNameSanitizer.Sanitize(skill.Name),
|
||||
Description = $$"""
|
||||
{
|
||||
"description": "{{skill.Description}}",
|
||||
"tags": "[{{string.Join(", ", skill.Tags ?? [])}}]",
|
||||
"examples": "[{{string.Join(", ", skill.Examples ?? [])}}]",
|
||||
"inputModes": "[{{string.Join(", ", skill.InputModes ?? [])}}]",
|
||||
"outputModes": "[{{string.Join(", ", skill.OutputModes ?? [])}}]"
|
||||
}
|
||||
""",
|
||||
};
|
||||
|
||||
yield return AIFunctionFactory.Create(RunAgentAsync, options);
|
||||
}
|
||||
|
||||
async Task<string> RunAgentAsync(string input, CancellationToken cancellationToken)
|
||||
{
|
||||
var response = await a2aAgent.RunAsync(input, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return response.Text;
|
||||
}
|
||||
}
|
||||
|
||||
internal static partial class FunctionNameSanitizer
|
||||
{
|
||||
public static string Sanitize(string name)
|
||||
{
|
||||
return InvalidNameCharsRegex().Replace(name, "_");
|
||||
}
|
||||
|
||||
[GeneratedRegex("[^0-9A-Za-z]+")]
|
||||
private static partial Regex InvalidNameCharsRegex();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
# A2A Agent as Function Tools
|
||||
|
||||
This sample demonstrates how to represent an A2A agent as a set of function tools, where each function tool corresponds to a skill of the A2A agent,
|
||||
and register these function tools with another AI agent so it can leverage the A2A agent's skills.
|
||||
|
||||
# Prerequisites
|
||||
|
||||
Before you begin, ensure you have the following prerequisites:
|
||||
|
||||
- .NET 8.0 SDK or later
|
||||
- Access to the A2A agent host service
|
||||
|
||||
**Note**: These samples need to be run against a valid A2A server. If no A2A server is available, they can be run against the echo-agent that can be
|
||||
spun up locally by following the guidelines at: https://github.com/a2aproject/a2a-dotnet/blob/main/samples/AgentServer/README.md
|
||||
|
||||
Set the following environment variables:
|
||||
|
||||
```powershell
|
||||
$env:A2A_AGENT_HOST="https://your-a2a-agent-host" # Replace with your A2A agent host endpoint
|
||||
$env:AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/" # Replace with your Azure OpenAI resource endpoint
|
||||
$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini" # Optional, defaults to gpt-4o-mini
|
||||
```
|
||||
@@ -0,0 +1,50 @@
|
||||
# Agent-to-Agent (A2A) Samples
|
||||
|
||||
These samples demonstrate how to work with Agent-to-Agent (A2A) specific features in the Agent Framework.
|
||||
|
||||
For other samples that demonstrate how to use AIAgent instances,
|
||||
see the [Getting Started With Agents](../Agents/README.md) samples.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
See the README.md for each sample for the prerequisites for that sample.
|
||||
|
||||
## Samples
|
||||
|
||||
|Sample|Description|
|
||||
|---|---|
|
||||
|[A2A Agent As Function Tools](./A2AAgent_AsFunctionTools/)|This sample demonstrates how to represent an A2A agent as a set of function tools, where each function tool corresponds to a skill of the A2A agent, and register these function tools with another AI agent so it can leverage the A2A agent's skills.|
|
||||
|
||||
## Running the samples from the console
|
||||
|
||||
To run the samples, navigate to the desired sample directory, e.g.
|
||||
|
||||
```powershell
|
||||
cd A2AAgent_AsFunctionTools
|
||||
```
|
||||
|
||||
Set the required environment variables as documented in the sample readme.
|
||||
If the variables are not set, you will be prompted for the values when running the samples.
|
||||
Execute the following command to build the sample:
|
||||
|
||||
```powershell
|
||||
dotnet build
|
||||
```
|
||||
|
||||
Execute the following command to run the sample:
|
||||
|
||||
```powershell
|
||||
dotnet run --no-build
|
||||
```
|
||||
|
||||
Or just build and run in one step:
|
||||
|
||||
```powershell
|
||||
dotnet run
|
||||
```
|
||||
|
||||
## Running the samples from Visual Studio
|
||||
|
||||
Open the solution in Visual Studio and set the desired sample project as the startup project. Then, run the project using the built-in debugger or by pressing `F5`.
|
||||
|
||||
You will be prompted for any required environment variables if they are not already set.
|
||||
@@ -10,8 +10,8 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="A2A" />
|
||||
<PackageReference Include="System.Net.ServerSentEvents" VersionOverride="10.0.0-preview.5.25277.114" />
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" VersionOverride="10.0.0-preview.5.25277.114" />
|
||||
<PackageReference Include="System.Net.ServerSentEvents" VersionOverride="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" VersionOverride="10.0.0-rc.2.25502.107" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
+2
-5
@@ -14,9 +14,6 @@ var endpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_OPENAI_ENDPOINT
|
||||
var apiKey = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_OPENAI_APIKEY");
|
||||
var model = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_MODEL_DEPLOYMENT") ?? "Phi-4-mini-instruct";
|
||||
|
||||
const string JokerName = "Joker";
|
||||
const string JokerInstructions = "You are good at telling jokes.";
|
||||
|
||||
// Since we are using the OpenAI Client SDK, we need to override the default endpoint to point to Azure Foundry.
|
||||
var clientOptions = new OpenAIClientOptions() { Endpoint = new Uri(endpoint) };
|
||||
|
||||
@@ -26,8 +23,8 @@ OpenAIClient client = string.IsNullOrWhiteSpace(apiKey)
|
||||
: new OpenAIClient(new ApiKeyCredential(apiKey), clientOptions);
|
||||
|
||||
AIAgent agent = client
|
||||
.GetChatClient(model)
|
||||
.CreateAIAgent(JokerInstructions, JokerName);
|
||||
.GetChatClient(model)
|
||||
.CreateAIAgent(instructions: "You are good at telling jokes.", name: "Joker");
|
||||
|
||||
// Invoke the agent and output the text result.
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate."));
|
||||
|
||||
+1
-4
@@ -10,14 +10,11 @@ using OpenAI;
|
||||
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";
|
||||
|
||||
const string JokerName = "Joker";
|
||||
const string JokerInstructions = "You are good at telling jokes.";
|
||||
|
||||
AIAgent agent = new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new AzureCliCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(JokerInstructions, JokerName);
|
||||
.CreateAIAgent(instructions: "You are good at telling jokes.", name: "Joker");
|
||||
|
||||
// Invoke the agent and output the text result.
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate."));
|
||||
|
||||
+1
-4
@@ -10,14 +10,11 @@ using OpenAI;
|
||||
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";
|
||||
|
||||
const string JokerName = "Joker";
|
||||
const string JokerInstructions = "You are good at telling jokes.";
|
||||
|
||||
AIAgent agent = new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new AzureCliCredential())
|
||||
.GetOpenAIResponseClient(deploymentName)
|
||||
.CreateAIAgent(JokerInstructions, JokerName);
|
||||
.CreateAIAgent(instructions: "You are good at telling jokes.", name: "Joker");
|
||||
|
||||
// Invoke the agent and output the text result.
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate."));
|
||||
|
||||
@@ -10,12 +10,9 @@ using Microsoft.ML.OnnxRuntimeGenAI;
|
||||
// E.g. C:\repos\Phi-4-mini-instruct-onnx\cpu_and_mobile\cpu-int4-rtn-block-32-acc-level-4
|
||||
var modelPath = Environment.GetEnvironmentVariable("ONNX_MODEL_PATH") ?? throw new InvalidOperationException("ONNX_MODEL_PATH is not set.");
|
||||
|
||||
const string JokerName = "Joker";
|
||||
const string JokerInstructions = "You are good at telling jokes.";
|
||||
|
||||
// Get a chat client for ONNX and use it to construct an AIAgent.
|
||||
using OnnxRuntimeGenAIChatClient chatClient = new(modelPath);
|
||||
AIAgent agent = chatClient.CreateAIAgent(JokerInstructions, JokerName);
|
||||
AIAgent agent = chatClient.CreateAIAgent(instructions: "You are good at telling jokes.", name: "Joker");
|
||||
|
||||
// Invoke the agent and output the text result.
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate."));
|
||||
|
||||
@@ -9,12 +9,9 @@ using OllamaSharp;
|
||||
var endpoint = Environment.GetEnvironmentVariable("OLLAMA_ENDPOINT") ?? throw new InvalidOperationException("OLLAMA_ENDPOINT is not set.");
|
||||
var modelName = Environment.GetEnvironmentVariable("OLLAMA_MODEL_NAME") ?? throw new InvalidOperationException("OLLAMA_MODEL_NAME is not set.");
|
||||
|
||||
const string JokerName = "Joker";
|
||||
const string JokerInstructions = "You are good at telling jokes.";
|
||||
|
||||
// Get a chat client for Ollama and use it to construct an AIAgent.
|
||||
AIAgent agent = new OllamaApiClient(new Uri(endpoint), modelName)
|
||||
.CreateAIAgent(JokerInstructions, JokerName);
|
||||
.CreateAIAgent(instructions: "You are good at telling jokes.", name: "Joker");
|
||||
|
||||
// Invoke the agent and output the text result.
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate."));
|
||||
|
||||
+1
-4
@@ -8,13 +8,10 @@ using OpenAI;
|
||||
var apiKey = Environment.GetEnvironmentVariable("OPENAI_APIKEY") ?? throw new InvalidOperationException("OPENAI_APIKEY is not set.");
|
||||
var model = Environment.GetEnvironmentVariable("OPENAI_MODEL") ?? "gpt-4o-mini";
|
||||
|
||||
const string JokerName = "Joker";
|
||||
const string JokerInstructions = "You are good at telling jokes.";
|
||||
|
||||
AIAgent agent = new OpenAIClient(
|
||||
apiKey)
|
||||
.GetChatClient(model)
|
||||
.CreateAIAgent(JokerInstructions, JokerName);
|
||||
.CreateAIAgent(instructions: "You are good at telling jokes.", name: "Joker");
|
||||
|
||||
// Invoke the agent and output the text result.
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate."));
|
||||
|
||||
@@ -8,13 +8,10 @@ using OpenAI;
|
||||
var apiKey = Environment.GetEnvironmentVariable("OPENAI_APIKEY") ?? throw new InvalidOperationException("OPENAI_APIKEY is not set.");
|
||||
var model = Environment.GetEnvironmentVariable("OPENAI_MODEL") ?? "gpt-4o-mini";
|
||||
|
||||
const string JokerName = "Joker";
|
||||
const string JokerInstructions = "You are good at telling jokes.";
|
||||
|
||||
AIAgent agent = new OpenAIClient(
|
||||
apiKey)
|
||||
.GetOpenAIResponseClient(model)
|
||||
.CreateAIAgent(JokerInstructions, JokerName);
|
||||
.CreateAIAgent(instructions: "You are good at telling jokes.", name: "Joker");
|
||||
|
||||
// Invoke the agent and output the text result.
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate."));
|
||||
|
||||
+1
-4
@@ -10,12 +10,9 @@ using OpenAI.Chat;
|
||||
var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new InvalidOperationException("OPENAI_API_KEY is not set.");
|
||||
var model = Environment.GetEnvironmentVariable("OPENAI_MODEL") ?? "gpt-4o-mini";
|
||||
|
||||
const string JokerName = "Joker";
|
||||
const string JokerInstructions = "You are good at telling jokes.";
|
||||
|
||||
AIAgent agent = new OpenAIClient(apiKey)
|
||||
.GetChatClient(model)
|
||||
.CreateAIAgent(JokerInstructions, JokerName);
|
||||
.CreateAIAgent(instructions: "You are good at telling jokes.", name: "Joker");
|
||||
|
||||
UserChatMessage chatMessage = new("Tell me a joke about a pirate.");
|
||||
|
||||
|
||||
@@ -10,14 +10,11 @@ using OpenAI;
|
||||
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";
|
||||
|
||||
const string JokerName = "Joker";
|
||||
const string JokerInstructions = "You are good at telling jokes.";
|
||||
|
||||
AIAgent agent = new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new AzureCliCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(JokerInstructions, JokerName);
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(instructions: "You are good at telling jokes.", name: "Joker");
|
||||
|
||||
// Invoke the agent and output the text result.
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate."));
|
||||
|
||||
@@ -10,14 +10,11 @@ using OpenAI;
|
||||
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";
|
||||
|
||||
const string JokerName = "Joker";
|
||||
const string JokerInstructions = "You are good at telling jokes.";
|
||||
|
||||
AIAgent agent = new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new AzureCliCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(JokerInstructions, JokerName);
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(instructions: "You are good at telling jokes.", name: "Joker");
|
||||
|
||||
// Invoke the agent with a multi-turn conversation, where the context is preserved in the thread object.
|
||||
AgentThread thread = agent.GetNewThread();
|
||||
|
||||
@@ -21,8 +21,8 @@ static string GetWeather([Description("The location to get the weather for.")] s
|
||||
AIAgent agent = new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new AzureCliCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(instructions: "You are a helpful assistant", tools: [AIFunctionFactory.Create(GetWeather)]);
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(instructions: "You are a helpful assistant", tools: [AIFunctionFactory.Create(GetWeather)]);
|
||||
|
||||
// Non-streaming agent interaction with function tools.
|
||||
Console.WriteLine(await agent.RunAsync("What is the weather like in Amsterdam?"));
|
||||
|
||||
+2
-2
@@ -25,8 +25,8 @@ static string GetWeather([Description("The location to get the weather for.")] s
|
||||
AIAgent agent = new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new AzureCliCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(instructions: "You are a helpful assistant", tools: [new ApprovalRequiredAIFunction(AIFunctionFactory.Create(GetWeather))]);
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(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.
|
||||
AgentThread thread = agent.GetNewThread();
|
||||
|
||||
@@ -11,15 +11,12 @@ using OpenAI;
|
||||
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";
|
||||
|
||||
const string JokerName = "Joker";
|
||||
const string JokerInstructions = "You are good at telling jokes.";
|
||||
|
||||
// Create the agent
|
||||
AIAgent agent = new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new AzureCliCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(JokerInstructions, JokerName);
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(instructions: "You are good at telling jokes.", name: "Joker");
|
||||
|
||||
// Start a new thread for the agent conversation.
|
||||
AgentThread thread = agent.GetNewThread();
|
||||
|
||||
+13
-16
@@ -17,9 +17,6 @@ using SampleApp;
|
||||
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";
|
||||
|
||||
const string JokerName = "Joker";
|
||||
const string JokerInstructions = "You are good at telling jokes.";
|
||||
|
||||
// Create a vector store to store the chat messages in.
|
||||
// Replace this with a vector store implementation of your choice if you want to persist the chat history to disk.
|
||||
VectorStore vectorStore = new InMemoryVectorStore();
|
||||
@@ -28,19 +25,19 @@ VectorStore vectorStore = new InMemoryVectorStore();
|
||||
AIAgent agent = new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new AzureCliCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(new ChatClientAgentOptions
|
||||
{
|
||||
Name = JokerName,
|
||||
Instructions = JokerInstructions,
|
||||
ChatMessageStoreFactory = ctx =>
|
||||
{
|
||||
// Create a new chat message store for this agent that stores the messages in a vector store.
|
||||
// Each thread must get its own copy of the VectorChatMessageStore, since the store
|
||||
// also contains the id that the thread is stored under.
|
||||
return new VectorChatMessageStore(vectorStore, ctx.SerializedState, ctx.JsonSerializerOptions);
|
||||
}
|
||||
});
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(new ChatClientAgentOptions
|
||||
{
|
||||
Instructions = "You are good at telling jokes.",
|
||||
Name = "Joker",
|
||||
ChatMessageStoreFactory = ctx =>
|
||||
{
|
||||
// Create a new chat message store for this agent that stores the messages in a vector store.
|
||||
// Each thread must get its own copy of the VectorChatMessageStore, since the store
|
||||
// also contains the id that the thread is stored under.
|
||||
return new VectorChatMessageStore(vectorStore, ctx.SerializedState, ctx.JsonSerializerOptions);
|
||||
}
|
||||
});
|
||||
|
||||
// Start a new thread for the agent conversation.
|
||||
AgentThread thread = agent.GetNewThread();
|
||||
|
||||
+1
@@ -11,6 +11,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Azure.Monitor.OpenTelemetry.Exporter" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
|
||||
<PackageReference Include="OpenTelemetry" />
|
||||
<PackageReference Include="OpenTelemetry.Exporter.Console" />
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.Identity;
|
||||
using Azure.Monitor.OpenTelemetry.Exporter;
|
||||
using Microsoft.Agents.AI;
|
||||
using OpenAI;
|
||||
using OpenTelemetry;
|
||||
@@ -11,22 +12,24 @@ using OpenTelemetry.Trace;
|
||||
|
||||
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";
|
||||
|
||||
const string JokerName = "Joker";
|
||||
const string JokerInstructions = "You are good at telling jokes.";
|
||||
var applicationInsightsConnectionString = Environment.GetEnvironmentVariable("APPLICATIONINSIGHTS_CONNECTION_STRING");
|
||||
|
||||
// Create TracerProvider with console exporter
|
||||
// This will output the telemetry data to the console.
|
||||
string sourceName = Guid.NewGuid().ToString("N");
|
||||
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
|
||||
var tracerProviderBuilder = Sdk.CreateTracerProviderBuilder()
|
||||
.AddSource(sourceName)
|
||||
.AddConsoleExporter()
|
||||
.Build();
|
||||
.AddConsoleExporter();
|
||||
if (!string.IsNullOrWhiteSpace(applicationInsightsConnectionString))
|
||||
{
|
||||
tracerProviderBuilder.AddAzureMonitorTraceExporter(options => options.ConnectionString = applicationInsightsConnectionString);
|
||||
}
|
||||
using var tracerProvider = tracerProviderBuilder.Build();
|
||||
|
||||
// Create the agent, and enable OpenTelemetry instrumentation.
|
||||
AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(JokerInstructions, JokerName)
|
||||
.CreateAIAgent(instructions: "You are good at telling jokes.", name: "Joker")
|
||||
.AsBuilder()
|
||||
.UseOpenTelemetry(sourceName: sourceName)
|
||||
.Build();
|
||||
|
||||
@@ -18,9 +18,8 @@ var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT
|
||||
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
|
||||
|
||||
// Add agent options to the service collection.
|
||||
const string JokerName = "Joker";
|
||||
const string JokerInstructions = "You are good at telling jokes.";
|
||||
builder.Services.AddSingleton(new ChatClientAgentOptions(JokerInstructions, JokerName));
|
||||
builder.Services.AddSingleton(
|
||||
new ChatClientAgentOptions(instructions: "You are good at telling jokes.", name: "Joker"));
|
||||
|
||||
// Add a chat client to the service collection.
|
||||
builder.Services.AddKeyedChatClient("AzureOpenAI", (sp) => new AzureOpenAIClient(
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" />
|
||||
<PackageReference Include="ModelContextProtocol" />
|
||||
<PackageReference Include="System.Net.ServerSentEvents" VersionOverride="10.0.0-preview.4.25258.110" />
|
||||
<PackageReference Include="System.Net.ServerSentEvents" VersionOverride="10.0.0-rc.2.25502.107" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -12,18 +12,14 @@ using ModelContextProtocol.Server;
|
||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_FOUNDRY_PROJECT_ENDPOINT is not set.");
|
||||
var deploymentName = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
|
||||
|
||||
const string JokerName = "Joker";
|
||||
const string JokerDescription = "An agent that tells jokes.";
|
||||
const string JokerInstructions = "You are good at telling jokes, and you always start each joke with 'Aye aye, captain!'.";
|
||||
|
||||
var persistentAgentsClient = new PersistentAgentsClient(endpoint, new AzureCliCredential());
|
||||
|
||||
// Create a server side persistent agent
|
||||
var agentMetadata = await persistentAgentsClient.Administration.CreateAgentAsync(
|
||||
model: deploymentName,
|
||||
name: JokerName,
|
||||
description: JokerDescription,
|
||||
instructions: JokerInstructions);
|
||||
instructions: "You are good at telling jokes, and you always start each joke with 'Aye aye, captain!'.",
|
||||
name: "Joker",
|
||||
description: "An agent that tells jokes.");
|
||||
|
||||
// Retrieve the server side persistent agent as an AIAgent.
|
||||
AIAgent agent = await persistentAgentsClient.GetAIAgentAsync(agentMetadata.Value.Id);
|
||||
|
||||
@@ -31,8 +31,8 @@ AIAgent weatherAgent = new AzureOpenAIClient(
|
||||
AIAgent agent = new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new AzureCliCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(instructions: "You are a helpful assistant who responds in French.", tools: [weatherAgent.AsAIFunction()]);
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(instructions: "You are a helpful assistant who responds in French.", tools: [weatherAgent.AsAIFunction()]);
|
||||
|
||||
// Invoke the agent and output the text result.
|
||||
Console.WriteLine(await agent.RunAsync("What is the weather like in Amsterdam?"));
|
||||
|
||||
@@ -10,7 +10,6 @@ using System.Text.RegularExpressions;
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.ChatClient;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
// Get Azure AI Foundry configuration from environment variables
|
||||
|
||||
@@ -27,16 +27,13 @@ services.AddSingleton<AgentPlugin>(); // The plugin depends on WeatherProvider a
|
||||
|
||||
IServiceProvider serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
const string AgentName = "Assistant";
|
||||
const string AgentInstructions = "You are a helpful assistant that helps people find information.";
|
||||
|
||||
AIAgent agent = new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new AzureCliCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(
|
||||
instructions: AgentInstructions,
|
||||
name: AgentName,
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(
|
||||
instructions: "You are a helpful assistant that helps people find information.",
|
||||
name: "Assistant",
|
||||
tools: [.. serviceProvider.GetRequiredService<AgentPlugin>().AsAITools()],
|
||||
services: serviceProvider); // Pass the service provider to the agent so it will be available to plugin functions to resolve dependencies.
|
||||
|
||||
|
||||
@@ -14,20 +14,17 @@ using OpenAI;
|
||||
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";
|
||||
|
||||
const string JokerName = "Joker";
|
||||
const string JokerInstructions = "You are good at telling jokes.";
|
||||
|
||||
// Construct the agent, and provide a factory to create an in-memory chat message store with a reducer that keeps only the last 2 non-system messages.
|
||||
AIAgent agent = new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new AzureCliCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(new ChatClientAgentOptions
|
||||
{
|
||||
Name = JokerName,
|
||||
Instructions = JokerInstructions,
|
||||
ChatMessageStoreFactory = ctx => new InMemoryChatMessageStore(new MessageCountingChatReducer(2), ctx.SerializedState, ctx.JsonSerializerOptions)
|
||||
});
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(new ChatClientAgentOptions
|
||||
{
|
||||
Instructions = "You are good at telling jokes.",
|
||||
Name = "Joker",
|
||||
ChatMessageStoreFactory = ctx => new InMemoryChatMessageStore(new MessageCountingChatReducer(2), ctx.SerializedState, ctx.JsonSerializerOptions)
|
||||
});
|
||||
|
||||
AgentThread thread = agent.GetNewThread();
|
||||
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
|
||||
<PackageReference Include="ModelContextProtocol" />
|
||||
<PackageReference Include="System.Net.ServerSentEvents" VersionOverride="10.0.0-preview.4.25258.110" />
|
||||
<PackageReference Include="System.Net.ServerSentEvents" VersionOverride="10.0.0-rc.2.25502.107" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
|
||||
<PackageReference Include="ModelContextProtocol" />
|
||||
<PackageReference Include="System.Net.ServerSentEvents" VersionOverride="10.0.0-preview.4.25258.110" />
|
||||
<PackageReference Include="System.Net.ServerSentEvents" VersionOverride="10.0.0-rc.2.25502.107" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
+2
-5
@@ -9,9 +9,6 @@ using Microsoft.Agents.AI;
|
||||
var endpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_FOUNDRY_PROJECT_ENDPOINT is not set.");
|
||||
var model = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_MODEL_ID") ?? "gpt-4.1-mini";
|
||||
|
||||
const string AgentName = "MicrosoftLearnAgent";
|
||||
const string AgentInstructions = "You answer questions by searching the Microsoft Learn content only.";
|
||||
|
||||
// Get a client to create/retrieve server side agents with.
|
||||
var persistentAgentsClient = new PersistentAgentsClient(endpoint, new AzureCliCredential());
|
||||
|
||||
@@ -24,8 +21,8 @@ mcpTool.AllowedTools.Add("microsoft_docs_search");
|
||||
// Create a server side persistent agent with the Azure.AI.Agents.Persistent SDK.
|
||||
var agentMetadata = await persistentAgentsClient.Administration.CreateAgentAsync(
|
||||
model: model,
|
||||
name: AgentName,
|
||||
instructions: AgentInstructions,
|
||||
name: "MicrosoftLearnAgent",
|
||||
instructions: "You answer questions by searching the Microsoft Learn content only.",
|
||||
tools: [mcpTool]);
|
||||
|
||||
// Retrieve an already created server side persistent agent as an AIAgent.
|
||||
|
||||
@@ -9,6 +9,7 @@ of the agent framework.
|
||||
|---|---|
|
||||
|[Agents](./Agents/README.md)|Step by step instructions for getting started with agents|
|
||||
|[Agent Providers](./AgentProviders/README.md)|Getting started with creating agents using various providers|
|
||||
|[A2A](./A2A/README.md)|Getting started with A2A (Agent-to-Agent) specific features|
|
||||
|[Agent Open Telemetry](./AgentOpenTelemetry/README.md)|Getting started with OpenTelemetry for agents|
|
||||
|[Agent With OpenAI exchange types](./AgentWithOpenAI/README.md)|Using OpenAI exchange types with agents|
|
||||
|[Workflow](./Workflows/README.md)|Getting started with Workflow|
|
||||
|
||||
@@ -6,7 +6,6 @@ using Azure.AI.OpenAI;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.Workflows;
|
||||
using Microsoft.Agents.AI.Workflows.Reflection;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace WorkflowCustomAgentExecutorsSample;
|
||||
@@ -50,7 +49,7 @@ public static class Program
|
||||
|
||||
// Execute the workflow
|
||||
await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, "Create a slogan for a new electric SUV that is affordable and fun to drive.");
|
||||
await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
|
||||
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
|
||||
{
|
||||
if (evt is SloganGeneratedEvent or FeedbackEvent)
|
||||
{
|
||||
@@ -107,10 +106,7 @@ internal sealed class SloganGeneratedEvent(SloganResult sloganResult) : Workflow
|
||||
/// 1. HandleAsync(string message): Handles the initial task to create a slogan.
|
||||
/// 2. HandleAsync(Feedback message): Handles feedback to improve the slogan.
|
||||
/// </summary>
|
||||
internal sealed class SloganWriterExecutor
|
||||
: ReflectingExecutor<SloganWriterExecutor>,
|
||||
IMessageHandler<string, SloganResult>,
|
||||
IMessageHandler<FeedbackResult, SloganResult>
|
||||
internal sealed class SloganWriterExecutor : Executor
|
||||
{
|
||||
private readonly AIAgent _agent;
|
||||
private readonly AgentThread _thread;
|
||||
@@ -134,17 +130,21 @@ internal sealed class SloganWriterExecutor
|
||||
this._thread = this._agent.GetNewThread();
|
||||
}
|
||||
|
||||
public async ValueTask<SloganResult> HandleAsync(string message, IWorkflowContext context)
|
||||
protected override RouteBuilder ConfigureRoutes(RouteBuilder routeBuilder) =>
|
||||
routeBuilder.AddHandler<string, SloganResult>(this.HandleAsync)
|
||||
.AddHandler<FeedbackResult, SloganResult>(this.HandleAsync);
|
||||
|
||||
public async ValueTask<SloganResult> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var result = await this._agent.RunAsync(message, this._thread);
|
||||
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.");
|
||||
|
||||
await context.AddEventAsync(new SloganGeneratedEvent(sloganResult));
|
||||
await context.AddEventAsync(new SloganGeneratedEvent(sloganResult), cancellationToken);
|
||||
return sloganResult;
|
||||
}
|
||||
|
||||
public async ValueTask<SloganResult> HandleAsync(FeedbackResult message, IWorkflowContext context)
|
||||
public async ValueTask<SloganResult> HandleAsync(FeedbackResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var feedbackMessage = $"""
|
||||
Here is the feedback on your previous slogan:
|
||||
@@ -155,10 +155,10 @@ internal sealed class SloganWriterExecutor
|
||||
Please use this feedback to improve your slogan.
|
||||
""";
|
||||
|
||||
var result = await this._agent.RunAsync(feedbackMessage, this._thread);
|
||||
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));
|
||||
await context.AddEventAsync(new SloganGeneratedEvent(sloganResult), cancellationToken);
|
||||
return sloganResult;
|
||||
}
|
||||
}
|
||||
@@ -175,7 +175,7 @@ internal sealed class FeedbackEvent(FeedbackResult feedbackResult) : WorkflowEve
|
||||
/// <summary>
|
||||
/// A custom executor that uses an AI agent to provide feedback on a slogan.
|
||||
/// </summary>
|
||||
internal sealed class FeedbackExecutor : ReflectingExecutor<FeedbackExecutor>, IMessageHandler<SloganResult>
|
||||
internal sealed class FeedbackExecutor : Executor<SloganResult>
|
||||
{
|
||||
private readonly AIAgent _agent;
|
||||
private readonly AgentThread _thread;
|
||||
@@ -205,7 +205,7 @@ internal sealed class FeedbackExecutor : ReflectingExecutor<FeedbackExecutor>, I
|
||||
this._thread = this._agent.GetNewThread();
|
||||
}
|
||||
|
||||
public async ValueTask HandleAsync(SloganResult message, IWorkflowContext context)
|
||||
public override async ValueTask HandleAsync(SloganResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var sloganMessage = $"""
|
||||
Here is a slogan for the task '{message.Task}':
|
||||
@@ -213,24 +213,24 @@ internal sealed class FeedbackExecutor : ReflectingExecutor<FeedbackExecutor>, I
|
||||
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._thread);
|
||||
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));
|
||||
await context.AddEventAsync(new FeedbackEvent(feedback), cancellationToken);
|
||||
|
||||
if (feedback.Rating >= this.MinimumRating)
|
||||
{
|
||||
await context.YieldOutputAsync($"The following slogan was accepted:\n\n{message.Slogan}");
|
||||
await context.YieldOutputAsync($"The following slogan was accepted:\n\n{message.Slogan}", cancellationToken);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._attempts >= this.MaxAttempts)
|
||||
{
|
||||
await context.YieldOutputAsync($"The slogan was rejected after {this.MaxAttempts} attempts. Final slogan:\n\n{message.Slogan}");
|
||||
await context.YieldOutputAsync($"The slogan was rejected after {this.MaxAttempts} attempts. Final slogan:\n\n{message.Slogan}", cancellationToken);
|
||||
return;
|
||||
}
|
||||
|
||||
await context.SendMessageAsync(feedback);
|
||||
await context.SendMessageAsync(feedback, cancellationToken: cancellationToken);
|
||||
this._attempts++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ public static class Program
|
||||
// The agents are wrapped as executors. When they receive messages,
|
||||
// they will cache the messages and only start processing when they receive a TurnToken.
|
||||
await run.TrySendMessageAsync(new TurnToken(emitEvents: true));
|
||||
await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
|
||||
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
|
||||
{
|
||||
if (evt is AgentRunUpdateEvent executorComplete)
|
||||
{
|
||||
|
||||
@@ -35,7 +35,7 @@ public static class Program
|
||||
var chatClient = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()).GetChatClient(deploymentName).AsIChatClient();
|
||||
|
||||
// Create the workflow and turn it into an agent
|
||||
var workflow = await WorkflowHelper.GetWorkflowAsync(chatClient).ConfigureAwait(false);
|
||||
var workflow = await WorkflowHelper.GetWorkflowAsync(chatClient);
|
||||
var agent = workflow.AsAgent("workflow-agent", "Workflow Agent");
|
||||
var thread = agent.GetNewThread();
|
||||
|
||||
@@ -59,7 +59,7 @@ public static class Program
|
||||
static async Task ProcessInputAsync(AIAgent agent, AgentThread thread, string input)
|
||||
{
|
||||
Dictionary<string, List<AgentRunResponseUpdate>> buffer = [];
|
||||
await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync(input, thread).ConfigureAwait(false))
|
||||
await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync(input, thread))
|
||||
{
|
||||
if (update.MessageId is null)
|
||||
{
|
||||
|
||||
+11
-10
@@ -2,7 +2,6 @@
|
||||
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.Workflows;
|
||||
using Microsoft.Agents.AI.Workflows.Reflection;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace WorkflowAsAnAgentsSample;
|
||||
@@ -43,21 +42,22 @@ internal static class WorkflowHelper
|
||||
/// Executor that starts the concurrent processing by sending messages to the agents.
|
||||
/// </summary>
|
||||
private sealed class ConcurrentStartExecutor() :
|
||||
ReflectingExecutor<ConcurrentStartExecutor>("ConcurrentStartExecutor"),
|
||||
IMessageHandler<List<ChatMessage>>
|
||||
Executor<List<ChatMessage>>("ConcurrentStartExecutor")
|
||||
{
|
||||
/// <summary>
|
||||
/// Starts the concurrent processing by sending messages to the agents.
|
||||
/// </summary>
|
||||
/// <param name="message">The user message to process</param>
|
||||
/// <param name="context">Workflow context for accessing workflow services and adding events</param>
|
||||
public async ValueTask HandleAsync(List<ChatMessage> message, IWorkflowContext context)
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
|
||||
/// The default is <see cref="CancellationToken.None"/>.</param>
|
||||
public override async ValueTask HandleAsync(List<ChatMessage> message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Broadcast the message to all connected agents. Receiving agents will queue
|
||||
// the message but will not start processing until they receive a turn token.
|
||||
await context.SendMessageAsync(message);
|
||||
await context.SendMessageAsync(message, cancellationToken: cancellationToken);
|
||||
// Broadcast the turn token to kick off the agents.
|
||||
await context.SendMessageAsync(new TurnToken(emitEvents: true));
|
||||
await context.SendMessageAsync(new TurnToken(emitEvents: true), cancellationToken: cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,8 +65,7 @@ internal static class WorkflowHelper
|
||||
/// Executor that aggregates the results from the concurrent agents.
|
||||
/// </summary>
|
||||
private sealed class ConcurrentAggregationExecutor() :
|
||||
ReflectingExecutor<ConcurrentAggregationExecutor>("ConcurrentAggregationExecutor"),
|
||||
IMessageHandler<ChatMessage>
|
||||
Executor<ChatMessage>("ConcurrentAggregationExecutor")
|
||||
{
|
||||
private readonly List<ChatMessage> _messages = [];
|
||||
|
||||
@@ -75,14 +74,16 @@ internal static class WorkflowHelper
|
||||
/// </summary>
|
||||
/// <param name="message">The message from the agent</param>
|
||||
/// <param name="context">Workflow context for accessing workflow services and adding events</param>
|
||||
public async ValueTask HandleAsync(ChatMessage message, IWorkflowContext context)
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
|
||||
/// The default is <see cref="CancellationToken.None"/>.</param>
|
||||
public override async ValueTask HandleAsync(ChatMessage message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
this._messages.Add(message);
|
||||
|
||||
if (this._messages.Count == 2)
|
||||
{
|
||||
var formattedMessages = string.Join(Environment.NewLine, this._messages.Select(m => $"{m.Text}"));
|
||||
await context.YieldOutputAsync(formattedMessages);
|
||||
await context.YieldOutputAsync(formattedMessages, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+7
-8
@@ -25,7 +25,7 @@ public static class Program
|
||||
private static async Task Main()
|
||||
{
|
||||
// Create the workflow
|
||||
var workflow = await WorkflowHelper.GetWorkflowAsync().ConfigureAwait(false);
|
||||
var workflow = await WorkflowHelper.GetWorkflowAsync();
|
||||
|
||||
// Create checkpoint manager
|
||||
var checkpointManager = CheckpointManager.Default;
|
||||
@@ -33,9 +33,9 @@ public static class Program
|
||||
|
||||
// Execute the workflow and save checkpoints
|
||||
await using Checkpointed<StreamingRun> checkpointedRun = await InProcessExecution
|
||||
.StreamAsync(workflow, NumberSignal.Init, checkpointManager)
|
||||
.ConfigureAwait(false);
|
||||
await foreach (WorkflowEvent evt in checkpointedRun.Run.WatchStreamAsync().ConfigureAwait(false))
|
||||
.StreamAsync(workflow, NumberSignal.Init, checkpointManager);
|
||||
|
||||
await foreach (WorkflowEvent evt in checkpointedRun.Run.WatchStreamAsync())
|
||||
{
|
||||
if (evt is ExecutorCompletedEvent executorCompletedEvt)
|
||||
{
|
||||
@@ -67,16 +67,15 @@ public static class Program
|
||||
Console.WriteLine($"Number of checkpoints created: {checkpoints.Count}");
|
||||
|
||||
// Rehydrate a new workflow instance from a saved checkpoint and continue execution
|
||||
var newWorkflow = await WorkflowHelper.GetWorkflowAsync().ConfigureAwait(false);
|
||||
var newWorkflow = await WorkflowHelper.GetWorkflowAsync();
|
||||
const int CheckpointIndex = 5;
|
||||
Console.WriteLine($"\n\nHydrating a new workflow instance from the {CheckpointIndex + 1}th checkpoint.");
|
||||
CheckpointInfo savedCheckpoint = checkpoints[CheckpointIndex];
|
||||
|
||||
await using Checkpointed<StreamingRun> newCheckpointedRun =
|
||||
await InProcessExecution.ResumeStreamAsync(newWorkflow, savedCheckpoint, checkpointManager, checkpointedRun.Run.RunId)
|
||||
.ConfigureAwait(false);
|
||||
await InProcessExecution.ResumeStreamAsync(newWorkflow, savedCheckpoint, checkpointManager, checkpointedRun.Run.RunId);
|
||||
|
||||
await foreach (WorkflowEvent evt in newCheckpointedRun.Run.WatchStreamAsync().ConfigureAwait(false))
|
||||
await foreach (WorkflowEvent evt in newCheckpointedRun.Run.WatchStreamAsync())
|
||||
{
|
||||
if (evt is ExecutorCompletedEvent executorCompletedEvt)
|
||||
{
|
||||
|
||||
+14
-15
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Agents.AI.Workflows;
|
||||
using Microsoft.Agents.AI.Workflows.Reflection;
|
||||
|
||||
namespace WorkflowCheckpointAndRehydrateSample;
|
||||
|
||||
@@ -42,7 +41,7 @@ internal enum NumberSignal
|
||||
/// <summary>
|
||||
/// Executor that makes a guess based on the current bounds.
|
||||
/// </summary>
|
||||
internal sealed class GuessNumberExecutor() : ReflectingExecutor<GuessNumberExecutor>("Guess"), IMessageHandler<NumberSignal>
|
||||
internal sealed class GuessNumberExecutor() : Executor<NumberSignal>("Guess")
|
||||
{
|
||||
/// <summary>
|
||||
/// The lower bound of the guessing range.
|
||||
@@ -69,20 +68,20 @@ internal sealed class GuessNumberExecutor() : ReflectingExecutor<GuessNumberExec
|
||||
|
||||
private int NextGuess => (this.LowerBound + this.UpperBound) / 2;
|
||||
|
||||
public async ValueTask HandleAsync(NumberSignal message, IWorkflowContext context)
|
||||
public override async ValueTask HandleAsync(NumberSignal message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
switch (message)
|
||||
{
|
||||
case NumberSignal.Init:
|
||||
await context.SendMessageAsync(this.NextGuess).ConfigureAwait(false);
|
||||
await context.SendMessageAsync(this.NextGuess, cancellationToken: cancellationToken);
|
||||
break;
|
||||
case NumberSignal.Above:
|
||||
this.UpperBound = this.NextGuess - 1;
|
||||
await context.SendMessageAsync(this.NextGuess).ConfigureAwait(false);
|
||||
await context.SendMessageAsync(this.NextGuess, cancellationToken: cancellationToken);
|
||||
break;
|
||||
case NumberSignal.Below:
|
||||
this.LowerBound = this.NextGuess + 1;
|
||||
await context.SendMessageAsync(this.NextGuess).ConfigureAwait(false);
|
||||
await context.SendMessageAsync(this.NextGuess, cancellationToken: cancellationToken);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -92,20 +91,20 @@ internal sealed class GuessNumberExecutor() : ReflectingExecutor<GuessNumberExec
|
||||
/// This must be overridden to save any state that is needed to resume the executor.
|
||||
/// </summary>
|
||||
protected override ValueTask OnCheckpointingAsync(IWorkflowContext context, CancellationToken cancellationToken = default) =>
|
||||
context.QueueStateUpdateAsync(StateKey, (this.LowerBound, this.UpperBound));
|
||||
context.QueueStateUpdateAsync(StateKey, (this.LowerBound, this.UpperBound), cancellationToken: cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Restore the state of the executor from a checkpoint.
|
||||
/// This must be overridden to restore any state that was saved during checkpointing.
|
||||
/// </summary>
|
||||
protected override async ValueTask OnCheckpointRestoredAsync(IWorkflowContext context, CancellationToken cancellationToken = default) =>
|
||||
(this.LowerBound, this.UpperBound) = await context.ReadStateAsync<(int, int)>(StateKey).ConfigureAwait(false);
|
||||
(this.LowerBound, this.UpperBound) = await context.ReadStateAsync<(int, int)>(StateKey, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executor that judges the guess and provides feedback.
|
||||
/// </summary>
|
||||
internal sealed class JudgeExecutor() : ReflectingExecutor<JudgeExecutor>("Judge"), IMessageHandler<int>
|
||||
internal sealed class JudgeExecutor() : Executor<int>("Judge")
|
||||
{
|
||||
private readonly int _targetNumber;
|
||||
private int _tries;
|
||||
@@ -120,20 +119,20 @@ internal sealed class JudgeExecutor() : ReflectingExecutor<JudgeExecutor>("Judge
|
||||
this._targetNumber = targetNumber;
|
||||
}
|
||||
|
||||
public async ValueTask HandleAsync(int message, IWorkflowContext context)
|
||||
public override async ValueTask HandleAsync(int message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
this._tries++;
|
||||
if (message == this._targetNumber)
|
||||
{
|
||||
await context.YieldOutputAsync($"{this._targetNumber} found in {this._tries} tries!").ConfigureAwait(false);
|
||||
await context.YieldOutputAsync($"{this._targetNumber} found in {this._tries} tries!", cancellationToken: cancellationToken);
|
||||
}
|
||||
else if (message < this._targetNumber)
|
||||
{
|
||||
await context.SendMessageAsync(NumberSignal.Below).ConfigureAwait(false);
|
||||
await context.SendMessageAsync(NumberSignal.Below, cancellationToken: cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
await context.SendMessageAsync(NumberSignal.Above).ConfigureAwait(false);
|
||||
await context.SendMessageAsync(NumberSignal.Above, cancellationToken: cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,12 +141,12 @@ internal sealed class JudgeExecutor() : ReflectingExecutor<JudgeExecutor>("Judge
|
||||
/// This must be overridden to save any state that is needed to resume the executor.
|
||||
/// </summary>
|
||||
protected override ValueTask OnCheckpointingAsync(IWorkflowContext context, CancellationToken cancellationToken = default) =>
|
||||
context.QueueStateUpdateAsync(StateKey, this._tries);
|
||||
context.QueueStateUpdateAsync(StateKey, this._tries, cancellationToken: cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Restore the state of the executor from a checkpoint.
|
||||
/// This must be overridden to restore any state that was saved during checkpointing.
|
||||
/// </summary>
|
||||
protected override async ValueTask OnCheckpointRestoredAsync(IWorkflowContext context, CancellationToken cancellationToken = default) =>
|
||||
this._tries = await context.ReadStateAsync<int>(StateKey).ConfigureAwait(false);
|
||||
this._tries = await context.ReadStateAsync<int>(StateKey, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ public static class Program
|
||||
private static async Task Main()
|
||||
{
|
||||
// Create the workflow
|
||||
var workflow = await WorkflowHelper.GetWorkflowAsync().ConfigureAwait(false);
|
||||
var workflow = await WorkflowHelper.GetWorkflowAsync();
|
||||
|
||||
// Create checkpoint manager
|
||||
var checkpointManager = CheckpointManager.Default;
|
||||
@@ -33,8 +33,8 @@ public static class Program
|
||||
// Execute the workflow and save checkpoints
|
||||
await using Checkpointed<StreamingRun> checkpointedRun = await InProcessExecution
|
||||
.StreamAsync(workflow, NumberSignal.Init, checkpointManager)
|
||||
.ConfigureAwait(false);
|
||||
await foreach (WorkflowEvent evt in checkpointedRun.Run.WatchStreamAsync().ConfigureAwait(false))
|
||||
;
|
||||
await foreach (WorkflowEvent evt in checkpointedRun.Run.WatchStreamAsync())
|
||||
{
|
||||
if (evt is ExecutorCompletedEvent executorCompletedEvt)
|
||||
{
|
||||
@@ -70,8 +70,8 @@ public static class Program
|
||||
Console.WriteLine($"\n\nRestoring from the {CheckpointIndex + 1}th checkpoint.");
|
||||
CheckpointInfo savedCheckpoint = checkpoints[CheckpointIndex];
|
||||
// Note that we are restoring the state directly to the same run instance.
|
||||
await checkpointedRun.RestoreCheckpointAsync(savedCheckpoint, CancellationToken.None).ConfigureAwait(false);
|
||||
await foreach (WorkflowEvent evt in checkpointedRun.Run.WatchStreamAsync().ConfigureAwait(false))
|
||||
await checkpointedRun.RestoreCheckpointAsync(savedCheckpoint, CancellationToken.None);
|
||||
await foreach (WorkflowEvent evt in checkpointedRun.Run.WatchStreamAsync())
|
||||
{
|
||||
if (evt is ExecutorCompletedEvent executorCompletedEvt)
|
||||
{
|
||||
|
||||
+14
-15
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Agents.AI.Workflows;
|
||||
using Microsoft.Agents.AI.Workflows.Reflection;
|
||||
|
||||
namespace WorkflowCheckpointAndResumeSample;
|
||||
|
||||
@@ -42,7 +41,7 @@ internal enum NumberSignal
|
||||
/// <summary>
|
||||
/// Executor that makes a guess based on the current bounds.
|
||||
/// </summary>
|
||||
internal sealed class GuessNumberExecutor() : ReflectingExecutor<GuessNumberExecutor>("Guess"), IMessageHandler<NumberSignal>
|
||||
internal sealed class GuessNumberExecutor() : Executor<NumberSignal>("Guess")
|
||||
{
|
||||
/// <summary>
|
||||
/// The lower bound of the guessing range.
|
||||
@@ -69,20 +68,20 @@ internal sealed class GuessNumberExecutor() : ReflectingExecutor<GuessNumberExec
|
||||
|
||||
private int NextGuess => (this.LowerBound + this.UpperBound) / 2;
|
||||
|
||||
public async ValueTask HandleAsync(NumberSignal message, IWorkflowContext context)
|
||||
public override async ValueTask HandleAsync(NumberSignal message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
switch (message)
|
||||
{
|
||||
case NumberSignal.Init:
|
||||
await context.SendMessageAsync(this.NextGuess).ConfigureAwait(false);
|
||||
await context.SendMessageAsync(this.NextGuess, cancellationToken: cancellationToken);
|
||||
break;
|
||||
case NumberSignal.Above:
|
||||
this.UpperBound = this.NextGuess - 1;
|
||||
await context.SendMessageAsync(this.NextGuess).ConfigureAwait(false);
|
||||
await context.SendMessageAsync(this.NextGuess, cancellationToken: cancellationToken);
|
||||
break;
|
||||
case NumberSignal.Below:
|
||||
this.LowerBound = this.NextGuess + 1;
|
||||
await context.SendMessageAsync(this.NextGuess).ConfigureAwait(false);
|
||||
await context.SendMessageAsync(this.NextGuess, cancellationToken: cancellationToken);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -92,20 +91,20 @@ internal sealed class GuessNumberExecutor() : ReflectingExecutor<GuessNumberExec
|
||||
/// This must be overridden to save any state that is needed to resume the executor.
|
||||
/// </summary>
|
||||
protected override ValueTask OnCheckpointingAsync(IWorkflowContext context, CancellationToken cancellationToken = default) =>
|
||||
context.QueueStateUpdateAsync(StateKey, (this.LowerBound, this.UpperBound));
|
||||
context.QueueStateUpdateAsync(StateKey, (this.LowerBound, this.UpperBound), cancellationToken: cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Restore the state of the executor from a checkpoint.
|
||||
/// This must be overridden to restore any state that was saved during checkpointing.
|
||||
/// </summary>
|
||||
protected override async ValueTask OnCheckpointRestoredAsync(IWorkflowContext context, CancellationToken cancellationToken = default) =>
|
||||
(this.LowerBound, this.UpperBound) = await context.ReadStateAsync<(int, int)>(StateKey).ConfigureAwait(false);
|
||||
(this.LowerBound, this.UpperBound) = await context.ReadStateAsync<(int, int)>(StateKey, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executor that judges the guess and provides feedback.
|
||||
/// </summary>
|
||||
internal sealed class JudgeExecutor() : ReflectingExecutor<JudgeExecutor>("Judge"), IMessageHandler<int>
|
||||
internal sealed class JudgeExecutor() : Executor<int>("Judge")
|
||||
{
|
||||
private readonly int _targetNumber;
|
||||
private int _tries;
|
||||
@@ -120,20 +119,20 @@ internal sealed class JudgeExecutor() : ReflectingExecutor<JudgeExecutor>("Judge
|
||||
this._targetNumber = targetNumber;
|
||||
}
|
||||
|
||||
public async ValueTask HandleAsync(int message, IWorkflowContext context)
|
||||
public override async ValueTask HandleAsync(int message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
this._tries++;
|
||||
if (message == this._targetNumber)
|
||||
{
|
||||
await context.YieldOutputAsync($"{this._targetNumber} found in {this._tries} tries!").ConfigureAwait(false);
|
||||
await context.YieldOutputAsync($"{this._targetNumber} found in {this._tries} tries!", cancellationToken);
|
||||
}
|
||||
else if (message < this._targetNumber)
|
||||
{
|
||||
await context.SendMessageAsync(NumberSignal.Below).ConfigureAwait(false);
|
||||
await context.SendMessageAsync(NumberSignal.Below, cancellationToken: cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
await context.SendMessageAsync(NumberSignal.Above).ConfigureAwait(false);
|
||||
await context.SendMessageAsync(NumberSignal.Above, cancellationToken: cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,12 +141,12 @@ internal sealed class JudgeExecutor() : ReflectingExecutor<JudgeExecutor>("Judge
|
||||
/// This must be overridden to save any state that is needed to resume the executor.
|
||||
/// </summary>
|
||||
protected override ValueTask OnCheckpointingAsync(IWorkflowContext context, CancellationToken cancellationToken = default) =>
|
||||
context.QueueStateUpdateAsync(StateKey, this._tries);
|
||||
context.QueueStateUpdateAsync(StateKey, this._tries, cancellationToken: cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Restore the state of the executor from a checkpoint.
|
||||
/// This must be overridden to restore any state that was saved during checkpointing.
|
||||
/// </summary>
|
||||
protected override async ValueTask OnCheckpointRestoredAsync(IWorkflowContext context, CancellationToken cancellationToken = default) =>
|
||||
this._tries = await context.ReadStateAsync<int>(StateKey).ConfigureAwait(false);
|
||||
this._tries = await context.ReadStateAsync<int>(StateKey, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
+7
-7
@@ -27,7 +27,7 @@ public static class Program
|
||||
private static async Task Main()
|
||||
{
|
||||
// Create the workflow
|
||||
var workflow = await WorkflowHelper.GetWorkflowAsync().ConfigureAwait(false);
|
||||
var workflow = await WorkflowHelper.GetWorkflowAsync();
|
||||
|
||||
// Create checkpoint manager
|
||||
var checkpointManager = CheckpointManager.Default;
|
||||
@@ -36,15 +36,15 @@ public static class Program
|
||||
// Execute the workflow and save checkpoints
|
||||
await using Checkpointed<StreamingRun> checkpointedRun = await InProcessExecution
|
||||
.StreamAsync(workflow, new SignalWithNumber(NumberSignal.Init), checkpointManager)
|
||||
.ConfigureAwait(false);
|
||||
await foreach (WorkflowEvent evt in checkpointedRun.Run.WatchStreamAsync().ConfigureAwait(false))
|
||||
;
|
||||
await foreach (WorkflowEvent evt in checkpointedRun.Run.WatchStreamAsync())
|
||||
{
|
||||
switch (evt)
|
||||
{
|
||||
case RequestInfoEvent requestInputEvt:
|
||||
// Handle `RequestInfoEvent` from the workflow
|
||||
ExternalResponse response = HandleExternalRequest(requestInputEvt.Request);
|
||||
await checkpointedRun.Run.SendResponseAsync(response).ConfigureAwait(false);
|
||||
await checkpointedRun.Run.SendResponseAsync(response);
|
||||
break;
|
||||
case ExecutorCompletedEvent executorCompletedEvt:
|
||||
Console.WriteLine($"* Executor {executorCompletedEvt.ExecutorId} completed.");
|
||||
@@ -76,15 +76,15 @@ public static class Program
|
||||
Console.WriteLine($"\n\nRestoring from the {CheckpointIndex + 1}th checkpoint.");
|
||||
CheckpointInfo savedCheckpoint = checkpoints[CheckpointIndex];
|
||||
// Note that we are restoring the state directly to the same run instance.
|
||||
await checkpointedRun.RestoreCheckpointAsync(savedCheckpoint, CancellationToken.None).ConfigureAwait(false);
|
||||
await foreach (WorkflowEvent evt in checkpointedRun.Run.WatchStreamAsync().ConfigureAwait(false))
|
||||
await checkpointedRun.RestoreCheckpointAsync(savedCheckpoint, CancellationToken.None);
|
||||
await foreach (WorkflowEvent evt in checkpointedRun.Run.WatchStreamAsync())
|
||||
{
|
||||
switch (evt)
|
||||
{
|
||||
case RequestInfoEvent requestInputEvt:
|
||||
// Handle `RequestInfoEvent` from the workflow
|
||||
ExternalResponse response = HandleExternalRequest(requestInputEvt.Request);
|
||||
await checkpointedRun.Run.SendResponseAsync(response).ConfigureAwait(false);
|
||||
await checkpointedRun.Run.SendResponseAsync(response);
|
||||
break;
|
||||
case ExecutorCompletedEvent executorCompletedEvt:
|
||||
Console.WriteLine($"* Executor {executorCompletedEvt.ExecutorId} completed.");
|
||||
|
||||
+7
-9
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Agents.AI.Workflows;
|
||||
using Microsoft.Agents.AI.Workflows.Reflection;
|
||||
|
||||
namespace WorkflowCheckpointWithHumanInTheLoopSample;
|
||||
|
||||
@@ -54,7 +53,7 @@ internal sealed class SignalWithNumber
|
||||
/// <summary>
|
||||
/// Executor that judges the guess and provides feedback.
|
||||
/// </summary>
|
||||
internal sealed class JudgeExecutor() : ReflectingExecutor<JudgeExecutor>("Judge"), IMessageHandler<int>
|
||||
internal sealed class JudgeExecutor() : Executor<int>("Judge")
|
||||
{
|
||||
private readonly int _targetNumber;
|
||||
private int _tries;
|
||||
@@ -69,21 +68,20 @@ internal sealed class JudgeExecutor() : ReflectingExecutor<JudgeExecutor>("Judge
|
||||
this._targetNumber = targetNumber;
|
||||
}
|
||||
|
||||
public async ValueTask HandleAsync(int message, IWorkflowContext context)
|
||||
public override async ValueTask HandleAsync(int message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
this._tries++;
|
||||
if (message == this._targetNumber)
|
||||
{
|
||||
await context.YieldOutputAsync($"{this._targetNumber} found in {this._tries} tries!")
|
||||
.ConfigureAwait(false);
|
||||
await context.YieldOutputAsync($"{this._targetNumber} found in {this._tries} tries!", cancellationToken);
|
||||
}
|
||||
else if (message < this._targetNumber)
|
||||
{
|
||||
await context.SendMessageAsync(new SignalWithNumber(NumberSignal.Below, message)).ConfigureAwait(false);
|
||||
await context.SendMessageAsync(new SignalWithNumber(NumberSignal.Below, message), cancellationToken: cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
await context.SendMessageAsync(new SignalWithNumber(NumberSignal.Above, message)).ConfigureAwait(false);
|
||||
await context.SendMessageAsync(new SignalWithNumber(NumberSignal.Above, message), cancellationToken: cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,12 +90,12 @@ internal sealed class JudgeExecutor() : ReflectingExecutor<JudgeExecutor>("Judge
|
||||
/// This must be overridden to save any state that is needed to resume the executor.
|
||||
/// </summary>
|
||||
protected override ValueTask OnCheckpointingAsync(IWorkflowContext context, CancellationToken cancellationToken = default) =>
|
||||
context.QueueStateUpdateAsync(StateKey, this._tries);
|
||||
context.QueueStateUpdateAsync(StateKey, this._tries, cancellationToken: cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Restore the state of the executor from a checkpoint.
|
||||
/// This must be overridden to restore any state that was saved during checkpointing.
|
||||
/// </summary>
|
||||
protected override async ValueTask OnCheckpointRestoredAsync(IWorkflowContext context, CancellationToken cancellationToken = default) =>
|
||||
this._tries = await context.ReadStateAsync<int>(StateKey).ConfigureAwait(false);
|
||||
this._tries = await context.ReadStateAsync<int>(StateKey, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ using Azure.AI.OpenAI;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.Workflows;
|
||||
using Microsoft.Agents.AI.Workflows.Reflection;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace WorkflowConcurrentSample;
|
||||
@@ -60,7 +59,7 @@ public static class Program
|
||||
|
||||
// Execute the workflow in streaming mode
|
||||
await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, "What is temperature?");
|
||||
await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
|
||||
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
|
||||
{
|
||||
if (evt is WorkflowOutputEvent output)
|
||||
{
|
||||
@@ -74,22 +73,23 @@ public static class Program
|
||||
/// Executor that starts the concurrent processing by sending messages to the agents.
|
||||
/// </summary>
|
||||
internal sealed class ConcurrentStartExecutor() :
|
||||
ReflectingExecutor<ConcurrentStartExecutor>("ConcurrentStartExecutor"),
|
||||
IMessageHandler<string>
|
||||
Executor<string>("ConcurrentStartExecutor")
|
||||
{
|
||||
/// <summary>
|
||||
/// Starts the concurrent processing by sending messages to the agents.
|
||||
/// </summary>
|
||||
/// <param name="message">The user message to process</param>
|
||||
/// <param name="context">Workflow context for accessing workflow services and adding events</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
|
||||
/// The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A task representing the asynchronous operation</returns>
|
||||
public async ValueTask HandleAsync(string message, IWorkflowContext context)
|
||||
public override async ValueTask HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Broadcast the message to all connected agents. Receiving agents will queue
|
||||
// the message but will not start processing until they receive a turn token.
|
||||
await context.SendMessageAsync(new ChatMessage(ChatRole.User, message));
|
||||
await context.SendMessageAsync(new ChatMessage(ChatRole.User, message), cancellationToken: cancellationToken);
|
||||
// Broadcast the turn token to kick off the agents.
|
||||
await context.SendMessageAsync(new TurnToken(emitEvents: true));
|
||||
await context.SendMessageAsync(new TurnToken(emitEvents: true), cancellationToken: cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,8 +97,7 @@ internal sealed class ConcurrentStartExecutor() :
|
||||
/// Executor that aggregates the results from the concurrent agents.
|
||||
/// </summary>
|
||||
internal sealed class ConcurrentAggregationExecutor() :
|
||||
ReflectingExecutor<ConcurrentAggregationExecutor>("ConcurrentAggregationExecutor"),
|
||||
IMessageHandler<ChatMessage>
|
||||
Executor<ChatMessage>("ConcurrentAggregationExecutor")
|
||||
{
|
||||
private readonly List<ChatMessage> _messages = [];
|
||||
|
||||
@@ -107,15 +106,17 @@ internal sealed class ConcurrentAggregationExecutor() :
|
||||
/// </summary>
|
||||
/// <param name="message">The message from the agent</param>
|
||||
/// <param name="context">Workflow context for accessing workflow services and adding events</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
|
||||
/// The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A task representing the asynchronous operation</returns>
|
||||
public async ValueTask HandleAsync(ChatMessage message, IWorkflowContext context)
|
||||
public override async ValueTask HandleAsync(ChatMessage message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
this._messages.Add(message);
|
||||
|
||||
if (this._messages.Count == 2)
|
||||
{
|
||||
var formattedMessages = string.Join(Environment.NewLine, this._messages.Select(m => $"{m.AuthorName}: {m.Text}"));
|
||||
await context.YieldOutputAsync(formattedMessages);
|
||||
await context.YieldOutputAsync(formattedMessages, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Agents.AI.Workflows;
|
||||
using Microsoft.Agents.AI.Workflows.Reflection;
|
||||
|
||||
namespace WorkflowMapReduceSample;
|
||||
|
||||
@@ -129,8 +129,7 @@ public static class Program
|
||||
/// Splits data into roughly equal chunks based on the number of mapper nodes.
|
||||
/// </summary>
|
||||
internal sealed class Split(string[] mapperIds, string id) :
|
||||
ReflectingExecutor<Split>(id),
|
||||
IMessageHandler<string>
|
||||
Executor<string>(id)
|
||||
{
|
||||
private readonly string[] _mapperIds = mapperIds;
|
||||
private static readonly string[] s_lineSeparators = ["\r\n", "\r", "\n"];
|
||||
@@ -138,7 +137,7 @@ internal sealed class Split(string[] mapperIds, string id) :
|
||||
/// <summary>
|
||||
/// Tokenize input and assign contiguous index ranges to each mapper via shared state.
|
||||
/// </summary>
|
||||
public async ValueTask HandleAsync(string message, IWorkflowContext context)
|
||||
public override async ValueTask HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Ensure temp directory exists
|
||||
Directory.CreateDirectory(MapReduceConstants.TempDir);
|
||||
@@ -147,7 +146,7 @@ internal sealed class Split(string[] mapperIds, string id) :
|
||||
var wordList = Preprocess(message);
|
||||
|
||||
// Store the tokenized words once so that all mappers can read by index
|
||||
await context.QueueStateUpdateAsync(MapReduceConstants.DataToProcessKey, wordList, scopeName: MapReduceConstants.StateScope);
|
||||
await context.QueueStateUpdateAsync(MapReduceConstants.DataToProcessKey, wordList, scopeName: MapReduceConstants.StateScope, cancellationToken);
|
||||
|
||||
// Divide indices into contiguous slices for each mapper
|
||||
var mapperCount = this._mapperIds.Length;
|
||||
@@ -160,10 +159,10 @@ internal sealed class Split(string[] mapperIds, string id) :
|
||||
var endIndex = i < mapperCount - 1 ? startIndex + chunkSize : wordList.Length;
|
||||
|
||||
// Save the indices under the mapper's Id
|
||||
await context.QueueStateUpdateAsync(this._mapperIds[i], (startIndex, endIndex), scopeName: MapReduceConstants.StateScope);
|
||||
await context.QueueStateUpdateAsync(this._mapperIds[i], (startIndex, endIndex), scopeName: MapReduceConstants.StateScope, cancellationToken);
|
||||
|
||||
// Notify the mapper that data is ready
|
||||
await context.SendMessageAsync(new SplitComplete(), targetId: this._mapperIds[i]);
|
||||
await context.SendMessageAsync(new SplitComplete(), targetId: this._mapperIds[i], cancellationToken);
|
||||
}
|
||||
|
||||
// Process all the chunks
|
||||
@@ -187,15 +186,15 @@ internal sealed class Split(string[] mapperIds, string id) :
|
||||
/// <summary>
|
||||
/// Maps each token to a count of 1 and writes pairs to a per-mapper file.
|
||||
/// </summary>
|
||||
internal sealed class Mapper(string id) : ReflectingExecutor<Mapper>(id), IMessageHandler<SplitComplete>
|
||||
internal sealed class Mapper(string id) : Executor<SplitComplete>(id)
|
||||
{
|
||||
/// <summary>
|
||||
/// Read the assigned slice, emit (word, 1) pairs, and persist to disk.
|
||||
/// </summary>
|
||||
public async ValueTask HandleAsync(SplitComplete message, IWorkflowContext context)
|
||||
public override async ValueTask HandleAsync(SplitComplete message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var dataToProcess = await context.ReadStateAsync<string[]>(MapReduceConstants.DataToProcessKey, scopeName: MapReduceConstants.StateScope);
|
||||
var chunk = await context.ReadStateAsync<(int start, int end)>(this.Id, scopeName: MapReduceConstants.StateScope);
|
||||
var dataToProcess = await context.ReadStateAsync<string[]>(MapReduceConstants.DataToProcessKey, scopeName: MapReduceConstants.StateScope, cancellationToken);
|
||||
var chunk = await context.ReadStateAsync<(int start, int end)>(this.Id, scopeName: MapReduceConstants.StateScope, cancellationToken);
|
||||
|
||||
var results = dataToProcess![chunk.start..chunk.end]
|
||||
.Select(word => (word, 1))
|
||||
@@ -204,9 +203,9 @@ internal sealed class Mapper(string id) : ReflectingExecutor<Mapper>(id), IMessa
|
||||
// Write this mapper's results as simple text lines for easy debugging
|
||||
var filePath = Path.Combine(MapReduceConstants.TempDir, $"map_results_{this.Id}.txt");
|
||||
var lines = results.Select(r => $"{r.word}: {r.Item2}");
|
||||
await File.WriteAllLinesAsync(filePath, lines);
|
||||
await File.WriteAllLinesAsync(filePath, lines, cancellationToken);
|
||||
|
||||
await context.SendMessageAsync(new MapComplete(filePath));
|
||||
await context.SendMessageAsync(new MapComplete(filePath), cancellationToken: cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,8 +213,7 @@ internal sealed class Mapper(string id) : ReflectingExecutor<Mapper>(id), IMessa
|
||||
/// Groups intermediate pairs by key and partitions them across reducers.
|
||||
/// </summary>
|
||||
internal sealed class Shuffler(string[] reducerIds, string[] mapperIds, string id) :
|
||||
ReflectingExecutor<Shuffler>(id),
|
||||
IMessageHandler<MapComplete>
|
||||
Executor<MapComplete>(id)
|
||||
{
|
||||
private readonly string[] _reducerIds = reducerIds;
|
||||
private readonly string[] _mapperIds = mapperIds;
|
||||
@@ -224,7 +222,7 @@ internal sealed class Shuffler(string[] reducerIds, string[] mapperIds, string i
|
||||
/// <summary>
|
||||
/// Aggregate mapper outputs and write one partition file per reducer.
|
||||
/// </summary>
|
||||
public async ValueTask HandleAsync(MapComplete message, IWorkflowContext context)
|
||||
public override async ValueTask HandleAsync(MapComplete message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
this._mapResults.Add(message);
|
||||
|
||||
@@ -241,9 +239,9 @@ internal sealed class Shuffler(string[] reducerIds, string[] mapperIds, string i
|
||||
// Write one grouped partition for reducer index and notify that reducer
|
||||
var filePath = Path.Combine(MapReduceConstants.TempDir, $"shuffle_results_{index}.txt");
|
||||
var lines = chunk.Select(kvp => $"{kvp.key}: {JsonSerializer.Serialize(kvp.values)}");
|
||||
await File.WriteAllLinesAsync(filePath, lines);
|
||||
await File.WriteAllLinesAsync(filePath, lines, cancellationToken);
|
||||
|
||||
await context.SendMessageAsync(new ShuffleComplete(filePath, this._reducerIds[index]));
|
||||
await context.SendMessageAsync(new ShuffleComplete(filePath, this._reducerIds[index]), cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
var tasks = chunks.Select((chunk, i) => ProcessChunkAsync(chunk, i));
|
||||
@@ -313,12 +311,12 @@ internal sealed class Shuffler(string[] reducerIds, string[] mapperIds, string i
|
||||
/// <summary>
|
||||
/// Sums grouped counts per key for its assigned partition.
|
||||
/// </summary>
|
||||
internal sealed class Reducer(string id) : ReflectingExecutor<Reducer>(id), IMessageHandler<ShuffleComplete>
|
||||
internal sealed class Reducer(string id) : Executor<ShuffleComplete>(id)
|
||||
{
|
||||
/// <summary>
|
||||
/// Read one shuffle partition and reduce it to totals.
|
||||
/// </summary>
|
||||
public async ValueTask HandleAsync(ShuffleComplete message, IWorkflowContext context)
|
||||
public override async ValueTask HandleAsync(ShuffleComplete message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (message.ReducerId != this.Id)
|
||||
{
|
||||
@@ -327,7 +325,7 @@ internal sealed class Reducer(string id) : ReflectingExecutor<Reducer>(id), IMes
|
||||
}
|
||||
|
||||
// Read grouped values from the shuffle output
|
||||
var lines = await File.ReadAllLinesAsync(message.FilePath);
|
||||
var lines = await File.ReadAllLinesAsync(message.FilePath, cancellationToken);
|
||||
|
||||
// Sum values per key. Values are serialized JSON arrays like [1, 1, ...]
|
||||
var reducedResults = new Dictionary<string, int>();
|
||||
@@ -345,9 +343,9 @@ internal sealed class Reducer(string id) : ReflectingExecutor<Reducer>(id), IMes
|
||||
// Persist our partition totals
|
||||
var filePath = Path.Combine(MapReduceConstants.TempDir, $"reduced_results_{this.Id}.txt");
|
||||
var outputLines = reducedResults.Select(kvp => $"{kvp.Key}: {kvp.Value}");
|
||||
await File.WriteAllLinesAsync(filePath, outputLines);
|
||||
await File.WriteAllLinesAsync(filePath, outputLines, cancellationToken);
|
||||
|
||||
await context.SendMessageAsync(new ReduceComplete(filePath));
|
||||
await context.SendMessageAsync(new ReduceComplete(filePath), cancellationToken: cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,16 +353,15 @@ internal sealed class Reducer(string id) : ReflectingExecutor<Reducer>(id), IMes
|
||||
/// Joins all reducer outputs and yields the final output.
|
||||
/// </summary>
|
||||
internal sealed class CompletionExecutor(string id) :
|
||||
ReflectingExecutor<CompletionExecutor>(id),
|
||||
IMessageHandler<List<ReduceComplete>>
|
||||
Executor<List<ReduceComplete>>(id)
|
||||
{
|
||||
/// <summary>
|
||||
/// Collect reducer output file paths and yield final output.
|
||||
/// </summary>
|
||||
public async ValueTask HandleAsync(List<ReduceComplete> message, IWorkflowContext context)
|
||||
public override async ValueTask HandleAsync(List<ReduceComplete> message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var filePaths = message.ConvertAll(r => r.FilePath);
|
||||
await context.YieldOutputAsync(filePaths);
|
||||
await context.YieldOutputAsync(filePaths, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+15
-16
@@ -6,7 +6,6 @@ using Azure.AI.OpenAI;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.Workflows;
|
||||
using Microsoft.Agents.AI.Workflows.Reflection;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace WorkflowEdgeConditionSample;
|
||||
@@ -64,7 +63,7 @@ public static class Program
|
||||
// Execute the workflow
|
||||
await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, new ChatMessage(ChatRole.User, email));
|
||||
await run.TrySendMessageAsync(new TurnToken(emitEvents: true));
|
||||
await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
|
||||
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
|
||||
{
|
||||
if (evt is WorkflowOutputEvent outputEvent)
|
||||
{
|
||||
@@ -147,7 +146,7 @@ internal sealed class Email
|
||||
/// <summary>
|
||||
/// Executor that detects spam using an AI agent.
|
||||
/// </summary>
|
||||
internal sealed class SpamDetectionExecutor : ReflectingExecutor<SpamDetectionExecutor>, IMessageHandler<ChatMessage, DetectionResult>
|
||||
internal sealed class SpamDetectionExecutor : Executor<ChatMessage, DetectionResult>
|
||||
{
|
||||
private readonly AIAgent _spamDetectionAgent;
|
||||
|
||||
@@ -160,7 +159,7 @@ internal sealed class SpamDetectionExecutor : ReflectingExecutor<SpamDetectionEx
|
||||
this._spamDetectionAgent = spamDetectionAgent;
|
||||
}
|
||||
|
||||
public async ValueTask<DetectionResult> HandleAsync(ChatMessage message, IWorkflowContext context)
|
||||
public override async ValueTask<DetectionResult> HandleAsync(ChatMessage message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Generate a random email ID and store the email content to the shared state
|
||||
var newEmail = new Email
|
||||
@@ -168,10 +167,10 @@ internal sealed class SpamDetectionExecutor : ReflectingExecutor<SpamDetectionEx
|
||||
EmailId = Guid.NewGuid().ToString("N"),
|
||||
EmailContent = message.Text
|
||||
};
|
||||
await context.QueueStateUpdateAsync(newEmail.EmailId, newEmail, scopeName: EmailStateConstants.EmailStateScope);
|
||||
await context.QueueStateUpdateAsync(newEmail.EmailId, newEmail, scopeName: EmailStateConstants.EmailStateScope, cancellationToken);
|
||||
|
||||
// Invoke the agent
|
||||
var response = await this._spamDetectionAgent.RunAsync(message);
|
||||
var response = await this._spamDetectionAgent.RunAsync(message, cancellationToken: cancellationToken);
|
||||
var detectionResult = JsonSerializer.Deserialize<DetectionResult>(response.Text);
|
||||
|
||||
detectionResult!.EmailId = newEmail.EmailId;
|
||||
@@ -192,7 +191,7 @@ public sealed class EmailResponse
|
||||
/// <summary>
|
||||
/// Executor that assists with email responses using an AI agent.
|
||||
/// </summary>
|
||||
internal sealed class EmailAssistantExecutor : ReflectingExecutor<EmailAssistantExecutor>, IMessageHandler<DetectionResult, EmailResponse>
|
||||
internal sealed class EmailAssistantExecutor : Executor<DetectionResult, EmailResponse>
|
||||
{
|
||||
private readonly AIAgent _emailAssistantAgent;
|
||||
|
||||
@@ -205,7 +204,7 @@ internal sealed class EmailAssistantExecutor : ReflectingExecutor<EmailAssistant
|
||||
this._emailAssistantAgent = emailAssistantAgent;
|
||||
}
|
||||
|
||||
public async ValueTask<EmailResponse> HandleAsync(DetectionResult message, IWorkflowContext context)
|
||||
public override async ValueTask<EmailResponse> HandleAsync(DetectionResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (message.IsSpam)
|
||||
{
|
||||
@@ -213,11 +212,11 @@ internal sealed class EmailAssistantExecutor : ReflectingExecutor<EmailAssistant
|
||||
}
|
||||
|
||||
// Retrieve the email content from the shared state
|
||||
var email = await context.ReadStateAsync<Email>(message.EmailId, scopeName: EmailStateConstants.EmailStateScope)
|
||||
var email = await context.ReadStateAsync<Email>(message.EmailId, scopeName: EmailStateConstants.EmailStateScope, cancellationToken)
|
||||
?? throw new InvalidOperationException("Email not found.");
|
||||
|
||||
// Invoke the agent
|
||||
var response = await this._emailAssistantAgent.RunAsync(email.EmailContent);
|
||||
var response = await this._emailAssistantAgent.RunAsync(email.EmailContent, cancellationToken: cancellationToken);
|
||||
var emailResponse = JsonSerializer.Deserialize<EmailResponse>(response.Text);
|
||||
|
||||
return emailResponse!;
|
||||
@@ -227,28 +226,28 @@ internal sealed class EmailAssistantExecutor : ReflectingExecutor<EmailAssistant
|
||||
/// <summary>
|
||||
/// Executor that sends emails.
|
||||
/// </summary>
|
||||
internal sealed class SendEmailExecutor() : ReflectingExecutor<SendEmailExecutor>("SendEmailExecutor"), IMessageHandler<EmailResponse>
|
||||
internal sealed class SendEmailExecutor() : Executor<EmailResponse>("SendEmailExecutor")
|
||||
{
|
||||
/// <summary>
|
||||
/// Simulate the sending of an email.
|
||||
/// </summary>
|
||||
public async ValueTask HandleAsync(EmailResponse message, IWorkflowContext context) =>
|
||||
await context.YieldOutputAsync($"Email sent: {message.Response}");
|
||||
public override async ValueTask HandleAsync(EmailResponse message, IWorkflowContext context, CancellationToken cancellationToken = default) =>
|
||||
await context.YieldOutputAsync($"Email sent: {message.Response}", cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executor that handles spam messages.
|
||||
/// </summary>
|
||||
internal sealed class HandleSpamExecutor() : ReflectingExecutor<HandleSpamExecutor>("HandleSpamExecutor"), IMessageHandler<DetectionResult>
|
||||
internal sealed class HandleSpamExecutor() : Executor<DetectionResult>("HandleSpamExecutor")
|
||||
{
|
||||
/// <summary>
|
||||
/// Simulate the handling of a spam message.
|
||||
/// </summary>
|
||||
public async ValueTask HandleAsync(DetectionResult message, IWorkflowContext context)
|
||||
public override async ValueTask HandleAsync(DetectionResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (message.IsSpam)
|
||||
{
|
||||
await context.YieldOutputAsync($"Email marked as spam: {message.Reason}");
|
||||
await context.YieldOutputAsync($"Email marked as spam: {message.Reason}", cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -6,7 +6,6 @@ using Azure.AI.OpenAI;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.Workflows;
|
||||
using Microsoft.Agents.AI.Workflows.Reflection;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace WorkflowSwitchCaseSample;
|
||||
@@ -80,7 +79,7 @@ public static class Program
|
||||
// Execute the workflow
|
||||
await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, new ChatMessage(ChatRole.User, email));
|
||||
await run.TrySendMessageAsync(new TurnToken(emitEvents: true));
|
||||
await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
|
||||
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
|
||||
{
|
||||
if (evt is WorkflowOutputEvent outputEvent)
|
||||
{
|
||||
@@ -172,7 +171,7 @@ internal sealed class Email
|
||||
/// <summary>
|
||||
/// Executor that detects spam using an AI agent.
|
||||
/// </summary>
|
||||
internal sealed class SpamDetectionExecutor : ReflectingExecutor<SpamDetectionExecutor>, IMessageHandler<ChatMessage, DetectionResult>
|
||||
internal sealed class SpamDetectionExecutor : Executor<ChatMessage, DetectionResult>
|
||||
{
|
||||
private readonly AIAgent _spamDetectionAgent;
|
||||
|
||||
@@ -185,7 +184,7 @@ internal sealed class SpamDetectionExecutor : ReflectingExecutor<SpamDetectionEx
|
||||
this._spamDetectionAgent = spamDetectionAgent;
|
||||
}
|
||||
|
||||
public async ValueTask<DetectionResult> HandleAsync(ChatMessage message, IWorkflowContext context)
|
||||
public override async ValueTask<DetectionResult> HandleAsync(ChatMessage message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Generate a random email ID and store the email content
|
||||
var newEmail = new Email
|
||||
@@ -193,10 +192,10 @@ internal sealed class SpamDetectionExecutor : ReflectingExecutor<SpamDetectionEx
|
||||
EmailId = Guid.NewGuid().ToString("N"),
|
||||
EmailContent = message.Text
|
||||
};
|
||||
await context.QueueStateUpdateAsync(newEmail.EmailId, newEmail, scopeName: EmailStateConstants.EmailStateScope);
|
||||
await context.QueueStateUpdateAsync(newEmail.EmailId, newEmail, scopeName: EmailStateConstants.EmailStateScope, cancellationToken);
|
||||
|
||||
// Invoke the agent
|
||||
var response = await this._spamDetectionAgent.RunAsync(message);
|
||||
var response = await this._spamDetectionAgent.RunAsync(message, cancellationToken: cancellationToken);
|
||||
var detectionResult = JsonSerializer.Deserialize<DetectionResult>(response.Text);
|
||||
|
||||
detectionResult!.EmailId = newEmail.EmailId;
|
||||
@@ -217,7 +216,7 @@ public sealed class EmailResponse
|
||||
/// <summary>
|
||||
/// Executor that assists with email responses using an AI agent.
|
||||
/// </summary>
|
||||
internal sealed class EmailAssistantExecutor : ReflectingExecutor<EmailAssistantExecutor>, IMessageHandler<DetectionResult, EmailResponse>
|
||||
internal sealed class EmailAssistantExecutor : Executor<DetectionResult, EmailResponse>
|
||||
{
|
||||
private readonly AIAgent _emailAssistantAgent;
|
||||
|
||||
@@ -230,7 +229,7 @@ internal sealed class EmailAssistantExecutor : ReflectingExecutor<EmailAssistant
|
||||
this._emailAssistantAgent = emailAssistantAgent;
|
||||
}
|
||||
|
||||
public async ValueTask<EmailResponse> HandleAsync(DetectionResult message, IWorkflowContext context)
|
||||
public override async ValueTask<EmailResponse> HandleAsync(DetectionResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (message.spamDecision == SpamDecision.Spam)
|
||||
{
|
||||
@@ -238,10 +237,10 @@ internal sealed class EmailAssistantExecutor : ReflectingExecutor<EmailAssistant
|
||||
}
|
||||
|
||||
// Retrieve the email content from the context
|
||||
var email = await context.ReadStateAsync<Email>(message.EmailId, scopeName: EmailStateConstants.EmailStateScope);
|
||||
var email = await context.ReadStateAsync<Email>(message.EmailId, scopeName: EmailStateConstants.EmailStateScope, cancellationToken);
|
||||
|
||||
// Invoke the agent
|
||||
var response = await this._emailAssistantAgent.RunAsync(email!.EmailContent);
|
||||
var response = await this._emailAssistantAgent.RunAsync(email!.EmailContent, cancellationToken: cancellationToken);
|
||||
var emailResponse = JsonSerializer.Deserialize<EmailResponse>(response.Text);
|
||||
|
||||
return emailResponse!;
|
||||
@@ -251,28 +250,28 @@ internal sealed class EmailAssistantExecutor : ReflectingExecutor<EmailAssistant
|
||||
/// <summary>
|
||||
/// Executor that sends emails.
|
||||
/// </summary>
|
||||
internal sealed class SendEmailExecutor() : ReflectingExecutor<SendEmailExecutor>("SendEmailExecutor"), IMessageHandler<EmailResponse>
|
||||
internal sealed class SendEmailExecutor() : Executor<EmailResponse>("SendEmailExecutor")
|
||||
{
|
||||
/// <summary>
|
||||
/// Simulate the sending of an email.
|
||||
/// </summary>
|
||||
public async ValueTask HandleAsync(EmailResponse message, IWorkflowContext context) =>
|
||||
await context.YieldOutputAsync($"Email sent: {message.Response}").ConfigureAwait(false);
|
||||
public override async ValueTask HandleAsync(EmailResponse message, IWorkflowContext context, CancellationToken cancellationToken = default) =>
|
||||
await context.YieldOutputAsync($"Email sent: {message.Response}", cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executor that handles spam messages.
|
||||
/// </summary>
|
||||
internal sealed class HandleSpamExecutor() : ReflectingExecutor<HandleSpamExecutor>("HandleSpamExecutor"), IMessageHandler<DetectionResult>
|
||||
internal sealed class HandleSpamExecutor() : Executor<DetectionResult>("HandleSpamExecutor")
|
||||
{
|
||||
/// <summary>
|
||||
/// Simulate the handling of a spam message.
|
||||
/// </summary>
|
||||
public async ValueTask HandleAsync(DetectionResult message, IWorkflowContext context)
|
||||
public override async ValueTask HandleAsync(DetectionResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (message.spamDecision == SpamDecision.Spam)
|
||||
{
|
||||
await context.YieldOutputAsync($"Email marked as spam: {message.Reason}").ConfigureAwait(false);
|
||||
await context.YieldOutputAsync($"Email marked as spam: {message.Reason}", cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -284,17 +283,17 @@ internal sealed class HandleSpamExecutor() : ReflectingExecutor<HandleSpamExecut
|
||||
/// <summary>
|
||||
/// Executor that handles uncertain emails.
|
||||
/// </summary>
|
||||
internal sealed class HandleUncertainExecutor() : ReflectingExecutor<HandleUncertainExecutor>("HandleUncertainExecutor"), IMessageHandler<DetectionResult>
|
||||
internal sealed class HandleUncertainExecutor() : Executor<DetectionResult>("HandleUncertainExecutor")
|
||||
{
|
||||
/// <summary>
|
||||
/// Simulate the handling of an uncertain spam decision.
|
||||
/// </summary>
|
||||
public async ValueTask HandleAsync(DetectionResult message, IWorkflowContext context)
|
||||
public override async ValueTask HandleAsync(DetectionResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (message.spamDecision == SpamDecision.Uncertain)
|
||||
{
|
||||
var email = await context.ReadStateAsync<Email>(message.EmailId, scopeName: EmailStateConstants.EmailStateScope);
|
||||
await context.YieldOutputAsync($"Email marked as uncertain: {message.Reason}. Email content: {email?.EmailContent}");
|
||||
var email = await context.ReadStateAsync<Email>(message.EmailId, scopeName: EmailStateConstants.EmailStateScope, cancellationToken);
|
||||
await context.YieldOutputAsync($"Email marked as uncertain: {message.Reason}. Email content: {email?.EmailContent}", cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+29
-30
@@ -6,7 +6,6 @@ using Azure.AI.OpenAI;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.Workflows;
|
||||
using Microsoft.Agents.AI.Workflows.Reflection;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace WorkflowMultiSelectionSample;
|
||||
@@ -88,7 +87,7 @@ public static class Program
|
||||
// Execute the workflow
|
||||
await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, new ChatMessage(ChatRole.User, email));
|
||||
await run.TrySendMessageAsync(new TurnToken(emitEvents: true));
|
||||
await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
|
||||
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
|
||||
{
|
||||
if (evt is WorkflowOutputEvent outputEvent)
|
||||
{
|
||||
@@ -228,7 +227,7 @@ internal sealed class Email
|
||||
/// <summary>
|
||||
/// Executor that analyzes emails using an AI agent.
|
||||
/// </summary>
|
||||
internal sealed class EmailAnalysisExecutor : ReflectingExecutor<EmailAnalysisExecutor>, IMessageHandler<ChatMessage, AnalysisResult>
|
||||
internal sealed class EmailAnalysisExecutor : Executor<ChatMessage, AnalysisResult>
|
||||
{
|
||||
private readonly AIAgent _emailAnalysisAgent;
|
||||
|
||||
@@ -241,7 +240,7 @@ internal sealed class EmailAnalysisExecutor : ReflectingExecutor<EmailAnalysisEx
|
||||
this._emailAnalysisAgent = emailAnalysisAgent;
|
||||
}
|
||||
|
||||
public async ValueTask<AnalysisResult> HandleAsync(ChatMessage message, IWorkflowContext context)
|
||||
public override async ValueTask<AnalysisResult> HandleAsync(ChatMessage message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Generate a random email ID and store the email content
|
||||
var newEmail = new Email
|
||||
@@ -249,10 +248,10 @@ internal sealed class EmailAnalysisExecutor : ReflectingExecutor<EmailAnalysisEx
|
||||
EmailId = Guid.NewGuid().ToString("N"),
|
||||
EmailContent = message.Text
|
||||
};
|
||||
await context.QueueStateUpdateAsync(newEmail.EmailId, newEmail, scopeName: EmailStateConstants.EmailStateScope);
|
||||
await context.QueueStateUpdateAsync(newEmail.EmailId, newEmail, scopeName: EmailStateConstants.EmailStateScope, cancellationToken);
|
||||
|
||||
// Invoke the agent
|
||||
var response = await this._emailAnalysisAgent.RunAsync(message);
|
||||
var response = await this._emailAnalysisAgent.RunAsync(message, cancellationToken: cancellationToken);
|
||||
var AnalysisResult = JsonSerializer.Deserialize<AnalysisResult>(response.Text);
|
||||
|
||||
AnalysisResult!.EmailId = newEmail.EmailId;
|
||||
@@ -274,7 +273,7 @@ public sealed class EmailResponse
|
||||
/// <summary>
|
||||
/// Executor that assists with email responses using an AI agent.
|
||||
/// </summary>
|
||||
internal sealed class EmailAssistantExecutor : ReflectingExecutor<EmailAssistantExecutor>, IMessageHandler<AnalysisResult, EmailResponse>
|
||||
internal sealed class EmailAssistantExecutor : Executor<AnalysisResult, EmailResponse>
|
||||
{
|
||||
private readonly AIAgent _emailAssistantAgent;
|
||||
|
||||
@@ -287,7 +286,7 @@ internal sealed class EmailAssistantExecutor : ReflectingExecutor<EmailAssistant
|
||||
this._emailAssistantAgent = emailAssistantAgent;
|
||||
}
|
||||
|
||||
public async ValueTask<EmailResponse> HandleAsync(AnalysisResult message, IWorkflowContext context)
|
||||
public override async ValueTask<EmailResponse> HandleAsync(AnalysisResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (message.spamDecision == SpamDecision.Spam)
|
||||
{
|
||||
@@ -295,10 +294,10 @@ internal sealed class EmailAssistantExecutor : ReflectingExecutor<EmailAssistant
|
||||
}
|
||||
|
||||
// Retrieve the email content from the context
|
||||
var email = await context.ReadStateAsync<Email>(message.EmailId, scopeName: EmailStateConstants.EmailStateScope);
|
||||
var email = await context.ReadStateAsync<Email>(message.EmailId, scopeName: EmailStateConstants.EmailStateScope, cancellationToken);
|
||||
|
||||
// Invoke the agent
|
||||
var response = await this._emailAssistantAgent.RunAsync(email!.EmailContent);
|
||||
var response = await this._emailAssistantAgent.RunAsync(email!.EmailContent, cancellationToken: cancellationToken);
|
||||
var emailResponse = JsonSerializer.Deserialize<EmailResponse>(response.Text);
|
||||
|
||||
return emailResponse!;
|
||||
@@ -308,28 +307,28 @@ internal sealed class EmailAssistantExecutor : ReflectingExecutor<EmailAssistant
|
||||
/// <summary>
|
||||
/// Executor that sends emails.
|
||||
/// </summary>
|
||||
internal sealed class SendEmailExecutor() : ReflectingExecutor<SendEmailExecutor>("SendEmailExecutor"), IMessageHandler<EmailResponse>
|
||||
internal sealed class SendEmailExecutor() : Executor<EmailResponse>("SendEmailExecutor")
|
||||
{
|
||||
/// <summary>
|
||||
/// Simulate the sending of an email.
|
||||
/// </summary>
|
||||
public async ValueTask HandleAsync(EmailResponse message, IWorkflowContext context) =>
|
||||
await context.YieldOutputAsync($"Email sent: {message.Response}");
|
||||
public override async ValueTask HandleAsync(EmailResponse message, IWorkflowContext context, CancellationToken cancellationToken = default) =>
|
||||
await context.YieldOutputAsync($"Email sent: {message.Response}", cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executor that handles spam messages.
|
||||
/// </summary>
|
||||
internal sealed class HandleSpamExecutor() : ReflectingExecutor<HandleSpamExecutor>("HandleSpamExecutor"), IMessageHandler<AnalysisResult>
|
||||
internal sealed class HandleSpamExecutor() : Executor<AnalysisResult>("HandleSpamExecutor")
|
||||
{
|
||||
/// <summary>
|
||||
/// Simulate the handling of a spam message.
|
||||
/// </summary>
|
||||
public async ValueTask HandleAsync(AnalysisResult message, IWorkflowContext context)
|
||||
public override async ValueTask HandleAsync(AnalysisResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (message.spamDecision == SpamDecision.Spam)
|
||||
{
|
||||
await context.YieldOutputAsync($"Email marked as spam: {message.Reason}");
|
||||
await context.YieldOutputAsync($"Email marked as spam: {message.Reason}", cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -341,17 +340,17 @@ internal sealed class HandleSpamExecutor() : ReflectingExecutor<HandleSpamExecut
|
||||
/// <summary>
|
||||
/// Executor that handles uncertain messages.
|
||||
/// </summary>
|
||||
internal sealed class HandleUncertainExecutor() : ReflectingExecutor<HandleUncertainExecutor>("HandleUncertainExecutor"), IMessageHandler<AnalysisResult>
|
||||
internal sealed class HandleUncertainExecutor() : Executor<AnalysisResult>("HandleUncertainExecutor")
|
||||
{
|
||||
/// <summary>
|
||||
/// Simulate the handling of an uncertain spam decision.
|
||||
/// </summary>
|
||||
public async ValueTask HandleAsync(AnalysisResult message, IWorkflowContext context)
|
||||
public override async ValueTask HandleAsync(AnalysisResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (message.spamDecision == SpamDecision.Uncertain)
|
||||
{
|
||||
var email = await context.ReadStateAsync<Email>(message.EmailId, scopeName: EmailStateConstants.EmailStateScope);
|
||||
await context.YieldOutputAsync($"Email marked as uncertain: {message.Reason}. Email content: {email?.EmailContent}");
|
||||
var email = await context.ReadStateAsync<Email>(message.EmailId, scopeName: EmailStateConstants.EmailStateScope, cancellationToken);
|
||||
await context.YieldOutputAsync($"Email marked as uncertain: {message.Reason}. Email content: {email?.EmailContent}", cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -372,7 +371,7 @@ public sealed class EmailSummary
|
||||
/// <summary>
|
||||
/// Executor that summarizes emails using an AI agent.
|
||||
/// </summary>
|
||||
internal sealed class EmailSummaryExecutor : ReflectingExecutor<EmailSummaryExecutor>, IMessageHandler<AnalysisResult, AnalysisResult>
|
||||
internal sealed class EmailSummaryExecutor : Executor<AnalysisResult, AnalysisResult>
|
||||
{
|
||||
private readonly AIAgent _emailSummaryAgent;
|
||||
|
||||
@@ -385,13 +384,13 @@ internal sealed class EmailSummaryExecutor : ReflectingExecutor<EmailSummaryExec
|
||||
this._emailSummaryAgent = emailSummaryAgent;
|
||||
}
|
||||
|
||||
public async ValueTask<AnalysisResult> HandleAsync(AnalysisResult message, IWorkflowContext context)
|
||||
public override async ValueTask<AnalysisResult> HandleAsync(AnalysisResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Read the email content from the shared states
|
||||
var email = await context.ReadStateAsync<Email>(message.EmailId, scopeName: EmailStateConstants.EmailStateScope);
|
||||
var email = await context.ReadStateAsync<Email>(message.EmailId, scopeName: EmailStateConstants.EmailStateScope, cancellationToken);
|
||||
|
||||
// Invoke the agent
|
||||
var response = await this._emailSummaryAgent.RunAsync(email!.EmailContent);
|
||||
var response = await this._emailSummaryAgent.RunAsync(email!.EmailContent, cancellationToken: cancellationToken);
|
||||
var emailSummary = JsonSerializer.Deserialize<EmailSummary>(response.Text);
|
||||
message.EmailSummary = emailSummary!.Summary;
|
||||
|
||||
@@ -408,19 +407,19 @@ internal sealed class DatabaseEvent(string message) : WorkflowEvent(message) { }
|
||||
/// <summary>
|
||||
/// Executor that handles database access.
|
||||
/// </summary>
|
||||
internal sealed class DatabaseAccessExecutor() : ReflectingExecutor<DatabaseAccessExecutor>("DatabaseAccessExecutor"), IMessageHandler<AnalysisResult>
|
||||
internal sealed class DatabaseAccessExecutor() : Executor<AnalysisResult>("DatabaseAccessExecutor")
|
||||
{
|
||||
public async ValueTask HandleAsync(AnalysisResult message, IWorkflowContext context)
|
||||
public override async ValueTask HandleAsync(AnalysisResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. Save the email content
|
||||
await context.ReadStateAsync<Email>(message.EmailId, scopeName: EmailStateConstants.EmailStateScope);
|
||||
await Task.Delay(100); // Simulate database access delay
|
||||
await context.ReadStateAsync<Email>(message.EmailId, scopeName: EmailStateConstants.EmailStateScope, cancellationToken);
|
||||
await Task.Delay(100, cancellationToken); // Simulate database access delay
|
||||
|
||||
// 2. Save the analysis result
|
||||
await Task.Delay(100); // Simulate database access delay
|
||||
await Task.Delay(100, cancellationToken); // Simulate database access delay
|
||||
|
||||
// Not using the `WorkflowCompletedEvent` because this is not the end of the workflow.
|
||||
// The end of the workflow is signaled by the `SendEmailExecutor` or the `HandleUnknownExecutor`.
|
||||
await context.AddEventAsync(new DatabaseEvent($"Email {message.EmailId} saved to database."));
|
||||
await context.AddEventAsync(new DatabaseEvent($"Email {message.EmailId} saved to database."), cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,22 +52,22 @@ public static class TestWorkflowProvider
|
||||
"FOUNDRY_AGENT_RESEARCHWEATHER").ConfigureAwait(false);
|
||||
|
||||
// Initialize variables
|
||||
await context.QueueStateUpdateAsync("AgentResponse", UnassignedValue.Instance, "Local").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync("AgentResponseText", UnassignedValue.Instance, "Local").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync("AvailableAgents", UnassignedValue.Instance, "Local").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync("FinalResponse", UnassignedValue.Instance, "Local").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync("InputTask", UnassignedValue.Instance, "Local").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync("InternalConversationId", UnassignedValue.Instance, "Local").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync("NextSpeaker", UnassignedValue.Instance, "Local").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync("Plan", UnassignedValue.Instance, "Local").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync("ProgressLedgerUpdate", UnassignedValue.Instance, "Local").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync("RestartCount", UnassignedValue.Instance, "Local").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync("SeedTask", UnassignedValue.Instance, "Local").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync("StallCount", UnassignedValue.Instance, "Local").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync("TaskFacts", UnassignedValue.Instance, "Local").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync("TaskInstructions", UnassignedValue.Instance, "Local").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync("TeamDescription", UnassignedValue.Instance, "Local").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync("TypedProgressLedger", UnassignedValue.Instance, "Local").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync("AgentResponse", UnassignedValue.Instance, "Local");
|
||||
await context.QueueStateUpdateAsync("AgentResponseText", UnassignedValue.Instance, "Local");
|
||||
await context.QueueStateUpdateAsync("AvailableAgents", UnassignedValue.Instance, "Local");
|
||||
await context.QueueStateUpdateAsync("FinalResponse", UnassignedValue.Instance, "Local");
|
||||
await context.QueueStateUpdateAsync("InputTask", UnassignedValue.Instance, "Local");
|
||||
await context.QueueStateUpdateAsync("InternalConversationId", UnassignedValue.Instance, "Local");
|
||||
await context.QueueStateUpdateAsync("NextSpeaker", UnassignedValue.Instance, "Local");
|
||||
await context.QueueStateUpdateAsync("Plan", UnassignedValue.Instance, "Local");
|
||||
await context.QueueStateUpdateAsync("ProgressLedgerUpdate", UnassignedValue.Instance, "Local");
|
||||
await context.QueueStateUpdateAsync("RestartCount", UnassignedValue.Instance, "Local");
|
||||
await context.QueueStateUpdateAsync("SeedTask", UnassignedValue.Instance, "Local");
|
||||
await context.QueueStateUpdateAsync("StallCount", UnassignedValue.Instance, "Local");
|
||||
await context.QueueStateUpdateAsync("TaskFacts", UnassignedValue.Instance, "Local");
|
||||
await context.QueueStateUpdateAsync("TaskInstructions", UnassignedValue.Instance, "Local");
|
||||
await context.QueueStateUpdateAsync("TeamDescription", UnassignedValue.Instance, "Local");
|
||||
await context.QueueStateUpdateAsync("TypedProgressLedger", UnassignedValue.Instance, "Local");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,8 +97,8 @@ public static class TestWorkflowProvider
|
||||
agentid: Env.FOUNDRY_AGENT_RESEARCHWEB
|
||||
}
|
||||
]
|
||||
""").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync(key: "AvailableAgents", value: evaluatedValue, scopeName: "Local").ConfigureAwait(false);
|
||||
""");
|
||||
await context.QueueStateUpdateAsync(key: "AvailableAgents", value: evaluatedValue, scopeName: "Local");
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -115,8 +115,8 @@ public static class TestWorkflowProvider
|
||||
object? evaluatedValue = await context.EvaluateValueAsync<object>("""
|
||||
Concat(ForAll(Local.AvailableAgents, $"- " & name & $": " & description), Value, "
|
||||
")
|
||||
""").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync(key: "TeamDescription", value: evaluatedValue, scopeName: "Local").ConfigureAwait(false);
|
||||
""");
|
||||
await context.QueueStateUpdateAsync(key: "TeamDescription", value: evaluatedValue, scopeName: "Local");
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -130,8 +130,8 @@ public static class TestWorkflowProvider
|
||||
// <inheritdoc />
|
||||
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
object? evaluatedValue = await context.EvaluateValueAsync<object>("System.LastMessage.Text").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync(key: "InputTask", value: evaluatedValue, scopeName: "Local").ConfigureAwait(false);
|
||||
object? evaluatedValue = await context.EvaluateValueAsync<object>("System.LastMessage.Text");
|
||||
await context.QueueStateUpdateAsync(key: "InputTask", value: evaluatedValue, scopeName: "Local");
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -145,8 +145,8 @@ public static class TestWorkflowProvider
|
||||
// <inheritdoc />
|
||||
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
object? evaluatedValue = await context.EvaluateValueAsync<object>("UserMessage(Local.InputTask)").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync(key: "SeedTask", value: evaluatedValue, scopeName: "Local").ConfigureAwait(false);
|
||||
object? evaluatedValue = await context.EvaluateValueAsync<object>("UserMessage(Local.InputTask)");
|
||||
await context.QueueStateUpdateAsync(key: "SeedTask", value: evaluatedValue, scopeName: "Local");
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -167,7 +167,7 @@ public static class TestWorkflowProvider
|
||||
"""
|
||||
);
|
||||
AgentRunResponse response = new([new ChatMessage(ChatRole.Assistant, activityText)]);
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response)).ConfigureAwait(false);
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response));
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -180,8 +180,8 @@ public static class TestWorkflowProvider
|
||||
{
|
||||
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
string conversationId = await agentProvider.CreateConversationAsync(cancellationToken).ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync(key: "InternalConversationId", value: conversationId, scopeName: "Local").ConfigureAwait(false);
|
||||
string conversationId = await agentProvider.CreateConversationAsync(cancellationToken);
|
||||
await context.QueueStateUpdateAsync(key: "InternalConversationId", value: conversationId, scopeName: "Local");
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -195,14 +195,14 @@ public static class TestWorkflowProvider
|
||||
// <inheritdoc />
|
||||
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
string? agentName = await context.ReadStateAsync<string>(key: "FOUNDRY_AGENT_RESEARCHANALYST", scopeName: "Env").ConfigureAwait(false);
|
||||
string? agentName = await context.ReadStateAsync<string>(key: "FOUNDRY_AGENT_RESEARCHANALYST", scopeName: "Env");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(agentName))
|
||||
{
|
||||
throw new InvalidOperationException($"Agent name must be defined: {this.Id}");
|
||||
}
|
||||
|
||||
string? conversationId = await context.ReadStateAsync<string>(key: "InternalConversationId", scopeName: "Local").ConfigureAwait(false);
|
||||
string? conversationId = await context.ReadStateAsync<string>(key: "InternalConversationId", scopeName: "Local");
|
||||
bool autoSend = true;
|
||||
string additionalInstructions =
|
||||
await context.FormatTemplateAsync(
|
||||
@@ -226,7 +226,7 @@ public static class TestWorkflowProvider
|
||||
|
||||
DO NOT include any other headings or sections in your response. DO NOT list next steps or plans until asked to do so.
|
||||
""");
|
||||
IList<ChatMessage>? inputMessages = await context.EvaluateListAsync<ChatMessage>("UserMessage(Local.InputTask)").ConfigureAwait(false);
|
||||
IList<ChatMessage>? inputMessages = await context.EvaluateListAsync<ChatMessage>("UserMessage(Local.InputTask)");
|
||||
|
||||
AgentRunResponse agentResponse =
|
||||
await InvokeAgentAsync(
|
||||
@@ -236,14 +236,14 @@ public static class TestWorkflowProvider
|
||||
autoSend,
|
||||
additionalInstructions,
|
||||
inputMessages,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
cancellationToken);
|
||||
|
||||
if (autoSend)
|
||||
{
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, agentResponse)).ConfigureAwait(false);
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, agentResponse));
|
||||
}
|
||||
|
||||
await context.QueueStateUpdateAsync(key: "TaskFacts", value: agentResponse.Messages, scopeName: "Local").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync(key: "TaskFacts", value: agentResponse.Messages, scopeName: "Local");
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -264,7 +264,7 @@ public static class TestWorkflowProvider
|
||||
"""
|
||||
);
|
||||
AgentRunResponse response = new([new ChatMessage(ChatRole.Assistant, activityText)]);
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response)).ConfigureAwait(false);
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response));
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -278,14 +278,14 @@ public static class TestWorkflowProvider
|
||||
// <inheritdoc />
|
||||
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
string? agentName = await context.ReadStateAsync<string>(key: "FOUNDRY_AGENT_RESEARCHMANAGER", scopeName: "Env").ConfigureAwait(false);
|
||||
string? agentName = await context.ReadStateAsync<string>(key: "FOUNDRY_AGENT_RESEARCHMANAGER", scopeName: "Env");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(agentName))
|
||||
{
|
||||
throw new InvalidOperationException($"Agent name must be defined: {this.Id}");
|
||||
}
|
||||
|
||||
string? conversationId = await context.ReadStateAsync<string>(key: "InternalConversationId", scopeName: "Local").ConfigureAwait(false);
|
||||
string? conversationId = await context.ReadStateAsync<string>(key: "InternalConversationId", scopeName: "Local");
|
||||
bool autoSend = true;
|
||||
string additionalInstructions =
|
||||
await context.FormatTemplateAsync(
|
||||
@@ -300,7 +300,7 @@ public static class TestWorkflowProvider
|
||||
|
||||
Remember, there is no requirement to involve the entire team -- only select team member's whose particular expertise is required for this task.
|
||||
""");
|
||||
IList<ChatMessage>? inputMessages = await context.EvaluateListAsync<ChatMessage>("UserMessage(Local.InputTask)").ConfigureAwait(false);
|
||||
IList<ChatMessage>? inputMessages = await context.EvaluateListAsync<ChatMessage>("UserMessage(Local.InputTask)");
|
||||
|
||||
AgentRunResponse agentResponse =
|
||||
await InvokeAgentAsync(
|
||||
@@ -310,14 +310,14 @@ public static class TestWorkflowProvider
|
||||
autoSend,
|
||||
additionalInstructions,
|
||||
inputMessages,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
cancellationToken);
|
||||
|
||||
if (autoSend)
|
||||
{
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, agentResponse)).ConfigureAwait(false);
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, agentResponse));
|
||||
}
|
||||
|
||||
await context.QueueStateUpdateAsync(key: "Plan", value: agentResponse.Messages, scopeName: "Local").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync(key: "Plan", value: agentResponse.Messages, scopeName: "Local");
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -354,8 +354,8 @@ public static class TestWorkflowProvider
|
||||
Here is the plan to follow as best as possible:
|
||||
|
||||
" & Last(Local.Plan).Text
|
||||
""").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync(key: "TaskInstructions", value: evaluatedValue, scopeName: "Local").ConfigureAwait(false);
|
||||
""");
|
||||
await context.QueueStateUpdateAsync(key: "TaskInstructions", value: evaluatedValue, scopeName: "Local");
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -376,7 +376,7 @@ public static class TestWorkflowProvider
|
||||
"""
|
||||
);
|
||||
AgentRunResponse response = new([new ChatMessage(ChatRole.Assistant, activityText)]);
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response)).ConfigureAwait(false);
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response));
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -390,14 +390,14 @@ public static class TestWorkflowProvider
|
||||
// <inheritdoc />
|
||||
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
string? agentName = await context.ReadStateAsync<string>(key: "FOUNDRY_AGENT_RESEARCHMANAGER", scopeName: "Env").ConfigureAwait(false);
|
||||
string? agentName = await context.ReadStateAsync<string>(key: "FOUNDRY_AGENT_RESEARCHMANAGER", scopeName: "Env");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(agentName))
|
||||
{
|
||||
throw new InvalidOperationException($"Agent name must be defined: {this.Id}");
|
||||
}
|
||||
|
||||
string? conversationId = await context.ReadStateAsync<string>(key: "InternalConversationId", scopeName: "Local").ConfigureAwait(false);
|
||||
string? conversationId = await context.ReadStateAsync<string>(key: "InternalConversationId", scopeName: "Local");
|
||||
bool autoSend = true;
|
||||
string additionalInstructions =
|
||||
await context.FormatTemplateAsync(
|
||||
@@ -443,7 +443,7 @@ public static class TestWorkflowProvider
|
||||
}}
|
||||
}}
|
||||
""");
|
||||
IList<ChatMessage>? inputMessages = await context.EvaluateListAsync<ChatMessage>("UserMessage(Local.AgentResponseText)").ConfigureAwait(false);
|
||||
IList<ChatMessage>? inputMessages = await context.EvaluateListAsync<ChatMessage>("UserMessage(Local.AgentResponseText)");
|
||||
|
||||
AgentRunResponse agentResponse =
|
||||
await InvokeAgentAsync(
|
||||
@@ -453,14 +453,14 @@ public static class TestWorkflowProvider
|
||||
autoSend,
|
||||
additionalInstructions,
|
||||
inputMessages,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
cancellationToken);
|
||||
|
||||
if (autoSend)
|
||||
{
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, agentResponse)).ConfigureAwait(false);
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, agentResponse));
|
||||
}
|
||||
|
||||
await context.QueueStateUpdateAsync(key: "ProgressLedgerUpdate", value: agentResponse.Messages, scopeName: "Local").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync(key: "ProgressLedgerUpdate", value: agentResponse.Messages, scopeName: "Local");
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -496,8 +496,8 @@ public static class TestWorkflowProvider
|
||||
VariableType.Record(
|
||||
("reason", typeof(string)),
|
||||
("answer", typeof(string)))));
|
||||
object? parsedValue = await context.ConvertValueAsync(targetType, "Last(Local.ProgressLedgerUpdate).Text", cancellationToken).ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync(key: "TypedProgressLedger", value: parsedValue, scopeName: "Local").ConfigureAwait(false);
|
||||
object? parsedValue = await context.ConvertValueAsync(targetType, "Last(Local.ProgressLedgerUpdate).Text", cancellationToken);
|
||||
await context.QueueStateUpdateAsync(key: "TypedProgressLedger", value: parsedValue, scopeName: "Local");
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -511,13 +511,13 @@ public static class TestWorkflowProvider
|
||||
// <inheritdoc />
|
||||
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
bool condition0 = await context.EvaluateValueAsync<bool>("Local.TypedProgressLedger.is_request_satisfied.answer").ConfigureAwait(false);
|
||||
bool condition0 = await context.EvaluateValueAsync<bool>("Local.TypedProgressLedger.is_request_satisfied.answer");
|
||||
if (condition0)
|
||||
{
|
||||
return "conditionItem_fj432c";
|
||||
}
|
||||
|
||||
bool condition1 = await context.EvaluateValueAsync<bool>("Local.TypedProgressLedger.is_in_loop.answer || Not(Local.TypedProgressLedger.is_progress_being_made.answer)").ConfigureAwait(false);
|
||||
bool condition1 = await context.EvaluateValueAsync<bool>("Local.TypedProgressLedger.is_in_loop.answer || Not(Local.TypedProgressLedger.is_progress_being_made.answer)");
|
||||
if (condition1)
|
||||
{
|
||||
return "conditionItem_yiqund";
|
||||
@@ -542,7 +542,7 @@ public static class TestWorkflowProvider
|
||||
"""
|
||||
);
|
||||
AgentRunResponse response = new([new ChatMessage(ChatRole.Assistant, activityText)]);
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response)).ConfigureAwait(false);
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response));
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -556,14 +556,14 @@ public static class TestWorkflowProvider
|
||||
// <inheritdoc />
|
||||
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
string? agentName = await context.ReadStateAsync<string>(key: "FOUNDRY_AGENT_RESEARCHMANAGER", scopeName: "Env").ConfigureAwait(false);
|
||||
string? agentName = await context.ReadStateAsync<string>(key: "FOUNDRY_AGENT_RESEARCHMANAGER", scopeName: "Env");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(agentName))
|
||||
{
|
||||
throw new InvalidOperationException($"Agent name must be defined: {this.Id}");
|
||||
}
|
||||
|
||||
string? conversationId = await context.ReadStateAsync<string>(key: "ConversationId", scopeName: "System").ConfigureAwait(false);
|
||||
string? conversationId = await context.ReadStateAsync<string>(key: "ConversationId", scopeName: "System");
|
||||
bool autoSend = true;
|
||||
string additionalInstructions =
|
||||
await context.FormatTemplateAsync(
|
||||
@@ -572,7 +572,7 @@ public static class TestWorkflowProvider
|
||||
Based only on the conversation and without adding any new information, synthesize the result of the conversation as a complete response to the user task.
|
||||
The user will only every see this last response and not the entire conversation, so please ensure it is complete and self-contained.
|
||||
""");
|
||||
IList<ChatMessage>? inputMessages = await context.ReadListAsync<ChatMessage>(key: "SeedTask", scopeName: "Local").ConfigureAwait(false);
|
||||
IList<ChatMessage>? inputMessages = await context.ReadListAsync<ChatMessage>(key: "SeedTask", scopeName: "Local");
|
||||
|
||||
AgentRunResponse agentResponse =
|
||||
await InvokeAgentAsync(
|
||||
@@ -582,14 +582,14 @@ public static class TestWorkflowProvider
|
||||
autoSend,
|
||||
additionalInstructions,
|
||||
inputMessages,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
cancellationToken);
|
||||
|
||||
if (autoSend)
|
||||
{
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, agentResponse)).ConfigureAwait(false);
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, agentResponse));
|
||||
}
|
||||
|
||||
await context.QueueStateUpdateAsync(key: "FinalResponse", value: agentResponse.Messages, scopeName: "Local").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync(key: "FinalResponse", value: agentResponse.Messages, scopeName: "Local");
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -603,8 +603,8 @@ public static class TestWorkflowProvider
|
||||
// <inheritdoc />
|
||||
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
object? evaluatedValue = await context.EvaluateValueAsync<object>("Local.StallCount + 1").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync(key: "StallCount", value: evaluatedValue, scopeName: "Local").ConfigureAwait(false);
|
||||
object? evaluatedValue = await context.EvaluateValueAsync<object>("Local.StallCount + 1");
|
||||
await context.QueueStateUpdateAsync(key: "StallCount", value: evaluatedValue, scopeName: "Local");
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -618,13 +618,13 @@ public static class TestWorkflowProvider
|
||||
// <inheritdoc />
|
||||
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
bool condition0 = await context.EvaluateValueAsync<bool>(".TypedProgressLedger.is_in_loop.answer").ConfigureAwait(false);
|
||||
bool condition0 = await context.EvaluateValueAsync<bool>(".TypedProgressLedger.is_in_loop.answer");
|
||||
if (condition0)
|
||||
{
|
||||
return "conditionItem_fpaNL9";
|
||||
}
|
||||
|
||||
bool condition1 = await context.EvaluateValueAsync<bool>("Not(Local.TypedProgressLedger.is_progress_being_made.answer)").ConfigureAwait(false);
|
||||
bool condition1 = await context.EvaluateValueAsync<bool>("Not(Local.TypedProgressLedger.is_progress_being_made.answer)");
|
||||
if (condition1)
|
||||
{
|
||||
return "conditionItem_NnqvXh";
|
||||
@@ -649,7 +649,7 @@ public static class TestWorkflowProvider
|
||||
"""
|
||||
);
|
||||
AgentRunResponse response = new([new ChatMessage(ChatRole.Assistant, activityText)]);
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response)).ConfigureAwait(false);
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response));
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -670,7 +670,7 @@ public static class TestWorkflowProvider
|
||||
"""
|
||||
);
|
||||
AgentRunResponse response = new([new ChatMessage(ChatRole.Assistant, activityText)]);
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response)).ConfigureAwait(false);
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response));
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -684,7 +684,7 @@ public static class TestWorkflowProvider
|
||||
// <inheritdoc />
|
||||
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
bool condition0 = await context.EvaluateValueAsync<bool>("Local.StallCount > 2").ConfigureAwait(false);
|
||||
bool condition0 = await context.EvaluateValueAsync<bool>("Local.StallCount > 2");
|
||||
if (condition0)
|
||||
{
|
||||
return "conditionItem_NlQTBv";
|
||||
@@ -709,7 +709,7 @@ public static class TestWorkflowProvider
|
||||
"""
|
||||
);
|
||||
AgentRunResponse response = new([new ChatMessage(ChatRole.Assistant, activityText)]);
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response)).ConfigureAwait(false);
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response));
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -723,7 +723,7 @@ public static class TestWorkflowProvider
|
||||
// <inheritdoc />
|
||||
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
bool condition0 = await context.EvaluateValueAsync<bool>("Local.RestartCount > 2").ConfigureAwait(false);
|
||||
bool condition0 = await context.EvaluateValueAsync<bool>("Local.RestartCount > 2");
|
||||
if (condition0)
|
||||
{
|
||||
return "conditionItem_EXAlhZ";
|
||||
@@ -748,7 +748,7 @@ public static class TestWorkflowProvider
|
||||
"""
|
||||
);
|
||||
AgentRunResponse response = new([new ChatMessage(ChatRole.Assistant, activityText)]);
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response)).ConfigureAwait(false);
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response));
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -769,7 +769,7 @@ public static class TestWorkflowProvider
|
||||
"""
|
||||
);
|
||||
AgentRunResponse response = new([new ChatMessage(ChatRole.Assistant, activityText)]);
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response)).ConfigureAwait(false);
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response));
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -783,14 +783,14 @@ public static class TestWorkflowProvider
|
||||
// <inheritdoc />
|
||||
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
string? agentName = await context.ReadStateAsync<string>(key: "FOUNDRY_AGENT_RESEARCHANALYST", scopeName: "Env").ConfigureAwait(false);
|
||||
string? agentName = await context.ReadStateAsync<string>(key: "FOUNDRY_AGENT_RESEARCHANALYST", scopeName: "Env");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(agentName))
|
||||
{
|
||||
throw new InvalidOperationException($"Agent name must be defined: {this.Id}");
|
||||
}
|
||||
|
||||
string? conversationId = await context.ReadStateAsync<string>(key: "InternalConversationId", scopeName: "Local").ConfigureAwait(false);
|
||||
string? conversationId = await context.ReadStateAsync<string>(key: "InternalConversationId", scopeName: "Local");
|
||||
bool autoSend = true;
|
||||
string additionalInstructions =
|
||||
await context.FormatTemplateAsync(
|
||||
@@ -810,7 +810,7 @@ public static class TestWorkflowProvider
|
||||
"As a reminder, we are working to solve the following task:
|
||||
|
||||
" & Local.InputTask)
|
||||
""").ConfigureAwait(false);
|
||||
""");
|
||||
|
||||
AgentRunResponse agentResponse =
|
||||
await InvokeAgentAsync(
|
||||
@@ -820,14 +820,14 @@ public static class TestWorkflowProvider
|
||||
autoSend,
|
||||
additionalInstructions,
|
||||
inputMessages,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
cancellationToken);
|
||||
|
||||
if (autoSend)
|
||||
{
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, agentResponse)).ConfigureAwait(false);
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, agentResponse));
|
||||
}
|
||||
|
||||
await context.QueueStateUpdateAsync(key: "TaskFacts", value: agentResponse.Messages, scopeName: "Local").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync(key: "TaskFacts", value: agentResponse.Messages, scopeName: "Local");
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -848,7 +848,7 @@ public static class TestWorkflowProvider
|
||||
"""
|
||||
);
|
||||
AgentRunResponse response = new([new ChatMessage(ChatRole.Assistant, activityText)]);
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response)).ConfigureAwait(false);
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response));
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -862,14 +862,14 @@ public static class TestWorkflowProvider
|
||||
// <inheritdoc />
|
||||
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
string? agentName = await context.ReadStateAsync<string>(key: "FOUNDRY_AGENT_RESEARCHMANAGER", scopeName: "Env").ConfigureAwait(false);
|
||||
string? agentName = await context.ReadStateAsync<string>(key: "FOUNDRY_AGENT_RESEARCHMANAGER", scopeName: "Env");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(agentName))
|
||||
{
|
||||
throw new InvalidOperationException($"Agent name must be defined: {this.Id}");
|
||||
}
|
||||
|
||||
string? conversationId = await context.ReadStateAsync<string>(key: "InternalConversationId", scopeName: "Local").ConfigureAwait(false);
|
||||
string? conversationId = await context.ReadStateAsync<string>(key: "InternalConversationId", scopeName: "Local");
|
||||
bool autoSend = true;
|
||||
string additionalInstructions =
|
||||
await context.FormatTemplateAsync(
|
||||
@@ -891,14 +891,14 @@ public static class TestWorkflowProvider
|
||||
autoSend,
|
||||
additionalInstructions,
|
||||
inputMessages,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
cancellationToken);
|
||||
|
||||
if (autoSend)
|
||||
{
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, agentResponse)).ConfigureAwait(false);
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, agentResponse));
|
||||
}
|
||||
|
||||
await context.QueueStateUpdateAsync(key: "Plan", value: agentResponse.Messages, scopeName: "Local").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync(key: "Plan", value: agentResponse.Messages, scopeName: "Local");
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -935,8 +935,8 @@ public static class TestWorkflowProvider
|
||||
Here is the plan to follow as best as possible:
|
||||
|
||||
" & Local.Plan.Text
|
||||
""").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync(key: "TaskInstructions", value: evaluatedValue, scopeName: "Local").ConfigureAwait(false);
|
||||
""");
|
||||
await context.QueueStateUpdateAsync(key: "TaskInstructions", value: evaluatedValue, scopeName: "Local");
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -951,7 +951,7 @@ public static class TestWorkflowProvider
|
||||
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
object? evaluatedValue = 0;
|
||||
await context.QueueStateUpdateAsync(key: "StallCount", value: evaluatedValue, scopeName: "Local").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync(key: "StallCount", value: evaluatedValue, scopeName: "Local");
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -965,8 +965,8 @@ public static class TestWorkflowProvider
|
||||
// <inheritdoc />
|
||||
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
object? evaluatedValue = await context.EvaluateValueAsync<object>("Local.RestartCount + 1").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync(key: "RestartCount", value: evaluatedValue, scopeName: "Local").ConfigureAwait(false);
|
||||
object? evaluatedValue = await context.EvaluateValueAsync<object>("Local.RestartCount + 1");
|
||||
await context.QueueStateUpdateAsync(key: "RestartCount", value: evaluatedValue, scopeName: "Local");
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -989,7 +989,7 @@ public static class TestWorkflowProvider
|
||||
"""
|
||||
);
|
||||
AgentRunResponse response = new([new ChatMessage(ChatRole.Assistant, activityText)]);
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response)).ConfigureAwait(false);
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response));
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -1004,7 +1004,7 @@ public static class TestWorkflowProvider
|
||||
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
object? evaluatedValue = 0;
|
||||
await context.QueueStateUpdateAsync(key: "StallCount", value: evaluatedValue, scopeName: "Local").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync(key: "StallCount", value: evaluatedValue, scopeName: "Local");
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -1018,8 +1018,8 @@ public static class TestWorkflowProvider
|
||||
// <inheritdoc />
|
||||
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
object? evaluatedValue = await context.EvaluateValueAsync<object>("Search(Local.AvailableAgents, Local.TypedProgressLedger.next_speaker.answer, name)").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync(key: "NextSpeaker", value: evaluatedValue, scopeName: "Local").ConfigureAwait(false);
|
||||
object? evaluatedValue = await context.EvaluateValueAsync<object>("Search(Local.AvailableAgents, Local.TypedProgressLedger.next_speaker.answer, name)");
|
||||
await context.QueueStateUpdateAsync(key: "NextSpeaker", value: evaluatedValue, scopeName: "Local");
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -1033,7 +1033,7 @@ public static class TestWorkflowProvider
|
||||
// <inheritdoc />
|
||||
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
bool condition0 = await context.EvaluateValueAsync<bool>("CountRows(Local.NextSpeaker) = 1").ConfigureAwait(false);
|
||||
bool condition0 = await context.EvaluateValueAsync<bool>("CountRows(Local.NextSpeaker) = 1");
|
||||
if (condition0)
|
||||
{
|
||||
return "conditionItem_GmigcU";
|
||||
@@ -1051,21 +1051,21 @@ public static class TestWorkflowProvider
|
||||
// <inheritdoc />
|
||||
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
string? agentName = await context.EvaluateValueAsync<string>("First(Local.NextSpeaker).agentid").ConfigureAwait(false);
|
||||
string? agentName = await context.EvaluateValueAsync<string>("First(Local.NextSpeaker).agentid");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(agentName))
|
||||
{
|
||||
throw new InvalidOperationException($"Agent name must be defined: {this.Id}");
|
||||
}
|
||||
|
||||
string? conversationId = await context.ReadStateAsync<string>(key: "ConversationId", scopeName: "System").ConfigureAwait(false);
|
||||
string? conversationId = await context.ReadStateAsync<string>(key: "ConversationId", scopeName: "System");
|
||||
bool autoSend = true;
|
||||
string additionalInstructions =
|
||||
await context.FormatTemplateAsync(
|
||||
"""
|
||||
{Local.TypedProgressLedger.instruction_or_question.answer}
|
||||
""");
|
||||
IList<ChatMessage>? inputMessages = await context.ReadListAsync<ChatMessage>(key: "SeedTask", scopeName: "Local").ConfigureAwait(false);
|
||||
IList<ChatMessage>? inputMessages = await context.ReadListAsync<ChatMessage>(key: "SeedTask", scopeName: "Local");
|
||||
|
||||
AgentRunResponse agentResponse =
|
||||
await InvokeAgentAsync(
|
||||
@@ -1075,14 +1075,14 @@ public static class TestWorkflowProvider
|
||||
autoSend,
|
||||
additionalInstructions,
|
||||
inputMessages,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
cancellationToken);
|
||||
|
||||
if (autoSend)
|
||||
{
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, agentResponse)).ConfigureAwait(false);
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, agentResponse));
|
||||
}
|
||||
|
||||
await context.QueueStateUpdateAsync(key: "AgentResponse", value: agentResponse.Messages, scopeName: "Local").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync(key: "AgentResponse", value: agentResponse.Messages, scopeName: "Local");
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -1096,8 +1096,8 @@ public static class TestWorkflowProvider
|
||||
// <inheritdoc />
|
||||
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
object? evaluatedValue = await context.EvaluateValueAsync<object>("Last(Local.AgentResponse).Text").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync(key: "AgentResponseText", value: evaluatedValue, scopeName: "Local").ConfigureAwait(false);
|
||||
object? evaluatedValue = await context.EvaluateValueAsync<object>("Last(Local.AgentResponse).Text");
|
||||
await context.QueueStateUpdateAsync(key: "AgentResponseText", value: evaluatedValue, scopeName: "Local");
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -1111,7 +1111,7 @@ public static class TestWorkflowProvider
|
||||
{
|
||||
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
await context.QueueStateUpdateAsync(key: "SeedTask", value: UnassignedValue.Instance, scopeName: "Local").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync(key: "SeedTask", value: UnassignedValue.Instance, scopeName: "Local");
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -1132,7 +1132,7 @@ public static class TestWorkflowProvider
|
||||
"""
|
||||
);
|
||||
AgentRunResponse response = new([new ChatMessage(ChatRole.Assistant, activityText)]);
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response)).ConfigureAwait(false);
|
||||
await context.AddEventAsync(new AgentRunResponseEvent(this.Id, response));
|
||||
|
||||
return default;
|
||||
}
|
||||
@@ -1146,8 +1146,8 @@ public static class TestWorkflowProvider
|
||||
// <inheritdoc />
|
||||
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
object? evaluatedValue = await context.EvaluateValueAsync<object>("Local.StallCount + 1").ConfigureAwait(false);
|
||||
await context.QueueStateUpdateAsync(key: "StallCount", value: evaluatedValue, scopeName: "Local").ConfigureAwait(false);
|
||||
object? evaluatedValue = await context.EvaluateValueAsync<object>("Local.StallCount + 1");
|
||||
await context.QueueStateUpdateAsync(key: "StallCount", value: evaluatedValue, scopeName: "Local");
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
@@ -76,102 +76,103 @@ internal sealed class Program
|
||||
|
||||
string? messageId = null;
|
||||
|
||||
await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
|
||||
await foreach (WorkflowEvent workflowEvent in run.WatchStreamAsync())
|
||||
{
|
||||
if (evt is ExecutorInvokedEvent executorInvoked)
|
||||
switch (workflowEvent)
|
||||
{
|
||||
Debug.WriteLine($"STEP ENTER #{executorInvoked.ExecutorId}");
|
||||
}
|
||||
else if (evt is ExecutorCompletedEvent executorComplete)
|
||||
{
|
||||
Debug.WriteLine($"STEP EXIT #{executorComplete.ExecutorId}");
|
||||
}
|
||||
else if (evt is ExecutorFailedEvent executorFailure)
|
||||
{
|
||||
Debug.WriteLine($"STEP ERROR #{executorFailure.ExecutorId}: {executorFailure.Data?.Message ?? "Unknown"}");
|
||||
}
|
||||
else if (evt is WorkflowErrorEvent workflowError)
|
||||
{
|
||||
Debug.WriteLine("WORKFLOW ERROR");
|
||||
}
|
||||
else if (evt is ConversationUpdateEvent invokeEvent)
|
||||
{
|
||||
Debug.WriteLine($"CONVERSATION: {invokeEvent.Data}");
|
||||
}
|
||||
else if (evt is AgentRunUpdateEvent streamEvent)
|
||||
{
|
||||
if (!string.Equals(messageId, streamEvent.Update.MessageId, StringComparison.Ordinal))
|
||||
{
|
||||
messageId = streamEvent.Update.MessageId;
|
||||
case ExecutorInvokedEvent executorInvoked:
|
||||
Debug.WriteLine($"STEP ENTER #{executorInvoked.ExecutorId}");
|
||||
break;
|
||||
|
||||
if (messageId is not null)
|
||||
case ExecutorCompletedEvent executorComplete:
|
||||
Debug.WriteLine($"STEP EXIT #{executorComplete.ExecutorId}");
|
||||
break;
|
||||
|
||||
case ExecutorFailedEvent executorFailure:
|
||||
Debug.WriteLine($"STEP ERROR #{executorFailure.ExecutorId}: {executorFailure.Data?.Message ?? "Unknown"}");
|
||||
break;
|
||||
|
||||
case WorkflowErrorEvent workflowError:
|
||||
throw workflowError.Data as Exception ?? new InvalidOperationException("Unexpected failure...");
|
||||
|
||||
case ConversationUpdateEvent invokeEvent:
|
||||
Debug.WriteLine($"CONVERSATION: {invokeEvent.Data}");
|
||||
break;
|
||||
|
||||
case AgentRunUpdateEvent streamEvent:
|
||||
if (!string.Equals(messageId, streamEvent.Update.MessageId, StringComparison.Ordinal))
|
||||
{
|
||||
string? agentId = streamEvent.Update.AuthorName;
|
||||
if (agentId is not null)
|
||||
messageId = streamEvent.Update.MessageId;
|
||||
|
||||
if (messageId is not null)
|
||||
{
|
||||
if (!s_nameCache.TryGetValue(agentId, out string? realName))
|
||||
string? agentId = streamEvent.Update.AuthorName;
|
||||
if (agentId is not null)
|
||||
{
|
||||
PersistentAgent agent = await this.FoundryClient.Administration.GetAgentAsync(agentId);
|
||||
s_nameCache[agentId] = agent.Name;
|
||||
realName = agent.Name;
|
||||
if (!s_nameCache.TryGetValue(agentId, out string? realName))
|
||||
{
|
||||
PersistentAgent agent = await this.FoundryClient.Administration.GetAgentAsync(agentId);
|
||||
s_nameCache[agentId] = agent.Name;
|
||||
realName = agent.Name;
|
||||
}
|
||||
agentId = realName;
|
||||
}
|
||||
agentId = realName;
|
||||
}
|
||||
agentId ??= nameof(ChatRole.Assistant);
|
||||
Console.ForegroundColor = ConsoleColor.Cyan;
|
||||
Console.Write($"\n{agentId.ToUpperInvariant()}:");
|
||||
Console.ForegroundColor = ConsoleColor.DarkGray;
|
||||
Console.WriteLine($" [{messageId}]");
|
||||
}
|
||||
}
|
||||
|
||||
ChatResponseUpdate? chatUpdate = streamEvent.Update.RawRepresentation as ChatResponseUpdate;
|
||||
switch (chatUpdate?.RawRepresentation)
|
||||
{
|
||||
case MessageContentUpdate messageUpdate:
|
||||
string? fileId = messageUpdate.ImageFileId ?? messageUpdate.TextAnnotation?.OutputFileId;
|
||||
if (fileId is not null && s_fileCache.Add(fileId))
|
||||
{
|
||||
BinaryData content = await this.FoundryClient.Files.GetFileContentAsync(fileId);
|
||||
await DownloadFileContentAsync(Path.GetFileName(messageUpdate.TextAnnotation?.TextToReplace ?? "response.png"), content);
|
||||
}
|
||||
break;
|
||||
}
|
||||
try
|
||||
{
|
||||
Console.ResetColor();
|
||||
Console.Write(streamEvent.Data);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.ResetColor();
|
||||
}
|
||||
}
|
||||
else if (evt is AgentRunResponseEvent messageEvent)
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.WriteLine();
|
||||
if (messageEvent.Response.AgentId is null)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Cyan;
|
||||
Console.WriteLine("ACTIVITY:");
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine(messageEvent.Response?.Text.Trim());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (messageEvent.Response.Usage is not null)
|
||||
{
|
||||
agentId ??= nameof(ChatRole.Assistant);
|
||||
Console.ForegroundColor = ConsoleColor.Cyan;
|
||||
Console.Write($"\n{agentId.ToUpperInvariant()}:");
|
||||
Console.ForegroundColor = ConsoleColor.DarkGray;
|
||||
Console.WriteLine($"[Tokens Total: {messageEvent.Response.Usage.TotalTokenCount}, Input: {messageEvent.Response.Usage.InputTokenCount}, Output: {messageEvent.Response.Usage.OutputTokenCount}]");
|
||||
Console.WriteLine($" [{messageId}]");
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.ResetColor();
|
||||
}
|
||||
|
||||
ChatResponseUpdate? chatUpdate = streamEvent.Update.RawRepresentation as ChatResponseUpdate;
|
||||
switch (chatUpdate?.RawRepresentation)
|
||||
{
|
||||
case MessageContentUpdate messageUpdate:
|
||||
string? fileId = messageUpdate.ImageFileId ?? messageUpdate.TextAnnotation?.OutputFileId;
|
||||
if (fileId is not null && s_fileCache.Add(fileId))
|
||||
{
|
||||
BinaryData content = await this.FoundryClient.Files.GetFileContentAsync(fileId);
|
||||
await DownloadFileContentAsync(Path.GetFileName(messageUpdate.TextAnnotation?.TextToReplace ?? "response.png"), content);
|
||||
}
|
||||
break;
|
||||
}
|
||||
try
|
||||
{
|
||||
Console.ResetColor();
|
||||
Console.Write(streamEvent.Data);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.ResetColor();
|
||||
}
|
||||
break;
|
||||
|
||||
case AgentRunResponseEvent messageEvent:
|
||||
try
|
||||
{
|
||||
Console.WriteLine();
|
||||
if (messageEvent.Response.AgentId is null)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Cyan;
|
||||
Console.WriteLine("ACTIVITY:");
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine(messageEvent.Response?.Text.Trim());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (messageEvent.Response.Usage is not null)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.DarkGray;
|
||||
Console.WriteLine($"[Tokens Total: {messageEvent.Response.Usage.TotalTokenCount}, Input: {messageEvent.Response.Usage.InputTokenCount}, Output: {messageEvent.Response.Usage.OutputTokenCount}]");
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.ResetColor();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// Uncomment this to enable JSON checkpointing to the local file system.
|
||||
//#define CHECKPOINT_JSON
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using Azure.AI.Agents.Persistent;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI.Workflows;
|
||||
#if CHECKPOINT_JSON
|
||||
using Microsoft.Agents.AI.Workflows.Checkpointing;
|
||||
#endif
|
||||
using Microsoft.Agents.AI.Workflows.Declarative;
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.Events;
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.Kit;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
@@ -57,15 +65,23 @@ internal sealed class Program
|
||||
// Run the workflow, just like any other workflow
|
||||
string input = this.GetWorkflowInput();
|
||||
|
||||
#if CHECKPOINT_JSON
|
||||
// Use a file-system based JSON checkpoint store to persist checkpoints to disk.
|
||||
DirectoryInfo checkpointFolder = Directory.CreateDirectory(Path.Combine(".", $"chk-{DateTime.Now:yyMMdd-hhmmss-ff}"));
|
||||
CheckpointManager checkpointManager = CheckpointManager.CreateJson(new FileSystemJsonCheckpointStore(checkpointFolder));
|
||||
#else
|
||||
// Use an in-memory checkpoint store that will not persist checkpoints beyond the lifetime of the process.
|
||||
CheckpointManager checkpointManager = CheckpointManager.CreateInMemory();
|
||||
#endif
|
||||
|
||||
Checkpointed<StreamingRun> run = await InProcessExecution.StreamAsync(workflow, input, checkpointManager);
|
||||
|
||||
bool isComplete = false;
|
||||
InputResponse? response = null;
|
||||
object? response = null;
|
||||
do
|
||||
{
|
||||
ExternalRequest? inputRequest = await this.MonitorAndDisposeWorkflowRunAsync(run, response);
|
||||
if (inputRequest is not null)
|
||||
ExternalRequest? externalRequest = await this.MonitorAndDisposeWorkflowRunAsync(run, response);
|
||||
if (externalRequest is not null)
|
||||
{
|
||||
Notify("\nWORKFLOW: Yield");
|
||||
|
||||
@@ -75,7 +91,7 @@ internal sealed class Program
|
||||
}
|
||||
|
||||
// Process the external request.
|
||||
response = HandleExternalRequest(inputRequest);
|
||||
response = await this.HandleExternalRequestAsync(externalRequest);
|
||||
|
||||
// Let's resume on an entirely new workflow instance to demonstrate checkpoint portability.
|
||||
workflow = this.CreateWorkflow();
|
||||
@@ -96,11 +112,25 @@ internal sealed class Program
|
||||
Notify("\nWORKFLOW: Done!\n");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create the workflow from the declarative YAML. Includes definition of the
|
||||
/// <see cref="DeclarativeWorkflowOptions" /> and the associated <see cref="WorkflowAgentProvider"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value assigned to <see cref="IncludeFunctions" /> controls on whether the function
|
||||
/// tools (<see cref="AIFunction"/>) initialized in the constructor are included for auto-invocation.
|
||||
/// </remarks>
|
||||
private Workflow CreateWorkflow()
|
||||
{
|
||||
// Use DeclarativeWorkflowBuilder to build a workflow based on a YAML file.
|
||||
AzureAgentProvider agentProvider = new(this.FoundryEndpoint, new AzureCliCredential())
|
||||
{
|
||||
// Functions included here will be auto-executed by the framework.
|
||||
Functions = IncludeFunctions ? this.FunctionMap.Values : null,
|
||||
};
|
||||
|
||||
DeclarativeWorkflowOptions options =
|
||||
new(new AzureAgentProvider(this.FoundryEndpoint, new AzureCliCredential()))
|
||||
new(agentProvider)
|
||||
{
|
||||
Configuration = this.Configuration,
|
||||
//ConversationId = null, // Assign to continue a conversation
|
||||
@@ -110,8 +140,18 @@ internal sealed class Program
|
||||
return DeclarativeWorkflowBuilder.Build<string>(this.WorkflowFile, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configuration key used to identify the Foundry project endpoint.
|
||||
/// </summary>
|
||||
private const string ConfigKeyFoundryEndpoint = "FOUNDRY_PROJECT_ENDPOINT";
|
||||
|
||||
/// <summary>
|
||||
/// Controls on whether the function tools (<see cref="AIFunction"/>) initialized
|
||||
/// in the constructor are included for auto-invocation.
|
||||
/// NOTE: By default, no functions exist as part of this sample.
|
||||
/// </summary>
|
||||
private const bool IncludeFunctions = true;
|
||||
|
||||
private static Dictionary<string, string> NameCache { get; } = [];
|
||||
private static HashSet<string> FileCache { get; } = [];
|
||||
|
||||
@@ -121,6 +161,7 @@ internal sealed class Program
|
||||
private PersistentAgentsClient FoundryClient { get; }
|
||||
private IConfiguration Configuration { get; }
|
||||
private CheckpointInfo? LastCheckpoint { get; set; }
|
||||
private Dictionary<string, AIFunction> FunctionMap { get; }
|
||||
|
||||
private Program(string workflowFile, string? workflowInput)
|
||||
{
|
||||
@@ -131,15 +172,24 @@ internal sealed class Program
|
||||
|
||||
this.FoundryEndpoint = this.Configuration[ConfigKeyFoundryEndpoint] ?? throw new InvalidOperationException($"Undefined configuration setting: {ConfigKeyFoundryEndpoint}");
|
||||
this.FoundryClient = new PersistentAgentsClient(this.FoundryEndpoint, new AzureCliCredential());
|
||||
|
||||
List<AIFunction> functions =
|
||||
[
|
||||
// Manually define any custom functions that may be required by agents within the workflow.
|
||||
// By default, this sample does not include any functions.
|
||||
//AIFunctionFactory.Create(),
|
||||
];
|
||||
this.FunctionMap = functions.ToDictionary(f => f.Name);
|
||||
}
|
||||
|
||||
private async Task<ExternalRequest?> MonitorAndDisposeWorkflowRunAsync(Checkpointed<StreamingRun> run, InputResponse? response = null)
|
||||
private async Task<ExternalRequest?> MonitorAndDisposeWorkflowRunAsync(Checkpointed<StreamingRun> run, object? response = null)
|
||||
{
|
||||
await using IAsyncDisposable disposeRun = run;
|
||||
|
||||
bool hasStreamed = false;
|
||||
string? messageId = null;
|
||||
|
||||
await foreach (WorkflowEvent workflowEvent in run.Run.WatchStreamAsync().ConfigureAwait(false))
|
||||
await foreach (WorkflowEvent workflowEvent in run.Run.WatchStreamAsync())
|
||||
{
|
||||
switch (workflowEvent)
|
||||
{
|
||||
@@ -163,6 +213,9 @@ internal sealed class Program
|
||||
Debug.WriteLine($"STEP ERROR #{executorFailure.ExecutorId}: {executorFailure.Data?.Message ?? "Unknown"}");
|
||||
break;
|
||||
|
||||
case WorkflowErrorEvent workflowError:
|
||||
throw workflowError.Data as Exception ?? new InvalidOperationException("Unexpected failure...");
|
||||
|
||||
case SuperStepCompletedEvent checkpointCompleted:
|
||||
this.LastCheckpoint = checkpointCompleted.CompletionInfo?.Checkpoint;
|
||||
Debug.WriteLine($"CHECKPOINT x{checkpointCompleted.StepNumber} [{this.LastCheckpoint?.CheckpointId ?? "(none)"}]");
|
||||
@@ -173,12 +226,12 @@ internal sealed class Program
|
||||
if (response is not null)
|
||||
{
|
||||
ExternalResponse requestResponse = requestInfo.Request.CreateResponse(response);
|
||||
await run.Run.SendResponseAsync(requestResponse).ConfigureAwait(false);
|
||||
await run.Run.SendResponseAsync(requestResponse);
|
||||
response = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
await run.Run.DisposeAsync().ConfigureAwait(false);
|
||||
await run.Run.DisposeAsync();
|
||||
return requestInfo.Request;
|
||||
}
|
||||
break;
|
||||
@@ -197,11 +250,12 @@ internal sealed class Program
|
||||
case AgentRunUpdateEvent streamEvent:
|
||||
if (!string.Equals(messageId, streamEvent.Update.MessageId, StringComparison.Ordinal))
|
||||
{
|
||||
hasStreamed = false;
|
||||
messageId = streamEvent.Update.MessageId;
|
||||
|
||||
if (messageId is not null)
|
||||
{
|
||||
string? agentId = streamEvent.Update.AuthorName;
|
||||
string? agentId = streamEvent.Update.AgentId;
|
||||
if (agentId is not null)
|
||||
{
|
||||
if (!NameCache.TryGetValue(agentId, out string? realName))
|
||||
@@ -231,11 +285,18 @@ internal sealed class Program
|
||||
await DownloadFileContentAsync(Path.GetFileName(messageUpdate.TextAnnotation?.TextToReplace ?? "response.png"), content);
|
||||
}
|
||||
break;
|
||||
case RequiredActionUpdate actionUpdate:
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
Console.Write($"Calling tool: {actionUpdate.FunctionName}");
|
||||
Console.ForegroundColor = ConsoleColor.DarkGray;
|
||||
Console.WriteLine($" [{actionUpdate.ToolCallId}]");
|
||||
break;
|
||||
}
|
||||
try
|
||||
{
|
||||
Console.ResetColor();
|
||||
Console.Write(streamEvent.Data);
|
||||
Console.Write(streamEvent.Update.Text);
|
||||
hasStreamed |= !string.IsNullOrEmpty(streamEvent.Update.Text);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -246,7 +307,11 @@ internal sealed class Program
|
||||
case AgentRunResponseEvent messageEvent:
|
||||
try
|
||||
{
|
||||
Console.WriteLine();
|
||||
if (hasStreamed)
|
||||
{
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
if (messageEvent.Response.Usage is not null)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.DarkGray;
|
||||
@@ -263,14 +328,31 @@ internal sealed class Program
|
||||
|
||||
return default;
|
||||
}
|
||||
private static InputResponse HandleExternalRequest(ExternalRequest request)
|
||||
|
||||
/// <summary>
|
||||
/// Handle request for external input, either from a human or a function tool invocation.
|
||||
/// </summary>
|
||||
private async ValueTask<object> HandleExternalRequestAsync(ExternalRequest request) =>
|
||||
request.Data.TypeId.TypeName switch
|
||||
{
|
||||
// Request for human input
|
||||
_ when request.Data.TypeId.IsMatch<InputRequest>() => HandleInputRequest(request.DataAs<InputRequest>()!),
|
||||
// Request for function tool invocation. (Only active when functions are defined and IncludeFunctions is true.)
|
||||
_ when request.Data.TypeId.IsMatch<AgentToolRequest>() => await this.HandleToolRequestAsync(request.DataAs<AgentToolRequest>()!),
|
||||
// Unknown request type.
|
||||
_ => throw new InvalidOperationException($"Unsupported external request type: {request.GetType().Name}."),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Handle request for human input.
|
||||
/// </summary>
|
||||
private static InputResponse HandleInputRequest(InputRequest request)
|
||||
{
|
||||
InputRequest? message = request.Data.As<InputRequest>();
|
||||
string? userInput;
|
||||
do
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.DarkGreen;
|
||||
Console.Write($"\n{message?.Prompt ?? "INPUT:"} ");
|
||||
Console.Write($"\n{request.Prompt ?? "INPUT:"} ");
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
userInput = Console.ReadLine();
|
||||
}
|
||||
@@ -279,6 +361,30 @@ internal sealed class Program
|
||||
return new InputResponse(userInput);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle a function tool request by invoking the specified tools and returning the results.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This handler is only active when <see cref="IncludeFunctions"/> is set to true and
|
||||
/// one or more <see cref="AIFunction"/> instances are defined in the constructor.
|
||||
/// </remarks>
|
||||
private async ValueTask<AgentToolResponse> HandleToolRequestAsync(AgentToolRequest request)
|
||||
{
|
||||
Task<FunctionResultContent>[] functionTasks = request.FunctionCalls.Select(functionCall => InvokesToolAsync(functionCall)).ToArray();
|
||||
|
||||
await Task.WhenAll(functionTasks);
|
||||
|
||||
return AgentToolResponse.Create(request, functionTasks.Select(task => task.Result));
|
||||
|
||||
async Task<FunctionResultContent> InvokesToolAsync(FunctionCallContent functionCall)
|
||||
{
|
||||
AIFunction functionTool = this.FunctionMap[functionCall.Name];
|
||||
AIFunctionArguments? functionArguments = functionCall.Arguments is null ? null : new(functionCall.Arguments.NormalizePortableValues());
|
||||
object? result = await functionTool.InvokeAsync(functionArguments);
|
||||
return new FunctionResultContent(functionCall.CallId, JsonSerializer.Serialize(result));
|
||||
}
|
||||
}
|
||||
|
||||
private static string? ParseWorkflowFile(string[] args)
|
||||
{
|
||||
string? workflowFile = args.FirstOrDefault();
|
||||
|
||||
+4
-4
@@ -24,18 +24,18 @@ public static class Program
|
||||
private static async Task Main()
|
||||
{
|
||||
// Create the workflow
|
||||
var workflow = await WorkflowHelper.GetWorkflowAsync().ConfigureAwait(false);
|
||||
var workflow = await WorkflowHelper.GetWorkflowAsync();
|
||||
|
||||
// Execute the workflow
|
||||
await using StreamingRun handle = await InProcessExecution.StreamAsync(workflow, NumberSignal.Init).ConfigureAwait(false);
|
||||
await foreach (WorkflowEvent evt in handle.WatchStreamAsync().ConfigureAwait(false))
|
||||
await using StreamingRun handle = await InProcessExecution.StreamAsync(workflow, NumberSignal.Init);
|
||||
await foreach (WorkflowEvent evt in handle.WatchStreamAsync())
|
||||
{
|
||||
switch (evt)
|
||||
{
|
||||
case RequestInfoEvent requestInputEvt:
|
||||
// Handle `RequestInfoEvent` from the workflow
|
||||
ExternalResponse response = HandleExternalRequest(requestInputEvt.Request);
|
||||
await handle.SendResponseAsync(response).ConfigureAwait(false);
|
||||
await handle.SendResponseAsync(response);
|
||||
break;
|
||||
|
||||
case WorkflowOutputEvent outputEvt:
|
||||
|
||||
+5
-7
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Agents.AI.Workflows;
|
||||
using Microsoft.Agents.AI.Workflows.Reflection;
|
||||
|
||||
namespace WorkflowHumanInTheLoopBasicSample;
|
||||
|
||||
@@ -39,7 +38,7 @@ internal enum NumberSignal
|
||||
/// <summary>
|
||||
/// Executor that judges the guess and provides feedback.
|
||||
/// </summary>
|
||||
internal sealed class JudgeExecutor() : ReflectingExecutor<JudgeExecutor>("Judge"), IMessageHandler<int>
|
||||
internal sealed class JudgeExecutor() : Executor<int>("Judge")
|
||||
{
|
||||
private readonly int _targetNumber;
|
||||
private int _tries;
|
||||
@@ -53,21 +52,20 @@ internal sealed class JudgeExecutor() : ReflectingExecutor<JudgeExecutor>("Judge
|
||||
this._targetNumber = targetNumber;
|
||||
}
|
||||
|
||||
public async ValueTask HandleAsync(int message, IWorkflowContext context)
|
||||
public override async ValueTask HandleAsync(int message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
this._tries++;
|
||||
if (message == this._targetNumber)
|
||||
{
|
||||
await context.YieldOutputAsync($"{this._targetNumber} found in {this._tries} tries!")
|
||||
.ConfigureAwait(false);
|
||||
await context.YieldOutputAsync($"{this._targetNumber} found in {this._tries} tries!", cancellationToken);
|
||||
}
|
||||
else if (message < this._targetNumber)
|
||||
{
|
||||
await context.SendMessageAsync(NumberSignal.Below).ConfigureAwait(false);
|
||||
await context.SendMessageAsync(NumberSignal.Below, cancellationToken: cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
await context.SendMessageAsync(NumberSignal.Above).ConfigureAwait(false);
|
||||
await context.SendMessageAsync(NumberSignal.Above, cancellationToken: cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Agents.AI.Workflows;
|
||||
using Microsoft.Agents.AI.Workflows.Reflection;
|
||||
|
||||
namespace WorkflowLoopSample;
|
||||
|
||||
@@ -33,8 +32,8 @@ public static class Program
|
||||
.BuildAsync<NumberSignal>();
|
||||
|
||||
// Execute the workflow
|
||||
await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, NumberSignal.Init).ConfigureAwait(false);
|
||||
await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
|
||||
await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, NumberSignal.Init);
|
||||
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
|
||||
{
|
||||
if (evt is WorkflowOutputEvent outputEvent)
|
||||
{
|
||||
@@ -57,7 +56,7 @@ internal enum NumberSignal
|
||||
/// <summary>
|
||||
/// Executor that makes a guess based on the current bounds.
|
||||
/// </summary>
|
||||
internal sealed class GuessNumberExecutor : ReflectingExecutor<GuessNumberExecutor>, IMessageHandler<NumberSignal>
|
||||
internal sealed class GuessNumberExecutor : Executor<NumberSignal>
|
||||
{
|
||||
/// <summary>
|
||||
/// The lower bound of the guessing range.
|
||||
@@ -83,20 +82,20 @@ internal sealed class GuessNumberExecutor : ReflectingExecutor<GuessNumberExecut
|
||||
|
||||
private int NextGuess => (this.LowerBound + this.UpperBound) / 2;
|
||||
|
||||
public async ValueTask HandleAsync(NumberSignal message, IWorkflowContext context)
|
||||
public override async ValueTask HandleAsync(NumberSignal message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
switch (message)
|
||||
{
|
||||
case NumberSignal.Init:
|
||||
await context.SendMessageAsync(this.NextGuess).ConfigureAwait(false);
|
||||
await context.SendMessageAsync(this.NextGuess, cancellationToken: cancellationToken);
|
||||
break;
|
||||
case NumberSignal.Above:
|
||||
this.UpperBound = this.NextGuess - 1;
|
||||
await context.SendMessageAsync(this.NextGuess).ConfigureAwait(false);
|
||||
await context.SendMessageAsync(this.NextGuess, cancellationToken: cancellationToken);
|
||||
break;
|
||||
case NumberSignal.Below:
|
||||
this.LowerBound = this.NextGuess + 1;
|
||||
await context.SendMessageAsync(this.NextGuess).ConfigureAwait(false);
|
||||
await context.SendMessageAsync(this.NextGuess, cancellationToken: cancellationToken);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -105,7 +104,7 @@ internal sealed class GuessNumberExecutor : ReflectingExecutor<GuessNumberExecut
|
||||
/// <summary>
|
||||
/// Executor that judges the guess and provides feedback.
|
||||
/// </summary>
|
||||
internal sealed class JudgeExecutor : ReflectingExecutor<JudgeExecutor>, IMessageHandler<int>
|
||||
internal sealed class JudgeExecutor : Executor<int>
|
||||
{
|
||||
private readonly int _targetNumber;
|
||||
private int _tries;
|
||||
@@ -120,21 +119,21 @@ internal sealed class JudgeExecutor : ReflectingExecutor<JudgeExecutor>, IMessag
|
||||
this._targetNumber = targetNumber;
|
||||
}
|
||||
|
||||
public async ValueTask HandleAsync(int message, IWorkflowContext context)
|
||||
public override async ValueTask HandleAsync(int message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
this._tries++;
|
||||
if (message == this._targetNumber)
|
||||
{
|
||||
await context.YieldOutputAsync($"{this._targetNumber} found in {this._tries} tries!")
|
||||
.ConfigureAwait(false);
|
||||
await context.YieldOutputAsync($"{this._targetNumber} found in {this._tries} tries!", cancellationToken)
|
||||
;
|
||||
}
|
||||
else if (message < this._targetNumber)
|
||||
{
|
||||
await context.SendMessageAsync(NumberSignal.Below).ConfigureAwait(false);
|
||||
await context.SendMessageAsync(NumberSignal.Below, cancellationToken: cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
await context.SendMessageAsync(NumberSignal.Above).ConfigureAwait(false);
|
||||
await context.SendMessageAsync(NumberSignal.Above, cancellationToken: cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Monitor.OpenTelemetry.Exporter" />
|
||||
<PackageReference Include="OpenTelemetry" />
|
||||
<PackageReference Include="System.Diagnostics.DiagnosticSource" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Workflows\Microsoft.Agents.AI.Workflows.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Diagnostics;
|
||||
using Azure.Monitor.OpenTelemetry.Exporter;
|
||||
using Microsoft.Agents.AI.Workflows;
|
||||
using OpenTelemetry;
|
||||
using OpenTelemetry.Resources;
|
||||
using OpenTelemetry.Trace;
|
||||
|
||||
namespace WorkflowObservabilitySample;
|
||||
|
||||
/// <summary>
|
||||
/// This sample shows how to enable observability in a workflow and send the traces
|
||||
/// to be visualized in Application Insights.
|
||||
///
|
||||
/// In this example, we create a simple text processing pipeline that:
|
||||
/// 1. Takes input text and converts it to uppercase using an UppercaseExecutor
|
||||
/// 2. Takes the uppercase text and reverses it using a ReverseTextExecutor
|
||||
///
|
||||
/// The executors are connected sequentially, so data flows from one to the next in order.
|
||||
/// For input "Hello, World!", the workflow produces "!DLROW ,OLLEH".
|
||||
/// </summary>
|
||||
public static class Program
|
||||
{
|
||||
private const string SourceName = "Workflow.ApplicationInsightsSample";
|
||||
private static readonly ActivitySource s_activitySource = new(SourceName);
|
||||
|
||||
private static async Task Main()
|
||||
{
|
||||
var applicationInsightsConnectionString = Environment.GetEnvironmentVariable("APPLICATIONINSIGHTS_CONNECTION_STRING") ?? throw new InvalidOperationException("APPLICATIONINSIGHTS_CONNECTION_STRING is not set.");
|
||||
|
||||
var resourceBuilder = ResourceBuilder
|
||||
.CreateDefault()
|
||||
.AddService("WorkflowSample");
|
||||
|
||||
using var traceProvider = Sdk.CreateTracerProviderBuilder()
|
||||
.SetResourceBuilder(resourceBuilder)
|
||||
.AddSource("Microsoft.Agents.AI.Workflows*")
|
||||
.AddSource(SourceName)
|
||||
.AddAzureMonitorTraceExporter(options => options.ConnectionString = applicationInsightsConnectionString)
|
||||
.Build();
|
||||
|
||||
// Start a root activity for the application
|
||||
using var activity = s_activitySource.StartActivity("main");
|
||||
Console.WriteLine($"Operation/Trace ID: {Activity.Current?.TraceId}");
|
||||
|
||||
// Create the executors
|
||||
UppercaseExecutor uppercase = new();
|
||||
ReverseTextExecutor reverse = new();
|
||||
|
||||
// Build the workflow by connecting executors sequentially
|
||||
var workflow = new WorkflowBuilder(uppercase)
|
||||
.AddEdge(uppercase, reverse)
|
||||
.Build();
|
||||
|
||||
// Execute the workflow with input data
|
||||
Run run = await InProcessExecution.RunAsync(workflow, "Hello, World!");
|
||||
foreach (WorkflowEvent evt in run.NewEvents)
|
||||
{
|
||||
if (evt is ExecutorCompletedEvent executorComplete)
|
||||
{
|
||||
Console.WriteLine($"{executorComplete.ExecutorId}: {executorComplete.Data}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// First executor: converts input text to uppercase.
|
||||
/// </summary>
|
||||
internal sealed class UppercaseExecutor() : Executor<string, string>("UppercaseExecutor")
|
||||
{
|
||||
/// <summary>
|
||||
/// Processes the input message by converting it to uppercase.
|
||||
/// </summary>
|
||||
/// <param name="message">The input text to convert</param>
|
||||
/// <param name="context">Workflow context for accessing workflow services and adding events</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
|
||||
/// The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The input text converted to uppercase</returns>
|
||||
public override async ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default) =>
|
||||
message.ToUpperInvariant(); // The return value will be sent as a message along an edge to subsequent executors
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Second executor: reverses the input text and completes the workflow.
|
||||
/// </summary>
|
||||
internal sealed class ReverseTextExecutor() : Executor<string, string>("ReverseTextExecutor")
|
||||
{
|
||||
/// <summary>
|
||||
/// Processes the input message by reversing the text.
|
||||
/// </summary>
|
||||
/// <param name="message">The input text to reverse</param>
|
||||
/// <param name="context">Workflow context for accessing workflow services and adding events</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
|
||||
/// The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The input text reversed</returns>
|
||||
public override async ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
=> new(message.Reverse().ToArray());
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Agents.AI.Workflows;
|
||||
using Microsoft.Agents.AI.Workflows.Reflection;
|
||||
using OpenTelemetry;
|
||||
using OpenTelemetry.Logs;
|
||||
using OpenTelemetry.Metrics;
|
||||
@@ -71,28 +70,33 @@ public static class Program
|
||||
/// <summary>
|
||||
/// First executor: converts input text to uppercase.
|
||||
/// </summary>
|
||||
internal sealed class UppercaseExecutor() : ReflectingExecutor<UppercaseExecutor>("UppercaseExecutor"), IMessageHandler<string, string>
|
||||
internal sealed class UppercaseExecutor() : Executor<string, string>("UppercaseExecutor")
|
||||
{
|
||||
/// <summary>
|
||||
/// Processes the input message by converting it to uppercase.
|
||||
/// </summary>
|
||||
/// <param name="message">The input text to convert</param>
|
||||
/// <param name="context">Workflow context for accessing workflow services and adding events</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
|
||||
/// The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The input text converted to uppercase</returns>
|
||||
public async ValueTask<string> HandleAsync(string message, IWorkflowContext context) =>
|
||||
public override async ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default) =>
|
||||
message.ToUpperInvariant(); // The return value will be sent as a message along an edge to subsequent executors
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Second executor: reverses the input text and completes the workflow.
|
||||
/// </summary>
|
||||
internal sealed class ReverseTextExecutor() : ReflectingExecutor<ReverseTextExecutor>("ReverseTextExecutor"), IMessageHandler<string, string>
|
||||
internal sealed class ReverseTextExecutor() : Executor<string, string>("ReverseTextExecutor")
|
||||
{
|
||||
/// <summary>
|
||||
/// Processes the input message by reversing the text.
|
||||
/// </summary>
|
||||
/// <param name="message">The input text to reverse</param>
|
||||
/// <param name="context">Workflow context for accessing workflow services and adding events</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
|
||||
/// The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The input text reversed</returns>
|
||||
public async ValueTask<string> HandleAsync(string message, IWorkflowContext context) => new string(message.Reverse().ToArray());
|
||||
public override async ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
=> new(message.Reverse().ToArray());
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Agents.AI.Workflows;
|
||||
using Microsoft.Agents.AI.Workflows.Reflection;
|
||||
|
||||
namespace WorkflowSharedStatesSample;
|
||||
|
||||
@@ -52,15 +51,15 @@ internal static class FileContentStateConstants
|
||||
public const string FileContentStateScope = "FileContentState";
|
||||
}
|
||||
|
||||
internal sealed class FileReadExecutor() : ReflectingExecutor<FileReadExecutor>("FileReadExecutor"), IMessageHandler<string, string>
|
||||
internal sealed class FileReadExecutor() : Executor<string, string>("FileReadExecutor")
|
||||
{
|
||||
public async ValueTask<string> HandleAsync(string message, IWorkflowContext context)
|
||||
public override async ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Read file content from embedded resource
|
||||
string fileContent = Resources.Read(message);
|
||||
// Store file content in a shared state for access by other executors
|
||||
string fileID = Guid.NewGuid().ToString("N");
|
||||
await context.QueueStateUpdateAsync(fileID, fileContent, scopeName: FileContentStateConstants.FileContentStateScope);
|
||||
await context.QueueStateUpdateAsync(fileID, fileContent, scopeName: FileContentStateConstants.FileContentStateScope, cancellationToken);
|
||||
|
||||
return fileID;
|
||||
}
|
||||
@@ -72,12 +71,12 @@ internal sealed class FileStats
|
||||
public int WordCount { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class WordCountingExecutor() : ReflectingExecutor<WordCountingExecutor>("WordCountingExecutor"), IMessageHandler<string, FileStats>
|
||||
internal sealed class WordCountingExecutor() : Executor<string, FileStats>("WordCountingExecutor")
|
||||
{
|
||||
public async ValueTask<FileStats> HandleAsync(string message, IWorkflowContext context)
|
||||
public override async ValueTask<FileStats> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Retrieve the file content from the shared state
|
||||
var fileContent = await context.ReadStateAsync<string>(message, scopeName: FileContentStateConstants.FileContentStateScope)
|
||||
var fileContent = await context.ReadStateAsync<string>(message, scopeName: FileContentStateConstants.FileContentStateScope, cancellationToken)
|
||||
?? throw new InvalidOperationException("File content state not found");
|
||||
|
||||
int wordCount = fileContent.Split([' ', '\n', '\r'], StringSplitOptions.RemoveEmptyEntries).Length;
|
||||
@@ -86,12 +85,12 @@ internal sealed class WordCountingExecutor() : ReflectingExecutor<WordCountingEx
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ParagraphCountingExecutor() : ReflectingExecutor<ParagraphCountingExecutor>("ParagraphCountingExecutor"), IMessageHandler<string, FileStats>
|
||||
internal sealed class ParagraphCountingExecutor() : Executor<string, FileStats>("ParagraphCountingExecutor")
|
||||
{
|
||||
public async ValueTask<FileStats> HandleAsync(string message, IWorkflowContext context)
|
||||
public override async ValueTask<FileStats> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Retrieve the file content from the shared state
|
||||
var fileContent = await context.ReadStateAsync<string>(message, scopeName: FileContentStateConstants.FileContentStateScope)
|
||||
var fileContent = await context.ReadStateAsync<string>(message, scopeName: FileContentStateConstants.FileContentStateScope, cancellationToken)
|
||||
?? throw new InvalidOperationException("File content state not found");
|
||||
|
||||
int paragraphCount = fileContent.Split(['\n', '\r'], StringSplitOptions.RemoveEmptyEntries).Length;
|
||||
@@ -100,11 +99,11 @@ internal sealed class ParagraphCountingExecutor() : ReflectingExecutor<Paragraph
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class AggregationExecutor() : ReflectingExecutor<AggregationExecutor>("AggregationExecutor"), IMessageHandler<FileStats>
|
||||
internal sealed class AggregationExecutor() : Executor<FileStats>("AggregationExecutor")
|
||||
{
|
||||
private readonly List<FileStats> _messages = [];
|
||||
|
||||
public async ValueTask HandleAsync(FileStats message, IWorkflowContext context)
|
||||
public override async ValueTask HandleAsync(FileStats message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
this._messages.Add(message);
|
||||
|
||||
@@ -113,7 +112,7 @@ internal sealed class AggregationExecutor() : ReflectingExecutor<AggregationExec
|
||||
// Aggregate the results from both executors
|
||||
var totalParagraphCount = this._messages.Sum(m => m.ParagraphCount);
|
||||
var totalWordCount = this._messages.Sum(m => m.WordCount);
|
||||
await context.YieldOutputAsync($"Total Paragraphs: {totalParagraphCount}, Total Words: {totalWordCount}");
|
||||
await context.YieldOutputAsync($"Total Paragraphs: {totalParagraphCount}, Total Words: {totalWordCount}", cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-7
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Agents.AI.Workflows;
|
||||
using Microsoft.Agents.AI.Workflows.Reflection;
|
||||
|
||||
namespace WorkflowExecutorsAndEdgesSample;
|
||||
|
||||
@@ -44,32 +43,36 @@ public static class Program
|
||||
/// <summary>
|
||||
/// First executor: converts input text to uppercase.
|
||||
/// </summary>
|
||||
internal sealed class UppercaseExecutor() : ReflectingExecutor<UppercaseExecutor>("UppercaseExecutor"), IMessageHandler<string, string>
|
||||
internal sealed class UppercaseExecutor() : Executor<string, string>("UppercaseExecutor")
|
||||
{
|
||||
/// <summary>
|
||||
/// Processes the input message by converting it to uppercase.
|
||||
/// </summary>
|
||||
/// <param name="message">The input text to convert</param>
|
||||
/// <param name="context">Workflow context for accessing workflow services and adding events</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
|
||||
/// The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The input text converted to uppercase</returns>
|
||||
public async ValueTask<string> HandleAsync(string message, IWorkflowContext context) =>
|
||||
message.ToUpperInvariant(); // The return value will be sent as a message along an edge to subsequent executors
|
||||
public override ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default) =>
|
||||
ValueTask.FromResult(message.ToUpperInvariant()); // The return value will be sent as a message along an edge to subsequent executors
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Second executor: reverses the input text and completes the workflow.
|
||||
/// </summary>
|
||||
internal sealed class ReverseTextExecutor() : ReflectingExecutor<ReverseTextExecutor>("ReverseTextExecutor"), IMessageHandler<string, string>
|
||||
internal sealed class ReverseTextExecutor() : Executor<string, string>("ReverseTextExecutor")
|
||||
{
|
||||
/// <summary>
|
||||
/// Processes the input message by reversing the text.
|
||||
/// </summary>
|
||||
/// <param name="message">The input text to reverse</param>
|
||||
/// <param name="context">Workflow context for accessing workflow services and adding events</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
|
||||
/// The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The input text reversed</returns>
|
||||
public async ValueTask<string> HandleAsync(string message, IWorkflowContext context)
|
||||
public override ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Because we do not suppress it, the returned result will be yielded as an output from this executor.
|
||||
return string.Concat(message.Reverse());
|
||||
return ValueTask.FromResult(string.Concat(message.Reverse()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Agents.AI.Workflows;
|
||||
using Microsoft.Agents.AI.Workflows.Reflection;
|
||||
|
||||
namespace WorkflowStreamingSample;
|
||||
|
||||
@@ -30,7 +29,7 @@ public static class Program
|
||||
|
||||
// Execute the workflow in streaming mode
|
||||
await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, "Hello, World!");
|
||||
await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
|
||||
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
|
||||
{
|
||||
if (evt is ExecutorCompletedEvent executorCompleted)
|
||||
{
|
||||
@@ -43,32 +42,36 @@ public static class Program
|
||||
/// <summary>
|
||||
/// First executor: converts input text to uppercase.
|
||||
/// </summary>
|
||||
internal sealed class UppercaseExecutor() : ReflectingExecutor<UppercaseExecutor>("UppercaseExecutor"), IMessageHandler<string, string>
|
||||
internal sealed class UppercaseExecutor() : Executor<string, string>("UppercaseExecutor")
|
||||
{
|
||||
/// <summary>
|
||||
/// Processes the input message by converting it to uppercase.
|
||||
/// </summary>
|
||||
/// <param name="message">The input text to convert</param>
|
||||
/// <param name="context">Workflow context for accessing workflow services and adding events</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
|
||||
/// The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The input text converted to uppercase</returns>
|
||||
public async ValueTask<string> HandleAsync(string message, IWorkflowContext context) =>
|
||||
message.ToUpperInvariant(); // The return value will be sent as a message along an edge to subsequent executors
|
||||
public override ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default) =>
|
||||
ValueTask.FromResult(message.ToUpperInvariant()); // The return value will be sent as a message along an edge to subsequent executors
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Second executor: reverses the input text and completes the workflow.
|
||||
/// </summary>
|
||||
internal sealed class ReverseTextExecutor() : ReflectingExecutor<ReverseTextExecutor>("ReverseTextExecutor"), IMessageHandler<string, string>
|
||||
internal sealed class ReverseTextExecutor() : Executor<string, string>("ReverseTextExecutor")
|
||||
{
|
||||
/// <summary>
|
||||
/// Processes the input message by reversing the text.
|
||||
/// </summary>
|
||||
/// <param name="message">The input text to reverse</param>
|
||||
/// <param name="context">Workflow context for accessing workflow services and adding events</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
|
||||
/// The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The input text reversed</returns>
|
||||
public async ValueTask<string> HandleAsync(string message, IWorkflowContext context)
|
||||
public override ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Because we do not suppress it, the returned result will be yielded as an output from this executor.
|
||||
return string.Concat(message.Reverse());
|
||||
return ValueTask.FromResult(string.Concat(message.Reverse()));
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@ public static class Program
|
||||
// The agents are wrapped as executors. When they receive messages,
|
||||
// they will cache the messages and only start processing when they receive a TurnToken.
|
||||
await run.TrySendMessageAsync(new TurnToken(emitEvents: true));
|
||||
await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
|
||||
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
|
||||
{
|
||||
if (evt is AgentRunUpdateEvent executorComplete)
|
||||
{
|
||||
|
||||
+2
-2
@@ -70,7 +70,7 @@ public static class Program
|
||||
|
||||
case "groupchat":
|
||||
await RunWorkflowAsync(
|
||||
AgentWorkflowBuilder.CreateGroupChatBuilderWith(agents => new AgentWorkflowBuilder.RoundRobinGroupChatManager(agents) { MaximumIterationCount = 5 })
|
||||
AgentWorkflowBuilder.CreateGroupChatBuilderWith(agents => new RoundRobinGroupChatManager(agents) { MaximumIterationCount = 5 })
|
||||
.AddParticipants(from lang in (string[])["French", "Spanish", "English"] select GetTranslationAgent(lang, client))
|
||||
.Build(),
|
||||
[new(ChatRole.User, "Hello, world!")]);
|
||||
@@ -86,7 +86,7 @@ public static class Program
|
||||
|
||||
await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, messages);
|
||||
await run.TrySendMessageAsync(new TurnToken(emitEvents: true));
|
||||
await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
|
||||
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
|
||||
{
|
||||
if (evt is AgentRunUpdateEvent e)
|
||||
{
|
||||
|
||||
+1
-2
@@ -58,8 +58,7 @@ AIAgent reporter = new ChatClientAgent(anthropic,
|
||||
|
||||
// Build a sequential workflow: Researcher -> Fact-Checker -> Reporter
|
||||
AIAgent workflowAgent = await AgentWorkflowBuilder.BuildSequential(researcher, factChecker, reporter)
|
||||
.AsAgentAsync()
|
||||
.ConfigureAwait(false);
|
||||
.AsAgentAsync();
|
||||
|
||||
// Run the workflow, streaming the output as it arrives.
|
||||
string? lastAuthor = null;
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Workflows\Microsoft.Agents.AI.Workflows.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,156 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Agents.AI.Workflows;
|
||||
|
||||
namespace WorkflowSubWorkflowsSample;
|
||||
|
||||
/// <summary>
|
||||
/// This sample demonstrates how to compose workflows hierarchically by using
|
||||
/// a workflow as an executor within another workflow (sub-workflows).
|
||||
///
|
||||
/// A sub-workflow is a workflow that is embedded as an executor within a parent workflow.
|
||||
/// This allows you to:
|
||||
/// 1. Encapsulate and reuse complex workflow logic as modular components
|
||||
/// 2. Build hierarchical workflow structures
|
||||
/// 3. Create composable, maintainable workflow architectures
|
||||
///
|
||||
/// In this example, we create:
|
||||
/// - A text processing sub-workflow (uppercase → reverse → append suffix)
|
||||
/// - A parent workflow that adds a prefix, processes through the sub-workflow, and post-processes
|
||||
///
|
||||
/// For input "hello", the workflow produces: "INPUT: [FINAL] OLLEH [PROCESSED] [END]"
|
||||
/// </summary>
|
||||
public static class Program
|
||||
{
|
||||
private static async Task Main()
|
||||
{
|
||||
Console.WriteLine("\n=== Sub-Workflow Demonstration ===\n");
|
||||
|
||||
// Step 1: Build a simple text processing sub-workflow
|
||||
Console.WriteLine("Building sub-workflow: Uppercase → Reverse → Append Suffix...\n");
|
||||
|
||||
UppercaseExecutor uppercase = new();
|
||||
ReverseExecutor reverse = new();
|
||||
AppendSuffixExecutor append = new(" [PROCESSED]");
|
||||
|
||||
var subWorkflow = new WorkflowBuilder(uppercase)
|
||||
.AddEdge(uppercase, reverse)
|
||||
.AddEdge(reverse, append)
|
||||
.WithOutputFrom(append)
|
||||
.Build();
|
||||
|
||||
// Step 2: Configure the sub-workflow as an executor for use in the parent workflow
|
||||
ExecutorIsh subWorkflowExecutor = subWorkflow.ConfigureSubWorkflow("TextProcessingSubWorkflow");
|
||||
|
||||
// Step 3: Build a main workflow that uses the sub-workflow as an executor
|
||||
Console.WriteLine("Building main workflow that uses the sub-workflow as an executor...\n");
|
||||
|
||||
PrefixExecutor prefix = new("INPUT: ");
|
||||
PostProcessExecutor postProcess = new();
|
||||
|
||||
var mainWorkflow = new WorkflowBuilder(prefix)
|
||||
.AddEdge(prefix, subWorkflowExecutor)
|
||||
.AddEdge(subWorkflowExecutor, postProcess)
|
||||
.WithOutputFrom(postProcess)
|
||||
.Build();
|
||||
|
||||
// Step 4: Execute the main workflow
|
||||
Console.WriteLine("Executing main workflow with input: 'hello'\n");
|
||||
await using Run run = await InProcessExecution.RunAsync(mainWorkflow, "hello");
|
||||
|
||||
// Display results
|
||||
foreach (WorkflowEvent evt in run.NewEvents)
|
||||
{
|
||||
if (evt is ExecutorCompletedEvent executorComplete && executorComplete.Data is not null)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine($"[{executorComplete.ExecutorId}] {executorComplete.Data}");
|
||||
Console.ResetColor();
|
||||
}
|
||||
else if (evt is WorkflowOutputEvent output)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Cyan;
|
||||
Console.WriteLine("\n=== Main Workflow Completed ===");
|
||||
Console.WriteLine($"Final Output: {output.Data}");
|
||||
Console.ResetColor();
|
||||
}
|
||||
}
|
||||
|
||||
// Optional: Visualize the workflow structure - Note that sub-workflows are not rendered
|
||||
Console.ForegroundColor = ConsoleColor.DarkGray;
|
||||
Console.WriteLine("\n=== Workflow Visualization ===\n");
|
||||
Console.WriteLine(mainWorkflow.ToMermaidString());
|
||||
Console.ResetColor();
|
||||
|
||||
Console.WriteLine("\nâś… Sample Complete: Workflows can be composed hierarchically using sub-workflows\n");
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================
|
||||
// Text Processing Executors
|
||||
// ====================================
|
||||
|
||||
/// <summary>
|
||||
/// Adds a prefix to the input text.
|
||||
/// </summary>
|
||||
internal sealed class PrefixExecutor(string prefix) : Executor<string, string>("PrefixExecutor")
|
||||
{
|
||||
public override ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
string result = prefix + message;
|
||||
Console.WriteLine($"[Prefix] '{message}' → '{result}'");
|
||||
return ValueTask.FromResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts input text to uppercase.
|
||||
/// </summary>
|
||||
internal sealed class UppercaseExecutor() : Executor<string, string>("UppercaseExecutor")
|
||||
{
|
||||
public override ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
string result = message.ToUpperInvariant();
|
||||
Console.WriteLine($"[Uppercase] '{message}' → '{result}'");
|
||||
return ValueTask.FromResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reverses the input text.
|
||||
/// </summary>
|
||||
internal sealed class ReverseExecutor() : Executor<string, string>("ReverseExecutor")
|
||||
{
|
||||
public override ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
string result = string.Concat(message.Reverse());
|
||||
Console.WriteLine($"[Reverse] '{message}' → '{result}'");
|
||||
return ValueTask.FromResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends a suffix to the input text.
|
||||
/// </summary>
|
||||
internal sealed class AppendSuffixExecutor(string suffix) : Executor<string, string>("AppendSuffixExecutor")
|
||||
{
|
||||
public override ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
string result = message + suffix;
|
||||
Console.WriteLine($"[AppendSuffix] '{message}' → '{result}'");
|
||||
return ValueTask.FromResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs final post-processing by wrapping the text.
|
||||
/// </summary>
|
||||
internal sealed class PostProcessExecutor() : Executor<string, string>("PostProcessExecutor")
|
||||
{
|
||||
public override ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
string result = $"[FINAL] {message} [END]";
|
||||
Console.WriteLine($"[PostProcess] '{message}' → '{result}'");
|
||||
return ValueTask.FromResult(result);
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ The samples are subdivided into the following categories:
|
||||
`AIAgent` and can be used with any underlying service that provides an `AIAgent` implementation.
|
||||
- [Getting Started - Agent Providers](./GettingStarted/AgentProviders/README.md): Shows how to create an AIAgent instance for a selection of providers.
|
||||
- [Getting Started - Agent Telemetry](./GettingStarted/AgentOpenTelemetry/README.md): Demo which showcases the integration of OpenTelemetry with the Microsoft Agent Framework using Azure OpenAI and .NET Aspire Dashboard for telemetry visualization.
|
||||
- [Semantic Kernel Migration](./SemanticKernelMigration/): Semantic Kernel to Agent Framework migration guide
|
||||
- [Semantic Kernel to Agent Framework Migration](https://github.com/microsoft/semantic-kernel/tree/main/dotnet/samples/AgentFrameworkMigration): For instructions and samples describing how to migrate from Semantic Kernel to Microsoft Agent Framework
|
||||
|
||||
## Prerequisites
|
||||
|
||||
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<NoWarn>$(NoWarn);CA1812;RCS1102;CA1707;VSTHRD200</NoWarn>
|
||||
<InjectSharedThrow>true</InjectSharedThrow>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SemanticKernel" VersionOverride="1.*" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Agents.AzureAI" VersionOverride="1.*-*" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Agents.Core" VersionOverride="1.*" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Agents.Orchestration" VersionOverride="1.*-*" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Agents.Runtime.InProcess" VersionOverride="1.*-*" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Abstractions\Microsoft.Agents.AI.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Workflows\Microsoft.Agents.AI.Workflows.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
-109
@@ -1,109 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.Workflows;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Agents;
|
||||
using Microsoft.SemanticKernel.Agents.Orchestration;
|
||||
using Microsoft.SemanticKernel.Agents.Orchestration.Concurrent;
|
||||
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
|
||||
|
||||
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";
|
||||
|
||||
var agentInstructions = "You are a translation assistant who only responds in {0}. Respond to any input by outputting the name of the input language and then translating the input to {0}.";
|
||||
|
||||
// This sample compares running concurrent orchestrations using
|
||||
// Semantic Kernel and the Agent Framework.
|
||||
Console.WriteLine("=== Semantic Kernel Concurrent Orchestration ===");
|
||||
await SKConcurrentOrchestration();
|
||||
|
||||
Console.WriteLine("\n=== Agent Framework Concurrent Agent Workflow ===");
|
||||
await AFConcurrentAgentWorkflow();
|
||||
|
||||
# region SKConcurrentOrchestration
|
||||
#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
|
||||
async Task SKConcurrentOrchestration()
|
||||
{
|
||||
ConcurrentOrchestration orchestration = new([
|
||||
GetSKTranslationAgent("French"),
|
||||
GetSKTranslationAgent("Spanish")])
|
||||
{
|
||||
StreamingResponseCallback = StreamingResultCallback,
|
||||
};
|
||||
|
||||
InProcessRuntime runtime = new();
|
||||
await runtime.StartAsync();
|
||||
|
||||
// Run the orchestration
|
||||
OrchestrationResult<string[]> result = await orchestration.InvokeAsync("Hello, world!", runtime);
|
||||
string[] texts = await result.GetValueAsync(TimeSpan.FromSeconds(20));
|
||||
|
||||
await runtime.RunUntilIdleAsync();
|
||||
}
|
||||
#pragma warning restore SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
|
||||
|
||||
ChatCompletionAgent GetSKTranslationAgent(string targetLanguage)
|
||||
{
|
||||
var kernel = Kernel.CreateBuilder().AddAzureOpenAIChatCompletion(deploymentName, endpoint, new AzureCliCredential()).Build();
|
||||
return new ChatCompletionAgent()
|
||||
{
|
||||
Kernel = kernel,
|
||||
Instructions = string.Format(agentInstructions, targetLanguage),
|
||||
Description = $"Agent that translates texts to {targetLanguage}",
|
||||
Name = $"SKTranslationAgent_{targetLanguage}"
|
||||
};
|
||||
}
|
||||
|
||||
ValueTask StreamingResultCallback(StreamingChatMessageContent streamedResponse, bool isFinal)
|
||||
{
|
||||
Console.Write(streamedResponse.Content);
|
||||
|
||||
if (isFinal)
|
||||
{
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
# endregion
|
||||
|
||||
# region AFConcurrentAgentWorkflow
|
||||
async Task AFConcurrentAgentWorkflow()
|
||||
{
|
||||
var client = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()).GetChatClient(deploymentName).AsIChatClient();
|
||||
var frenchAgent = GetAFTranslationAgent("French", client);
|
||||
var spanishAgent = GetAFTranslationAgent("Spanish", client);
|
||||
var concurrentAgentWorkflow = AgentWorkflowBuilder.BuildConcurrent([frenchAgent, spanishAgent]);
|
||||
|
||||
await using StreamingRun run = await InProcessExecution.StreamAsync(concurrentAgentWorkflow, "Hello, world!");
|
||||
await run.TrySendMessageAsync(new TurnToken(emitEvents: true));
|
||||
|
||||
string? lastExecutorId = null;
|
||||
await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
|
||||
{
|
||||
if (evt is AgentRunUpdateEvent e)
|
||||
{
|
||||
if (string.IsNullOrEmpty(e.Update.Text))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (e.ExecutorId != lastExecutorId)
|
||||
{
|
||||
lastExecutorId = e.ExecutorId;
|
||||
Console.WriteLine();
|
||||
Console.Write($"{e.Update.AuthorName}: ");
|
||||
}
|
||||
|
||||
Console.Write(e.Update.Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ChatClientAgent GetAFTranslationAgent(string targetLanguage, IChatClient chatClient) =>
|
||||
new(chatClient, string.Format(agentInstructions, targetLanguage), name: $"AFTranslationAgent_{targetLanguage}");
|
||||
# endregion
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<NoWarn>$(NoWarn);CA1812;RCS1102;CA1707;VSTHRD200</NoWarn>
|
||||
<InjectSharedThrow>true</InjectSharedThrow>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SemanticKernel" VersionOverride="1.*" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Agents.AzureAI" VersionOverride="1.*-*" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Agents.Core" VersionOverride="1.*" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Agents.Orchestration" VersionOverride="1.*-*" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Agents.Runtime.InProcess" VersionOverride="1.*-*" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Abstractions\Microsoft.Agents.AI.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Workflows\Microsoft.Agents.AI.Workflows.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
-112
@@ -1,112 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.Workflows;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Agents;
|
||||
using Microsoft.SemanticKernel.Agents.Orchestration;
|
||||
using Microsoft.SemanticKernel.Agents.Orchestration.Sequential;
|
||||
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
|
||||
|
||||
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";
|
||||
|
||||
var agentInstructions = "You are a translation assistant who only responds in {0}. Respond to any input by outputting the name of the input language and then translating the input to {0}.";
|
||||
|
||||
// This sample compares running sequential orchestrations using
|
||||
// Semantic Kernel and the Agent Framework.
|
||||
Console.WriteLine("=== Semantic Kernel Sequential Orchestration ===");
|
||||
await SKSequentialOrchestration();
|
||||
|
||||
Console.WriteLine("\n=== Agent Framework Sequential Agent Workflow ===");
|
||||
await AFSequentialAgentWorkflow();
|
||||
|
||||
# region SKSequentialOrchestration
|
||||
#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
|
||||
async Task SKSequentialOrchestration()
|
||||
{
|
||||
SequentialOrchestration orchestration = new([
|
||||
GetSKTranslationAgent("French"),
|
||||
GetSKTranslationAgent("Spanish"),
|
||||
GetSKTranslationAgent("English")])
|
||||
{
|
||||
StreamingResponseCallback = StreamingResultCallback,
|
||||
};
|
||||
|
||||
InProcessRuntime runtime = new();
|
||||
await runtime.StartAsync();
|
||||
|
||||
// Run the orchestration
|
||||
OrchestrationResult<string> result = await orchestration.InvokeAsync("Hello, world!", runtime);
|
||||
string text = await result.GetValueAsync(TimeSpan.FromSeconds(20));
|
||||
|
||||
await runtime.RunUntilIdleAsync();
|
||||
}
|
||||
#pragma warning restore SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
|
||||
|
||||
ChatCompletionAgent GetSKTranslationAgent(string targetLanguage)
|
||||
{
|
||||
var kernel = Kernel.CreateBuilder().AddAzureOpenAIChatCompletion(deploymentName, endpoint, new AzureCliCredential()).Build();
|
||||
return new ChatCompletionAgent()
|
||||
{
|
||||
Kernel = kernel,
|
||||
Instructions = string.Format(agentInstructions, targetLanguage),
|
||||
Description = $"Agent that translates texts to {targetLanguage}",
|
||||
Name = $"SKTranslationAgent_{targetLanguage}"
|
||||
};
|
||||
}
|
||||
|
||||
ValueTask StreamingResultCallback(StreamingChatMessageContent streamedResponse, bool isFinal)
|
||||
{
|
||||
Console.Write(streamedResponse.Content);
|
||||
|
||||
if (isFinal)
|
||||
{
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
# endregion
|
||||
|
||||
# region AFSequentialAgentWorkflow
|
||||
async Task AFSequentialAgentWorkflow()
|
||||
{
|
||||
var client = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()).GetChatClient(deploymentName).AsIChatClient();
|
||||
var frenchAgent = GetAFTranslationAgent("French", client);
|
||||
var spanishAgent = GetAFTranslationAgent("Spanish", client);
|
||||
var englishAgent = GetAFTranslationAgent("English", client);
|
||||
var sequentialAgentWorkflow = AgentWorkflowBuilder.BuildSequential(
|
||||
[frenchAgent, spanishAgent, englishAgent]);
|
||||
|
||||
await using StreamingRun run = await InProcessExecution.StreamAsync(sequentialAgentWorkflow, "Hello, world!");
|
||||
await run.TrySendMessageAsync(new TurnToken(emitEvents: true));
|
||||
|
||||
string? lastExecutorId = null;
|
||||
await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
|
||||
{
|
||||
if (evt is AgentRunUpdateEvent e)
|
||||
{
|
||||
if (string.IsNullOrEmpty(e.Update.Text))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (e.ExecutorId != lastExecutorId)
|
||||
{
|
||||
lastExecutorId = e.ExecutorId;
|
||||
Console.WriteLine();
|
||||
Console.Write($"{e.Update.AuthorName}: ");
|
||||
}
|
||||
|
||||
Console.Write(e.Update.Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ChatClientAgent GetAFTranslationAgent(string targetLanguage, IChatClient chatClient) =>
|
||||
new(chatClient, string.Format(agentInstructions, targetLanguage), name: $"AFTranslationAgent_{targetLanguage}");
|
||||
# endregion
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<NoWarn>$(NoWarn);CA1812;RCS1102;CA1707;VSTHRD200</NoWarn>
|
||||
<InjectSharedThrow>true</InjectSharedThrow>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SemanticKernel" VersionOverride="1.*" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Agents.AzureAI" VersionOverride="1.*-*" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Agents.Core" VersionOverride="1.*" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Agents.Orchestration" VersionOverride="1.*-*" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Agents.Runtime.InProcess" VersionOverride="1.*-*" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Abstractions\Microsoft.Agents.AI.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Workflows\Microsoft.Agents.AI.Workflows.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,247 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
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;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Agents;
|
||||
using Microsoft.SemanticKernel.Agents.Orchestration;
|
||||
using Microsoft.SemanticKernel.Agents.Orchestration.Handoff;
|
||||
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
|
||||
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";
|
||||
|
||||
// Queries to simulate user input during the interactive orchestration
|
||||
List<string> Queries = [
|
||||
"I'd like to track the status of my first order 123.",
|
||||
"I want to return another order of mine whose ID is 456 because it arrived damaged.",
|
||||
];
|
||||
|
||||
// This sample compares running handoff orchestrations using
|
||||
// Semantic Kernel and the Agent Framework.
|
||||
Console.WriteLine("=== Semantic Kernel Handoff Orchestration ===");
|
||||
// State to help format the streaming output
|
||||
bool newAgentTurn = true;
|
||||
string previousFunctionCallId = string.Empty;
|
||||
await SKHandoffOrchestration();
|
||||
|
||||
Console.WriteLine("\n=== Agent Framework Handoff Agent Workflow ===");
|
||||
await AFHandoffAgentWorkflow();
|
||||
|
||||
# region SKHandoffOrchestration
|
||||
[KernelFunction]
|
||||
string SKCheckOrderStatus(string orderId) => $"Order {orderId} is shipped and will arrive in 2-3 days.";
|
||||
|
||||
[KernelFunction]
|
||||
string SKProcessReturn(string orderId, string reason) => $"Return for order {orderId} has been processed successfully.";
|
||||
|
||||
[KernelFunction]
|
||||
string SKProcessRefund(string orderId, string reason) => $"Refund for order {orderId} has been processed successfully.";
|
||||
|
||||
#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
|
||||
async Task SKHandoffOrchestration()
|
||||
{
|
||||
// Create agents
|
||||
var triageAgent = GetSKAgent(
|
||||
instructions: "You are a customer support agent that triages issues.",
|
||||
name: "TriageAgent",
|
||||
description: "Handle customer requests.");
|
||||
var statusAgent = GetSKAgent(
|
||||
instructions: "You are a customer support agent that checks order status.",
|
||||
name: "OrderStatusAgent",
|
||||
description: "Handle order status requests.");
|
||||
statusAgent.Kernel.Plugins.AddFromFunctions("OrderStatusPlugin", [KernelFunctionFactory.CreateFromMethod(SKCheckOrderStatus)]);
|
||||
var returnAgent = GetSKAgent(
|
||||
instructions: "You are a customer support agent that handles order returns.",
|
||||
name: "OrderReturnAgent",
|
||||
description: "Handle order return requests.");
|
||||
returnAgent.Kernel.Plugins.AddFromFunctions("OrderReturnPlugin", [KernelFunctionFactory.CreateFromMethod(SKProcessReturn)]);
|
||||
var refundAgent = GetSKAgent(
|
||||
instructions: "You are a customer support agent that handles order refunds.",
|
||||
name: "OrderRefundAgent",
|
||||
description: "Handle order refund requests.");
|
||||
refundAgent.Kernel.Plugins.AddFromFunctions("OrderRefundPlugin", [KernelFunctionFactory.CreateFromMethod(SKProcessRefund)]);
|
||||
|
||||
Queue<string> queries = new(Queries);
|
||||
|
||||
// Create orchestration with handoffs
|
||||
HandoffOrchestration orchestration =
|
||||
new(OrchestrationHandoffs
|
||||
.StartWith(triageAgent)
|
||||
.Add(triageAgent, statusAgent, returnAgent, refundAgent)
|
||||
.Add(statusAgent, triageAgent, "Transfer to this agent if the issue is not status related")
|
||||
.Add(returnAgent, triageAgent, "Transfer to this agent if the issue is not return related")
|
||||
.Add(refundAgent, triageAgent, "Transfer to this agent if the issue is not refund related"),
|
||||
triageAgent,
|
||||
statusAgent,
|
||||
returnAgent,
|
||||
refundAgent)
|
||||
{
|
||||
InteractiveCallback = () =>
|
||||
{
|
||||
string input = queries.Count > 0 ? queries.Dequeue() : "exit";
|
||||
Console.WriteLine($"\nUser: {input}");
|
||||
return ValueTask.FromResult(new ChatMessageContent(AuthorRole.User, input));
|
||||
},
|
||||
StreamingResponseCallback = StreamingResultCallback,
|
||||
};
|
||||
|
||||
InProcessRuntime runtime = new();
|
||||
await runtime.StartAsync();
|
||||
|
||||
// Run the orchestration
|
||||
OrchestrationResult<string> result = await orchestration.InvokeAsync(
|
||||
"I am a customer that needs help with my two orders",
|
||||
runtime);
|
||||
string text = await result.GetValueAsync();
|
||||
Console.WriteLine($"\nFinal Result: {text}");
|
||||
|
||||
await runtime.RunUntilIdleAsync();
|
||||
}
|
||||
#pragma warning restore SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
|
||||
|
||||
ChatCompletionAgent GetSKAgent(string instructions, string name, string description)
|
||||
{
|
||||
var kernel = Kernel.CreateBuilder().AddAzureOpenAIChatCompletion(deploymentName, endpoint, new AzureCliCredential()).Build();
|
||||
return new ChatCompletionAgent()
|
||||
{
|
||||
Kernel = kernel,
|
||||
Instructions = instructions,
|
||||
Description = description,
|
||||
Name = name
|
||||
};
|
||||
}
|
||||
|
||||
ValueTask StreamingResultCallback(StreamingChatMessageContent streamedResponse, bool isFinal)
|
||||
{
|
||||
if (newAgentTurn)
|
||||
{
|
||||
Console.Write($"\n{streamedResponse.AuthorName}: ");
|
||||
newAgentTurn = false;
|
||||
}
|
||||
Console.Write(streamedResponse.Content);
|
||||
|
||||
if (streamedResponse.Items.OfType<StreamingFunctionCallUpdateContent>().FirstOrDefault()
|
||||
is StreamingFunctionCallUpdateContent call)
|
||||
{
|
||||
if (call.CallId is not null && previousFunctionCallId != call.CallId)
|
||||
{
|
||||
Console.Write($"\nCalling function '{call.Name}' with arguments: ");
|
||||
previousFunctionCallId = call.CallId;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(call.Arguments))
|
||||
{
|
||||
Console.Write($"{call.Arguments}");
|
||||
}
|
||||
}
|
||||
|
||||
if (isFinal)
|
||||
{
|
||||
newAgentTurn = true;
|
||||
previousFunctionCallId = string.Empty;
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
# endregion
|
||||
|
||||
# region AFHandoffAgentWorkflow
|
||||
[Description("Get the order status for a given order ID.")]
|
||||
static string AFCheckOrderStatus([Description("The order ID to check the status for.")] string orderId)
|
||||
=> $"Order {orderId} is shipped and will arrive in 2-3 days.";
|
||||
|
||||
[Description("Process a return for a given order ID.")]
|
||||
static string AFProcessReturn(
|
||||
[Description("The order ID to process the return for.")] string orderId,
|
||||
[Description("The reason for the return.")] string reason)
|
||||
=> $"Return for order {orderId} has been processed successfully for the following reason: {reason}.";
|
||||
|
||||
[Description("Process a refund for a given order ID.")]
|
||||
static string AFProcessRefund([Description("The order ID to process the refund for.")] string orderId)
|
||||
=> $"Refund for order {orderId} has been processed successfully.";
|
||||
|
||||
async Task AFHandoffAgentWorkflow()
|
||||
{
|
||||
// Create agents
|
||||
var client = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()).GetChatClient(deploymentName).AsIChatClient();
|
||||
|
||||
ChatClientAgent triageAgent = new(client,
|
||||
instructions: "A customer support agent that triages issues.",
|
||||
name: "TriageAgent",
|
||||
description: "Handle customer requests.");
|
||||
ChatClientAgent statusAgent = new(client,
|
||||
name: "OrderStatusAgent",
|
||||
instructions: "Handle order status requests.",
|
||||
description: "A customer support agent that checks order status.",
|
||||
tools: [AIFunctionFactory.Create(AFCheckOrderStatus)]);
|
||||
ChatClientAgent returnAgent = new(client,
|
||||
name: "OrderReturnAgent",
|
||||
instructions: "Handle order return requests.",
|
||||
description: "A customer support agent that handles order returns.",
|
||||
tools: [AIFunctionFactory.Create(AFProcessReturn)]);
|
||||
ChatClientAgent refundAgent = new(client,
|
||||
name: "OrderRefundAgent",
|
||||
instructions: "Handle order refund requests.",
|
||||
description: "A customer support agent that handles order refund.",
|
||||
tools: [AIFunctionFactory.Create(AFProcessRefund)]);
|
||||
|
||||
// Create workflow with handoffs
|
||||
var handoffAgentWorkflow = AgentWorkflowBuilder.CreateHandoffBuilderWith(triageAgent)
|
||||
.WithHandoffs(triageAgent, [statusAgent, returnAgent, refundAgent])
|
||||
.WithHandoff(statusAgent, triageAgent, "Transfer to this agent if the issue is not status related")
|
||||
.WithHandoff(returnAgent, triageAgent, "Transfer to this agent if the issue is not return related")
|
||||
.WithHandoff(refundAgent, triageAgent, "Transfer to this agent if the issue is not refund related")
|
||||
.Build();
|
||||
|
||||
// Run the workflow
|
||||
List<ChatMessage> messages = [];
|
||||
foreach (var query in Queries)
|
||||
{
|
||||
Console.WriteLine($"User: {query}");
|
||||
messages.Add(new(ChatRole.User, query));
|
||||
|
||||
await using var run = await InProcessExecution.StreamAsync(handoffAgentWorkflow, messages);
|
||||
await run.TrySendMessageAsync(new TurnToken(emitEvents: true));
|
||||
|
||||
string? lastExecutorId = null;
|
||||
await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
|
||||
{
|
||||
if (evt is AgentRunUpdateEvent e)
|
||||
{
|
||||
if (string.IsNullOrEmpty(e.Update.Text) && e.Update.Contents.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (e.ExecutorId != lastExecutorId)
|
||||
{
|
||||
lastExecutorId = e.ExecutorId;
|
||||
Console.WriteLine();
|
||||
Console.Write($"{e.Update.AuthorName}: ");
|
||||
}
|
||||
|
||||
Console.Write(e.Update.Text);
|
||||
|
||||
if (e.Update.Contents.OfType<Microsoft.Extensions.AI.FunctionCallContent>().FirstOrDefault()
|
||||
is Microsoft.Extensions.AI.FunctionCallContent call)
|
||||
{
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($"Calling function '{call.Name}' with arguments: {JsonSerializer.Serialize(call.Arguments)}");
|
||||
}
|
||||
}
|
||||
else if (evt is WorkflowOutputEvent output)
|
||||
{
|
||||
Console.WriteLine("\n");
|
||||
messages.AddRange(output.As<List<ChatMessage>>()!);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
# endregion
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<NoWarn>$(NoWarn);CA1812;RCS1102</NoWarn>
|
||||
<InjectSharedThrow>true</InjectSharedThrow>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Linq.Async" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel" VersionOverride="1.*" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Agents.AzureAI" VersionOverride="1.*-*" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Agents.Core" VersionOverride="1.*" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Abstractions\Microsoft.Agents.AI.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,77 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Azure.AI.Agents.Persistent;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Agents.AzureAI;
|
||||
|
||||
var azureEndpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_FOUNDRY_PROJECT_ENDPOINT is not set.");
|
||||
var deploymentName = System.Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME") ?? "gpt-4o";
|
||||
var userInput = "Tell me a joke about a pirate.";
|
||||
|
||||
Console.WriteLine($"User Input: {userInput}");
|
||||
|
||||
await SKAgentAsync();
|
||||
await AFAgentAsync();
|
||||
|
||||
async Task SKAgentAsync()
|
||||
{
|
||||
Console.WriteLine("\n=== SK Agent ===\n");
|
||||
|
||||
var azureAgentClient = AzureAIAgent.CreateAgentsClient(azureEndpoint, new AzureCliCredential());
|
||||
|
||||
PersistentAgent definition = await azureAgentClient.Administration.CreateAgentAsync(
|
||||
deploymentName,
|
||||
name: "GenerateStory",
|
||||
instructions: "You are good at telling jokes.");
|
||||
|
||||
AzureAIAgent agent = new(definition, azureAgentClient);
|
||||
|
||||
var thread = new AzureAIAgentThread(azureAgentClient);
|
||||
|
||||
AzureAIAgentInvokeOptions options = new() { MaxPromptTokens = 1000 };
|
||||
var result = await agent.InvokeAsync(userInput, thread, options).FirstAsync();
|
||||
Console.WriteLine(result.Message);
|
||||
|
||||
Console.WriteLine("---");
|
||||
await foreach (StreamingChatMessageContent update in agent.InvokeStreamingAsync(userInput, thread))
|
||||
{
|
||||
Console.Write(update);
|
||||
}
|
||||
|
||||
// Clean up
|
||||
await thread.DeleteAsync();
|
||||
await azureAgentClient.Administration.DeleteAgentAsync(agent.Id);
|
||||
}
|
||||
|
||||
async Task AFAgentAsync()
|
||||
{
|
||||
Console.WriteLine("\n=== AF Agent ===\n");
|
||||
|
||||
var azureAgentClient = new PersistentAgentsClient(azureEndpoint, new AzureCliCredential());
|
||||
|
||||
var agent = await azureAgentClient.CreateAIAgentAsync(
|
||||
deploymentName,
|
||||
name: "GenerateStory",
|
||||
instructions: "You are good at telling jokes.");
|
||||
|
||||
var thread = agent.GetNewThread();
|
||||
var agentOptions = new ChatClientAgentRunOptions(new() { MaxOutputTokens = 1000 });
|
||||
|
||||
var result = await agent.RunAsync(userInput, thread, agentOptions);
|
||||
Console.WriteLine(result);
|
||||
|
||||
Console.WriteLine("---");
|
||||
await foreach (var update in agent.RunStreamingAsync(userInput, thread, agentOptions))
|
||||
{
|
||||
Console.Write(update);
|
||||
}
|
||||
|
||||
// Clean up
|
||||
if (thread is ChatClientAgentThread chatThread)
|
||||
{
|
||||
await azureAgentClient.Threads.DeleteThreadAsync(chatThread.ConversationId);
|
||||
}
|
||||
await azureAgentClient.Administration.DeleteAgentAsync(agent.Id);
|
||||
}
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Linq.Async" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel" VersionOverride="1.*" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Agents.AzureAI" VersionOverride="1.*-*" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Agents.Core" VersionOverride="1.*" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Abstractions\Microsoft.Agents.AI.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,86 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.ComponentModel;
|
||||
using Azure.AI.Agents.Persistent;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Agents.AzureAI;
|
||||
|
||||
var azureEndpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_FOUNDRY_PROJECT_ENDPOINT is not set.");
|
||||
var deploymentName = System.Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME") ?? "gpt-4o";
|
||||
var userInput = "What is the weather like in Amsterdam?";
|
||||
|
||||
Console.WriteLine($"User Input: {userInput}");
|
||||
|
||||
[KernelFunction]
|
||||
[Description("Get the weather for a given location.")]
|
||||
static string GetWeather([Description("The location to get the weather for.")] string location)
|
||||
=> $"The weather in {location} is cloudy with a high of 15°C.";
|
||||
|
||||
await SKAgentAsync();
|
||||
await AFAgentAsync();
|
||||
|
||||
async Task SKAgentAsync()
|
||||
{
|
||||
Console.WriteLine("\n=== SK Agent ===\n");
|
||||
|
||||
var azureAgentClient = AzureAIAgent.CreateAgentsClient(azureEndpoint, new AzureCliCredential());
|
||||
|
||||
PersistentAgent definition = await azureAgentClient.Administration.CreateAgentAsync(deploymentName, instructions: "You are a helpful assistant");
|
||||
|
||||
AzureAIAgent agent = new(definition, azureAgentClient)
|
||||
{
|
||||
Kernel = Kernel.CreateBuilder().Build(),
|
||||
Name = "Host",
|
||||
Instructions = "You are a helpful assistant",
|
||||
Arguments = new KernelArguments(new PromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }),
|
||||
};
|
||||
|
||||
var thread = new AzureAIAgentThread(azureAgentClient);
|
||||
|
||||
// Initialize plugin and add to the agent's Kernel (same as direct Kernel usage).
|
||||
agent.Kernel.Plugins.Add(KernelPluginFactory.CreateFromFunctions("KernelPluginName", [KernelFunctionFactory.CreateFromMethod(GetWeather)]));
|
||||
|
||||
var result = await agent.InvokeAsync(userInput).FirstAsync();
|
||||
Console.WriteLine(result.Message);
|
||||
|
||||
Console.WriteLine("---");
|
||||
await foreach (ChatMessageContent update in agent.InvokeAsync(userInput, thread))
|
||||
{
|
||||
Console.Write(update);
|
||||
}
|
||||
|
||||
// Clean up
|
||||
await thread.DeleteAsync();
|
||||
await azureAgentClient.Administration.DeleteAgentAsync(agent.Id);
|
||||
}
|
||||
|
||||
async Task AFAgentAsync()
|
||||
{
|
||||
Console.WriteLine("\n=== AF Agent ===\n");
|
||||
|
||||
var azureAgentClient = new PersistentAgentsClient(azureEndpoint, new AzureCliCredential());
|
||||
|
||||
var agent = await azureAgentClient.CreateAIAgentAsync(deploymentName, instructions: "Answer questions about the menu");
|
||||
|
||||
var thread = agent.GetNewThread();
|
||||
var agentOptions = new ChatClientAgentRunOptions(new() { Tools = [AIFunctionFactory.Create(GetWeather)] });
|
||||
|
||||
var result = await agent.RunAsync(userInput, thread, agentOptions);
|
||||
Console.WriteLine(result);
|
||||
|
||||
Console.WriteLine("---");
|
||||
await foreach (var update in agent.RunStreamingAsync(userInput, thread, agentOptions))
|
||||
{
|
||||
Console.Write(update);
|
||||
}
|
||||
|
||||
// Clean up
|
||||
if (thread is ChatClientAgentThread chatThread)
|
||||
{
|
||||
await azureAgentClient.Threads.DeleteThreadAsync(chatThread.ConversationId);
|
||||
}
|
||||
await azureAgentClient.Administration.DeleteAgentAsync(agent.Id);
|
||||
}
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Linq.Async" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel" VersionOverride="1.*" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Agents.AzureAI" VersionOverride="1.*-*" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Agents.Core" VersionOverride="1.*" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Abstractions\Microsoft.Agents.AI.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
-99
@@ -1,99 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Azure.AI.Agents.Persistent;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Agents.AzureAI;
|
||||
|
||||
var azureEndpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_FOUNDRY_PROJECT_ENDPOINT is not set.");
|
||||
var deploymentName = System.Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME") ?? "gpt-4o";
|
||||
var userInput = "Tell me a joke about a pirate.";
|
||||
|
||||
Console.WriteLine($"User Input: {userInput}");
|
||||
|
||||
await SKAgentAsync();
|
||||
await AFAgentAsync();
|
||||
|
||||
async Task SKAgentAsync()
|
||||
{
|
||||
Console.WriteLine("\n=== SK Agent ===\n");
|
||||
|
||||
var serviceCollection = new ServiceCollection();
|
||||
serviceCollection.AddSingleton((sp) => AzureAIAgent.CreateAgentsClient(azureEndpoint, new AzureCliCredential()));
|
||||
serviceCollection.AddTransient<AzureAIAgent>((sp) =>
|
||||
{
|
||||
var azureAgentClient = sp.GetRequiredService<PersistentAgentsClient>();
|
||||
|
||||
Console.Write("Creating agent in the cloud...");
|
||||
|
||||
PersistentAgent definition = azureAgentClient.Administration
|
||||
.CreateAgent(deploymentName,
|
||||
name: "GenerateStory",
|
||||
instructions: "You are good at telling jokes.");
|
||||
|
||||
Console.Write("Done\n");
|
||||
|
||||
return new(definition, azureAgentClient);
|
||||
});
|
||||
serviceCollection.AddKernel();
|
||||
|
||||
await using ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
|
||||
var agent = serviceProvider.GetRequiredService<AzureAIAgent>();
|
||||
|
||||
var thread = new AzureAIAgentThread(agent.Client);
|
||||
|
||||
var result = await agent.InvokeAsync(userInput).FirstAsync();
|
||||
Console.WriteLine(result.Message);
|
||||
|
||||
Console.WriteLine("---");
|
||||
await foreach (ChatMessageContent update in agent.InvokeAsync(userInput, thread))
|
||||
{
|
||||
Console.Write(update);
|
||||
}
|
||||
|
||||
// Clean up
|
||||
await thread.DeleteAsync();
|
||||
await agent.Client.Administration.DeleteAgentAsync(agent.Id);
|
||||
}
|
||||
|
||||
async Task AFAgentAsync()
|
||||
{
|
||||
Console.WriteLine("\n=== AF Agent ===\n");
|
||||
|
||||
var serviceCollection = new ServiceCollection();
|
||||
serviceCollection.AddSingleton((sp) => new PersistentAgentsClient(azureEndpoint, new AzureCliCredential()));
|
||||
serviceCollection.AddTransient<AIAgent>((sp) =>
|
||||
{
|
||||
var azureAgentClient = sp.GetRequiredService<PersistentAgentsClient>();
|
||||
|
||||
return azureAgentClient.CreateAIAgent(
|
||||
deploymentName,
|
||||
name: "GenerateStory",
|
||||
instructions: "You are good at telling jokes.");
|
||||
});
|
||||
|
||||
await using ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
|
||||
var agent = serviceProvider.GetRequiredService<AIAgent>();
|
||||
|
||||
var thread = agent.GetNewThread();
|
||||
|
||||
var result = await agent.RunAsync(userInput, thread);
|
||||
Console.WriteLine(result);
|
||||
|
||||
Console.WriteLine("---");
|
||||
await foreach (var update in agent.RunStreamingAsync(userInput, thread))
|
||||
{
|
||||
Console.Write(update);
|
||||
}
|
||||
|
||||
// Clean up
|
||||
var azureAgentClient = serviceProvider.GetRequiredService<PersistentAgentsClient>();
|
||||
if (thread is ChatClientAgentThread chatThread)
|
||||
{
|
||||
await azureAgentClient.Threads.DeleteThreadAsync(chatThread.ConversationId);
|
||||
}
|
||||
await azureAgentClient.Administration.DeleteAgentAsync(agent.Id);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user