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 @@ -29,6 +29,7 @@
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
Expand All @@ -41,6 +42,8 @@
public class DashScopeChatFormatter
extends AbstractBaseFormatter<DashScopeMessage, DashScopeResponse, DashScopeRequest> {

private static final Map<String, String> EPHEMERAL_CACHE_CONTROL = Map.of("type", "ephemeral");

private final DashScopeMessageConverter messageConverter;
private final DashScopeResponseParser responseParser;
private final DashScopeToolsHelper toolsHelper;
Expand Down Expand Up @@ -168,4 +171,37 @@ public DashScopeRequest buildRequest(

return request;
}

/**
* Apply cache control to DashScope messages.
*
* <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.
*
* @param messages the list of formatted DashScope messages
*/
public void applyCacheControl(List<DashScopeMessage> messages) {
if (messages == null || messages.isEmpty()) {
return;
}
for (DashScopeMessage msg : messages) {
if ("system".equals(msg.getRole()) && msg.getCacheControl() == null) {
msg.setCacheControl(EPHEMERAL_CACHE_CONTROL);
}
}
DashScopeMessage lastMsg = messages.get(messages.size() - 1);
if (lastMsg.getCacheControl() == null) {
lastMsg.setCacheControl(EPHEMERAL_CACHE_CONTROL);
}
}

/**
* Get the ephemeral cache control constant.
*
* @return unmodifiable map representing ephemeral cache control
*/
static Map<String, String> getEphemeralCacheControl() {
return EPHEMERAL_CACHE_CONTROL;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import io.agentscope.core.message.AudioBlock;
import io.agentscope.core.message.ContentBlock;
import io.agentscope.core.message.ImageBlock;
import io.agentscope.core.message.MessageMetadataKeys;
import io.agentscope.core.message.Msg;
import io.agentscope.core.message.MsgRole;
import io.agentscope.core.message.TextBlock;
Expand Down Expand Up @@ -64,11 +65,17 @@ public DashScopeMessageConverter(Function<List<ContentBlock>, String> toolResult
* @return The converted DashScopeMessage
*/
public DashScopeMessage convertToMessage(Msg msg, boolean useMultimodalFormat) {
DashScopeMessage result;
if (useMultimodalFormat) {
return convertToMultimodalContent(msg);
result = convertToMultimodalContent(msg);
} else {
return convertToSimpleContent(msg);
result = convertToSimpleContent(msg);
}

// Apply cache_control from message metadata if manually marked
applyCacheControlFromMetadata(msg, result);

return result;
}

/**
Expand Down Expand Up @@ -237,4 +244,20 @@ private String extractTextContent(Msg msg) {
.map(block -> ((TextBlock) block).getText())
.reduce("", (a, b) -> a.isEmpty() ? b : a + "\n" + b);
}

/**
* Apply cache_control from Msg metadata to the converted DashScopeMessage.
*
* @param msg the source message with metadata
* @param result the converted DashScope message
*/
private void applyCacheControlFromMetadata(Msg msg, DashScopeMessage result) {
if (msg.getMetadata() == null) {
return;
}
Object cacheFlag = msg.getMetadata().get(MessageMetadataKeys.CACHE_CONTROL);
if (Boolean.TRUE.equals(cacheFlag)) {
result.setCacheControl(DashScopeChatFormatter.getEphemeralCacheControl());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
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 @@ -363,4 +364,29 @@ private static class MessageGroup {
this.messages = messages;
}
}

/**
* Apply cache control to DashScope messages.
*
* <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.
*
* @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);
}
}
DashScopeMessage lastMsg = messages.get(messages.size() - 1);
if (lastMsg.getCacheControl() == null) {
lastMsg.setCacheControl(ephemeral);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.fasterxml.jackson.core.type.TypeReference;
import io.agentscope.core.util.JsonUtils;
import java.util.List;
import java.util.Map;

/**
* DashScope message DTO.
Expand Down Expand Up @@ -80,6 +81,10 @@ public class DashScopeMessage {
@JsonProperty("reasoning_content")
private String reasoningContent;

/** Cache control configuration for prompt caching. */
@JsonProperty("cache_control")
private Map<String, String> cacheControl;

public DashScopeMessage() {}

public String getRole() {
Expand Down Expand Up @@ -177,6 +182,14 @@ public void setReasoningContent(String reasoningContent) {
this.reasoningContent = reasoningContent;
}

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

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

public static Builder builder() {
return new Builder();
}
Expand Down Expand Up @@ -219,6 +232,11 @@ public Builder reasoningContent(String reasoningContent) {
return this;
}

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

public DashScopeMessage build() {
return message;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import io.agentscope.core.model.ToolSchema;
import java.time.Instant;
import java.util.List;
import java.util.Map;

/**
* Base formatter for OpenAI Chat Completion HTTP API.
Expand All @@ -41,6 +42,8 @@
public abstract class OpenAIBaseFormatter
extends AbstractBaseFormatter<OpenAIMessage, OpenAIResponse, OpenAIRequest> {

private static final Map<String, String> EPHEMERAL_CACHE_CONTROL = Map.of("type", "ephemeral");

protected final OpenAIMessageConverter messageConverter;
protected final OpenAIResponseParser responseParser;

Expand Down Expand Up @@ -165,4 +168,37 @@ public OpenAIRequest buildRequest(

return request;
}

/**
* Apply cache control to OpenAI messages.
*
* <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.
*
* @param messages the list of formatted OpenAI messages
*/
public void applyCacheControl(List<OpenAIMessage> messages) {
if (messages == null || messages.isEmpty()) {
return;
}
for (OpenAIMessage msg : messages) {
if ("system".equals(msg.getRole()) && msg.getCacheControl() == null) {
msg.setCacheControl(EPHEMERAL_CACHE_CONTROL);
}
}
OpenAIMessage lastMsg = messages.get(messages.size() - 1);
if (lastMsg.getCacheControl() == null) {
lastMsg.setCacheControl(EPHEMERAL_CACHE_CONTROL);
}
}

/**
* Get the ephemeral cache control constant.
*
* @return unmodifiable map representing ephemeral cache control
*/
static Map<String, String> getEphemeralCacheControl() {
return EPHEMERAL_CACHE_CONTROL;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import io.agentscope.core.message.Base64Source;
import io.agentscope.core.message.ContentBlock;
import io.agentscope.core.message.ImageBlock;
import io.agentscope.core.message.MessageMetadataKeys;
import io.agentscope.core.message.Msg;
import io.agentscope.core.message.MsgRole;
import io.agentscope.core.message.Source;
Expand Down Expand Up @@ -75,16 +76,23 @@ public OpenAIMessageConverter(
*/
public OpenAIMessage convertToMessage(Msg msg, boolean hasMediaContent) {
// Check if SYSTEM message contains tool result - treat as TOOL role
OpenAIMessage result;
if (msg.getRole() == MsgRole.SYSTEM && msg.hasContentBlocks(ToolResultBlock.class)) {
return convertToolMessage(msg);
result = convertToolMessage(msg);
} else {
result =
switch (msg.getRole()) {
case SYSTEM -> convertSystemMessage(msg);
case USER -> convertUserMessage(msg, hasMediaContent);
case ASSISTANT -> convertAssistantMessage(msg);
case TOOL -> convertToolMessage(msg);
};
}

return switch (msg.getRole()) {
case SYSTEM -> convertSystemMessage(msg);
case USER -> convertUserMessage(msg, hasMediaContent);
case ASSISTANT -> convertAssistantMessage(msg);
case TOOL -> convertToolMessage(msg);
};
// Apply cache_control from message metadata if manually marked
applyCacheControlFromMetadata(msg, result);

return result;
}

/**
Expand Down Expand Up @@ -468,4 +476,20 @@ private String convertVideoSourceToUrl(Source source) {
private String detectAudioFormat(String mediaType) {
return OpenAIConverterUtils.detectAudioFormat(mediaType);
}

/**
* Apply cache_control from Msg metadata to the converted OpenAIMessage.
*
* @param msg the source message with metadata
* @param result the converted OpenAI message
*/
private void applyCacheControlFromMetadata(Msg msg, OpenAIMessage result) {
if (msg.getMetadata() == null) {
return;
}
Object cacheFlag = msg.getMetadata().get(MessageMetadataKeys.CACHE_CONTROL);
if (Boolean.TRUE.equals(cacheFlag)) {
result.setCacheControl(OpenAIBaseFormatter.getEphemeralCacheControl());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
import java.util.Map;

/**
* OpenAI message DTO.
Expand Down Expand Up @@ -93,6 +94,10 @@ public class OpenAIMessage {
@JsonProperty("refusal")
private String refusal;

/** Cache control configuration for prompt caching. */
@JsonProperty("cache_control")
private Map<String, String> cacheControl;

public OpenAIMessage() {}

public String getRole() {
Expand Down Expand Up @@ -159,6 +164,14 @@ public void setRefusal(String refusal) {
this.refusal = refusal;
}

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

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

/**
* Get content as String (for text-only messages).
*
Expand Down Expand Up @@ -259,6 +272,11 @@ public Builder refusal(String refusal) {
return this;
}

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

public OpenAIMessage build() {
OpenAIMessage result = message;
message = new OpenAIMessage();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,30 @@ private MessageMetadataKeys() {
* }</pre>
*/
public static final String STRUCTURED_OUTPUT = "_structured_output";

/**
* Metadata key to mark a message for prompt caching.
*
* <p>When set to {@code true}, the formatter will add <code>cache_control:
* {"type": "ephemeral"}</code> to this message during formatting. This allows users to manually
* mark specific
* messages for caching, independent of the automatic cache control strategy configured via
* {@link io.agentscope.core.model.GenerateOptions#getCacheControl()}.
*
* <p>Manually marked messages take priority over the automatic strategy — they will not be
* overwritten.
*
* <p><b>Type:</b> Boolean
* <p><b>Example:</b>
* <pre>{@code
* Map<String, Object> metadata = new HashMap<>();
* metadata.put(MessageMetadataKeys.CACHE_CONTROL, true);
* Msg msg = Msg.builder()
* .role(MsgRole.USER)
* .textContent("Important context to cache...")
* .metadata(metadata)
* .build();
* }</pre>
*/
public static final String CACHE_CONTROL = "_cache_control";
}
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,15 @@ private Flux<ChatResponse> streamWithHttpClient(
// Apply thinking mode if enabled
applyThinkingMode(request, effectiveOptions);

// Apply cache control if enabled (adds cache_control to system msgs + last msg)
if (Boolean.TRUE.equals(effectiveOptions.getCacheControl())) {
if (formatter instanceof DashScopeChatFormatter chatFmt) {
chatFmt.applyCacheControl(request.getInput().getMessages());
} else if (formatter instanceof DashScopeMultiAgentFormatter multiFmt) {
multiFmt.applyCacheControl(request.getInput().getMessages());
}
}

// Set endpoint type for endpoint selection
request.setEndpointType(endpointType);

Expand Down
Loading
Loading