Creating Your Agent

Overview#

aevatar Agents are powerful, stateful entities that can interact, process events, and manage their own lifecycle. This guide will walk you through the process of creating a new Agent.

Prerequisites#

  • Understand C# and .NET
  • Familiarity with Orleans framework
  • Basic knowledge of dependency injection
  • Basic understanding of event-driven architectures
  • Agent Types#

    Aevatar supports two main types of Agents:

  • GAgentBase: A generic agent with basic state management
  • AIGAgentBase: An AI-specific agent with additional brain and LLM capabilities
  • Dependency package #

    dotnet add package Aevatar.Core:

    https://www.myget.org/F/aelf-project-dev/api/v3/index.json

    dotnet add package Aevatar.EventSourcing.Core:

    https://www.myget.org/F/aelf-project-dev/api/v3/index.json

    dotnet add package Aevatar.Core.Abstractions:

    https://www.myget.org/F/aelf-project-dev/api/v3/index.json

    Creating Agent Steps#

    Define State#

    Your agent’s persistent state must inherit from StateBase and be annotated properly with [GenerateSerializer] and [Id(...)]. For example:

    using System;
    using System.Collections.Generic;
    using Aevatar.Core;
    using Aevatar.Core.Abstractions;
    using Aevatar.EventSourcing.Core;
    using Orleans.Serialization;
    
    [GenerateSerializer]
    public class TwitterGAgentState : StateBase
    {
        [Id(0)] public Guid Id { get; set; } = Guid.NewGuid();
    
        [Id(1)] public string UserId { get; set; }
    
        [Id(2)] public string Token { get; set; }
    
        [Id(3)] public string TokenSecret { get; set; }
    
        [Id(4)] public Dictionary<string, string> RepliedTweets { get; set; } = new();
    
        [Id(5)] public string UserName { get; set; }
    
        // Add other relevant fields as needed
    }

    Note:

  • Initialization: Set default values (e.g., RepliedTweets = new Dictionary<string, string>()) so you don’t deal with null references.
  • Sensitive Data: If tokens are sensitive, consider storing them securely (e.g., encryption at rest).
  • Define Event Sourcing Event(s)#

    Event Sourcing events (which are appended to the event log) must inherit from StateLogEventBase. They should also use the Orleans [GenerateSerializer] and [Id(...)] attributes:

    using Aevatar.EventSourcing.Core;
    using Orleans.Serialization;
    
    [GenerateSerializer]
    public class TweetGEvent :StateLogEventBase<TweetGEvent>
    {
        [Id(0)] public string Text { get; set; }
    
        // You can add more properties as needed, e.g., TweetId, Timestamp, etc.
    }

    Note:

  • Keep your Event Sourcing payload small. Store larger data in the state or external storage if necessary.
  • Use purposeful naming (e.g., TweetCreatedEvent) to make the event clearer.
  • Define External Message Event(s)#

    Events that come from "outside" (i.e., external messages or commands) inherit from EventBase. This is what your agent will handle with [EventHandler].

    using Aevatar.Core.Abstractions;
    using Orleans.Serialization;
    
    [GenerateSerializer]
    public class CreateTweetGEvent : EventBase
    {
        [Id(0)] public string Text { get; set; }
    }

    Note:

  • Use CreateSomethingEvent, UpdateSomethingEvent, etc. to clarify intent.
  • If there is additional metadata (like user info), include that as well.
  • Create the Agent (Grain)#

    Your agent implements a corresponding Grain interface and inherits from GAgentBase<TState, TEvent>. You must specify:

  • [StorageProvider(ProviderName = "PubSubStore")] – Orleans Storage Provider used for streaming, reminders, etc.
  • [LogConsistencyProvider(ProviderName = "LogStorage")] – Provider for event-sourced log consistency.
  • For example, define an interface:

    using Orleans;
    public interface ITwitterGAgent : IGrainWithStringKey
    {
        // Any custom methods you'd like to expose, e.g.:
        // Task HandleExternalEventAsync(CreateTweetGEvent @event);
    }

    Then implement the agent:

    using System.Threading.Tasks;
    using Microsoft.Extensions.Logging;
    using Aevatar.Core;
    using Aevatar.Core.Abstractions;
    using Aevatar.EventSourcing.Core;
    using Aevatar.GAgents;
    using Orleans;
    using Orleans.Providers;
    
    [StorageProvider(ProviderName = "PubSubStore")]
    [LogConsistencyProvider(ProviderName = "LogStorage")]
    public class TwitterGAgent : GAgentBase<TwitterGAgentState, TweetGEvent>, ITwitterGAgent
    {
        private readonly ILogger<TwitterGAgent> _logger;
        public TwitterGAgent(ILogger<TwitterGAgent> logger) : base(logger)
        {
            _logger = logger;
        }
    
        /// <summary>
        /// Handle external event for creating a Tweet.
        /// </summary>
        [EventHandler]
        public async Task HandleEventAsync(CreateTweetGEvent @event)
        {
            _logger.LogDebug("Received CreateTweetGEvent. Text: {Text}", @event.Text);
            if (string.IsNullOrWhiteSpace(@event.Text))
            {
                _logger.LogWarning("CreateTweetGEvent received with empty text. Skipping...");
                return;
            }
    
            if (string.IsNullOrWhiteSpace(State.UserId))
            {
                _logger.LogWarning("No UserId found in state. Skipping tweet creation...");
                return;
            }
            // 1. Raise an internal event for event sourcing (if needed)
            await RaiseEventAsync(new TweetGEvent
            {
                Text = @event.Text
            });
            // 2. Optionally, publish to the global stream so that other GAgents or services can react
            await PublishAsync(new SocialGEvent
            {
                Content = @event.Text
                // Fill in other fields if needed
            });
        }
        /// <summary>
        /// Optional override: When a TweetGEvent is applied, update the state if needed.
        /// This is called when the event is raised via RaiseEventAsync.
        /// </summary>
        protected override Task ApplyEventAsync(TweetGEvent @event)
        {
            _logger.LogInformation("Applying TweetGEvent to state. Text: {Text}", @event.Text);
    
            // Example: record last tweeted content in the agent's state
            // State.LastTweetText = @event.Text;
            // Then save changes to the state if necessary (GAgentBase handles saving, but you can do additional logic).
    
            return Task.CompletedTask;
        }
    }

    Key Notes:#

  • Raising vs. Publishing:
    • RaiseEventAsync() appends an event to the event log (i.e., TweetGEvent).
    • PublishAsync() sends a message to an Orleans stream that other grains or external consumers can subscribe to.
  • [EventHandler]:
    • This attribute on the method indicates that this method handles external events/commands (like CreateTweetGEvent).
    • The ApplyEventAsync() method (or any relevant method in your base class) is where your internal state is updated once the event is appended to the log.
  • State Lifecycle:
    • GAgentBase typically manages saving and loading your TState from the configured storage provider.
    • The event-sourcing provider replays the log of TEvent events to rebuild the current state.
  • Logging:
    • Use different log levels (Debug, Information, Warning, Error) for clarity.
  • Creating AI Agent Steps#

    1: Define AI Agent State#

    Create a custom state class that inherits from StateBase or AIGAgentStateBase:

    [GenerateSerializer]
    public class SampleAIAgentState : StateBase
    {
        // Define your agent's state properties
        [Id(0)] public Guid Id { get; set; } = Guid.NewGuid();
        [Id(1)] public string Name { get; set; }
        [Id(2)] public int Age { get; set; }
    }
    
    [GenerateSerializer]
    public class SampleAIAgentStateLogEvent : StateLogEventBase<SampleAIAgentStateLogEvent>
    {
        // Add any additional properties for the state log event here
    }
    
    [GenerateSerializer]
    public class SampleAIAgentConfigurationEvent : ConfigurationEventBase
    {
        // Add any additional properties for the configuration event here
    }

    2: Create AI Agent Class#

    Inherit from GAgentBase or AIGAgentBase:

    public class SampleAIAgent : GAgentBase<SampleAIAgentState, SampleAIAgentStateLogEvent, EventBase, SampleAIAgentConfigurationEvent>
    {
        public SampleAIAgent(ILogger<SampleAIAgent> logger) : base(logger)
        {
        }
    
        // Define agent-specific methods and event handlers
        [EventHandler]
        public async Task DoSomethingAsync()
        {
            // Agent logic here
            Logger.LogInformation($"Agent {State.Name} is doing something");
        }
        
        public override async Task PerformConfigAsync(SampleAIAgentConfigurationEvent configurationEvent)
        {
            // Your agent-specific configuration logic goes here.
            // For example, if you need to set some internal values based on the configuration event:
            // State.SomeProperty = configurationEvent.SomeValue;
            
            // Example placeholder implementation:
            // await Task.Delay(10);
        }
    }

    3: Event Handling#

    Implement event handling methods:

    [EventHandler]
    public async Task HandleSomeEventAsync(SomeEventType eventData)
    {
        // Process the event
        // Optionally modify state or raise new events
    }

    Either mark the [EventHandler] attribute, or use HandleEventAsync as method name.

    4: Registration and Lifecycle#

    Agents can be registered and managed using these methods:

  • RegisterAsync(): Register an agent as a child of another agent
  • ActivateAsync(): Manually activate the agent
  • OnGAgentActivateAsync(): Override to add custom activation logic
  • Best Practices#

  • Keep state minimal and focused
  • Use logging for debugging and tracking
  • Implement proper error handling
  • Use dependency injection for services
  • Advanced Features#

  • Subscribe to GAgent's binding stream using SubscribeToAsync()
  • Unsubscribe from GAgent's binding stream using UnsubscribeFromAsync()
  • Publish events using PublishAsync()
  • Forward events using ForwardEventAsync()
  • Dependency Injection#

    Ensure your agent is registered in the dependency injection container:

    services.AddTransient<MyAgent>();
    services.AddSingleton<IBrainFactory, DefaultBrainFactory>();

    Example: AI Agent with LLM#

    public class AIAssistantAgent : AIGAgentBase<MyAgentState, StateLogEventBase<MyAgentState>>
    {
        public AIAssistantAgent(ILogger<AIAssistantAgent> logger) : base(logger)
        {
        }
        public async Task<string> GenerateResponseAsync(string prompt)
        {
            // Use the initialized brain to generate a response
            return await _brain.GenerateResponseAsync(prompt);
        }
    }

    Troubleshooting#

  • Ensure all required dependencies are registered
  • Check logger for initialization and runtime errors
  • Verify event subscriptions and handlers
  • Conclusion#

    Creating an Agent in Aevatar is a powerful way to build modular, event-driven, and stateful components in your application.

    References#

  • Orleans Documentation
  • aevatar.ai Documentation
  • Edited on: 19 February 2025 07:32:30 GMT+0