Spring AI:ChatClient动态变量函数包装器添加问题与API密钥报错排查
Spring AI动态函数回调配置问题及解决方案
背景
尝试实现Spring AI团队GitHub上的预订示例类似功能,但文档已过时,修改实现时遇到错误,需要帮助。
问题
目前缺少清晰示例或文档说明如何添加可动态接收变量并在聊天提示执行时用于函数回调的函数包装器,同时疑问函数包装器是否已被弃用?
示例场景
想要实现的简化版本聚焦于修改预订日期:客户在进入聊天会话前通过表单提供预订ID(无需AI参与),客户IP地址等静态数据由调用应用传入,最终请求由静态数据+AI输入参数组成。
@Service public class CustomerSupportAssistant { private final ChatClient chatClient; private final BookingService bookingService; @Autowired public CustomerSupportAssistant( ChatClient.Builder modelBuilder, VectorStore vectorStore, ChatMemory chatMemory, BookingService bookingService ) { this.bookingService = bookingService; this.chatClient = modelBuilder .defaultSystem(""" You are a customer chat support agent of an airline named "Funnair". Respond in a friendly, helpful, and joyful manner. You are interacting with customers through an online chat system. Use the provided functions to change bookings. Today is {current_date}. """) .defaultAdvisors( new PromptChatMemoryAdvisor(chatMemory), new QuestionAnswerAdvisor(vectorStore, SearchRequest.defaults())) // removed the default functions to add dynamic input functions .build(); } public Flux<String> chat( String chatId, String userMessageContent, Booking booking, // 由调用应用传入 String ipAddress // 由调用应用传入,无需客户在聊天中提供 ) { var dynamicChatOptions = OpenAiChatOptions.builder() .withModel("gpt-3.5-turbo") .withMaxTokens(100) .withFunctionCallbacks( List.of( FunctionCallbackWrapper.builder( (ChangeBookingDatesRequest request) -> { bookingService.changeBooking( booking.id(), request.from(), // 用于验证日期是否合法 request.to(), request.ipAddress() // 安全检查或审计日志 ); return "Booking dates changed successfully"; }) .withName("changeBooking") .withDescription("Change booking dates with additional parameters") .build())) .build(); return this.chatClient.prompt() .withOptions(dynamicChatOptions) .system(s -> s.param("current_date", LocalDate.now().toString())) .user(userMessageContent) .advisors(a -> a .param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId) .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 100)) .stream().content(); } }
错误现象
添加dynamicChatOptions到chatClient.prompt()方法后出现API密钥未提供的错误,此前代码正常运行。
配置信息
spring: ai: openai: api-key: ${OPEN_AI_KEY} chat: options: model: gpt-3.5-turbo-0125
错误详情
{ "error": { "message": "You didn't provide an API key. You need to provide your API key in an Authorization header using Bearer auth (i.e. Authorization: Bearer YOUR_KEY), or as the password field (with blank username) if you're accessing the API from your browser and are prompted for a username and password. You can obtain an API key from https://platform.openai.com/account/api-keys.", "type": "invalid_request_error", "param": null, "code": null } }
org.springframework.web.reactive.function.client.WebClientResponseException$BadRequest: 400 Bad Request from POST https://api.openai.com/v1/chat/completions at org.springframework.web.reactive.function.client.WebClientResponseException.create(WebClientResponseException.java:307) Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: Error has been observed at the following site(s): *__checkpoint ⇢ 400 BAD_REQUEST from POST https://api.openai.com/v1/chat/completions [DefaultWebClient] Original Stack Trace:
解决方案
1. 修复API密钥缺失问题
手动构建OpenAiChatOptions时,不会自动继承全局配置中的API密钥,需要显式传入:
- 注入
OpenAiChatProperties获取全局配置的API密钥 - 在构建
OpenAiChatOptions时添加withApiKey配置
2. 调整静态上下文参数传递
当前代码中ipAddress属于静态上下文参数,无需通过AI生成的请求传递,直接使用方法参数中的值即可(Java 8+支持lambda引用effectively final的方法参数)。
修改后的完整代码
@Service public class CustomerSupportAssistant { private final ChatClient chatClient; private final BookingService bookingService; private final OpenAiChatProperties openAiChatProperties; // 注入全局配置 @Autowired public CustomerSupportAssistant( ChatClient.Builder modelBuilder, VectorStore vectorStore, ChatMemory chatMemory, BookingService bookingService, OpenAiChatProperties openAiChatProperties // 添加注入 ) { this.bookingService = bookingService; this.openAiChatProperties = openAiChatProperties; this.chatClient = modelBuilder .defaultSystem(""" You are a customer chat support agent of an airline named "Funnair". Respond in a friendly, helpful, and joyful manner. You are interacting with customers through an online chat system. Use the provided functions to change bookings. Today is {current_date}. """) .defaultAdvisors( new PromptChatMemoryAdvisor(chatMemory), new QuestionAnswerAdvisor(vectorStore, SearchRequest.defaults())) .build(); } public Flux<String> chat( String chatId, String userMessageContent, Booking booking, String ipAddress ) { var dynamicChatOptions = OpenAiChatOptions.builder() .withApiKey(openAiChatProperties.getApiKey()) // 添加全局API密钥 .withModel("gpt-3.5-turbo") .withMaxTokens(100) .withFunctionCallbacks( List.of( FunctionCallbackWrapper.builder( (ChangeBookingDatesRequest request) -> { bookingService.changeBooking( booking.id(), request.from(), request.to(), ipAddress // 直接使用静态参数 ); return "Booking dates changed successfully"; }) .withName("changeBooking") .withDescription("Change booking dates with additional parameters") .build())) .build(); return this.chatClient.prompt() .withOptions(dynamicChatOptions) .system(s -> s.param("current_date", LocalDate.now().toString())) .user(userMessageContent) .advisors(a -> a .param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId) .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 100)) .stream().content(); } }
关于函数包装器的说明
函数包装器并未被弃用,Spring AI依然支持通过FunctionCallbackWrapper定义函数回调,动态配置时只需注意:
- 手动构建
OpenAiChatOptions需显式继承全局配置(如API密钥) - 静态上下文参数可通过lambda直接引用effectively final的方法参数
内容的提问来源于stack exchange,提问作者Amith reddy
相关产品推荐
相关产品推荐

