Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,11 @@
/**
* Converts AgentScope Msg objects to OpenAI DTO types (for HTTP API).
*
* <p>This class handles all message role conversions including system, user, assistant, and tool
* messages. It supports multimodal content (text, images, audio) and tool calling functionality.
* <p>
* This class handles all message role conversions including system, user,
* assistant, and tool
* messages. It supports multimodal content (text, images, audio) and tool
* calling functionality.
*/
public class OpenAIMessageConverter {

Expand All @@ -60,7 +63,7 @@ public class OpenAIMessageConverter {
/**
* Create an OpenAIMessageConverter with required dependency functions.
*
* @param textExtractor Function to extract text content from Msg
* @param textExtractor Function to extract text content from Msg
* @param toolResultConverter Function to convert tool result blocks to strings
*/
public OpenAIMessageConverter(
Expand All @@ -70,10 +73,14 @@ public OpenAIMessageConverter(
this.toolResultConverter = toolResultConverter;
}

private String sanitizeMessageName(String name) {
return name.replaceAll("[^a-zA-Z0-9_-]", "_");
}

/**
* Convert single Msg to OpenAI DTO Message.
*
* @param msg The message to convert
* @param msg The message to convert
* @param hasMediaContent Whether the message contains media (images/audio)
* @return OpenAIMessage for OpenAI API
*/
Expand Down Expand Up @@ -115,15 +122,15 @@ private OpenAIMessage convertSystemMessage(Msg msg) {
/**
* Convert user message with support for multimodal content.
*
* @param msg The user message
* @param msg The user message
* @param hasMediaContent Whether the message contains media
* @return OpenAIMessage
*/
private OpenAIMessage convertUserMessage(Msg msg, boolean hasMediaContent) {
OpenAIMessage.Builder builder = OpenAIMessage.builder().role("user");

if (msg.getName() != null) {
builder.name(msg.getName());
builder.name(sanitizeMessageName(msg.getName()));
}

List<ContentBlock> blocks = msg.getContent();
Expand Down Expand Up @@ -311,8 +318,10 @@ private OpenAIMessage convertAssistantMessage(Msg msg) {

String textContent = textExtractor.apply(msg);
// Qwen3 and similar models in thinking mode may produce assistant messages with
// reasoning_content + tool_calls but null content. Due to @JsonInclude(NON_NULL),
// a null content would be omitted from the JSON, causing strict OpenAI-compatible
// reasoning_content + tool_calls but null content. Due to
// @JsonInclude(NON_NULL),
// a null content would be omitted from the JSON, causing strict
// OpenAI-compatible
// APIs (e.g. vLLM) to reject with "Field required: input.messages.N.content".
// Always ensure content is present — use the actual text or fall back to "".
if (textContent != null && !textContent.isEmpty()) {
Expand Down Expand Up @@ -350,7 +359,7 @@ private OpenAIMessage convertAssistantMessage(Msg msg) {
}

if (msg.getName() != null) {
builder.name(msg.getName());
builder.name(sanitizeMessageName(msg.getName()));
}

// Handle tool calls
Expand Down Expand Up @@ -517,7 +526,7 @@ private String detectAudioFormat(String mediaType) {
/**
* Apply cache_control from Msg metadata to the converted OpenAIMessage.
*
* @param msg the source message with metadata
* @param msg the source message with metadata
* @param result the converted OpenAI message
*/
private void applyCacheControlFromMetadata(Msg msg, OpenAIMessage result) {
Expand Down
Loading