This guide outlines the necessary changes to migrate your code from the previous message handling structure (using the
Message class) to the new structure introduced to support multi-modal content and
tool calls, based on the abstract ChatMessage class.
- Message Class Removed: The old
Messageclass has been removed. - ChatMessage Hierarchy: A new abstract base class
ChatMessagehas been introduced. Specific message types now extend this class:SystemMessageUserMessageAssistantMessageToolMessage
- Multi-Modal Content: Messages (primarily
UserMessage,AssistantMessage,ToolMessage) now use aList<ContentChunk>for theircontentfield to support text, images, etc. TheContentChunkinterface has implementations likeTextChunk,ImageURLChunk, etc.getTextContent()helper methods were introduced to prevent having to loop overContentChunks to findTextChunks
- Tool Calls:
AssistantMessagenow includes aList<ToolCall> toolCallsfield, and aToolMessagemessage type was introduced to support function calling/tools. - Streaming Deltas: The
DeltaChoiceclass (used in streaming responses viaMessageChunk) now contains aDeltaMessageobject (accessible viagetDelta()). ThisDeltaMessageobject, in turn, holds the partial delta fields:role,content(asList<ContentChunk>), andtoolCalls.
The messages field in ChatCompletionRequest now expects List<ChatMessage> instead of List<Message>.
Before:
import nl.dannyj.mistral.models.completion.Message;
// ...
List<Message> messages = ...;
ChatCompletionRequest request = ChatCompletionRequest.builder()
.messages(messages)
// ...
.build();After:
import nl.dannyj.mistral.models.completion.ChatMessage;
// ...
List<ChatMessage> messages = ...; // See MessageListBuilder changes below
ChatCompletionRequest request = ChatCompletionRequest.builder()
.messages(messages)
// ...
.build();The MessageListBuilder now works with ChatMessage and its subclasses.
Before:
import nl.dannyj.mistral.models.completion.Message;
import nl.dannyj.mistral.builders.MessageListBuilder;
// ...
List<Message> messages = new MessageListBuilder()
.system("System prompt")
.user("User query")
.assistant("Assistant response")
.build();After:
import nl.dannyj.mistral.models.completion.ChatMessage;
import nl.dannyj.mistral.builders.MessageListBuilder;
// ...
List<ChatMessage> messages = new MessageListBuilder()
.system("System prompt") // Creates SystemMessage
.user("User query") // Creates UserMessage
.assistant("Assistant response") // Creates AssistantMessage (text only)
// .assistant(listOfToolCalls) // Use this overload for tool calls
// .tool("Tool result", "tool_call_id_123") // Creates ToolMessage
.build();
// Alternatively, add pre-constructed messages:
// messages = new MessageListBuilder()
// .message(new SystemMessage("System prompt"))
// .message(new UserMessage("User query"))
// .message(AssistantMessage.fromText("Assistant response")) // Factory method
// .build();The message field within the Choice object (obtained from ChatCompletionResponse.getChoices()) is now specifically
an AssistantMessage.
Before:
ChatCompletionResponse response = client.createChatCompletion(request);
Message responseMsg = response.getChoices().get(0).getMessage();
String content = responseMsg.getContent();
System.out.println(content);After:
import nl.dannyj.mistral.models.completion.message.AssistantMessage;
import nl.dannyj.mistral.models.completion.content.ContentChunk;
import nl.dannyj.mistral.models.completion.content.TextChunk;
// ...
ChatCompletionResponse response = client.createChatCompletion(request);
AssistantMessage responseMsg = response.getChoices().get(0).getMessage();
// Extract text content (handle potential multi-modal content)
String textContent = "";
if (responseMsg.getContent() != null) {
for (ContentChunk chunk : responseMsg.getContent()) {
if (chunk instanceof TextChunk textChunk) {
textContent += textChunk.getText(); // Append text from TextChunks
}
// Handle other chunk types (ImageURLChunk, etc.) if necessary
}
}
System.out.println(textContent);
// Check for tool calls if applicable
if (responseMsg.getToolCalls() != null) {
// Process tool calls
}The onChunkReceived method receives MessageChunk. The structure within MessageChunk.getChoices().get(0) (which is
a DeltaChoice) has changed. Access role, content, and toolCalls directly from the DeltaChoice object.
Before:
client.createChatCompletionStream(request, new ChatCompletionChunkCallback() {
@Override
public void onChunkReceived(MessageChunk chunk) {
// Old structure assumed delta was nested in a Message object
String contentDelta = chunk.getChoices().get(0).getMessage().getContent();
if (contentDelta != null) {
System.out.print(contentDelta);
}
}
// ... onComplete, onError
});After:
import nl.dannyj.mistral.models.completion.DeltaChoice;
import nl.dannyj.mistral.models.completion.message.DeltaMessage; // <-- Add this import
import nl.dannyj.mistral.models.completion.message.MessageChunk; // <-- Updated import path
import nl.dannyj.mistral.models.completion.content.ContentChunk;
import nl.dannyj.mistral.models.completion.content.TextChunk;
// ...
client.createChatCompletionStream(request, new ChatCompletionChunkCallback() {
@Override
public void onChunkReceived(MessageChunk chunk) {
if (chunk.getChoices() == null || chunk.getChoices().isEmpty()) return;
DeltaChoice deltaChoice = chunk.getChoices().get(0);
DeltaMessage delta = deltaChoice.getDelta(); // <-- Get the DeltaMessage
// Check for text content delta using the convenience method
String textDelta = delta.getTextContent();
if (textDelta != null) {
System.out.print(textDelta);
}
}
// ... onComplete, onError
});The Choice class, which is part of the ChatCompletionResponse.getChoices(), has the following breaking changes:
- The
finishReasonfield is now of typeFinishReason(an enum) instead of a rawString. - Before:
String finishReason = choice.getFinishReason(); - After:
FinishReason finishReason = choice.getFinishReason(); - To get the underlying string value (e.g., "stop", "length"), you can call
finishReason.getReason().
Example: Handling finishReason (within the context of iterating choices from ChatCompletionResponse)
Before(inside loop processing choice):
String reason = choice.getFinishReason();
if ("stop".equals(reason)) {
// ...
}After(inside loop processing choice):
import nl.dannyj.mistral.models.completion.FinishReason; // Ensure import
// ...
FinishReason reason = choice.getFinishReason();
if (reason == FinishReason.STOP) {
// ...
}
// Or, to compare with the string value:
// if ("stop".equals(reason.getReason())) { ... }- The
logProbsfield (previously aString) has been removed from theChoiceclass as it is no longer returned by the API.