fix: 统一 OpenAI 消息内容块数组格式

- 将 system、user、assistant、tool 及历史上下文 content 统一为数组

- 保留 DeepSeek、GLM 专用格式规则并补充多模态与工具消息测试
This commit is contained in:
2026-07-28 12:24:38 +08:00
parent c48d9a9da6
commit a7e89cee3d
8 changed files with 259 additions and 41 deletions

View File

@@ -0,0 +1,24 @@
package com.easyagents.agent.runtime.agentscope;
import io.agentscope.core.formatter.openai.DeepSeekFormatter;
import io.agentscope.core.formatter.openai.dto.OpenAIMessage;
import io.agentscope.core.message.Msg;
import java.util.List;
/**
* 保留 DeepSeek 专用规则并将全部消息 content 规范为内容块数组。
*/
public final class AgentDeepSeekChatFormatter extends DeepSeekFormatter {
/**
* 转换 DeepSeek 消息并在供应商规则之后统一 content 格式。
*
* @param messages AgentScope 消息
* @return OpenAI 请求消息
*/
@Override
protected List<OpenAIMessage> doFormat(List<Msg> messages) {
return AgentOpenAIChatFormatter.normalizeContent(super.doFormat(messages));
}
}

View File

@@ -0,0 +1,24 @@
package com.easyagents.agent.runtime.agentscope;
import io.agentscope.core.formatter.openai.GLMFormatter;
import io.agentscope.core.formatter.openai.dto.OpenAIMessage;
import io.agentscope.core.message.Msg;
import java.util.List;
/**
* 保留 GLM 专用规则并将全部消息 content 规范为内容块数组。
*/
public final class AgentGLMChatFormatter extends GLMFormatter {
/**
* 转换 GLM 消息并在供应商规则之后统一 content 格式。
*
* @param messages AgentScope 消息
* @return OpenAI 请求消息
*/
@Override
protected List<OpenAIMessage> doFormat(List<Msg> messages) {
return AgentOpenAIChatFormatter.normalizeContent(super.doFormat(messages));
}
}

View File

@@ -8,22 +8,37 @@ import io.agentscope.core.message.Msg;
import java.util.List;
/**
* 为 OpenAI-compatible 模型补充 system content 内容块数组兼容能力。
* 为 OpenAI-compatible 模型补充全部消息 content 内容块数组兼容能力。
*/
public final class AgentOpenAIChatFormatter extends OpenAIChatFormatter {
/**
* 将 AgentScope 消息转换为 OpenAI 消息,并规范 system 文本的 content 格式。
* 将 AgentScope 消息转换为 OpenAI 消息,并规范全部角色的 content 格式。
*
* @param messages AgentScope 消息
* @return OpenAI 请求消息
*/
@Override
protected List<OpenAIMessage> doFormat(List<Msg> messages) {
List<OpenAIMessage> formattedMessages = super.doFormat(messages);
return normalizeContent(super.doFormat(messages));
}
/**
* 将 OpenAI 消息中的 content 统一规范为内容块数组。
*
* @param formattedMessages 已完成供应商规则转换的 OpenAI 消息
* @return content 已规范为数组的原消息列表
*/
static List<OpenAIMessage> normalizeContent(List<OpenAIMessage> formattedMessages) {
for (OpenAIMessage message : formattedMessages) {
if ("system".equals(message.getRole()) && message.getContent() instanceof String text) {
Object content = message.getContent();
if (content instanceof String text) {
message.setContent(List.of(OpenAIContentPart.text(text)));
} else if (content == null) {
message.setContent(List.of(OpenAIContentPart.text("")));
} else if (!(content instanceof List<?>)) {
throw new IllegalStateException(
"Unsupported OpenAI message content type: " + content.getClass().getName());
}
}
return formattedMessages;

View File

@@ -6,7 +6,7 @@ import com.easyagents.agent.runtime.model.AgentHttpVersionPolicy;
import com.easyagents.agent.runtime.model.AgentModelFactory;
import com.easyagents.agent.runtime.model.AgentModelProviderType;
import com.easyagents.agent.runtime.model.AgentModelSpec;
import com.easyagents.agent.runtime.model.AgentSystemContentFormat;
import com.easyagents.agent.runtime.model.AgentMessageContentFormat;
import io.agentscope.core.formatter.openai.DeepSeekFormatter;
import io.agentscope.core.formatter.openai.GLMFormatter;
import io.agentscope.core.model.*;
@@ -154,7 +154,7 @@ public class AgentScopeModelFactory implements AgentModelFactory<Model> {
.stream(Boolean.TRUE.equals(options.getStream()))
.httpTransport(httpTransportProvider.getTransport(modelSpec.getHttpVersionPolicy(), baseUrl))
.generateOptions(options);
if (modelSpec.getSystemContentFormat() == AgentSystemContentFormat.TEXT_PARTS) {
if (modelSpec.getMessageContentFormat() == AgentMessageContentFormat.TEXT_PARTS) {
builder.formatter(new AgentOpenAIChatFormatter());
}
return builder.build();
@@ -210,7 +210,9 @@ public class AgentScopeModelFactory implements AgentModelFactory<Model> {
.endpointPath(modelSpec.getEndpointPath())
.stream(Boolean.TRUE.equals(options.getStream()))
.httpTransport(httpTransportProvider.getTransport(modelSpec.getHttpVersionPolicy(), baseUrl))
.formatter(new DeepSeekFormatter())
.formatter(modelSpec.getMessageContentFormat() == AgentMessageContentFormat.TEXT_PARTS
? new AgentDeepSeekChatFormatter()
: new DeepSeekFormatter())
.generateOptions(options);
return builder.build();
}
@@ -231,7 +233,9 @@ public class AgentScopeModelFactory implements AgentModelFactory<Model> {
.endpointPath(modelSpec.getEndpointPath())
.stream(Boolean.TRUE.equals(options.getStream()))
.httpTransport(httpTransportProvider.getTransport(modelSpec.getHttpVersionPolicy(), baseUrl))
.formatter(new GLMFormatter())
.formatter(modelSpec.getMessageContentFormat() == AgentMessageContentFormat.TEXT_PARTS
? new AgentGLMChatFormatter()
: new GLMFormatter())
.generateOptions(options);
return builder.build();
}

View File

@@ -0,0 +1,13 @@
package com.easyagents.agent.runtime.model;
/**
* OpenAI-compatible 请求中消息 content 的格式策略。
*/
public enum AgentMessageContentFormat {
/** 使用 AgentScope 默认格式,纯文本为字符串,多模态内容为数组。 */
STANDARD,
/** 将全部角色的 content 规范为内容块数组。 */
TEXT_PARTS
}

View File

@@ -16,7 +16,7 @@ public class AgentModelSpec {
private boolean supportImage;
private boolean supportImageBase64Only;
private AgentHttpVersionPolicy httpVersionPolicy = AgentHttpVersionPolicy.AUTO;
private AgentSystemContentFormat systemContentFormat = AgentSystemContentFormat.STRING;
private AgentMessageContentFormat messageContentFormat = AgentMessageContentFormat.STANDARD;
private Map<String, Object> metadata = new LinkedHashMap<>();
/**
@@ -164,23 +164,23 @@ public class AgentModelSpec {
}
/**
* 获取 OpenAI-compatible 请求中的 system content 格式。
* 获取 OpenAI-compatible 请求中的消息 content 格式。
*
* @return system content 格式
* @return 消息 content 格式
*/
public AgentSystemContentFormat getSystemContentFormat() {
return systemContentFormat;
public AgentMessageContentFormat getMessageContentFormat() {
return messageContentFormat;
}
/**
* 设置 OpenAI-compatible 请求中的 system content 格式。
* 设置 OpenAI-compatible 请求中的消息 content 格式。
*
* @param systemContentFormat system content 格式
* @param messageContentFormat 消息 content 格式
*/
public void setSystemContentFormat(AgentSystemContentFormat systemContentFormat) {
this.systemContentFormat = systemContentFormat == null
? AgentSystemContentFormat.STRING
: systemContentFormat;
public void setMessageContentFormat(AgentMessageContentFormat messageContentFormat) {
this.messageContentFormat = messageContentFormat == null
? AgentMessageContentFormat.STANDARD
: messageContentFormat;
}
/**

View File

@@ -1,13 +0,0 @@
package com.easyagents.agent.runtime.model;
/**
* OpenAI-compatible 请求中 system 消息的 content 格式。
*/
public enum AgentSystemContentFormat {
/** 使用字符串 content保持 OpenAI Chat Completions 的常规格式。 */
STRING,
/** 使用仅包含 text 内容块的数组 content。 */
TEXT_PARTS
}