diff --git a/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/agentscope/AgentOpenAIChatFormatter.java b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/agentscope/AgentOpenAIChatFormatter.java new file mode 100644 index 0000000..8f56602 --- /dev/null +++ b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/agentscope/AgentOpenAIChatFormatter.java @@ -0,0 +1,31 @@ +package com.easyagents.agent.runtime.agentscope; + +import io.agentscope.core.formatter.openai.OpenAIChatFormatter; +import io.agentscope.core.formatter.openai.dto.OpenAIContentPart; +import io.agentscope.core.formatter.openai.dto.OpenAIMessage; +import io.agentscope.core.message.Msg; + +import java.util.List; + +/** + * 为 OpenAI-compatible 模型补充 system content 内容块数组兼容能力。 + */ +public final class AgentOpenAIChatFormatter extends OpenAIChatFormatter { + + /** + * 将 AgentScope 消息转换为 OpenAI 消息,并规范 system 文本的 content 格式。 + * + * @param messages AgentScope 消息 + * @return OpenAI 请求消息 + */ + @Override + protected List doFormat(List messages) { + List formattedMessages = super.doFormat(messages); + for (OpenAIMessage message : formattedMessages) { + if ("system".equals(message.getRole()) && message.getContent() instanceof String text) { + message.setContent(List.of(OpenAIContentPart.text(text))); + } + } + return formattedMessages; + } +} diff --git a/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/agentscope/AgentScopeModelFactory.java b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/agentscope/AgentScopeModelFactory.java index 15437d0..2f5ccdb 100644 --- a/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/agentscope/AgentScopeModelFactory.java +++ b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/agentscope/AgentScopeModelFactory.java @@ -6,6 +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 io.agentscope.core.formatter.openai.DeepSeekFormatter; import io.agentscope.core.formatter.openai.GLMFormatter; import io.agentscope.core.model.*; @@ -153,6 +154,9 @@ public class AgentScopeModelFactory implements AgentModelFactory { .stream(Boolean.TRUE.equals(options.getStream())) .httpTransport(httpTransportProvider.getTransport(modelSpec.getHttpVersionPolicy(), baseUrl)) .generateOptions(options); + if (modelSpec.getSystemContentFormat() == AgentSystemContentFormat.TEXT_PARTS) { + builder.formatter(new AgentOpenAIChatFormatter()); + } return builder.build(); } diff --git a/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/model/AgentModelSpec.java b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/model/AgentModelSpec.java index 59bc84a..ac33ff1 100644 --- a/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/model/AgentModelSpec.java +++ b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/model/AgentModelSpec.java @@ -16,6 +16,7 @@ public class AgentModelSpec { private boolean supportImage; private boolean supportImageBase64Only; private AgentHttpVersionPolicy httpVersionPolicy = AgentHttpVersionPolicy.AUTO; + private AgentSystemContentFormat systemContentFormat = AgentSystemContentFormat.STRING; private Map metadata = new LinkedHashMap<>(); /** @@ -162,6 +163,26 @@ public class AgentModelSpec { this.httpVersionPolicy = httpVersionPolicy == null ? AgentHttpVersionPolicy.AUTO : httpVersionPolicy; } + /** + * 获取 OpenAI-compatible 请求中的 system content 格式。 + * + * @return system content 格式 + */ + public AgentSystemContentFormat getSystemContentFormat() { + return systemContentFormat; + } + + /** + * 设置 OpenAI-compatible 请求中的 system content 格式。 + * + * @param systemContentFormat system content 格式 + */ + public void setSystemContentFormat(AgentSystemContentFormat systemContentFormat) { + this.systemContentFormat = systemContentFormat == null + ? AgentSystemContentFormat.STRING + : systemContentFormat; + } + /** * 获取元数据。 * diff --git a/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/model/AgentSystemContentFormat.java b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/model/AgentSystemContentFormat.java new file mode 100644 index 0000000..f06fd45 --- /dev/null +++ b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/model/AgentSystemContentFormat.java @@ -0,0 +1,13 @@ +package com.easyagents.agent.runtime.model; + +/** + * OpenAI-compatible 请求中 system 消息的 content 格式。 + */ +public enum AgentSystemContentFormat { + + /** 使用字符串 content,保持 OpenAI Chat Completions 的常规格式。 */ + STRING, + + /** 使用仅包含 text 内容块的数组 content。 */ + TEXT_PARTS +} diff --git a/easy-agents-agent-runtime/src/test/java/com/easyagents/agent/runtime/agentscope/AgentOpenAIChatFormatterTest.java b/easy-agents-agent-runtime/src/test/java/com/easyagents/agent/runtime/agentscope/AgentOpenAIChatFormatterTest.java new file mode 100644 index 0000000..3e4816e --- /dev/null +++ b/easy-agents-agent-runtime/src/test/java/com/easyagents/agent/runtime/agentscope/AgentOpenAIChatFormatterTest.java @@ -0,0 +1,62 @@ +package com.easyagents.agent.runtime.agentscope; + +import com.easyagents.agent.runtime.model.AgentGenerationOptions; +import com.easyagents.agent.runtime.model.AgentModelProviderType; +import com.easyagents.agent.runtime.model.AgentModelSpec; +import com.easyagents.agent.runtime.model.AgentSystemContentFormat; +import io.agentscope.core.formatter.openai.dto.OpenAIContentPart; +import io.agentscope.core.formatter.openai.dto.OpenAIMessage; +import io.agentscope.core.message.Msg; +import io.agentscope.core.message.MsgRole; +import io.agentscope.core.model.OpenAIChatModel; +import org.junit.Assert; +import org.junit.Test; + +import java.lang.reflect.Field; +import java.util.List; + +/** + * Agent OpenAI Chat 消息格式兼容测试。 + */ +public class AgentOpenAIChatFormatterTest { + + /** + * 验证 system 文本转换为 text 内容块数组,普通 user 文本保持字符串。 + */ + @Test + public void shouldConvertOnlySystemTextToContentParts() { + AgentOpenAIChatFormatter formatter = new AgentOpenAIChatFormatter(); + List messages = formatter.format(List.of( + Msg.builder().role(MsgRole.SYSTEM).textContent("system prompt").build(), + Msg.builder().role(MsgRole.USER).textContent("hello").build())); + + Assert.assertTrue(messages.get(0).getContent() instanceof List); + List contentParts = messages.get(0).getContentAsList(); + Assert.assertEquals(1, contentParts.size()); + Assert.assertEquals("text", contentParts.get(0).getType()); + Assert.assertEquals("system prompt", contentParts.get(0).getText()); + Assert.assertEquals("hello", messages.get(1).getContent()); + } + + /** + * 验证内容块数组策略会安装 EasyAgents 的 OpenAI Formatter。 + * + * @throws Exception 反射读取 Formatter 失败时抛出 + */ + @Test + public void textPartsPolicyShouldInstallAgentOpenAIFormatter() throws Exception { + AgentModelSpec spec = new AgentModelSpec(); + spec.setProviderType(AgentModelProviderType.OPENAI_COMPATIBLE); + spec.setModelName("vlm-test"); + spec.setBaseUrl("http://model.example.com/v1"); + spec.setApiKey("test-key"); + spec.setSystemContentFormat(AgentSystemContentFormat.TEXT_PARTS); + + OpenAIChatModel model = (OpenAIChatModel) new AgentScopeModelFactory() + .create(spec, new AgentGenerationOptions()); + Field formatterField = OpenAIChatModel.class.getDeclaredField("formatter"); + formatterField.setAccessible(true); + + Assert.assertTrue(formatterField.get(model) instanceof AgentOpenAIChatFormatter); + } +}