Skip to content
Open
Show file tree
Hide file tree
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 @@ -16,6 +16,7 @@
package io.agentscope.core.formatter.dashscope;

import io.agentscope.core.formatter.AbstractBaseFormatter;
import io.agentscope.core.formatter.dashscope.dto.DashScopeContentPart;
import io.agentscope.core.formatter.dashscope.dto.DashScopeInput;
import io.agentscope.core.formatter.dashscope.dto.DashScopeMessage;
import io.agentscope.core.formatter.dashscope.dto.DashScopeParameters;
Expand Down Expand Up @@ -173,11 +174,15 @@ public DashScopeRequest buildRequest(
}

/**
* Apply cache control to DashScope messages.
* Apply cache control to DashScope messages at the content block level.
*
* <p>Adds <code>cache_control: {"type": "ephemeral"}</code> to all system messages and the last
* message in the list. Messages that already have cache_control set (e.g., via manual metadata
* marking) will not be overwritten.
* <p>Per the DashScope API specification, {@code cache_control} must be placed inside content
* blocks (within the {@code content} array), not at the message level. This method converts
* string content to array format when needed and sets {@code cache_control} on the last content
* block of each target message.
*
* <p>Target messages: all system messages and the last message in the list. Messages whose last
* content block already has {@code cache_control} set will not be overwritten.
*
* @param messages the list of formatted DashScope messages
*/
Expand All @@ -186,14 +191,48 @@ public void applyCacheControl(List<DashScopeMessage> messages) {
return;
}
for (DashScopeMessage msg : messages) {
if ("system".equals(msg.getRole()) && msg.getCacheControl() == null) {
msg.setCacheControl(EPHEMERAL_CACHE_CONTROL);
if ("system".equals(msg.getRole())) {
applyCacheControlToContentBlock(msg);
}
}
DashScopeMessage lastMsg = messages.get(messages.size() - 1);
if (lastMsg.getCacheControl() == null) {
lastMsg.setCacheControl(EPHEMERAL_CACHE_CONTROL);
applyCacheControlToContentBlock(lastMsg);
}

/**
* Apply ephemeral cache_control to the last content block of the given message.
* If content is a plain string, it is first converted to array format.
* Skips if the last content block already has cache_control set.
*/
static void applyCacheControlToContentBlock(DashScopeMessage msg) {
List<DashScopeContentPart> parts = ensureContentArray(msg);
if (parts.isEmpty()) {
return;
}
DashScopeContentPart lastPart = parts.get(parts.size() - 1);
if (lastPart.getCacheControl() == null) {
lastPart.setCacheControl(EPHEMERAL_CACHE_CONTROL);
}
Comment on lines +203 to +215
}

/**
* Ensure the message content is in array format ({@code List<DashScopeContentPart>}).
* If content is a plain string, converts it to {@code [{"type":"text","text":"..."}]}.
*
* @return the content part list (never null, may be empty)
*/
@SuppressWarnings("unchecked")
static List<DashScopeContentPart> ensureContentArray(DashScopeMessage msg) {
Object content = msg.getContent();
if (content instanceof List) {
return (List<DashScopeContentPart>) content;
}
List<DashScopeContentPart> parts = new ArrayList<>();
if (content instanceof String text) {
parts.add(DashScopeContentPart.text(text));
}
msg.setContent(parts);
return parts;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,9 @@ private String extractTextContent(Msg msg) {
}

/**
* Apply cache_control from Msg metadata to the converted DashScopeMessage.
* Apply cache_control from Msg metadata to the converted DashScopeMessage at content block
* level. When the metadata flag is set, this converts the message content to array format
* (if needed) and sets cache_control on the last content block.
*
* @param msg the source message with metadata
* @param result the converted DashScope message
Expand All @@ -260,7 +262,7 @@ private void applyCacheControlFromMetadata(Msg msg, DashScopeMessage result) {
}
Object cacheFlag = msg.getMetadata().get(MessageMetadataKeys.CACHE_CONTROL);
if (Boolean.TRUE.equals(cacheFlag)) {
result.setCacheControl(DashScopeChatFormatter.getEphemeralCacheControl());
DashScopeChatFormatter.applyCacheControlToContentBlock(result);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
* DashScope formatter for multi-agent conversations.
Expand Down Expand Up @@ -365,27 +364,27 @@ private static class MessageGroup {
}

/**
* Apply cache control to DashScope messages.
* Apply cache control to DashScope messages at the content block level.
*
* <p>Adds <code>cache_control: {"type": "ephemeral"}</code> to all system messages and the last
* message in the list. Messages that already have cache_control set (e.g., via manual metadata
* marking) will not be overwritten.
* <p>Per the DashScope API specification, {@code cache_control} must be placed inside content
* blocks (within the {@code content} array), not at the message level. This method delegates
* to {@link DashScopeChatFormatter#applyCacheControlToContentBlock(DashScopeMessage)} for each
* target message.
*
* <p>Target messages: all system messages and the last message in the list.
*
* @param messages the list of formatted DashScope messages
*/
public void applyCacheControl(List<DashScopeMessage> messages) {
if (messages == null || messages.isEmpty()) {
return;
}
Map<String, String> ephemeral = DashScopeChatFormatter.getEphemeralCacheControl();
for (DashScopeMessage msg : messages) {
if ("system".equals(msg.getRole()) && msg.getCacheControl() == null) {
msg.setCacheControl(ephemeral);
if ("system".equals(msg.getRole())) {
DashScopeChatFormatter.applyCacheControlToContentBlock(msg);
}
}
DashScopeMessage lastMsg = messages.get(messages.size() - 1);
if (lastMsg.getCacheControl() == null) {
lastMsg.setCacheControl(ephemeral);
}
DashScopeChatFormatter.applyCacheControlToContentBlock(lastMsg);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
import java.util.Map;

/**
* DashScope content part DTO for multimodal messages.
Expand Down Expand Up @@ -79,6 +80,10 @@ public class DashScopeContentPart {
@JsonProperty("total_pixels")
private Integer totalPixels;

/** Cache control configuration for prompt caching (placed at content block level per DashScope API spec). */
@JsonProperty("cache_control")
private Map<String, String> cacheControl;

public DashScopeContentPart() {}

public String getText() {
Expand Down Expand Up @@ -153,6 +158,14 @@ public void setTotalPixels(Integer totalPixels) {
this.totalPixels = totalPixels;
}

public Map<String, String> getCacheControl() {
return cacheControl;
}

public void setCacheControl(Map<String, String> cacheControl) {
this.cacheControl = cacheControl;
}

/**
* Get video as URL string.
*
Expand Down Expand Up @@ -290,6 +303,11 @@ public Builder totalPixels(Integer totalPixels) {
return this;
}

public Builder cacheControl(Map<String, String> cacheControl) {
part.setCacheControl(cacheControl);
return this;
}

public DashScopeContentPart build() {
return part;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package io.agentscope.core.formatter.openai;

import io.agentscope.core.formatter.AbstractBaseFormatter;
import io.agentscope.core.formatter.openai.dto.OpenAIContentPart;
import io.agentscope.core.formatter.openai.dto.OpenAIMessage;
import io.agentscope.core.formatter.openai.dto.OpenAIRequest;
import io.agentscope.core.formatter.openai.dto.OpenAIResponse;
Expand All @@ -24,6 +25,7 @@
import io.agentscope.core.model.ToolChoice;
import io.agentscope.core.model.ToolSchema;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

Expand Down Expand Up @@ -170,11 +172,15 @@ public OpenAIRequest buildRequest(
}

/**
* Apply cache control to OpenAI messages.
* Apply cache control to OpenAI messages at the content block level.
*
* <p>Adds <code>cache_control: {"type": "ephemeral"}</code> to all system messages and the last
* message in the list. Messages that already have cache_control set (e.g., via manual metadata
* marking) will not be overwritten.
* <p>Per the DashScope API specification (which also applies to the OpenAI-compatible protocol),
* {@code cache_control} must be placed inside content blocks (within the {@code content} array),
* not at the message level. This method converts string content to array format when needed and
* sets {@code cache_control} on the last content block of each target message.
*
* <p>Target messages: all system messages and the last message in the list. Messages whose last
* content block already has {@code cache_control} set will not be overwritten.
*
* @param messages the list of formatted OpenAI messages
*/
Expand All @@ -183,14 +189,48 @@ public void applyCacheControl(List<OpenAIMessage> messages) {
return;
}
for (OpenAIMessage msg : messages) {
if ("system".equals(msg.getRole()) && msg.getCacheControl() == null) {
msg.setCacheControl(EPHEMERAL_CACHE_CONTROL);
if ("system".equals(msg.getRole())) {
applyCacheControlToContentBlock(msg);
}
}
OpenAIMessage lastMsg = messages.get(messages.size() - 1);
if (lastMsg.getCacheControl() == null) {
lastMsg.setCacheControl(EPHEMERAL_CACHE_CONTROL);
applyCacheControlToContentBlock(lastMsg);
}

/**
* Apply ephemeral cache_control to the last content block of the given message.
* If content is a plain string, it is first converted to array format.
* Skips if the last content block already has cache_control set.
*/
static void applyCacheControlToContentBlock(OpenAIMessage msg) {
List<OpenAIContentPart> parts = ensureContentArray(msg);
if (parts.isEmpty()) {
return;
}
OpenAIContentPart lastPart = parts.get(parts.size() - 1);
if (lastPart.getCacheControl() == null) {
lastPart.setCacheControl(EPHEMERAL_CACHE_CONTROL);
Comment on lines +201 to +212
}
}

/**
* Ensure the message content is in array format ({@code List<OpenAIContentPart>}).
* If content is a plain string, converts it to {@code [{"type":"text","text":"..."}]}.
*
* @return the content part list (never null, may be empty)
*/
@SuppressWarnings("unchecked")
static List<OpenAIContentPart> ensureContentArray(OpenAIMessage msg) {
Object content = msg.getContent();
if (content instanceof List) {
return (List<OpenAIContentPart>) content;
}
List<OpenAIContentPart> parts = new ArrayList<>();
if (content instanceof String text) {
parts.add(OpenAIContentPart.text(text));
}
msg.setContent(parts);
return parts;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,9 @@ private String detectAudioFormat(String mediaType) {
}

/**
* Apply cache_control from Msg metadata to the converted OpenAIMessage.
* Apply cache_control from Msg metadata to the converted OpenAIMessage at content block level.
* When the metadata flag is set, this converts the message content to array format (if needed)
* and sets cache_control on the last content block.
*
* @param msg the source message with metadata
* @param result the converted OpenAI message
Expand All @@ -480,7 +482,7 @@ private void applyCacheControlFromMetadata(Msg msg, OpenAIMessage result) {
}
Object cacheFlag = msg.getMetadata().get(MessageMetadataKeys.CACHE_CONTROL);
if (Boolean.TRUE.equals(cacheFlag)) {
result.setCacheControl(OpenAIBaseFormatter.getEphemeralCacheControl());
OpenAIBaseFormatter.applyCacheControlToContentBlock(result);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Map;

/**
* OpenAI content part DTO for multimodal messages.
Expand Down Expand Up @@ -65,6 +66,10 @@ public class OpenAIContentPart {
@JsonProperty("video_url")
private OpenAIVideoUrl videoUrl;

/** Cache control configuration for prompt caching (placed at content block level per API spec). */
@JsonProperty("cache_control")
private Map<String, String> cacheControl;

public OpenAIContentPart() {}

public String getType() {
Expand Down Expand Up @@ -107,6 +112,14 @@ public void setVideoUrl(OpenAIVideoUrl videoUrl) {
this.videoUrl = videoUrl;
}

public Map<String, String> getCacheControl() {
return cacheControl;
}

public void setCacheControl(Map<String, String> cacheControl) {
this.cacheControl = cacheControl;
}

/**
* Create a text content part.
*
Expand Down Expand Up @@ -206,6 +219,11 @@ public Builder videoUrl(OpenAIVideoUrl videoUrl) {
return this;
}

public Builder cacheControl(Map<String, String> cacheControl) {
part.setCacheControl(cacheControl);
return this;
}

public OpenAIContentPart build() {
OpenAIContentPart result = part;
part = new OpenAIContentPart();
Expand Down
Loading
Loading