Skip to content

Commit

Permalink
Merge branch 'main' into rysweet-4321-implicitly-created-subscription…
Browse files Browse the repository at this point in the history
…-for-agent-rpc
  • Loading branch information
rysweet authored Nov 25, 2024
2 parents b45bcfd + 1a02e2b commit c379fc5
Show file tree
Hide file tree
Showing 77 changed files with 1,882 additions and 701 deletions.
21 changes: 21 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,27 @@ jobs:
poe --directory ${{ matrix.package }} docs-check
working-directory: ./python

docs-example-check:
runs-on: ubuntu-latest
strategy:
matrix:
package: ["./packages/autogen-core"]
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v3
with:
enable-cache: true
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- run: uv sync --locked --all-extras
working-directory: ./python
- name: Run task
run: |
source ${{ github.workspace }}/python/.venv/bin/activate
poe --directory ${{ matrix.package }} docs-check-examples
working-directory: ./python

check-proto-changes-python:
runs-on: ubuntu-latest
steps:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ jobs:
{ ref: "v0.4.0.dev4", dest-dir: "0.4.0.dev4" },
{ ref: "v0.4.0.dev5", dest-dir: "0.4.0.dev5" },
{ ref: "v0.4.0.dev6", dest-dir: "0.4.0.dev6" },
{ ref: "v0.4.0.dev7", dest-dir: "0.4.0.dev7" },
]
steps:
- name: Checkout
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ We look forward to your contributions!
First install the packages:

```bash
pip install 'autogen-agentchat==0.4.0.dev6' 'autogen-ext[openai]==0.4.0.dev6'
pip install 'autogen-agentchat==0.4.0.dev7' 'autogen-ext[openai]==0.4.0.dev7'
```

The following code uses OpenAI's GPT-4o model and you need to provide your
Expand Down
16 changes: 10 additions & 6 deletions docs/design/01 - Programming Model.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,27 @@ The programming model is basically publish-subscribe. Agents subscribe to events

## Events Delivered as CloudEvents

Each event in the system is defined using the [CloudEvents Specification](https://cloudevents.io/). This allows for a common event format that can be used across different systems and languages. In CloudEvents, each event has a Context Attributes that must unique *id* (eg a UUID) a *source* (a unique urn or path), a *type* (the namespace of the event - prefixed with a reverse-DNS name. The prefixed domain dictates the organization which defines the semantics of this event type: e.g *com.github.pull_request.opened* or
*com.example.object.deleted.v2*), and optionally fields describing the data schema/content-type or extensions.
Each event in the system is defined using the [CloudEvents Specification](https://cloudevents.io/). This allows for a common event format that can be used across different systems and languages. In CloudEvents, each event has "Context Attributes" that must include:

1. *id* - A unique id (eg. a UUID).
2. *source* - A URI or URN indicating the event's origin.
3. *type* - The namespace of the event - prefixed with a reverse-DNS name.
- The prefixed domain dictates the organization which defines the semantics of this event type: e.g `com.github.pull_request.opened` or `com.example.object.deleted.v2`), and optionally fields describing the data schema/content-type or extensions.

## Event Handlers

Each agent has a set of event handlers, that are bound to a specific match against a CloudEvents *type*. Event Handlers could match against an exact type or match for a pattern of events of a particular level in the type heirarchy (eg: *com.Microsoft.AutoGen.Agents.System.\** for all Events in the *System* namespace) Each event handler is a function that can change state, call models, access memory, call external tools, emit other events, and flow data to/from other systems. Each event handler can be a simple function or a more complex function that uses a state machine or other control logic.
Each agent has a set of event handlers, that are bound to a specific match against a CloudEvents *type*. Event Handlers could match against an exact type or match for a pattern of events of a particular level in the type heirarchy (eg: `com.Microsoft.AutoGen.Agents.System.*` for all Events in the `System` namespace) Each event handler is a function that can change state, call models, access memory, call external tools, emit other events, and flow data to/from other systems. Each event handler can be a simple function or a more complex function that uses a state machine or other control logic.

## Orchestrating Agents

If is possible to build a functional and scalable agent system that only reacts to external events. In many cases, however, you will want to orchestrate the agents to achieve a specific goal or follow a pre-determined workflow. In this case, you will need to build an orchestrator agent that manages the flow of events between agents.
It is possible to build a functional and scalable agent system that only reacts to external events. In many cases, however, you will want to orchestrate the agents to achieve a specific goal or follow a pre-determined workflow. In this case, you will need to build an orchestrator agent that manages the flow of events between agents.

## Built-in Event Types

The AutoGen system comes with a set of built-in event types that are used to manage the system. These include:

* System Events - Events that are used to manage the system itself. These include events for starting and stopping the Agents, sending messages to all agents, and other system-level events.
* ? insert other types here ?
- *System Events* - Events that are used to manage the system itself. These include events for starting and stopping the Agents, sending messages to all agents, and other system-level events.
- *Insert other types here*

## Agent Contracts

Expand Down
8 changes: 4 additions & 4 deletions docs/design/02 - Topics.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,16 @@ This document does not specify RPC/direct messaging
A topic is identified by two components (called a `TopicId`):

- [`type`](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md#type) - represents the type of event that occurs, this is static and defined in code
- SHOULD use reverse domain name notation to avoid naming conflicts. For example: `com.example.my-topic`.
- SHOULD use reverse domain name notation to avoid naming conflicts. For example: `com.example.my-topic`.
- [`source`](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md#source-1) - represents where the event originated from, this is dynamic and based on the message itself
- SHOULD be a URI
- SHOULD be a URI

Agent instances are identified by two components (called an `AgentId`):

- `type` - represents the type of agent, this is static and defined in code
- MUST be a valid identifier as defined [here](https://docs.python.org/3/reference/lexical_analysis.html#identifiers) except that only the ASCII range is allowed
- MUST be a valid identifier as defined [here](https://docs.python.org/3/reference/lexical_analysis.html#identifiers) except that only the ASCII range is allowed
- `key` - represents the instance of the agent type for the key
- SHOULD be a URI
- SHOULD be a URI

For example: `GraphicDesigner:1234`

Expand Down
2 changes: 1 addition & 1 deletion docs/design/03 - Agent Worker Protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Agents are never explicitly created or destroyed. When a request is received for

## Worker protocol flow

The worker protocol has three phases, following the lifetime of the worker: initiation, operation, and termination.
The worker protocol has three phases, following the lifetime of the worker: initialization, operation, and termination.

### Initialization

Expand Down
48 changes: 24 additions & 24 deletions docs/design/04 - Agent and Topic ID Specs.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,40 @@ This document describes the structure, constraints, and behavior of Agent IDs an

#### type

* Type: `string`
* Description: The agent type is not an agent class. It associates an agent with a specific factory function, which produces instances of agents of the same agent `type`. For example, different factory functions can produce the same agent class but with different constructor perameters.
* Constraints: UTF8 and only contain alphanumeric letters (a-z) and (0-9), or underscores (_). A valid identifier cannot start with a number, or contain any spaces.
* Examples:
* `code_reviewer`
* `WebSurfer`
* `UserProxy`
- Type: `string`
- Description: The agent type is not an agent class. It associates an agent with a specific factory function, which produces instances of agents of the same agent `type`. For example, different factory functions can produce the same agent class but with different constructor perameters.
- Constraints: UTF8 and only contain alphanumeric letters (a-z) and (0-9), or underscores (\_). A valid identifier cannot start with a number, or contain any spaces.
- Examples:
- `code_reviewer`
- `WebSurfer`
- `UserProxy`

#### key

* Type: `string`
* Description: The agent key is an instance identifier for the given agent `type`
* Constraints: UTF8 and only contain characters between (inclusive) ascii 32 (space) and 126 (~).
* Examples:
* `default`
* A memory address
* a UUID string
- Type: `string`
- Description: The agent key is an instance identifier for the given agent `type`
- Constraints: UTF8 and only contain characters between (inclusive) ascii 32 (space) and 126 (~).
- Examples:
- `default`
- A memory address
- a UUID string

## Topic ID

### Required Attributes

#### type

* Type: `string`
* Description: topic type is usually defined by application code to mark the type of messages the topic is for.
* Constraints: UTF8 and only contain alphanumeric letters (a-z) and (0-9), or underscores (_). A valid identifier cannot start with a number, or contain any spaces.
* Examples:
* `GitHub_Issues`
- Type: `string`
- Description: Topic type is usually defined by application code to mark the type of messages the topic is for.
- Constraints: UTF8 and only contain alphanumeric letters (a-z) and (0-9), or underscores (\_). A valid identifier cannot start with a number, or contain any spaces.
- Examples:
- `GitHub_Issues`

#### source

* Type: `string`
* Description: Topic source is the unique identifier for a topic within a topic type. It is typically defined by application data.
* Constraints: UTF8 and only contain characters between (inclusive) ascii 32 (space) and 126 (~).
* Examples:
* `github.com/{repo_name}/issues/{issue_number}`
- Type: `string`
- Description: Topic source is the unique identifier for a topic within a topic type. It is typically defined by application data.
- Constraints: UTF8 and only contain characters between (inclusive) ascii 32 (space) and 126 (~).
- Examples:
- `github.com/{repo_name}/issues/{issue_number}`
7 changes: 6 additions & 1 deletion docs/switcher.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,12 @@
{
"name": "0.4.0.dev6",
"version": "0.4.0.dev6",
"url": "/autogen/0.4.0.dev6/",
"url": "/autogen/0.4.0.dev6/"
},
{
"name": "0.4.0.dev7",
"version": "0.4.0.dev7",
"url": "/autogen/0.4.0.dev7/",
"preferred": true
}
]
8 changes: 8 additions & 0 deletions dotnet/AutoGen.v3.ncrunchsolution
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<SolutionConfiguration>
<Settings>
<AllowParallelTestExecution>True</AllowParallelTestExecution>
<EnableRDI>True</EnableRDI>
<RdiConfigured>True</RdiConfigured>
<SolutionConfigured>True</SolutionConfigured>
</Settings>
</SolutionConfiguration>
6 changes: 5 additions & 1 deletion dotnet/samples/Hello/HelloAgent/HelloAgent.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" />
</ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions dotnet/src/AutoGen.OpenAI/AutoGen.OpenAI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

<ItemGroup>
<PackageReference Include="OpenAI" Version="$(OpenAISDKVersion)" />
<ProjectReference Include="..\AutoGen.SourceGenerator\AutoGen.SourceGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>

<ItemGroup>
Expand Down
133 changes: 133 additions & 0 deletions dotnet/src/AutoGen.OpenAI/Orchestrator/RolePlayToolCallOrchestrator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// RolePlayToolCallOrchestrator.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using AutoGen.OpenAI.Extension;
using OpenAI.Chat;

namespace AutoGen.OpenAI.Orchestrator;

/// <summary>
/// Orchestrating group chat using role play tool call
/// </summary>
public partial class RolePlayToolCallOrchestrator : IOrchestrator
{
public readonly ChatClient chatClient;
private readonly Graph? workflow;

public RolePlayToolCallOrchestrator(ChatClient chatClient, Graph? workflow = null)
{
this.chatClient = chatClient;
this.workflow = workflow;
}

public async Task<IAgent?> GetNextSpeakerAsync(
OrchestrationContext context,
CancellationToken cancellationToken = default)
{
var candidates = context.Candidates.ToList();

if (candidates.Count == 0)
{
return null;
}

if (candidates.Count == 1)
{
return candidates.First();
}

// if there's a workflow
// and the next available agent from the workflow is in the group chat
// then return the next agent from the workflow
if (this.workflow != null)
{
var lastMessage = context.ChatHistory.LastOrDefault();
if (lastMessage == null)
{
return null;
}
var currentSpeaker = candidates.First(candidates => candidates.Name == lastMessage.From);
var nextAgents = await this.workflow.TransitToNextAvailableAgentsAsync(currentSpeaker, context.ChatHistory, cancellationToken);
nextAgents = nextAgents.Where(nextAgent => candidates.Any(candidate => candidate.Name == nextAgent.Name));
candidates = nextAgents.ToList();
if (!candidates.Any())
{
return null;
}

if (candidates is { Count: 1 })
{
return candidates.First();
}
}

// In this case, since there are more than one available agents from the workflow for the next speaker
// We need to invoke LLM to select the next speaker via select next speaker function

var chatHistoryStringBuilder = new StringBuilder();
foreach (var message in context.ChatHistory)
{
var chatHistoryPrompt = $"{message.From}: {message.GetContent()}";

chatHistoryStringBuilder.AppendLine(chatHistoryPrompt);
}

var chatHistory = chatHistoryStringBuilder.ToString();

var prompt = $"""
# Task: Select the next speaker
You are in a role-play game. Carefully read the conversation history and select the next speaker from the available roles.
# Conversation
{chatHistory}
# Available roles
- {string.Join(",", candidates.Select(candidate => candidate.Name))}
Select the next speaker from the available roles and provide a reason for your selection.
""";

// enforce the next speaker to be selected by the LLM
var option = new ChatCompletionOptions
{
ToolChoice = ChatToolChoice.CreateFunctionChoice(this.SelectNextSpeakerFunctionContract.Name),
};

option.Tools.Add(this.SelectNextSpeakerFunctionContract.ToChatTool());
var toolCallMiddleware = new FunctionCallMiddleware(
functions: [this.SelectNextSpeakerFunctionContract],
functionMap: new Dictionary<string, Func<string, Task<string>>>
{
[this.SelectNextSpeakerFunctionContract.Name] = this.SelectNextSpeakerWrapper,
});

var selectAgent = new OpenAIChatAgent(
chatClient,
"admin",
option)
.RegisterMessageConnector()
.RegisterMiddleware(toolCallMiddleware);

var reply = await selectAgent.SendAsync(prompt);

var nextSpeaker = candidates.FirstOrDefault(candidate => candidate.Name == reply.GetContent());

return nextSpeaker;
}

/// <summary>
/// Select the next speaker by name and reason
/// </summary>
[Function]
public async Task<string> SelectNextSpeaker(string name, string reason)
{
return name;
}
}
6 changes: 3 additions & 3 deletions dotnet/src/Microsoft.AutoGen/Abstractions/IAgentRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ public interface IAgentRuntime
ValueTask SendRequestAsync(IAgentBase agent, RpcRequest request, CancellationToken cancellationToken = default);
ValueTask SendMessageAsync(Message message, CancellationToken cancellationToken = default);
ValueTask PublishEventAsync(CloudEvent @event, CancellationToken cancellationToken = default);
void Update(Activity? activity, RpcRequest request);
void Update(Activity? activity, CloudEvent cloudEvent);
(string?, string?) GetTraceIDandState(IDictionary<string, string> metadata);
void Update(RpcRequest request, Activity? activity);
void Update(CloudEvent cloudEvent, Activity? activity);
(string?, string?) GetTraceIdAndState(IDictionary<string, string> metadata);
IDictionary<string, string> ExtractMetadata(IDictionary<string, string> metadata);
}
20 changes: 18 additions & 2 deletions dotnet/src/Microsoft.AutoGen/Abstractions/IAgentState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,24 @@

namespace Microsoft.AutoGen.Abstractions;

/// <summary>
/// Interface for managing the state of an agent.
/// </summary>
public interface IAgentState
{
ValueTask<AgentState> ReadStateAsync();
ValueTask<string> WriteStateAsync(AgentState state, string eTag);
/// <summary>
/// Reads the current state of the agent asynchronously.
/// </summary>
/// <param name="cancellationToken">A token to cancel the operation.</param>
/// <returns>A task that represents the asynchronous read operation. The task result contains the current state of the agent.</returns>
ValueTask<AgentState> ReadStateAsync(CancellationToken cancellationToken = default);

/// <summary>
/// Writes the specified state of the agent asynchronously.
/// </summary>
/// <param name="state">The state to write.</param>
/// <param name="eTag">The ETag for concurrency control.</param>
/// <param name="cancellationToken">A token to cancel the operation.</param>
/// <returns>A task that represents the asynchronous write operation. The task result contains the ETag of the written state.</returns>
ValueTask<string> WriteStateAsync(AgentState state, string eTag, CancellationToken cancellationToken = default);
}
Loading

0 comments on commit c379fc5

Please sign in to comment.