Skip to content

.Net: Bug: Using Gemini for Agents can't select next agent #10939

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
sscowden opened this issue Mar 12, 2025 · 6 comments
Closed

.Net: Bug: Using Gemini for Agents can't select next agent #10939

sscowden opened this issue Mar 12, 2025 · 6 comments
Assignees
Labels
agents bug Something isn't working .NET Issue or Pull requests regarding .NET code

Comments

@sscowden
Copy link
Contributor

Describe the bug
Trying to use Gemini for an Agent demo and it always fails with the following error:
'Agent Failure - Strategy unable to select next agent: BillParser'

To Reproduce

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Chat;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.Google;
using System.Text;

var builder = Kernel.CreateBuilder();

builder.AddGoogleAIGeminiChatCompletion("gemini-2.0-flash", "[Insert Key]");

var kernel = builder.Build();

await StartAsync();

AgentGroupChat GetGroupChat()
{
    // Define the agent names for use in the function template
    const string BillParserName = "BillParser";
    const string ValidatorName = "Validator";

    // Create the agents
    ChatCompletionAgent billParserAgent =
        new()
        {
            Name = BillParserName,
            Instructions = "Parse the incoming bill and return the line item totals",
            Kernel = kernel,
            Arguments = new KernelArguments(new GeminiPromptExecutionSettings()
            {
                FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(),
                Temperature = 0.1
            }),
            Description = "Parses a fuel bill to extract necessary information"
        };

    ChatCompletionAgent validatorAgent =
        new()
        {
            Name = ValidatorName,
            Instructions = "Review all returned content from the fuel bill and verify that all data is accurate",
            Kernel = kernel,
            Description = "Validates all data returned from the fuel bill against data in our system"
        };

    // Define a kernel function for the selection strategy
    var selectionFunction =
    AgentGroupChat.CreatePromptFunctionForStrategy(
        $"""
    Determine which participant takes the next turn in a conversation based on the the most recent participant.
    State only the name of the participant to take the next turn.
    No participant should take more than one turn in a row.

    Choose only from these participants:
    - {ValidatorName}
    - {BillParserName}

    Always follow these rules when selecting the next participant:
    - After {BillParserName}, it is {ValidatorName}'s turn.
    - After {ValidatorName}, it is {BillParserName}'s turn.
    """,
        safeParameterNames: "history");

    KernelFunction terminationFunction =
        AgentGroupChat.CreatePromptFunctionForStrategy(
            $$$"""
                Determine if the JSON result is valid and the conversation should end.
                """,
            safeParameterNames: "history");

    // Define the selection strategy
    KernelFunctionSelectionStrategy selectionStrategy =
      new(selectionFunction, kernel)
      {
          // Always start with the writer agent.
          InitialAgent = billParserAgent,
          // Parse the function response.
          ResultParser = (result) => result.GetValue<string>() ?? ValidatorName,
          // The prompt variable name for the history argument.
          HistoryVariableName = "history",
          // Only include the agent names and not the message content
          EvaluateNameOnly = true,
      };

    // Define the termination strategy
    KernelFunctionTerminationStrategy terminationStrategy =
      new(terminationFunction, kernel)
      {
          // Only the reviewer may give approval.
          Agents = [validatorAgent],
          // Parse the function response.
          ResultParser = (result) =>
            result.GetValue<string>()?.Contains("yes", StringComparison.OrdinalIgnoreCase) ?? false,
          // The prompt variable name for the history argument.
          HistoryVariableName = "history",
          // Limit total number of turns no matter what
          MaximumIterations = 10,
      };

    // Create a chat using the defined selection strategy.
    AgentGroupChat chat =
        new(billParserAgent, validatorAgent)
        {
            ExecutionSettings = new() { SelectionStrategy = selectionStrategy, TerminationStrategy = terminationStrategy },
        };

    return chat;
}

async Task StartAsync()
{
    var chat = GetGroupChat();
    chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, "Hi"));

    var sb = new StringBuilder();
    await foreach (var msg in chat.InvokeAsync())
    {
        sb.AppendLine(msg.InnerContent.ToString());
        Console.WriteLine(msg.InnerContent);
    }
}

Expected behavior
Expect group chat to select the agents properly

Platform

  • Language: C#
  • Source: NuGet version 1.40.1
  • AI model: gemini-2.0-flash
  • IDE: Visual Studio
  • OS: Windows
@sscowden sscowden added the bug Something isn't working label Mar 12, 2025
@markwallace-microsoft markwallace-microsoft added .NET Issue or Pull requests regarding .NET code triage labels Mar 12, 2025
@github-actions github-actions bot changed the title Bug: Using Gemini for Agents can't select next agent .Net: Bug: Using Gemini for Agents can't select next agent Mar 12, 2025
@MayueCif
Copy link

I also encountered a similar problem, which seems to be related to the model's ability. I had no problem using DeepSeeker R1, but the QwQ-32B model reported this error

@crickman
Copy link
Contributor

crickman commented Apr 8, 2025

@sscowden, thanks for the report.

Please consider updating the ResultParser property of both strategies to log or output the result:

ResultParser = (result) =>
{
      string? resultValue = result.GetValue<string>();
      Debug.WriteLine($"Termination Result: {resultValue}");
      return resultValue?.Contains("yes", StringComparison.OrdinalIgnoreCase) ?? false;
}

Following this convention will also allow the ability to set breakpoint in the result parser.

The exception message reports BillParser as the selection result...which does match the agent name and your setup looks good.

I wonder if there's an encoding issue that is affecting comparison? After making the update to ResultParser can you set a break point and (when the break-point is hit) evaluate this expression: BillParserName.Equals(resultValue).

Also, you should be able to step the debugger back into the strategy for direct inspection of the failure point.

@sscowden
Copy link
Contributor Author

sscowden commented Apr 8, 2025

Will do this tomorrow and get back to you. Thanks!

@sscowden
Copy link
Contributor Author

ResultParser = (result) =>
{
string? resultValue = result.GetValue();
Debug.WriteLine($"Termination Result: {resultValue}");
return resultValue?.Contains("yes", StringComparison.OrdinalIgnoreCase) ?? false;
}

You called it!

"BillParser\n"

Is this something I should be handling manually in the ResultParser or is this a bug?

@AyushAP8
Copy link

AyushAP8 commented Apr 16, 2025

in python we do something like this
result_parser=lambda result: TERMINATION_KEYWORD in str(result.value[0]).lower().strip()
the strip() function takes care of "\n"
I am not sure but what is the equivalent in .NET but you can make the result parser to do something similar
Func<ResultType, bool> resultParser = result =>
result.Value[0].ToString().Trim().ToLower().Contains(TERMINATION_KEYWORD.ToLower());

@crickman
Copy link
Contributor

crickman commented May 1, 2025

ResultParser = (result) =>
{
string? resultValue = result.GetValue();
Debug.WriteLine($"Termination Result: {resultValue}");
return resultValue?.Contains("yes", StringComparison.OrdinalIgnoreCase) ?? false;
}

You called it!

"BillParser\n"

Is this something I should be handling manually in the ResultParser or is this a bug?

Glad to hear this helped provide insight.

I feel that the most appropriate approach is to have the ResultParser handle this manually.

The platform level, may not have the most complete perpsective with regards to making data-processing (result-parser) decisions. Even if we trimmed whitespace, there's going to be othercases where it might be: I pick BillParser or "BillParser".

I'm extremely fond of using structured output for these strategies as the primary tactic for raising response consistency / reliability.

@crickman crickman closed this as completed May 1, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
agents bug Something isn't working .NET Issue or Pull requests regarding .NET code
Projects
Archived in project
Development

No branches or pull requests

5 participants