Skip to content

Complex AIFunction Arguments #6544

@SGStino

Description

@SGStino

Description

There seems to be a difference in serialization of function arguments between SemanticKernel's KernelFunctions and the AIFunction as an AIKernelFunction.

When a complex item like

public record TodoListItems(string[] Items);

is used as a function argument, it tries to call the function with a json string representation of that object.

While the jsonschema seems to be correct for both:

{
  "type": "object",
  "properties": {
    "values": {
      "type": "object",
      "properties": {
        "items": {
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      },
      "required": [
        "items"
      ]
    },
    "scope": {
      "type": "string"
    }
  },
  "required": [
    "values",
    "scope"
  ]
}

Reproduction Steps

Let SemanticKernel via AzureOpenAI call these two methods:

// this one works
            KernelFunctionFactory.CreateFromMethod((TodoListItems items, string scope)=> items.Items.Reverse().ToArray(),new(){
                FunctionName = "add_todos"
            }),
// this one fails
            AIFunctionFactory.Create((TodoListItems values, string scope) => "success", new()
            {
                Name = "add_special_todos",
                Description = "add todos, but only if the user explicitly asks for special todos"
            }).AsKernelFunction()

If you ask to "add some random todos", it will call the delegate.
If you ask to "add some special random todos", it will throw an error.

To debug the AIFunction, i wrapped it:

public class DebugAiFunction(AIFunction inner) : AIFunction
{
    public override string Name => inner.Name;
    public override IReadOnlyDictionary<string, object?> AdditionalProperties => inner.AdditionalProperties;
    public override string Description => inner.Description;
    public override JsonElement JsonSchema => inner.JsonSchema;
    public override JsonSerializerOptions JsonSerializerOptions => inner.JsonSerializerOptions;
    public override JsonElement? ReturnJsonSchema => inner.ReturnJsonSchema;
    public override MethodInfo? UnderlyingMethod => inner.UnderlyingMethod;
    protected override ValueTask<object?> InvokeCoreAsync(AIFunctionArguments arguments, CancellationToken cancellationToken)
    {
        return inner.InvokeAsync(arguments, cancellationToken);
    }
}

But the SemanticKernel's KernelFunction is internal, so that didn't work there so, I made a Matryoshka:

    new DebugAiFunction(KernelFunctionFactory.CreateFromMethod((TodoListItems items, string scope)=> items.Items.Reverse().ToArray(),new(){
        FunctionName = "add_todos"
    }).AsAIFunction()).AsKernelFunction(),

Both debug functions see exactly the same argument (as a literal string):

{"Items": ["Buy groceries", "Call mom", "Finish reading book"]}

Expected behavior

The same behavior in a KernelFunction and an AIFunction.

Actual behavior

System.ArgumentException: 'Object of type 'System.String' cannot be converted to type

Regression?

No response

Known Workarounds

Writing a strongly typed plugin in SemanticKernel to wrap the MCP client, but then ... there goes the flexibility of the MCP...

Configuration

    <TargetFramework>net9.0</TargetFramework>
    <PackageReference Include="ModelContextProtocol-SemanticKernel" Version="0.3.0-preview-01" />
    <PackageReference Include="Microsoft.SemanticKernel.Connectors.AzureOpenAI" Version="1.58.0" />

Other information

The whole pipeline that went wrong here was something along the likes of:

https://github.com/microsoft/semantic-kernel/blob/fac316e7dc2cd7dabbaab0ee899c04187c301093/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPClient/Samples/MCPToolsSample.cs#L42

With the MCP server expecting an array of arguments.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-aiMicrosoft.Extensions.AI librariesbugThis issue describes a behavior which is not expected - a bug.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions