AzureOpenAI - Adopt ToolCallingManager API
- Use the new ToolCallingManager API for AzureOpenAI chat model - Add Builder to construct AzureOpenAI chat model instance - Deprecate existing constructors - Update documentation about the change Signed-off-by: Ilayaperumal Gopinathan <ilayaperumal.gopinathan@broadcom.com>
This commit is contained in:
@@ -19,11 +19,9 @@ package org.springframework.ai.azure.openai;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import com.azure.ai.openai.OpenAIAsyncClient;
|
||||
@@ -58,6 +56,8 @@ import com.azure.core.util.BinaryData;
|
||||
import io.micrometer.observation.Observation;
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.ai.chat.messages.AssistantMessage;
|
||||
@@ -88,7 +88,13 @@ import org.springframework.ai.model.ModelOptionsUtils;
|
||||
import org.springframework.ai.model.function.FunctionCallback;
|
||||
import org.springframework.ai.model.function.FunctionCallbackResolver;
|
||||
import org.springframework.ai.model.function.FunctionCallingOptions;
|
||||
import org.springframework.ai.model.tool.LegacyToolCallingManager;
|
||||
import org.springframework.ai.model.tool.ToolCallingChatOptions;
|
||||
import org.springframework.ai.model.tool.ToolCallingManager;
|
||||
import org.springframework.ai.model.tool.ToolExecutionResult;
|
||||
import org.springframework.ai.observation.conventions.AiProvider;
|
||||
import org.springframework.ai.tool.definition.ToolDefinition;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
@@ -115,12 +121,16 @@ import org.springframework.util.CollectionUtils;
|
||||
*/
|
||||
public class AzureOpenAiChatModel extends AbstractToolCallSupport implements ChatModel {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(AzureOpenAiChatModel.class);
|
||||
|
||||
private static final String DEFAULT_DEPLOYMENT_NAME = "gpt-4o";
|
||||
|
||||
private static final Double DEFAULT_TEMPERATURE = 0.7;
|
||||
|
||||
private static final ChatModelObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultChatModelObservationConvention();
|
||||
|
||||
private static final ToolCallingManager DEFAULT_TOOL_CALLING_MANAGER = ToolCallingManager.builder().build();
|
||||
|
||||
/**
|
||||
* The {@link OpenAIClient} used to interact with the Azure OpenAI service.
|
||||
*/
|
||||
@@ -146,6 +156,12 @@ public class AzureOpenAiChatModel extends AbstractToolCallSupport implements Cha
|
||||
*/
|
||||
private ChatModelObservationConvention observationConvention = DEFAULT_OBSERVATION_CONVENTION;
|
||||
|
||||
/**
|
||||
* ToolCalling manager used for ToolCalling support.
|
||||
*/
|
||||
private final ToolCallingManager toolCallingManager;
|
||||
|
||||
@Deprecated
|
||||
public AzureOpenAiChatModel(OpenAIClientBuilder openAIClientBuilder) {
|
||||
this(openAIClientBuilder,
|
||||
AzureOpenAiChatOptions.builder()
|
||||
@@ -154,29 +170,52 @@ public class AzureOpenAiChatModel extends AbstractToolCallSupport implements Cha
|
||||
.build());
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public AzureOpenAiChatModel(OpenAIClientBuilder openAIClientBuilder, AzureOpenAiChatOptions options) {
|
||||
this(openAIClientBuilder, options, null);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public AzureOpenAiChatModel(OpenAIClientBuilder openAIClientBuilder, AzureOpenAiChatOptions options,
|
||||
FunctionCallbackResolver functionCallbackResolver) {
|
||||
this(openAIClientBuilder, options, functionCallbackResolver, List.of());
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public AzureOpenAiChatModel(OpenAIClientBuilder openAIClientBuilder, AzureOpenAiChatOptions options,
|
||||
FunctionCallbackResolver functionCallbackResolver, List<FunctionCallback> toolFunctionCallbacks) {
|
||||
@Nullable FunctionCallbackResolver functionCallbackResolver,
|
||||
@Nullable List<FunctionCallback> toolFunctionCallbacks) {
|
||||
this(openAIClientBuilder, options, functionCallbackResolver, toolFunctionCallbacks, ObservationRegistry.NOOP);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public AzureOpenAiChatModel(OpenAIClientBuilder openAIClientBuilder, AzureOpenAiChatOptions options,
|
||||
FunctionCallbackResolver functionCallbackResolver, List<FunctionCallback> toolFunctionCallbacks,
|
||||
ObservationRegistry observationRegistry) {
|
||||
super(functionCallbackResolver, options, toolFunctionCallbacks);
|
||||
@Nullable FunctionCallbackResolver functionCallbackResolver,
|
||||
@Nullable List<FunctionCallback> toolFunctionCallbacks, ObservationRegistry observationRegistry) {
|
||||
this(openAIClientBuilder, options,
|
||||
LegacyToolCallingManager.builder()
|
||||
.functionCallbackResolver(functionCallbackResolver)
|
||||
.functionCallbacks(toolFunctionCallbacks)
|
||||
.build(),
|
||||
observationRegistry);
|
||||
logger.warn("This constructor is deprecated and will be removed in the next milestone. "
|
||||
+ "Please use the AzureOpenAiChatModel.Builder or the new constructor accepting ToolCallingManager instead.");
|
||||
}
|
||||
|
||||
public AzureOpenAiChatModel(OpenAIClientBuilder openAIClientBuilder, AzureOpenAiChatOptions defaultOptions,
|
||||
ToolCallingManager toolCallingManager, ObservationRegistry observationRegistry) {
|
||||
// We do not pass the 'defaultOptions' to the AbstractToolSupport,
|
||||
// because it modifies them. We are using ToolCallingManager instead,
|
||||
// so we just pass empty options here.
|
||||
super(null, AzureOpenAiChatOptions.builder().build(), List.of());
|
||||
Assert.notNull(openAIClientBuilder, "com.azure.ai.openai.OpenAIClient must not be null");
|
||||
Assert.notNull(options, "AzureOpenAiChatOptions must not be null");
|
||||
Assert.notNull(defaultOptions, "defaultOptions cannot be null");
|
||||
Assert.notNull(toolCallingManager, "toolCallingManager cannot be null");
|
||||
Assert.notNull(observationRegistry, "observationRegistry cannot be null");
|
||||
this.openAIClient = openAIClientBuilder.buildClient();
|
||||
this.openAIAsyncClient = openAIClientBuilder.buildAsyncClient();
|
||||
this.defaultOptions = options;
|
||||
this.defaultOptions = defaultOptions;
|
||||
this.toolCallingManager = toolCallingManager;
|
||||
this.observationRegistry = observationRegistry;
|
||||
}
|
||||
|
||||
@@ -228,7 +267,10 @@ public class AzureOpenAiChatModel extends AbstractToolCallSupport implements Cha
|
||||
|
||||
@Override
|
||||
public ChatResponse call(Prompt prompt) {
|
||||
return this.internalCall(prompt, null);
|
||||
// Before moving any further, build the final request Prompt,
|
||||
// merging runtime and default options.
|
||||
Prompt requestPrompt = buildRequestPrompt(prompt);
|
||||
return this.internalCall(requestPrompt, null);
|
||||
}
|
||||
|
||||
public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse) {
|
||||
@@ -252,12 +294,21 @@ public class AzureOpenAiChatModel extends AbstractToolCallSupport implements Cha
|
||||
return chatResponse;
|
||||
});
|
||||
|
||||
if (!isProxyToolCalls(prompt, this.defaultOptions)
|
||||
&& isToolCall(response, Set.of(String.valueOf(CompletionsFinishReason.TOOL_CALLS).toLowerCase()))) {
|
||||
var toolCallConversation = handleToolCalls(prompt, response);
|
||||
// Recursively call the call method with the tool call message
|
||||
// conversation that contains the call responses.
|
||||
return this.internalCall(new Prompt(toolCallConversation, prompt.getOptions()), response);
|
||||
if (ToolCallingChatOptions.isInternalToolExecutionEnabled(prompt.getOptions()) && response != null
|
||||
&& response.hasToolCalls()) {
|
||||
var toolExecutionResult = this.toolCallingManager.executeToolCalls(prompt, response);
|
||||
if (toolExecutionResult.returnDirect()) {
|
||||
// Return tool execution result directly to the client.
|
||||
return ChatResponse.builder()
|
||||
.from(response)
|
||||
.generations(ToolExecutionResult.buildGenerations(toolExecutionResult))
|
||||
.build();
|
||||
}
|
||||
else {
|
||||
// Send the tool execution result back to the model.
|
||||
return this.internalCall(new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()),
|
||||
response);
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
@@ -265,7 +316,10 @@ public class AzureOpenAiChatModel extends AbstractToolCallSupport implements Cha
|
||||
|
||||
@Override
|
||||
public Flux<ChatResponse> stream(Prompt prompt) {
|
||||
return this.internalStream(prompt, null);
|
||||
// Before moving any further, build the final request Prompt,
|
||||
// merging runtime and default options.
|
||||
Prompt requestPrompt = buildRequestPrompt(prompt);
|
||||
return this.internalStream(requestPrompt, null);
|
||||
}
|
||||
|
||||
public Flux<ChatResponse> internalStream(Prompt prompt, ChatResponse previousChatResponse) {
|
||||
@@ -344,12 +398,22 @@ public class AzureOpenAiChatModel extends AbstractToolCallSupport implements Cha
|
||||
});
|
||||
|
||||
return chatResponseFlux.flatMap(chatResponse -> {
|
||||
if (!isProxyToolCalls(prompt, this.defaultOptions) && isToolCall(chatResponse,
|
||||
Set.of(String.valueOf(CompletionsFinishReason.TOOL_CALLS).toLowerCase()))) {
|
||||
var toolCallConversation = handleToolCalls(prompt, chatResponse);
|
||||
// Recursively call the call method with the tool call message
|
||||
// conversation that contains the call responses.
|
||||
return this.internalStream(new Prompt(toolCallConversation, prompt.getOptions()), chatResponse);
|
||||
if (ToolCallingChatOptions.isInternalToolExecutionEnabled(prompt.getOptions())
|
||||
&& chatResponse.hasToolCalls()) {
|
||||
var toolExecutionResult = this.toolCallingManager.executeToolCalls(prompt, chatResponse);
|
||||
if (toolExecutionResult.returnDirect()) {
|
||||
// Return tool execution result directly to the client.
|
||||
return Flux.just(ChatResponse.builder()
|
||||
.from(chatResponse)
|
||||
.generations(ToolExecutionResult.buildGenerations(toolExecutionResult))
|
||||
.build());
|
||||
}
|
||||
else {
|
||||
// Send the tool execution result back to the model.
|
||||
return this.internalStream(
|
||||
new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()),
|
||||
chatResponse);
|
||||
}
|
||||
}
|
||||
|
||||
Flux<ChatResponse> flux = Flux.just(chatResponse)
|
||||
@@ -447,7 +511,7 @@ public class AzureOpenAiChatModel extends AbstractToolCallSupport implements Cha
|
||||
*/
|
||||
ChatCompletionsOptions toAzureChatCompletionsOptions(Prompt prompt) {
|
||||
|
||||
Set<String> functionsForThisRequest = new HashSet<>();
|
||||
List<ToolDefinition> functionsForThisRequest = new ArrayList<>();
|
||||
|
||||
List<ChatRequestMessage> azureMessages = prompt.getInstructions()
|
||||
.stream()
|
||||
@@ -459,27 +523,27 @@ public class AzureOpenAiChatModel extends AbstractToolCallSupport implements Cha
|
||||
|
||||
options = this.merge(options, this.defaultOptions);
|
||||
|
||||
if (!CollectionUtils.isEmpty(this.defaultOptions.getFunctions())) {
|
||||
functionsForThisRequest.addAll(this.defaultOptions.getFunctions());
|
||||
}
|
||||
|
||||
if (prompt.getOptions() != null) {
|
||||
AzureOpenAiChatOptions updatedRuntimeOptions;
|
||||
if (prompt.getOptions() instanceof FunctionCallingOptions functionCallingOptions) {
|
||||
updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(functionCallingOptions,
|
||||
FunctionCallingOptions.class, AzureOpenAiChatOptions.class);
|
||||
}
|
||||
if (prompt.getOptions() instanceof ToolCallingChatOptions toolCallingChatOptions) {
|
||||
updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(toolCallingChatOptions,
|
||||
ToolCallingChatOptions.class, AzureOpenAiChatOptions.class);
|
||||
}
|
||||
else {
|
||||
updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(prompt.getOptions(), ChatOptions.class,
|
||||
AzureOpenAiChatOptions.class);
|
||||
}
|
||||
options = this.merge(updatedRuntimeOptions, options);
|
||||
|
||||
functionsForThisRequest.addAll(this.runtimeFunctionCallbackConfigurations(updatedRuntimeOptions));
|
||||
// Add the tool definitions to the request's tools parameter.
|
||||
functionsForThisRequest.addAll(this.toolCallingManager.resolveToolDefinitions(updatedRuntimeOptions));
|
||||
}
|
||||
|
||||
// Add the enabled functions definitions to the request's tools parameter.
|
||||
|
||||
if (!CollectionUtils.isEmpty(functionsForThisRequest)) {
|
||||
List<ChatCompletionsFunctionToolDefinition> tools = this.getFunctionTools(functionsForThisRequest);
|
||||
List<ChatCompletionsToolDefinition> tools2 = tools.stream()
|
||||
@@ -491,14 +555,12 @@ public class AzureOpenAiChatModel extends AbstractToolCallSupport implements Cha
|
||||
return options;
|
||||
}
|
||||
|
||||
private List<ChatCompletionsFunctionToolDefinition> getFunctionTools(Set<String> functionNames) {
|
||||
return this.resolveFunctionCallbacks(functionNames).stream().map(functionCallback -> {
|
||||
|
||||
private List<ChatCompletionsFunctionToolDefinition> getFunctionTools(List<ToolDefinition> toolDefinitions) {
|
||||
return toolDefinitions.stream().map(toolDefinition -> {
|
||||
ChatCompletionsFunctionToolDefinitionFunction functionDefinition = new ChatCompletionsFunctionToolDefinitionFunction(
|
||||
functionCallback.getName());
|
||||
functionDefinition.setDescription(functionCallback.getDescription());
|
||||
BinaryData parameters = BinaryData
|
||||
.fromObject(ModelOptionsUtils.jsonToMap(functionCallback.getInputTypeSchema()));
|
||||
toolDefinition.name());
|
||||
functionDefinition.setDescription(toolDefinition.description());
|
||||
BinaryData parameters = BinaryData.fromObject(ModelOptionsUtils.jsonToMap(toolDefinition.inputSchema()));
|
||||
functionDefinition.setParameters(parameters);
|
||||
return new ChatCompletionsFunctionToolDefinition(functionDefinition);
|
||||
}).toList();
|
||||
@@ -589,6 +651,53 @@ public class AzureOpenAiChatModel extends AbstractToolCallSupport implements Cha
|
||||
return list != null ? list : Collections.emptyList();
|
||||
}
|
||||
|
||||
Prompt buildRequestPrompt(Prompt prompt) {
|
||||
// Process runtime options
|
||||
AzureOpenAiChatOptions runtimeOptions = null;
|
||||
if (prompt.getOptions() != null) {
|
||||
if (prompt.getOptions() instanceof ToolCallingChatOptions toolCallingChatOptions) {
|
||||
runtimeOptions = ModelOptionsUtils.copyToTarget(toolCallingChatOptions, ToolCallingChatOptions.class,
|
||||
AzureOpenAiChatOptions.class);
|
||||
}
|
||||
else if (prompt.getOptions() instanceof FunctionCallingOptions functionCallingOptions) {
|
||||
runtimeOptions = ModelOptionsUtils.copyToTarget(functionCallingOptions, FunctionCallingOptions.class,
|
||||
AzureOpenAiChatOptions.class);
|
||||
}
|
||||
else {
|
||||
runtimeOptions = ModelOptionsUtils.copyToTarget(prompt.getOptions(), ChatOptions.class,
|
||||
AzureOpenAiChatOptions.class);
|
||||
}
|
||||
}
|
||||
|
||||
// Define request options by merging runtime options and default options
|
||||
AzureOpenAiChatOptions requestOptions = ModelOptionsUtils.merge(runtimeOptions, this.defaultOptions,
|
||||
AzureOpenAiChatOptions.class);
|
||||
|
||||
// Merge @JsonIgnore-annotated options explicitly since they are ignored by
|
||||
// Jackson, used by ModelOptionsUtils.
|
||||
if (runtimeOptions != null) {
|
||||
requestOptions.setInternalToolExecutionEnabled(
|
||||
ModelOptionsUtils.mergeOption(runtimeOptions.isInternalToolExecutionEnabled(),
|
||||
this.defaultOptions.isInternalToolExecutionEnabled()));
|
||||
requestOptions.setToolNames(ToolCallingChatOptions.mergeToolNames(runtimeOptions.getToolNames(),
|
||||
this.defaultOptions.getToolNames()));
|
||||
requestOptions.setToolCallbacks(ToolCallingChatOptions.mergeToolCallbacks(runtimeOptions.getToolCallbacks(),
|
||||
this.defaultOptions.getToolCallbacks()));
|
||||
requestOptions.setToolContext(ToolCallingChatOptions.mergeToolContext(runtimeOptions.getToolContext(),
|
||||
this.defaultOptions.getToolContext()));
|
||||
}
|
||||
else {
|
||||
requestOptions.setInternalToolExecutionEnabled(this.defaultOptions.isInternalToolExecutionEnabled());
|
||||
requestOptions.setToolNames(this.defaultOptions.getToolNames());
|
||||
requestOptions.setToolCallbacks(this.defaultOptions.getToolCallbacks());
|
||||
requestOptions.setToolContext(this.defaultOptions.getToolContext());
|
||||
}
|
||||
|
||||
ToolCallingChatOptions.validateToolCallbacks(requestOptions.getToolCallbacks());
|
||||
|
||||
return new Prompt(prompt.getInstructions(), requestOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the Azure's {@link ChatCompletionsOptions} (fromAzureOptions) into the
|
||||
* Spring AI's {@link AzureOpenAiChatOptions} (toSpringAiOptions) and return a new
|
||||
@@ -842,4 +951,94 @@ public class AzureOpenAiChatModel extends AbstractToolCallSupport implements Cha
|
||||
this.observationConvention = observationConvention;
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder to construct {@link AzureOpenAiChatModel}.
|
||||
*/
|
||||
public static class Builder {
|
||||
|
||||
private OpenAIClientBuilder openAIClientBuilder;
|
||||
|
||||
private AzureOpenAiChatOptions defaultOptions = AzureOpenAiChatOptions.builder()
|
||||
.deploymentName(DEFAULT_DEPLOYMENT_NAME)
|
||||
.temperature(DEFAULT_TEMPERATURE)
|
||||
.build();
|
||||
|
||||
private ToolCallingManager toolCallingManager;
|
||||
|
||||
private FunctionCallbackResolver functionCallbackResolver;
|
||||
|
||||
private List<FunctionCallback> toolFunctionCallbacks;
|
||||
|
||||
private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
|
||||
public Builder openAIClientBuilder(OpenAIClientBuilder openAIClientBuilder) {
|
||||
this.openAIClientBuilder = openAIClientBuilder;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder defaultOptions(AzureOpenAiChatOptions defaultOptions) {
|
||||
this.defaultOptions = defaultOptions;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder toolCallingManager(ToolCallingManager toolCallingManager) {
|
||||
this.toolCallingManager = toolCallingManager;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public Builder functionCallbackResolver(FunctionCallbackResolver functionCallbackResolver) {
|
||||
this.functionCallbackResolver = functionCallbackResolver;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public Builder toolFunctionCallbacks(List<FunctionCallback> toolFunctionCallbacks) {
|
||||
this.toolFunctionCallbacks = toolFunctionCallbacks;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder observationRegistry(ObservationRegistry observationRegistry) {
|
||||
this.observationRegistry = observationRegistry;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AzureOpenAiChatModel build() {
|
||||
if (toolCallingManager != null) {
|
||||
Assert.isNull(functionCallbackResolver,
|
||||
"functionCallbackResolver cannot be set when toolCallingManager is set");
|
||||
Assert.isNull(toolFunctionCallbacks,
|
||||
"toolFunctionCallbacks cannot be set when toolCallingManager is set");
|
||||
|
||||
return new AzureOpenAiChatModel(openAIClientBuilder, defaultOptions, toolCallingManager,
|
||||
observationRegistry);
|
||||
}
|
||||
|
||||
if (functionCallbackResolver != null) {
|
||||
Assert.isNull(toolCallingManager,
|
||||
"toolCallingManager cannot be set when functionCallbackResolver is set");
|
||||
List<FunctionCallback> toolCallbacks = this.toolFunctionCallbacks != null ? this.toolFunctionCallbacks
|
||||
: List.of();
|
||||
|
||||
return new Builder().openAIClientBuilder(openAIClientBuilder)
|
||||
.defaultOptions(defaultOptions)
|
||||
.functionCallbackResolver(functionCallbackResolver)
|
||||
.toolFunctionCallbacks(toolCallbacks)
|
||||
.observationRegistry(observationRegistry)
|
||||
.build();
|
||||
}
|
||||
|
||||
return new AzureOpenAiChatModel(openAIClientBuilder, defaultOptions, DEFAULT_TOOL_CALLING_MANAGER,
|
||||
observationRegistry);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
package org.springframework.ai.azure.openai;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -30,7 +32,9 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.springframework.ai.model.function.FunctionCallback;
|
||||
import org.springframework.ai.model.function.FunctionCallingOptions;
|
||||
import org.springframework.ai.model.tool.ToolCallingChatOptions;
|
||||
import org.springframework.ai.tool.ToolCallback;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -44,7 +48,7 @@ import org.springframework.util.Assert;
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
public class AzureOpenAiChatOptions implements FunctionCallingOptions {
|
||||
public class AzureOpenAiChatOptions implements ToolCallingChatOptions {
|
||||
|
||||
/**
|
||||
* The maximum number of tokens to generate.
|
||||
@@ -138,33 +142,6 @@ public class AzureOpenAiChatOptions implements FunctionCallingOptions {
|
||||
@JsonProperty("response_format")
|
||||
private AzureOpenAiResponseFormat responseFormat;
|
||||
|
||||
/**
|
||||
* OpenAI Tool Function Callbacks to register with the ChatModel. For Prompt Options
|
||||
* the functionCallbacks are automatically enabled for the duration of the prompt
|
||||
* execution. For Default Options the functionCallbacks are registered but disabled by
|
||||
* default. Use the enableFunctions to set the functions from the registry to be used
|
||||
* by the ChatModel chat completion requests.
|
||||
*/
|
||||
@JsonIgnore
|
||||
private List<FunctionCallback> functionCallbacks = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* List of functions, identified by their names, to configure for function calling in
|
||||
* the chat completion requests. Functions with those names must exist in the
|
||||
* functionCallbacks registry. The {@link #functionCallbacks} from the PromptOptions
|
||||
* are automatically enabled for the duration of the prompt execution.
|
||||
*
|
||||
* Note that function enabled with the default options are enabled for all chat
|
||||
* completion requests. This could impact the token count and the billing. If the
|
||||
* functions is set in a prompt options, then the enabled functions are only active
|
||||
* for the duration of this prompt execution.
|
||||
*/
|
||||
@JsonIgnore
|
||||
private Set<String> functions = new HashSet<>();
|
||||
|
||||
@JsonIgnore
|
||||
private Boolean proxyToolCalls;
|
||||
|
||||
/**
|
||||
* Seed value for deterministic sampling such that the same seed and parameters return
|
||||
* the same result.
|
||||
@@ -199,7 +176,68 @@ public class AzureOpenAiChatOptions implements FunctionCallingOptions {
|
||||
private ChatCompletionStreamOptions streamOptions;
|
||||
|
||||
@JsonIgnore
|
||||
private Map<String, Object> toolContext;
|
||||
private Map<String, Object> toolContext = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Collection of {@link ToolCallback}s to be used for tool calling in the chat
|
||||
* completion requests.
|
||||
*/
|
||||
@JsonIgnore
|
||||
private List<FunctionCallback> toolCallbacks = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Collection of tool names to be resolved at runtime and used for tool calling in the
|
||||
* chat completion requests.
|
||||
*/
|
||||
@JsonIgnore
|
||||
private Set<String> toolNames = new HashSet<>();
|
||||
|
||||
/**
|
||||
* Whether to enable the tool execution lifecycle internally in ChatModel.
|
||||
*/
|
||||
@JsonIgnore
|
||||
private Boolean internalToolExecutionEnabled;
|
||||
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public List<FunctionCallback> getToolCallbacks() {
|
||||
return this.toolCallbacks;
|
||||
}
|
||||
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public void setToolCallbacks(List<FunctionCallback> toolCallbacks) {
|
||||
Assert.notNull(toolCallbacks, "toolCallbacks cannot be null");
|
||||
Assert.noNullElements(toolCallbacks, "toolCallbacks cannot contain null elements");
|
||||
this.toolCallbacks = toolCallbacks;
|
||||
}
|
||||
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public Set<String> getToolNames() {
|
||||
return this.toolNames;
|
||||
}
|
||||
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public void setToolNames(Set<String> toolNames) {
|
||||
Assert.notNull(toolNames, "toolNames cannot be null");
|
||||
Assert.noNullElements(toolNames, "toolNames cannot contain null elements");
|
||||
this.toolNames = toolNames;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
@JsonIgnore
|
||||
public Boolean isInternalToolExecutionEnabled() {
|
||||
return internalToolExecutionEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecutionEnabled) {
|
||||
this.internalToolExecutionEnabled = internalToolExecutionEnabled;
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
@@ -224,7 +262,10 @@ public class AzureOpenAiChatOptions implements FunctionCallingOptions {
|
||||
.topLogprobs(fromOptions.getTopLogProbs())
|
||||
.enhancements(fromOptions.getEnhancements())
|
||||
.toolContext(fromOptions.getToolContext())
|
||||
.internalToolExecutionEnabled(fromOptions.isInternalToolExecutionEnabled())
|
||||
.streamOptions(fromOptions.getStreamOptions())
|
||||
.toolCallbacks(fromOptions.getToolCallbacks())
|
||||
.toolNames(fromOptions.getToolNames())
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -336,21 +377,28 @@ public class AzureOpenAiChatOptions implements FunctionCallingOptions {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
@JsonIgnore
|
||||
public List<FunctionCallback> getFunctionCallbacks() {
|
||||
return this.functionCallbacks;
|
||||
}
|
||||
|
||||
public void setFunctionCallbacks(List<FunctionCallback> functionCallbacks) {
|
||||
this.functionCallbacks = functionCallbacks;
|
||||
return this.getToolCallbacks();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
@JsonIgnore
|
||||
public void setFunctionCallbacks(List<FunctionCallback> functionCallbacks) {
|
||||
this.setToolCallbacks(functionCallbacks);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
@JsonIgnore
|
||||
public Set<String> getFunctions() {
|
||||
return this.functions;
|
||||
return this.getToolNames();
|
||||
}
|
||||
|
||||
public void setFunctions(Set<String> functions) {
|
||||
this.functions = functions;
|
||||
this.setToolNames(functions);
|
||||
}
|
||||
|
||||
public AzureOpenAiResponseFormat getResponseFormat() {
|
||||
@@ -400,12 +448,16 @@ public class AzureOpenAiChatOptions implements FunctionCallingOptions {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
@JsonIgnore
|
||||
public Boolean getProxyToolCalls() {
|
||||
return this.proxyToolCalls;
|
||||
return this.internalToolExecutionEnabled != null ? !this.internalToolExecutionEnabled : null;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@JsonIgnore
|
||||
public void setProxyToolCalls(Boolean proxyToolCalls) {
|
||||
this.proxyToolCalls = proxyToolCalls;
|
||||
this.internalToolExecutionEnabled = proxyToolCalls != null ? !proxyToolCalls : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -493,21 +545,19 @@ public class AzureOpenAiChatOptions implements FunctionCallingOptions {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public Builder functionCallbacks(List<FunctionCallback> functionCallbacks) {
|
||||
this.options.functionCallbacks = functionCallbacks;
|
||||
return this;
|
||||
return toolCallbacks(functionCallbacks);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public Builder functions(Set<String> functionNames) {
|
||||
Assert.notNull(functionNames, "Function names must not be null");
|
||||
this.options.functions = functionNames;
|
||||
return this;
|
||||
return toolNames(functionNames);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public Builder function(String functionName) {
|
||||
Assert.hasText(functionName, "Function name must not be empty");
|
||||
this.options.functions.add(functionName);
|
||||
return this;
|
||||
return toolNames(functionName);
|
||||
}
|
||||
|
||||
public Builder responseFormat(AzureOpenAiResponseFormat responseFormat) {
|
||||
@@ -515,8 +565,11 @@ public class AzureOpenAiChatOptions implements FunctionCallingOptions {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public Builder proxyToolCalls(Boolean proxyToolCalls) {
|
||||
this.options.proxyToolCalls = proxyToolCalls;
|
||||
if (proxyToolCalls != null) {
|
||||
this.options.setInternalToolExecutionEnabled(!proxyToolCalls);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -555,6 +608,34 @@ public class AzureOpenAiChatOptions implements FunctionCallingOptions {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder toolCallbacks(List<FunctionCallback> toolCallbacks) {
|
||||
this.options.setToolCallbacks(toolCallbacks);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder toolCallbacks(FunctionCallback... toolCallbacks) {
|
||||
Assert.notNull(toolCallbacks, "toolCallbacks cannot be null");
|
||||
this.options.toolCallbacks.addAll(Arrays.asList(toolCallbacks));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder toolNames(Set<String> toolNames) {
|
||||
Assert.notNull(toolNames, "toolNames cannot be null");
|
||||
this.options.setToolNames(toolNames);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder toolNames(String... toolNames) {
|
||||
Assert.notNull(toolNames, "toolNames cannot be null");
|
||||
this.options.toolNames.addAll(Set.of(toolNames));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder internalToolExecutionEnabled(@Nullable Boolean internalToolExecutionEnabled) {
|
||||
this.options.setInternalToolExecutionEnabled(internalToolExecutionEnabled);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AzureOpenAiChatOptions build() {
|
||||
return this.options;
|
||||
}
|
||||
|
||||
@@ -71,7 +71,10 @@ public class AzureChatCompletionsOptionsTests {
|
||||
.responseFormat(AzureOpenAiResponseFormat.TEXT)
|
||||
.build();
|
||||
|
||||
var client = new AzureOpenAiChatModel(mockClient, defaultOptions);
|
||||
var client = AzureOpenAiChatModel.builder()
|
||||
.openAIClientBuilder(mockClient)
|
||||
.defaultOptions(defaultOptions)
|
||||
.build();
|
||||
|
||||
var requestOptions = client.toAzureChatCompletionsOptions(new Prompt("Test message content"));
|
||||
|
||||
|
||||
@@ -161,9 +161,10 @@ public class AzureOpenAiChatClientIT {
|
||||
|
||||
@Bean
|
||||
public AzureOpenAiChatModel azureOpenAiChatModel(OpenAIClientBuilder openAIClientBuilder) {
|
||||
return new AzureOpenAiChatModel(openAIClientBuilder,
|
||||
AzureOpenAiChatOptions.builder().deploymentName("gpt-4o").maxTokens(1000).build());
|
||||
|
||||
return AzureOpenAiChatModel.builder()
|
||||
.openAIClientBuilder(openAIClientBuilder)
|
||||
.defaultOptions(AzureOpenAiChatOptions.builder().deploymentName("gpt-4o").maxTokens(1000).build())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -269,9 +269,10 @@ class AzureOpenAiChatModelIT {
|
||||
|
||||
@Bean
|
||||
public AzureOpenAiChatModel azureOpenAiChatModel(OpenAIClientBuilder openAIClientBuilder) {
|
||||
return new AzureOpenAiChatModel(openAIClientBuilder,
|
||||
AzureOpenAiChatOptions.builder().deploymentName("gpt-4o").maxTokens(1000).build());
|
||||
|
||||
return AzureOpenAiChatModel.builder()
|
||||
.openAIClientBuilder(openAIClientBuilder)
|
||||
.defaultOptions(AzureOpenAiChatOptions.builder().deploymentName("gpt-4o").maxTokens(1000).build())
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -194,9 +194,11 @@ class AzureOpenAiChatModelObservationIT {
|
||||
@Bean
|
||||
public AzureOpenAiChatModel azureOpenAiChatModel(OpenAIClientBuilder openAIClientBuilder,
|
||||
TestObservationRegistry observationRegistry) {
|
||||
return new AzureOpenAiChatModel(openAIClientBuilder,
|
||||
AzureOpenAiChatOptions.builder().deploymentName("gpt-4o").maxTokens(1000).build(), null, List.of(),
|
||||
observationRegistry);
|
||||
return AzureOpenAiChatModel.builder()
|
||||
.openAIClientBuilder(openAIClientBuilder)
|
||||
.defaultOptions(AzureOpenAiChatOptions.builder().deploymentName("gpt-4o").maxTokens(1000).build())
|
||||
.observationRegistry(observationRegistry)
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.azure.openai;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.azure.ai.openai.OpenAIClientBuilder;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.ai.model.function.FunctionCallback;
|
||||
import org.springframework.ai.model.function.FunctionCallbackResolver;
|
||||
|
||||
/**
|
||||
* @author Jihoon Kim
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class AzureOpenAiChatModelTests {
|
||||
|
||||
@Mock
|
||||
OpenAIClientBuilder mockClient;
|
||||
|
||||
@Mock
|
||||
FunctionCallbackResolver functionCallbackResolver;
|
||||
|
||||
@Test
|
||||
public void createAzureOpenAiChatModelTest() {
|
||||
String callbackFromChatOptions = "callbackFromChatOptions";
|
||||
String callbackFromConstructorParam = "callbackFromConstructorParam";
|
||||
|
||||
AzureOpenAiChatOptions chatOptions = AzureOpenAiChatOptions.builder()
|
||||
.functionCallbacks(List.of(new TestFunctionCallback(callbackFromChatOptions)))
|
||||
.build();
|
||||
|
||||
List<FunctionCallback> functionCallbacks = List.of(new TestFunctionCallback(callbackFromConstructorParam));
|
||||
|
||||
AzureOpenAiChatModel openAiChatModel = new AzureOpenAiChatModel(this.mockClient, chatOptions,
|
||||
this.functionCallbackResolver, functionCallbacks);
|
||||
|
||||
assert 2 == openAiChatModel.getFunctionCallbackRegister().size();
|
||||
|
||||
assert callbackFromChatOptions == openAiChatModel.getFunctionCallbackRegister()
|
||||
.get(callbackFromChatOptions)
|
||||
.getName();
|
||||
|
||||
assert callbackFromConstructorParam == openAiChatModel.getFunctionCallbackRegister()
|
||||
.get(callbackFromConstructorParam)
|
||||
.getName();
|
||||
}
|
||||
|
||||
private class TestFunctionCallback implements FunctionCallback {
|
||||
|
||||
private final String name;
|
||||
|
||||
TestFunctionCallback(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getInputTypeSchema() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String call(String functionInput) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -59,7 +59,7 @@ public class MockAzureOpenAiTestConfiguration {
|
||||
|
||||
@Bean
|
||||
AzureOpenAiChatModel azureOpenAiChatModel(OpenAIClientBuilder microsoftAzureOpenAiClient) {
|
||||
return new AzureOpenAiChatModel(microsoftAzureOpenAiClient);
|
||||
return AzureOpenAiChatModel.builder().openAIClientBuilder(microsoftAzureOpenAiClient).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -228,8 +228,10 @@ class AzureOpenAiChatModelFunctionCallIT {
|
||||
|
||||
@Bean
|
||||
public AzureOpenAiChatModel azureOpenAiChatModel(OpenAIClientBuilder openAIClient, String selectedModel) {
|
||||
return new AzureOpenAiChatModel(openAIClient,
|
||||
AzureOpenAiChatOptions.builder().deploymentName(selectedModel).maxTokens(500).build());
|
||||
return AzureOpenAiChatModel.builder()
|
||||
.openAIClientBuilder(openAIClient)
|
||||
.defaultOptions(AzureOpenAiChatOptions.builder().deploymentName(selectedModel).maxTokens(500).build())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
= Azure OpenAI Function Calling
|
||||
= Azure OpenAI Function Calling (Deprecated)
|
||||
|
||||
WARNING: This page describes the previous version of the Function Calling API, which has been deprecated and marked for remove in the next release. The current version is available at xref:api/tools.adoc[Tool Calling]. See the xref:api/tools-migration.adoc[Migration Guide] for more information.
|
||||
|
||||
Function calling lets developers create a description of a function in their code, then pass that description to a language model in a request. The response from the model includes the name of a function that matches the description and the arguments to call it with.
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import com.azure.core.util.ClientOptions;
|
||||
import com.azure.core.util.Header;
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
|
||||
import org.springframework.ai.autoconfigure.chat.model.ToolCallingAutoConfiguration;
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiAudioTranscriptionModel;
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiEmbeddingModel;
|
||||
@@ -35,10 +36,11 @@ import org.springframework.ai.azure.openai.AzureOpenAiImageModel;
|
||||
import org.springframework.ai.chat.observation.ChatModelObservationConvention;
|
||||
import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention;
|
||||
import org.springframework.ai.model.function.DefaultFunctionCallbackResolver;
|
||||
import org.springframework.ai.model.function.FunctionCallback;
|
||||
import org.springframework.ai.model.function.FunctionCallbackResolver;
|
||||
import org.springframework.ai.model.tool.ToolCallingManager;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
@@ -55,12 +57,14 @@ import org.springframework.util.StringUtils;
|
||||
* @author Piotr Olaszewski
|
||||
* @author Soby Chacko
|
||||
* @author Manuel Andreo Garcia
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@AutoConfiguration(after = { ToolCallingAutoConfiguration.class })
|
||||
@ConditionalOnClass({ OpenAIClientBuilder.class, AzureOpenAiChatModel.class })
|
||||
@EnableConfigurationProperties({ AzureOpenAiChatProperties.class, AzureOpenAiEmbeddingProperties.class,
|
||||
AzureOpenAiConnectionProperties.class, AzureOpenAiImageOptionsProperties.class,
|
||||
AzureOpenAiAudioTranscriptionProperties.class })
|
||||
@ImportAutoConfiguration(classes = { ToolCallingAutoConfiguration.class })
|
||||
public class AzureOpenAiAutoConfiguration {
|
||||
|
||||
private static final String APPLICATION_ID = "spring-ai";
|
||||
@@ -121,13 +125,16 @@ public class AzureOpenAiAutoConfiguration {
|
||||
@ConditionalOnProperty(prefix = AzureOpenAiChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
|
||||
matchIfMissing = true)
|
||||
public AzureOpenAiChatModel azureOpenAiChatModel(OpenAIClientBuilder openAIClientBuilder,
|
||||
AzureOpenAiChatProperties chatProperties, List<FunctionCallback> toolFunctionCallbacks,
|
||||
FunctionCallbackResolver functionCallbackResolver, ObjectProvider<ObservationRegistry> observationRegistry,
|
||||
AzureOpenAiChatProperties chatProperties, ToolCallingManager toolCallingManager,
|
||||
ObjectProvider<ObservationRegistry> observationRegistry,
|
||||
ObjectProvider<ChatModelObservationConvention> observationConvention) {
|
||||
|
||||
var chatModel = new AzureOpenAiChatModel(openAIClientBuilder, chatProperties.getOptions(),
|
||||
functionCallbackResolver, toolFunctionCallbacks,
|
||||
observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP));
|
||||
var chatModel = AzureOpenAiChatModel.builder()
|
||||
.openAIClientBuilder(openAIClientBuilder)
|
||||
.defaultOptions(chatProperties.getOptions())
|
||||
.toolCallingManager(toolCallingManager)
|
||||
.observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP))
|
||||
.build();
|
||||
observationConvention.ifAvailable(chatModel::setObservationConvention);
|
||||
|
||||
return chatModel;
|
||||
|
||||
Reference in New Issue
Block a user