Advancing Tool Support - Part 4
* Adopted new tool calling logic in OllamaChatModel, while maintaining full API backward compatibility thanks to the LegacyToolCallingManager. * Improved efficiency and robustness of merging options in prompts for Ollama. * Update Ollama Autoconfiguration to use the new ToolCallingManager. * Improved troubleshooting for new tool calling APIs and finalised changes for full backward compatibility. * Updated Ollama Testcontainers dependency to 0.5.7. Relates to gh-2049 Signed-off-by: Thomas Vitale <ThomasVitale@users.noreply.github.com>
This commit is contained in:
committed by
Christian Tzolov
parent
76ab91fab8
commit
b902ca2afb
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2023-2024 the original author or authors.
|
||||
* Copyright 2023-2025 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.
|
||||
@@ -18,15 +18,21 @@ package org.springframework.ai.ollama;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Base64;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
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 org.springframework.ai.model.tool.LegacyToolCallingManager;
|
||||
import org.springframework.ai.model.tool.ToolCallingChatOptions;
|
||||
import org.springframework.ai.model.tool.ToolCallingManager;
|
||||
import org.springframework.ai.tool.definition.ToolDefinition;
|
||||
import org.springframework.ai.util.json.JsonParser;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.ai.chat.messages.AssistantMessage;
|
||||
@@ -82,6 +88,8 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public class OllamaChatModel extends AbstractToolCallSupport implements ChatModel {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(OllamaChatModel.class);
|
||||
|
||||
private static final String DONE = "done";
|
||||
|
||||
private static final String METADATA_PROMPT_EVAL_COUNT = "prompt-eval-count";
|
||||
@@ -100,6 +108,8 @@ public class OllamaChatModel extends AbstractToolCallSupport implements ChatMode
|
||||
|
||||
private static final ChatModelObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultChatModelObservationConvention();
|
||||
|
||||
private static final ToolCallingManager DEFAULT_TOOL_CALLING_MANAGER = ToolCallingManager.builder().build();
|
||||
|
||||
private final OllamaApi chatApi;
|
||||
|
||||
private final OllamaOptions defaultOptions;
|
||||
@@ -108,8 +118,11 @@ public class OllamaChatModel extends AbstractToolCallSupport implements ChatMode
|
||||
|
||||
private final OllamaModelManager modelManager;
|
||||
|
||||
private final ToolCallingManager toolCallingManager;
|
||||
|
||||
private ChatModelObservationConvention observationConvention = DEFAULT_OBSERVATION_CONVENTION;
|
||||
|
||||
@Deprecated
|
||||
public OllamaChatModel(OllamaApi ollamaApi, OllamaOptions defaultOptions,
|
||||
FunctionCallbackResolver functionCallbackResolver, List<FunctionCallback> toolFunctionCallbacks,
|
||||
ObservationRegistry observationRegistry, ModelManagementOptions modelManagementOptions) {
|
||||
@@ -120,6 +133,26 @@ public class OllamaChatModel extends AbstractToolCallSupport implements ChatMode
|
||||
Assert.notNull(modelManagementOptions, "modelManagementOptions must not be null");
|
||||
this.chatApi = ollamaApi;
|
||||
this.defaultOptions = defaultOptions;
|
||||
this.toolCallingManager = new LegacyToolCallingManager(functionCallbackResolver, toolFunctionCallbacks);
|
||||
this.observationRegistry = observationRegistry;
|
||||
this.modelManager = new OllamaModelManager(this.chatApi, modelManagementOptions);
|
||||
initializeModel(defaultOptions.getModel(), modelManagementOptions.pullModelStrategy());
|
||||
|
||||
logger.warn("This constructor is deprecated and will be removed in the next milestone. "
|
||||
+ "Please use the new constructor accepting ToolCallingManager instead.");
|
||||
}
|
||||
|
||||
public OllamaChatModel(OllamaApi ollamaApi, OllamaOptions defaultOptions, ToolCallingManager toolCallingManager,
|
||||
ObservationRegistry observationRegistry, ModelManagementOptions modelManagementOptions) {
|
||||
super(null, defaultOptions, List.of());
|
||||
Assert.notNull(ollamaApi, "ollamaApi must not be null");
|
||||
Assert.notNull(defaultOptions, "defaultOptions must not be null");
|
||||
Assert.notNull(toolCallingManager, "toolCallingManager must not be null");
|
||||
Assert.notNull(observationRegistry, "observationRegistry must not be null");
|
||||
Assert.notNull(modelManagementOptions, "modelManagementOptions must not be null");
|
||||
this.chatApi = ollamaApi;
|
||||
this.defaultOptions = defaultOptions;
|
||||
this.toolCallingManager = toolCallingManager;
|
||||
this.observationRegistry = observationRegistry;
|
||||
this.modelManager = new OllamaModelManager(this.chatApi, modelManagementOptions);
|
||||
initializeModel(defaultOptions.getModel(), modelManagementOptions.pullModelStrategy());
|
||||
@@ -186,7 +219,10 @@ public class OllamaChatModel extends AbstractToolCallSupport implements ChatMode
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
private ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse) {
|
||||
@@ -196,7 +232,7 @@ public class OllamaChatModel extends AbstractToolCallSupport implements ChatMode
|
||||
ChatModelObservationContext observationContext = ChatModelObservationContext.builder()
|
||||
.prompt(prompt)
|
||||
.provider(OllamaApi.PROVIDER_NAME)
|
||||
.requestOptions(buildRequestOptions(request))
|
||||
.requestOptions(prompt.getOptions())
|
||||
.build();
|
||||
|
||||
ChatResponse response = ChatModelObservationDocumentation.CHAT_MODEL_OPERATION
|
||||
@@ -233,9 +269,9 @@ public class OllamaChatModel extends AbstractToolCallSupport implements ChatMode
|
||||
|
||||
});
|
||||
|
||||
if (!isProxyToolCalls(prompt, this.defaultOptions) && response != null
|
||||
&& isToolCall(response, Set.of("stop"))) {
|
||||
var toolCallConversation = handleToolCalls(prompt, response);
|
||||
if (ToolCallingChatOptions.isInternalToolExecutionEnabled(prompt.getOptions()) && response != null
|
||||
&& response.hasToolCalls()) {
|
||||
var toolCallConversation = this.toolCallingManager.executeToolCalls(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);
|
||||
@@ -246,7 +282,10 @@ public class OllamaChatModel extends AbstractToolCallSupport implements ChatMode
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
private Flux<ChatResponse> internalStream(Prompt prompt, ChatResponse previousChatResponse) {
|
||||
@@ -256,7 +295,7 @@ public class OllamaChatModel extends AbstractToolCallSupport implements ChatMode
|
||||
final ChatModelObservationContext observationContext = ChatModelObservationContext.builder()
|
||||
.prompt(prompt)
|
||||
.provider(OllamaApi.PROVIDER_NAME)
|
||||
.requestOptions(buildRequestOptions(request))
|
||||
.requestOptions(prompt.getOptions())
|
||||
.build();
|
||||
|
||||
Observation observation = ChatModelObservationDocumentation.CHAT_MODEL_OPERATION.observation(
|
||||
@@ -295,8 +334,8 @@ public class OllamaChatModel extends AbstractToolCallSupport implements ChatMode
|
||||
|
||||
// @formatter:off
|
||||
Flux<ChatResponse> chatResponseFlux = chatResponse.flatMap(response -> {
|
||||
if (isToolCall(response, Set.of("stop"))) {
|
||||
var toolCallConversation = handleToolCalls(prompt, response);
|
||||
if (ToolCallingChatOptions.isInternalToolExecutionEnabled(prompt.getOptions()) && response.hasToolCalls()) {
|
||||
var toolCallConversation = this.toolCallingManager.executeToolCalls(prompt, response);
|
||||
// Recursively call the stream method with the tool call message
|
||||
// conversation that contains the call responses.
|
||||
return this.internalStream(new Prompt(toolCallConversation, prompt.getOptions()), response);
|
||||
@@ -316,6 +355,48 @@ public class OllamaChatModel extends AbstractToolCallSupport implements ChatMode
|
||||
});
|
||||
}
|
||||
|
||||
Prompt buildRequestPrompt(Prompt prompt) {
|
||||
// Process runtime options
|
||||
OllamaOptions runtimeOptions = null;
|
||||
if (prompt.getOptions() != null) {
|
||||
if (prompt.getOptions() instanceof ToolCallingChatOptions toolCallingChatOptions) {
|
||||
runtimeOptions = ModelOptionsUtils.copyToTarget(toolCallingChatOptions, ToolCallingChatOptions.class,
|
||||
OllamaOptions.class);
|
||||
}
|
||||
else if (prompt.getOptions() instanceof FunctionCallingOptions functionCallingOptions) {
|
||||
runtimeOptions = ModelOptionsUtils.copyToTarget(functionCallingOptions, FunctionCallingOptions.class,
|
||||
OllamaOptions.class);
|
||||
}
|
||||
else {
|
||||
runtimeOptions = ModelOptionsUtils.copyToTarget(prompt.getOptions(), ChatOptions.class,
|
||||
OllamaOptions.class);
|
||||
}
|
||||
}
|
||||
|
||||
// Define request options by merging runtime options and default options
|
||||
OllamaOptions requestOptions = ModelOptionsUtils.merge(runtimeOptions, this.defaultOptions,
|
||||
OllamaOptions.class);
|
||||
// Merge tool names and tool callbacks explicitly since they are ignored by
|
||||
// Jackson, used by ModelOptionsUtils.
|
||||
if (runtimeOptions != null) {
|
||||
requestOptions.setTools(
|
||||
ToolCallingChatOptions.mergeToolNames(runtimeOptions.getTools(), this.defaultOptions.getTools()));
|
||||
requestOptions.setToolCallbacks(ToolCallingChatOptions.mergeToolCallbacks(runtimeOptions.getToolCallbacks(),
|
||||
this.defaultOptions.getToolCallbacks()));
|
||||
}
|
||||
else {
|
||||
requestOptions.setTools(this.defaultOptions.getTools());
|
||||
requestOptions.setToolCallbacks(this.defaultOptions.getToolCallbacks());
|
||||
}
|
||||
|
||||
// Validate request options
|
||||
if (!StringUtils.hasText(requestOptions.getModel())) {
|
||||
throw new IllegalArgumentException("model cannot be null or empty");
|
||||
}
|
||||
|
||||
return new Prompt(prompt.getInstructions(), requestOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Package access for testing.
|
||||
*/
|
||||
@@ -338,7 +419,8 @@ public class OllamaChatModel extends AbstractToolCallSupport implements ChatMode
|
||||
if (!CollectionUtils.isEmpty(assistantMessage.getToolCalls())) {
|
||||
toolCalls = assistantMessage.getToolCalls().stream().map(toolCall -> {
|
||||
var function = new ToolCallFunction(toolCall.name(),
|
||||
ModelOptionsUtils.jsonToMap(toolCall.arguments()));
|
||||
JsonParser.fromJson(toolCall.arguments(), new TypeReference<>() {
|
||||
}));
|
||||
return new ToolCall(function);
|
||||
}).toList();
|
||||
}
|
||||
@@ -356,49 +438,24 @@ public class OllamaChatModel extends AbstractToolCallSupport implements ChatMode
|
||||
throw new IllegalArgumentException("Unsupported message type: " + message.getMessageType());
|
||||
}).flatMap(List::stream).toList();
|
||||
|
||||
Set<String> functionsForThisRequest = new HashSet<>();
|
||||
OllamaOptions requestOptions = (OllamaOptions) prompt.getOptions();
|
||||
|
||||
// runtime options
|
||||
OllamaOptions runtimeOptions = null;
|
||||
if (prompt.getOptions() != null) {
|
||||
if (prompt.getOptions() instanceof FunctionCallingOptions functionCallingOptions) {
|
||||
runtimeOptions = ModelOptionsUtils.copyToTarget(functionCallingOptions, FunctionCallingOptions.class,
|
||||
OllamaOptions.class);
|
||||
}
|
||||
else {
|
||||
runtimeOptions = ModelOptionsUtils.copyToTarget(prompt.getOptions(), ChatOptions.class,
|
||||
OllamaOptions.class);
|
||||
}
|
||||
functionsForThisRequest.addAll(this.runtimeFunctionCallbackConfigurations(runtimeOptions));
|
||||
}
|
||||
|
||||
if (!CollectionUtils.isEmpty(this.defaultOptions.getFunctions())) {
|
||||
functionsForThisRequest.addAll(this.defaultOptions.getFunctions());
|
||||
}
|
||||
OllamaOptions mergedOptions = ModelOptionsUtils.merge(runtimeOptions, this.defaultOptions, OllamaOptions.class);
|
||||
|
||||
// Override the model.
|
||||
if (!StringUtils.hasText(mergedOptions.getModel())) {
|
||||
throw new IllegalArgumentException("Model is not set!");
|
||||
}
|
||||
|
||||
String model = mergedOptions.getModel();
|
||||
OllamaApi.ChatRequest.Builder requestBuilder = OllamaApi.ChatRequest.builder(model)
|
||||
OllamaApi.ChatRequest.Builder requestBuilder = OllamaApi.ChatRequest.builder(requestOptions.getModel())
|
||||
.stream(stream)
|
||||
.messages(ollamaMessages)
|
||||
.options(mergedOptions);
|
||||
.options(requestOptions);
|
||||
|
||||
if (mergedOptions.getFormat() != null) {
|
||||
requestBuilder.format(mergedOptions.getFormat());
|
||||
if (requestOptions.getFormat() != null) {
|
||||
requestBuilder.format(requestOptions.getFormat());
|
||||
}
|
||||
|
||||
if (mergedOptions.getKeepAlive() != null) {
|
||||
requestBuilder.keepAlive(mergedOptions.getKeepAlive());
|
||||
if (requestOptions.getKeepAlive() != null) {
|
||||
requestBuilder.keepAlive(requestOptions.getKeepAlive());
|
||||
}
|
||||
|
||||
// Add the enabled functions definitions to the request's tools parameter.
|
||||
if (!CollectionUtils.isEmpty(functionsForThisRequest)) {
|
||||
requestBuilder.tools(this.getFunctionTools(functionsForThisRequest));
|
||||
List<ToolDefinition> toolDefinitions = this.toolCallingManager.resolveToolDefinitions(requestOptions);
|
||||
if (!CollectionUtils.isEmpty(toolDefinitions)) {
|
||||
requestBuilder.tools(this.getTools(toolDefinitions));
|
||||
}
|
||||
|
||||
return requestBuilder.build();
|
||||
@@ -417,28 +474,14 @@ public class OllamaChatModel extends AbstractToolCallSupport implements ChatMode
|
||||
|
||||
}
|
||||
|
||||
private List<ChatRequest.Tool> getFunctionTools(Set<String> functionNames) {
|
||||
return this.resolveFunctionCallbacks(functionNames).stream().map(functionCallback -> {
|
||||
var function = new ChatRequest.Tool.Function(functionCallback.getName(), functionCallback.getDescription(),
|
||||
functionCallback.getInputTypeSchema());
|
||||
return new ChatRequest.Tool(function);
|
||||
private List<ChatRequest.Tool> getTools(List<ToolDefinition> toolDefinitions) {
|
||||
return toolDefinitions.stream().map(toolDefinition -> {
|
||||
var tool = new ChatRequest.Tool.Function(toolDefinition.name(), toolDefinition.description(),
|
||||
toolDefinition.inputSchema());
|
||||
return new ChatRequest.Tool(tool);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
private ChatOptions buildRequestOptions(OllamaApi.ChatRequest request) {
|
||||
var options = ModelOptionsUtils.mapToClass(request.options(), OllamaOptions.class);
|
||||
return ChatOptions.builder()
|
||||
.model(request.model())
|
||||
.frequencyPenalty(options.getFrequencyPenalty())
|
||||
.maxTokens(options.getMaxTokens())
|
||||
.presencePenalty(options.getPresencePenalty())
|
||||
.stopSequences(options.getStopSequences())
|
||||
.temperature(options.getTemperature())
|
||||
.topK(options.getTopK())
|
||||
.topP(options.getTopP())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatOptions getDefaultOptions() {
|
||||
return OllamaOptions.fromOptions(this.defaultOptions);
|
||||
@@ -468,9 +511,11 @@ public class OllamaChatModel extends AbstractToolCallSupport implements ChatMode
|
||||
|
||||
private OllamaOptions defaultOptions = OllamaOptions.builder().model(OllamaModel.MISTRAL.id()).build();
|
||||
|
||||
private ToolCallingManager toolCallingManager;
|
||||
|
||||
private FunctionCallbackResolver functionCallbackResolver;
|
||||
|
||||
private List<FunctionCallback> toolFunctionCallbacks = List.of();
|
||||
private List<FunctionCallback> toolFunctionCallbacks;
|
||||
|
||||
private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
|
||||
|
||||
@@ -489,11 +534,18 @@ public class OllamaChatModel extends AbstractToolCallSupport implements ChatMode
|
||||
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;
|
||||
@@ -510,8 +562,27 @@ public class OllamaChatModel extends AbstractToolCallSupport implements ChatMode
|
||||
}
|
||||
|
||||
public OllamaChatModel build() {
|
||||
return new OllamaChatModel(this.ollamaApi, this.defaultOptions, this.functionCallbackResolver,
|
||||
this.toolFunctionCallbacks, this.observationRegistry, this.modelManagementOptions);
|
||||
if (toolCallingManager != null) {
|
||||
Assert.isNull(functionCallbackResolver,
|
||||
"functionCallbackResolver must not be set when toolCallingManager is set");
|
||||
Assert.isNull(toolFunctionCallbacks,
|
||||
"toolFunctionCallbacks must not be set when toolCallingManager is set");
|
||||
|
||||
return new OllamaChatModel(this.ollamaApi, this.defaultOptions, this.toolCallingManager,
|
||||
this.observationRegistry, this.modelManagementOptions);
|
||||
}
|
||||
|
||||
if (functionCallbackResolver != null) {
|
||||
Assert.isNull(toolCallingManager,
|
||||
"toolCallingManager must not be set when functionCallbackResolver is set");
|
||||
List<FunctionCallback> toolCallbacks = this.toolFunctionCallbacks != null ? this.toolFunctionCallbacks
|
||||
: List.of();
|
||||
return new OllamaChatModel(this.ollamaApi, this.defaultOptions, this.functionCallbackResolver,
|
||||
toolCallbacks, this.observationRegistry, this.modelManagementOptions);
|
||||
}
|
||||
|
||||
return new OllamaChatModel(this.ollamaApi, this.defaultOptions, DEFAULT_TOOL_CALLING_MANAGER,
|
||||
this.observationRegistry, this.modelManagementOptions);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2023-2024 the original author or authors.
|
||||
* Copyright 2023-2025 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.
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.ai.ollama.api;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -32,7 +33,8 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.springframework.ai.embedding.EmbeddingOptions;
|
||||
import org.springframework.ai.model.ModelOptionsUtils;
|
||||
import org.springframework.ai.model.function.FunctionCallback;
|
||||
import org.springframework.ai.model.function.FunctionCallingOptions;
|
||||
import org.springframework.ai.model.tool.ToolCallingChatOptions;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -48,7 +50,7 @@ import org.springframework.util.Assert;
|
||||
* @see <a href="https://github.com/ollama/ollama/blob/main/api/types.go">Ollama Types</a>
|
||||
*/
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
public class OllamaOptions implements FunctionCallingOptions, EmbeddingOptions {
|
||||
public class OllamaOptions implements ToolCallingChatOptions, EmbeddingOptions {
|
||||
|
||||
private static final List<String> NON_SUPPORTED_FIELDS = List.of("model", "format", "keep_alive", "truncate");
|
||||
|
||||
@@ -305,6 +307,9 @@ public class OllamaOptions implements FunctionCallingOptions, EmbeddingOptions {
|
||||
@JsonProperty("truncate")
|
||||
private Boolean truncate;
|
||||
|
||||
@JsonIgnore
|
||||
private Boolean internalToolExecutionEnabled;
|
||||
|
||||
/**
|
||||
* Tool Function Callbacks to register with the ChatModel.
|
||||
* For Prompt Options the functionCallbacks are automatically enabled for the duration of the prompt execution.
|
||||
@@ -312,21 +317,18 @@ public class OllamaOptions implements FunctionCallingOptions, EmbeddingOptions {
|
||||
* from the registry to be used by the ChatModel chat completion requests.
|
||||
*/
|
||||
@JsonIgnore
|
||||
private List<FunctionCallback> functionCallbacks = new ArrayList<>();
|
||||
private List<FunctionCallback> toolCallbacks = 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.
|
||||
* The {@link #toolCallbacks} 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;
|
||||
private Set<String> toolNames = new HashSet<>();
|
||||
|
||||
@JsonIgnore
|
||||
private Map<String, Object> toolContext;
|
||||
@@ -381,9 +383,9 @@ public class OllamaOptions implements FunctionCallingOptions, EmbeddingOptions {
|
||||
.mirostatEta(fromOptions.getMirostatEta())
|
||||
.penalizeNewline(fromOptions.getPenalizeNewline())
|
||||
.stop(fromOptions.getStop())
|
||||
.functions(fromOptions.getFunctions())
|
||||
.proxyToolCalls(fromOptions.getProxyToolCalls())
|
||||
.functionCallbacks(fromOptions.getFunctionCallbacks())
|
||||
.tools(fromOptions.getTools())
|
||||
.internalToolExecutionEnabled(fromOptions.isInternalToolExecutionEnabled())
|
||||
.toolCallbacks(fromOptions.getToolCallbacks())
|
||||
.toolContext(fromOptions.getToolContext()).build();
|
||||
}
|
||||
|
||||
@@ -683,23 +685,73 @@ public class OllamaOptions implements FunctionCallingOptions, EmbeddingOptions {
|
||||
}
|
||||
|
||||
@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> getTools() {
|
||||
return this.toolNames;
|
||||
}
|
||||
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public void setTools(Set<String> toolNames) {
|
||||
Assert.notNull(toolNames, "toolNames cannot be null");
|
||||
Assert.noNullElements(toolNames, "toolNames cannot contain null elements");
|
||||
toolNames.forEach(tool -> Assert.hasText(tool, "toolNames cannot contain empty elements"));
|
||||
this.toolNames = toolNames;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
@JsonIgnore
|
||||
public Boolean isInternalToolExecutionEnabled() {
|
||||
return internalToolExecutionEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecutionEnabled) {
|
||||
this.internalToolExecutionEnabled = internalToolExecutionEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
@JsonIgnore
|
||||
public List<FunctionCallback> getFunctionCallbacks() {
|
||||
return this.functionCallbacks;
|
||||
return this.getToolCallbacks();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
@JsonIgnore
|
||||
public void setFunctionCallbacks(List<FunctionCallback> functionCallbacks) {
|
||||
this.functionCallbacks = functionCallbacks;
|
||||
this.setToolCallbacks(functionCallbacks);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
@JsonIgnore
|
||||
public Set<String> getFunctions() {
|
||||
return this.functions;
|
||||
return this.getTools();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
@JsonIgnore
|
||||
public void setFunctions(Set<String> functions) {
|
||||
this.functions = functions;
|
||||
this.setTools(functions);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -709,20 +761,26 @@ public class OllamaOptions implements FunctionCallingOptions, EmbeddingOptions {
|
||||
}
|
||||
|
||||
@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
|
||||
@JsonIgnore
|
||||
public Map<String, Object> getToolContext() {
|
||||
return this.toolContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public void setToolContext(Map<String, Object> toolContext) {
|
||||
this.toolContext = toolContext;
|
||||
}
|
||||
@@ -769,9 +827,9 @@ public class OllamaOptions implements FunctionCallingOptions, EmbeddingOptions {
|
||||
&& Objects.equals(this.mirostat, that.mirostat) && Objects.equals(this.mirostatTau, that.mirostatTau)
|
||||
&& Objects.equals(this.mirostatEta, that.mirostatEta)
|
||||
&& Objects.equals(this.penalizeNewline, that.penalizeNewline) && Objects.equals(this.stop, that.stop)
|
||||
&& Objects.equals(this.functionCallbacks, that.functionCallbacks)
|
||||
&& Objects.equals(this.proxyToolCalls, that.proxyToolCalls)
|
||||
&& Objects.equals(this.functions, that.functions) && Objects.equals(this.toolContext, that.toolContext);
|
||||
&& Objects.equals(this.toolCallbacks, that.toolCallbacks)
|
||||
&& Objects.equals(this.internalToolExecutionEnabled, that.internalToolExecutionEnabled)
|
||||
&& Objects.equals(this.toolNames, that.toolNames) && Objects.equals(this.toolContext, that.toolContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -781,7 +839,7 @@ public class OllamaOptions implements FunctionCallingOptions, EmbeddingOptions {
|
||||
this.useMMap, this.useMLock, this.numThread, this.numKeep, this.seed, this.numPredict, this.topK,
|
||||
this.topP, this.tfsZ, this.typicalP, this.repeatLastN, this.temperature, this.repeatPenalty,
|
||||
this.presencePenalty, this.frequencyPenalty, this.mirostat, this.mirostatTau, this.mirostatEta,
|
||||
this.penalizeNewline, this.stop, this.functionCallbacks, this.functions, this.proxyToolCalls,
|
||||
this.penalizeNewline, this.stop, this.toolCallbacks, this.toolNames, this.internalToolExecutionEnabled,
|
||||
this.toolContext);
|
||||
}
|
||||
|
||||
@@ -959,25 +1017,53 @@ public class OllamaOptions implements FunctionCallingOptions, EmbeddingOptions {
|
||||
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 tools(Set<String> toolNames) {
|
||||
this.options.setTools(toolNames);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder tools(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;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public Builder functionCallbacks(List<FunctionCallback> functionCallbacks) {
|
||||
this.options.functionCallbacks = functionCallbacks;
|
||||
return this;
|
||||
return toolCallbacks(functionCallbacks);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public Builder functions(Set<String> functions) {
|
||||
Assert.notNull(functions, "Function names must not be null");
|
||||
this.options.functions = functions;
|
||||
return this;
|
||||
return tools(functions);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public Builder function(String functionName) {
|
||||
Assert.hasText(functionName, "Function name must not be empty");
|
||||
this.options.functions.add(functionName);
|
||||
return this;
|
||||
return tools(functionName);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public Builder proxyToolCalls(Boolean proxyToolCalls) {
|
||||
this.options.proxyToolCalls = proxyToolCalls;
|
||||
if (proxyToolCalls != null) {
|
||||
this.options.setInternalToolExecutionEnabled(!proxyToolCalls);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2023-2024 the original author or authors.
|
||||
* Copyright 2023-2025 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.
|
||||
@@ -29,7 +29,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Christian Tzolov
|
||||
* @author Thomas Vitale
|
||||
*/
|
||||
public class OllamaChatRequestTests {
|
||||
class OllamaChatRequestTests {
|
||||
|
||||
OllamaChatModel chatModel = OllamaChatModel.builder()
|
||||
.ollamaApi(new OllamaApi())
|
||||
@@ -37,9 +37,10 @@ public class OllamaChatRequestTests {
|
||||
.build();
|
||||
|
||||
@Test
|
||||
public void createRequestWithDefaultOptions() {
|
||||
void createRequestWithDefaultOptions() {
|
||||
var prompt = this.chatModel.buildRequestPrompt(new Prompt("Test message content"));
|
||||
|
||||
var request = this.chatModel.ollamaChatRequest(new Prompt("Test message content"), false);
|
||||
var request = this.chatModel.ollamaChatRequest(prompt, false);
|
||||
|
||||
assertThat(request.messages()).hasSize(1);
|
||||
assertThat(request.stream()).isFalse();
|
||||
@@ -52,12 +53,12 @@ public class OllamaChatRequestTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createRequestWithPromptOllamaOptions() {
|
||||
|
||||
void createRequestWithPromptOllamaOptions() {
|
||||
// Runtime options should override the default options.
|
||||
OllamaOptions promptOptions = OllamaOptions.builder().temperature(0.8).topP(0.5).numGPU(2).build();
|
||||
var prompt = this.chatModel.buildRequestPrompt(new Prompt("Test message content", promptOptions));
|
||||
|
||||
var request = this.chatModel.ollamaChatRequest(new Prompt("Test message content", promptOptions), true);
|
||||
var request = this.chatModel.ollamaChatRequest(prompt, true);
|
||||
|
||||
assertThat(request.messages()).hasSize(1);
|
||||
assertThat(request.stream()).isTrue();
|
||||
@@ -74,11 +75,11 @@ public class OllamaChatRequestTests {
|
||||
|
||||
@Test
|
||||
public void createRequestWithPromptPortableChatOptions() {
|
||||
|
||||
// Ollama runtime options.
|
||||
ChatOptions portablePromptOptions = ChatOptions.builder().temperature(0.9).topK(100).topP(0.6).build();
|
||||
var prompt = this.chatModel.buildRequestPrompt(new Prompt("Test message content", portablePromptOptions));
|
||||
|
||||
var request = this.chatModel.ollamaChatRequest(new Prompt("Test message content", portablePromptOptions), true);
|
||||
var request = this.chatModel.ollamaChatRequest(prompt, true);
|
||||
|
||||
assertThat(request.messages()).hasSize(1);
|
||||
assertThat(request.stream()).isTrue();
|
||||
@@ -92,31 +93,33 @@ public class OllamaChatRequestTests {
|
||||
|
||||
@Test
|
||||
public void createRequestWithPromptOptionsModelOverride() {
|
||||
|
||||
// Ollama runtime options.
|
||||
OllamaOptions promptOptions = OllamaOptions.builder().model("PROMPT_MODEL").build();
|
||||
var prompt = this.chatModel.buildRequestPrompt(new Prompt("Test message content", promptOptions));
|
||||
|
||||
var request = this.chatModel.ollamaChatRequest(new Prompt("Test message content", promptOptions), true);
|
||||
var request = this.chatModel.ollamaChatRequest(prompt, true);
|
||||
|
||||
assertThat(request.model()).isEqualTo("PROMPT_MODEL");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createRequestWithDefaultOptionsModelOverride() {
|
||||
|
||||
OllamaChatModel chatModel = OllamaChatModel.builder()
|
||||
.ollamaApi(new OllamaApi())
|
||||
.defaultOptions(OllamaOptions.builder().model("DEFAULT_OPTIONS_MODEL").build())
|
||||
.build();
|
||||
|
||||
var request = chatModel.ollamaChatRequest(new Prompt("Test message content"), true);
|
||||
var prompt1 = chatModel.buildRequestPrompt(new Prompt("Test message content"));
|
||||
|
||||
var request = chatModel.ollamaChatRequest(prompt1, true);
|
||||
|
||||
assertThat(request.model()).isEqualTo("DEFAULT_OPTIONS_MODEL");
|
||||
|
||||
// Prompt options should override the default options.
|
||||
OllamaOptions promptOptions = OllamaOptions.builder().model("PROMPT_MODEL").build();
|
||||
var prompt2 = chatModel.buildRequestPrompt(new Prompt("Test message content", promptOptions));
|
||||
|
||||
request = chatModel.ollamaChatRequest(new Prompt("Test message content", promptOptions), true);
|
||||
request = chatModel.ollamaChatRequest(prompt2, true);
|
||||
|
||||
assertThat(request.model()).isEqualTo("PROMPT_MODEL");
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ class OllamaWithOpenAiChatModelIT {
|
||||
private static final String DEFAULT_OLLAMA_MODEL = "mistral";
|
||||
|
||||
@Container
|
||||
static OllamaContainer ollamaContainer = new OllamaContainer("ollama/ollama:0.5.1");
|
||||
static OllamaContainer ollamaContainer = new OllamaContainer("ollama/ollama:0.5.7");
|
||||
|
||||
static String baseUrl = "http://localhost:11434";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user