diff --git a/README.md b/README.md index 8a4b8ba507be..a876fa19ea26 100644 --- a/README.md +++ b/README.md @@ -181,7 +181,7 @@ var app = await App.PublishMessageAsync("HelloAgents", new NewMessageReceived await App.RuntimeApp!.WaitForShutdownAsync(); await app.WaitForShutdownAsync(); -[TopicSubscription("HelloAgents")] +[TopicSubscription("agents")] public class HelloAgent( IAgentContext context, [FromKeyedServices("EventTypes")] EventTypes typeRegistry) : ConsoleAgent( diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props index 517eaf735c34..008c28647371 100644 --- a/dotnet/Directory.Packages.props +++ b/dotnet/Directory.Packages.props @@ -14,6 +14,7 @@ + diff --git a/dotnet/samples/Hello/Hello.AppHost/Hello.AppHost.csproj b/dotnet/samples/Hello/Hello.AppHost/Hello.AppHost.csproj index 5ce0d0531faf..370d13fb32e2 100644 --- a/dotnet/samples/Hello/Hello.AppHost/Hello.AppHost.csproj +++ b/dotnet/samples/Hello/Hello.AppHost/Hello.AppHost.csproj @@ -14,6 +14,7 @@ + diff --git a/dotnet/samples/Hello/Hello.AppHost/Program.cs b/dotnet/samples/Hello/Hello.AppHost/Program.cs index 326eddbcc9ec..f261f1eae325 100644 --- a/dotnet/samples/Hello/Hello.AppHost/Program.cs +++ b/dotnet/samples/Hello/Hello.AppHost/Program.cs @@ -5,15 +5,23 @@ var builder = DistributedApplication.CreateBuilder(args); var backend = builder.AddProject("backend").WithExternalHttpEndpoints(); -builder.AddProject("client") +var client = builder.AddProject("HelloAgentsDotNET") .WithReference(backend) - .WithEnvironment("AGENT_HOST", $"{backend.GetEndpoint("https").Property(EndpointProperty.Url)}") + .WithEnvironment("AGENT_HOST", backend.GetEndpoint("https")) + .WithEnvironment("STAY_ALIVE_ON_GOODBYE", "true") .WaitFor(backend); - +#pragma warning disable ASPIREHOSTINGPYTHON001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. +// xlang is over http for now - in prod use TLS between containers +builder.AddPythonApp("HelloAgentsPython", "../../../../python/packages/autogen-core/samples/xlang/hello_python_agent", "hello_python_agent.py", "../../../../../.venv") + .WithReference(backend) + .WithEnvironment("AGENT_HOST", backend.GetEndpoint("http")) + .WithEnvironment("STAY_ALIVE_ON_GOODBYE", "true") + .WithEnvironment("GRPC_DNS_RESOLVER", "native") + .WithOtlpExporter() + .WaitFor(client); +#pragma warning restore ASPIREHOSTINGPYTHON001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. using var app = builder.Build(); - await app.StartAsync(); var url = backend.GetEndpoint("http").Url; Console.WriteLine("Backend URL: " + url); - await app.WaitForShutdownAsync(); diff --git a/dotnet/samples/Hello/HelloAIAgents/HelloAIAgent.cs b/dotnet/samples/Hello/HelloAIAgents/HelloAIAgent.cs index d2ba81e659a4..4b8d663de193 100644 --- a/dotnet/samples/Hello/HelloAIAgents/HelloAIAgent.cs +++ b/dotnet/samples/Hello/HelloAIAgents/HelloAIAgent.cs @@ -6,7 +6,7 @@ using Microsoft.Extensions.AI; namespace Hello; -[TopicSubscription("HelloAgents")] +[TopicSubscription("agents")] public class HelloAIAgent( IAgentRuntime context, [FromKeyedServices("EventTypes")] EventTypes typeRegistry, diff --git a/dotnet/samples/Hello/HelloAIAgents/Program.cs b/dotnet/samples/Hello/HelloAIAgents/Program.cs index 9612a0a07951..891c026f943c 100644 --- a/dotnet/samples/Hello/HelloAIAgents/Program.cs +++ b/dotnet/samples/Hello/HelloAIAgents/Program.cs @@ -30,7 +30,7 @@ namespace Hello { - [TopicSubscription("HelloAgents")] + [TopicSubscription("agents")] public class HelloAgent( IAgentRuntime context, [FromKeyedServices("EventTypes")] EventTypes typeRegistry, diff --git a/dotnet/samples/Hello/HelloAgent/Program.cs b/dotnet/samples/Hello/HelloAgent/Program.cs index 4f74520a71e0..ce3fed2f61d7 100644 --- a/dotnet/samples/Hello/HelloAgent/Program.cs +++ b/dotnet/samples/Hello/HelloAgent/Program.cs @@ -6,25 +6,17 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -// step 1: create in-memory agent runtime - -// step 2: register HelloAgent to that agent runtime - -// step 3: start the agent runtime - -// step 4: send a message to the agent - -// step 5: wait for the agent runtime to shutdown +var local = true; +if (Environment.GetEnvironmentVariable("AGENT_HOST") != null) { local = false; } var app = await AgentsApp.PublishMessageAsync("HelloAgents", new NewMessageReceived { Message = "World" -}, local: true); -//var app = await AgentsApp.StartAsync(); +}, local: local).ConfigureAwait(false); await app.WaitForShutdownAsync(); namespace Hello { - [TopicSubscription("HelloAgents")] + [TopicSubscription("agents")] public class HelloAgent( IAgentRuntime context, IHostApplicationLifetime hostApplicationLifetime, [FromKeyedServices("EventTypes")] EventTypes typeRegistry) : AgentBase( @@ -53,7 +45,10 @@ public async Task Handle(ConversationClosed item) var goodbye = $"********************* {item.UserId} said {item.UserMessage} ************************"; var evt = new Output { Message = goodbye }; await PublishMessageAsync(evt).ConfigureAwait(true); - await PublishMessageAsync(new Shutdown()).ConfigureAwait(false); + if (Environment.GetEnvironmentVariable("STAY_ALIVE_ON_GOODBYE") != "true") + { + await PublishMessageAsync(new Shutdown()).ConfigureAwait(false); + } } public async Task Handle(Shutdown item) diff --git a/dotnet/samples/Hello/HelloAgentState/Program.cs b/dotnet/samples/Hello/HelloAgentState/Program.cs index 664689de824d..e3c9dd2121ca 100644 --- a/dotnet/samples/Hello/HelloAgentState/Program.cs +++ b/dotnet/samples/Hello/HelloAgentState/Program.cs @@ -15,7 +15,7 @@ namespace Hello { - [TopicSubscription("HelloAgents")] + [TopicSubscription("agents")] public class HelloAgent( IAgentRuntime context, IHostApplicationLifetime hostApplicationLifetime, diff --git a/dotnet/samples/Hello/protos/agent_events.proto b/dotnet/samples/Hello/protos/agent_events.proto new file mode 100644 index 000000000000..64ef2d69d604 --- /dev/null +++ b/dotnet/samples/Hello/protos/agent_events.proto @@ -0,0 +1,43 @@ +syntax = "proto3"; + +package HelloAgents; + +option csharp_namespace = "Microsoft.AutoGen.Abstractions"; +message TextMessage { + string textMessage = 1; + string source = 2; +} +message Input { + string message = 1; +} +message InputProcessed { + string route = 1; +} +message Output { + string message = 1; +} +message OutputWritten { + string route = 1; +} +message IOError { + string message = 1; +} +message NewMessageReceived { + string message = 1; +} +message ResponseGenerated { + string response = 1; +} +message GoodBye { + string message = 1; +} +message MessageStored { + string message = 1; +} +message ConversationClosed { + string user_id = 1; + string user_message = 2; +} +message Shutdown { + string message = 1; +} diff --git a/dotnet/src/Microsoft.AutoGen/Abstractions/MessageExtensions.cs b/dotnet/src/Microsoft.AutoGen/Abstractions/MessageExtensions.cs index 2c8f5d053063..c686b437bdc3 100644 --- a/dotnet/src/Microsoft.AutoGen/Abstractions/MessageExtensions.cs +++ b/dotnet/src/Microsoft.AutoGen/Abstractions/MessageExtensions.cs @@ -8,6 +8,7 @@ namespace Microsoft.AutoGen.Abstractions; public static class MessageExtensions { + private const string PROTO_DATA_CONTENT_TYPE = "application/x-protobuf"; public static CloudEvent ToCloudEvent(this T message, string source) where T : IMessage { return new CloudEvent @@ -15,8 +16,8 @@ public static CloudEvent ToCloudEvent(this T message, string source) where T ProtoData = Any.Pack(message), Type = message.Descriptor.FullName, Source = source, - Id = Guid.NewGuid().ToString() - + Id = Guid.NewGuid().ToString(), + Attributes = { { "datacontenttype", new CloudEvent.Types.CloudEventAttributeValue { CeString = PROTO_DATA_CONTENT_TYPE } } } }; } public static T FromCloudEvent(this CloudEvent cloudEvent) where T : IMessage, new() diff --git a/dotnet/src/Microsoft.AutoGen/Agents/AgentBase.cs b/dotnet/src/Microsoft.AutoGen/Agents/AgentBase.cs index 01ad856a2d49..5ff964070ffd 100644 --- a/dotnet/src/Microsoft.AutoGen/Agents/AgentBase.cs +++ b/dotnet/src/Microsoft.AutoGen/Agents/AgentBase.cs @@ -36,23 +36,50 @@ protected AgentBase( runtime.AgentInstance = this; this.EventTypes = eventTypes; _logger = logger ?? LoggerFactory.Create(builder => { }).CreateLogger(); - var subscriptionRequest = new AddSubscriptionRequest + AddImplicitSubscriptionsAsync().AsTask().Wait(); + Completion = Start(); + } + internal Task Completion { get; } + + private async ValueTask AddImplicitSubscriptionsAsync() + { + var topicTypes = new List + { + this.AgentId.Type + ":" + this.AgentId.Key, + this.AgentId.Type + }; + + foreach (var topicType in topicTypes) { - RequestId = Guid.NewGuid().ToString(), - Subscription = new Subscription + var subscriptionRequest = new AddSubscriptionRequest { - TypeSubscription = new TypeSubscription + RequestId = Guid.NewGuid().ToString(), + Subscription = new Subscription { - AgentType = this.AgentId.Type, - TopicType = this.AgentId.Type + "/" + this.AgentId.Key + TypeSubscription = new TypeSubscription + { + AgentType = this.AgentId.Type, + TopicType = topicType + } } + }; + // explicitly wait for this to complete + await _runtime.SendMessageAsync(new Message { AddSubscriptionRequest = subscriptionRequest }).ConfigureAwait(true); + } + + // using reflection, find all methods that Handle and subscribe to the topic T + var handleMethods = this.GetType().GetMethods().Where(m => m.Name == "Handle").ToList(); + foreach (var method in handleMethods) + { + var eventType = method.GetParameters()[0].ParameterType; + var topic = EventTypes.EventsMap.FirstOrDefault(x => x.Value.Contains(eventType.Name)).Key; + if (topic != null) + { + Subscribe(nameof(topic)); } - }; - _runtime.SendMessageAsync(new Message { AddSubscriptionRequest = subscriptionRequest }).AsTask().Wait(); - Completion = Start(); - } - internal Task Completion { get; } + } + } internal Task Start() { var didSuppress = false; diff --git a/dotnet/src/Microsoft.AutoGen/Agents/Services/Grpc/GrpcAgentWorker.cs b/dotnet/src/Microsoft.AutoGen/Agents/Services/Grpc/GrpcAgentWorker.cs index 48f07573430d..636bca487fc7 100644 --- a/dotnet/src/Microsoft.AutoGen/Agents/Services/Grpc/GrpcAgentWorker.cs +++ b/dotnet/src/Microsoft.AutoGen/Agents/Services/Grpc/GrpcAgentWorker.cs @@ -216,6 +216,7 @@ private async ValueTask RegisterAgentTypeAsync(string type, Type agentType, Canc //var state = agentType.BaseType?.GetGenericArguments().First(); var topicTypes = agentType.GetCustomAttributes().Select(t => t.Topic); + //TODO: do something with the response (like retry on error) await WriteChannelAsync(new Message { RegisterAgentTypeRequest = new RegisterAgentTypeRequest @@ -227,9 +228,47 @@ await WriteChannelAsync(new Message //Events = { events } } }, cancellationToken).ConfigureAwait(false); + + foreach (var topic in topicTypes) + { + var subscriptionRequest = new Message + { + AddSubscriptionRequest = new AddSubscriptionRequest + { + RequestId = Guid.NewGuid().ToString(), + Subscription = new Subscription + { + TypeSubscription = new TypeSubscription + { + AgentType = type, + TopicType = topic + } + } + } + }; + await WriteChannelAsync(subscriptionRequest, cancellationToken).ConfigureAwait(true); + foreach (var e in events) + { + subscriptionRequest = new Message + { + AddSubscriptionRequest = new AddSubscriptionRequest + { + RequestId = Guid.NewGuid().ToString(), + Subscription = new Subscription + { + TypeSubscription = new TypeSubscription + { + AgentType = type, + TopicType = topic + "." + e + } + } + } + }; + await WriteChannelAsync(subscriptionRequest, cancellationToken).ConfigureAwait(true); + } + } } } - // new is intentional public new async ValueTask SendResponseAsync(RpcResponse response, CancellationToken cancellationToken = default) { diff --git a/dotnet/src/Microsoft.AutoGen/Agents/Services/Grpc/GrpcGateway.cs b/dotnet/src/Microsoft.AutoGen/Agents/Services/Grpc/GrpcGateway.cs index ab24a0e15fe5..9ba36410a30f 100644 --- a/dotnet/src/Microsoft.AutoGen/Agents/Services/Grpc/GrpcGateway.cs +++ b/dotnet/src/Microsoft.AutoGen/Agents/Services/Grpc/GrpcGateway.cs @@ -40,7 +40,6 @@ public GrpcGateway(IClusterClient clusterClient, ILogger logger) } public async ValueTask BroadcastEvent(CloudEvent evt) { - // TODO: filter the workers that receive the event var tasks = new List(_workers.Count); foreach (var (_, connection) in _supportedAgentTypes) { @@ -119,13 +118,26 @@ private async ValueTask RespondBadRequestAsync(GrpcWorkerConnection connection, { throw new RpcException(new Status(StatusCode.InvalidArgument, error)); } + + // agentype:rpc_request={requesting_agent_id} + // {genttype}:rpc_response={request_id} private async ValueTask AddSubscriptionAsync(GrpcWorkerConnection connection, AddSubscriptionRequest request) { - var topic = request.Subscription.TypeSubscription.TopicType; - var agentType = request.Subscription.TypeSubscription.AgentType; + var topic = ""; + var agentType = ""; + if (request.Subscription.TypePrefixSubscription is not null) + { + topic = request.Subscription.TypePrefixSubscription.TopicTypePrefix; + agentType = request.Subscription.TypePrefixSubscription.AgentType; + } + else if (request.Subscription.TypeSubscription is not null) + { + topic = request.Subscription.TypeSubscription.TopicType; + agentType = request.Subscription.TypeSubscription.AgentType; + } _subscriptionsByAgentType[agentType] = request.Subscription; _subscriptionsByTopic.GetOrAdd(topic, _ => []).Add(agentType); - await _subscriptions.Subscribe(topic, agentType); + await _subscriptions.SubscribeAsync(topic, agentType); //var response = new AddSubscriptionResponse { RequestId = request.RequestId, Error = "", Success = true }; Message response = new() { @@ -153,31 +165,50 @@ private async ValueTask RegisterAgentTypeAsync(GrpcWorkerConnection connection, Success = true } }; - // add a default subscription for the agent type - //TODO: we should consider having constraints on the namespace or at least migrate all our examples to use well typed namesspaces like com.microsoft.autogen/hello/HelloAgents etc - var subscriptionRequest = new AddSubscriptionRequest + await connection.ResponseStream.WriteAsync(response).ConfigureAwait(false); + } + private async ValueTask DispatchEventAsync(CloudEvent evt) + { + // get the event type and then send to all agents that are subscribed to that event type + var eventType = evt.Type; + // ensure that we get agentTypes as an async enumerable list - try to get the value of agentTypes by topic and then cast it to an async enumerable list + if (_subscriptionsByTopic.TryGetValue(eventType, out var agentTypes)) + { + await DispatchEventToAgentsAsync(agentTypes, evt); + } + // instead of an exact match, we can also check for a prefix match where key starts with the eventType + else if (_subscriptionsByTopic.Keys.Any(key => key.StartsWith(eventType))) + { + _subscriptionsByTopic.Where( + kvp => kvp.Key.StartsWith(eventType)) + .SelectMany(kvp => kvp.Value) + .Distinct() + .ToList() + .ForEach(async agentType => + { + await DispatchEventToAgentsAsync(new List { agentType }, evt).ConfigureAwait(false); + }); + } + else { - RequestId = Guid.NewGuid().ToString(), - Subscription = new Subscription + // log that no agent types were found + _logger.LogWarning("No agent types found for event type {EventType}.", eventType); + } + } + private async ValueTask DispatchEventToAgentsAsync(IEnumerable agentTypes, CloudEvent evt) + { + var tasks = new List(agentTypes.Count()); + foreach (var agentType in agentTypes) + { + if (_supportedAgentTypes.TryGetValue(agentType, out var connections)) { - TypeSubscription = new TypeSubscription + foreach (var connection in connections) { - AgentType = msg.Type, - TopicType = msg.Type + tasks.Add(this.SendMessageAsync(connection, evt)); } } - }; - await AddSubscriptionAsync(connection, subscriptionRequest).ConfigureAwait(true); - - await connection.ResponseStream.WriteAsync(response).ConfigureAwait(false); - } - private async ValueTask DispatchEventAsync(CloudEvent evt) - { - await BroadcastEvent(evt).ConfigureAwait(false); - /* - var topic = _clusterClient.GetStreamProvider("agents").GetStream(StreamId.Create(evt.Namespace, evt.Type)); - await topic.OnNextAsync(evt.ToEvent()); - */ + } + await Task.WhenAll(tasks).ConfigureAwait(false); } private async ValueTask DispatchRequestAsync(GrpcWorkerConnection connection, RpcRequest request) { diff --git a/dotnet/src/Microsoft.AutoGen/Agents/Services/Orleans/ISubscriptionsGrain.cs b/dotnet/src/Microsoft.AutoGen/Agents/Services/Orleans/ISubscriptionsGrain.cs index 302df9ebff98..d3af459bb7ff 100644 --- a/dotnet/src/Microsoft.AutoGen/Agents/Services/Orleans/ISubscriptionsGrain.cs +++ b/dotnet/src/Microsoft.AutoGen/Agents/Services/Orleans/ISubscriptionsGrain.cs @@ -4,7 +4,7 @@ namespace Microsoft.AutoGen.Agents; public interface ISubscriptionsGrain : IGrainWithIntegerKey { - ValueTask Subscribe(string agentType, string topic); - ValueTask Unsubscribe(string agentType, string topic); + ValueTask SubscribeAsync(string agentType, string topic); + ValueTask UnsubscribeAsync(string agentType, string topic); ValueTask>> GetSubscriptions(string agentType); } diff --git a/dotnet/src/Microsoft.AutoGen/Agents/Services/Orleans/SubscriptionsGrain.cs b/dotnet/src/Microsoft.AutoGen/Agents/Services/Orleans/SubscriptionsGrain.cs index 682073f0b97c..0e647dbab980 100644 --- a/dotnet/src/Microsoft.AutoGen/Agents/Services/Orleans/SubscriptionsGrain.cs +++ b/dotnet/src/Microsoft.AutoGen/Agents/Services/Orleans/SubscriptionsGrain.cs @@ -15,7 +15,7 @@ public ValueTask>> GetSubscriptions(string? agen } return new ValueTask>>(_subscriptions); } - public ValueTask Subscribe(string agentType, string topic) + public async ValueTask SubscribeAsync(string agentType, string topic) { if (!_subscriptions.TryGetValue(topic, out var subscriptions)) { @@ -27,11 +27,9 @@ public ValueTask Subscribe(string agentType, string topic) } _subscriptions[topic] = subscriptions; state.State.Subscriptions = _subscriptions; - state.WriteStateAsync(); - - return ValueTask.CompletedTask; + await state.WriteStateAsync().ConfigureAwait(false); } - public ValueTask Unsubscribe(string agentType, string topic) + public async ValueTask UnsubscribeAsync(string agentType, string topic) { if (!_subscriptions.TryGetValue(topic, out var subscriptions)) { @@ -43,8 +41,7 @@ public ValueTask Unsubscribe(string agentType, string topic) } _subscriptions[topic] = subscriptions; state.State.Subscriptions = _subscriptions; - state.WriteStateAsync(); - return ValueTask.CompletedTask; + await state.WriteStateAsync(); } } public sealed class SubscriptionsState diff --git a/protos/cloudevent.proto b/protos/cloudevent.proto index e4b4aeb1beff..0cd2ea85daec 100644 --- a/protos/cloudevent.proto +++ b/protos/cloudevent.proto @@ -21,7 +21,6 @@ message CloudEvent { // Optional & Extension Attributes map attributes = 5; map metadata = 6; - // -- CloudEvent Data (Bytes, Text, or Proto) oneof data { bytes binary_data = 7; diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/task/_console.py b/python/packages/autogen-agentchat/src/autogen_agentchat/task/_console.py index 9899366cdc07..596f9de481c7 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/task/_console.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/task/_console.py @@ -58,6 +58,7 @@ async def Console( f"Duration: {duration:.2f} seconds\n" ) sys.stdout.write(output) + sys.stdout.flush() # mypy ignore last_processed = message # type: ignore @@ -71,6 +72,7 @@ async def Console( total_usage.completion_tokens += message.chat_message.models_usage.completion_tokens total_usage.prompt_tokens += message.chat_message.models_usage.prompt_tokens sys.stdout.write(output) + sys.stdout.flush() # Print summary. if message.inner_messages is not None: @@ -85,6 +87,7 @@ async def Console( f"Duration: {duration:.2f} seconds\n" ) sys.stdout.write(output) + sys.stdout.flush() # mypy ignore last_processed = message # type: ignore @@ -97,6 +100,7 @@ async def Console( total_usage.completion_tokens += message.models_usage.completion_tokens total_usage.prompt_tokens += message.models_usage.prompt_tokens sys.stdout.write(output) + sys.stdout.flush() if last_processed is None: raise ValueError("No TaskResult or Response was processed.") diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_magentic_one/_magentic_one_group_chat.py b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_magentic_one/_magentic_one_group_chat.py index 04194c4a495f..e8d83176ff26 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_magentic_one/_magentic_one_group_chat.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_magentic_one/_magentic_one_group_chat.py @@ -7,6 +7,7 @@ from ....base import ChatAgent, TerminationCondition from .._base_group_chat import BaseGroupChat from ._magentic_one_orchestrator import MagenticOneOrchestrator +from ._prompts import ORCHESTRATOR_FINAL_ANSWER_PROMPT trace_logger = logging.getLogger(TRACE_LOGGER_NAME) event_logger = logging.getLogger(EVENT_LOGGER_NAME) @@ -25,6 +26,7 @@ class MagenticOneGroupChat(BaseGroupChat): Without a termination condition, the group chat will run based on the orchestrator logic or until the maximum number of turns is reached. max_turns (int, optional): The maximum number of turns in the group chat before stopping. Defaults to 20. max_stalls (int, optional): The maximum number of stalls allowed before re-planning. Defaults to 3. + final_answer_prompt (str, optional): The LLM prompt used to generate the final answer or response from the team's transcript. A default (sensible for GPT-4o class models) is provided. Raises: ValueError: In orchestration logic if progress ledger does not have required keys or if next speaker is not valid. @@ -64,6 +66,7 @@ def __init__( termination_condition: TerminationCondition | None = None, max_turns: int | None = 20, max_stalls: int = 3, + final_answer_prompt: str = ORCHESTRATOR_FINAL_ANSWER_PROMPT, ): super().__init__( participants, @@ -77,6 +80,7 @@ def __init__( raise ValueError("At least one participant is required for MagenticOneGroupChat.") self._model_client = model_client self._max_stalls = max_stalls + self._final_answer_prompt = final_answer_prompt def _create_group_chat_manager_factory( self, @@ -95,5 +99,6 @@ def _create_group_chat_manager_factory( max_turns, self._model_client, self._max_stalls, + self._final_answer_prompt, termination_condition, ) diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_magentic_one/_magentic_one_orchestrator.py b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_magentic_one/_magentic_one_orchestrator.py index 9e7deb15d0ca..d4509a61b4f2 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_magentic_one/_magentic_one_orchestrator.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_magentic_one/_magentic_one_orchestrator.py @@ -48,6 +48,7 @@ def __init__( max_turns: int | None, model_client: ChatCompletionClient, max_stalls: int, + final_answer_prompt: str, termination_condition: TerminationCondition | None, ): super().__init__( @@ -60,6 +61,7 @@ def __init__( ) self._model_client = model_client self._max_stalls = max_stalls + self._final_answer_prompt = final_answer_prompt self._name = "MagenticOneOrchestrator" self._max_json_retries = 10 self._task = "" @@ -95,7 +97,10 @@ def _get_task_ledger_plan_update_prompt(self, team: str) -> str: return ORCHESTRATOR_TASK_LEDGER_PLAN_UPDATE_PROMPT.format(team=team) def _get_final_answer_prompt(self, task: str) -> str: - return ORCHESTRATOR_FINAL_ANSWER_PROMPT.format(task=task) + if self._final_answer_prompt == ORCHESTRATOR_FINAL_ANSWER_PROMPT: + return ORCHESTRATOR_FINAL_ANSWER_PROMPT.format(task=task) + else: + return self._final_answer_prompt async def _log_message(self, log_message: str) -> None: trace_logger.debug(log_message) diff --git a/python/packages/autogen-core/docs/src/conf.py b/python/packages/autogen-core/docs/src/conf.py index bdd4579c3bca..a723decc57e7 100644 --- a/python/packages/autogen-core/docs/src/conf.py +++ b/python/packages/autogen-core/docs/src/conf.py @@ -149,6 +149,7 @@ } autodoc_pydantic_model_show_config_summary = False +python_use_unqualified_type_names = True intersphinx_mapping = {"python": ("https://docs.python.org/3", None)} diff --git a/python/packages/autogen-core/pyproject.toml b/python/packages/autogen-core/pyproject.toml index cbc4c7155a89..67f9bbc54a3b 100644 --- a/python/packages/autogen-core/pyproject.toml +++ b/python/packages/autogen-core/pyproject.toml @@ -82,7 +82,7 @@ dev-dependencies = [ [tool.ruff] extend = "../../pyproject.toml" -exclude = ["build", "dist", "src/autogen_core/application/protos", "tests/protos"] +exclude = ["build", "dist", "src/autogen_core/application/protos", "tests/protos", "samples/protos"] include = ["src/**", "samples/*.py", "docs/**/*.ipynb", "tests/**"] [tool.ruff.lint.per-file-ignores] @@ -92,7 +92,7 @@ include = ["src/**", "samples/*.py", "docs/**/*.ipynb", "tests/**"] [tool.pyright] extends = "../../pyproject.toml" include = ["src", "tests", "samples"] -exclude = ["src/autogen_core/application/protos", "tests/protos"] +exclude = ["src/autogen_core/application/protos", "tests/protos", "samples/protos"] reportDeprecated = true [tool.pytest.ini_options] diff --git a/python/packages/autogen-core/samples/protos/__init__.py b/python/packages/autogen-core/samples/protos/__init__.py new file mode 100644 index 000000000000..b3ea671c3b9b --- /dev/null +++ b/python/packages/autogen-core/samples/protos/__init__.py @@ -0,0 +1,8 @@ +""" +The :mod:`autogen_core.worker.protos` module provides Google Protobuf classes for agent-worker communication +""" + +import os +import sys + +sys.path.insert(0, os.path.abspath(os.path.dirname(__file__))) diff --git a/python/packages/autogen-core/samples/protos/agent_events_pb2.py b/python/packages/autogen-core/samples/protos/agent_events_pb2.py new file mode 100644 index 000000000000..b93b1219e019 --- /dev/null +++ b/python/packages/autogen-core/samples/protos/agent_events_pb2.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: agent_events.proto +# Protobuf Python Version: 4.25.1 +"""Generated protocol buffer code.""" + +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder + +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( + b'\n\x12\x61gent_events.proto\x12\x06\x61gents"2\n\x0bTextMessage\x12\x13\n\x0btextMessage\x18\x01 \x01(\t\x12\x0e\n\x06source\x18\x02 \x01(\t"\x18\n\x05Input\x12\x0f\n\x07message\x18\x01 \x01(\t"\x1f\n\x0eInputProcessed\x12\r\n\x05route\x18\x01 \x01(\t"\x19\n\x06Output\x12\x0f\n\x07message\x18\x01 \x01(\t"\x1e\n\rOutputWritten\x12\r\n\x05route\x18\x01 \x01(\t"\x1a\n\x07IOError\x12\x0f\n\x07message\x18\x01 \x01(\t"%\n\x12NewMessageReceived\x12\x0f\n\x07message\x18\x01 \x01(\t"%\n\x11ResponseGenerated\x12\x10\n\x08response\x18\x01 \x01(\t"\x1a\n\x07GoodBye\x12\x0f\n\x07message\x18\x01 \x01(\t" \n\rMessageStored\x12\x0f\n\x07message\x18\x01 \x01(\t";\n\x12\x43onversationClosed\x12\x0f\n\x07user_id\x18\x01 \x01(\t\x12\x14\n\x0cuser_message\x18\x02 \x01(\t"\x1b\n\x08Shutdown\x12\x0f\n\x07message\x18\x01 \x01(\tB!\xaa\x02\x1eMicrosoft.AutoGen.Abstractionsb\x06proto3' +) + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "agent_events_pb2", _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + _globals["DESCRIPTOR"]._options = None + _globals["DESCRIPTOR"]._serialized_options = b"\252\002\036Microsoft.AutoGen.Abstractions" + _globals["_TEXTMESSAGE"]._serialized_start = 30 + _globals["_TEXTMESSAGE"]._serialized_end = 80 + _globals["_INPUT"]._serialized_start = 82 + _globals["_INPUT"]._serialized_end = 106 + _globals["_INPUTPROCESSED"]._serialized_start = 108 + _globals["_INPUTPROCESSED"]._serialized_end = 139 + _globals["_OUTPUT"]._serialized_start = 141 + _globals["_OUTPUT"]._serialized_end = 166 + _globals["_OUTPUTWRITTEN"]._serialized_start = 168 + _globals["_OUTPUTWRITTEN"]._serialized_end = 198 + _globals["_IOERROR"]._serialized_start = 200 + _globals["_IOERROR"]._serialized_end = 226 + _globals["_NEWMESSAGERECEIVED"]._serialized_start = 228 + _globals["_NEWMESSAGERECEIVED"]._serialized_end = 265 + _globals["_RESPONSEGENERATED"]._serialized_start = 267 + _globals["_RESPONSEGENERATED"]._serialized_end = 304 + _globals["_GOODBYE"]._serialized_start = 306 + _globals["_GOODBYE"]._serialized_end = 332 + _globals["_MESSAGESTORED"]._serialized_start = 334 + _globals["_MESSAGESTORED"]._serialized_end = 366 + _globals["_CONVERSATIONCLOSED"]._serialized_start = 368 + _globals["_CONVERSATIONCLOSED"]._serialized_end = 427 + _globals["_SHUTDOWN"]._serialized_start = 429 + _globals["_SHUTDOWN"]._serialized_end = 456 +# @@protoc_insertion_point(module_scope) diff --git a/python/packages/autogen-core/samples/protos/agent_events_pb2.pyi b/python/packages/autogen-core/samples/protos/agent_events_pb2.pyi new file mode 100644 index 000000000000..01cfbafee51e --- /dev/null +++ b/python/packages/autogen-core/samples/protos/agent_events_pb2.pyi @@ -0,0 +1,197 @@ +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file +""" + +import builtins +import google.protobuf.descriptor +import google.protobuf.message +import typing + +DESCRIPTOR: google.protobuf.descriptor.FileDescriptor + +@typing.final +class TextMessage(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + TEXTMESSAGE_FIELD_NUMBER: builtins.int + SOURCE_FIELD_NUMBER: builtins.int + textMessage: builtins.str + source: builtins.str + def __init__( + self, + *, + textMessage: builtins.str = ..., + source: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["source", b"source", "textMessage", b"textMessage"]) -> None: ... + +global___TextMessage = TextMessage + +@typing.final +class Input(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + MESSAGE_FIELD_NUMBER: builtins.int + message: builtins.str + def __init__( + self, + *, + message: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["message", b"message"]) -> None: ... + +global___Input = Input + +@typing.final +class InputProcessed(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + ROUTE_FIELD_NUMBER: builtins.int + route: builtins.str + def __init__( + self, + *, + route: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["route", b"route"]) -> None: ... + +global___InputProcessed = InputProcessed + +@typing.final +class Output(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + MESSAGE_FIELD_NUMBER: builtins.int + message: builtins.str + def __init__( + self, + *, + message: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["message", b"message"]) -> None: ... + +global___Output = Output + +@typing.final +class OutputWritten(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + ROUTE_FIELD_NUMBER: builtins.int + route: builtins.str + def __init__( + self, + *, + route: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["route", b"route"]) -> None: ... + +global___OutputWritten = OutputWritten + +@typing.final +class IOError(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + MESSAGE_FIELD_NUMBER: builtins.int + message: builtins.str + def __init__( + self, + *, + message: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["message", b"message"]) -> None: ... + +global___IOError = IOError + +@typing.final +class NewMessageReceived(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + MESSAGE_FIELD_NUMBER: builtins.int + message: builtins.str + def __init__( + self, + *, + message: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["message", b"message"]) -> None: ... + +global___NewMessageReceived = NewMessageReceived + +@typing.final +class ResponseGenerated(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + RESPONSE_FIELD_NUMBER: builtins.int + response: builtins.str + def __init__( + self, + *, + response: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["response", b"response"]) -> None: ... + +global___ResponseGenerated = ResponseGenerated + +@typing.final +class GoodBye(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + MESSAGE_FIELD_NUMBER: builtins.int + message: builtins.str + def __init__( + self, + *, + message: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["message", b"message"]) -> None: ... + +global___GoodBye = GoodBye + +@typing.final +class MessageStored(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + MESSAGE_FIELD_NUMBER: builtins.int + message: builtins.str + def __init__( + self, + *, + message: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["message", b"message"]) -> None: ... + +global___MessageStored = MessageStored + +@typing.final +class ConversationClosed(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + USER_ID_FIELD_NUMBER: builtins.int + USER_MESSAGE_FIELD_NUMBER: builtins.int + user_id: builtins.str + user_message: builtins.str + def __init__( + self, + *, + user_id: builtins.str = ..., + user_message: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["user_id", b"user_id", "user_message", b"user_message"]) -> None: ... + +global___ConversationClosed = ConversationClosed + +@typing.final +class Shutdown(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + MESSAGE_FIELD_NUMBER: builtins.int + message: builtins.str + def __init__( + self, + *, + message: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["message", b"message"]) -> None: ... + +global___Shutdown = Shutdown diff --git a/python/packages/autogen-core/samples/protos/agent_events_pb2_grpc.py b/python/packages/autogen-core/samples/protos/agent_events_pb2_grpc.py new file mode 100644 index 000000000000..bf947056a2f4 --- /dev/null +++ b/python/packages/autogen-core/samples/protos/agent_events_pb2_grpc.py @@ -0,0 +1,4 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" + +import grpc diff --git a/python/packages/autogen-core/samples/protos/agent_events_pb2_grpc.pyi b/python/packages/autogen-core/samples/protos/agent_events_pb2_grpc.pyi new file mode 100644 index 000000000000..a6a9cff9dfd4 --- /dev/null +++ b/python/packages/autogen-core/samples/protos/agent_events_pb2_grpc.pyi @@ -0,0 +1,17 @@ +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file +""" + +import abc +import collections.abc +import grpc +import grpc.aio +import typing + +_T = typing.TypeVar("_T") + +class _MaybeAsyncIterator(collections.abc.AsyncIterator[_T], collections.abc.Iterator[_T], metaclass=abc.ABCMeta): ... + +class _ServicerContext(grpc.ServicerContext, grpc.aio.ServicerContext): # type: ignore[misc, type-arg] + ... diff --git a/python/packages/autogen-core/samples/xlang/hello_python_agent/README.md b/python/packages/autogen-core/samples/xlang/hello_python_agent/README.md new file mode 100644 index 000000000000..bb94d34f305e --- /dev/null +++ b/python/packages/autogen-core/samples/xlang/hello_python_agent/README.md @@ -0,0 +1,14 @@ +# Python and dotnet agents interoperability sample + +This sample demonstrates how to create a Python agent that interacts with a .NET agent. +To run the sample, check out the autogen repository. +Then do the following: + +1. Navigate to autogen/dotnet/samples/Hello/Hello.AppHost +2. Run `dotnet run` to start the .NET Aspire app host, which runs three projects: + - Backend (the .NET Agent Runtime) + - HelloAgent (the .NET Agent) + - this Python agent - hello_python_agent.py +3. The AppHost will start the Aspire dashboard on [https://localhost:15887](https://localhost:15887). + +The Python agent will interact with the .NET agent by sending a message to the .NET runtime, which will relay the message to the .NET agent. diff --git a/python/packages/autogen-core/samples/xlang/hello_python_agent/hello_python_agent.py b/python/packages/autogen-core/samples/xlang/hello_python_agent/hello_python_agent.py new file mode 100644 index 000000000000..cc131f5f2701 --- /dev/null +++ b/python/packages/autogen-core/samples/xlang/hello_python_agent/hello_python_agent.py @@ -0,0 +1,69 @@ +import asyncio +import logging +import os +import sys + +from autogen_core.application import WorkerAgentRuntime + +# from protos.agents_events_pb2 import NewMessageReceived +from autogen_core.base import PROTOBUF_DATA_CONTENT_TYPE, AgentId, try_get_known_serializers_for_type +from autogen_core.components import DefaultSubscription, DefaultTopicId, TypeSubscription + +# Add the local package directory to sys.path +thisdir = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(os.path.join(thisdir, "..", "..")) +from dotenv import load_dotenv # type: ignore # noqa: E402 +from protos.agent_events_pb2 import NewMessageReceived, Output # type: ignore # noqa: E402 +from user_input import UserProxy # type: ignore # noqa: E402 + +agnext_logger = logging.getLogger("autogen_core") + + +async def main() -> None: + load_dotenv() + agentHost = os.getenv("AGENT_HOST") or "localhost:53072" + # grpc python bug - can only use the hostname, not prefix - if hostname has a prefix we have to remove it: + if agentHost.startswith("http://"): + agentHost = agentHost[7:] + if agentHost.startswith("https://"): + agentHost = agentHost[8:] + agnext_logger.info("0") + agnext_logger.info(agentHost) + runtime = WorkerAgentRuntime(host_address=agentHost, payload_serialization_format=PROTOBUF_DATA_CONTENT_TYPE) + + agnext_logger.info("1") + runtime.start() + runtime.add_message_serializer(try_get_known_serializers_for_type(NewMessageReceived)) + + agnext_logger.info("2") + + await UserProxy.register(runtime, "HelloAgents", lambda: UserProxy()) + await runtime.add_subscription(DefaultSubscription(agent_type="HelloAgents")) + await runtime.add_subscription(TypeSubscription(topic_type="agents.NewMessageReceived", agent_type="HelloAgents")) + await runtime.add_subscription(TypeSubscription(topic_type="agents.ConversationClosed", agent_type="HelloAgents")) + await runtime.add_subscription(TypeSubscription(topic_type="agents.Output", agent_type="HelloAgents")) + agnext_logger.info("3") + + new_message = NewMessageReceived(message="from Python!") + output_message = Output(message="^v^v^v---Wild Hello from Python!---^v^v^v") + + await runtime.publish_message( + message=new_message, + topic_id=DefaultTopicId("agents.NewMessageReceived", "HelloAgents/python"), + sender=AgentId("HelloAgents", "python"), + ) + + await runtime.publish_message( + message=output_message, + topic_id=DefaultTopicId("agents.Output", "HelloAgents/python"), + sender=AgentId("HelloAgents", "python"), + ) + await runtime.stop_when_signal() + # await runtime.stop_when_idle() + + +if __name__ == "__main__": + logging.basicConfig(level=logging.DEBUG) + agnext_logger.setLevel(logging.DEBUG) + agnext_logger.log(logging.DEBUG, "Starting worker") + asyncio.run(main()) diff --git a/python/packages/autogen-core/samples/xlang/hello_python_agent/user_input.py b/python/packages/autogen-core/samples/xlang/hello_python_agent/user_input.py new file mode 100644 index 000000000000..d2ab73e6f746 --- /dev/null +++ b/python/packages/autogen-core/samples/xlang/hello_python_agent/user_input.py @@ -0,0 +1,39 @@ +import asyncio +import logging +from typing import Union + +from autogen_core.base import MessageContext +from autogen_core.components import DefaultTopicId, RoutedAgent, message_handler +from protos.agent_events_pb2 import ConversationClosed, Input, NewMessageReceived, Output # type: ignore + +input_types = Union[ConversationClosed, Input, Output] + + +class UserProxy(RoutedAgent): + """An agent that allows the user to play the role of an agent in the conversation via input.""" + + DEFAULT_DESCRIPTION = "A human user." + + def __init__( + self, + description: str = DEFAULT_DESCRIPTION, + ) -> None: + super().__init__(description) + + @message_handler + async def handle_user_chat_input(self, message: input_types, ctx: MessageContext) -> None: + logger = logging.getLogger("autogen_core") + + if isinstance(message, Input): + response = await self.ainput("User input ('exit' to quit): ") + response = response.strip() + logger.info(response) + + await self.publish_message(NewMessageReceived(message=response), topic_id=DefaultTopicId()) + elif isinstance(message, Output): + logger.info(message.message) + else: + pass + + async def ainput(self, prompt: str) -> str: + return await asyncio.to_thread(input, f"{prompt} ") diff --git a/python/packages/autogen-core/src/autogen_core/application/_worker_runtime.py b/python/packages/autogen-core/src/autogen_core/application/_worker_runtime.py index 24007fadfc7d..f1208ea11eb6 100644 --- a/python/packages/autogen-core/src/autogen_core/application/_worker_runtime.py +++ b/python/packages/autogen-core/src/autogen_core/application/_worker_runtime.py @@ -733,7 +733,7 @@ async def factory_wrapper() -> T: async def _process_register_agent_type_response(self, response: agent_worker_pb2.RegisterAgentTypeResponse) -> None: future = self._pending_requests.pop(response.request_id) - if response.HasField("error"): + if response.HasField("error") and response.error != "": future.set_exception(RuntimeError(response.error)) else: future.set_result(None) @@ -835,7 +835,7 @@ async def add_subscription(self, subscription: Subscription) -> None: async def _process_add_subscription_response(self, response: agent_worker_pb2.AddSubscriptionResponse) -> None: future = self._pending_requests.pop(response.request_id) - if response.HasField("error"): + if response.HasField("error") and response.error != "": future.set_exception(RuntimeError(response.error)) else: future.set_result(None) diff --git a/python/packages/autogen-core/src/autogen_core/application/protos/cloudevent_pb2.py b/python/packages/autogen-core/src/autogen_core/application/protos/cloudevent_pb2.py index a1a9edc11613..e59848860dba 100644 --- a/python/packages/autogen-core/src/autogen_core/application/protos/cloudevent_pb2.py +++ b/python/packages/autogen-core/src/autogen_core/application/protos/cloudevent_pb2.py @@ -16,7 +16,7 @@ from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x10\x63loudevent.proto\x12\ncloudevent\x1a\x19google/protobuf/any.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\x8b\x05\n\nCloudEvent\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0e\n\x06source\x18\x02 \x01(\t\x12\x14\n\x0cspec_version\x18\x03 \x01(\t\x12\x0c\n\x04type\x18\x04 \x01(\t\x12:\n\nattributes\x18\x05 \x03(\x0b\x32&.cloudevent.CloudEvent.AttributesEntry\x12\x36\n\x08metadata\x18\x06 \x03(\x0b\x32$.cloudevent.CloudEvent.MetadataEntry\x12\x15\n\x0b\x62inary_data\x18\x07 \x01(\x0cH\x00\x12\x13\n\ttext_data\x18\x08 \x01(\tH\x00\x12*\n\nproto_data\x18\t \x01(\x0b\x32\x14.google.protobuf.AnyH\x00\x1a\x62\n\x0f\x41ttributesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12>\n\x05value\x18\x02 \x01(\x0b\x32/.cloudevent.CloudEvent.CloudEventAttributeValue:\x02\x38\x01\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\xd3\x01\n\x18\x43loudEventAttributeValue\x12\x14\n\nce_boolean\x18\x01 \x01(\x08H\x00\x12\x14\n\nce_integer\x18\x02 \x01(\x05H\x00\x12\x13\n\tce_string\x18\x03 \x01(\tH\x00\x12\x12\n\x08\x63\x65_bytes\x18\x04 \x01(\x0cH\x00\x12\x10\n\x06\x63\x65_uri\x18\x05 \x01(\tH\x00\x12\x14\n\nce_uri_ref\x18\x06 \x01(\tH\x00\x12\x32\n\x0c\x63\x65_timestamp\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.TimestampH\x00\x42\x06\n\x04\x61ttrB\x06\n\x04\x64\x61taB!\xaa\x02\x1eMicrosoft.AutoGen.Abstractionsb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x10\x63loudevent.proto\x12\ncloudevent\x1a\x19google/protobuf/any.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\xa4\x05\n\nCloudEvent\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0e\n\x06source\x18\x02 \x01(\t\x12\x14\n\x0cspec_version\x18\x03 \x01(\t\x12\x0c\n\x04type\x18\x04 \x01(\t\x12:\n\nattributes\x18\x05 \x03(\x0b\x32&.cloudevent.CloudEvent.AttributesEntry\x12\x36\n\x08metadata\x18\x06 \x03(\x0b\x32$.cloudevent.CloudEvent.MetadataEntry\x12\x17\n\x0f\x64\x61tacontenttype\x18\x07 \x01(\t\x12\x15\n\x0b\x62inary_data\x18\x08 \x01(\x0cH\x00\x12\x13\n\ttext_data\x18\t \x01(\tH\x00\x12*\n\nproto_data\x18\n \x01(\x0b\x32\x14.google.protobuf.AnyH\x00\x1a\x62\n\x0f\x41ttributesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12>\n\x05value\x18\x02 \x01(\x0b\x32/.cloudevent.CloudEvent.CloudEventAttributeValue:\x02\x38\x01\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\xd3\x01\n\x18\x43loudEventAttributeValue\x12\x14\n\nce_boolean\x18\x01 \x01(\x08H\x00\x12\x14\n\nce_integer\x18\x02 \x01(\x05H\x00\x12\x13\n\tce_string\x18\x03 \x01(\tH\x00\x12\x12\n\x08\x63\x65_bytes\x18\x04 \x01(\x0cH\x00\x12\x10\n\x06\x63\x65_uri\x18\x05 \x01(\tH\x00\x12\x14\n\nce_uri_ref\x18\x06 \x01(\tH\x00\x12\x32\n\x0c\x63\x65_timestamp\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.TimestampH\x00\x42\x06\n\x04\x61ttrB\x06\n\x04\x64\x61taB!\xaa\x02\x1eMicrosoft.AutoGen.Abstractionsb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -29,11 +29,11 @@ _globals['_CLOUDEVENT_METADATAENTRY']._options = None _globals['_CLOUDEVENT_METADATAENTRY']._serialized_options = b'8\001' _globals['_CLOUDEVENT']._serialized_start=93 - _globals['_CLOUDEVENT']._serialized_end=744 - _globals['_CLOUDEVENT_ATTRIBUTESENTRY']._serialized_start=375 - _globals['_CLOUDEVENT_ATTRIBUTESENTRY']._serialized_end=473 - _globals['_CLOUDEVENT_METADATAENTRY']._serialized_start=475 - _globals['_CLOUDEVENT_METADATAENTRY']._serialized_end=522 - _globals['_CLOUDEVENT_CLOUDEVENTATTRIBUTEVALUE']._serialized_start=525 - _globals['_CLOUDEVENT_CLOUDEVENTATTRIBUTEVALUE']._serialized_end=736 + _globals['_CLOUDEVENT']._serialized_end=769 + _globals['_CLOUDEVENT_ATTRIBUTESENTRY']._serialized_start=400 + _globals['_CLOUDEVENT_ATTRIBUTESENTRY']._serialized_end=498 + _globals['_CLOUDEVENT_METADATAENTRY']._serialized_start=500 + _globals['_CLOUDEVENT_METADATAENTRY']._serialized_end=547 + _globals['_CLOUDEVENT_CLOUDEVENTATTRIBUTEVALUE']._serialized_start=550 + _globals['_CLOUDEVENT_CLOUDEVENTATTRIBUTEVALUE']._serialized_end=761 # @@protoc_insertion_point(module_scope) diff --git a/python/packages/autogen-core/src/autogen_core/application/protos/cloudevent_pb2.pyi b/python/packages/autogen-core/src/autogen_core/application/protos/cloudevent_pb2.pyi index 1cf61a523ffc..c51398893086 100644 --- a/python/packages/autogen-core/src/autogen_core/application/protos/cloudevent_pb2.pyi +++ b/python/packages/autogen-core/src/autogen_core/application/protos/cloudevent_pb2.pyi @@ -97,6 +97,7 @@ class CloudEvent(google.protobuf.message.Message): TYPE_FIELD_NUMBER: builtins.int ATTRIBUTES_FIELD_NUMBER: builtins.int METADATA_FIELD_NUMBER: builtins.int + DATACONTENTTYPE_FIELD_NUMBER: builtins.int BINARY_DATA_FIELD_NUMBER: builtins.int TEXT_DATA_FIELD_NUMBER: builtins.int PROTO_DATA_FIELD_NUMBER: builtins.int @@ -109,6 +110,8 @@ class CloudEvent(google.protobuf.message.Message): """URI-reference""" spec_version: builtins.str type: builtins.str + datacontenttype: builtins.str + """MIME type""" binary_data: builtins.bytes text_data: builtins.str @property @@ -128,12 +131,13 @@ class CloudEvent(google.protobuf.message.Message): type: builtins.str = ..., attributes: collections.abc.Mapping[builtins.str, global___CloudEvent.CloudEventAttributeValue] | None = ..., metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., + datacontenttype: builtins.str = ..., binary_data: builtins.bytes = ..., text_data: builtins.str = ..., proto_data: google.protobuf.any_pb2.Any | None = ..., ) -> None: ... def HasField(self, field_name: typing.Literal["binary_data", b"binary_data", "data", b"data", "proto_data", b"proto_data", "text_data", b"text_data"]) -> builtins.bool: ... - def ClearField(self, field_name: typing.Literal["attributes", b"attributes", "binary_data", b"binary_data", "data", b"data", "id", b"id", "metadata", b"metadata", "proto_data", b"proto_data", "source", b"source", "spec_version", b"spec_version", "text_data", b"text_data", "type", b"type"]) -> None: ... + def ClearField(self, field_name: typing.Literal["attributes", b"attributes", "binary_data", b"binary_data", "data", b"data", "datacontenttype", b"datacontenttype", "id", b"id", "metadata", b"metadata", "proto_data", b"proto_data", "source", b"source", "spec_version", b"spec_version", "text_data", b"text_data", "type", b"type"]) -> None: ... def WhichOneof(self, oneof_group: typing.Literal["data", b"data"]) -> typing.Literal["binary_data", "text_data", "proto_data"] | None: ... global___CloudEvent = CloudEvent diff --git a/python/packages/autogen-core/src/autogen_core/base/_agent_instantiation.py b/python/packages/autogen-core/src/autogen_core/base/_agent_instantiation.py index e38f94a5250e..06364381de8a 100644 --- a/python/packages/autogen-core/src/autogen_core/base/_agent_instantiation.py +++ b/python/packages/autogen-core/src/autogen_core/base/_agent_instantiation.py @@ -12,23 +12,24 @@ def __init__(self) -> None: "AgentInstantiationContext cannot be instantiated. It is a static class that provides context management for agent instantiation." ) - AGENT_INSTANTIATION_CONTEXT_VAR: ClassVar[ContextVar[tuple[AgentRuntime, AgentId]]] = ContextVar( - "AGENT_INSTANTIATION_CONTEXT_VAR" + _AGENT_INSTANTIATION_CONTEXT_VAR: ClassVar[ContextVar[tuple[AgentRuntime, AgentId]]] = ContextVar( + "_AGENT_INSTANTIATION_CONTEXT_VAR" ) @classmethod @contextmanager def populate_context(cls, ctx: tuple[AgentRuntime, AgentId]) -> Generator[None, Any, None]: - token = AgentInstantiationContext.AGENT_INSTANTIATION_CONTEXT_VAR.set(ctx) + """:meta private:""" + token = AgentInstantiationContext._AGENT_INSTANTIATION_CONTEXT_VAR.set(ctx) try: yield finally: - AgentInstantiationContext.AGENT_INSTANTIATION_CONTEXT_VAR.reset(token) + AgentInstantiationContext._AGENT_INSTANTIATION_CONTEXT_VAR.reset(token) @classmethod def current_runtime(cls) -> AgentRuntime: try: - return cls.AGENT_INSTANTIATION_CONTEXT_VAR.get()[0] + return cls._AGENT_INSTANTIATION_CONTEXT_VAR.get()[0] except LookupError as e: raise RuntimeError( "AgentInstantiationContext.runtime() must be called within an instantiation context such as when the AgentRuntime is instantiating an agent. Mostly likely this was caused by directly instantiating an agent instead of using the AgentRuntime to do so." @@ -37,7 +38,7 @@ def current_runtime(cls) -> AgentRuntime: @classmethod def current_agent_id(cls) -> AgentId: try: - return cls.AGENT_INSTANTIATION_CONTEXT_VAR.get()[1] + return cls._AGENT_INSTANTIATION_CONTEXT_VAR.get()[1] except LookupError as e: raise RuntimeError( "AgentInstantiationContext.agent_id() must be called within an instantiation context such as when the AgentRuntime is instantiating an agent. Mostly likely this was caused by directly instantiating an agent instead of using the AgentRuntime to do so." diff --git a/python/packages/autogen-core/src/autogen_core/base/_agent_runtime.py b/python/packages/autogen-core/src/autogen_core/base/_agent_runtime.py index 27c37ad9f349..c4025ce23f99 100644 --- a/python/packages/autogen-core/src/autogen_core/base/_agent_runtime.py +++ b/python/packages/autogen-core/src/autogen_core/base/_agent_runtime.py @@ -86,6 +86,9 @@ async def register( ) -> AgentType: """Register an agent factory with the runtime associated with a specific type. The type must be unique. + .. deprecated:: 0.4.0.dev1 + Use a specific agent's `register` method directly instead of this method. For example: :meth:`BaseAgent.register` + Args: type (str): The type of agent this factory creates. It is not the same as agent class name. The `type` parameter is used to differentiate between different factory functions rather than agent classes. agent_factory (Callable[[], T]): The factory that creates the agent, where T is a concrete Agent type. Inside the factory, use `autogen_core.base.AgentInstantiationContext` to access variables like the current runtime and agent ID. diff --git a/python/packages/autogen-core/src/autogen_core/base/_base_agent.py b/python/packages/autogen-core/src/autogen_core/base/_base_agent.py index 70481705ca6e..1b7a98d51eb7 100644 --- a/python/packages/autogen-core/src/autogen_core/base/_base_agent.py +++ b/python/packages/autogen-core/src/autogen_core/base/_base_agent.py @@ -29,6 +29,8 @@ # Decorator for adding an unbound subscription to an agent def subscription_factory(subscription: UnboundSubscription) -> Callable[[Type[BaseAgentType]], Type[BaseAgentType]]: + """:meta private:""" + def decorator(cls: Type[BaseAgentType]) -> Type[BaseAgentType]: cls.internal_unbound_subscriptions_list.append(subscription) return cls @@ -56,7 +58,9 @@ def decorator(cls: Type[BaseAgentType]) -> Type[BaseAgentType]: class BaseAgent(ABC, Agent): internal_unbound_subscriptions_list: ClassVar[List[UnboundSubscription]] = [] + """:meta private:""" internal_extra_handles_types: ClassVar[List[Tuple[Type[Any], List[MessageSerializer[Any]]]]] = [] + """:meta private:""" def __init_subclass__(cls, **kwargs: Any) -> None: super().__init_subclass__(**kwargs) diff --git a/python/packages/autogen-core/src/autogen_core/base/_message_handler_context.py b/python/packages/autogen-core/src/autogen_core/base/_message_handler_context.py index f4707ff0fe68..b0f08ac8ca98 100644 --- a/python/packages/autogen-core/src/autogen_core/base/_message_handler_context.py +++ b/python/packages/autogen-core/src/autogen_core/base/_message_handler_context.py @@ -11,20 +11,21 @@ def __init__(self) -> None: "MessageHandlerContext cannot be instantiated. It is a static class that provides context management for agent instantiation." ) - MESSAGE_HANDLER_CONTEXT: ClassVar[ContextVar[AgentId]] = ContextVar("MESSAGE_HANDLER_CONTEXT") + _MESSAGE_HANDLER_CONTEXT: ClassVar[ContextVar[AgentId]] = ContextVar("_MESSAGE_HANDLER_CONTEXT") @classmethod @contextmanager def populate_context(cls, ctx: AgentId) -> Generator[None, Any, None]: - token = MessageHandlerContext.MESSAGE_HANDLER_CONTEXT.set(ctx) + """:meta private:""" + token = MessageHandlerContext._MESSAGE_HANDLER_CONTEXT.set(ctx) try: yield finally: - MessageHandlerContext.MESSAGE_HANDLER_CONTEXT.reset(token) + MessageHandlerContext._MESSAGE_HANDLER_CONTEXT.reset(token) @classmethod def agent_id(cls) -> AgentId: try: - return cls.MESSAGE_HANDLER_CONTEXT.get() + return cls._MESSAGE_HANDLER_CONTEXT.get() except LookupError as e: raise RuntimeError("MessageHandlerContext.agent_id() must be called within a message handler.") from e diff --git a/python/packages/autogen-core/src/autogen_core/base/_serialization.py b/python/packages/autogen-core/src/autogen_core/base/_serialization.py index 74e028641126..608fe9180d18 100644 --- a/python/packages/autogen-core/src/autogen_core/base/_serialization.py +++ b/python/packages/autogen-core/src/autogen_core/base/_serialization.py @@ -199,6 +199,8 @@ def _type_name(cls: type[Any] | Any) -> str: def try_get_known_serializers_for_type(cls: type[Any]) -> list[MessageSerializer[Any]]: + """:meta private:""" + serializers: List[MessageSerializer[Any]] = [] if issubclass(cls, BaseModel): serializers.append(PydanticJsonMessageSerializer(cls)) @@ -211,6 +213,8 @@ def try_get_known_serializers_for_type(cls: type[Any]) -> list[MessageSerializer class SerializationRegistry: + """:meta private:""" + def __init__(self) -> None: # type_name, data_content_type -> serializer self._serializers: dict[tuple[str, str], MessageSerializer[Any]] = {} diff --git a/python/packages/autogen-core/src/autogen_core/base/_subscription_context.py b/python/packages/autogen-core/src/autogen_core/base/_subscription_context.py index 1e7e563a5ace..e67bcfc9b7a2 100644 --- a/python/packages/autogen-core/src/autogen_core/base/_subscription_context.py +++ b/python/packages/autogen-core/src/autogen_core/base/_subscription_context.py @@ -11,21 +11,22 @@ def __init__(self) -> None: "SubscriptionInstantiationContext cannot be instantiated. It is a static class that provides context management for subscription instantiation." ) - SUBSCRIPTION_CONTEXT_VAR: ClassVar[ContextVar[AgentType]] = ContextVar("SUBSCRIPTION_CONTEXT_VAR") + _SUBSCRIPTION_CONTEXT_VAR: ClassVar[ContextVar[AgentType]] = ContextVar("_SUBSCRIPTION_CONTEXT_VAR") @classmethod @contextmanager def populate_context(cls, ctx: AgentType) -> Generator[None, Any, None]: - token = SubscriptionInstantiationContext.SUBSCRIPTION_CONTEXT_VAR.set(ctx) + """:meta private:""" + token = SubscriptionInstantiationContext._SUBSCRIPTION_CONTEXT_VAR.set(ctx) try: yield finally: - SubscriptionInstantiationContext.SUBSCRIPTION_CONTEXT_VAR.reset(token) + SubscriptionInstantiationContext._SUBSCRIPTION_CONTEXT_VAR.reset(token) @classmethod def agent_type(cls) -> AgentType: try: - return cls.SUBSCRIPTION_CONTEXT_VAR.get() + return cls._SUBSCRIPTION_CONTEXT_VAR.get() except LookupError as e: raise RuntimeError( "SubscriptionInstantiationContext.runtime() must be called within an instantiation context such as when the AgentRuntime is instantiating an agent. Mostly likely this was caused by directly instantiating an agent instead of using the AgentRuntime to do so." diff --git a/python/packages/autogen-core/src/autogen_core/components/code_executor/_func_with_reqs.py b/python/packages/autogen-core/src/autogen_core/components/code_executor/_func_with_reqs.py index 1ef01cedd858..b7f4fcaef8db 100644 --- a/python/packages/autogen-core/src/autogen_core/components/code_executor/_func_with_reqs.py +++ b/python/packages/autogen-core/src/autogen_core/components/code_executor/_func_with_reqs.py @@ -161,6 +161,7 @@ def wrapper(func: Callable[P, T]) -> FunctionWithRequirements[T, P]: def build_python_functions_file( funcs: Sequence[Union[FunctionWithRequirements[Any, P], Callable[..., Any], FunctionWithRequirementsStr]], ) -> str: + """:meta private:""" # First collect all global imports global_imports: Set[Import] = set() for func in funcs: diff --git a/python/pyproject.toml b/python/pyproject.toml index e9b9753cfca9..9f4d37735754 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -80,4 +80,6 @@ check = ["fmt", "lint", "pyright", "mypy", "test"] gen-proto = "python -m grpc_tools.protoc --python_out=./packages/autogen-core/src/autogen_core/application/protos --grpc_python_out=./packages/autogen-core/src/autogen_core/application/protos --mypy_out=./packages/autogen-core/src/autogen_core/application/protos --mypy_grpc_out=./packages/autogen-core/src/autogen_core/application/protos --proto_path ../protos/ agent_worker.proto --proto_path ../protos/ cloudevent.proto" -gen-test-proto = "python -m grpc_tools.protoc --python_out=./packages/autogen-core/tests/protos --grpc_python_out=./packages/autogen-core/tests/protos --mypy_out=./packages/autogen-core/tests/protos --mypy_grpc_out=./packages/autogen-core/tests/protos --proto_path ./packages/autogen-core/tests/protos serialization_test.proto" \ No newline at end of file +gen-test-proto = "python -m grpc_tools.protoc --python_out=./packages/autogen-core/tests/protos --grpc_python_out=./packages/autogen-core/tests/protos --mypy_out=./packages/autogen-core/tests/protos --mypy_grpc_out=./packages/autogen-core/tests/protos --proto_path ./packages/autogen-core/tests/protos serialization_test.proto" + +gen-proto-samples = "python -m grpc_tools.protoc --python_out=./packages/autogen-core/samples/protos --grpc_python_out=./packages/autogen-core/samples/protos --mypy_out=./packages/autogen-core/samples/protos --mypy_grpc_out=./packages/autogen-core/samples/protos --proto_path ../protos/ agent_events.proto" \ No newline at end of file