mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Compare commits
100
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43302fb4cd | ||
|
|
0965a83dcd | ||
|
|
2caf1bb151 | ||
|
|
dec4b3885d | ||
|
|
b55095f0fb | ||
|
|
69225b4aa2 | ||
|
|
71d9d268dd | ||
|
|
c6e73a4ebe | ||
|
|
d889fa6f87 | ||
|
|
1e6aedb3f9 | ||
|
|
d7ca96923b | ||
|
|
0675000f4b | ||
|
|
551219cb55 | ||
|
|
c34af002b8 | ||
|
|
cfcb71334a | ||
|
|
eb06faea2d | ||
|
|
8c6b12e664 | ||
|
|
2d3ba95036 | ||
|
|
f5f909f2e2 | ||
|
|
f43261094f | ||
|
|
900c2a04fb | ||
|
|
417a529e0e | ||
|
|
9c1b2f9a3f | ||
|
|
51721bd9f6 | ||
|
|
db36809744 | ||
|
|
b78a2b6d2e | ||
|
|
f2ed5b55f6 | ||
|
|
6809510413 | ||
|
|
f5786b2c5b | ||
|
|
d858c23bab | ||
|
|
365e438981 | ||
|
|
01a04a3114 | ||
|
|
ee340bef1d | ||
|
|
19ac6e57c0 | ||
|
|
292a2078fa | ||
|
|
ae06c9e4bb | ||
|
|
f7a9005235 | ||
|
|
b86c130411 | ||
|
|
d774b64df0 | ||
|
|
42ffe59592 | ||
|
|
a683c0e865 | ||
|
|
a597a925cb | ||
|
|
9c2e800189 | ||
|
|
a26e9d6274 | ||
|
|
411ee7a60f | ||
|
|
6835161f2d | ||
|
|
3cfd34836f | ||
|
|
a81279d960 | ||
|
|
931197ba95 | ||
|
|
d57303cf53 | ||
|
|
b5595f6f70 | ||
|
|
6232dd8305 | ||
|
|
e9ab514696 | ||
|
|
b69f994840 | ||
|
|
ceaea46765 | ||
|
|
1edd28b264 | ||
|
|
5c70e143a0 | ||
|
|
c2d285e614 | ||
|
|
cb343dd707 | ||
|
|
0d5d10d24a | ||
|
|
e3b700ad9f | ||
|
|
8ae33d3c2b | ||
|
|
bf326e7f78 | ||
|
|
b457d3cdbe | ||
|
|
139c292807 | ||
|
|
b26108975d | ||
|
|
a3909b8b5b | ||
|
|
67de7bcd93 | ||
|
|
e769256976 | ||
|
|
257e717608 | ||
|
|
66a399ee2d | ||
|
|
8cf8b0f995 | ||
|
|
6c624319db | ||
|
|
306c81aef8 | ||
|
|
0f2c5e6cb8 | ||
|
|
17b4dfab14 | ||
|
|
486b78126d | ||
|
|
0723f1d183 | ||
|
|
a57b37d5fa | ||
|
|
907d79ab3c | ||
|
|
ed53ba158b | ||
|
|
5d8be5836c | ||
|
|
0c1d12feca | ||
|
|
b1c210c9d8 | ||
|
|
b8260ae24c | ||
|
|
d302bffc63 | ||
|
|
7a4f6877c3 | ||
|
|
b6c3b8d3f2 | ||
|
|
1dde57981e | ||
|
|
112410ecd6 | ||
|
|
a89c15d6e6 | ||
|
|
27b3a517eb | ||
|
|
1b4103dce3 | ||
|
|
2a4802eac3 | ||
|
|
e303cc963d | ||
|
|
c78edda64f | ||
|
|
a610a4769c | ||
|
|
bcbf1b33e8 | ||
|
|
9f43108ef1 | ||
|
|
db424d56f3 |
@@ -1,10 +1,11 @@
|
||||
{
|
||||
"name": "C# (.NET)",
|
||||
"image": "mcr.microsoft.com/devcontainers/dotnet:9.0",
|
||||
"image": "mcr.microsoft.com/devcontainers/dotnet:10.0",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/dotnet:2.4.0": {},
|
||||
"ghcr.io/devcontainers/features/powershell:1.5.1": {},
|
||||
"ghcr.io/devcontainers/features/azure-cli:1.2.8": {}
|
||||
"ghcr.io/devcontainers/features/azure-cli:1.2.8": {},
|
||||
"ghcr.io/devcontainers/features/docker-in-docker:2.12.4": {}
|
||||
},
|
||||
"workspaceFolder": "/workspaces/agent-framework/dotnet/",
|
||||
"customizations": {
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
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]".
|
||||
@@ -1636,4 +1636,4 @@ The property mapping guide from a `AutoFunctionInvocationContext` to a `Function
|
||||
| Result | Use `return` from the delegate |
|
||||
| Terminate | Terminate |
|
||||
| CancellationToken | provided via argument to middleware delegate |
|
||||
| Arguments | Arguments |
|
||||
| Arguments | Arguments |
|
||||
|
||||
@@ -79,7 +79,7 @@ jobs:
|
||||
workflow-samples
|
||||
|
||||
- name: Setup dotnet
|
||||
uses: actions/setup-dotnet@v5.0.0
|
||||
uses: actions/setup-dotnet@v5.0.1
|
||||
with:
|
||||
global-json-file: ${{ github.workspace }}/dotnet/global.json
|
||||
- name: Build dotnet solutions
|
||||
@@ -124,6 +124,16 @@ jobs:
|
||||
popd
|
||||
rm -rf "$TEMP_DIR"
|
||||
|
||||
# Start Cosmos DB Emulator for Cosmos-based unit tests (only on Windows)
|
||||
- name: Start Azure Cosmos DB Emulator
|
||||
if: runner.os == 'Windows'
|
||||
shell: pwsh
|
||||
run: |
|
||||
Write-Host "Launching Azure Cosmos DB Emulator"
|
||||
Import-Module "$env:ProgramFiles\Azure Cosmos DB Emulator\PSModules\Microsoft.Azure.CosmosDB.Emulator"
|
||||
Start-CosmosDbEmulator -NoUI -Key "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="
|
||||
echo "COSMOS_EMULATOR_AVAILABLE=true" >> $env:GITHUB_ENV
|
||||
|
||||
- name: Run Unit Tests
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -143,6 +153,10 @@ jobs:
|
||||
echo "Skipping $project - does not support target framework ${{ matrix.targetFramework }} (supports: $target_frameworks)"
|
||||
fi
|
||||
done
|
||||
env:
|
||||
# Cosmos DB Emulator connection settings
|
||||
COSMOSDB_ENDPOINT: https://localhost:8081
|
||||
COSMOSDB_KEY: C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==
|
||||
|
||||
- name: Log event name and matrix integration-tests
|
||||
shell: bash
|
||||
@@ -181,6 +195,9 @@ jobs:
|
||||
fi
|
||||
done
|
||||
env:
|
||||
# Cosmos DB Emulator connection settings
|
||||
COSMOSDB_ENDPOINT: https://localhost:8081
|
||||
COSMOSDB_KEY: C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==
|
||||
# OpenAI Models
|
||||
OpenAI__ApiKey: ${{ secrets.OPENAI__APIKEY }}
|
||||
OpenAI__ChatModelId: ${{ vars.OPENAI__CHATMODELID }}
|
||||
@@ -200,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.4.18
|
||||
uses: danielpalme/ReportGenerator-GitHub-Action@5.5.1
|
||||
with:
|
||||
reports: "./TestResults/Coverage/**/coverage.cobertura.xml"
|
||||
targetdir: "./TestResults/Reports"
|
||||
|
||||
@@ -48,6 +48,8 @@ dotnet add package Microsoft.Agents.AI
|
||||
- **[Migration from Semantic Kernel](https://learn.microsoft.com/en-us/agent-framework/migration-guide/from-semantic-kernel)** - Guide to migrate from Semantic Kernel
|
||||
- **[Migration from AutoGen](https://learn.microsoft.com/en-us/agent-framework/migration-guide/from-autogen)** - Guide to migrate from AutoGen
|
||||
|
||||
Still have questions? Join our [weekly office hours](./COMMUNITY.md#public-community-office-hours) or ask questions in our [Discord channel](https://discord.gg/b5zjErwbQM) to get help from the team and other users.
|
||||
|
||||
### ✨ **Highlights**
|
||||
|
||||
- **Graph-based Workflows**: Connect agents and deterministic functions using data flows with streaming, checkpointing, human-in-the-loop, and time-travel capabilities
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ Microsoft Agent Framework relies on existing LLMs. Using the framework retains c
|
||||
|
||||
- **Platform Requirements**: Python 3.10+ required, specific .NET versions (.NET 8.0, 9.0, 10.0, netstandard2.0, net472)
|
||||
- **API Dependencies**: Requires proper configuration of LLM provider keys and endpoints
|
||||
- **Orchestration Features**: Advanced orchestration patterns like GroupChat, Sequential, and Concurrent orchestrations are "coming soon" for Python implementation
|
||||
- **Orchestration Features**: Advanced orchestration patterns including GroupChat, Sequential, and Concurrent workflows are now available in both Python and .NET implementations. See the respective language documentation for examples.
|
||||
- **Privacy and Data Protection**: The framework allows for human participation in conversations between agents. It is important to ensure that user data and conversations are protected and that developers use appropriate measures to safeguard privacy.
|
||||
- **Accountability and Transparency**: The framework involves multiple agents conversing and collaborating, it is important to establish clear accountability and transparency mechanisms. Users should be able to understand and trace the decision-making process of the agents involved in order to ensure accountability and address any potential issues or biases.
|
||||
- **Security & unintended consequences**: The use of multi-agent conversations and automation in complex tasks may have unintended consequences. Especially, allowing agents to make changes in external environments through tool calls or function execution could pose significant risks. Developers should carefully consider the potential risks and ensure that appropriate safeguards are in place to prevent harm or negative outcomes, including keeping a human in the loop for decision making.
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
kind: Prompt
|
||||
name: Assistant
|
||||
description: Helpful assistant
|
||||
instructions: You are a helpful assistant. You answer questions is the language specified by the user. You return your answers in a JSON format. You must include Assistants as the type in your response.
|
||||
instructions: You are a helpful assistant. You answer questions in the language specified by the user. You return your answers in a JSON format. You must include Assistants as the type in your response.
|
||||
model:
|
||||
id: =Env.AZURE_OPENAI_DEPLOYMENT_NAME
|
||||
id: gpt-4o-mini
|
||||
provider: AzureOpenAI
|
||||
apiType: Assistants
|
||||
options:
|
||||
@@ -12,14 +12,14 @@ model:
|
||||
outputSchema:
|
||||
properties:
|
||||
language:
|
||||
kind: string
|
||||
type: string
|
||||
required: true
|
||||
description: The language of the answer.
|
||||
answer:
|
||||
kind: string
|
||||
type: string
|
||||
required: true
|
||||
description: The answer text.
|
||||
type:
|
||||
kind: string
|
||||
type: string
|
||||
required: true
|
||||
description: The type of the response.
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
kind: Prompt
|
||||
name: Assistant
|
||||
description: Helpful assistant
|
||||
instructions: You are a helpful assistant. You answer questions in the language specified by the user. You return your answers in a JSON format. You must include Chat as the type in your response.
|
||||
model:
|
||||
id: gpt-4o-mini
|
||||
provider: AzureOpenAI
|
||||
apiType: Chat
|
||||
options:
|
||||
temperature: 0.9
|
||||
topP: 0.95
|
||||
outputSchema:
|
||||
properties:
|
||||
language:
|
||||
type: string
|
||||
required: true
|
||||
description: The language of the answer.
|
||||
answer:
|
||||
type: string
|
||||
required: true
|
||||
description: The answer text.
|
||||
type:
|
||||
type: string
|
||||
required: true
|
||||
description: The type of the response.
|
||||
@@ -1,28 +1,25 @@
|
||||
kind: Prompt
|
||||
name: Assistant
|
||||
description: Helpful assistant
|
||||
instructions: You are a helpful assistant. You answer questions is the language specified by the user. You return your answers in a JSON format. You must include Responses as the type in your response.
|
||||
instructions: You are a helpful assistant. You answer questions in the language specified by the user. You return your answers in a JSON format. You must include Responses as the type in your response.
|
||||
model:
|
||||
id: =Env.AZURE_OPENAI_DEPLOYMENT_NAME
|
||||
id: gpt-4o-mini
|
||||
provider: AzureOpenAI
|
||||
apiType: Responses
|
||||
options:
|
||||
text:
|
||||
verbosity: medium
|
||||
connection:
|
||||
kind: remote
|
||||
endpoint: =Env.AZURE_OPENAI_ENDPOINT
|
||||
temperature: 0.9
|
||||
topP: 0.95
|
||||
outputSchema:
|
||||
properties:
|
||||
language:
|
||||
kind: string
|
||||
type: string
|
||||
required: true
|
||||
description: The language of the answer.
|
||||
answer:
|
||||
kind: string
|
||||
type: string
|
||||
required: true
|
||||
description: The answer text.
|
||||
type:
|
||||
kind: string
|
||||
type: string
|
||||
required: true
|
||||
description: The type of the response.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
kind: Prompt
|
||||
name: Assistant
|
||||
description: Helpful assistant
|
||||
instructions: You are a helpful assistant. You answer questions is the language specified by the user. You return your answers in a JSON format.
|
||||
instructions: You are a helpful assistant. You answer questions in the language specified by the user. You return your answers in a JSON format.
|
||||
model:
|
||||
options:
|
||||
temperature: 0.9
|
||||
@@ -9,10 +9,10 @@ model:
|
||||
outputSchema:
|
||||
properties:
|
||||
language:
|
||||
kind: string
|
||||
type: string
|
||||
required: true
|
||||
description: The language of the answer.
|
||||
answer:
|
||||
kind: string
|
||||
type: string
|
||||
required: true
|
||||
description: The answer text.
|
||||
|
||||
@@ -4,6 +4,8 @@ description: Helpful assistant
|
||||
instructions: You are a helpful assistant. You answer questions using the tools provided.
|
||||
model:
|
||||
options:
|
||||
temperature: 0.9
|
||||
topP: 0.95
|
||||
allowMultipleToolCalls: true
|
||||
chatToolMode: auto
|
||||
tools:
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
kind: Prompt
|
||||
name: Assistant
|
||||
description: Helpful assistant
|
||||
instructions: You are a helpful assistant. You answer questions in the language specified by the user. You return your answers in a JSON format.
|
||||
model:
|
||||
id: gpt-4.1-mini
|
||||
options:
|
||||
temperature: 0.9
|
||||
topP: 0.95
|
||||
connection:
|
||||
kind: Remote
|
||||
endpoint: =Env.AZURE_FOUNDRY_PROJECT_ENDPOINT
|
||||
outputSchema:
|
||||
properties:
|
||||
language:
|
||||
type: string
|
||||
required: true
|
||||
description: The language of the answer.
|
||||
answer:
|
||||
type: string
|
||||
required: true
|
||||
description: The answer text.
|
||||
@@ -1,30 +1,28 @@
|
||||
kind: Prompt
|
||||
name: Assistant
|
||||
description: Helpful assistant
|
||||
instructions: You are a helpful assistant. You answer questions is the language specified by the user. You return your answers in a JSON format. You must include Assistants as the type in your response.
|
||||
instructions: You are a helpful assistant. You answer questions in the language specified by the user. You return your answers in a JSON format. You must include Assistants as the type in your response.
|
||||
model:
|
||||
id: =Env.OPENAI_MODEL
|
||||
id: gpt-4.1-mini
|
||||
provider: OpenAI
|
||||
apiType: Assistants
|
||||
options:
|
||||
temperature: 0.9
|
||||
topP: 0.95
|
||||
connection:
|
||||
kind: key
|
||||
kind: ApiKey
|
||||
key: =Env.OPENAI_APIKEY
|
||||
outputSchema:
|
||||
name: AssistantResponse
|
||||
description: The response from the assistant.
|
||||
properties:
|
||||
language:
|
||||
kind: string
|
||||
type: string
|
||||
required: true
|
||||
description: The language of the answer.
|
||||
answer:
|
||||
kind: string
|
||||
type: string
|
||||
required: true
|
||||
description: The answer text.
|
||||
type:
|
||||
kind: string
|
||||
type: string
|
||||
required: true
|
||||
description: The type of the response.
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
kind: Prompt
|
||||
name: Assistant
|
||||
description: Helpful assistant
|
||||
instructions: You are a helpful assistant. You answer questions in the language specified by the user. You return your answers in a JSON format. You must include Chat as the type in your response.
|
||||
model:
|
||||
id: gpt-4.1-mini
|
||||
provider: OpenAI
|
||||
apiType: Chat
|
||||
options:
|
||||
temperature: 0.9
|
||||
topP: 0.95
|
||||
connection:
|
||||
kind: ApiKey
|
||||
key: =Env.OPENAI_APIKEY
|
||||
outputSchema:
|
||||
properties:
|
||||
language:
|
||||
type: string
|
||||
required: true
|
||||
description: The language of the answer.
|
||||
answer:
|
||||
type: string
|
||||
required: true
|
||||
description: The answer text.
|
||||
type:
|
||||
type: string
|
||||
required: true
|
||||
description: The type of the response.
|
||||
@@ -1,28 +1,28 @@
|
||||
kind: Prompt
|
||||
name: Assistant
|
||||
description: Helpful assistant
|
||||
instructions: You are a helpful assistant. You answer questions is the language specified by the user. You return your answers in a JSON format. You must include Responses as the type in your response.
|
||||
instructions: You are a helpful assistant. You answer questions in the language specified by the user. You return your answers in a JSON format. You must include Responses as the type in your response.
|
||||
model:
|
||||
id: =Env.OPENAI_MODEL
|
||||
id: gpt-4.1-mini
|
||||
provider: OpenAI
|
||||
apiType: Responses
|
||||
options:
|
||||
text:
|
||||
verbosity: medium
|
||||
temperature: 0.9
|
||||
topP: 0.95
|
||||
connection:
|
||||
kind: key
|
||||
kind: ApiKey
|
||||
key: =Env.OPENAI_APIKEY
|
||||
outputSchema:
|
||||
properties:
|
||||
language:
|
||||
kind: string
|
||||
type: string
|
||||
required: true
|
||||
description: The language of the answer.
|
||||
answer:
|
||||
kind: string
|
||||
type: string
|
||||
required: true
|
||||
description: The answer text.
|
||||
type:
|
||||
kind: string
|
||||
type: string
|
||||
required: true
|
||||
description: The type of the response.
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<NoWarn>$(NoWarn);NU5128</NoWarn>
|
||||
<NoWarn>$(NoWarn);NU5128;CS8002</NoWarn>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<TargetFrameworksCore>net10.0;net9.0;net8.0</TargetFrameworksCore>
|
||||
<TargetFrameworks>$(TargetFrameworksCore);netstandard2.0;net472</TargetFrameworks>
|
||||
|
||||
@@ -7,10 +7,12 @@
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Aspire -->
|
||||
<AspireAppHostSdkVersion>13.0.0</AspireAppHostSdkVersion>
|
||||
<AspireAppHostSdkVersion>13.0.1</AspireAppHostSdkVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<!-- Aspire.* -->
|
||||
<PackageVersion Include="Anthropic" Version="11.0.0" />
|
||||
<PackageVersion Include="Anthropic.Foundry" Version="0.1.0" />
|
||||
<PackageVersion Include="Aspire.Azure.AI.OpenAI" Version="13.0.0-preview.1.25560.3" />
|
||||
<PackageVersion Include="Aspire.Hosting.AppHost" Version="$(AspireAppHostSdkVersion)" />
|
||||
<PackageVersion Include="Aspire.Hosting.Azure.CognitiveServices" Version="$(AspireAppHostSdkVersion)" />
|
||||
@@ -19,10 +21,17 @@
|
||||
<!-- 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.7" />
|
||||
<PackageVersion Include="Azure.AI.OpenAI" Version="2.5.0-beta.1" />
|
||||
<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.0" />
|
||||
<PackageVersion Include="Azure.Monitor.OpenTelemetry.Exporter" Version="1.4.0" />
|
||||
<!-- Google Gemini -->
|
||||
<PackageVersion Include="Google.GenAI" Version="0.6.0" />
|
||||
<PackageVersion Include="Mscc.GenerativeAI.Microsoft" Version="2.9.3" />
|
||||
<!-- Microsoft.Azure.* -->
|
||||
<PackageVersion Include="Microsoft.Azure.Cosmos" Version="3.54.0" />
|
||||
<!-- Newtonsoft.Json -->
|
||||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
|
||||
<!-- System.* -->
|
||||
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Bcl.HashCode" Version="6.0.0" />
|
||||
@@ -37,6 +46,7 @@
|
||||
<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" />
|
||||
@@ -91,7 +101,6 @@
|
||||
<!-- 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.6" />
|
||||
<PackageVersion Include="Microsoft.ML.OnnxRuntimeGenAI" Version="0.10.0" />
|
||||
<PackageVersion Include="OllamaSharp" Version="5.4.8" />
|
||||
@@ -127,6 +136,7 @@
|
||||
<PackageVersion Include="xunit" Version="2.9.3" />
|
||||
<PackageVersion Include="xunit.abstractions" Version="2.0.3" />
|
||||
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.3" />
|
||||
<PackageVersion Include="Xunit.SkippableFact" Version="1.5.23" />
|
||||
<PackageVersion Include="xretry" Version="1.9.0" />
|
||||
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
|
||||
<!-- Symbols -->
|
||||
@@ -153,20 +163,20 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageVersion Include="Roslynator.Analyzers" Version="[4.14.1]" />
|
||||
<PackageVersion Include="Roslynator.Analyzers" Version="4.14.1" />
|
||||
<PackageReference Include="Roslynator.Analyzers">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageVersion Include="Roslynator.CodeAnalysis.Analyzers" Version="[4.14.0]" />
|
||||
<PackageVersion Include="Roslynator.CodeAnalysis.Analyzers" Version="4.14.1" />
|
||||
<PackageReference Include="Roslynator.CodeAnalysis.Analyzers">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageVersion Include="Roslynator.Formatting.Analyzers" Version="[4.14.0]" />
|
||||
<PackageVersion Include="Roslynator.Formatting.Analyzers" Version="4.14.1" />
|
||||
<PackageReference Include="Roslynator.Formatting.Analyzers">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -40,16 +40,19 @@
|
||||
<Folder Name="/Samples/GettingStarted/A2A/">
|
||||
<File Path="samples/GettingStarted/A2A/README.md" />
|
||||
<Project Path="samples/GettingStarted/A2A/A2AAgent_AsFunctionTools/A2AAgent_AsFunctionTools.csproj" />
|
||||
<Project Path="samples/GettingStarted/A2A/A2AAgent_PollingForTaskCompletion/A2AAgent_PollingForTaskCompletion.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/GettingStarted/AgentProviders/">
|
||||
<File Path="samples/GettingStarted/AgentProviders/README.md" />
|
||||
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_A2A/Agent_With_A2A.csproj" />
|
||||
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_Anthropic/Agent_With_Anthropic.csproj" />
|
||||
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgentsPersistent/Agent_With_AzureAIAgentsPersistent.csproj" />
|
||||
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIProject/Agent_With_AzureAIProject.csproj" />
|
||||
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureFoundryModel/Agent_With_AzureFoundryModel.csproj" />
|
||||
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureOpenAIChatCompletion/Agent_With_AzureOpenAIChatCompletion.csproj" />
|
||||
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureOpenAIResponses/Agent_With_AzureOpenAIResponses.csproj" />
|
||||
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/Agent_With_CustomImplementation.csproj" />
|
||||
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_GoogleGemini/Agent_With_GoogleGemini.csproj" />
|
||||
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_Ollama/Agent_With_Ollama.csproj" />
|
||||
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_ONNX/Agent_With_ONNX.csproj" />
|
||||
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_OpenAIAssistants/Agent_With_OpenAIAssistants.csproj" />
|
||||
@@ -76,11 +79,44 @@
|
||||
<Project Path="samples/GettingStarted/Agents/Agent_Step16_ChatReduction/Agent_Step16_ChatReduction.csproj" />
|
||||
<Project Path="samples/GettingStarted/Agents/Agent_Step17_BackgroundResponses/Agent_Step17_BackgroundResponses.csproj" />
|
||||
<Project Path="samples/GettingStarted/Agents/Agent_Step18_DeepResearch/Agent_Step18_DeepResearch.csproj" />
|
||||
<Project Path="samples/GettingStarted/Agents/Agent_Step19_Declarative/Agent_Step19_Declarative.csproj" />
|
||||
</Folder>
|
||||
<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" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/GettingStarted/AgentWithAnthropic/">
|
||||
<File Path="samples/GettingStarted/AgentWithAnthropic/README.md" />
|
||||
<Project Path="samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step01_Running/Agent_Anthropic_Step01_Running.csproj" />
|
||||
<Project Path="samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step02_Reasoning/Agent_Anthropic_Step02_Reasoning.csproj" />
|
||||
<Project Path="samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step03_UsingFunctionTools/Agent_Anthropic_Step03_UsingFunctionTools.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/GettingStarted/AgentWithMemory/">
|
||||
<File Path="samples/GettingStarted/AgentWithMemory/README.md" />
|
||||
<Project Path="samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step01_ChatHistoryMemory/AgentWithMemory_Step01_ChatHistoryMemory.csproj" />
|
||||
@@ -91,6 +127,8 @@
|
||||
<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" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/Purview/" />
|
||||
<Folder Name="/Samples/Purview/AgentWithPurview/">
|
||||
@@ -160,7 +198,7 @@
|
||||
<Project Path="samples/GettingStarted/Workflows/Declarative/ToolApproval/ToolApproval.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/GettingStarted/Workflows/Declarative/Examples/">
|
||||
<File Path="../workflow-samples/ConfirmInput.yaml" />
|
||||
<File Path="../workflow-samples/CustomerSupport.yaml" />
|
||||
<File Path="../workflow-samples/DeepResearch.yaml" />
|
||||
<File Path="../workflow-samples/Marketing.yaml" />
|
||||
<File Path="../workflow-samples/MathChat.yaml" />
|
||||
@@ -311,6 +349,7 @@
|
||||
<File Path="src/Shared/Demos/SampleEnvironment.cs" />
|
||||
</Folder>
|
||||
<Folder Name="/Solution Items/src/Shared/IntegrationTests/">
|
||||
<File Path="src/Shared/IntegrationTests/AnthropicConfiguration.cs" />
|
||||
<File Path="src/Shared/IntegrationTests/AzureAIConfiguration.cs" />
|
||||
<File Path="src/Shared/IntegrationTests/Mem0Configuration.cs" />
|
||||
<File Path="src/Shared/IntegrationTests/OpenAIConfiguration.cs" />
|
||||
@@ -335,9 +374,12 @@
|
||||
<Project Path="src/Microsoft.Agents.AI.A2A/Microsoft.Agents.AI.A2A.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Abstractions/Microsoft.Agents.AI.Abstractions.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.AGUI/Microsoft.Agents.AI.AGUI.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Anthropic/Microsoft.Agents.AI.Anthropic.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.AzureAI.Persistent/Microsoft.Agents.AI.AzureAI.Persistent.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.AzureAI/Microsoft.Agents.AI.AzureAI.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.CopilotStudio/Microsoft.Agents.AI.CopilotStudio.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.CosmosNoSql/Microsoft.Agents.AI.CosmosNoSql.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Declarative/Microsoft.Agents.AI.Declarative.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.DevUI/Microsoft.Agents.AI.DevUI.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.DurableTask/Microsoft.Agents.AI.DurableTask.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Hosting.A2A.AspNetCore/Microsoft.Agents.AI.Hosting.A2A.AspNetCore.csproj" />
|
||||
@@ -357,6 +399,7 @@
|
||||
<Folder Name="/Tests/" />
|
||||
<Folder Name="/Tests/IntegrationTests/">
|
||||
<Project Path="tests/AgentConformance.IntegrationTests/AgentConformance.IntegrationTests.csproj" />
|
||||
<Project Path="tests/AnthropicChatCompletion.IntegrationTests/AnthropicChatCompletion.IntegrationTests.csproj" />
|
||||
<Project Path="tests/AzureAI.IntegrationTests/AzureAI.IntegrationTests.csproj" />
|
||||
<Project Path="tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistent.IntegrationTests.csproj" />
|
||||
<Project Path="tests/CopilotStudio.IntegrationTests/CopilotStudio.IntegrationTests.csproj" />
|
||||
@@ -373,8 +416,11 @@
|
||||
<Project Path="tests/Microsoft.Agents.AI.A2A.UnitTests/Microsoft.Agents.AI.A2A.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.Abstractions.UnitTests/Microsoft.Agents.AI.Abstractions.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.AGUI.UnitTests/Microsoft.Agents.AI.AGUI.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.Anthropic.UnitTests/Microsoft.Agents.AI.Anthropic.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.AzureAI.Persistent.UnitTests/Microsoft.Agents.AI.AzureAI.Persistent.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.AzureAI.UnitTests/Microsoft.Agents.AI.AzureAI.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.CosmosNoSql.UnitTests/Microsoft.Agents.AI.CosmosNoSql.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.Declarative.UnitTests/Microsoft.Agents.AI.Declarative.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.DevUI.UnitTests/Microsoft.Agents.AI.DevUI.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.DurableTask.UnitTests/Microsoft.Agents.AI.DurableTask.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/Microsoft.Agents.AI.Hosting.A2A.UnitTests.csproj" />
|
||||
@@ -389,4 +435,4 @@
|
||||
<Project Path="tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.Workflows.UnitTests/Microsoft.Agents.AI.Workflows.UnitTests.csproj" />
|
||||
</Folder>
|
||||
</Solution>
|
||||
</Solution>
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,9 @@
|
||||
<PropertyGroup>
|
||||
<!-- Central version prefix - applies to all nuget packages. -->
|
||||
<VersionPrefix>1.0.0</VersionPrefix>
|
||||
<PackageVersion Condition="'$(VersionSuffix)' != ''">$(VersionPrefix)-$(VersionSuffix).251114.1</PackageVersion>
|
||||
<PackageVersion Condition="'$(VersionSuffix)' == ''">$(VersionPrefix)-preview.251114.1</PackageVersion>
|
||||
<GitTag>1.0.0-preview.251114.1</GitTag>
|
||||
<PackageVersion Condition="'$(VersionSuffix)' != ''">$(VersionPrefix)-$(VersionSuffix).251204.1</PackageVersion>
|
||||
<PackageVersion Condition="'$(VersionSuffix)' == ''">$(VersionPrefix)-preview.251204.1</PackageVersion>
|
||||
<GitTag>1.0.0-preview.251204.1</GitTag>
|
||||
|
||||
<Configurations>Debug;Release;Publish</Configurations>
|
||||
<IsPackable>true</IsPackable>
|
||||
|
||||
@@ -14,11 +14,6 @@
|
||||
<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,6 +4,7 @@ using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OpenAI;
|
||||
using OpenAI.Chat;
|
||||
|
||||
namespace A2A;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
using OpenAI;
|
||||
using OpenAI.Chat;
|
||||
|
||||
namespace A2AServer;
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
// 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;
|
||||
|
||||
@@ -8,4 +12,12 @@ 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;
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
// 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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// 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; }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// 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; } = [];
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// 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;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AGUIDojoServer.AgenticUI;
|
||||
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<StepStatus>))]
|
||||
internal enum StepStatus
|
||||
{
|
||||
Pending,
|
||||
Completed
|
||||
}
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AGUIDojoServer;
|
||||
namespace AGUIDojoServer.BackendToolRendering;
|
||||
|
||||
internal sealed class WeatherInfo
|
||||
{
|
||||
@@ -2,10 +2,15 @@
|
||||
|
||||
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;
|
||||
using Microsoft.Extensions.AI;
|
||||
using OpenAI;
|
||||
using ChatClient = OpenAI.Chat.ChatClient;
|
||||
|
||||
namespace AGUIDojoServer;
|
||||
@@ -66,13 +71,46 @@ internal static class ChatClientAgentFactory
|
||||
description: "An agent that uses tools to generate user interfaces using Azure OpenAI");
|
||||
}
|
||||
|
||||
public static ChatClientAgent CreateAgenticUI()
|
||||
public static AIAgent CreateAgenticUI(JsonSerializerOptions options)
|
||||
{
|
||||
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.
|
||||
|
||||
return chatClient.AsIChatClient().CreateAIAgent(
|
||||
name: "AgenticUIAgent",
|
||||
description: "An agent that generates agentic user interfaces using Azure OpenAI");
|
||||
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);
|
||||
}
|
||||
|
||||
public static AIAgent CreateSharedState(JsonSerializerOptions options)
|
||||
@@ -86,6 +124,44 @@ 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()
|
||||
{
|
||||
@@ -95,4 +171,11 @@ 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";
|
||||
}
|
||||
}
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// 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;
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
// 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,11 +35,13 @@ 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;
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AGUIDojoServer;
|
||||
namespace AGUIDojoServer.SharedState;
|
||||
|
||||
internal sealed class Ingredient
|
||||
{
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AGUIDojoServer;
|
||||
namespace AGUIDojoServer.SharedState;
|
||||
|
||||
internal sealed class Recipe
|
||||
{
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AGUIDojoServer;
|
||||
namespace AGUIDojoServer.SharedState;
|
||||
|
||||
#pragma warning disable CA1812 // Used for the JsonSchema response format
|
||||
internal sealed class RecipeResponse
|
||||
+1
-1
@@ -6,7 +6,7 @@ using System.Text.Json;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace AGUIDojoServer;
|
||||
namespace AGUIDojoServer.SharedState;
|
||||
|
||||
[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Instantiated by ChatClientAgentFactory.CreateSharedState")]
|
||||
internal sealed class SharedStateAgent : DelegatingAIAgent
|
||||
@@ -6,7 +6,7 @@ using Azure.AI.OpenAI;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI.Hosting.AGUI.AspNetCore;
|
||||
using Microsoft.Extensions.AI;
|
||||
using OpenAI;
|
||||
using OpenAI.Chat;
|
||||
|
||||
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
|
||||
builder.Services.AddHttpClient().AddLogging();
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<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>
|
||||
@@ -0,0 +1,23 @@
|
||||
<!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);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<div class="lds-ellipsis"><div></div><div></div><div></div><div></div></div>
|
||||
@@ -0,0 +1,89 @@
|
||||
/* 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
@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>
|
||||
@@ -0,0 +1,20 @@
|
||||
#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;
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
@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();
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
.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;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
@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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
.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;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<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; }
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
.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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
@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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
.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;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
@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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
.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;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
@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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
.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;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// 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;
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
@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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
.suggestions {
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
gap: 0.5rem;
|
||||
justify-content: flex-end;
|
||||
flex-wrap: wrap;
|
||||
display: flex;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<Router AppAssembly="typeof(Program).Assembly">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
|
||||
<FocusOnNavigate RouteData="routeData" Selector="h1" />
|
||||
</Found>
|
||||
</Router>
|
||||
@@ -0,0 +1,12 @@
|
||||
@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
|
||||
@@ -0,0 +1,34 @@
|
||||
// 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();
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
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.
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,185 @@
|
||||
# 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" /> *@
|
||||
```
|
||||
@@ -0,0 +1,21 @@
|
||||
<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>
|
||||
@@ -0,0 +1,35 @@
|
||||
// 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();
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$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;
|
||||
using OpenAI.Chat;
|
||||
|
||||
// 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;
|
||||
using OpenAI.Chat;
|
||||
|
||||
// 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;
|
||||
using OpenAI.Chat;
|
||||
|
||||
// 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;
|
||||
using OpenAI.Chat;
|
||||
|
||||
// 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;
|
||||
using OpenAI.Chat;
|
||||
|
||||
// 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;
|
||||
using OpenAI.Chat;
|
||||
|
||||
// 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;
|
||||
using OpenAI.Chat;
|
||||
|
||||
// Get the Azure OpenAI endpoint and deployment name from environment variables.
|
||||
string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
|
||||
|
||||
-5
@@ -15,11 +15,6 @@
|
||||
<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;
|
||||
using OpenAI.Chat;
|
||||
|
||||
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";
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="A2A" />
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" />
|
||||
<PackageReference Include="System.Net.ServerSentEvents" />
|
||||
<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" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This sample demonstrates how to poll for long-running task completion using continuation tokens with an A2A AI agent.
|
||||
|
||||
using A2A;
|
||||
using Microsoft.Agents.AI;
|
||||
|
||||
var a2aAgentHost = Environment.GetEnvironmentVariable("A2A_AGENT_HOST") ?? throw new InvalidOperationException("A2A_AGENT_HOST is not set.");
|
||||
|
||||
// Initialize an A2ACardResolver to get an A2A agent card.
|
||||
A2ACardResolver agentCardResolver = new(new Uri(a2aAgentHost));
|
||||
|
||||
// Get the agent card
|
||||
AgentCard agentCard = await agentCardResolver.GetAgentCardAsync();
|
||||
|
||||
// Create an instance of the AIAgent for an existing A2A agent specified by the agent card.
|
||||
AIAgent agent = agentCard.GetAIAgent();
|
||||
|
||||
AgentThread thread = agent.GetNewThread();
|
||||
|
||||
// Start the initial run with a long-running task.
|
||||
AgentRunResponse response = await agent.RunAsync("Conduct a comprehensive analysis of quantum computing applications in cryptography, including recent breakthroughs, implementation challenges, and future roadmap. Please include diagrams and visual representations to illustrate complex concepts.", thread);
|
||||
|
||||
// Poll until the response is complete.
|
||||
while (response.ContinuationToken is { } token)
|
||||
{
|
||||
// Wait before polling again.
|
||||
await Task.Delay(TimeSpan.FromSeconds(2));
|
||||
|
||||
// Continue with the token.
|
||||
response = await agent.RunAsync(thread, options: new AgentRunOptions { ContinuationToken = token });
|
||||
}
|
||||
|
||||
// Display the result
|
||||
Console.WriteLine(response);
|
||||
@@ -0,0 +1,25 @@
|
||||
# Polling for A2A Agent Task Completion
|
||||
|
||||
This sample demonstrates how to poll for long-running task completion using continuation tokens with an A2A AI agent, following the background responses pattern.
|
||||
|
||||
The sample:
|
||||
|
||||
- Connects to an A2A agent server specified in the `A2A_AGENT_HOST` environment variable
|
||||
- Sends a request to the agent that may take time to complete
|
||||
- Polls the agent at regular intervals using continuation tokens until a final response is received
|
||||
- Displays the final result
|
||||
|
||||
This pattern is useful when an AI model cannot complete a complex task in a single response and needs multiple rounds of processing.
|
||||
|
||||
# Prerequisites
|
||||
|
||||
Before you begin, ensure you have the following prerequisites:
|
||||
|
||||
- .NET 10.0 SDK or later
|
||||
- An A2A agent server running and accessible via HTTP
|
||||
|
||||
Set the following environment variable:
|
||||
|
||||
```powershell
|
||||
$env:A2A_AGENT_HOST="http://localhost:5000" # Replace with your A2A agent server host
|
||||
```
|
||||
@@ -14,6 +14,7 @@ See the README.md for each sample for the prerequisites for that sample.
|
||||
|Sample|Description|
|
||||
|---|---|
|
||||
|[A2A Agent As Function Tools](./A2AAgent_AsFunctionTools/)|This sample demonstrates how to represent an A2A agent as a set of function tools, where each function tool corresponds to a skill of the A2A agent, and register these function tools with another AI agent so it can leverage the A2A agent's skills.|
|
||||
|[A2A Agent Polling For Task Completion](./A2AAgent_PollingForTaskCompletion/)|This sample demonstrates how to poll for long-running task completion using continuation tokens with an A2A agent.|
|
||||
|
||||
## Running the samples from the console
|
||||
|
||||
|
||||
@@ -0,0 +1,304 @@
|
||||
# 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
|
||||
@@ -0,0 +1,15 @@
|
||||
<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>
|
||||
@@ -0,0 +1,94 @@
|
||||
// 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}");
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// 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();
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"$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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<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>
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<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>
|
||||
@@ -0,0 +1,126 @@
|
||||
// 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}");
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
// 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;
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"$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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<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>
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user