Bug description
I created a custom ChatClient Bean (instead of declaring the model configuration in the application.yml) and autowired it into my Controller. Everything worked fine before I used this ChatClient to invoke functions in the model. However, as soon as I used it to call a function in the model, an error like java.lang.IllegalStateException: No function callback found for name: weatherByCity
occurred. It doesn't work no matter how I try to call it. I spent an entire day troubleshooting it.
In the end, I found that when I define the model, key, and base-url in application.yml, it works.
I'm sorry this might be a silly question.But I’m not sure why this happens. Is this a bug?
Environment
JDK: OpenJDK-21.0.3 SpringBoot: 3.4.2-SNAPSHOT Spring-ai: 1.0.0-SNAPSHOT
Steps to reproduce
As shown below, when the weatherByCity function is called by AI, an IllegalArgumentException will be thrown
Minimal Complete Reproducible example
@Configuration
public class ModelConfig {
@Bean
public ChatClient myClient() {
OpenAiApi openAiApi = new OpenAiApi(MY_BASE_URL, MY_KEY);
OpenAiChatOptions openAiChatOptions = new OpenAiChatOptions();
openAiChatOptions.setModel("gpt-4o");
openAiChatOptions.setStreamOptions(new OpenAiApi.ChatCompletionRequest.StreamOptions(true));
OpenAiChatModel chatModel = new OpenAiChatModel(openAiApi, openAiChatOptions);
return ChatClient.builder(chatModel).build();
}
}
@RestController
public class ChatController {
@Autowired
@Qualifier("myClient")
private ChatClient chatClient;
@GetMapping("/chat/function")
String chat(String cityName) {
var userPromptTemplate = "What's the weather like in {cityName} now?";
return chatClient.prompt()
.user(userSpec -> userSpec
.text(userPromptTemplate)
.param("cityName",cityName )
)
.functions("weatherByCity")
.call()
.content();
}
@Configuration(proxyBeanMethods = false)
public class Functions {
@Bean
@Description("Get weather based on the input city name")
public Function<WeatherService.City,WeatherService.WeatherInfo> weatherByCity(WeatherService weatherService){
return weatherService::getCityWeather;
}
}
@Service
public class WeatherService {
private String URL = "***";
private RestClient restClient;
public WeatherService() {
this.restClient = RestClient.create(URL);
}
public record City(String city){}
public record WeatherInfo(String city,Data data){}
public record Data(String date,Condition night,String high,String low){}
public record Condition(String type,String fengli){}
public WeatherInfo getCityWeather(City cityName) {
System.out.println("request is :"+cityName);
WeatherInfo response = restClient.get()
.uri("/weather?city={city}", cityName.city())
.retrieve()
.body(WeatherInfo.class);
System.out.println("response is :"+response);
return response;
}
}
Logger
java.lang.IllegalStateException: No function callback found for name: weatherByCity
at org.springframework.ai.chat.model.AbstractToolCallSupport.resolveFunctionCallbacks(AbstractToolCallSupport.java:197) ~[spring-ai-core-1.0.0-20241224.161216-1116.jar:1.0.0-SNAPSHOT]
at org.springframework.ai.openai.OpenAiChatModel.getFunctionTools(OpenAiChatModel.java:643) ~[spring-ai-openai-1.0.0-20241224.161216-1038.jar:1.0.0-SNAPSHOT]
at org.springframework.ai.openai.OpenAiChatModel.createRequest(OpenAiChatModel.java:591) ~[spring-ai-openai-1.0.0-20241224.161216-1038.jar:1.0.0-SNAPSHOT]
at org.springframework.ai.openai.OpenAiChatModel.internalCall(OpenAiChatModel.java:225) ~[spring-ai-openai-1.0.0-20241224.161216-1038.jar:1.0.0-SNAPSHOT]
at org.springframework.ai.openai.OpenAiChatModel.call(OpenAiChatModel.java:220) ~[spring-ai-openai-1.0.0-20241224.161216-1038.jar:1.0.0-SNAPSHOT]
at org.springframework.ai.chat.client.DefaultChatClient$DefaultChatClientRequestSpec$1.aroundCall(DefaultChatClient.java:675) ~[spring-ai-core-1.0.0-20241224.161216-1116.jar:1.0.0-SNAPSHOT]
at org.springframework.ai.chat.client.advisor.DefaultAroundAdvisorChain.lambda$nextAroundCall$1(DefaultAroundAdvisorChain.java:98) ~[spring-ai-core-1.0.0-20241224.161216-1116.jar:1.0.0-SNAPSHOT]
at io.micrometer.observation.Observation.observe(Observation.java:564) ~[micrometer-observation-1.14.2.jar:1.14.2]
at org.springframework.ai.chat.client.advisor.DefaultAroundAdvisorChain.nextAroundCall(DefaultAroundAdvisorChain.java:98) ~[spring-ai-core-1.0.0-20241224.161216-1116.jar:1.0.0-SNAPSHOT]
at org.springframework.ai.chat.client.DefaultChatClient$DefaultCallResponseSpec.doGetChatResponse(DefaultChatClient.java:488) ~[spring-ai-core-1.0.0-20241224.161216-1116.jar:1.0.0-SNAPSHOT]
at org.springframework.ai.chat.client.DefaultChatClient$DefaultCallResponseSpec.lambda$doGetObservableChatResponse$1(DefaultChatClient.java:477) ~[spring-ai-core-1.0.0-20241224.161216-1116.jar:1.0.0-SNAPSHOT]
at io.micrometer.observation.Observation.observe(Observation.java:564) ~[micrometer-observation-1.14.2.jar:1.14.2]
at org.springframework.ai.chat.client.DefaultChatClient$DefaultCallResponseSpec.doGetObservableChatResponse(DefaultChatClient.java:477) ~[spring-ai-core-1.0.0-20241224.161216-1116.jar:1.0.0-SNAPSHOT]
at org.springframework.ai.chat.client.DefaultChatClient$DefaultCallResponseSpec.doGetChatResponse(DefaultChatClient.java:461) ~[spring-ai-core-1.0.0-20241224.161216-1116.jar:1.0.0-SNAPSHOT]
at org.springframework.ai.chat.client.DefaultChatClient$DefaultCallResponseSpec.content(DefaultChatClient.java:511) ~[spring-ai-core-1.0.0-20241224.161216-1116.jar:1.0.0-SNAPSHOT]
at com.ling.controller.ChatController.chat(ChatController.java:44) ~[classes/:na]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:257) ~[spring-web-6.2.1.jar:6.2.1]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:190) ~[spring-web-6.2.1.jar:6.2.1]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) ~[spring-webmvc-6.2.1.jar:6.2.1]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986) ~[spring-webmvc-6.2.1.jar:6.2.1]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891) ~[spring-webmvc-6.2.1.jar:6.2.1]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-6.2.1.jar:6.2.1]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1088) ~[spring-webmvc-6.2.1.jar:6.2.1]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:978) ~[spring-webmvc-6.2.1.jar:6.2.1]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) ~[spring-webmvc-6.2.1.jar:6.2.1]
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903) ~[spring-webmvc-6.2.1.jar:6.2.1]
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564) ~[tomcat-embed-core-10.1.34.jar:6.0]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) ~[spring-webmvc-6.2.1.jar:6.2.1]
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) ~[tomcat-embed-core-10.1.34.jar:6.0]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) ~[tomcat-embed-websocket-10.1.34.jar:10.1.34]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-6.2.1.jar:6.2.1]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.2.1.jar:6.2.1]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-6.2.1.jar:6.2.1]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.2.1.jar:6.2.1]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-6.2.1.jar:6.2.1]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.2.1.jar:6.2.1]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:397) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:905) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1190) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) ~[tomcat-embed-core-10.1.34.jar:10.1.34]
at java.base/java.lang.Thread.run(Thread.java:1583) ~[na:na]
Comment From: LingLambda
sorry, the issue was mistakenly closed earlier.
Comment From: LingLambda
In short, when I manually configure ChatClient
or ChatClient.Builder
as a Bean and use them to call the function I created, the function cannot be found. However, this issue does not occur when using autoconfiguration. During debugging, I traced the issue to line 186 of org.springframework.ai.chat.model.AbstractToolCallSupport
, where functionCallbackResolver
is null
, causing this exception to be thrown.
Manual Configuration :
Auto Configuration
Comment From: ThomasVitale
The problem seems to be when building the ChatModel
instance. If you want to use the function calling capabilities, the OpenAiChatModel
needs to have a FunctionCallbackResolver
instance. You can either autowire one (there's an instance auto-configured by the Spring AI OpenAI Spring Boot Starter) or instantiate your own (from DefaultFunctionCallbackResolver
).
@Bean
public ChatClient myClient(FunctionCallbackResolver functionCallbackResolver) {
...
OpenAiChatModel chatModel = new OpenAiChatModel(openAiApi, openAiChatOptions, functionCallbackResolver, RetryUtils.DEFAULT_RETRY_TEMPLATE);
...
}
Comment From: LingLambda
问题似乎出在构建
ChatModel
实例时。如果要使用函数调用功能,OpenAiChatModel
需要有一个FunctionCallbackResolver
实例。您可以自动连接一个实例(Spring AI OpenAI Spring Boot Starter 自动配置了一个实例),也可以实例化您自己的实例(从DefaultFunctionCallbackResolver
开始)。
@Bean public ChatClient myClient(FunctionCallbackResolver functionCallbackResolver) { ... OpenAiChatModel chatModel = new OpenAiChatModel(openAiApi, openAiChatOptions, functionCallbackResolver, RetryUtils.DEFAULT_RETRY_TEMPLATE); ... }
Thank you so much for your help and explanation!