feat: Add function calling support to invoke methods with dynamic arguments and return values
This change enables more flexible integration between Spring AI and LLM function
calling capabilities while maintaining type safety and ease of use.
- Add new MethodFunctionCallback class to support method invocation via reflection
- Supports both static and non-static method calls
- Handles multiple parameter types including primitives, objects, collections
- Supports empty parameters and empty response
- Auto-generates JSON schema from method parameters
- Special handling for ToolContext parameters
- Builder pattern for easy configuration
- Add comprehensive unit tests for MethodFunctionCallback
- Add integration tests for MethodFunctionCallback with both Anthropic and OpenAI clients
- Add jackson-module-jsonSchema dependency
- Modify FunctionCallback to check for empty tool context
Testing coverage includes:
- Static method invocation scenarios
- Non-static method calls with various parameter types
- Void return type methods
- Complex parameter types (enums, records, lists)
- Tool context handling
- Error cases and validation
Comment From: markpollack
This looks amazing. I'm havingsome odd issues that seem to be related to classpath.
when building and then running ./mvnw verify -Pintegration-tests -pl models/spring-ai-openai
I get a test failure
[INFO] Running org.springframework.ai.openai.chat.client.OpenAiChatClientMethodFunctionCallbackIT
[ERROR] Tests run: 6, Failures: 1, Errors: 5, Skipped: 0, Time elapsed: 0.029 s <<< FAILURE! -- in org.springframework.ai.openai.chat.client.OpenAiChatClientMethodFunctionCallbackIT
[ERROR] org.springframework.ai.openai.chat.client.OpenAiChatClientMethodFunctionCallbackIT.methodTurnLightNoResponse -- Time elapsed: 0.005 s <<< ERROR!
java.lang.NoClassDefFoundError: org/springframework/ai/model/function/MethodFunctionCallback
at org.springframework.ai.openai.chat.client.OpenAiChatClientMethodFunctionCallbackIT.methodTurnLightNoResponse(OpenAiChatClientMethodFunctionCallbackIT.java:145)
at java.base/java.lang.reflect.Method.invoke(Method.java:569)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
Caused by: java.lang.ClassNotFoundException: org.springframework.ai.model.function.MethodFunctionCallback
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525)
... 4 more
[ERROR] org.springframework.ai.openai.chat.client.OpenAiChatClientMethodFunctionCallbackIT.methodGetWeatherToolContext -- Time elapsed: 0.002 s <<< ERROR!
java.lang.NoClassDefFoundError: org/springframework/ai/model/function/MethodFunctionCallback
at org.springframework.ai.openai.chat.client.OpenAiChatClientMethodFunctionCallbackIT.methodGetWeatherToolContext(OpenAiChatClientMethodFunctionCallbackIT.java:196)
at java.base/java.lang.reflect.Method.invoke(Method.java:569)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
Caused by: java.lang.ClassNotFoundException: org.springframework.ai.model.function.MethodFunctionCallback
... 4 more
[ERROR] org.springframework.ai.openai.chat.client.OpenAiChatClientMethodFunctionCallbackIT.methodNoParameters -- Time elapsed: 0.002 s <<< ERROR!
java.lang.NoClassDefFoundError: org/springframework/ai/model/function/MethodFunctionCallback
at org.springframework.ai.openai.chat.client.OpenAiChatClientMethodFunctionCallbackIT.methodNoParameters(OpenAiChatClientMethodFunctionCallbackIT.java:246)
at java.base/java.lang.reflect.Method.invoke(Method.java:569)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
Caused by: java.lang.ClassNotFoundException: org.springframework.ai.model.function.MethodFunctionCallback
... 4 more
[ERROR] org.springframework.ai.openai.chat.client.OpenAiChatClientMethodFunctionCallbackIT.methodGetWeatherNonStatic -- Time elapsed: 0.002 s <<< ERROR!
java.lang.NoClassDefFoundError: org/springframework/ai/model/function/MethodFunctionCallback
at org.springframework.ai.openai.chat.client.OpenAiChatClientMethodFunctionCallbackIT.methodGetWeatherNonStatic(OpenAiChatClientMethodFunctionCallbackIT.java:171)
at java.base/java.lang.reflect.Method.invoke(Method.java:569)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
Caused by: java.lang.ClassNotFoundException: org.springframework.ai.model.function.MethodFunctionCallback
... 4 more
[ERROR] org.springframework.ai.openai.chat.client.OpenAiChatClientMethodFunctionCallbackIT.methodGetWeatherToolContextButNonContextMethod -- Time elapsed: 0.005 s <<< FAILURE!
java.lang.AssertionError: Configured method does not accept ToolContext as input parameter!: unexpected exception type thrown; expected:<java.lang.IllegalArgumentException> but was:<java.lang.NoClassDefFoundError>
at org.junit.Assert.assertThrows(Assert.java:1020)
at org.springframework.ai.openai.chat.client.OpenAiChatClientMethodFunctionCallbackIT.methodGetWeatherToolContextButNonContextMethod(OpenAiChatClientMethodFunctionCallbackIT.java:221)
at java.base/java.lang.reflect.Method.invoke(Method.java:569)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
Caused by: java.lang.NoClassDefFoundError: org/springframework/ai/model/function/MethodFunctionCallback
at org.springframework.ai.openai.chat.client.OpenAiChatClientMethodFunctionCallbackIT.lambda$methodGetWeatherToolContextButNonContextMethod$0(OpenAiChatClientMethodFunctionCallbackIT.java:224)
at org.junit.Assert.assertThrows(Assert.java:1001)
... 4 more
Caused by: java.lang.ClassNotFoundException: org.springframework.ai.model.function.MethodFunctionCallback
... 6 more
[ERROR] org.springframework.ai.openai.chat.client.OpenAiChatClientMethodFunctionCallbackIT.methodGetWeatherStatic -- Time elapsed: 0.001 s <<< ERROR!
java.lang.NoClassDefFoundError: org/springframework/ai/model/function/MethodFunctionCallback
at org.springframework.ai.openai.chat.client.OpenAiChatClientMethodFunctionCallbackIT.methodGetWeatherStatic(OpenAiChatClientMethodFunctionCallbackIT.java:122)
at java.base/java.lang.reflect.Method.invoke(Method.java:569)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
Caused by: java.lang.ClassNotFoundException: org.springframework.ai.model.function.MethodFunctionCallback
... 4 more
and when running it in intellij it gives
java.lang.NoClassDefFoundError: com/fasterxml/jackson/module/jsonSchema/JsonSchemaGenerator
at org.springframework.ai.model.function.MethodFunctionCallback.generateJsonSchema(MethodFunctionCallback.java:184)
at org.springframework.ai.model.function.MethodFunctionCallback.<init>(MethodFunctionCallback.java:108)
at org.springframework.ai.model.function.MethodFunctionCallback$Builder.build(MethodFunctionCallback.java:305)
at org.springframework.ai.openai.chat.client.OpenAiChatClientMethodFunctionCallbackIT.methodTurnLightNoResponse(OpenAiChatClientMethodFunctionCallbackIT.java:149)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
Caused by: java.lang.ClassNotFoundException: com.fasterxml.jackson.module.jsonSchema.JsonSchemaGenerator
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526)
... 7 more
when running OpenAiChatClientMethodFunctionCallbackIT.methodTurnLightNoResponse
Comment From: tzolov
@markpollack thanks for reviewing it.
I guess the errors you observer are due to the need to rebuild the entire project first: e.g. ./mvnw clean install -DskipTests
or ./mvnw clean install -DskipTests
-U
.
Then the ./mvnw verify -Pintegration-tests -pl models/spring-ai-openai
should work. I guess something similar is happening with the IDE.
Comment From: markpollack
The -U seems to have helped with the maven build, so that is passing now. For IntelliJ I had to do the 'repair ide' steps and eventually it worked. Odd.
Comment From: markpollack
merged in 5f6b892eda068c420ba421f7421501eef726a2ec