Skip to content

Commit 6155765

Browse files
committed
Add support for Kotlin functions
This commit adds support for proper Kotlin functions handling by adapting kotlin.jvm.functions.Function1 to java.util.function.Function and kotlin.jvm.functions.Function2 to java.util.function.BiFunction. It also removes the dependency on Spring Cloud Function and net.jodah:typetools which are replaced by leveraging Spring Framework ResolvableType capabilities.
1 parent bf66415 commit 6155765

File tree

11 files changed

+667
-118
lines changed

11 files changed

+667
-118
lines changed

spring-ai-core/pom.xml

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -54,18 +54,6 @@
5454
<version>${jsonschema.version}</version>
5555
</dependency>
5656

57-
<dependency>
58-
<groupId>org.springframework.cloud</groupId>
59-
<artifactId>spring-cloud-function-context</artifactId>
60-
<version>${spring-cloud-function-context.version}</version>
61-
<exclusions>
62-
<exclusion>
63-
<groupId>org.springframework.boot</groupId>
64-
<artifactId>spring-boot-autoconfigure</artifactId>
65-
</exclusion>
66-
</exclusions>
67-
</dependency>
68-
6957
<!-- production dependencies -->
7058
<dependency>
7159
<groupId>org.antlr</groupId>
@@ -138,6 +126,13 @@
138126
<version>${jackson.version}</version>
139127
</dependency>
140128

129+
<dependency>
130+
<groupId>org.jetbrains.kotlin</groupId>
131+
<artifactId>kotlin-stdlib</artifactId>
132+
<version>${kotlin.version}</version>
133+
<optional>true</optional>
134+
</dependency>
135+
141136
<!-- test dependencies -->
142137
<dependency>
143138
<groupId>org.springframework.boot</groupId>
@@ -146,16 +141,16 @@
146141
</dependency>
147142

148143
<dependency>
149-
<groupId>org.jetbrains.kotlin</groupId>
150-
<artifactId>kotlin-stdlib</artifactId>
151-
<version>${kotlin.version}</version>
144+
<groupId>com.fasterxml.jackson.module</groupId>
145+
<artifactId>jackson-module-kotlin</artifactId>
146+
<version>${jackson.version}</version>
152147
<scope>test</scope>
153148
</dependency>
154149

155150
<dependency>
156-
<groupId>com.fasterxml.jackson.module</groupId>
157-
<artifactId>jackson-module-kotlin</artifactId>
158-
<version>${jackson.version}</version>
151+
<groupId>io.mockk</groupId>
152+
<artifactId>mockk-jvm</artifactId>
153+
<version>1.13.13</version>
159154
<scope>test</scope>
160155
</dependency>
161156

spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallbackContext.java

Lines changed: 54 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,21 @@
1616

1717
package org.springframework.ai.model.function;
1818

19-
import java.lang.reflect.Type;
2019
import java.util.function.BiFunction;
2120
import java.util.function.Function;
2221

2322
import com.fasterxml.jackson.annotation.JsonClassDescription;
23+
import kotlin.jvm.functions.Function1;
24+
import kotlin.jvm.functions.Function2;
2425

2526
import org.springframework.ai.chat.model.ToolContext;
2627
import org.springframework.beans.BeansException;
27-
import org.springframework.cloud.function.context.catalog.FunctionTypeUtils;
28-
import org.springframework.cloud.function.context.config.FunctionContextUtils;
2928
import org.springframework.context.ApplicationContext;
3029
import org.springframework.context.ApplicationContextAware;
3130
import org.springframework.context.annotation.Description;
3231
import org.springframework.context.support.GenericApplicationContext;
32+
import org.springframework.core.KotlinDetector;
33+
import org.springframework.core.ResolvableType;
3334
import org.springframework.lang.NonNull;
3435
import org.springframework.lang.Nullable;
3536
import org.springframework.util.StringUtils;
@@ -49,6 +50,7 @@
4950
*
5051
* @author Christian Tzolov
5152
* @author Christopher Smith
53+
* @author Sebastien Deleuze
5254
*/
5355
public class FunctionCallbackContext implements ApplicationContextAware {
5456

@@ -65,26 +67,13 @@ public void setApplicationContext(@NonNull ApplicationContext applicationContext
6567
this.applicationContext = (GenericApplicationContext) applicationContext;
6668
}
6769

68-
@SuppressWarnings({ "rawtypes", "unchecked" })
70+
@SuppressWarnings({ "unchecked" })
6971
public FunctionCallback getFunctionCallback(@NonNull String beanName, @Nullable String defaultDescription) {
7072

71-
Type beanType = FunctionContextUtils.findType(this.applicationContext.getBeanFactory(), beanName);
73+
ResolvableType functionType = TypeResolverHelper.resolveBeanType(this.applicationContext, beanName);
74+
ResolvableType functionInputType = TypeResolverHelper.getFunctionArgumentType(functionType, 0);
7275

73-
if (beanType == null) {
74-
throw new IllegalArgumentException(
75-
"Functional bean with name: " + beanName + " does not exist in the context.");
76-
}
77-
78-
if (!Function.class.isAssignableFrom(FunctionTypeUtils.getRawType(beanType))
79-
&& !BiFunction.class.isAssignableFrom(FunctionTypeUtils.getRawType(beanType))) {
80-
throw new IllegalArgumentException(
81-
"Function call Bean must be of type Function or BiFunction. Found: " + beanType.getTypeName());
82-
}
83-
84-
Type functionInputType = TypeResolverHelper.getFunctionArgumentType(beanType, 0);
85-
86-
Class<?> functionInputClass = FunctionTypeUtils.getRawType(functionInputType);
87-
String functionName = beanName;
76+
Class<?> functionInputClass = functionInputType.toClass();
8877
String functionDescription = defaultDescription;
8978

9079
if (!StringUtils.hasText(functionDescription)) {
@@ -114,24 +103,42 @@ public FunctionCallback getFunctionCallback(@NonNull String beanName, @Nullable
114103

115104
Object bean = this.applicationContext.getBean(beanName);
116105

106+
if (KotlinDetector.isKotlinPresent()) {
107+
if (KotlinDelegate.isKotlinFunction(functionType.toClass())) {
108+
return FunctionCallbackWrapper.builder(KotlinDelegate.wrapKotlinFunction(bean))
109+
.withName(beanName)
110+
.withSchemaType(this.schemaType)
111+
.withDescription(functionDescription)
112+
.withInputType(functionInputClass)
113+
.build();
114+
}
115+
else if (KotlinDelegate.isKotlinBiFunction(functionType.toClass())) {
116+
return FunctionCallbackWrapper.builder(KotlinDelegate.wrapKotlinBiFunction(bean))
117+
.withName(beanName)
118+
.withSchemaType(this.schemaType)
119+
.withDescription(functionDescription)
120+
.withInputType(functionInputClass)
121+
.build();
122+
}
123+
}
117124
if (bean instanceof Function<?, ?> function) {
118125
return FunctionCallbackWrapper.builder(function)
119-
.withName(functionName)
126+
.withName(beanName)
120127
.withSchemaType(this.schemaType)
121128
.withDescription(functionDescription)
122129
.withInputType(functionInputClass)
123130
.build();
124131
}
125-
else if (bean instanceof BiFunction<?, ?, ?> biFunction) {
126-
return FunctionCallbackWrapper.builder((BiFunction<?, ToolContext, ?>) biFunction)
127-
.withName(functionName)
132+
else if (bean instanceof BiFunction<?, ?, ?>) {
133+
return FunctionCallbackWrapper.builder((BiFunction<?, ToolContext, ?>) bean)
134+
.withName(beanName)
128135
.withSchemaType(this.schemaType)
129136
.withDescription(functionDescription)
130137
.withInputType(functionInputClass)
131138
.build();
132139
}
133140
else {
134-
throw new IllegalArgumentException("Bean must be of type Function");
141+
throw new IllegalStateException();
135142
}
136143
}
137144

@@ -141,4 +148,26 @@ public enum SchemaType {
141148

142149
}
143150

151+
private static class KotlinDelegate {
152+
153+
public static boolean isKotlinFunction(Class<?> clazz) {
154+
return Function1.class.isAssignableFrom(clazz);
155+
}
156+
157+
@SuppressWarnings("unchecked")
158+
public static Function<?, ?> wrapKotlinFunction(Object function) {
159+
return t -> ((Function1<Object, Object>) function).invoke(t);
160+
}
161+
162+
public static boolean isKotlinBiFunction(Class<?> clazz) {
163+
return Function2.class.isAssignableFrom(clazz);
164+
}
165+
166+
@SuppressWarnings("unchecked")
167+
public static BiFunction<?, ToolContext, ?> wrapKotlinBiFunction(Object function) {
168+
return (t, u) -> ((Function2<Object, ToolContext, Object>) function).invoke(t, u);
169+
}
170+
171+
}
172+
144173
}

0 commit comments

Comments
 (0)