Skip to content

Commit c3c95a8

Browse files
sdeleuzetzolov
authored andcommitted
Add Kotlin support and documentation
- Add 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`. - Removes the dependency on Spring Cloud Function and `net.jodah:typetools` which are replaced by leveraging Spring Framework `ResolvableType` capabilities. - Add a Kotlin extension function for `FunctionCallbackWrapper.Builder.withInputType` allowing to specify `withInputType<T>()` instead of `withInputType(T::class.java)`. - Add Kotlin documentation.
1 parent 78a2a27 commit c3c95a8

File tree

15 files changed

+861
-122
lines changed

15 files changed

+861
-122
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)