spring-projects / spring-ai

An Application Framework for AI Engineering
https://docs.spring.io/spring-ai/reference/index.html
Apache License 2.0
3.33k stars 855 forks source link

ChatClient mutates the default chat options instead of using a copy of it #1064

Closed timosalm closed 4 months ago

timosalm commented 4 months ago

Please do a quick search on GitHub issues first, there might be already a duplicate issue for the one you are about to create. If the bug is trivial, just go ahead and create the issue. Otherwise, please take a few moments and fill in the following sections:

Bug description It looks like there is something setting function bean names” to defaultChatClientRequest.chatOptions after setting them in an individual chat client request.

Therefore, function calling is also activated with this function for each chatClient request, even if it's not configured.

Environment Java 21, Spring AI 1.0.0-M1, Azure OpenAI Model

Steps to reproduce chatClientBuilder.build().prompt() .messages(promptMessage) .call() .entity(Recipe.class); -> No function call

chatClientBuilder.build().prompt() .messages(promptMessage) .functions("fetchIngredientsAvailableAtHome") .call() .entity(Recipe.class);

-> Function call

chatClientBuilder.build().prompt() .messages(promptMessage) .call() .entity(Recipe.class); -> Function call

Expected behavior The configuration for a function (or something else) for a request shouldn't have any effect on the configuration of a different request.

Minimal Complete Reproducible example A sample with a workaround is available here: https://github.com/timosalm/spring-ai Here are the instructions in the README to enable Azure OpenAI.

timosalm commented 4 months ago

My current workaround is to reset the functions in the defaultChatClientRequest.chatOptions before any chatClient request via Reflection.

var defaultClientRequest = (ChatClient.ChatClientRequest) FieldUtils.readField(chatClient, "defaultChatClientRequest", true);
var chatOptions = FieldUtils.readField(defaultClientRequest, "chatOptions", true);
if (chatOptions instanceof FunctionCallingOptions) {
    FieldUtils.writeField(chatOptions, "functions", new HashSet<String>(), true);
}