From 227f0703ec63f873c08de98ee4d5b4ff1b43f702 Mon Sep 17 00:00:00 2001 From: Christian Tzolov Date: Thu, 16 May 2024 09:41:01 +0200 Subject: [PATCH] Fix: function calling not able to resolve input types Resolves #726 --- .../ai/model/function/TypeResolverHelper.java | 14 +-- .../function/StandaloneWeatherFunction.java | 34 +++++++ .../model/function/TypeResolverHelperIT.java | 90 +++++++++++++++++++ 3 files changed, 132 insertions(+), 6 deletions(-) create mode 100644 spring-ai-core/src/test/java/org/springframework/ai/model/function/StandaloneWeatherFunction.java create mode 100644 spring-ai-core/src/test/java/org/springframework/ai/model/function/TypeResolverHelperIT.java diff --git a/spring-ai-core/src/main/java/org/springframework/ai/model/function/TypeResolverHelper.java b/spring-ai-core/src/main/java/org/springframework/ai/model/function/TypeResolverHelper.java index 7d40ebac7..f35411b15 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/model/function/TypeResolverHelper.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/model/function/TypeResolverHelper.java @@ -22,6 +22,8 @@ import java.util.function.Function; import net.jodah.typetools.TypeResolver; +import org.springframework.cloud.function.context.catalog.FunctionTypeUtils; + /** * @author Christian Tzolov */ @@ -58,6 +60,12 @@ public class TypeResolverHelper { } public static Type getFunctionArgumentType(Type functionType, int argumentIndex) { + + // Resolves: https://github.com/spring-projects/spring-ai/issues/726 + if (!(functionType instanceof ParameterizedType)) { + functionType = FunctionTypeUtils.discoverFunctionTypeFromClass(FunctionTypeUtils.getRawType(functionType)); + } + var argumentType = functionType instanceof ParameterizedType ? ((ParameterizedType) functionType).getActualTypeArguments()[argumentIndex] : Object.class; @@ -77,10 +85,4 @@ public class TypeResolverHelper { : null; } - // public static void main(String[] args) { - // Class> clazz = MockWeatherService.class; - // System.out.println(getFunctionInputType(clazz)); - - // } - } diff --git a/spring-ai-core/src/test/java/org/springframework/ai/model/function/StandaloneWeatherFunction.java b/spring-ai-core/src/test/java/org/springframework/ai/model/function/StandaloneWeatherFunction.java new file mode 100644 index 000000000..b5ac63a52 --- /dev/null +++ b/spring-ai-core/src/test/java/org/springframework/ai/model/function/StandaloneWeatherFunction.java @@ -0,0 +1,34 @@ +/* + * Copyright 2024-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.model.function; + +import java.util.function.Function; + +import org.springframework.ai.model.function.TypeResolverHelperIT.WeatherRequest; +import org.springframework.ai.model.function.TypeResolverHelperIT.WeatherResponse; + +/** + * @author Christian Tzolov + */ +public class StandaloneWeatherFunction implements Function { + + @Override + public WeatherResponse apply(WeatherRequest weatherRequest) { + return new WeatherResponse(42.0f); + } + +} diff --git a/spring-ai-core/src/test/java/org/springframework/ai/model/function/TypeResolverHelperIT.java b/spring-ai-core/src/test/java/org/springframework/ai/model/function/TypeResolverHelperIT.java new file mode 100644 index 000000000..f4647be23 --- /dev/null +++ b/spring-ai-core/src/test/java/org/springframework/ai/model/function/TypeResolverHelperIT.java @@ -0,0 +1,90 @@ +/* + * Copyright 2023 - 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ai.model.function; + +import java.lang.reflect.Type; +import java.util.function.Function; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.function.context.config.FunctionContextUtils; +import org.springframework.context.annotation.Bean; +import org.springframework.context.support.GenericApplicationContext; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +class TypeResolverHelperIT { + + @Autowired + GenericApplicationContext applicationContext; + + @ParameterizedTest(name = "{0} : {displayName} ") + @ValueSource(strings = { "weatherClassDefinition", "weatherFunctionDefinition", "standaloneWeatherFunction" }) + void beanInputTypeResolutionTest(String beanName) { + assertThat(applicationContext).isNotNull(); + Type beanType = FunctionContextUtils.findType(applicationContext.getBeanFactory(), beanName); + assertThat(beanType).isNotNull(); + Type functionInputType = TypeResolverHelper.getFunctionArgumentType(beanType, 0); + assertThat(functionInputType).isNotNull(); + assertThat(functionInputType.getTypeName()).isEqualTo(WeatherRequest.class.getName()); + + } + + public record WeatherRequest(String city) { + } + + public record WeatherResponse(float temperatureInCelsius) { + } + + public static class Outer { + + public static class InnerWeatherFunction implements Function { + + @Override + public WeatherResponse apply(WeatherRequest weatherRequest) { + return new WeatherResponse(42.0f); + } + + } + + } + + @SpringBootConfiguration + public static class TypeResolverHelperConfiguration { + + @Bean() + Outer.InnerWeatherFunction weatherClassDefinition() { + return new Outer.InnerWeatherFunction(); + } + + @Bean() + Function weatherFunctionDefinition() { + return new Outer.InnerWeatherFunction(); + } + + @Bean() + StandaloneWeatherFunction standaloneWeatherFunction() { + return new StandaloneWeatherFunction(); + } + + } + +}