Bug description When using a Kotlin function, the input type is not inferred. This causes an invalid_request_error error:

com.azure.core.exception.HttpResponseException: Status code 400, "{
  "error": {
    "message": "Invalid schema for function 'Weather': schema must be a JSON Schema of 'type: \"object\"', got 'type: \"None\"'.",
    "type": "invalid_request_error",
    "param": "tools[0].function.parameters",
    "code": "invalid_function_parameters"
  }
}"

Workaround is to specify the input type explictly withInputType (and use a custom object mapper)

Environment Latest Snapshot.

Steps to reproduce See example below

Expected behavior Spring AI should also work with Kotlin or update the documentation.

Minimal Complete Reproducible example


@Component
class MyAi(
  val functionCallbacks: List<FunctionCallback>,
) : ApplicationRunner {

  override fun run(args: ApplicationArguments) {

    val openAIClient = OpenAIClientBuilder()
      .credential(AzureKeyCredential(System.getenv("AZURE_OPENAI_API_KEY")))
      .endpoint(System.getenv("AZURE_OPENAI_ENDPOINT"))
      .buildClient()

    val openAIChatOptions = AzureOpenAiChatOptions.builder()
      .withDeploymentName("policy-ai-gpt-4o")
      .withTemperature(0.0f)
      .withFunctionCallbacks(functionCallbacks)
      .build()

    val chatModel = AzureOpenAiChatModel(openAIClient, openAIChatOptions)

    val response = chatModel.call(
      Prompt("Is it rainy in Paris?", AzureOpenAiChatOptions.builder().withFunction("Weather").build())
    )

    println(response.result.output)

  }
}

@Configuration
class AiFunctionCallbacks(val objectMapper: ObjectMapper) {

  @Bean
  fun weatherFunctionCallback(): FunctionCallback {
    return FunctionCallbackWrapper.builder { request: WeatherRequest ->
      WeatherResponse(rainy = false)
    }
      .withName("Weather")
      .withDescription("Get weather information for a city")
      .withObjectMapper(objectMapper)
//      .withInputType(WeatherRequest::class.java) // this is required, but should not
      .build()
  }

  data class WeatherRequest(
    val city: String?,
  )

  data class WeatherResponse(
    val rainy: Boolean?,
  )
}

Comment From: KAMO030

I reproduced the problem. The cause is that when inputType is not specified, the resolveInputType function uses reflection to get the generic type. However, when using a function with a trailing lambda, it is not possible to obtain the generic type declaration.

public FunctionCallbackWrapper<I, O> build() {
// ...
            if (this.inputType == null) {
                this.inputType = FunctionCallbackWrapper.resolveInputType(this.function);
            }
// ...
}

Currently, the issue can be resolved through the aforementioned method, or by passing in a specific implementation class. Alternatively, it could be addressed by adding support for reified generic function extensions in the library:

inline fun<reified I,O> functionCallbackWrapperBuild(
    noinline function:(I)->O
): FunctionCallbackWrapper.Builder<I,O> =
    FunctionCallbackWrapper.builder(function).withInputType(I::class.java)

or perhaps by import other reflection packages to obtain the Metadata info?

Comment From: tzolov

@jochenchrist , @KAMO030 not Kotlin user (yet) but can imagine that the generic inference can get messed up. @KAMO030 we can not plug Kotlin code in the project. Is there a pure java way to extend the reflection generic libs?

Comment From: sdeleuze

The issue is superseded by #1666. See also #1667.