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.