Skip to content

.Net Bug: SequentialOrchestration does not invoke IPromptRenderFilter or IFunctionInvocationFilter #12590

Open
@Daniellled

Description

@Daniellled

Describe the bug
When using SequentialOrchestration the IPromptRenderFilter or IFunctionInvocationFilter objects associated with the ChatCompletionAgent's kernel object are not getting invoked.

To Reproduce
Steps to reproduce the behavior:

  1. Create a SequentialOrchestration (or just follow the example here
  2. Implement and add an IFunctionInvocationFilter to the kernel
  3. Implement and add an IPromptRenderFilter to the kernel
  4. Invoke the SequentialOrchestration

Expected behavior
Since the SequentialOrchestration is simply a collection of kernel objects. The IPromptRenderFilter and IFunctionInvocationFilter objects associated with those kernel objects should get invoked when the runtime switches to those agents.

Screenshots
If applicable, add screenshots to help explain your problem.

Platform

  • Language: C#
  • Source: Microsoft.SemanticKernel Version="1.58.0
  • AI model: llama3.2
  • IDE: Visual Studio
  • OS: Windows

Additional context
Sample code.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Orchestration;
using Microsoft.SemanticKernel.Agents.Orchestration.Sequential;
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
using OpenTelemetry.Logs;
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;

#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
#pragma warning disable SKEXP0070 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
#pragma warning disable SKEXP0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
namespace SequentialTest
{
    internal class SQ
    {
        public static async Task<string> RunSequentialOrchestrationAsync(string input, string model = "llama3.2")
        {
            ChatCompletionAgent analystAgent =
           CreateAgent(
               name: "Analyst",
               instructions:
               """
                You are a marketing analyst. Given a product description, identify:
                - Key features
                - Target audience
                - Unique selling points
                - Keep your response short (around 50 words)
                """,
               description: "A agent that extracts key concepts from a product description.",
                    model);
            ChatCompletionAgent writerAgent =
                CreateAgent(
                    name: "copywriter",
                    instructions:
                    """
                You are a marketing copywriter. Given a block of text describing features, audience, and USPs,
                compose a compelling marketing copy (like a newsletter section) that highlights these points.
                Output should be short (around 150 words), output just the copy as a single text block.
                """,
                    description: "An agent that writes a marketing copy based on the extracted concepts.",
                    model);
            ChatCompletionAgent editorAgent =
                CreateAgent(
                    name: "editor",
                    instructions:
                    """
                You are an editor. Given the draft copy, correct grammar, improve clarity, ensure consistent tone,
                give format and make it polished. Output the final improved copy as a single text block. Output should be short (around 150 words).
                """,
                    description: "An agent that formats and proofreads the marketing copy.",
                    model);


            SequentialOrchestration orchestration =
           new(analystAgent, writerAgent, editorAgent)
           {
               ResponseCallback = ResponseCallback,
           };


            // Start the runtime
            InProcessRuntime runtime = new();
            await runtime.StartAsync();

            // Run the orchestration
            Console.ForegroundColor = ConsoleColor.Blue;
            Console.WriteLine($"\n# INPUT: {input}\n");
            Console.WriteLine("**************************************************************************************");
            Console.ResetColor();
            OrchestrationResult<string> result = await orchestration.InvokeAsync(input, runtime);
            string text = await result.GetValueAsync(TimeSpan.FromSeconds(25000));
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine("**************************************************************************************");
            Console.WriteLine($"\n# RESULT: {text}");
            Console.ResetColor();

            await runtime.RunUntilIdleAsync();
            return text;
        }

        public static async Task<string> RunKernelDirectlyAsync(string prompt, string model = "llama3.2")
        {
            Kernel kernel = CreateKernel(model);
            var results = await kernel.InvokePromptAsync(
                        promptTemplate: prompt);
            Console.WriteLine($"\n# RESULT: {results}");
            return results.ToString();
        }

        private static ValueTask ResponseCallback(ChatMessageContent response)
        {
            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine($"\n# RESPONSE CALLBACK:{response?.AuthorName}: {response?.Role}, {response?.Content}");
            Console.ResetColor();
            return default;
        }

        private static ChatCompletionAgent CreateAgent(string name, string instructions, string description, string model)
        {
            return new ChatCompletionAgent()
            {
                Name = name,
                Description = description,
                Instructions = instructions,
                Kernel = CreateKernel(model)
            };
        }

        private static Kernel CreateKernel(string model)
        {
            IKernelBuilder kernelBuilder = Kernel.CreateBuilder();

            kernelBuilder.AddOllamaChatCompletion(
                modelId: model,
                httpClient: CreateHttpClient(new Uri("http://localhost:7869/v1"), 5000),
                serviceId: "OllamaChatCompletion"
            );

            kernelBuilder.Services.AddSingleton<IPromptRenderFilter, PromptFilter>();
            kernelBuilder.Services.AddSingleton<IFunctionInvocationFilter, FunctionFilter>();
            kernelBuilder.Services.AddSingleton<IAutoFunctionInvocationFilter, AutoFunctionFilter>();

            return kernelBuilder.Build();
        }

        private static HttpClient CreateHttpClient(Uri baseAddress, long minutes = 10)
        {
            return new HttpClient
            {
                Timeout = TimeSpan.FromMinutes(minutes),
                BaseAddress = baseAddress,
            };
        }

        
    }
}
#pragma warning restore SKEXP0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
#pragma warning restore SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
#pragma warning restore SKEXP0070 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.

The main method to execute the two test

// The filters are not executed
await SequentialTest.SQ.RunSequentialOrchestrationAsync("An eco-friendly stainless steel water bottle that keeps drinks cold for 24 hours");

Console.WriteLine("**************************** Press any key ****************************");
Console.Read();

// The filters are executed
await SequentialTest.SQ.RunKernelDirectlyAsync("Why is the sky blue?");

Console.WriteLine("**************************** DONE ****************************");
Console.Read();

Filters

using Microsoft.SemanticKernel;

namespace SequentialTest
{
    public class FunctionFilter : IFunctionInvocationFilter
    {
        public async Task OnFunctionInvocationAsync(FunctionInvocationContext context, Func<FunctionInvocationContext, Task> next)
        {
            Console.WriteLine($"FunctionInvoking - {context.Function.PluginName}.{context.Function.Name}");

            await next(context);

            Console.WriteLine($"FunctionInvoked - {context.Function.PluginName}.{context.Function.Name}");
        }
    }
}
using Microsoft.SemanticKernel;

namespace SequentialTest
{
    public class AutoFunctionFilter : IAutoFunctionInvocationFilter
    {
        public async Task OnAutoFunctionInvocationAsync(AutoFunctionInvocationContext context, Func<AutoFunctionInvocationContext, Task> next)
        {
            Console.WriteLine($"FunctionInvoking - {context.Function.PluginName}.{context.Function.Name}");

            await next(context);

            Console.WriteLine($"FunctionInvoked - {context.Function.PluginName}.{context.Function.Name}");
        }
    }
}
using Microsoft.SemanticKernel;

namespace SequentialTest
{
    internal class PromptFilter : IPromptRenderFilter
    {
        public async Task OnPromptRenderAsync(PromptRenderContext context, Func<PromptRenderContext, Task> next)
        {
            Console.WriteLine($"PromptRendering - {context.Function.PluginName}.{context.Function.Name} {context.RenderedPrompt}");

            await next(context);

            Console.WriteLine($"PromptRendered - {context.Function.PluginName}.{context.Function.Name} {context.RenderedPrompt}");
        }
    }
}

Metadata

Metadata

Assignees

Labels

.NETIssue or Pull requests regarding .NET codeagentsbugSomething isn't working

Type

Projects

Status

Bug

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions