Compare commits

..
Author SHA1 Message Date
westey f109a5fc9a Fix formatting 2025-12-04 09:18:48 +00:00
westey c5f003498b Fix formatting issue 2025-12-04 07:17:51 +00:00
westeyandGitHub 6c8ef728fa Merge branch 'main' into contextual-function-provider-port 2025-12-03 12:18:48 +00:00
CopilotGitHubwestey-mcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
7965285808 .NET: Add serialization support for ContextualFunctionProvider (#2601)
* Initial plan

* Add serialization support for ContextualFunctionProvider

- Added constructor overload that accepts JsonElement serializedState
- Implemented Serialize method to persist recent messages
- Added ContextualFunctionProviderState class
- Registered state class with AgentJsonUtilities JsonContext
- Added comprehensive unit tests for serialization/deserialization

Co-authored-by: westey-m <164392973+westey-m@users.noreply.github.com>

* Fix constructor parameter ordering and make default more explicit

- Reordered constructor parameters to maintain consistency (options before jsonSerializerOptions)
- Made default JsonElement explicit with default(JsonElement) in constructor chaining
- All tests continue to pass

Co-authored-by: westey-m <164392973+westey-m@users.noreply.github.com>

* Improve XML documentation comments for clarity

- Updated jsonSerializerOptions parameter comments to clarify actual usage
- Made it clear that AgentJsonUtilities.DefaultOptions is used
- All tests continue to pass

Co-authored-by: westey-m <164392973+westey-m@users.noreply.github.com>

* Change NumberOfRecentMessagesInContext to 2 to demonstrate truncation

- Updated test to use limit of 2 messages instead of 3
- Now test shows truncation in action (3 messages added, only 2 kept)
- Updated assertion to expect 2 messages in serialized state
- All tests pass

Co-authored-by: westey-m <164392973+westey-m@users.noreply.github.com>

* Apply suggestion from @westey-m

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: westey-m <164392973+westey-m@users.noreply.github.com>
2025-12-03 12:18:19 +00:00
CopilotGitHubwestey-mcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
9008bb8cb7 Skip adding messages to context when invocation fails with exception (#2600)
* Initial plan

* Add exception check in InvokedAsync to skip adding messages when invocation fails

Co-authored-by: westey-m <164392973+westey-m@users.noreply.github.com>

* Refactor InvokedAsync to use early return pattern

Co-authored-by: westey-m <164392973+westey-m@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: westey-m <164392973+westey-m@users.noreply.github.com>
2025-12-03 11:56:29 +00:00
938961d2cc Apply suggestions from code review
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-27 18:16:21 +00:00
westey d9ce6ee292 Format files. 2025-11-27 17:33:37 +00:00
westey 2a3e41dbdd Port ContextualFunctionProvider from SK 2025-11-27 17:14:47 +00:00
591 changed files with 9956 additions and 26384 deletions
@@ -1,17 +0,0 @@
---
applyTo: "dotnet/src/Microsoft.Agents.AI.DurableTask/**,dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/**"
---
# Durable Task area code instructions
The following guidelines apply to pull requests that modify files under
`dotnet/src/Microsoft.Agents.AI.DurableTask/**` or
`dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/**`:
## CHANGELOG.md
- Each pull request that modifies code should add just one bulleted entry to the `CHANGELOG.md` file containing a change title (usually the PR title) and a link to the PR itself.
- New PRs should be added to the top of the `CHANGELOG.md` file under a "## [Unreleased]" heading.
- If the PR is the first since the last release, the existing "## [Unreleased]" heading should be replaced with a "## v[X.Y.Z]" heading and the PRs since the last release should be added to the new "## [Unreleased]" heading.
- The style of new `CHANGELOG.md` entries should match the style of the other entries in the file.
- If the PR introduces a breaking change, the changelog entry should be prefixed with "[BREAKING]".
@@ -142,9 +142,9 @@ Replace these Semantic Kernel agent classes with their Agent Framework equivalen
|----------------------|----------------------------|-------------------|
| `IChatCompletionService` | `IChatClient` | Convert to `IChatClient` using `chatService.AsChatClient()` extensions |
| `ChatCompletionAgent` | `ChatClientAgent` | Remove `Kernel` parameter, add `IChatClient` parameter |
| `OpenAIAssistantAgent` | `AIAgent` (via extension) | ⚠️ **Deprecated** - Use Responses API instead. <br> **New**: `OpenAIClient.GetAssistantClient().CreateAIAgent()` <br> **Existing**: `OpenAIClient.GetAssistantClient().GetAIAgent(assistantId)` |
| `OpenAIAssistantAgent` | `AIAgent` (via extension) | **New**: `OpenAIClient.GetAssistantClient().CreateAIAgent()` <br> **Existing**: `OpenAIClient.GetAssistantClient().GetAIAgent(assistantId)` |
| `AzureAIAgent` | `AIAgent` (via extension) | **New**: `PersistentAgentsClient.CreateAIAgent()` <br> **Existing**: `PersistentAgentsClient.GetAIAgent(agentId)` |
| `OpenAIResponseAgent` | `AIAgent` (via extension) | Replace with `OpenAIClient.GetOpenAIResponseClient(modelId).CreateAIAgent()` |
| `OpenAIResponseAgent` | `AIAgent` (via extension) | Replace with `OpenAIClient.GetOpenAIResponseClient().CreateAIAgent()` |
| `A2AAgent` | `AIAgent` (via extension) | Replace with `A2ACardResolver.GetAIAgentAsync()` |
| `BedrockAgent` | Not supported | Custom implementation required |
@@ -529,14 +529,14 @@ AIAgent agent = new OpenAIClient(apiKey)
.CreateAIAgent(instructions: instructions);
```
**OpenAI Assistants (New):** ⚠️ *Deprecated - Use Responses API instead*
**OpenAI Assistants (New):**
```csharp
AIAgent agent = new OpenAIClient(apiKey)
.GetAssistantClient()
.CreateAIAgent(modelId, instructions: instructions);
```
**OpenAI Assistants (Existing):** ⚠️ *Deprecated - Use Responses API instead*
**OpenAI Assistants (Existing):**
```csharp
AIAgent agent = new OpenAIClient(apiKey)
.GetAssistantClient()
@@ -562,20 +562,6 @@ AIAgent agent = await new PersistentAgentsClient(endpoint, credential)
.GetAIAgentAsync(agentId);
```
**OpenAI Responses:** *(Recommended for OpenAI)*
```csharp
AIAgent agent = new OpenAIClient(apiKey)
.GetOpenAIResponseClient(modelId)
.CreateAIAgent(instructions: instructions);
```
**Azure OpenAI Responses:** *(Recommended for Azure OpenAI)*
```csharp
AIAgent agent = new AzureOpenAIClient(endpoint, credential)
.GetOpenAIResponseClient(deploymentName)
.CreateAIAgent(instructions: instructions);
```
**A2A:**
```csharp
A2ACardResolver resolver = new(new Uri(agentHost));
@@ -776,57 +762,35 @@ await foreach (var content in agent.InvokeAsync(userInput, thread))
**With this Agent Framework CodeInterpreter pattern:**
```csharp
using System.Text;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
var result = await agent.RunAsync(userInput, thread);
Console.WriteLine(result);
// Get the CodeInterpreterToolCallContent (code input)
CodeInterpreterToolCallContent? toolCallContent = result.Messages
.SelectMany(m => m.Contents)
.OfType<CodeInterpreterToolCallContent>()
.FirstOrDefault();
// Extract chat response MEAI type via first level breaking glass
var chatResponse = result.RawRepresentation as ChatResponse;
if (toolCallContent?.Inputs is not null)
// Extract underlying SDK updates via second level breaking glass
var underlyingStreamingUpdates = chatResponse?.RawRepresentation as IEnumerable<object?> ?? [];
StringBuilder generatedCode = new();
foreach (object? underlyingUpdate in underlyingStreamingUpdates ?? [])
{
DataContent? codeInput = toolCallContent.Inputs.OfType<DataContent>().FirstOrDefault();
if (codeInput?.HasTopLevelMediaType("text") ?? false)
if (underlyingUpdate is RunStepDetailsUpdate stepDetailsUpdate && stepDetailsUpdate.CodeInterpreterInput is not null)
{
Console.WriteLine($"Code Input: {Encoding.UTF8.GetString(codeInput.Data.ToArray())}");
generatedCode.Append(stepDetailsUpdate.CodeInterpreterInput);
}
}
// Get the CodeInterpreterToolResultContent (code output)
CodeInterpreterToolResultContent? toolResultContent = result.Messages
.SelectMany(m => m.Contents)
.OfType<CodeInterpreterToolResultContent>()
.FirstOrDefault();
if (toolResultContent?.Outputs is not null)
if (!string.IsNullOrEmpty(generatedCode.ToString()))
{
TextContent? resultOutput = toolResultContent.Outputs.OfType<TextContent>().FirstOrDefault();
if (resultOutput is not null)
{
Console.WriteLine($"Code Tool Result: {resultOutput.Text}");
}
}
// Getting any annotations generated by the tool
foreach (AIAnnotation annotation in result.Messages
.SelectMany(m => m.Contents)
.SelectMany(c => c.Annotations ?? []))
{
Console.WriteLine($"Annotation: {annotation}");
Console.WriteLine($"\n# {chatResponse?.Messages[0].Role}:Generated Code:\n{generatedCode}");
}
```
**Functional differences:**
1. Code interpreter content is now available via MEAI abstractions - no breaking glass required
2. Use `CodeInterpreterToolCallContent` to access code inputs (the generated code)
3. Use `CodeInterpreterToolResultContent` to access code outputs (execution results)
4. Annotations are accessible via `AIAnnotation` on content items
1. Code interpreter output is separate from text content, not a metadata property
2. Access code via `RunStepDetailsUpdate.CodeInterpreterInput` instead of metadata
3. Use breaking glass pattern to access underlying SDK objects
4. Process text content and code interpreter output independently
</behavioral_changes>
#### Provider-Specific Options Configuration
@@ -1016,8 +980,6 @@ AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential(
### 3. OpenAI Assistants Migration
> ⚠️ **DEPRECATION WARNING**: The OpenAI Assistants API has been deprecated. The Agent Framework extension methods for Assistants are marked as `[Obsolete]`. **Please use the Responses API instead** (see Section 6: OpenAI Responses Migration).
<configuration_changes>
**Remove Semantic Kernel Packages:**
```xml
@@ -1329,7 +1291,52 @@ var result = await agent.RunAsync(userInput, thread);
```
</api_changes>
### 8. Unsupported Providers (Require Custom Implementation)
### 8. A2A Migration
<configuration_changes>
**Remove Semantic Kernel Packages:**
```xml
<PackageReference Include="Microsoft.SemanticKernel.Agents.A2A" />
```
**Add Agent Framework Packages:**
```xml
<PackageReference Include="Microsoft.Agents.AI.A2A" />
```
</configuration_changes>
<api_changes>
**Replace this Semantic Kernel pattern:**
```csharp
using A2A;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.A2A;
using var httpClient = CreateHttpClient();
var client = new A2AClient(agentUrl, httpClient);
var cardResolver = new A2ACardResolver(url, httpClient);
var agentCard = await cardResolver.GetAgentCardAsync();
Console.WriteLine(JsonSerializer.Serialize(agentCard, s_jsonSerializerOptions));
var agent = new A2AAgent(client, agentCard);
```
**With this Agent Framework pattern:**
```csharp
using System;
using A2A;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.A2A;
// Initialize an A2ACardResolver to get an A2A agent card.
A2ACardResolver agentCardResolver = new(new Uri(a2aAgentHost));
// Create an instance of the AIAgent for an existing A2A agent specified by the agent card.
AIAgent agent = await agentCardResolver.GetAIAgentAsync();
```
</api_changes>
### 9. Unsupported Providers (Require Custom Implementation)
<behavioral_changes>
#### BedrockAgent Migration
@@ -1500,7 +1507,7 @@ Console.WriteLine(result);
```
</behavioral_changes>
### 9. Function Invocation Filtering
### 10. Function Invocation Filtering
**Invocation Context**
@@ -1608,4 +1615,25 @@ var filteredAgent = originalAgent
.Build();
```
### 11. Function Invocation Contexts
**Invocation Context**
Semantic Kernel's `IAutoFunctionInvocationFilter` provides a `AutoFunctionInvocationContext` where Agent Framework provides `FunctionInvocationContext`
The property mapping guide from a `AutoFunctionInvocationContext` to a `FunctionInvocationContext` is as follows:
| Semantic Kernel | Agent Framework |
| --- | --- |
| RequestSequenceIndex | Iteration |
| FunctionSequenceIndex | FunctionCallIndex |
| ToolCallId | CallContent.CallId |
| ChatMessageContent | Messages[0] |
| ExecutionSettings | Options |
| ChatHistory | Messages |
| Function | Function |
| Kernel | N/A |
| Result | Use `return` from the delegate |
| Terminate | Terminate |
| CancellationToken | provided via argument to middleware delegate |
| Arguments | Arguments |
+1 -1
View File
@@ -32,7 +32,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v5
with:
persist-credentials: false
+3 -3
View File
@@ -37,7 +37,7 @@ jobs:
outputs:
dotnetChanges: ${{ steps.filter.outputs.dotnet}}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: dorny/paths-filter@v3
id: filter
with:
@@ -68,7 +68,7 @@ jobs:
runs-on: ${{ matrix.os }}
environment: ${{ matrix.environment }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
with:
persist-credentials: false
sparse-checkout: |
@@ -217,7 +217,7 @@ jobs:
# Generate test reports and check coverage
- name: Generate test reports
if: matrix.targetFramework == env.COVERAGE_FRAMEWORK
uses: danielpalme/ReportGenerator-GitHub-Action@5.5.1
uses: danielpalme/ReportGenerator-GitHub-Action@5.5.0
with:
reports: "./TestResults/Coverage/**/coverage.cobertura.xml"
targetdir: "./TestResults/Reports"
+1 -1
View File
@@ -30,7 +30,7 @@ jobs:
steps:
- name: Check out code
uses: actions/checkout@v6
uses: actions/checkout@v5
with:
fetch-depth: 0
persist-credentials: false
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-22.04
# check out the latest version of the code
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
with:
persist-credentials: false
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
env:
UV_PYTHON: ${{ matrix.python-version }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Set up python and install the project
+1 -1
View File
@@ -24,7 +24,7 @@ jobs:
run:
working-directory: python
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- name: Set up uv
uses: astral-sh/setup-uv@v7
with:
+2 -2
View File
@@ -24,7 +24,7 @@ jobs:
outputs:
pythonChanges: ${{ steps.filter.outputs.python}}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: dorny/paths-filter@v3
id: filter
with:
@@ -59,7 +59,7 @@ jobs:
run:
working-directory: python
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- name: Set up python and install the project
id: python-setup
+3 -3
View File
@@ -28,7 +28,7 @@ jobs:
outputs:
pythonChanges: ${{ steps.filter.outputs.python}}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: dorny/paths-filter@v3
id: filter
with:
@@ -75,7 +75,7 @@ jobs:
run:
working-directory: python
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- name: Set up python and install the project
id: python-setup
uses: ./.github/actions/python-setup
@@ -135,7 +135,7 @@ jobs:
run:
working-directory: python
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- name: Set up python and install the project
id: python-setup
uses: ./.github/actions/python-setup
+1 -1
View File
@@ -23,7 +23,7 @@ jobs:
run:
working-directory: python
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- name: Set up python and install the project
id: python-setup
uses: ./.github/actions/python-setup
@@ -19,7 +19,7 @@ jobs:
run:
working-directory: python
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- name: Download coverage report
uses: actions/download-artifact@v6
with:
@@ -39,7 +39,7 @@ jobs:
echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_ENV
- name: Pytest coverage comment
id: coverageComment
uses: MishaKav/pytest-coverage-comment@v1.2.0
uses: MishaKav/pytest-coverage-comment@v1.1.59
with:
github-token: ${{ secrets.GH_ACTIONS_PR_WRITE }}
issue-number: ${{ env.PR_NUMBER }}
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
env:
UV_PYTHON: "3.10"
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
# Save the PR number to a file since the workflow_run event
# in the coverage report workflow does not have access to it
- name: Save PR number
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
run:
working-directory: python
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- name: Set up python and install the project
id: python-setup
uses: ./.github/actions/python-setup
+1 -1
View File
@@ -11,7 +11,7 @@ model:
topP: 0.95
connection:
kind: ApiKey
key: =Env.OPENAI_API_KEY
key: =Env.OPENAI_APIKEY
outputSchema:
properties:
language:
+1 -1
View File
@@ -11,7 +11,7 @@ model:
topP: 0.95
connection:
kind: ApiKey
key: =Env.OPENAI_API_KEY
key: =Env.OPENAI_APIKEY
outputSchema:
properties:
language:
+1 -1
View File
@@ -11,7 +11,7 @@ model:
topP: 0.95
connection:
kind: ApiKey
key: =Env.OPENAI_API_KEY
key: =Env.OPENAI_APIKEY
outputSchema:
properties:
language:
+2 -1
View File
@@ -3,7 +3,8 @@
<!-- Default properties inherited by all projects. Projects can override. -->
<RunAnalyzersDuringBuild>true</RunAnalyzersDuringBuild>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<AnalysisLevel>10.0-all</AnalysisLevel>
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
<AnalysisLevel>latest</AnalysisLevel>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
+14 -16
View File
@@ -7,7 +7,7 @@
</PropertyGroup>
<PropertyGroup>
<!-- Aspire -->
<AspireAppHostSdkVersion>13.0.2</AspireAppHostSdkVersion>
<AspireAppHostSdkVersion>13.0.1</AspireAppHostSdkVersion>
</PropertyGroup>
<ItemGroup>
<!-- Aspire.* -->
@@ -21,9 +21,9 @@
<!-- Azure.* -->
<PackageVersion Include="Azure.AI.Projects" Version="1.2.0-beta.3" />
<PackageVersion Include="Azure.AI.Projects.OpenAI" Version="1.0.0-beta.4" />
<PackageVersion Include="Azure.AI.Agents.Persistent" Version="1.2.0-beta.8" />
<PackageVersion Include="Azure.AI.OpenAI" Version="2.7.0-beta.2" />
<PackageVersion Include="Azure.Identity" Version="1.17.1" />
<PackageVersion Include="Azure.AI.Agents.Persistent" Version="1.2.0-beta.7" />
<PackageVersion Include="Azure.AI.OpenAI" Version="2.7.0-beta.1" />
<PackageVersion Include="Azure.Identity" Version="1.17.0" />
<PackageVersion Include="Azure.Monitor.OpenTelemetry.Exporter" Version="1.4.0" />
<!-- Google Gemini -->
<PackageVersion Include="Google.GenAI" Version="0.6.0" />
@@ -33,20 +33,19 @@
<!-- Newtonsoft.Json -->
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
<!-- System.* -->
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.1" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.0" />
<PackageVersion Include="Microsoft.Bcl.HashCode" Version="6.0.0" />
<PackageVersion Include="System.ClientModel" Version="1.8.1" />
<PackageVersion Include="System.CodeDom" Version="10.0.0" />
<PackageVersion Include="System.Collections.Immutable" Version="10.0.0" />
<PackageVersion Include="System.CommandLine" Version="2.0.0-rc.2.25502.107" />
<PackageVersion Include="System.Diagnostics.DiagnosticSource" Version="10.0.1" />
<PackageVersion Include="System.Diagnostics.DiagnosticSource" Version="10.0.0" />
<PackageVersion Include="System.Linq.AsyncEnumerable" Version="10.0.0" />
<PackageVersion Include="System.Net.Http.Json" Version="10.0.0" />
<PackageVersion Include="System.Net.ServerSentEvents" Version="10.0.0" />
<PackageVersion Include="System.Text.Json" Version="10.0.1" />
<PackageVersion Include="System.Threading.Channels" Version="10.0.1" />
<PackageVersion Include="System.Text.Json" Version="10.0.0" />
<PackageVersion Include="System.Threading.Channels" Version="10.0.0" />
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.6.3" />
<PackageVersion Include="System.Net.Security" Version="4.3.2" />
<!-- OpenTelemetry -->
<PackageVersion Include="OpenTelemetry" Version="1.13.1" />
<PackageVersion Include="OpenTelemetry.Api" Version="1.13.1" />
@@ -61,10 +60,10 @@
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />
<PackageVersion Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.0.0" />
<!-- Microsoft.Extensions.* -->
<PackageVersion Include="Microsoft.Extensions.AI" Version="10.1.0" />
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="10.1.0" />
<PackageVersion Include="Microsoft.Extensions.AI" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.AI.AzureAIInference" Version="10.0.0-preview.1.25559.3" />
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="10.1.0-preview.1.25608.1" />
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="10.0.1-preview.1.25571.5" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.0" />
@@ -72,11 +71,11 @@
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.ServiceDiscovery" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.VectorData.Abstractions" Version="9.7.0" />
@@ -101,8 +100,7 @@
<!-- MCP -->
<PackageVersion Include="ModelContextProtocol" Version="0.4.0-preview.3" />
<!-- Inference SDKs -->
<PackageVersion Include="Anthropic.SDK" Version="5.8.0" />
<PackageVersion Include="AWSSDK.Extensions.Bedrock.MEAI" Version="4.0.4.11" />
<PackageVersion Include="AWSSDK.Extensions.Bedrock.MEAI" Version="4.0.4.6" />
<PackageVersion Include="Microsoft.ML.OnnxRuntimeGenAI" Version="0.10.0" />
<PackageVersion Include="OllamaSharp" Version="5.4.8" />
<PackageVersion Include="OpenAI" Version="2.7.0" />
+1 -27
View File
@@ -84,29 +84,6 @@
<Folder Name="/Samples/GettingStarted/DeclarativeAgents/">
<Project Path="samples/GettingStarted/DeclarativeAgents/ChatClient/DeclarativeChatClientAgents.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/AGUI/">
<File Path="samples/GettingStarted/AGUI/README.md" />
</Folder>
<Folder Name="/Samples/GettingStarted/AGUI/Step01_GettingStarted/">
<Project Path="samples/GettingStarted/AGUI/Step01_GettingStarted/Client/Client.csproj" />
<Project Path="samples/GettingStarted/AGUI/Step01_GettingStarted/Server/Server.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/AGUI/Step02_BackendTools/">
<Project Path="samples/GettingStarted/AGUI/Step02_BackendTools/Client/Client.csproj" />
<Project Path="samples/GettingStarted/AGUI/Step02_BackendTools/Server/Server.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/AGUI/Step03_FrontendTools/">
<Project Path="samples/GettingStarted/AGUI/Step03_FrontendTools/Client/Client.csproj" />
<Project Path="samples/GettingStarted/AGUI/Step03_FrontendTools/Server/Server.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/AGUI/Step04_HumanInLoop/">
<Project Path="samples/GettingStarted/AGUI/Step04_HumanInLoop/Client/Client.csproj" />
<Project Path="samples/GettingStarted/AGUI/Step04_HumanInLoop/Server/Server.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/AGUI/Step05_StateManagement/">
<Project Path="samples/GettingStarted/AGUI/Step05_StateManagement/Client/Client.csproj" />
<Project Path="samples/GettingStarted/AGUI/Step05_StateManagement/Server/Server.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/DevUI/">
<File Path="samples/GettingStarted/DevUI/README.md" />
<Project Path="samples/GettingStarted/DevUI/DevUI_Step01_BasicUsage/DevUI_Step01_BasicUsage.csproj" />
@@ -127,9 +104,6 @@
<File Path="samples/GettingStarted/AgentWithOpenAI/README.md" />
<Project Path="samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step01_Running/Agent_OpenAI_Step01_Running.csproj" />
<Project Path="samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step02_Reasoning/Agent_OpenAI_Step02_Reasoning.csproj" />
<Project Path="samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step03_CreateFromChatClient/Agent_OpenAI_Step03_CreateFromChatClient.csproj" />
<Project Path="samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step04_CreateFromOpenAIResponseClient/Agent_OpenAI_Step04_CreateFromOpenAIResponseClient.csproj" />
<Project Path="samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step05_Conversation/Agent_OpenAI_Step05_Conversation.csproj" />
</Folder>
<Folder Name="/Samples/Purview/" />
<Folder Name="/Samples/Purview/AgentWithPurview/">
@@ -199,7 +173,7 @@
<Project Path="samples/GettingStarted/Workflows/Declarative/ToolApproval/ToolApproval.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/Workflows/Declarative/Examples/">
<File Path="../workflow-samples/CustomerSupport.yaml" />
<File Path="../workflow-samples/ConfirmInput.yaml" />
<File Path="../workflow-samples/DeepResearch.yaml" />
<File Path="../workflow-samples/Marketing.yaml" />
<File Path="../workflow-samples/MathChat.yaml" />
-31
View File
@@ -1,31 +0,0 @@
{
"solution": {
"path": "agent-framework-dotnet.slnx",
"projects": [
"src\\Microsoft.Agents.AI.A2A\\Microsoft.Agents.AI.A2A.csproj",
"src\\Microsoft.Agents.AI.Abstractions\\Microsoft.Agents.AI.Abstractions.csproj",
"src\\Microsoft.Agents.AI.AGUI\\Microsoft.Agents.AI.AGUI.csproj",
"src\\Microsoft.Agents.AI.Anthropic\\Microsoft.Agents.AI.Anthropic.csproj",
"src\\Microsoft.Agents.AI.AzureAI.Persistent\\Microsoft.Agents.AI.AzureAI.Persistent.csproj",
"src\\Microsoft.Agents.AI.AzureAI\\Microsoft.Agents.AI.AzureAI.csproj",
"src\\Microsoft.Agents.AI.CopilotStudio\\Microsoft.Agents.AI.CopilotStudio.csproj",
"src\\Microsoft.Agents.AI.CosmosNoSql\\Microsoft.Agents.AI.CosmosNoSql.csproj",
"src\\Microsoft.Agents.AI.Declarative\\Microsoft.Agents.AI.Declarative.csproj",
"src\\Microsoft.Agents.AI.DevUI\\Microsoft.Agents.AI.DevUI.csproj",
"src\\Microsoft.Agents.AI.DurableTask\\Microsoft.Agents.AI.DurableTask.csproj",
"src\\Microsoft.Agents.AI.Hosting.A2A.AspNetCore\\Microsoft.Agents.AI.Hosting.A2A.AspNetCore.csproj",
"src\\Microsoft.Agents.AI.Hosting.A2A\\Microsoft.Agents.AI.Hosting.A2A.csproj",
"src\\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore\\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.csproj",
"src\\Microsoft.Agents.AI.Hosting.AzureFunctions\\Microsoft.Agents.AI.Hosting.AzureFunctions.csproj",
"src\\Microsoft.Agents.AI.Hosting.OpenAI\\Microsoft.Agents.AI.Hosting.OpenAI.csproj",
"src\\Microsoft.Agents.AI.Hosting\\Microsoft.Agents.AI.Hosting.csproj",
"src\\Microsoft.Agents.AI.Mem0\\Microsoft.Agents.AI.Mem0.csproj",
"src\\Microsoft.Agents.AI.OpenAI\\Microsoft.Agents.AI.OpenAI.csproj",
"src\\Microsoft.Agents.AI.Purview\\Microsoft.Agents.AI.Purview.csproj",
"src\\Microsoft.Agents.AI.Workflows.Declarative.AzureAI\\Microsoft.Agents.AI.Workflows.Declarative.AzureAI.csproj",
"src\\Microsoft.Agents.AI.Workflows.Declarative\\Microsoft.Agents.AI.Workflows.Declarative.csproj",
"src\\Microsoft.Agents.AI.Workflows\\Microsoft.Agents.AI.Workflows.csproj",
"src\\Microsoft.Agents.AI\\Microsoft.Agents.AI.csproj"
]
}
}
+3 -3
View File
@@ -2,9 +2,9 @@
<PropertyGroup>
<!-- Central version prefix - applies to all nuget packages. -->
<VersionPrefix>1.0.0</VersionPrefix>
<PackageVersion Condition="'$(VersionSuffix)' != ''">$(VersionPrefix)-$(VersionSuffix).251204.1</PackageVersion>
<PackageVersion Condition="'$(VersionSuffix)' == ''">$(VersionPrefix)-preview.251204.1</PackageVersion>
<GitTag>1.0.0-preview.251204.1</GitTag>
<PackageVersion Condition="'$(VersionSuffix)' != ''">$(VersionPrefix)-$(VersionSuffix).251125.1</PackageVersion>
<PackageVersion Condition="'$(VersionSuffix)' == ''">$(VersionPrefix)-preview.251125.1</PackageVersion>
<GitTag>1.0.0-preview.251125.1</GitTag>
<Configurations>Debug;Release;Publish</Configurations>
<IsPackable>true</IsPackable>
-1
View File
@@ -1,7 +1,6 @@
# Suppressing errors for Sample projects under dotnet/samples folder
[*.cs]
dotnet_diagnostic.CA1716.severity = none # Add summary to documentation comment.
dotnet_diagnostic.CA1873.severity = none # Evaluation of logging arguments may be expensive
dotnet_diagnostic.CA2000.severity = none # Call System.IDisposable.Dispose on object before all references to it are out of scope
dotnet_diagnostic.CA2007.severity = none # Do not directly await a Task
@@ -14,6 +14,11 @@
<PackageReference Include="Microsoft.Extensions.Hosting" />
</ItemGroup>
<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible($(TargetFramework), 'net10.0'))">
<PackageReference Include="System.Net.ServerSentEvents" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.A2A\Microsoft.Agents.AI.A2A.csproj" />
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.Abstractions\Microsoft.Agents.AI.Abstractions.csproj" />
@@ -4,7 +4,6 @@ using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;
using OpenAI;
using OpenAI.Chat;
namespace A2A;
@@ -6,7 +6,6 @@ using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using OpenAI;
using OpenAI.Chat;
namespace A2AServer;
@@ -1,10 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text.Json.Serialization;
using AGUIDojoServer.AgenticUI;
using AGUIDojoServer.BackendToolRendering;
using AGUIDojoServer.PredictiveStateUpdates;
using AGUIDojoServer.SharedState;
namespace AGUIDojoServer;
@@ -12,12 +8,4 @@ namespace AGUIDojoServer;
[JsonSerializable(typeof(Recipe))]
[JsonSerializable(typeof(Ingredient))]
[JsonSerializable(typeof(RecipeResponse))]
[JsonSerializable(typeof(Plan))]
[JsonSerializable(typeof(Step))]
[JsonSerializable(typeof(StepStatus))]
[JsonSerializable(typeof(StepStatus?))]
[JsonSerializable(typeof(JsonPatchOperation))]
[JsonSerializable(typeof(List<JsonPatchOperation>))]
[JsonSerializable(typeof(List<string>))]
[JsonSerializable(typeof(DocumentState))]
internal sealed partial class AGUIDojoServerSerializerContext : JsonSerializerContext;
@@ -1,52 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ComponentModel;
namespace AGUIDojoServer.AgenticUI;
internal static class AgenticPlanningTools
{
[Description("Create a plan with multiple steps.")]
public static Plan CreatePlan([Description("List of step descriptions to create the plan.")] List<string> steps)
{
return new Plan
{
Steps = [.. steps.Select(s => new Step { Description = s, Status = StepStatus.Pending })]
};
}
[Description("Update a step in the plan with new description or status.")]
public static async Task<List<JsonPatchOperation>> UpdatePlanStepAsync(
[Description("The index of the step to update.")] int index,
[Description("The new description for the step (optional).")] string? description = null,
[Description("The new status for the step (optional).")] StepStatus? status = null)
{
var changes = new List<JsonPatchOperation>();
if (description is not null)
{
changes.Add(new JsonPatchOperation
{
Op = "replace",
Path = $"/steps/{index}/description",
Value = description
});
}
if (status.HasValue)
{
// Status must be lowercase to match AG-UI frontend expectations: "pending" or "completed"
string statusValue = status.Value == StepStatus.Pending ? "pending" : "completed";
changes.Add(new JsonPatchOperation
{
Op = "replace",
Path = $"/steps/{index}/status",
Value = statusValue
});
}
await Task.Delay(1000);
return changes;
}
}
@@ -1,88 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Text.Json;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
namespace AGUIDojoServer.AgenticUI;
[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Instantiated by ChatClientAgentFactory.CreateAgenticUI")]
internal sealed class AgenticUIAgent : DelegatingAIAgent
{
private readonly JsonSerializerOptions _jsonSerializerOptions;
public AgenticUIAgent(AIAgent innerAgent, JsonSerializerOptions jsonSerializerOptions)
: base(innerAgent)
{
this._jsonSerializerOptions = jsonSerializerOptions;
}
public override Task<AgentRunResponse> RunAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
{
return this.RunStreamingAsync(messages, thread, options, cancellationToken).ToAgentRunResponseAsync(cancellationToken);
}
public override async IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(
IEnumerable<ChatMessage> messages,
AgentThread? thread = null,
AgentRunOptions? options = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
// Track function calls that should trigger state events
var trackedFunctionCalls = new Dictionary<string, FunctionCallContent>();
await foreach (var update in this.InnerAgent.RunStreamingAsync(messages, thread, options, cancellationToken).ConfigureAwait(false))
{
// Process contents: track function calls and emit state events for results
List<AIContent> stateEventsToEmit = new();
foreach (var content in update.Contents)
{
if (content is FunctionCallContent callContent)
{
if (callContent.Name == "create_plan" || callContent.Name == "update_plan_step")
{
trackedFunctionCalls[callContent.CallId] = callContent;
break;
}
}
else if (content is FunctionResultContent resultContent)
{
// Check if this result matches a tracked function call
if (trackedFunctionCalls.TryGetValue(resultContent.CallId, out var matchedCall))
{
var bytes = JsonSerializer.SerializeToUtf8Bytes((JsonElement)resultContent.Result!, this._jsonSerializerOptions);
// Determine event type based on the function name
if (matchedCall.Name == "create_plan")
{
stateEventsToEmit.Add(new DataContent(bytes, "application/json"));
}
else if (matchedCall.Name == "update_plan_step")
{
stateEventsToEmit.Add(new DataContent(bytes, "application/json-patch+json"));
}
}
}
}
yield return update;
yield return new AgentRunResponseUpdate(
new ChatResponseUpdate(role: ChatRole.System, stateEventsToEmit)
{
MessageId = "delta_" + Guid.NewGuid().ToString("N"),
CreatedAt = update.CreatedAt,
ResponseId = update.ResponseId,
AuthorName = update.AuthorName,
Role = update.Role,
ContinuationToken = update.ContinuationToken,
AdditionalProperties = update.AdditionalProperties,
})
{
AgentId = update.AgentId
};
}
}
}
@@ -1,20 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text.Json.Serialization;
namespace AGUIDojoServer.AgenticUI;
internal sealed class JsonPatchOperation
{
[JsonPropertyName("op")]
public required string Op { get; set; }
[JsonPropertyName("path")]
public required string Path { get; set; }
[JsonPropertyName("value")]
public object? Value { get; set; }
[JsonPropertyName("from")]
public string? From { get; set; }
}
@@ -1,11 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text.Json.Serialization;
namespace AGUIDojoServer.AgenticUI;
internal sealed class Plan
{
[JsonPropertyName("steps")]
public List<Step> Steps { get; set; } = [];
}
@@ -1,14 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text.Json.Serialization;
namespace AGUIDojoServer.AgenticUI;
internal sealed class Step
{
[JsonPropertyName("description")]
public required string Description { get; set; }
[JsonPropertyName("status")]
public StepStatus Status { get; set; } = StepStatus.Pending;
}
@@ -1,12 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text.Json.Serialization;
namespace AGUIDojoServer.AgenticUI;
[JsonConverter(typeof(JsonStringEnumConverter<StepStatus>))]
internal enum StepStatus
{
Pending,
Completed
}
@@ -2,10 +2,6 @@
using System.ComponentModel;
using System.Text.Json;
using AGUIDojoServer.AgenticUI;
using AGUIDojoServer.BackendToolRendering;
using AGUIDojoServer.PredictiveStateUpdates;
using AGUIDojoServer.SharedState;
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
@@ -70,46 +66,13 @@ internal static class ChatClientAgentFactory
description: "An agent that uses tools to generate user interfaces using Azure OpenAI");
}
public static AIAgent CreateAgenticUI(JsonSerializerOptions options)
public static ChatClientAgent CreateAgenticUI()
{
ChatClient chatClient = s_azureOpenAIClient!.GetChatClient(s_deploymentName!);
var baseAgent = chatClient.AsIChatClient().CreateAIAgent(new ChatClientAgentOptions
{
Name = "AgenticUIAgent",
Description = "An agent that generates agentic user interfaces using Azure OpenAI",
ChatOptions = new ChatOptions
{
Instructions = """
When planning use tools only, without any other messages.
IMPORTANT:
- Use the `create_plan` tool to set the initial state of the steps
- Use the `update_plan_step` tool to update the status of each step
- Do NOT repeat the plan or summarise it in a message
- Do NOT confirm the creation or updates in a message
- Do NOT ask the user for additional information or next steps
- Do NOT leave a plan hanging, always complete the plan via `update_plan_step` if one is ongoing.
- Continue calling update_plan_step until all steps are marked as completed.
Only one plan can be active at a time, so do not call the `create_plan` tool
again until all the steps in current plan are completed.
""",
Tools = [
AIFunctionFactory.Create(
AgenticPlanningTools.CreatePlan,
name: "create_plan",
description: "Create a plan with multiple steps.",
AGUIDojoServerSerializerContext.Default.Options),
AIFunctionFactory.Create(
AgenticPlanningTools.UpdatePlanStepAsync,
name: "update_plan_step",
description: "Update a step in the plan with new description or status.",
AGUIDojoServerSerializerContext.Default.Options)
],
AllowMultipleToolCalls = false
}
});
return new AgenticUIAgent(baseAgent, options);
return chatClient.AsIChatClient().CreateAIAgent(
name: "AgenticUIAgent",
description: "An agent that generates agentic user interfaces using Azure OpenAI");
}
public static AIAgent CreateSharedState(JsonSerializerOptions options)
@@ -123,44 +86,6 @@ internal static class ChatClientAgentFactory
return new SharedStateAgent(baseAgent, options);
}
public static AIAgent CreatePredictiveStateUpdates(JsonSerializerOptions options)
{
ChatClient chatClient = s_azureOpenAIClient!.GetChatClient(s_deploymentName!);
var baseAgent = chatClient.AsIChatClient().CreateAIAgent(new ChatClientAgentOptions
{
Name = "PredictiveStateUpdatesAgent",
Description = "An agent that demonstrates predictive state updates using Azure OpenAI",
ChatOptions = new ChatOptions
{
Instructions = """
You are a document editor assistant. When asked to write or edit content:
IMPORTANT:
- Use the `write_document` tool with the full document text in Markdown format
- Format the document extensively so it's easy to read
- You can use all kinds of markdown (headings, lists, bold, etc.)
- However, do NOT use italic or strike-through formatting
- You MUST write the full document, even when changing only a few words
- When making edits to the document, try to make them minimal - do not change every word
- Keep stories SHORT!
- After you are done writing the document you MUST call a confirm_changes tool after you call write_document
After the user confirms the changes, provide a brief summary of what you wrote.
""",
Tools = [
AIFunctionFactory.Create(
WriteDocument,
name: "write_document",
description: "Write a document. Use markdown formatting to format the document.",
AGUIDojoServerSerializerContext.Default.Options)
]
}
});
return new PredictiveStateUpdatesAgent(baseAgent, options);
}
[Description("Get the weather for a given location.")]
private static WeatherInfo GetWeather([Description("The location to get the weather for.")] string location) => new()
{
@@ -170,11 +95,4 @@ internal static class ChatClientAgentFactory
WindSpeed = 10,
FeelsLike = 25
};
[Description("Write a document in markdown format.")]
private static string WriteDocument([Description("The document content to write.")] string document)
{
// Simply return success - the document is tracked via state updates
return "Document written successfully";
}
}
@@ -2,7 +2,7 @@
using System.Text.Json.Serialization;
namespace AGUIDojoServer.SharedState;
namespace AGUIDojoServer;
internal sealed class Ingredient
{
@@ -1,11 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text.Json.Serialization;
namespace AGUIDojoServer.PredictiveStateUpdates;
internal sealed class DocumentState
{
[JsonPropertyName("document")]
public string Document { get; set; } = string.Empty;
}
@@ -1,104 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Text.Json;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
namespace AGUIDojoServer.PredictiveStateUpdates;
[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Instantiated by ChatClientAgentFactory.CreatePredictiveStateUpdates")]
internal sealed class PredictiveStateUpdatesAgent : DelegatingAIAgent
{
private readonly JsonSerializerOptions _jsonSerializerOptions;
private const int ChunkSize = 10; // Characters per chunk for streaming effect
public PredictiveStateUpdatesAgent(AIAgent innerAgent, JsonSerializerOptions jsonSerializerOptions)
: base(innerAgent)
{
this._jsonSerializerOptions = jsonSerializerOptions;
}
public override Task<AgentRunResponse> RunAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
{
return this.RunStreamingAsync(messages, thread, options, cancellationToken).ToAgentRunResponseAsync(cancellationToken);
}
public override async IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(
IEnumerable<ChatMessage> messages,
AgentThread? thread = null,
AgentRunOptions? options = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
// Track the last emitted document state to avoid duplicates
string? lastEmittedDocument = null;
await foreach (var update in this.InnerAgent.RunStreamingAsync(messages, thread, options, cancellationToken).ConfigureAwait(false))
{
// Check if we're seeing a write_document tool call and emit predictive state
bool hasToolCall = false;
string? documentContent = null;
foreach (var content in update.Contents)
{
if (content is FunctionCallContent callContent && callContent.Name == "write_document")
{
hasToolCall = true;
// Try to extract the document argument directly from the dictionary
if (callContent.Arguments?.TryGetValue("document", out var documentValue) == true)
{
documentContent = documentValue?.ToString();
}
}
}
// Always yield the original update first
yield return update;
// If we got a complete tool call with document content, "fake" stream it in chunks
if (hasToolCall && documentContent != null && documentContent != lastEmittedDocument)
{
// Chunk the document content and emit progressive state updates
int startIndex = 0;
if (lastEmittedDocument != null && documentContent.StartsWith(lastEmittedDocument, StringComparison.Ordinal))
{
// Only stream the new portion that was added
startIndex = lastEmittedDocument.Length;
}
// Stream the document in chunks
for (int i = startIndex; i < documentContent.Length; i += ChunkSize)
{
int length = Math.Min(ChunkSize, documentContent.Length - i);
string chunk = documentContent.Substring(0, i + length);
// Prepare predictive state update as DataContent
var stateUpdate = new DocumentState { Document = chunk };
byte[] stateBytes = JsonSerializer.SerializeToUtf8Bytes(
stateUpdate,
this._jsonSerializerOptions.GetTypeInfo(typeof(DocumentState)));
yield return new AgentRunResponseUpdate(
new ChatResponseUpdate(role: ChatRole.Assistant, [new DataContent(stateBytes, "application/json")])
{
MessageId = "snapshot" + Guid.NewGuid().ToString("N"),
CreatedAt = update.CreatedAt,
ResponseId = update.ResponseId,
AdditionalProperties = update.AdditionalProperties,
AuthorName = update.AuthorName,
ContinuationToken = update.ContinuationToken,
})
{
AgentId = update.AgentId
};
// Small delay to simulate streaming
await Task.Delay(50, cancellationToken).ConfigureAwait(false);
}
lastEmittedDocument = documentContent;
}
}
}
}
@@ -35,13 +35,11 @@ app.MapAGUI("/human_in_the_loop", ChatClientAgentFactory.CreateHumanInTheLoop())
app.MapAGUI("/tool_based_generative_ui", ChatClientAgentFactory.CreateToolBasedGenerativeUI());
app.MapAGUI("/agentic_generative_ui", ChatClientAgentFactory.CreateAgenticUI());
var jsonOptions = app.Services.GetRequiredService<IOptions<Microsoft.AspNetCore.Http.Json.JsonOptions>>();
app.MapAGUI("/agentic_generative_ui", ChatClientAgentFactory.CreateAgenticUI(jsonOptions.Value.SerializerOptions));
app.MapAGUI("/shared_state", ChatClientAgentFactory.CreateSharedState(jsonOptions.Value.SerializerOptions));
app.MapAGUI("/predictive_state_updates", ChatClientAgentFactory.CreatePredictiveStateUpdates(jsonOptions.Value.SerializerOptions));
await app.RunAsync();
public partial class Program;
@@ -2,7 +2,7 @@
using System.Text.Json.Serialization;
namespace AGUIDojoServer.SharedState;
namespace AGUIDojoServer;
internal sealed class Recipe
{
@@ -2,7 +2,7 @@
using System.Text.Json.Serialization;
namespace AGUIDojoServer.SharedState;
namespace AGUIDojoServer;
#pragma warning disable CA1812 // Used for the JsonSchema response format
internal sealed class RecipeResponse
@@ -6,7 +6,7 @@ using System.Text.Json;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
namespace AGUIDojoServer.SharedState;
namespace AGUIDojoServer;
[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Instantiated by ChatClientAgentFactory.CreateSharedState")]
internal sealed class SharedStateAgent : DelegatingAIAgent
@@ -2,7 +2,7 @@
using System.Text.Json.Serialization;
namespace AGUIDojoServer.BackendToolRendering;
namespace AGUIDojoServer;
internal sealed class WeatherInfo
{
@@ -6,7 +6,7 @@ using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI.Hosting.AGUI.AspNetCore;
using Microsoft.Extensions.AI;
using OpenAI.Chat;
using OpenAI;
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient().AddLogging();
@@ -1,14 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFrameworks>net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.AGUI\Microsoft.Agents.AI.AGUI.csproj" />
</ItemGroup>
</Project>
@@ -1,23 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<base href="/" />
<link rel="stylesheet" href="app.css" />
<link rel="stylesheet" href="AGUIWebChatClient.styles.css" />
<link rel="icon" type="image/png" href="favicon.png" />
<HeadOutlet @rendermode="@renderMode" />
</head>
<body>
<Routes @rendermode="@renderMode" />
<script src="_framework/blazor.web.js"></script>
</body>
</html>
@code {
private readonly IComponentRenderMode renderMode = new InteractiveServerRenderMode(prerender: false);
}
@@ -1 +0,0 @@
<div class="lds-ellipsis"><div></div><div></div><div></div><div></div></div>
@@ -1,89 +0,0 @@
/* Used under CC0 license */
.lds-ellipsis {
color: #666;
animation: fade-in 1s;
}
@keyframes fade-in {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.lds-ellipsis,
.lds-ellipsis div {
box-sizing: border-box;
}
.lds-ellipsis {
margin: auto;
display: block;
position: relative;
width: 80px;
height: 80px;
}
.lds-ellipsis div {
position: absolute;
top: 33.33333px;
width: 10px;
height: 10px;
border-radius: 50%;
background: currentColor;
animation-timing-function: cubic-bezier(0, 1, 1, 0);
}
.lds-ellipsis div:nth-child(1) {
left: 8px;
animation: lds-ellipsis1 0.6s infinite;
}
.lds-ellipsis div:nth-child(2) {
left: 8px;
animation: lds-ellipsis2 0.6s infinite;
}
.lds-ellipsis div:nth-child(3) {
left: 32px;
animation: lds-ellipsis2 0.6s infinite;
}
.lds-ellipsis div:nth-child(4) {
left: 56px;
animation: lds-ellipsis3 0.6s infinite;
}
@keyframes lds-ellipsis1 {
0% {
transform: scale(0);
}
100% {
transform: scale(1);
}
}
@keyframes lds-ellipsis3 {
0% {
transform: scale(1);
}
100% {
transform: scale(0);
}
}
@keyframes lds-ellipsis2 {
0% {
transform: translate(0, 0);
}
100% {
transform: translate(24px, 0);
}
}
@@ -1,9 +0,0 @@
@inherits LayoutComponentBase
@Body
<div id="blazor-error-ui" data-nosnippet>
An unhandled error has occurred.
<a href="." class="reload">Reload</a>
<span class="dismiss">đź—™</span>
</div>
@@ -1,20 +0,0 @@
#blazor-error-ui {
color-scheme: light only;
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
box-sizing: border-box;
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
}
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}
@@ -1,94 +0,0 @@
@page "/"
@using System.ComponentModel
@inject IChatClient ChatClient
@inject NavigationManager Nav
@implements IDisposable
<PageTitle>Chat</PageTitle>
<ChatHeader OnNewChat="@ResetConversationAsync" />
<ChatMessageList Messages="@messages" InProgressMessage="@currentResponseMessage">
<NoMessagesContent>
<div>Ask the assistant a question to start a conversation.</div>
</NoMessagesContent>
</ChatMessageList>
<div class="chat-container">
<ChatSuggestions OnSelected="@AddUserMessageAsync" @ref="@chatSuggestions" />
<ChatInput OnSend="@AddUserMessageAsync" @ref="@chatInput" />
</div>
@code {
private const string SystemPrompt = @"
You are a helpful assistant.
";
private int statefulMessageCount;
private readonly ChatOptions chatOptions = new();
private readonly List<ChatMessage> messages = new();
private CancellationTokenSource? currentResponseCancellation;
private ChatMessage? currentResponseMessage;
private ChatInput? chatInput;
private ChatSuggestions? chatSuggestions;
protected override void OnInitialized()
{
statefulMessageCount = 0;
messages.Add(new(ChatRole.System, SystemPrompt));
}
private async Task AddUserMessageAsync(ChatMessage userMessage)
{
CancelAnyCurrentResponse();
// Add the user message to the conversation
messages.Add(userMessage);
chatSuggestions?.Clear();
await chatInput!.FocusAsync();
// Stream and display a new response from the IChatClient
var responseText = new TextContent("");
currentResponseMessage = new ChatMessage(ChatRole.Assistant, [responseText]);
StateHasChanged();
currentResponseCancellation = new();
await foreach (var update in ChatClient.GetStreamingResponseAsync(messages.Skip(statefulMessageCount), chatOptions, currentResponseCancellation.Token))
{
messages.AddMessages(update, filter: c => c is not TextContent);
responseText.Text += update.Text;
chatOptions.ConversationId = update.ConversationId;
ChatMessageItem.NotifyChanged(currentResponseMessage);
}
// Store the final response in the conversation, and begin getting suggestions
messages.Add(currentResponseMessage!);
statefulMessageCount = chatOptions.ConversationId is not null ? messages.Count : 0;
currentResponseMessage = null;
chatSuggestions?.Update(messages);
}
private void CancelAnyCurrentResponse()
{
// If a response was cancelled while streaming, include it in the conversation so it's not lost
if (currentResponseMessage is not null)
{
messages.Add(currentResponseMessage);
}
currentResponseCancellation?.Cancel();
currentResponseMessage = null;
}
private async Task ResetConversationAsync()
{
CancelAnyCurrentResponse();
messages.Clear();
messages.Add(new(ChatRole.System, SystemPrompt));
chatOptions.ConversationId = null;
statefulMessageCount = 0;
chatSuggestions?.Clear();
await chatInput!.FocusAsync();
}
public void Dispose()
=> currentResponseCancellation?.Cancel();
}
@@ -1,11 +0,0 @@
.chat-container {
position: sticky;
bottom: 0;
padding-left: 1.5rem;
padding-right: 1.5rem;
padding-top: 0.75rem;
padding-bottom: 1.5rem;
border-top-width: 1px;
background-color: #F3F4F6;
border-color: #E5E7EB;
}
@@ -1,38 +0,0 @@
@using System.Web
@if (!string.IsNullOrWhiteSpace(viewerUrl))
{
<a href="@viewerUrl" target="_blank" class="citation">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z" />
</svg>
<div class="citation-content">
<div class="citation-file">@File</div>
<div>@Quote</div>
</div>
</a>
}
@code {
[Parameter]
public required string File { get; set; }
[Parameter]
public int? PageNumber { get; set; }
[Parameter]
public required string Quote { get; set; }
private string? viewerUrl;
protected override void OnParametersSet()
{
viewerUrl = null;
// If you ingest other types of content besides PDF files, construct a URL to an appropriate viewer here
if (File.EndsWith(".pdf"))
{
var search = Quote?.Trim('.', ',', ' ', '\n', '\r', '\t', '"', '\'');
viewerUrl = $"lib/pdf_viewer/viewer.html?file=/Data/{HttpUtility.UrlEncode(File)}#page={PageNumber}&search={HttpUtility.UrlEncode(search)}&phrase=true";
}
}
}
@@ -1,37 +0,0 @@
.citation {
display: inline-flex;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
padding-left: 0.75rem;
padding-right: 0.75rem;
margin-top: 1rem;
margin-right: 1rem;
border-bottom: 2px solid #a770de;
gap: 0.5rem;
border-radius: 0.25rem;
font-size: 0.875rem;
line-height: 1.25rem;
background-color: #ffffff;
}
.citation[href]:hover {
outline: 1px solid #865cb1;
}
.citation svg {
width: 1.5rem;
height: 1.5rem;
}
.citation:active {
background-color: rgba(0,0,0,0.05);
}
.citation-content {
display: flex;
flex-direction: column;
}
.citation-file {
font-weight: 600;
}
@@ -1,17 +0,0 @@
<div class="chat-header-container main-background-gradient">
<div class="chat-header-controls page-width">
<button class="btn-default" @onclick="@OnNewChat">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="new-chat-icon">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
</svg>
New chat
</button>
</div>
<h1 class="page-width">AGUI WebChat</h1>
</div>
@code {
[Parameter]
public EventCallback OnNewChat { get; set; }
}
@@ -1,25 +0,0 @@
.chat-header-container {
top: 0;
padding: 1.5rem;
}
.chat-header-controls {
margin-bottom: 1.5rem;
}
h1 {
overflow: hidden;
text-overflow: ellipsis;
}
.new-chat-icon {
width: 1.25rem;
height: 1.25rem;
color: rgb(55, 65, 81);
}
@media (min-width: 768px) {
.chat-header-container {
position: sticky;
}
}
@@ -1,51 +0,0 @@
@inject IJSRuntime JS
<EditForm Model="@this" OnValidSubmit="@SendMessageAsync">
<label class="input-box page-width">
<textarea @ref="@textArea" @bind="@messageText" placeholder="Type your message..." rows="1"></textarea>
<div class="tools">
<button type="submit" title="Send" class="send-button">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="tool-icon">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 12 3.269 3.125A59.769 59.769 0 0 1 21.485 12 59.768 59.768 0 0 1 3.27 20.875L5.999 12Zm0 0h7.5" />
</svg>
</button>
</div>
</label>
</EditForm>
@code {
private ElementReference textArea;
private string? messageText;
[Parameter]
public EventCallback<ChatMessage> OnSend { get; set; }
public ValueTask FocusAsync()
=> textArea.FocusAsync();
private async Task SendMessageAsync()
{
if (messageText is { Length: > 0 } text)
{
messageText = null;
await OnSend.InvokeAsync(new ChatMessage(ChatRole.User, text));
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
try
{
var module = await JS.InvokeAsync<IJSObjectReference>("import", "./Components/Pages/Chat/ChatInput.razor.js");
await module.InvokeVoidAsync("init", textArea);
await module.DisposeAsync();
}
catch (JSDisconnectedException)
{
}
}
}
}
@@ -1,57 +0,0 @@
.input-box {
display: flex;
flex-direction: column;
background: white;
border: 1px solid rgb(229, 231, 235);
border-radius: 8px;
padding: 0.5rem 0.75rem;
margin-top: 0.75rem;
}
.input-box:focus-within {
outline: 2px solid #4152d5;
}
textarea {
resize: none;
border: none;
outline: none;
flex-grow: 1;
}
textarea:placeholder-shown + .tools {
--send-button-color: #aaa;
}
.tools {
display: flex;
margin-top: 1rem;
align-items: center;
}
.tool-icon {
width: 1.25rem;
height: 1.25rem;
}
.send-button {
color: var(--send-button-color);
margin-left: auto;
}
.send-button:hover {
color: black;
}
.attach {
background-color: white;
border-style: dashed;
color: #888;
border-color: #888;
padding: 3px 8px;
}
.attach:hover {
background-color: #f0f0f0;
color: black;
}
@@ -1,43 +0,0 @@
export function init(elem) {
elem.focus();
// Auto-resize whenever the user types or if the value is set programmatically
elem.addEventListener('input', () => resizeToFit(elem));
afterPropertyWritten(elem, 'value', () => resizeToFit(elem));
// Auto-submit the form on 'enter' keypress
elem.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
elem.dispatchEvent(new CustomEvent('change', { bubbles: true }));
elem.closest('form').dispatchEvent(new CustomEvent('submit', { bubbles: true, cancelable: true }));
}
});
}
function resizeToFit(elem) {
const lineHeight = parseFloat(getComputedStyle(elem).lineHeight);
elem.rows = 1;
const numLines = Math.ceil(elem.scrollHeight / lineHeight);
elem.rows = Math.min(5, Math.max(1, numLines));
}
function afterPropertyWritten(target, propName, callback) {
const descriptor = getPropertyDescriptor(target, propName);
Object.defineProperty(target, propName, {
get: function () {
return descriptor.get.apply(this, arguments);
},
set: function () {
const result = descriptor.set.apply(this, arguments);
callback();
return result;
}
});
}
function getPropertyDescriptor(target, propertyName) {
return Object.getOwnPropertyDescriptor(target, propertyName)
|| getPropertyDescriptor(Object.getPrototypeOf(target), propertyName);
}
@@ -1,73 +0,0 @@
@using System.Runtime.CompilerServices
@using System.Text.RegularExpressions
@using System.Linq
@if (Message.Role == ChatRole.User)
{
<div class="user-message">
@Message.Text
</div>
}
else if (Message.Role == ChatRole.Assistant)
{
foreach (var content in Message.Contents)
{
if (content is TextContent { Text: { Length: > 0 } text })
{
<div class="assistant-message">
<div>
<div class="assistant-message-icon">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 18v-5.25m0 0a6.01 6.01 0 0 0 1.5-.189m-1.5.189a6.01 6.01 0 0 1-1.5-.189m3.75 7.478a12.06 12.06 0 0 1-4.5 0m3.75 2.383a14.406 14.406 0 0 1-3 0M14.25 18v-.192c0-.983.658-1.823 1.508-2.316a7.5 7.5 0 1 0-7.517 0c.85.493 1.509 1.333 1.509 2.316V18" />
</svg>
</div>
</div>
<div class="assistant-message-header">Assistant</div>
<div class="assistant-message-text">
<div>@((MarkupString)text)</div>
</div>
</div>
}
else if (content is FunctionCallContent { Name: "Search" } fcc && fcc.Arguments?.TryGetValue("searchPhrase", out var searchPhrase) is true)
{
<div class="assistant-search">
<div class="assistant-search-icon">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" />
</svg>
</div>
<div class="assistant-search-content">
Searching:
<span class="assistant-search-phrase">@searchPhrase</span>
@if (fcc.Arguments?.TryGetValue("filenameFilter", out var filenameObj) is true && filenameObj is string filename && !string.IsNullOrEmpty(filename))
{
<text> in </text><span class="assistant-search-phrase">@filename</span>
}
</div>
</div>
}
}
}
@code {
private static readonly ConditionalWeakTable<ChatMessage, ChatMessageItem> SubscribersLookup = new();
[Parameter, EditorRequired]
public required ChatMessage Message { get; set; }
[Parameter]
public bool InProgress { get; set;}
protected override void OnInitialized()
{
SubscribersLookup.AddOrUpdate(Message, this);
}
public static void NotifyChanged(ChatMessage source)
{
if (SubscribersLookup.TryGetValue(source, out var subscriber))
{
subscriber.StateHasChanged();
}
}
}
@@ -1,67 +0,0 @@
.user-message {
background: rgb(182 215 232);
align-self: flex-end;
min-width: 25%;
max-width: calc(100% - 5rem);
padding: 0.5rem 1.25rem;
border-radius: 0.25rem;
color: #1F2937;
white-space: pre-wrap;
}
.assistant-message, .assistant-search {
display: grid;
grid-template-rows: min-content;
grid-template-columns: 2rem minmax(0, 1fr);
gap: 0.25rem;
}
.assistant-message-header {
font-weight: 600;
}
.assistant-message-text {
grid-column-start: 2;
}
.assistant-message-icon {
display: flex;
justify-content: center;
align-items: center;
border-radius: 9999px;
width: 1.5rem;
height: 1.5rem;
color: #ffffff;
background: #9b72ce;
}
.assistant-message-icon svg {
width: 1rem;
height: 1rem;
}
.assistant-search {
font-size: 0.875rem;
line-height: 1.25rem;
}
.assistant-search-icon {
display: flex;
justify-content: center;
align-items: center;
width: 1.5rem;
height: 1.5rem;
}
.assistant-search-icon svg {
width: 1rem;
height: 1rem;
}
.assistant-search-content {
align-content: center;
}
.assistant-search-phrase {
font-weight: 600;
}
@@ -1,42 +0,0 @@
@inject IJSRuntime JS
<div class="message-list-container">
<chat-messages class="page-width message-list" in-progress="@(InProgressMessage is not null)">
@foreach (var message in Messages)
{
<ChatMessageItem @key="@message" Message="@message" />
}
@if (InProgressMessage is not null)
{
<ChatMessageItem Message="@InProgressMessage" InProgress="true" />
<LoadingSpinner />
}
else if (IsEmpty)
{
<div class="no-messages">@NoMessagesContent</div>
}
</chat-messages>
</div>
@code {
[Parameter]
public required IEnumerable<ChatMessage> Messages { get; set; }
[Parameter]
public ChatMessage? InProgressMessage { get; set; }
[Parameter]
public RenderFragment? NoMessagesContent { get; set; }
private bool IsEmpty => !Messages.Any(m => (m.Role == ChatRole.User || m.Role == ChatRole.Assistant) && !string.IsNullOrEmpty(m.Text));
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
// Activates the auto-scrolling behavior
await JS.InvokeVoidAsync("import", "./Components/Pages/Chat/ChatMessageList.razor.js");
}
}
}
@@ -1,22 +0,0 @@
.message-list-container {
margin: 2rem 1.5rem;
flex-grow: 1;
}
.message-list {
display: flex;
flex-direction: column;
gap: 1.25rem;
}
.no-messages {
text-align: center;
font-size: 1.25rem;
color: #999;
margin-top: calc(40vh - 18rem);
}
chat-messages > ::deep div:last-of-type {
/* Adds some vertical buffer to so that suggestions don't overlap the output when they appear */
margin-bottom: 2rem;
}
@@ -1,34 +0,0 @@
// The following logic provides auto-scroll behavior for the chat messages list.
// If you don't want that behavior, you can simply not load this module.
window.customElements.define('chat-messages', class ChatMessages extends HTMLElement {
static _isFirstAutoScroll = true;
connectedCallback() {
this._observer = new MutationObserver(mutations => this._scheduleAutoScroll(mutations));
this._observer.observe(this, { childList: true, attributes: true });
}
disconnectedCallback() {
this._observer.disconnect();
}
_scheduleAutoScroll(mutations) {
// Debounce the calls in case multiple DOM updates occur together
cancelAnimationFrame(this._nextAutoScroll);
this._nextAutoScroll = requestAnimationFrame(() => {
const addedUserMessage = mutations.some(m => Array.from(m.addedNodes).some(n => n.parentElement === this && n.classList?.contains('user-message')));
const elem = this.lastElementChild;
if (ChatMessages._isFirstAutoScroll || addedUserMessage || this._elemIsNearScrollBoundary(elem, 300)) {
elem.scrollIntoView({ behavior: ChatMessages._isFirstAutoScroll ? 'instant' : 'smooth' });
ChatMessages._isFirstAutoScroll = false;
}
});
}
_elemIsNearScrollBoundary(elem, threshold) {
const maxScrollPos = document.body.scrollHeight - window.innerHeight;
const remainingScrollDistance = maxScrollPos - window.scrollY;
return remainingScrollDistance < elem.offsetHeight + threshold;
}
});
@@ -1,78 +0,0 @@
@inject IChatClient ChatClient
@if (suggestions is not null)
{
<div class="page-width suggestions">
@foreach (var suggestion in suggestions)
{
<button class="btn-subtle" @onclick="@(() => AddSuggestionAsync(suggestion))">
@suggestion
</button>
}
</div>
}
@code {
private static string Prompt = @"
Suggest up to 3 follow-up questions that I could ask you to help me complete my task.
Each suggestion must be a complete sentence, maximum 6 words.
Each suggestion must be phrased as something that I (the user) would ask you (the assistant) in response to your previous message,
for example 'How do I do that?' or 'Explain ...'.
If there are no suggestions, reply with an empty list.
";
private string[]? suggestions;
private CancellationTokenSource? cancellation;
[Parameter]
public EventCallback<ChatMessage> OnSelected { get; set; }
public void Clear()
{
suggestions = null;
cancellation?.Cancel();
}
public void Update(IReadOnlyList<ChatMessage> messages)
{
// Runs in the background and handles its own cancellation/errors
_ = UpdateSuggestionsAsync(messages);
}
private async Task UpdateSuggestionsAsync(IReadOnlyList<ChatMessage> messages)
{
cancellation?.Cancel();
cancellation = new CancellationTokenSource();
try
{
var response = await ChatClient.GetResponseAsync<string[]>(
[.. ReduceMessages(messages), new(ChatRole.User, Prompt)],
cancellationToken: cancellation.Token);
if (!response.TryGetResult(out suggestions))
{
suggestions = null;
}
StateHasChanged();
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
await DispatchExceptionAsync(ex);
}
}
private async Task AddSuggestionAsync(string text)
{
await OnSelected.InvokeAsync(new(ChatRole.User, text));
}
private IEnumerable<ChatMessage> ReduceMessages(IReadOnlyList<ChatMessage> messages)
{
// Get any leading system messages, plus up to 5 user/assistant messages
// This should be enough context to generate suggestions without unnecessarily resending entire conversations when long
var systemMessages = messages.TakeWhile(m => m.Role == ChatRole.System);
var otherMessages = messages.Where((m, index) => m.Role == ChatRole.User || m.Role == ChatRole.Assistant).Where(m => !string.IsNullOrEmpty(m.Text)).TakeLast(5);
return systemMessages.Concat(otherMessages);
}
}
@@ -1,9 +0,0 @@
.suggestions {
text-align: right;
white-space: nowrap;
gap: 0.5rem;
justify-content: flex-end;
flex-wrap: wrap;
display: flex;
margin-bottom: 0.75rem;
}
@@ -1,6 +0,0 @@
<Router AppAssembly="typeof(Program).Assembly">
<Found Context="routeData">
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
<FocusOnNavigate RouteData="routeData" Selector="h1" />
</Found>
</Router>
@@ -1,12 +0,0 @@
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using AGUIWebChatClient
@using AGUIWebChatClient.Components
@using AGUIWebChatClient.Components.Layout
@using Microsoft.Extensions.AI
@@ -1,34 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using AGUIWebChatClient.Components;
using Microsoft.Agents.AI.AGUI;
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
string serverUrl = builder.Configuration["SERVER_URL"] ?? "http://localhost:5100";
builder.Services.AddHttpClient("aguiserver", httpClient => httpClient.BaseAddress = new Uri(serverUrl));
builder.Services.AddChatClient(sp => new AGUIChatClient(
sp.GetRequiredService<IHttpClientFactory>().CreateClient("aguiserver"), "ag-ui"));
WebApplication app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseAntiforgery();
app.MapStaticAssets();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
app.Run();
@@ -1,15 +0,0 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"SERVER_URL": "http://localhost:5100"
}
}
}
}
@@ -1,93 +0,0 @@
html {
min-height: 100vh;
}
html, .main-background-gradient {
background: linear-gradient(to bottom, rgb(225 227 233), #f4f4f4 25rem);
}
body {
display: flex;
flex-direction: column;
min-height: 100vh;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
html::after {
content: '';
background-image: linear-gradient(to right, #3a4ed5, #3acfd5 15%, #d53abf 85%, red);
width: 100%;
height: 2px;
position: fixed;
top: 0;
}
h1 {
font-size: 2.25rem;
line-height: 2.5rem;
font-weight: 600;
}
h1:focus {
outline: none;
}
.valid.modified:not([type=checkbox]) {
outline: 1px solid #26b050;
}
.invalid {
outline: 1px solid #e50000;
}
.validation-message {
color: #e50000;
}
.blazor-error-boundary {
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
padding: 1rem 1rem 1rem 3.7rem;
color: white;
}
.blazor-error-boundary::after {
content: "An error has occurred."
}
.btn-default {
display: flex;
padding: 0.25rem 0.75rem;
gap: 0.25rem;
align-items: center;
border-radius: 0.25rem;
border: 1px solid #9CA3AF;
font-size: 0.875rem;
line-height: 1.25rem;
font-weight: 600;
background-color: #D1D5DB;
}
.btn-default:hover {
background-color: #E5E7EB;
}
.btn-subtle {
display: flex;
padding: 0.25rem 0.75rem;
gap: 0.25rem;
align-items: center;
border-radius: 0.25rem;
border: 1px solid #D1D5DB;
font-size: 0.875rem;
line-height: 1.25rem;
}
.btn-subtle:hover {
border-color: #93C5FD;
background-color: #DBEAFE;
}
.page-width {
max-width: 1024px;
margin: auto;
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

-185
View File
@@ -1,185 +0,0 @@
# AGUI WebChat Sample
This sample demonstrates a Blazor-based web chat application using the AG-UI protocol to communicate with an AI agent server.
The sample consists of two projects:
1. **Server** - An ASP.NET Core server that hosts a simple chat agent using the AG-UI protocol
2. **Client** - A Blazor Server application with a rich chat UI for interacting with the agent
## Prerequisites
### Azure OpenAI Configuration
The server requires Azure OpenAI credentials. Set the following environment variables:
```powershell
$env:AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/"
$env:AZURE_OPENAI_DEPLOYMENT_NAME="your-deployment-name" # e.g., "gpt-4o"
```
The server uses `DefaultAzureCredential` for authentication. Ensure you are logged in using one of the following methods:
- Azure CLI: `az login`
- Azure PowerShell: `Connect-AzAccount`
- Visual Studio or VS Code with Azure extensions
- Environment variables with service principal credentials
## Running the Sample
### Step 1: Start the Server
Open a terminal and navigate to the Server directory:
```powershell
cd Server
dotnet run
```
The server will start on `http://localhost:5100` and expose the AG-UI endpoint at `/ag-ui`.
### Step 2: Start the Client
Open a new terminal and navigate to the Client directory:
```powershell
cd Client
dotnet run
```
The client will start on `http://localhost:5000`. Open your browser and navigate to `http://localhost:5000` to access the chat interface.
### Step 3: Chat with the Agent
Type your message in the text box at the bottom of the page and press Enter or click the send button. The assistant will respond with streaming text that appears in real-time.
Features:
- **Streaming responses**: Watch the assistant's response appear word by word
- **Conversation suggestions**: The assistant may offer follow-up questions after responding
- **New chat**: Click the "New chat" button to start a fresh conversation
- **Auto-scrolling**: The chat automatically scrolls to show new messages
## How It Works
### Server (AG-UI Host)
The server (`Server/Program.cs`) creates a simple chat agent:
```csharp
// Create Azure OpenAI client
AzureOpenAIClient azureOpenAIClient = new AzureOpenAIClient(
new Uri(endpoint),
new DefaultAzureCredential());
ChatClient chatClient = azureOpenAIClient.GetChatClient(deploymentName);
// Create AI agent
ChatClientAgent agent = chatClient.AsIChatClient().CreateAIAgent(
name: "ChatAssistant",
instructions: "You are a helpful assistant.");
// Map AG-UI endpoint
app.MapAGUI("/ag-ui", agent);
```
The server exposes the agent via the AG-UI protocol at `http://localhost:5100/ag-ui`.
### Client (Blazor Web App)
The client (`Client/Program.cs`) configures an `AGUIChatClient` to connect to the server:
```csharp
string serverUrl = builder.Configuration["SERVER_URL"] ?? "http://localhost:5100";
builder.Services.AddHttpClient("aguiserver", httpClient => httpClient.BaseAddress = new Uri(serverUrl));
builder.Services.AddChatClient(sp => new AGUIChatClient(
sp.GetRequiredService<IHttpClientFactory>().CreateClient("aguiserver"), "ag-ui"));
```
The Blazor UI (`Client/Components/Pages/Chat/Chat.razor`) uses the `IChatClient` to:
- Send user messages to the agent
- Stream responses back in real-time
- Maintain conversation history
- Display messages with appropriate styling
### UI Components
The chat interface is built from several Blazor components:
- **Chat.razor** - Main chat page coordinating the conversation flow
- **ChatHeader.razor** - Header with "New chat" button
- **ChatMessageList.razor** - Scrollable list of messages with auto-scroll
- **ChatMessageItem.razor** - Individual message rendering (user vs assistant)
- **ChatInput.razor** - Text input with auto-resize and keyboard shortcuts
- **ChatSuggestions.razor** - AI-generated follow-up question suggestions
- **LoadingSpinner.razor** - Animated loading indicator during streaming
## Configuration
### Server Configuration
The server URL and port are configured in `Server/Properties/launchSettings.json`:
```json
{
"profiles": {
"http": {
"applicationUrl": "http://localhost:5100"
}
}
}
```
### Client Configuration
The client connects to the server URL specified in `Client/Properties/launchSettings.json`:
```json
{
"profiles": {
"http": {
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"SERVER_URL": "http://localhost:5100"
}
}
}
}
```
To change the server URL, modify the `SERVER_URL` environment variable in the client's launch settings or provide it at runtime:
```powershell
$env:SERVER_URL="http://your-server:5100"
dotnet run
```
## Customization
### Changing the Agent Instructions
Edit the instructions in `Server/Program.cs`:
```csharp
ChatClientAgent agent = chatClient.AsIChatClient().CreateAIAgent(
name: "ChatAssistant",
instructions: "You are a helpful coding assistant specializing in C# and .NET.");
```
### Styling the UI
The chat interface uses CSS files colocated with each Razor component. Key styles:
- `wwwroot/app.css` - Global styles, buttons, color scheme
- `Components/Pages/Chat/Chat.razor.css` - Chat container layout
- `Components/Pages/Chat/ChatMessageItem.razor.css` - Message bubbles and icons
- `Components/Pages/Chat/ChatInput.razor.css` - Input box styling
### Disabling Suggestions
To disable the AI-generated follow-up suggestions, comment out the suggestions component in `Chat.razor`:
```razor
@* <ChatSuggestions OnSelected="@AddUserMessageAsync" @ref="@chatSuggestions" /> *@
```
@@ -1,21 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net10.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" />
<PackageReference Include="Azure.Identity" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.csproj" />
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.AGUI\Microsoft.Agents.AI.AGUI.csproj" />
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
</ItemGroup>
</Project>
@@ -1,35 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample demonstrates a basic AG-UI server hosting a chat agent for the Blazor web client.
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Hosting.AGUI.AspNetCore;
using Microsoft.Extensions.AI;
using OpenAI.Chat;
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient().AddLogging();
builder.Services.AddAGUI();
WebApplication app = builder.Build();
string endpoint = builder.Configuration["AZURE_OPENAI_ENDPOINT"] ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
string deploymentName = builder.Configuration["AZURE_OPENAI_DEPLOYMENT_NAME"] ?? throw new InvalidOperationException("AZURE_OPENAI_DEPLOYMENT_NAME is not set.");
// Create the AI agent
AzureOpenAIClient azureOpenAIClient = new(
new Uri(endpoint),
new DefaultAzureCredential());
ChatClient chatClient = azureOpenAIClient.GetChatClient(deploymentName);
ChatClientAgent agent = chatClient.AsIChatClient().CreateAIAgent(
name: "ChatAssistant",
instructions: "You are a helpful assistant.");
// Map the AG-UI agent endpoint
app.MapAGUI("/ag-ui", agent);
await app.RunAsync();
@@ -1,14 +0,0 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5100",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
@@ -7,7 +7,7 @@ using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Hosting.AzureFunctions;
using Microsoft.Azure.Functions.Worker.Builder;
using Microsoft.Extensions.Hosting;
using OpenAI.Chat;
using OpenAI;
// Get the Azure OpenAI endpoint and deployment name from environment variables.
string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
@@ -7,7 +7,7 @@ using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Hosting.AzureFunctions;
using Microsoft.Azure.Functions.Worker.Builder;
using Microsoft.Extensions.Hosting;
using OpenAI.Chat;
using OpenAI;
// Get the Azure OpenAI endpoint and deployment name from environment variables.
string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
@@ -7,7 +7,7 @@ using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Hosting.AzureFunctions;
using Microsoft.Azure.Functions.Worker.Builder;
using Microsoft.Extensions.Hosting;
using OpenAI.Chat;
using OpenAI;
// Get the Azure OpenAI endpoint and deployment name from environment variables.
string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
@@ -7,7 +7,7 @@ using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Hosting.AzureFunctions;
using Microsoft.Azure.Functions.Worker.Builder;
using Microsoft.Extensions.Hosting;
using OpenAI.Chat;
using OpenAI;
// Get the Azure OpenAI endpoint and deployment name from environment variables.
string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
@@ -7,7 +7,7 @@ using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Hosting.AzureFunctions;
using Microsoft.Azure.Functions.Worker.Builder;
using Microsoft.Extensions.Hosting;
using OpenAI.Chat;
using OpenAI;
// Get the Azure OpenAI endpoint and deployment name from environment variables.
string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
@@ -11,7 +11,7 @@ using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using OpenAI.Chat;
using OpenAI;
// Get the Azure OpenAI endpoint and deployment name from environment variables.
string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
@@ -13,7 +13,7 @@ using Microsoft.Agents.AI.DurableTask;
using Microsoft.Agents.AI.Hosting.AzureFunctions;
using Microsoft.Azure.Functions.Worker.Builder;
using Microsoft.Extensions.Hosting;
using OpenAI.Chat;
using OpenAI;
// Get the Azure OpenAI endpoint and deployment name from environment variables.
string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
@@ -15,6 +15,11 @@
<PackageReference Include="Microsoft.Extensions.Hosting" />
</ItemGroup>
<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible($(TargetFramework), 'net10.0'))">
<PackageReference Include="System.Net.ServerSentEvents" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
</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" />
@@ -10,7 +10,7 @@ using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using OpenAI.Chat;
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";
@@ -1,304 +0,0 @@
# AG-UI Getting Started Samples
This directory contains samples that demonstrate how to build AG-UI (Agent UI Protocol) servers and clients using the Microsoft Agent Framework.
## Prerequisites
- .NET 9.0 or later
- Azure OpenAI service endpoint and deployment configured
- Azure CLI installed and authenticated (`az login`)
- User has the `Cognitive Services OpenAI Contributor` role for the Azure OpenAI resource
## Environment Variables
All samples require the following environment variables:
```bash
export AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/"
export AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini"
```
For the client samples, you can optionally set:
```bash
export AGUI_SERVER_URL="http://localhost:8888"
```
## Samples
### Step01_GettingStarted
A basic AG-UI server and client that demonstrate the foundational concepts.
#### Server (`Step01_GettingStarted/Server`)
A basic AG-UI server that hosts an AI agent accessible via HTTP. Demonstrates:
- Creating an ASP.NET Core web application
- Setting up an AG-UI server endpoint with `MapAGUI`
- Creating an AI agent from an Azure OpenAI chat client
- Streaming responses via Server-Sent Events (SSE)
**Run the server:**
```bash
cd Step01_GettingStarted/Server
dotnet run --urls http://localhost:8888
```
#### Client (`Step01_GettingStarted/Client`)
An interactive console client that connects to an AG-UI server. Demonstrates:
- Creating an AG-UI client with `AGUIChatClient`
- Managing conversation threads
- Streaming responses with `RunStreamingAsync`
- Displaying colored console output for different content types
- Supporting both interactive and automated modes
**Prerequisites:** The Step01_GettingStarted server (or any AG-UI server) must be running.
**Run the client:**
```bash
cd Step01_GettingStarted/Client
dotnet run
```
Type messages and press Enter to interact with the agent. Type `:q` or `quit` to exit.
### Step02_BackendTools
An AG-UI server with function tools that execute on the backend.
#### Server (`Step02_BackendTools/Server`)
Demonstrates:
- Creating function tools using `AIFunctionFactory.Create`
- Using `[Description]` attributes for tool documentation
- Defining explicit request/response types for type safety
- Setting up JSON serialization contexts for source generation
- Backend tool rendering (tools execute on the server)
**Run the server:**
```bash
cd Step02_BackendTools/Server
dotnet run --urls http://localhost:8888
```
#### Client (`Step02_BackendTools/Client`)
A client that works with the backend tools server. Try asking: "Find Italian restaurants in Seattle" or "Search for Mexican food in Portland".
**Run the client:**
```bash
cd Step02_BackendTools/Client
dotnet run
```
### Step03_FrontendTools
Demonstrates frontend tool rendering (tools defined on client, executed on server).
#### Server (`Step03_FrontendTools/Server`)
A basic AG-UI server that accepts tool definitions from the client.
**Run the server:**
```bash
cd Step03_FrontendTools/Server
dotnet run --urls http://localhost:8888
```
#### Client (`Step03_FrontendTools/Client`)
A client that defines and sends tools to the server for execution.
**Run the client:**
```bash
cd Step03_FrontendTools/Client
dotnet run
```
### Step04_HumanInLoop
Demonstrates human-in-the-loop approval workflows for sensitive operations. This sample includes both a server and client component.
#### Server (`Step04_HumanInLoop/Server`)
An AG-UI server that implements approval workflows. Demonstrates:
- Wrapping tools with `ApprovalRequiredAIFunction`
- Converting `FunctionApprovalRequestContent` to approval requests
- Middleware pattern with `ServerFunctionApprovalServerAgent`
- Complete function call capture and restoration
**Run the server:**
```bash
cd Step04_HumanInLoop/Server
dotnet run --urls http://localhost:8888
```
#### Client (`Step04_HumanInLoop/Client`)
An interactive client that handles approval requests from the server. Demonstrates:
- Using `ServerFunctionApprovalClientAgent` middleware
- Detecting `FunctionApprovalRequestContent`
- Displaying approval details to users
- Prompting for approval/rejection
- Sending approval responses with `FunctionApprovalResponseContent`
- Resuming conversation after approval
**Run the client:**
```bash
cd Step04_HumanInLoop/Client
dotnet run
```
Try asking the agent to perform sensitive operations like "Approve expense report EXP-12345".
### Step05_StateManagement
An AG-UI server and client that demonstrate state management with predictive updates.
#### Server (`Step05_StateManagement/Server`)
Demonstrates:
- Defining state schemas using C# records
- Using `SharedStateAgent` middleware for state management
- Streaming predictive state updates with `AgentState` content
- Managing shared state between client and server
- Using JSON serialization contexts for state types
**Run the server:**
```bash
cd Step05_StateManagement/Server
dotnet run
```
The server runs on port 8888 by default.
#### Client (`Step05_StateManagement/Client`)
A client that displays and updates shared state from the server. Try asking: "Create a recipe for chocolate chip cookies" or "Suggest a pasta dish".
**Run the client:**
```bash
cd Step05_StateManagement/Client
dotnet run
```
## How AG-UI Works
### Server-Side
1. Client sends HTTP POST request with messages
2. ASP.NET Core endpoint receives the request via `MapAGUI`
3. Agent processes messages using Agent Framework
4. Responses are streamed back as Server-Sent Events (SSE)
### Client-Side
1. `AGUIAgent` sends HTTP POST request to server
2. Server responds with SSE stream
3. Client parses events into `AgentRunResponseUpdate` objects
4. Updates are displayed based on content type
5. `ConversationId` maintains conversation context
### Protocol Features
- **HTTP POST** for requests
- **Server-Sent Events (SSE)** for streaming responses
- **JSON** for event serialization
- **Thread IDs** (as `ConversationId`) for conversation context
- **Run IDs** (as `ResponseId`) for tracking individual executions
## Troubleshooting
### Connection Refused
Ensure the server is running before starting the client:
```bash
# Terminal 1
cd AGUI_Step01_ServerBasic
dotnet run --urls http://localhost:8888
# Terminal 2 (after server starts)
cd AGUI_Step02_ClientBasic
dotnet run
```
### Port Already in Use
If port 8888 is already in use, choose a different port:
```bash
# Server
dotnet run --urls http://localhost:8889
# Client (set environment variable)
export AGUI_SERVER_URL="http://localhost:8889"
dotnet run
```
### Authentication Errors
Make sure you're authenticated with Azure:
```bash
az login
```
Verify you have the `Cognitive Services OpenAI Contributor` role on the Azure OpenAI resource.
### Missing Environment Variables
If you see "AZURE_OPENAI_ENDPOINT is not set" errors, ensure environment variables are set in your current shell session before running the samples.
### Streaming Not Working
Check that the client timeout is sufficient (default is 60 seconds). For long-running operations, you may need to increase the timeout in the client code.
## Next Steps
After completing these samples, explore more AG-UI capabilities:
### Currently Available in C#
The samples above demonstrate the AG-UI features currently available in C#:
- âś… **Basic Server and Client**: Setting up AG-UI communication
- âś… **Backend Tool Rendering**: Function tools that execute on the server
- âś… **Streaming Responses**: Real-time Server-Sent Events
- âś… **State Management**: State schemas with predictive updates
- âś… **Human-in-the-Loop**: Approval workflows for sensitive operations
### Coming Soon to C#
The following advanced AG-UI features are available in the Python implementation and are planned for future C# releases:
- ⏳ **Generative UI**: Custom UI component generation
- ⏳ **Advanced State Patterns**: Complex state synchronization scenarios
For the most up-to-date AG-UI features, see the [Python samples](../../../../python/samples/) for working examples.
### Related Documentation
- [AG-UI Overview](https://learn.microsoft.com/agent-framework/integrations/ag-ui/) - Complete AG-UI documentation
- [Getting Started Tutorial](https://learn.microsoft.com/agent-framework/integrations/ag-ui/getting-started) - Step-by-step walkthrough
- [Backend Tool Rendering](https://learn.microsoft.com/agent-framework/integrations/ag-ui/backend-tool-rendering) - Function tools tutorial
- [Human-in-the-Loop](https://learn.microsoft.com/agent-framework/integrations/ag-ui/human-in-the-loop) - Approval workflows tutorial
- [State Management](https://learn.microsoft.com/agent-framework/integrations/ag-ui/state-management) - State management tutorial
- [Agent Framework Overview](https://learn.microsoft.com/agent-framework/overview/agent-framework-overview) - Core framework concepts
@@ -1,15 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net10.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.AGUI\Microsoft.Agents.AI.AGUI.csproj" />
</ItemGroup>
</Project>
@@ -1,94 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.AGUI;
using Microsoft.Extensions.AI;
string serverUrl = Environment.GetEnvironmentVariable("AGUI_SERVER_URL") ?? "http://localhost:8888";
Console.WriteLine($"Connecting to AG-UI server at: {serverUrl}\n");
// Create the AG-UI client agent
using HttpClient httpClient = new()
{
Timeout = TimeSpan.FromSeconds(60)
};
AGUIChatClient chatClient = new(httpClient, serverUrl);
AIAgent agent = chatClient.CreateAIAgent(
name: "agui-client",
description: "AG-UI Client Agent");
AgentThread thread = agent.GetNewThread();
List<ChatMessage> messages =
[
new(ChatRole.System, "You are a helpful assistant.")
];
try
{
while (true)
{
// Get user input
Console.Write("\nUser (:q or quit to exit): ");
string? message = Console.ReadLine();
if (string.IsNullOrWhiteSpace(message))
{
Console.WriteLine("Request cannot be empty.");
continue;
}
if (message is ":q" or "quit")
{
break;
}
messages.Add(new ChatMessage(ChatRole.User, message));
// Stream the response
bool isFirstUpdate = true;
string? threadId = null;
await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync(messages, thread))
{
ChatResponseUpdate chatUpdate = update.AsChatResponseUpdate();
// First update indicates run started
if (isFirstUpdate)
{
threadId = chatUpdate.ConversationId;
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine($"\n[Run Started - Thread: {chatUpdate.ConversationId}, Run: {chatUpdate.ResponseId}]");
Console.ResetColor();
isFirstUpdate = false;
}
// Display streaming text content
foreach (AIContent content in update.Contents)
{
if (content is TextContent textContent)
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.Write(textContent.Text);
Console.ResetColor();
}
else if (content is ErrorContent errorContent)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"\n[Error: {errorContent.Message}]");
Console.ResetColor();
}
}
}
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine($"\n[Run Finished - Thread: {threadId}]");
Console.ResetColor();
}
}
catch (Exception ex)
{
Console.WriteLine($"\nAn error occurred: {ex.Message}");
}
@@ -1,34 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Hosting.AGUI.AspNetCore;
using Microsoft.Extensions.AI;
using OpenAI.Chat;
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient().AddLogging();
builder.Services.AddAGUI();
WebApplication app = builder.Build();
string endpoint = builder.Configuration["AZURE_OPENAI_ENDPOINT"]
?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
string deploymentName = builder.Configuration["AZURE_OPENAI_DEPLOYMENT_NAME"]
?? throw new InvalidOperationException("AZURE_OPENAI_DEPLOYMENT_NAME is not set.");
// Create the AI agent
ChatClient chatClient = new AzureOpenAIClient(
new Uri(endpoint),
new DefaultAzureCredential())
.GetChatClient(deploymentName);
AIAgent agent = chatClient.AsIChatClient().CreateAIAgent(
name: "AGUIAssistant",
instructions: "You are a helpful assistant.");
// Map the AG-UI agent endpoint
app.MapAGUI("/", agent);
await app.RunAsync();
@@ -1,23 +0,0 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5253",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7047;http://localhost:5253",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
@@ -1,21 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net10.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" />
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.csproj" />
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
</ItemGroup>
</Project>
@@ -1,8 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
@@ -1,9 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
@@ -1,15 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net10.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.AGUI\Microsoft.Agents.AI.AGUI.csproj" />
</ItemGroup>
</Project>
@@ -1,126 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.AGUI;
using Microsoft.Extensions.AI;
string serverUrl = Environment.GetEnvironmentVariable("AGUI_SERVER_URL") ?? "http://localhost:8888";
Console.WriteLine($"Connecting to AG-UI server at: {serverUrl}\n");
// Create the AG-UI client agent
using HttpClient httpClient = new()
{
Timeout = TimeSpan.FromSeconds(60)
};
AGUIChatClient chatClient = new(httpClient, serverUrl);
AIAgent agent = chatClient.CreateAIAgent(
name: "agui-client",
description: "AG-UI Client Agent");
AgentThread thread = agent.GetNewThread();
List<ChatMessage> messages =
[
new(ChatRole.System, "You are a helpful assistant.")
];
try
{
while (true)
{
// Get user input
Console.Write("\nUser (:q or quit to exit): ");
string? message = Console.ReadLine();
if (string.IsNullOrWhiteSpace(message))
{
Console.WriteLine("Request cannot be empty.");
continue;
}
if (message is ":q" or "quit")
{
break;
}
messages.Add(new ChatMessage(ChatRole.User, message));
// Stream the response
bool isFirstUpdate = true;
string? threadId = null;
await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync(messages, thread))
{
ChatResponseUpdate chatUpdate = update.AsChatResponseUpdate();
// First update indicates run started
if (isFirstUpdate)
{
threadId = chatUpdate.ConversationId;
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine($"\n[Run Started - Thread: {chatUpdate.ConversationId}, Run: {chatUpdate.ResponseId}]");
Console.ResetColor();
isFirstUpdate = false;
}
// Display streaming content
foreach (AIContent content in update.Contents)
{
switch (content)
{
case TextContent textContent:
Console.ForegroundColor = ConsoleColor.Cyan;
Console.Write(textContent.Text);
Console.ResetColor();
break;
case FunctionCallContent functionCallContent:
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine($"\n[Function Call - Name: {functionCallContent.Name}]");
// Display individual parameters
if (functionCallContent.Arguments != null)
{
foreach (var kvp in functionCallContent.Arguments)
{
Console.WriteLine($" Parameter: {kvp.Key} = {kvp.Value}");
}
}
Console.ResetColor();
break;
case FunctionResultContent functionResultContent:
Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine($"\n[Function Result - CallId: {functionResultContent.CallId}]");
if (functionResultContent.Exception != null)
{
Console.WriteLine($" Exception: {functionResultContent.Exception}");
}
else
{
Console.WriteLine($" Result: {functionResultContent.Result}");
}
Console.ResetColor();
break;
case ErrorContent errorContent:
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"\n[Error: {errorContent.Message}]");
Console.ResetColor();
break;
}
}
}
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine($"\n[Run Finished - Thread: {threadId}]");
Console.ResetColor();
}
}
catch (Exception ex)
{
Console.WriteLine($"\nAn error occurred: {ex.Message}");
}
@@ -1,117 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ComponentModel;
using System.Text.Json.Serialization;
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Hosting.AGUI.AspNetCore;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Options;
using OpenAI.Chat;
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient().AddLogging();
builder.Services.ConfigureHttpJsonOptions(options =>
options.SerializerOptions.TypeInfoResolverChain.Add(SampleJsonSerializerContext.Default));
builder.Services.AddAGUI();
WebApplication app = builder.Build();
string endpoint = builder.Configuration["AZURE_OPENAI_ENDPOINT"]
?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
string deploymentName = builder.Configuration["AZURE_OPENAI_DEPLOYMENT_NAME"]
?? throw new InvalidOperationException("AZURE_OPENAI_DEPLOYMENT_NAME is not set.");
// Define the function tool
[Description("Search for restaurants in a location.")]
static RestaurantSearchResponse SearchRestaurants(
[Description("The restaurant search request")] RestaurantSearchRequest request)
{
// Simulated restaurant data
string cuisine = request.Cuisine == "any" ? "Italian" : request.Cuisine;
return new RestaurantSearchResponse
{
Location = request.Location,
Cuisine = request.Cuisine,
Results =
[
new RestaurantInfo
{
Name = "The Golden Fork",
Cuisine = cuisine,
Rating = 4.5,
Address = $"123 Main St, {request.Location}"
},
new RestaurantInfo
{
Name = "Spice Haven",
Cuisine = cuisine == "Italian" ? "Indian" : cuisine,
Rating = 4.7,
Address = $"456 Oak Ave, {request.Location}"
},
new RestaurantInfo
{
Name = "Green Leaf",
Cuisine = "Vegetarian",
Rating = 4.3,
Address = $"789 Elm Rd, {request.Location}"
}
]
};
}
// Get JsonSerializerOptions from the configured HTTP JSON options
Microsoft.AspNetCore.Http.Json.JsonOptions jsonOptions = app.Services.GetRequiredService<IOptions<Microsoft.AspNetCore.Http.Json.JsonOptions>>().Value;
// Create tool with serializer options
AITool[] tools =
[
AIFunctionFactory.Create(
SearchRestaurants,
serializerOptions: jsonOptions.SerializerOptions)
];
// Create the AI agent with tools
ChatClient chatClient = new AzureOpenAIClient(
new Uri(endpoint),
new DefaultAzureCredential())
.GetChatClient(deploymentName);
ChatClientAgent agent = chatClient.AsIChatClient().CreateAIAgent(
name: "AGUIAssistant",
instructions: "You are a helpful assistant with access to restaurant information.",
tools: tools);
// Map the AG-UI agent endpoint
app.MapAGUI("/", agent);
await app.RunAsync();
// Define request/response types for the tool
internal sealed class RestaurantSearchRequest
{
public string Location { get; set; } = string.Empty;
public string Cuisine { get; set; } = "any";
}
internal sealed class RestaurantSearchResponse
{
public string Location { get; set; } = string.Empty;
public string Cuisine { get; set; } = string.Empty;
public RestaurantInfo[] Results { get; set; } = [];
}
internal sealed class RestaurantInfo
{
public string Name { get; set; } = string.Empty;
public string Cuisine { get; set; } = string.Empty;
public double Rating { get; set; }
public string Address { get; set; } = string.Empty;
}
// JSON serialization context for source generation
[JsonSerializable(typeof(RestaurantSearchRequest))]
[JsonSerializable(typeof(RestaurantSearchResponse))]
internal sealed partial class SampleJsonSerializerContext : JsonSerializerContext;
@@ -1,23 +0,0 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5253",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7047;http://localhost:5253",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
@@ -1,21 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net10.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" />
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.csproj" />
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
</ItemGroup>
</Project>
@@ -1,8 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
@@ -1,9 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
@@ -1,15 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net10.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.AGUI\Microsoft.Agents.AI.AGUI.csproj" />
</ItemGroup>
</Project>

Some files were not shown because too many files have changed in this diff Show More