Skip to content

Commit b05878a

Browse files
authored
Create and publish documentation site for 0.4 dotnet (#5275)
<img width="1840" alt="Screenshot 2025-01-30 at 6 26 02 PM" src="https://github.com/user-attachments/assets/5b4c9ebf-0880-4b2e-aa1f-f2b956922b49" />
1 parent 465a974 commit b05878a

25 files changed

+512
-260
lines changed

.github/workflows/docs.yml

+53-1
Original file line numberDiff line numberDiff line change
@@ -185,12 +185,64 @@ jobs:
185185
path: "website/artifact"
186186
name: "02-docs"
187187

188+
build-04-dotnet:
189+
runs-on: ubuntu-latest
190+
steps:
191+
- uses: actions/checkout@v4
192+
with:
193+
lfs: true
194+
- name: Setup .NET 8.0
195+
uses: actions/setup-dotnet@v4
196+
with:
197+
dotnet-version: '8.0.x'
198+
global-json-file: dotnet/global.json
199+
- run: dotnet tool update -g docfx
200+
- run: |
201+
mkdir -p ./build/dotnet/dev/
202+
docfx build docfx.json --output ./build/dotnet/dev/
203+
working-directory: ./docs/dotnet
204+
- name: insert clarity snippet to *.html
205+
working-directory: ./docs/dotnet/build/dotnet/dev/
206+
shell: python
207+
run: |
208+
import os
209+
clarity_script = """
210+
<script type="text/javascript">
211+
(function(c,l,a,r,i,t,y){
212+
c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
213+
t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
214+
y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
215+
})(window, document, "clarity", "script", "lnxpe6skj1");
216+
</script>
217+
"""
218+
219+
site_folder = '.'
220+
221+
for root, dirs, files in os.walk(site_folder):
222+
for file in files:
223+
if file.endswith('.html'):
224+
html_path = os.path.join(root, file)
225+
226+
# insert the script into the html's head section
227+
with open(html_path, 'r') as file:
228+
html = file.read()
229+
html = html.replace('</head>', clarity_script + '</head>')
230+
231+
with open(html_path, 'w') as file:
232+
file.write(html)
233+
234+
print(f'Clarity script inserted into {html_path}')
235+
- uses: actions/upload-artifact@v4
236+
with:
237+
path: "./docs/dotnet/build"
238+
name: "dotnet-dev-docs"
239+
188240
deploy:
189241
environment:
190242
name: github-pages
191243
url: ${{ steps.deployment.outputs.page_url }}
192244
runs-on: ubuntu-latest
193-
needs: [build-02, build-04, gen-redirects, gen-component-schema]
245+
needs: [build-02, build-04, build-04-dotnet, gen-redirects, gen-component-schema]
194246
if: ${{ needs.build-02.result == 'success' && needs.build-04.result == 'success' && needs.gen-redirects.result == 'success' && github.ref == 'refs/heads/main' }}
195247
steps:
196248
- uses: actions/download-artifact@v4

docs/dotnet/.gitignore

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
###############
2+
# folder #
3+
###############
4+
/**/DROP/
5+
/**/TEMP/
6+
/**/packages/
7+
/**/bin/
8+
/**/obj/
9+
10+
# build artifacts for web
11+
_site/
12+
api/

docs/dotnet/README.md

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
## How to build and run the website
2+
3+
### Prerequisites
4+
- dotnet 7.0 or later
5+
6+
### Build
7+
Firstly, go to autogen/dotnet folder and run the following command to build the website:
8+
```bash
9+
dotnet tool restore
10+
dotnet tool run docfx website/docfx.json --serve
11+
```
12+
13+
After the command is executed, you can open your browser and navigate to `http://localhost:8080` to view the website.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Differences from Python
2+
3+
## Publishing to a topic that an agent is also subscribed to
4+
5+
> [!NOTE]
6+
> TLDR; Default behavior is identical.
7+
8+
When an agent publishes a message to a topic to which it also listens, the message will not be received by the agent that sent it. This is also the behavior in the Python runtime. However to support previous usage, in @Microsoft.AutoGen.Core.InProcessRuntime, you can set the @Microsoft.AutoGen.Core.InProcessRuntime.DeliverToSelf property to true in the TopicSubscription attribute to allow an agent to receive messages it sends.

docs/dotnet/core/index.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# AutoGen Core
2+
3+
AutoGen Core for .NET follows the same concepts and conventions of its Python counterpart. In fact, in order to understand the concepts in the .NET version, we recommend reading the Python documentation first. Unless otherwise stated, the concepts in the Python version map to .NET.
4+
5+
Any important differences between the language versions are documented in the [Differences from Python](./differences-from-python.md) section. For things that only affect a given language, such as dependency injection or host builder patterns, these will not be specified in the differences document.
6+
7+
For .NET we are starting with the core functionality and will be expanding support progressively. So far the core abstractions of Agent and Runtime are available. The InProcessRuntime is the only runtime available at this time. We will be expanding to cross language support in upcoming releases.

docs/dotnet/core/installation.md

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Installation
2+
3+
Install via `.NET cli`
4+
5+
```sh
6+
dotnet add package Microsoft.AutoGen.Contracts --version 0.4.0-dev.1
7+
dotnet add package Microsoft.AutoGen.Core --version 0.4.0-dev.1
8+
```
9+
10+
Or, install via `Package Manager`
11+
12+
```pwsh
13+
PM> NuGet\Install-Package Microsoft.AutoGen.Contracts -Version 0.4.0-dev.1
14+
PM> NuGet\Install-Package Microsoft.AutoGen.Core -Version 0.4.0-dev.1
15+
```
16+
17+
Or, add via `<PackageReference>`
18+
19+
```xml
20+
<PackageReference Include="Microsoft.AutoGen.Contracts" Version="0.4.0-dev.1" />
21+
<PackageReference Include="Microsoft.AutoGen.Core" Version="0.4.0-dev.1" />
22+
```
+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Using Protocol Buffers to Define Message Types
2+
3+
For a message to be sent using a runtime other than the @Microsoft.AutoGen.Core.InProcessRuntime, it must be defined as a Protocol Buffers message. This is because the message is serialized and deserialized using Protocol Buffers. This requirement may be relaxed in future by allowing for converters, custom serialization, or other mechanisms.
4+
5+
## How to include Protocol Buffers in a .NET project
6+
7+
The .proto file which defines the message types must be included in the project, which will automatically generate the C# classes for the messages.
8+
9+
1. Include `Grpc.Tools` package in your `.csproj` file:
10+
11+
```xml
12+
<PackageReference Include="Grpc.Tools" PrivateAssets="All" />
13+
```
14+
15+
2. Create an include a `.proto` file in the project:
16+
17+
```xml
18+
<ItemGroup>
19+
<Protobuf Include="messages.proto" GrpcServices="Client;Server" Link="messages.proto" />
20+
</ItemGroup>
21+
```
22+
23+
3. define your messages as specified in the [Protocol Buffers Language Guide](https://protobuf.dev/programming-guides/proto3/)
24+
25+
```proto
26+
syntax = "proto3";
27+
28+
package HelloAgents;
29+
30+
option csharp_namespace = "MyAgentsProtocol";
31+
32+
message TextMessage {
33+
string Source = 1;
34+
string Content = 2;
35+
}
36+
```
37+
38+
4. Code against the generated class for handling, sending and publishing messages:
39+
40+
```csharp
41+
using Microsoft.AutoGen.Contracts;
42+
using Microsoft.AutoGen.Core;
43+
using MyAgentsProtocol;
44+
45+
[TypeSubscription("default")]
46+
public class Checker(
47+
AgentId id,
48+
IAgentRuntime runtime,
49+
) :
50+
BaseAgent(id, runtime, "MyAgent", null),
51+
IHandle<TextMessage>
52+
{
53+
public async ValueTask HandleAsync(TextMessage item, MessageContext messageContext)
54+
{
55+
Console.WriteLine($"Received message from {item.Source}: {item.Content}");
56+
}
57+
}
58+
```

docs/dotnet/core/toc.yml

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
- name: Overview
2+
href: index.md
3+
- name: Installation
4+
href: installation.md
5+
- name: Tutorial
6+
href: tutorial.md
7+
- name: Differences from Python
8+
href: differences-from-python.md
9+
- name: Protobuf message types
10+
href: protobuf-message-types.md

docs/dotnet/core/tutorial.md

+165
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
# Tutorial
2+
3+
> [!TIP]
4+
> If you'd prefer to just see the code the entire sample is available as a [project here](https://github.com/microsoft/autogen/tree/main/dotnet/samples/GettingStarted).
5+
6+
In this tutorial we are going to define two agents, `Modifier` and `Checker`, that will count down from 10 to 1. The `Modifier` agent will modify the count and the `Checker` agent will check the count and stop the application when the count reaches 1.
7+
8+
## Defining the message types
9+
10+
The first thing we need to do is to define the messages that will be passed between the agents, we're simply going to define them as classes.
11+
12+
We're going to use `CountMessage` to pass the current count and `CountUpdate` to pass the updated count.
13+
14+
[!code-csharp[](../../../dotnet/samples/GettingStarted/CountMessage.cs#snippet_CountMessage)]
15+
[!code-csharp[](../../../dotnet/samples/GettingStarted/CountUpdate.cs#snippet_CountUpdate)]
16+
17+
By separating out the message types into strongly typed classes, we can build a workflow where agents react to certain types and produce certain types.
18+
19+
## Creating the agents
20+
21+
### Inherit from `BaseAgent`
22+
23+
In AutoGen an agent is a class that can receive and send messages. The agent defines its own logic of what to do with the messages it receives. To define an agent, create a class that inherits from @Microsoft.AutoGen.Core.BaseAgent, like so:
24+
25+
```csharp
26+
using Microsoft.AutoGen.Contracts;
27+
using Microsoft.AutoGen.Core;
28+
29+
public class Modifier(
30+
AgentId id,
31+
IAgentRuntime runtime,
32+
) :
33+
BaseAgent(id, runtime, "MyAgent", null),
34+
{
35+
}
36+
```
37+
38+
We will see how to pass arguments to an agent when it is constructed, but for now you just need to know that @Microsoft.AutoGen.Contracts.AgentId and @Microsoft.AutoGen.Core.IAgentRuntime will always be passed to the constructor, and those should be forwarded to the base class constructor. The other two arguments are a description of the agent and an optional logger.
39+
40+
Learn more about what an Agent ID is [here](https://microsoft.github.io/autogen/stable/user-guide/core-user-guide/core-concepts/agent-identity-and-lifecycle.html#agent-id).
41+
42+
### Create a handler
43+
44+
Now, we want `Modifier` to receive `CountMessage` and produce `CountUpdate` after it modifies the count. To do this, we need to implement the `IHandle<CountMessage>` interface:
45+
46+
```csharp
47+
public class Modifier(
48+
// ...
49+
) :
50+
BaseAgent(...),
51+
IHandle<CountMessage>
52+
{
53+
54+
public async ValueTask HandleAsync(CountMessage item, MessageContext messageContext)
55+
{
56+
// ...
57+
}
58+
}
59+
```
60+
61+
### Add a subscription
62+
63+
We've defined a function that will be called when a `CountMessage` is delivered to this agent, but there is still one step before the message will actually be delivered to the agent. The agent must subscribe to the topic to the message is published to. We can do this by adding the `TypeSubscription` attribute to the class:
64+
65+
```csharp
66+
[TypeSubscription("default")]
67+
public class Modifier(
68+
// ...
69+
```
70+
71+
Learn more about topics and subscriptions [here](https://microsoft.github.io/autogen/stable/user-guide/core-user-guide/core-concepts/topic-and-subscription.html).
72+
73+
### Publish a message
74+
75+
Now that we have a handler for `CountMessage`, and we have the subscription in place we can publish a result out of the handler.
76+
77+
```csharp
78+
public async ValueTask HandleAsync(CountMessage item, MessageContext messageContext)
79+
{
80+
int newValue = item.Content - 1;
81+
Console.WriteLine($"\nModifier:\nModified {item.Content} to {newValue}");
82+
83+
CountUpdate updateMessage = new CountUpdate { NewCount = newValue };
84+
await this.PublishMessageAsync(updateMessage, topic: new TopicId("default"));
85+
}
86+
```
87+
88+
You'll notice that when we publish the message, we specify the topic to publish to. We're using a topic called `default` in this case, which is the same topic which we subscribed to. We could have used a different topic, but in this case we're keeping it simple.
89+
90+
### Passing arguments to the agent
91+
92+
Let's extend our agent to make what we do to the count configurable. We'll do this by passing a function to the agent that will be used to modify the count.
93+
94+
```csharp
95+
using ModifyF = System.Func<int, int>;
96+
97+
// ...
98+
99+
[TypeSubscription("default")]
100+
public class Modifier(
101+
AgentId id,
102+
IAgentRuntime runtime,
103+
ModifyF modifyFunc // <-- Add this
104+
) :
105+
BaseAgent(...),
106+
IHandle<CountMessage>
107+
{
108+
109+
public async ValueTask HandleAsync(CountMessage item, MessageContext messageContext)
110+
{
111+
int newValue = modifyFunc(item.Content); // <-- use it here
112+
113+
// ...
114+
}
115+
}
116+
117+
```
118+
119+
### Final Modifier implementation
120+
121+
Here is the final implementation of the Modifier agent:
122+
123+
[!code-csharp[](../../../dotnet/samples/GettingStarted/Modifier.cs#snippet_Modifier)]
124+
125+
### Checker
126+
127+
We'll also define a Checker agent that will check the count and stop the application when the count reaches 1. Additionally, we'll use dependency injection to get a reference to the `IHostApplicationLifetime` service, which we can use to stop the application.
128+
129+
[!code-csharp[](../../../dotnet/samples/GettingStarted/Checker.cs#snippet_Checker)]
130+
131+
## Putting it all together
132+
133+
Now that we have our agents defined, we can put them together in a simple application that will count down from 10 to 1.
134+
135+
After includes, the first thing to do is to define the two functions for modifying and checking for completion.
136+
137+
[!code-csharp[](../../../dotnet/samples/GettingStarted/Program.cs#snippet_Program_funcs)]
138+
139+
Then, we create a builder and do the following things:
140+
141+
- Specify that we are using the in process runtime
142+
- Register our functions as services
143+
- Register the agent classes we defined earlier
144+
- Finally, build and start our app
145+
146+
[!code-csharp[](../../../dotnet/samples/GettingStarted/Program.cs#snippet_Program_builder)]
147+
148+
The app is now running, but we need to kick off the process with a message. We do this by publishing a `CountMessage` with an initial value of 10.
149+
Importantly we publish this to the "default" topic which is what our agents are subscribed to. Finally, we wait for the application to stop.
150+
151+
[!code-csharp[](../../../dotnet/samples/GettingStarted/Program.cs#snippet_Program_publish)]
152+
153+
That's it! You should see the count down from 10 to 1 in the console.
154+
155+
Here's the full code for the `Program` class:
156+
157+
[!code-csharp[](../../../dotnet/samples/GettingStarted/Program.cs#snippet_Program)]
158+
159+
## Things to try
160+
161+
Here are some ideas to try with this sample:
162+
163+
- Change the initial count
164+
- Create a new modifier function that counts up instead. (Don't forget to change the checker too!)
165+
- Create an agent that outputs to the console instead of the modifier or checker agent doing it themselves (hint: use a new message type)

0 commit comments

Comments
 (0)