Fix MiniMax model function call implementation
Implement function call capability for MiniMax model and add unit tests based on new tool classes. Address most scenarios, but note limitations in complex English contexts with multiple function calls. Weather query example: may stop prematurely when querying multiple locations due to single-location parameter limit. This behavior stems from model performance constraints. Streaming function calling is not passing tests, will be address seperately. Resolves #1077 Implement function call capability for the Moonshot model. Include unit tests to verify the new functionality. This feature addresses the requirements outlined in issue #1058. fix: MiniMax function call review
This commit is contained in:
@@ -17,24 +17,33 @@ package org.springframework.ai.minimax;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ai.chat.messages.AssistantMessage;
|
||||
import org.springframework.ai.chat.messages.MessageType;
|
||||
import org.springframework.ai.chat.messages.ToolResponseMessage;
|
||||
import org.springframework.ai.chat.metadata.ChatGenerationMetadata;
|
||||
import org.springframework.ai.chat.metadata.ChatResponseMetadata;
|
||||
import org.springframework.ai.chat.metadata.RateLimit;
|
||||
import org.springframework.ai.chat.model.AbstractToolCallSupport;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.model.Generation;
|
||||
import org.springframework.ai.chat.model.StreamingChatModel;
|
||||
import org.springframework.ai.chat.metadata.ChatGenerationMetadata;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.minimax.api.MiniMaxApi;
|
||||
import org.springframework.ai.minimax.api.MiniMaxApi.ChatCompletion;
|
||||
import org.springframework.ai.minimax.api.MiniMaxApi.ChatCompletion.Choice;
|
||||
import org.springframework.ai.minimax.api.MiniMaxApi.ChatCompletionChunk;
|
||||
import org.springframework.ai.minimax.api.MiniMaxApi.ChatCompletionFinishReason;
|
||||
import org.springframework.ai.minimax.api.MiniMaxApi.ChatCompletionMessage;
|
||||
import org.springframework.ai.minimax.api.MiniMaxApi.ChatCompletionMessage.ChatCompletionFunction;
|
||||
import org.springframework.ai.minimax.api.MiniMaxApi.ChatCompletionMessage.Role;
|
||||
import org.springframework.ai.minimax.api.MiniMaxApi.ChatCompletionMessage.ToolCall;
|
||||
import org.springframework.ai.minimax.api.MiniMaxApi.ChatCompletionRequest;
|
||||
import org.springframework.ai.minimax.api.MiniMaxApi.FunctionTool;
|
||||
import org.springframework.ai.minimax.metadata.MiniMaxUsage;
|
||||
import org.springframework.ai.model.ModelOptionsUtils;
|
||||
import org.springframework.ai.model.function.AbstractFunctionCallSupport;
|
||||
import org.springframework.ai.model.function.FunctionCallback;
|
||||
import org.springframework.ai.model.function.FunctionCallbackContext;
|
||||
import org.springframework.ai.retry.RetryUtils;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -42,12 +51,11 @@ import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@@ -61,9 +69,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
* @see MiniMaxApi
|
||||
* @since 1.0.0 M1
|
||||
*/
|
||||
public class MiniMaxChatModel extends
|
||||
AbstractFunctionCallSupport<MiniMaxApi.ChatCompletionMessage, MiniMaxApi.ChatCompletionRequest, ResponseEntity<MiniMaxApi.ChatCompletion>>
|
||||
implements ChatModel, StreamingChatModel {
|
||||
public class MiniMaxChatModel extends AbstractToolCallSupport implements ChatModel, StreamingChatModel {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MiniMaxChatModel.class);
|
||||
|
||||
@@ -113,10 +119,27 @@ public class MiniMaxChatModel extends
|
||||
*/
|
||||
public MiniMaxChatModel(MiniMaxApi miniMaxApi, MiniMaxChatOptions options,
|
||||
FunctionCallbackContext functionCallbackContext, RetryTemplate retryTemplate) {
|
||||
super(functionCallbackContext);
|
||||
this(miniMaxApi, options, functionCallbackContext, List.of(), retryTemplate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a new instance of the MiniMaxChatModel.
|
||||
* @param miniMaxApi The MiniMaxApi instance to be used for interacting with the
|
||||
* MiniMax Chat API.
|
||||
* @param options The MiniMaxChatOptions to configure the chat model.
|
||||
* @param functionCallbackContext The function callback context.
|
||||
* @param toolFunctionCallbacks The tool function callbacks.
|
||||
* @param retryTemplate The retry template.
|
||||
*/
|
||||
public MiniMaxChatModel(MiniMaxApi miniMaxApi, MiniMaxChatOptions options,
|
||||
FunctionCallbackContext functionCallbackContext, List<FunctionCallback> toolFunctionCallbacks,
|
||||
RetryTemplate retryTemplate) {
|
||||
super(functionCallbackContext, options, toolFunctionCallbacks);
|
||||
Assert.notNull(miniMaxApi, "MiniMaxApi must not be null");
|
||||
Assert.notNull(options, "Options must not be null");
|
||||
Assert.notNull(retryTemplate, "RetryTemplate must not be null");
|
||||
Assert.isTrue(CollectionUtils.isEmpty(options.getFunctionCallbacks()),
|
||||
"The default function callbacks must be set via the toolFunctionCallbacks constructor parameter");
|
||||
this.miniMaxApi = miniMaxApi;
|
||||
this.defaultOptions = options;
|
||||
this.retryTemplate = retryTemplate;
|
||||
@@ -124,94 +147,150 @@ public class MiniMaxChatModel extends
|
||||
|
||||
@Override
|
||||
public ChatResponse call(Prompt prompt) {
|
||||
|
||||
ChatCompletionRequest request = createRequest(prompt, false);
|
||||
|
||||
return this.retryTemplate.execute(ctx -> {
|
||||
ResponseEntity<ChatCompletion> completionEntity = this.retryTemplate
|
||||
.execute(ctx -> this.miniMaxApi.chatCompletionEntity(request));
|
||||
|
||||
ResponseEntity<ChatCompletion> completionEntity = this.callWithFunctionSupport(request);
|
||||
var chatCompletion = completionEntity.getBody();
|
||||
|
||||
var chatCompletion = completionEntity.getBody();
|
||||
if (chatCompletion == null) {
|
||||
logger.warn("No chat completion returned for prompt: {}", prompt);
|
||||
return new ChatResponse(List.of());
|
||||
}
|
||||
if (chatCompletion == null) {
|
||||
logger.warn("No chat completion returned for prompt: {}", prompt);
|
||||
return new ChatResponse(List.of());
|
||||
}
|
||||
|
||||
if (chatCompletion.baseResponse() != null && chatCompletion.baseResponse().statusCode() != 0) {
|
||||
throw new RuntimeException(chatCompletion.baseResponse().message());
|
||||
}
|
||||
List<Choice> choices = chatCompletion.choices();
|
||||
if (choices == null) {
|
||||
logger.warn("No choices returned for prompt: {}, because: {}}", prompt,
|
||||
chatCompletion.baseResponse().message());
|
||||
return new ChatResponse(List.of());
|
||||
}
|
||||
|
||||
List<Generation> generations = chatCompletion.choices().stream().map(choice -> {
|
||||
return new Generation(choice.message().content(), toMap(chatCompletion.id(), choice))
|
||||
.withGenerationMetadata(ChatGenerationMetadata.from(choice.finishReason().name(), null));
|
||||
}).toList();
|
||||
List<Generation> generations = choices.stream().map(choice -> {
|
||||
// @formatter:off
|
||||
Map<String, Object> metadata = Map.of(
|
||||
"id", chatCompletion.id(),
|
||||
"role", choice.message().role() != null ? choice.message().role().name() : "",
|
||||
"finishReason", choice.finishReason() != null ? choice.finishReason().name() : "");
|
||||
// @formatter:on
|
||||
return buildGeneration(choice, metadata);
|
||||
}).toList();
|
||||
|
||||
return new ChatResponse(generations);
|
||||
});
|
||||
ChatResponse chatResponse = new ChatResponse(generations, from(completionEntity.getBody()));
|
||||
|
||||
if (isToolCall(chatResponse,
|
||||
Set.of(ChatCompletionFinishReason.TOOL_CALLS.name(), ChatCompletionFinishReason.STOP.name()))) {
|
||||
var toolCallConversation = handleToolCalls(prompt, chatResponse);
|
||||
// Recursively call the call method with the tool call message
|
||||
// conversation that contains the call responses.
|
||||
return this.call(new Prompt(toolCallConversation, prompt.getOptions()));
|
||||
}
|
||||
|
||||
return chatResponse;
|
||||
}
|
||||
|
||||
private Map<String, Object> toMap(String id, ChatCompletion.Choice choice) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
|
||||
var message = choice.message();
|
||||
if (message.role() != null) {
|
||||
map.put("role", message.role().name());
|
||||
}
|
||||
if (choice.finishReason() != null) {
|
||||
map.put("finishReason", choice.finishReason().name());
|
||||
}
|
||||
map.put("id", id);
|
||||
return map;
|
||||
@Override
|
||||
public ChatOptions getDefaultOptions() {
|
||||
return MiniMaxChatOptions.fromOptions(this.defaultOptions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ChatResponse> stream(Prompt prompt) {
|
||||
|
||||
ChatCompletionRequest request = createRequest(prompt, true);
|
||||
|
||||
return this.retryTemplate.execute(ctx -> {
|
||||
Flux<ChatCompletionChunk> completionChunks = this.retryTemplate
|
||||
.execute(ctx -> this.miniMaxApi.chatCompletionStream(request));
|
||||
|
||||
Flux<ChatCompletionChunk> completionChunks = this.miniMaxApi.chatCompletionStream(request);
|
||||
// For chunked responses, only the first chunk contains the choice role.
|
||||
// The rest of the chunks with same ID share the same role.
|
||||
ConcurrentHashMap<String, String> roleMap = new ConcurrentHashMap<>();
|
||||
|
||||
// For chunked responses, only the first chunk contains the choice role.
|
||||
// The rest of the chunks with same ID share the same role.
|
||||
ConcurrentHashMap<String, String> roleMap = new ConcurrentHashMap<>();
|
||||
|
||||
// Convert the ChatCompletionChunk into a ChatCompletion to be able to reuse
|
||||
// the function call handling logic.
|
||||
return completionChunks.map(this::chunkToChatCompletion).map(chatCompletion -> {
|
||||
// Convert the ChatCompletionChunk into a ChatCompletion to be able to reuse
|
||||
// the function call handling logic.
|
||||
Flux<ChatResponse> chatResponse = completionChunks.map(this::chunkToChatCompletion)
|
||||
.switchMap(chatCompletion -> Mono.just(chatCompletion).map(chatCompletion2 -> {
|
||||
try {
|
||||
chatCompletion = handleFunctionCallOrReturn(request, ResponseEntity.of(Optional.of(chatCompletion)))
|
||||
.getBody();
|
||||
|
||||
@SuppressWarnings("null")
|
||||
String id = chatCompletion.id();
|
||||
String id = chatCompletion2.id();
|
||||
|
||||
List<Generation> generations = chatCompletion.choices().stream().map(choice -> {
|
||||
if (choice.message().role() != null) {
|
||||
roleMap.putIfAbsent(id, choice.message().role().name());
|
||||
}
|
||||
String finish = (choice.finishReason() != null ? choice.finishReason().name() : "");
|
||||
var generation = new Generation(choice.message().content(),
|
||||
Map.of("id", id, "role", roleMap.get(id), "finishReason", finish));
|
||||
if (choice.finishReason() != null) {
|
||||
generation = generation.withGenerationMetadata(
|
||||
ChatGenerationMetadata.from(choice.finishReason().name(), null));
|
||||
}
|
||||
return generation;
|
||||
}).toList();
|
||||
// @formatter:off
|
||||
List<Generation> generations = chatCompletion2.choices().stream().map(choice -> {
|
||||
if (choice.message().role() != null) {
|
||||
roleMap.putIfAbsent(id, choice.message().role().name());
|
||||
}
|
||||
Map<String, Object> metadata = Map.of(
|
||||
"id", chatCompletion2.id(),
|
||||
"role", roleMap.getOrDefault(id, ""),
|
||||
"finishReason", choice.finishReason() != null ? choice.finishReason().name() : "");
|
||||
return buildGeneration(choice, metadata);
|
||||
}).toList();
|
||||
// @formatter:on
|
||||
|
||||
return new ChatResponse(generations);
|
||||
if (chatCompletion2.usage() != null) {
|
||||
return new ChatResponse(generations, from(chatCompletion2));
|
||||
}
|
||||
else {
|
||||
return new ChatResponse(generations);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
logger.error("Error processing chat completion", e);
|
||||
return new ChatResponse(List.of());
|
||||
}
|
||||
|
||||
});
|
||||
}));
|
||||
|
||||
return chatResponse.flatMap(response -> {
|
||||
|
||||
if (isToolCall(response,
|
||||
Set.of(ChatCompletionFinishReason.TOOL_CALLS.name(), ChatCompletionFinishReason.STOP.name()))) {
|
||||
var toolCallConversation = handleToolCalls(prompt, response);
|
||||
// Recursively call the stream method with the tool call message
|
||||
// conversation that contains the call responses.
|
||||
return this.stream(new Prompt(toolCallConversation, prompt.getOptions()));
|
||||
}
|
||||
else {
|
||||
return Flux.just(response);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private ChatResponseMetadata from(ChatCompletion result, RateLimit rateLimit) {
|
||||
Assert.notNull(result, "MiniMax ChatCompletionResult must not be null");
|
||||
return ChatResponseMetadata.builder()
|
||||
.withId(result.id())
|
||||
.withUsage(MiniMaxUsage.from(result.usage()))
|
||||
.withModel(result.model())
|
||||
.withRateLimit(rateLimit)
|
||||
.withKeyValue("created", result.created())
|
||||
.build();
|
||||
}
|
||||
|
||||
private ChatResponseMetadata from(ChatCompletion result) {
|
||||
Assert.notNull(result, "MiniMax ChatCompletionResult must not be null");
|
||||
return ChatResponseMetadata.builder()
|
||||
.withId(result.id())
|
||||
.withUsage(MiniMaxUsage.from(result.usage()))
|
||||
.withModel(result.model())
|
||||
.withKeyValue("created", result.created())
|
||||
.build();
|
||||
}
|
||||
|
||||
private static Generation buildGeneration(Choice choice, Map<String, Object> metadata) {
|
||||
List<AssistantMessage.ToolCall> toolCalls = choice.message().toolCalls() == null ? List.of()
|
||||
: choice.message()
|
||||
.toolCalls()
|
||||
.stream()
|
||||
.map(toolCall -> new AssistantMessage.ToolCall(toolCall.id(), "function",
|
||||
toolCall.function().name(), toolCall.function().arguments()))
|
||||
.toList();
|
||||
|
||||
var assistantMessage = new AssistantMessage(choice.message().content(), metadata, toolCalls);
|
||||
String finishReason = (choice.finishReason() != null ? choice.finishReason().name() : "");
|
||||
var generationMetadata = ChatGenerationMetadata.from(finishReason, null);
|
||||
return new Generation(assistantMessage, generationMetadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the ChatCompletionChunk into a ChatCompletion. The Usage is set to null.
|
||||
* @param chunk the ChatCompletionChunk to convert
|
||||
@@ -235,49 +314,73 @@ public class MiniMaxChatModel extends
|
||||
*/
|
||||
ChatCompletionRequest createRequest(Prompt prompt, boolean stream) {
|
||||
|
||||
Set<String> functionsForThisRequest = new HashSet<>();
|
||||
List<ChatCompletionMessage> chatCompletionMessages = prompt.getInstructions().stream().map(message -> {
|
||||
if (message.getMessageType() == MessageType.USER || message.getMessageType() == MessageType.SYSTEM) {
|
||||
Object content = message.getContent();
|
||||
return List.of(new ChatCompletionMessage(content,
|
||||
ChatCompletionMessage.Role.valueOf(message.getMessageType().name())));
|
||||
}
|
||||
else if (message.getMessageType() == MessageType.ASSISTANT) {
|
||||
var assistantMessage = (AssistantMessage) message;
|
||||
List<ToolCall> toolCalls = null;
|
||||
if (!CollectionUtils.isEmpty(assistantMessage.getToolCalls())) {
|
||||
toolCalls = assistantMessage.getToolCalls().stream().map(toolCall -> {
|
||||
var function = new ChatCompletionFunction(toolCall.name(), toolCall.arguments());
|
||||
return new ToolCall(toolCall.id(), toolCall.type(), function);
|
||||
}).toList();
|
||||
}
|
||||
return List.of(new ChatCompletionMessage(assistantMessage.getContent(),
|
||||
ChatCompletionMessage.Role.ASSISTANT, null, null, toolCalls));
|
||||
}
|
||||
else if (message.getMessageType() == MessageType.TOOL) {
|
||||
ToolResponseMessage toolMessage = (ToolResponseMessage) message;
|
||||
|
||||
List<MiniMaxApi.ChatCompletionMessage> chatCompletionMessages = prompt.getInstructions()
|
||||
.stream()
|
||||
.map(m -> new MiniMaxApi.ChatCompletionMessage(m.getContent(),
|
||||
MiniMaxApi.ChatCompletionMessage.Role.valueOf(m.getMessageType().name())))
|
||||
.toList();
|
||||
toolMessage.getResponses().forEach(response -> {
|
||||
Assert.isTrue(response.id() != null, "ToolResponseMessage must have an id");
|
||||
Assert.isTrue(response.name() != null, "ToolResponseMessage must have a name");
|
||||
});
|
||||
|
||||
return toolMessage.getResponses()
|
||||
.stream()
|
||||
.map(tr -> new ChatCompletionMessage(tr.responseData(), ChatCompletionMessage.Role.TOOL, tr.name(),
|
||||
tr.id(), null))
|
||||
.toList();
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unsupported message type: " + message.getMessageType());
|
||||
}
|
||||
}).flatMap(List::stream).toList();
|
||||
|
||||
ChatCompletionRequest request = new ChatCompletionRequest(chatCompletionMessages, stream);
|
||||
|
||||
Set<String> enabledToolsToUse = new HashSet<>();
|
||||
|
||||
if (prompt.getOptions() != null) {
|
||||
MiniMaxChatOptions updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(prompt.getOptions(),
|
||||
ChatOptions.class, MiniMaxChatOptions.class);
|
||||
|
||||
Set<String> promptEnabledFunctions = this.handleFunctionCallbackConfigurations(updatedRuntimeOptions,
|
||||
IS_RUNTIME_CALL);
|
||||
functionsForThisRequest.addAll(promptEnabledFunctions);
|
||||
enabledToolsToUse.addAll(this.runtimeFunctionCallbackConfigurations(updatedRuntimeOptions));
|
||||
|
||||
request = ModelOptionsUtils.merge(updatedRuntimeOptions, request, ChatCompletionRequest.class);
|
||||
}
|
||||
|
||||
if (this.defaultOptions != null) {
|
||||
|
||||
Set<String> defaultEnabledFunctions = this.handleFunctionCallbackConfigurations(this.defaultOptions,
|
||||
!IS_RUNTIME_CALL);
|
||||
|
||||
functionsForThisRequest.addAll(defaultEnabledFunctions);
|
||||
|
||||
request = ModelOptionsUtils.merge(request, this.defaultOptions, ChatCompletionRequest.class);
|
||||
if (!CollectionUtils.isEmpty(this.defaultOptions.getFunctions())) {
|
||||
enabledToolsToUse.addAll(this.defaultOptions.getFunctions());
|
||||
}
|
||||
|
||||
// Add the enabled functions definitions to the request's tools parameter.
|
||||
if (!CollectionUtils.isEmpty(functionsForThisRequest)) {
|
||||
request = ModelOptionsUtils.merge(request, this.defaultOptions, ChatCompletionRequest.class);
|
||||
|
||||
if (!CollectionUtils.isEmpty(enabledToolsToUse)) {
|
||||
|
||||
request = ModelOptionsUtils.merge(
|
||||
MiniMaxChatOptions.builder().withTools(this.getFunctionTools(functionsForThisRequest)).build(),
|
||||
request, ChatCompletionRequest.class);
|
||||
MiniMaxChatOptions.builder().withTools(this.getFunctionTools(enabledToolsToUse)).build(), request,
|
||||
ChatCompletionRequest.class);
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
private List<MiniMaxApi.FunctionTool> getFunctionTools(Set<String> functionNames) {
|
||||
private List<FunctionTool> getFunctionTools(Set<String> functionNames) {
|
||||
return this.resolveFunctionCallbacks(functionNames).stream().map(functionCallback -> {
|
||||
var function = new FunctionTool.Function(functionCallback.getDescription(), functionCallback.getName(),
|
||||
functionCallback.getInputTypeSchema());
|
||||
@@ -285,80 +388,4 @@ public class MiniMaxChatModel extends
|
||||
}).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ChatCompletionRequest doCreateToolResponseRequest(ChatCompletionRequest previousRequest,
|
||||
ChatCompletionMessage responseMessage, List<ChatCompletionMessage> conversationHistory) {
|
||||
|
||||
// Every tool-call item requires a separate function call and a response (TOOL)
|
||||
// message.
|
||||
for (ToolCall toolCall : responseMessage.toolCalls()) {
|
||||
|
||||
var functionName = toolCall.function().name();
|
||||
String functionArguments = toolCall.function().arguments();
|
||||
|
||||
if (!this.functionCallbackRegister.containsKey(functionName)) {
|
||||
throw new IllegalStateException("No function callback found for function name: " + functionName);
|
||||
}
|
||||
|
||||
String functionResponse = this.functionCallbackRegister.get(functionName).call(functionArguments);
|
||||
|
||||
// Add the function response to the conversation.
|
||||
conversationHistory
|
||||
.add(new ChatCompletionMessage(functionResponse, Role.TOOL, functionName, toolCall.id(), null));
|
||||
}
|
||||
|
||||
// Recursively call chatCompletionWithTools until the model doesn't call a
|
||||
// functions anymore.
|
||||
ChatCompletionRequest newRequest = new ChatCompletionRequest(conversationHistory, false);
|
||||
newRequest = ModelOptionsUtils.merge(newRequest, previousRequest, ChatCompletionRequest.class);
|
||||
|
||||
return newRequest;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<ChatCompletionMessage> doGetUserMessages(ChatCompletionRequest request) {
|
||||
return request.messages();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ChatCompletionMessage doGetToolResponseMessage(ResponseEntity<ChatCompletion> chatCompletion) {
|
||||
return chatCompletion.getBody().choices().iterator().next().message();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ResponseEntity<ChatCompletion> doChatCompletion(ChatCompletionRequest request) {
|
||||
return this.miniMaxApi.chatCompletionEntity(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Flux<ResponseEntity<ChatCompletion>> doChatCompletionStream(ChatCompletionRequest request) {
|
||||
return this.miniMaxApi.chatCompletionStream(request)
|
||||
.map(this::chunkToChatCompletion)
|
||||
.map(Optional::ofNullable)
|
||||
.map(ResponseEntity::of);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isToolFunctionCall(ResponseEntity<ChatCompletion> chatCompletion) {
|
||||
var body = chatCompletion.getBody();
|
||||
if (body == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var choices = body.choices();
|
||||
if (CollectionUtils.isEmpty(choices)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var choice = choices.get(0);
|
||||
var message = choice.message();
|
||||
return message != null && !CollectionUtils.isEmpty(choice.message().toolCalls())
|
||||
&& choice.finishReason() == ChatCompletionFinishReason.TOOL_CALLS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatOptions getDefaultOptions() {
|
||||
return MiniMaxChatOptions.fromOptions(this.defaultOptions);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,11 +15,6 @@
|
||||
*/
|
||||
package org.springframework.ai.minimax;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
@@ -31,6 +26,11 @@ import org.springframework.ai.model.function.FunctionCallingOptions;
|
||||
import org.springframework.boot.context.properties.NestedConfigurationProperty;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* MiniMaxChatOptions represents the options for performing chat completion using the
|
||||
* MiniMax API. It provides methods to set and retrieve various options like model,
|
||||
|
||||
@@ -714,7 +714,7 @@ public class MiniMaxApi {
|
||||
.takeUntil(SSE_DONE_PREDICATE)
|
||||
.filter(SSE_DONE_PREDICATE.negate())
|
||||
.map(content -> ModelOptionsUtils.jsonToObject(content, ChatCompletionChunk.class))
|
||||
.map(chunk -> {
|
||||
.map(chunk -> {
|
||||
if (this.chunkMerger.isStreamingToolFunctionCall(chunk)) {
|
||||
isInsideTool.set(true);
|
||||
}
|
||||
@@ -730,7 +730,7 @@ public class MiniMaxApi {
|
||||
.concatMapIterable(window -> {
|
||||
Mono<ChatCompletionChunk> monoChunk = window.reduce(
|
||||
new ChatCompletionChunk(null, null, null, null, null, null),
|
||||
this.chunkMerger::merge);
|
||||
(previous, current) -> this.chunkMerger.merge(previous, current));
|
||||
return List.of(monoChunk);
|
||||
})
|
||||
.flatMap(mono -> mono);
|
||||
|
||||
@@ -15,7 +15,16 @@
|
||||
*/
|
||||
package org.springframework.ai.minimax.api;
|
||||
|
||||
import org.springframework.ai.minimax.api.MiniMaxApi.ChatCompletionChunk;
|
||||
import org.springframework.ai.minimax.api.MiniMaxApi.ChatCompletionChunk.ChunkChoice;
|
||||
import org.springframework.ai.minimax.api.MiniMaxApi.ChatCompletionFinishReason;
|
||||
import org.springframework.ai.minimax.api.MiniMaxApi.ChatCompletionMessage;
|
||||
import org.springframework.ai.minimax.api.MiniMaxApi.ChatCompletionMessage.ChatCompletionFunction;
|
||||
import org.springframework.ai.minimax.api.MiniMaxApi.ChatCompletionMessage.Role;
|
||||
import org.springframework.ai.minimax.api.MiniMaxApi.ChatCompletionMessage.ToolCall;
|
||||
import org.springframework.ai.minimax.api.MiniMaxApi.LogProbs;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -29,14 +38,7 @@ import java.util.List;
|
||||
*/
|
||||
public class MiniMaxStreamFunctionCallingHelper {
|
||||
|
||||
/**
|
||||
* Merge the previous and current ChatCompletionChunk into a single one.
|
||||
* @param previous the previous ChatCompletionChunk
|
||||
* @param current the current ChatCompletionChunk
|
||||
* @return the merged ChatCompletionChunk
|
||||
*/
|
||||
public MiniMaxApi.ChatCompletionChunk merge(MiniMaxApi.ChatCompletionChunk previous,
|
||||
MiniMaxApi.ChatCompletionChunk current) {
|
||||
public ChatCompletionChunk merge(ChatCompletionChunk previous, ChatCompletionChunk current) {
|
||||
|
||||
if (previous == null) {
|
||||
return current;
|
||||
@@ -49,47 +51,38 @@ public class MiniMaxStreamFunctionCallingHelper {
|
||||
: previous.systemFingerprint());
|
||||
String object = (current.object() != null ? current.object() : previous.object());
|
||||
|
||||
MiniMaxApi.ChatCompletionChunk.ChunkChoice previousChoice0 = (CollectionUtils.isEmpty(previous.choices()) ? null
|
||||
: previous.choices().get(0));
|
||||
MiniMaxApi.ChatCompletionChunk.ChunkChoice currentChoice0 = (CollectionUtils.isEmpty(current.choices()) ? null
|
||||
: current.choices().get(0));
|
||||
ChunkChoice previousChoice0 = (CollectionUtils.isEmpty(previous.choices()) ? null : previous.choices().get(0));
|
||||
ChunkChoice currentChoice0 = (CollectionUtils.isEmpty(current.choices()) ? null : current.choices().get(0));
|
||||
|
||||
MiniMaxApi.ChatCompletionChunk.ChunkChoice choice = merge(previousChoice0, currentChoice0);
|
||||
List<MiniMaxApi.ChatCompletionChunk.ChunkChoice> chunkChoices = choice == null ? List.of() : List.of(choice);
|
||||
return new MiniMaxApi.ChatCompletionChunk(id, chunkChoices, created, model, systemFingerprint, object);
|
||||
ChunkChoice choice = merge(previousChoice0, currentChoice0);
|
||||
List<ChunkChoice> chunkChoices = choice == null ? List.of() : List.of(choice);
|
||||
return new ChatCompletionChunk(id, chunkChoices, created, model, systemFingerprint, object);
|
||||
}
|
||||
|
||||
private MiniMaxApi.ChatCompletionChunk.ChunkChoice merge(MiniMaxApi.ChatCompletionChunk.ChunkChoice previous,
|
||||
MiniMaxApi.ChatCompletionChunk.ChunkChoice current) {
|
||||
private ChunkChoice merge(ChunkChoice previous, ChunkChoice current) {
|
||||
if (previous == null) {
|
||||
return current;
|
||||
}
|
||||
|
||||
MiniMaxApi.ChatCompletionFinishReason finishReason = (current.finishReason() != null ? current.finishReason()
|
||||
ChatCompletionFinishReason finishReason = (current.finishReason() != null ? current.finishReason()
|
||||
: previous.finishReason());
|
||||
Integer index = (current.index() != null ? current.index() : previous.index());
|
||||
LogProbs logprobs = (current.logprobs() != null ? current.logprobs() : previous.logprobs());
|
||||
|
||||
MiniMaxApi.ChatCompletionMessage message = merge(previous.delta(), current.delta());
|
||||
|
||||
MiniMaxApi.LogProbs logprobs = (current.logprobs() != null ? current.logprobs() : previous.logprobs());
|
||||
return new MiniMaxApi.ChatCompletionChunk.ChunkChoice(finishReason, index, message, logprobs);
|
||||
ChatCompletionMessage message = merge(previous.delta(), current.delta());
|
||||
return new ChunkChoice(finishReason, index, message, logprobs);
|
||||
}
|
||||
|
||||
private MiniMaxApi.ChatCompletionMessage merge(MiniMaxApi.ChatCompletionMessage previous,
|
||||
MiniMaxApi.ChatCompletionMessage current) {
|
||||
private ChatCompletionMessage merge(ChatCompletionMessage previous, ChatCompletionMessage current) {
|
||||
String content = (current.content() != null ? current.content()
|
||||
: (previous.content() != null) ? previous.content() : "");
|
||||
MiniMaxApi.ChatCompletionMessage.Role role = (current.role() != null ? current.role() : previous.role());
|
||||
role = (role != null ? role : MiniMaxApi.ChatCompletionMessage.Role.ASSISTANT); // default
|
||||
// to
|
||||
// ASSISTANT
|
||||
// (if
|
||||
// null
|
||||
: "" + ((previous.content() != null) ? previous.content() : ""));
|
||||
Role role = (current.role() != null ? current.role() : previous.role());
|
||||
role = (role != null ? role : Role.ASSISTANT); // default to ASSISTANT (if null
|
||||
String name = (current.name() != null ? current.name() : previous.name());
|
||||
String toolCallId = (current.toolCallId() != null ? current.toolCallId() : previous.toolCallId());
|
||||
|
||||
List<MiniMaxApi.ChatCompletionMessage.ToolCall> toolCalls = new ArrayList<>();
|
||||
MiniMaxApi.ChatCompletionMessage.ToolCall lastPreviousTooCall = null;
|
||||
List<ToolCall> toolCalls = new ArrayList<>();
|
||||
ToolCall lastPreviousTooCall = null;
|
||||
if (previous.toolCalls() != null) {
|
||||
lastPreviousTooCall = previous.toolCalls().get(previous.toolCalls().size() - 1);
|
||||
if (previous.toolCalls().size() > 1) {
|
||||
@@ -101,43 +94,40 @@ public class MiniMaxStreamFunctionCallingHelper {
|
||||
throw new IllegalStateException("Currently only one tool call is supported per message!");
|
||||
}
|
||||
var currentToolCall = current.toolCalls().iterator().next();
|
||||
if (currentToolCall.id() != null) {
|
||||
if (currentToolCall.id() == null
|
||||
|| (lastPreviousTooCall != null && currentToolCall.id().equals(lastPreviousTooCall.id()))) {
|
||||
toolCalls.add(merge(lastPreviousTooCall, currentToolCall));
|
||||
}
|
||||
else {
|
||||
if (lastPreviousTooCall != null) {
|
||||
toolCalls.add(lastPreviousTooCall);
|
||||
}
|
||||
toolCalls.add(currentToolCall);
|
||||
}
|
||||
else {
|
||||
toolCalls.add(merge(lastPreviousTooCall, currentToolCall));
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (lastPreviousTooCall != null) {
|
||||
toolCalls.add(lastPreviousTooCall);
|
||||
}
|
||||
}
|
||||
return new MiniMaxApi.ChatCompletionMessage(content, role, name, toolCallId, toolCalls);
|
||||
return new ChatCompletionMessage(content, role, name, toolCallId, toolCalls);
|
||||
}
|
||||
|
||||
private MiniMaxApi.ChatCompletionMessage.ToolCall merge(MiniMaxApi.ChatCompletionMessage.ToolCall previous,
|
||||
MiniMaxApi.ChatCompletionMessage.ToolCall current) {
|
||||
private ToolCall merge(ToolCall previous, ToolCall current) {
|
||||
if (previous == null) {
|
||||
return current;
|
||||
}
|
||||
String id = (current.id() != null ? current.id() : previous.id());
|
||||
String type = (current.type() != null ? current.type() : previous.type());
|
||||
MiniMaxApi.ChatCompletionMessage.ChatCompletionFunction function = merge(previous.function(),
|
||||
current.function());
|
||||
return new MiniMaxApi.ChatCompletionMessage.ToolCall(id, type, function);
|
||||
ChatCompletionFunction function = merge(previous.function(), current.function());
|
||||
return new ToolCall(id, type, function);
|
||||
}
|
||||
|
||||
private MiniMaxApi.ChatCompletionMessage.ChatCompletionFunction merge(
|
||||
MiniMaxApi.ChatCompletionMessage.ChatCompletionFunction previous,
|
||||
MiniMaxApi.ChatCompletionMessage.ChatCompletionFunction current) {
|
||||
private ChatCompletionFunction merge(ChatCompletionFunction previous, ChatCompletionFunction current) {
|
||||
if (previous == null) {
|
||||
return current;
|
||||
}
|
||||
String name = (current.name() != null ? current.name() : previous.name());
|
||||
String name = (StringUtils.hasLength(current.name()) ? current.name() : previous.name());
|
||||
StringBuilder arguments = new StringBuilder();
|
||||
if (previous.arguments() != null) {
|
||||
arguments.append(previous.arguments());
|
||||
@@ -145,14 +135,14 @@ public class MiniMaxStreamFunctionCallingHelper {
|
||||
if (current.arguments() != null) {
|
||||
arguments.append(current.arguments());
|
||||
}
|
||||
return new MiniMaxApi.ChatCompletionMessage.ChatCompletionFunction(name, arguments.toString());
|
||||
return new ChatCompletionFunction(name, arguments.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param chatCompletion the ChatCompletionChunk to check
|
||||
* @return true if the ChatCompletionChunk is a streaming tool function call.
|
||||
*/
|
||||
public boolean isStreamingToolFunctionCall(MiniMaxApi.ChatCompletionChunk chatCompletion) {
|
||||
public boolean isStreamingToolFunctionCall(ChatCompletionChunk chatCompletion) {
|
||||
|
||||
if (chatCompletion == null || CollectionUtils.isEmpty(chatCompletion.choices())) {
|
||||
return false;
|
||||
@@ -170,7 +160,7 @@ public class MiniMaxStreamFunctionCallingHelper {
|
||||
* @return true if the ChatCompletionChunk is a streaming tool function call and it is
|
||||
* the last one.
|
||||
*/
|
||||
public boolean isStreamingToolFunctionCallFinish(MiniMaxApi.ChatCompletionChunk chatCompletion) {
|
||||
public boolean isStreamingToolFunctionCallFinish(ChatCompletionChunk chatCompletion) {
|
||||
|
||||
if (chatCompletion == null || CollectionUtils.isEmpty(chatCompletion.choices())) {
|
||||
return false;
|
||||
@@ -180,23 +170,7 @@ public class MiniMaxStreamFunctionCallingHelper {
|
||||
if (choice == null || choice.delta() == null) {
|
||||
return false;
|
||||
}
|
||||
return choice.finishReason() == MiniMaxApi.ChatCompletionFinishReason.TOOL_CALLS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the ChatCompletionChunk into a ChatCompletion. The Usage is set to null.
|
||||
* @param chunk the ChatCompletionChunk to convert
|
||||
* @return the ChatCompletion
|
||||
*/
|
||||
public MiniMaxApi.ChatCompletion chunkToChatCompletion(MiniMaxApi.ChatCompletionChunk chunk) {
|
||||
List<MiniMaxApi.ChatCompletion.Choice> choices = chunk.choices()
|
||||
.stream()
|
||||
.map(chunkChoice -> new MiniMaxApi.ChatCompletion.Choice(chunkChoice.finishReason(), chunkChoice.index(),
|
||||
chunkChoice.delta(), chunkChoice.logprobs()))
|
||||
.toList();
|
||||
|
||||
return new MiniMaxApi.ChatCompletion(chunk.id(), choices, chunk.created(), chunk.model(),
|
||||
chunk.systemFingerprint(), "chat.completion", null, null);
|
||||
return choice.finishReason() == ChatCompletionFinishReason.TOOL_CALLS;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -54,7 +54,8 @@ public class MiniMaxApiToolFunctionCallIT {
|
||||
public void toolFunctionCall() {
|
||||
|
||||
// Step 1: send the conversation and available functions to the model
|
||||
var message = new ChatCompletionMessage("What's the weather like in San Francisco?", Role.USER);
|
||||
var message = new ChatCompletionMessage(
|
||||
"What's the weather like in San Francisco? Return the temperature in Celsius.", Role.USER);
|
||||
|
||||
var functionTool = new MiniMaxApi.FunctionTool(Type.FUNCTION, new MiniMaxApi.FunctionTool.Function(
|
||||
"Get the weather in location. Return temperature in 30°F or 30°C format.", "getCurrentWeather", """
|
||||
@@ -126,7 +127,8 @@ public class MiniMaxApiToolFunctionCallIT {
|
||||
|
||||
assertThat(chatCompletion2.getBody().choices().get(0).message().role()).isEqualTo(Role.ASSISTANT);
|
||||
assertThat(chatCompletion2.getBody().choices().get(0).message().content()).contains("San Francisco")
|
||||
.containsAnyOf("30.0°C", "30°C", "30.0°F", "30°F");
|
||||
.containsAnyOf("30.0°C", "30°C", "30.0")
|
||||
.containsAnyOf("°C", "Celsius");
|
||||
}
|
||||
|
||||
private static <T> T fromJson(String json, Class<T> targetClass) {
|
||||
|
||||
@@ -62,16 +62,8 @@ public class MiniMaxAutoConfiguration {
|
||||
var miniMaxApi = miniMaxApi(chatProperties.getBaseUrl(), commonProperties.getBaseUrl(),
|
||||
chatProperties.getApiKey(), commonProperties.getApiKey(), restClientBuilder, responseErrorHandler);
|
||||
|
||||
MiniMaxChatModel chatModel = new MiniMaxChatModel(miniMaxApi, chatProperties.getOptions(),
|
||||
functionCallbackContext, retryTemplate);
|
||||
|
||||
if (!CollectionUtils.isEmpty(toolFunctionCallbacks)) {
|
||||
Map<String, FunctionCallback> toolFunctionCallbackMap = toolFunctionCallbacks.stream()
|
||||
.collect(Collectors.toMap(FunctionCallback::getName, Function.identity(), (a, b) -> b));
|
||||
chatModel.getFunctionCallbackRegister().putAll(toolFunctionCallbackMap);
|
||||
}
|
||||
|
||||
return chatModel;
|
||||
return new MiniMaxChatModel(miniMaxApi, chatProperties.getOptions(), functionCallbackContext,
|
||||
toolFunctionCallbacks, retryTemplate);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -20,10 +20,10 @@ import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.model.Generation;
|
||||
import org.springframework.ai.chat.messages.AssistantMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.model.Generation;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.minimax.MiniMaxChatModel;
|
||||
import org.springframework.ai.minimax.MiniMaxChatOptions;
|
||||
@@ -53,11 +53,12 @@ public class FunctionCallbackInPromptIT {
|
||||
|
||||
@Test
|
||||
void functionCallTest() {
|
||||
contextRunner.withPropertyValues("spring.ai.minimax.chat.options.model=abab6-chat").run(context -> {
|
||||
contextRunner.withPropertyValues("spring.ai.minimax.chat.options.model=abab6.5s-chat").run(context -> {
|
||||
|
||||
MiniMaxChatModel chatModel = context.getBean(MiniMaxChatModel.class);
|
||||
|
||||
UserMessage userMessage = new UserMessage("What's the weather like in San Francisco, Tokyo, and Paris?");
|
||||
UserMessage userMessage = new UserMessage(
|
||||
"What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius.");
|
||||
|
||||
var promptOptions = MiniMaxChatOptions.builder()
|
||||
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
|
||||
@@ -78,11 +79,12 @@ public class FunctionCallbackInPromptIT {
|
||||
@Test
|
||||
void streamingFunctionCallTest() {
|
||||
|
||||
contextRunner.withPropertyValues("spring.ai.minimax.chat.options.model=abab6-chat").run(context -> {
|
||||
contextRunner.withPropertyValues("spring.ai.minimax.chat.options.model=abab6.5s-chat").run(context -> {
|
||||
|
||||
MiniMaxChatModel chatModel = context.getBean(MiniMaxChatModel.class);
|
||||
|
||||
UserMessage userMessage = new UserMessage("What's the weather like in San Francisco, Tokyo, and Paris?");
|
||||
UserMessage userMessage = new UserMessage(
|
||||
"What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius.");
|
||||
|
||||
var promptOptions = MiniMaxChatOptions.builder()
|
||||
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
|
||||
|
||||
@@ -20,10 +20,10 @@ import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.model.Generation;
|
||||
import org.springframework.ai.chat.messages.AssistantMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.model.Generation;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.minimax.MiniMaxChatModel;
|
||||
import org.springframework.ai.minimax.MiniMaxChatOptions;
|
||||
@@ -57,14 +57,16 @@ class FunctionCallbackWithPlainFunctionBeanIT {
|
||||
RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class))
|
||||
.withUserConfiguration(Config.class);
|
||||
|
||||
// FIXME: multiple function calls may stop prematurely due to model performance
|
||||
@Test
|
||||
void functionCallTest() {
|
||||
contextRunner.withPropertyValues("spring.ai.minimax.chat.options.model=abab6-chat").run(context -> {
|
||||
contextRunner.withPropertyValues("spring.ai.minimax.chat.options.model=abab6.5s-chat").run(context -> {
|
||||
|
||||
MiniMaxChatModel chatModel = context.getBean(MiniMaxChatModel.class);
|
||||
|
||||
// Test weatherFunction
|
||||
UserMessage userMessage = new UserMessage("What's the weather like in San Francisco, Tokyo, and Paris?");
|
||||
UserMessage userMessage = new UserMessage(
|
||||
"What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius.");
|
||||
|
||||
ChatResponse response = chatModel.call(new Prompt(List.of(userMessage),
|
||||
MiniMaxChatOptions.builder().withFunction("weatherFunction").build()));
|
||||
@@ -86,12 +88,13 @@ class FunctionCallbackWithPlainFunctionBeanIT {
|
||||
|
||||
@Test
|
||||
void functionCallWithPortableFunctionCallingOptions() {
|
||||
contextRunner.withPropertyValues("spring.ai.minimax.chat.options.model=abab6-chat").run(context -> {
|
||||
contextRunner.withPropertyValues("spring.ai.minimax.chat.options.model=abab6.5s-chat").run(context -> {
|
||||
|
||||
MiniMaxChatModel chatModel = context.getBean(MiniMaxChatModel.class);
|
||||
|
||||
// Test weatherFunction
|
||||
UserMessage userMessage = new UserMessage("What's the weather like in San Francisco, Tokyo, and Paris?");
|
||||
UserMessage userMessage = new UserMessage(
|
||||
"What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius.");
|
||||
|
||||
PortableFunctionCallingOptions functionOptions = FunctionCallingOptions.builder()
|
||||
.withFunction("weatherFunction")
|
||||
@@ -103,14 +106,16 @@ class FunctionCallbackWithPlainFunctionBeanIT {
|
||||
});
|
||||
}
|
||||
|
||||
// FIXME: multiple function calls may stop prematurely due to model performance
|
||||
@Test
|
||||
void streamFunctionCallTest() {
|
||||
contextRunner.withPropertyValues("spring.ai.minimax.chat.options.model=abab6-chat").run(context -> {
|
||||
contextRunner.withPropertyValues("spring.ai.minimax.chat.options.model=abab6.5s-chat").run(context -> {
|
||||
|
||||
MiniMaxChatModel chatModel = context.getBean(MiniMaxChatModel.class);
|
||||
|
||||
// Test weatherFunction
|
||||
UserMessage userMessage = new UserMessage("What's the weather like in San Francisco, Tokyo, and Paris?");
|
||||
UserMessage userMessage = new UserMessage(
|
||||
"What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius.");
|
||||
|
||||
Flux<ChatResponse> response = chatModel.stream(new Prompt(List.of(userMessage),
|
||||
MiniMaxChatOptions.builder().withFunction("weatherFunction").build()));
|
||||
|
||||
@@ -20,10 +20,10 @@ import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.model.Generation;
|
||||
import org.springframework.ai.chat.messages.AssistantMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.model.Generation;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.minimax.MiniMaxChatModel;
|
||||
import org.springframework.ai.minimax.MiniMaxChatOptions;
|
||||
@@ -57,11 +57,12 @@ public class FunctionCallbackWrapperIT {
|
||||
|
||||
@Test
|
||||
void functionCallTest() {
|
||||
contextRunner.withPropertyValues("spring.ai.minimax.chat.options.model=abab6-chat").run(context -> {
|
||||
contextRunner.withPropertyValues("spring.ai.minimax.chat.options.model=abab6.5s-chat").run(context -> {
|
||||
|
||||
MiniMaxChatModel chatModel = context.getBean(MiniMaxChatModel.class);
|
||||
|
||||
UserMessage userMessage = new UserMessage("What's the weather like in San Francisco, Tokyo, and Paris?");
|
||||
UserMessage userMessage = new UserMessage(
|
||||
"What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius.");
|
||||
|
||||
ChatResponse response = chatModel.call(
|
||||
new Prompt(List.of(userMessage), MiniMaxChatOptions.builder().withFunction("WeatherInfo").build()));
|
||||
@@ -75,11 +76,12 @@ public class FunctionCallbackWrapperIT {
|
||||
|
||||
@Test
|
||||
void streamFunctionCallTest() {
|
||||
contextRunner.withPropertyValues("spring.ai.minimax.chat.options.model=abab6-chat").run(context -> {
|
||||
contextRunner.withPropertyValues("spring.ai.minimax.chat.options.model=abab6.5s-chat").run(context -> {
|
||||
|
||||
MiniMaxChatModel chatModel = context.getBean(MiniMaxChatModel.class);
|
||||
|
||||
UserMessage userMessage = new UserMessage("What's the weather like in San Francisco, Tokyo, and Paris?");
|
||||
UserMessage userMessage = new UserMessage(
|
||||
"What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius.");
|
||||
|
||||
Flux<ChatResponse> response = chatModel.stream(
|
||||
new Prompt(List.of(userMessage), MiniMaxChatOptions.builder().withFunction("WeatherInfo").build()));
|
||||
|
||||
@@ -20,8 +20,8 @@ import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.embedding.EmbeddingResponse;
|
||||
import org.springframework.ai.minimax.MiniMaxChatModel;
|
||||
|
||||
@@ -34,7 +34,7 @@ public class MockWeatherService implements Function<MockWeatherService.Request,
|
||||
* Weather Function request.
|
||||
*/
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
@JsonClassDescription("Weather API request")
|
||||
@JsonClassDescription("Get the weather in location")
|
||||
public record Request(@JsonProperty(required = true,
|
||||
value = "location") @JsonPropertyDescription("The city and state e.g. San Francisco, CA") String location,
|
||||
@JsonProperty(required = true, value = "lat") @JsonPropertyDescription("The city latitude") double lat,
|
||||
|
||||
Reference in New Issue
Block a user