Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Further work on .NET SDK documentation:
- Agents
- Overview
- ASP.NET integration
  • Loading branch information
ckpearson committed May 28, 2025
commit 034ca1898fde0204bd590f6af8cca87416972cf0
2 changes: 1 addition & 1 deletion docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
},
{
"group": ".NET",
"pages": ["sdk/dotnet/overview"]
"pages": ["sdk/dotnet/overview", "sdk/dotnet/agents", "sdk/dotnet/aspnet"]
}
]
}
Expand Down
288 changes: 288 additions & 0 deletions docs/sdk/dotnet/agents.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
---
title: "Agents"
description: "Agent User Interaction Protocol .NET SDK Agent functionality"
---

# Agents

The Agent User Interaction Protocol SDK provides out-of-the-box agent
implementations that you can customise and build upon, as well as a low-level
interface for implementing your own agents from scratch.

## Low-Level

You can implement your own agents using the low-level interface, which gives you
the most flexibility and control over the agent's behaviour.

Reasons you might want to use the interface directly include:

- You need to implement a custom agent that uses a different agent framework.
- You need to implement a custom agent that doesn't fit the standard LLM-backed
conversational flow.

### The `IAGUIAgent` Interface

```csharp
public interface IAGUIAgent
{
Task RunAsync(
RunAgentInput input,
ChannelWriter<BaseEvent> events,
CancellationToken cancellationToken = default
);
}
```

All of the agents in the SDK implement this interface, which provides a simple
and extensible contract for running Agent User Interaction Protocol compatible
agents with the `RunAsync` method that takes the following parameters:

| Property | Type | Description |
| ------------------- | -------------------------- | -------------------------------------- |
| `input` | `RunAgentInput` | Input parameters for running the agent |
| `events` | `ChannelWriter<BaseEvent>` | Channel to write events to |
| `cancellationToken` | `CancellationToken` | Token to cancel the operation |

#### Invocation Helpers

You can construct your own channel to pass to the `RunAsync` method as follows:

```csharp
var channel = Channel.CreateUnbounded<BaseEvent>(
new UnboundedChannelOptions {
SingleReader = true,
SingleWriter = false,
AllowSynchronousContinuations = true,
}
);

_ = Task.Run(async () => {
await agent.RunAsync(
input,
channel.Writer,
cancellationToken
);
});

await foreach (var emittedEvent in
channel.Reader.ReadAllAsync(cancellationToken))
{
// Handle the emitted event
}
```

The SDK provides an extension method that takes care of this for you:

```csharp
await foreach(var emittedEvent in
agent.RunToCompletionAsync(input, cancellationToken))
{
// Handle the emitted event
}
```

### `EchoAgent` Example

The SDK includes a simple `EchoAgent` that implements the interface:

````csharp
public sealed class EchoAgent : IAGUIAgent
{
public async Task RunAsync(
RunAgentInput input,
ChannelWriter<BaseEvent> events,
CancellationToken ct = default
)
{
await events.WriteAsync(
new RunStartedEvent
{
ThreadId = input.ThreadId,
RunId = input.RunId,
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
},
ct
);

var lastMessage = input.Messages.LastOrDefault();

await Task.Delay(500, ct);

switch (lastMessage)
{
case SystemMessage system:
foreach (var ev in EventHelpers.SendSimpleMessage(
$"Echoing system message:\n\n```\n{system.Content}\n```\n"
))
{
await events.WriteAsync(ev, ct);
}
break;

case UserMessage user:
foreach (var ev in EventHelpers.SendSimpleMessage(
$"Echoing user message:\n\n```\n{user.Content}\n```\n"
))
{
await events.WriteAsync(ev, ct);
}
break;

case AssistantMessage assistant:
foreach (var ev in EventHelpers.SendSimpleMessage(
$"Echoing assistant message:\n\n```\n{assistant.Content}\n```\n"
))
{
await events.WriteAsync(ev, ct);
}
break;

case ToolMessage tool:
foreach (var ev in EventHelpers.SendSimpleMessage(
$"Echoing tool message for tool call '{tool.ToolCallId}':\n\n```\n{tool.Content}\n```\n"
))
{
await events.WriteAsync(ev, ct);
}
break;

default:
foreach (var ev in EventHelpers.SendSimpleMessage(
$"Unknown message type: {lastMessage?.GetType().Name ?? "null"}"
))
{
await events.WriteAsync(ev, ct);
}
break;
}

await events.WriteAsync(
new RunFinishedEvent
{
ThreadId = input.ThreadId,
RunId = input.RunId,
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
},
ct
);

events.Complete();
}
}
````

This simple agent echoes the last message it receives, but demonstrates the core
lifecycle events and how to emit Agent User Interaction Protocol events using
the provided channel writer.

## `Microsoft.Extensions.AI` Integration

<Note>
The SDK uses the `Microsoft.Extensions.AI` package, not the
`Microsoft.Extensions.AI.Abstractions` package, as it needs some of the
concrete implementations to handle function calling and other features.
</Note>

The SDK takes a dependency on the
[Microsoft.Extensions.AI](https://learn.microsoft.com/en-us/dotnet/ai/microsoft-extensions-ai)
package which means you can use the `IChatClient` abstractions to power your
agents - and bring all the LLM providers you already use.

### The `ChatClientAgent` Class

This class can be used standalone or as a base class for your own agents. It
provides for the core scenario of running an agent that involves an LLM
conversational flow, and handles the boilerplate of emitting lifecycle events
and handling backend and frontend tool calls.

```csharp
var myProviderIChatClient = GetProviderIChatClient();

var chatClient = new ChatClientBuilder(myProviderIChatClient)
.UseFunctionInvocation()
.Build();

var agent = new ChatClientAgent(
chatClient,
new ChatClientAgentOptions
{
SystemMessage = "You can override the system message passed in the RunAgentInput",
ChatOptions = new ChatOptions
{
Tools = [
AiFunctionFactory.Create(
MyDotnetDelegate
)
]
}
}
);
```

<Card
title="ChatClientAgent Reference"
icon="cube"
href="/sdk/dotnet/agent-types/chatclientagent"
color="#3B82F6"
iconType="solid"
>
Complete documentation of the `ChatClientAgent` class
</Card>

### The `StatefulChatClientAgent` Class

This agent extends the `ChatClientAgent` class and is tailored for scenarios
where the agent collaborates with the frontend to maintain a shared state.

An example of this could be where you have some state in the frontend that
represents the task the user is working on, and you want the agent to be able to
read and write to that state.

```csharp
record Recipe(
string Name,
string Description,
string Ingredients,
string Instructions
);

var myProviderIChatClient = GetProviderIChatClient();
var chatClient = new ChatClientBuilder(myProviderIChatClient)
.UseFunctionInvocation()
.Build();

var agent = new StatefulChatClientAgent<Recipe>(
chatClient,
// Define initial state for the agent to begin with if not provided by the run input
new Recipe(),
new StatefulChatClientAgentOptions<Recipe>
{
SystemMessage = "You can override the system message passed in the RunAgentInput",
ChatOptions = new ChatOptions
{
Tools = [
AiFunctionFactory.Create(
MyDotnetDelegate
)
]
}
}
)
```

This agent will use your provided system message, and tweak it to direct the LLM
to read and write to the shared state of type `Recipe` that you provide.

It registers new backend tools that allow the agent to do so, and emits the
necessary Agent User Interaction Protocol events to keep the frontend in sync
with the agent's state.

<Card
title="StatefulChatClientAgent Reference"
icon="cube"
href="/sdk/dotnet/agent-types/statefulchatclientagent"
color="#3B82F6"
iconType="solid"
>
Complete documentation of the `StatefulChatClientAgent` class
</Card>
39 changes: 39 additions & 0 deletions docs/sdk/dotnet/aspnet.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
title: "ASP.NET"
description: "Agent User Interaction Protocol .NET SDK ASP.NET integration"
---

The most common way to integrate Agent User Interaction Protocol agents
currently is over HTTP which frontends like
[CopilotKit](https://www.copilotkit.ai) can use via the AG-UI
[HttpAgent](/concepts/agents#httpagent) agent implementation.

For that reason the .NET SDK provides a simple extension method to register a
[Minimal API](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis/overview)
POST endpoint that can be used to expose agents:

```csharp
builder.Services.AddChatClient(
new ChatClientBuilder(myProviderIChatClient)
.UseFunctionInvocation()
);

var app = builder.Build();

var agentsGroup = app.MapGroup("/agents");

agentsGroup.MapAgentEndpoint(
"my-agent-name",
sp => {
var chatClient = sp.GetRequiredService<IChatClient>();
return new ChatClientAgent(chatClient);
}
);
```

This will create a POST endpoint at `/agents/my-agent-name` that accepts the
`RunAgentInput` body and returns a server-sent-events stream of the Agent User
Interaction Protocol events by invoking the provided agent.

You can configure the endpoint like any other one, including adding all the same
authentication / authorization / policy behaviour you would expect from ASP.NET.
31 changes: 31 additions & 0 deletions docs/sdk/dotnet/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ baseline agent implementations for building AG-UI compatible agents using the
[Microsoft.Extensions.AI](https://learn.microsoft.com/en-us/dotnet/ai/microsoft-extensions-ai)
`IChatClient` abstractions.

## .NET Version Support

The AGUIDotnet package currently targets the .NET 9 SDK, and includes a
framework reference for `Microsoft.AspNetCore.App` in order to provide helpful
integrations for hosting agents via endpoints.

## Types

Core data structures that represent building blocks of the system:
Expand Down Expand Up @@ -65,3 +71,28 @@ Events that power communication between agents and frontends:
>
Complete documentation of all events in the AGUIDotnet package
</Card>

## Agents

While you can use just the [Types](/sdk/dotnet/types) and
[Events](/sdk/dotnet/events) directly, the SDK has several agent abstractions
and implementations available.

- [IAGUIAgent](/sdk/dotnet/agents#the-iaguiagent-interface) - Low-level
interface for implementing your own agents from scratch
- [ChatClientAgent](/sdk/dotnet/agents#chat-client-agent) - A customisable
conversational agent backed by an instance of `IChatClient` that supports
backend & frontend tool calls
- [StatefulChatClientAgent](/sdk/dotnet/agents/#stateful-chat-client-agent) - A
derived type of `ChatClientAgent`, provides the same functionality and
customisation, while providing collaboration via shared agentic state

<Card
title="Agents Reference"
icon="cube"
href="/sdk/dotnet/agents"
color="#3B82F6"
iconType="solid"
>
Complete documentation of all agent functionality in the AGUIDotnet package
</Card>