最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Issue with Event Flow in Semantic Kernel Agent Process - Conditionally Handle Events - .NET - Stack Overflow

programmeradmin2浏览0评论

I am working on an agent-based system using Microsoft's Semantic Kernel and am facing an issue with the event flow. My expected flow is:

  1. Ask the user for a request

  2. Confirm the request (yes → proceed, no → restart request)

  3. Ask if they want an image (yes/no)

  4. Process the request (generate response with or without an image) : i.e if user wants image then response will be Text + Image, else the response should be only text

I used this repo, modified for my workflow

Current Implementation

I have set up an event-driven process using KernelProcess and ProcessStepBuilder. My process follows these steps:

  1. The WelcomeStep asks for user input.
  2. The UserInputStep gets user confirmation.
  3. If the user confirms, it asks whether they want an image.
  4. Based on the response, the request is processed.

The issue:

Even user does not want an image, the image is generated when completing the process. Because I don't know how to handle this. I need to generate image conditionally or I need another solution rather than handling conditional in events in program.cs. Please suggest a solution

Program.cs

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Chat;
using Microsoft.SemanticKernel.Agents.History;
using Microsoft.SemanticKernel.ChatCompletion;


var process = SetupAgentProcess(nameof(Demo));

// Execute process
using var localProcess = await process.StartAsync(kernel, new KernelProcessEvent()
{
    Id = Events.StartProcess
});



static KernelProcess SetupAgentProcess(string processName)
{
    ProcessBuilder process = new(processName);

    var welcomeStep = process.AddStepFromType<WelcomeStep>();
    var userInputStep = process.AddStepFromType<UserInputStep>();
    var renderMessageStep = process.AddStepFromType<RenderMessageStep>();
    var managerAgentStep = process.AddStepFromType<ManagerAgentStep>();
    var agentGroupStep = process.AddStepFromType<AgentGroupChatStep>();
    var imageCreatorStep = process.AddStepFromType<ImageCreatorStep>();

    AttachErrorStep(
        userInputStep,
        UserInputStep.Functions.GetUserInput);

    AttachErrorStep(
        managerAgentStep,
        ManagerAgentStep.Functions.InvokeAgent,
        ManagerAgentStep.Functions.InvokeGroup,
        ManagerAgentStep.Functions.ReceiveResponse);

    AttachErrorStep(
        agentGroupStep,
        AgentGroupChatStep.Functions.InvokeAgentGroup);

    // Entry point
    process.OnInputEvent(Events.StartProcess)
        .SendEventTo(new ProcessFunctionTargetBuilder(welcomeStep));

    // Once the welcome message has been shown, request user input
    welcomeStep.OnFunctionResult(WelcomeStep.Functions.WelcomeMessage)
             .SendEventTo(new ProcessFunctionTargetBuilder(userInputStep, UserInputStep.Functions.GetUserInput));

    // Pass user input to primary agent
    userInputStep
        .OnEvent(Events.UserInputReceived)
        .SendEventTo(new ProcessFunctionTargetBuilder(managerAgentStep, ManagerAgentStep.Functions.InvokeAgent));

    // Render response from primary agent
    managerAgentStep
        .OnEvent(Events.Agents.AgentResponse)
        .SendEventTo(new ProcessFunctionTargetBuilder(renderMessageStep, RenderMessageStep.Functions.RenderMessage, parameterName: "message"));

    // Request is complete
    managerAgentStep
        .OnEvent(Events.UserInputComplete)
        .SendEventTo(new ProcessFunctionTargetBuilder(renderMessageStep, RenderMessageStep.Functions.RenderDone))
        .StopProcess();

    managerAgentStep
        .OnEvent(Events.Agents.AgentRequestUserConfirmation)
        .SendEventTo(new ProcessFunctionTargetBuilder(userInputStep, UserInputStep.Functions.GetUserConfirmation));

    // Once the user confirms, ask if they want an image
    userInputStep
        .OnEvent(Events.UserConfirmedRequest)
        .SendEventTo(new ProcessFunctionTargetBuilder(userInputStep, UserInputStep.Functions.GetImagePreference));

    // trigger invokeagent once UserImagePreferenceReceived
    userInputStep
        .OnEvent(Events.UserImagePreferenceReceived)
        .SendEventTo(new ProcessFunctionTargetBuilder(managerAgentStep, ManagerAgentStep.Functions.InvokeAgent));

    // Request more user input
    managerAgentStep
        .OnEvent(Events.Agents.AgentResponded) // once agent response with image is generated event status goes to AgentResponded. 
        .SendEventTo(new ProcessFunctionTargetBuilder(userInputStep, UserInputStep.Functions.GetUserInput));// so this  fucntion triggerd

    // Delegate to inner agents
    managerAgentStep
        .OnEvent(Events.Agents.AgentWorking) // after confirmed event goes to AgentWorking and InvokeGroup  trirgger 
        .SendEventTo(new ProcessFunctionTargetBuilder(managerAgentStep, ManagerAgentStep.Functions.InvokeGroup));

    // Provide input to inner agents
    managerAgentStep
        .OnEvent(Events.Agents.GroupInput)
        .SendEventTo(new ProcessFunctionTargetBuilder(agentGroupStep, parameterName: "input"));

    // Step 1: Render response from inner chat for visibility
    agentGroupStep
        .OnEvent(Events.Agents.GroupMessage)
        .SendEventTo(new ProcessFunctionTargetBuilder(renderMessageStep, RenderMessageStep.Functions.RenderInnerMessage, parameterName: "message"));

    // Provide inner response to primary agent
    agentGroupStep
        .OnEvent(Events.Agents.GroupCompleted)
        .SendEventTo(new ProcessFunctionTargetBuilder(imageCreatorStep, ImageCreatorStep.Functions.CreateImageForCopy, parameterName: "copy")); // I need to handle the conditionally

    // Step 3: Once the image is created, proceed to the managerAgentStep
    imageCreatorStep
        .OnEvent(Events.ImageCreated)
        .SendEventTo(new ProcessFunctionTargetBuilder(managerAgentStep, ManagerAgentStep.Functions.ReceiveResponse, parameterName: "response"));

    var kernelProcess = process.Build();

    return kernelProcess;

    void AttachErrorStep(ProcessStepBuilder step, params string[] functionNames)
    {
        foreach (var functionName in functionNames)
        {
            step.OnFunctionError(functionName)
                .SendEventTo(new ProcessFunctionTargetBuilder(renderMessageStep, RenderMessageStep.Functions.RenderError, "error"))
                .StopProcess();
        }
    }
}


UserInputStep.cs


internal sealed class UserInputStep : KernelProcessStep<UserInputState>
{
    private UserInputState state;

    public static class Functions
    {
        public const string GetUserInput = nameof(GetUserInput);

        public const string GetUserConfirmation = nameof(GetUserConfirmation);
        public const string GetImagePreference = nameof(GetImagePreference);

    }

    public override ValueTask ActivateAsync(KernelProcessStepState<UserInputState> state)
    {
        this.state = state.State!;

        return ValueTask.CompletedTask;
    }

    [KernelFunction(Functions.GetUserInput)]
    public async ValueTask GetUserInputAsync(KernelProcessStepContext context)
    {
        Console.ForegroundColor = ConsoleColor.Green;
        Console.WriteLine("\nPlease enter a marketing request, or say «bye» to exit: ");
        Console.ForegroundColor = ConsoleColor.White;
        var userMessage = Console.ReadLine();

        if (userMessage?.StartsWith(@"bye", StringComparison.OrdinalIgnoreCase) ?? false)
        {
            await context.EmitEventAsync(new() { Id = Events.Exit, Data = userMessage });
            return;
        }

        state.UserInputs.Add(userMessage!);
        state.CurrentInputIndex++;

        await context.EmitEventAsync(new() { Id = Events.UserInputReceived, Data = userMessage });
    }

    [KernelFunction(Functions.GetUserConfirmation)]
    public async ValueTask GetUserConfirmationAsync(KernelProcessStepContext context)
    {
        Console.ForegroundColor = ConsoleColor.Green;
        Console.WriteLine("\nPlease confirm your request (yes/no), or provide a new marketing request. Just say «bye» to exit: ");
        Console.ForegroundColor = ConsoleColor.White;
        var userMessage = Console.ReadLine()?.Trim().ToLower();

        if (userMessage == "bye")
        {
            await context.EmitEventAsync(new() { Id = Events.Exit, Data = userMessage });
            return;
        }

        if (userMessage == "yes")
        {
            await context.EmitEventAsync(new() { Id = Events.UserConfirmedRequest });
        }
        else
        {
            state.UserInputs.Add(userMessage!);
            state.CurrentInputIndex++;
            await context.EmitEventAsync(new() { Id = Events.UserInputReceived, Data = userMessage });
        }
    }

    [KernelFunction(Functions.GetImagePreference)]
    public async ValueTask GetImagePreferenceAsync(KernelProcessStepContext context)
    {
        Console.ForegroundColor = ConsoleColor.Green;
        Console.WriteLine("\nDo you want an image with your response? (yes/no): ");
        Console.ForegroundColor = ConsoleColor.White;
        var userResponse = Console.ReadLine()?.Trim().ToLower();

        if (userResponse == "yes")
        {
            // If user says yes, set the WantsImage flag to true
            state.WantsImage = true;
            await context.EmitEventAsync(new() { Id = Events.UserImagePreferenceReceived, Data = true });
        }
        else if (userResponse == "no")
        {
            // If user says no, set the WantsImage flag to false
            state.WantsImage = false;
            await context.EmitEventAsync(new() { Id = Events.UserImagePreferenceReceived, Data = false });
        }
        else
        {
            // If the response is invalid (neither yes nor no), ask again
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine("Invalid input! Please respond with 'yes' or 'no'.");
            Console.ForegroundColor = ConsoleColor.White;

            // Re-prompt for image preference
            await GetImagePreferenceAsync(context);
        }
    }
}

public record UserInputState
{
    public List<string> UserInputs { get; init; } = [];

    public int CurrentInputIndex { get; set; } = 0;
    public bool WantsImage { get; set; } = false;  // I could not this state value in program.cs to hanlde the image generation part conditionally

}

Am I missing something in event handling within ProcessFunctionTargetBuilder or KernelProcess?

Update:

Can I read the state of UserInputStep in ImageCreatorStep? How ?

If possible I can handle conditionally (wants image/not) inside ImageCreatorStep.


internal sealed class ImageCreatorStep : KernelProcessStep
{
    public static class Functions
    {
        // public static bool RequiresImage;
        public const string CreateImageForCopy = nameof(CreateImageForCopy);
    }

    [KernelFunction(Functions.CreateImageForCopy)]
    public static async Task CreateImageForCopyAsync(KernelProcessStepContext context, Kernel kernel, string copy)
    {
            var userState = kernel.GetRequiredService<KernelProcessStepState<UserInputState>>()?.State;  // This also did not work, I get empty value for each property in userState

        var imageTask = await kernel.GetRequiredService<ITextToImageService>().GenerateImageAsync(copy, 1024, 1024);

        var result = $"Copy: {copy}\n\nImage URL: {imageTask}";

        await context.EmitEventAsync(new() { Id = Events.ImageCreated, Data = result });
    }
}
发布评论

评论列表(0)

  1. 暂无评论