Skip to content
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

.Net: Kernel function arguments cannot be complex type (e.g., array) #4696

Closed
ameier38 opened this issue Jan 22, 2024 · 1 comment · Fixed by #4757
Closed

.Net: Kernel function arguments cannot be complex type (e.g., array) #4696

ameier38 opened this issue Jan 22, 2024 · 1 comment · Fixed by #4757
Assignees
Labels
bug Something isn't working kernel Issues or pull requests impacting the core kernel .NET Issue or Pull requests regarding .NET code

Comments

@ameier38
Copy link

Describe the bug
I am trying to create a kernel function to sum an array of numbers however if I use an array as the function argument I get the error Function failed. Error: Object of type 'System.String' cannot be converted to type 'System.Double[]'.

To Reproduce
From the logs it looks like openai function call is correct.

{"function":{"name":"MathPlugin_Sum","description":"Sum an array of numbers.","parameters":{"type":"object","required":["numbers"],"properties":{"numbers":{"type":"array","items":{"type":"number"},"description":"The array of numbers to sum."}}}},"type":"function"}
"tool_calls": [
          {
            "id": "call_wj6NbjHTo1C4ErXMO0XyBU9e",
            "type": "function",
            "function": {
              "name": "MathPlugin_Sum",
              "arguments": "{\"numbers\": [1.1, 2.2, 3.3]}"
            }
          }
        ]
LogRecord.Severity:                Trace
LogRecord.SeverityText:            Trace
LogRecord.FormattedMessage:        Function call requests: MathPlugin_Sum({"numbers":[1.1,2.2,3.3]})
LogRecord.Body:                    Function call requests: {Requests}
LogRecord.Attributes (Key:Value):
    Requests: MathPlugin_Sum({"numbers":[1.1,2.2,3.3]})
    OriginalFormat (a.k.a Body): Function call requests: {Requests}

However later on it looks like the method is being called with a string.

LogRecord.Timestamp:               2024-01-22T16:57:32.0827729Z
LogRecord.TraceId:                 2f4f4d4f22bfdc2eba3093ee96725edb
LogRecord.SpanId:                  fd6afb6fd9204111
LogRecord.TraceFlags:              Recorded
LogRecord.CategoryName:            Sum
LogRecord.Severity:                Trace
LogRecord.SeverityText:            Trace
LogRecord.FormattedMessage:        Function arguments: {"numbers":"[1.1,2.2,3.3]"}
LogRecord.Body:                    Function arguments: {Arguments}
LogRecord.Attributes (Key:Value):
    Arguments: {"numbers":"[1.1,2.2,3.3]"}
    OriginalFormat (a.k.a Body): Function arguments: {Arguments}

Here is my F# script. Run with dotnet fsi script.fsx.

#r "nuget: OpenTelemetry"
#r "nuget: OpenTelemetry.Instrumentation.Http"
#r "nuget: OpenTelemetry.Exporter.Console"
#r "nuget: Microsoft.Extensions.Logging"
#r "nuget: Microsoft.Extensions.Logging.Console"
#r "nuget: Microsoft.SemanticKernel"

open Microsoft.Extensions.Logging
open Microsoft.Extensions.DependencyInjection
open Microsoft.SemanticKernel
open Microsoft.SemanticKernel.ChatCompletion
open Microsoft.SemanticKernel.Connectors.OpenAI
open OpenTelemetry
open OpenTelemetry.Resources
open OpenTelemetry.Logs
open OpenTelemetry.Trace
open System.ComponentModel
open System
open System.Diagnostics
open System.IO
open System.Net.Http

module Env =
    let variable key =
        match Environment.GetEnvironmentVariable(key) with
        | s when String.IsNullOrEmpty(s) -> failwith $"Environment variable {key} is not set"
        | value -> value

type MathPlugin(loggerFactory:ILoggerFactory) =
    let logger = loggerFactory.CreateLogger<MathPlugin>()

    [<KernelFunction>]
    [<Description("Sum an array of numbers.")>]
    member _.Sum
        ([<Description("The array of numbers to sum.")>] numbers:float[])
         : [<Description("The sum of the numbers.")>] float =
        logger.LogDebug("Summing numbers {numbers}", numbers)
        Array.sum(numbers)

let resourceBuilder = ResourceBuilder.CreateDefault().AddService("Scratch")

let enrichHttpRequest (activity:Activity) (req:HttpRequestMessage) =
    req.Content.LoadIntoBufferAsync().Wait()
    let ms = new MemoryStream()
    req.Content.CopyToAsync(ms).Wait()
    ms.Seek(0L, SeekOrigin.Begin) |> ignore
    use reader = new StreamReader(ms)
    let content = reader.ReadToEnd()
    activity.SetTag("requestBody", content) |> ignore

let enrichHttpResponse (activity:Activity) (res:HttpResponseMessage) =
    res.Content.LoadIntoBufferAsync().Wait()
    let ms = new MemoryStream()
    res.Content.CopyToAsync(ms).Wait()
    ms.Seek(0L, SeekOrigin.Begin) |> ignore
    use reader = new StreamReader(ms)
    let content = reader.ReadToEnd()
    activity.SetTag("responseBody", content) |> ignore

let tracerProvider =
    Sdk.CreateTracerProviderBuilder()
        .SetResourceBuilder(resourceBuilder)
        .AddSource("Scratch") // subscribe to Scratch resource (created above) traces
        .AddSource("Microsoft.SemanticKernel*") // subscribe to semantic kernel traces
        .AddHttpClientInstrumentation(fun opts ->
            opts.EnrichWithHttpRequestMessage <- Action<Activity,HttpRequestMessage>(enrichHttpRequest)
            opts.EnrichWithHttpResponseMessage <- Action<Activity,HttpResponseMessage>(enrichHttpResponse))
        .AddConsoleExporter()
        .Build()
let loggerFactory =
    LoggerFactory.Create(fun builder ->
        builder
            .SetMinimumLevel(LogLevel.Trace)
            .AddOpenTelemetry(fun opts ->
                opts.SetResourceBuilder(resourceBuilder) |> ignore
                opts.AddConsoleExporter() |> ignore // export logs to console
                opts.IncludeFormattedMessage <- true)
            |> ignore)
let apiKey = Env.variable "OPENAI_API_KEY"
let builder = Kernel.CreateBuilder()
builder.Services.AddSingleton(loggerFactory)
builder.Services.AddOpenAIChatCompletion("gpt-3.5-turbo-1106", apiKey)
builder.Plugins.AddFromType<MathPlugin>()
let kernel = builder.Build()

let tracer = tracerProvider.GetTracer("Scratch")
let settings = OpenAIPromptExecutionSettings(ToolCallBehavior=ToolCallBehavior.AutoInvokeKernelFunctions)
let chatService = kernel.Services.GetRequiredService<IChatCompletionService>()
let history = ChatHistory("You are a friendly analyst. You can add numbers.")

let span = tracer.StartActiveSpan("Solving math problem")
history.AddUserMessage("What is the sum of 1.1, 2.2, and 3.3?")
let res = chatService.GetChatMessageContentAsync(history, settings, kernel).Result
span.Dispose()
printfn $"Response: {res}"

Expected behavior
I am expecting the method to be called with an array of floats instead of a string.

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

Platform

  • OS: Windows
  • IDE: VS Code
  • Language: F#
  • Source: nuget Micrsoft.SemanticKernel 1.1.0
@shawncal shawncal added .NET Issue or Pull requests regarding .NET code triage labels Jan 22, 2024
@github-actions github-actions bot changed the title Kernel function arguments cannot be complex type (e.g., array) .Net: Kernel function arguments cannot be complex type (e.g., array) Jan 22, 2024
@stephentoub
Copy link
Member

cc: @SergeyMenshykh

@matthewbolanos matthewbolanos added bug Something isn't working kernel Issues or pull requests impacting the core kernel and removed triage labels Jan 22, 2024
github-merge-queue bot pushed a commit that referenced this issue Feb 6, 2024
### Motivation and Context
It would be very convenient and beneficial for both SK consumer code and
SK itself if there was an implicit mechanism for deserializing function
arguments provided as JSON into their actual types specified in the
function signature, similar to the implicit data type conversion
mechanism SK currently has.

Closes #4696,
#3126

### Description
The change adds the 'TryToDeserializeValue' fallback, which attempts to
deserialize the argument value into the target parameter type if all the
methods to convert the argument to its actual type have been exhausted.
Bryan-Roe pushed a commit to Bryan-Roe/semantic-kernel that referenced this issue Oct 6, 2024
### Motivation and Context
It would be very convenient and beneficial for both SK consumer code and
SK itself if there was an implicit mechanism for deserializing function
arguments provided as JSON into their actual types specified in the
function signature, similar to the implicit data type conversion
mechanism SK currently has.

Closes microsoft#4696,
microsoft#3126

### Description
The change adds the 'TryToDeserializeValue' fallback, which attempts to
deserialize the argument value into the target parameter type if all the
methods to convert the argument to its actual type have been exhausted.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working kernel Issues or pull requests impacting the core kernel .NET Issue or Pull requests regarding .NET code
Projects
Archived in project
Development

Successfully merging a pull request may close this issue.

5 participants